From 058fcb97a358a972d34a4cabd45773e249e15cf0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 17:51:54 +0800 Subject: [PATCH 001/281] multiple render camera supports for 3dsmax --- openpype/hosts/max/api/lib_rendersettings.py | 11 ++-------- .../hosts/max/plugins/create/create_render.py | 22 ++++++++++++++----- .../max/plugins/publish/collect_render.py | 5 +++++ .../plugins/publish/submit_max_deadline.py | 11 ++++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 91e4a5bf9b..db8dee3340 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -35,15 +35,8 @@ class RenderSettings(object): ) def set_render_camera(self, selection): - for sel in selection: - # to avoid Attribute Error from pymxs wrapper - found = False - if rt.classOf(sel) in rt.Camera.classes: - found = True - rt.viewport.setCamera(sel) - break - if not found: - raise RuntimeError("Camera not found") + # to avoid Attribute Error from pymxs wrapper + return rt.viewport.setCamera(selection[0]) def render_output(self, container): folder = rt.maxFilePath diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 5ad895b86e..ec5635b81b 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -14,7 +14,10 @@ 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) + sel_obj = [ + c for c in rt.Objects + if rt.classOf(c) in rt.Camera.classes] + file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename @@ -24,11 +27,20 @@ class CreateRender(plugin.MaxCreator): instance_data, pre_create_data) # type: CreatedInstance container_name = instance.data.get("instance_node") - container = rt.getNodeByName(container_name) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - for obj in sel_obj: - obj.parent = container + if self.selected_nodes: + # set viewport camera for + # rendering(mandatory for deadline) + sel_obj = [ + c for c in rt.getCurrentSelection() + if rt.classOf(c) in rt.Camera.classes] + + if not sel_obj: + raise RuntimeError("Please add at least one camera to the scene " + "before creating the render instance") + + RenderSettings().set_render_camera(sel_obj) # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) @@ -37,7 +49,5 @@ class CreateRender(plugin.MaxCreator): # Changing the Render Setup dialog settings should be done # with the actual Render Setup dialog in a closed state. - # set viewport camera for rendering(mandatory for deadline) - RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index db5c84fad9..cbb3a7b4d6 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -48,6 +48,10 @@ class CollectRender(pyblish.api.InstancePlugin): instance.name, asset_id) self.log.debug("version_doc: {0}".format(version_doc)) + sel_obj = [ + c for c in rt.Objects + if rt.classOf(c) in rt.Camera.classes] + version_int = 1 if version_doc: version_int += int(version_doc["name"]) @@ -78,6 +82,7 @@ class CollectRender(pyblish.api.InstancePlugin): "renderer": renderer, "source": filepath, "plugin": "3dsmax", + "cameras": sel_obj, "frameStart": int(rt.rendStart), "frameEnd": int(rt.rendEnd), "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 b6a30e36b7..ff5adc39ad 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -212,6 +212,17 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_data["RenderOutput"] = beauty_name # as 3dsmax has version with different languages plugin_data["Language"] = "ENU" + render_cameras = instance.data["cameras"] + if render_cameras: + for i, camera in enumerate(render_cameras): + cam_name = "Camera%s" % (i + 1) + plugin_data[cam_name] = camera.name + # set the default camera + plugin_data["Camera"] = render_cameras[0].name + # set empty camera of Camera 0 for the ' + # correct parameter submission + plugin_data["Camera0"] = None + renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] From 5fc037880fa7ddee5c65d63679d1db2810469411 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 19 Jun 2023 17:33:16 +0800 Subject: [PATCH 002/281] refactor the collector and deadline for multiple camera --- openpype/hosts/max/plugins/publish/collect_render.py | 2 +- .../modules/deadline/plugins/publish/submit_max_deadline.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index cbb3a7b4d6..8e39da0fbb 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -49,7 +49,7 @@ class CollectRender(pyblish.api.InstancePlugin): asset_id) self.log.debug("version_doc: {0}".format(version_doc)) sel_obj = [ - c for c in rt.Objects + c.name for c in rt.Objects if rt.classOf(c) in rt.Camera.classes] version_int = 1 diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index ff5adc39ad..365be7b07b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -216,9 +216,9 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if render_cameras: for i, camera in enumerate(render_cameras): cam_name = "Camera%s" % (i + 1) - plugin_data[cam_name] = camera.name - # set the default camera - plugin_data["Camera"] = render_cameras[0].name + plugin_data[cam_name] = camera + # set the default camera + plugin_data["Camera"] = camera # set empty camera of Camera 0 for the ' # correct parameter submission plugin_data["Camera0"] = None From 6b716b8ce92d5e0466ecb92c03aad86e920b8217 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:16:52 +0800 Subject: [PATCH 003/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index a1039b9309..45d7d7134d 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -155,8 +155,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() @@ -169,15 +169,15 @@ class RenderProducts(object): if name == "RsCryptomatte": render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, 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, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Arnold": @@ -186,7 +186,7 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_arnold_product( - output_file, name, start_frame, end_frame, img_fmt) + output_file, name, start_frame, end_frame, img_fmt) }) elif renderer in [ "V_Ray_6_Hotfix_3", @@ -198,8 +198,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) # noqa + output_file, name, start_frame, + end_frame, img_fmt) # noqa }) return render_dict From e96bada178f11d8f24d1036746a3f3e519e95b84 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:23:03 +0800 Subject: [PATCH 004/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 44 ++++++++++--------- openpype/hosts/max/api/lib_rendersettings.py | 2 +- .../hosts/max/plugins/create/create_render.py | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 45d7d7134d..433214935d 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -33,6 +33,7 @@ class RenderProducts(object): output_file, start_frame, end_frame, img_fmt ) } + def get_multiple_beauty(self, outputs, cameras): beauty_output_frames = dict() for output, camera in zip(outputs, cameras): @@ -68,9 +69,9 @@ class RenderProducts(object): for name in render_name: aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) ) }) elif renderer == "Redshift_Renderer": @@ -84,9 +85,9 @@ class RenderProducts(object): if name == "RsCryptomatte": aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) ) }) else: @@ -105,9 +106,9 @@ class RenderProducts(object): for name in render_name: aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_arnold_product( - filename, name, start_frame, - end_frame, ext) + self.get_expected_arnold_product( + filename, name, start_frame, + end_frame, ext) ) }) elif renderer in [ @@ -120,9 +121,9 @@ class RenderProducts(object): for name in render_name: aovs_frames.update({ f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) ) }) @@ -155,8 +156,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() @@ -169,15 +170,15 @@ class RenderProducts(object): if name == "RsCryptomatte": render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, 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, start_frame, - end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer == "Arnold": @@ -186,7 +187,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_arnold_product( - output_file, name, start_frame, end_frame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) elif renderer in [ "V_Ray_6_Hotfix_3", @@ -198,8 +200,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, start_frame, - end_frame, img_fmt) # noqa + output_file, name, start_frame, + end_frame, img_fmt) # noqa }) return render_dict diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 663f610e45..33cfc6dc4a 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -192,7 +192,7 @@ class RenderSettings(object): renderlayer = rt.batchRenderMgr.GetView(layer_no) # use camera name as renderlayer name renderlayer.name = cam - renderlayer.outputFilename ="{0}_{1}..{2}".format( + renderlayer.outputFilename = "{0}_{1}..{2}".format( output, cam, img_fmt) outputs.append(renderlayer.outputFilename) return outputs diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 64e97fb941..23397e1a98 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -17,7 +17,7 @@ class CreateRender(plugin.MaxCreator): file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename - num_of_renderlayer = rt.batchRenderMgr.numViews + num_of_renderlayer = rt.batchRenderMgr.numViews if num_of_renderlayer > 0: rt.batchRenderMgr.DeleteView(num_of_renderlayer) From 9ce0d97e48d4313b3d50d6edab36ab3608a98799 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:28:08 +0800 Subject: [PATCH 005/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 433214935d..d2d5fb08da 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -77,7 +77,7 @@ class RenderProducts(object): elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() if render_name: - rs_aov_files = rt.Execute("renderers.current.separateAovFiles") + rs_aov_files = rt.Execute("renderers.current.separateAovFiles") # noqa # this doesn't work, always returns False # rs_AovFiles = rt.RedShift_Renderer().separateAovFiles if ext == "exr" and not rs_aov_files: @@ -97,9 +97,8 @@ class RenderProducts(object): self.get_expected_render_elements( filename, name, start_frame, end_frame, ext) - ) - }) - + ) + }) elif renderer == "Arnold": render_name = self.get_arnold_product_name() if render_name: From b182f29c90dd8810ab130331ad370e58fc21ab38 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:32:25 +0800 Subject: [PATCH 006/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 24 ++++++-------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index d2d5fb08da..03dfcf3345 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -68,11 +68,9 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_render_elements( filename, name, start_frame, end_frame, ext) - ) }) elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() @@ -84,31 +82,25 @@ class RenderProducts(object): for name in render_name: if name == "RsCryptomatte": aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_render_elements( filename, name, start_frame, end_frame, ext) - ) }) else: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_render_elements( filename, name, start_frame, end_frame, ext) - ) }) elif renderer == "Arnold": render_name = self.get_arnold_product_name() if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_arnold_product( + f"{camera}_{name}": self.get_expected_arnold_product( filename, name, start_frame, end_frame, ext) - ) }) elif renderer in [ "V_Ray_6_Hotfix_3", @@ -119,11 +111,9 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": ( - self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) - ) + f"{camera}_{name}": self.get_expected_render_elements( + filename, name, start_frame, + end_frame, ext) }) return aovs_frames From 4c01e09ef9d3ae8f4fb67b724e9c227e9afaa7f1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:35:01 +0800 Subject: [PATCH 007/281] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 03dfcf3345..59417a39fa 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -68,7 +68,7 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_aovs( filename, name, start_frame, end_frame, ext) }) @@ -82,14 +82,14 @@ class RenderProducts(object): for name in render_name: if name == "RsCryptomatte": aovs_frames.update({ - f"{camera}_{name}": self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_aovs( filename, name, start_frame, end_frame, ext) }) else: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": self.get_expected_render_elements( + f"{camera}_{name}": self.get_expected_aovs( filename, name, start_frame, end_frame, ext) }) @@ -111,9 +111,9 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": self.get_expected_render_elements( - filename, name, start_frame, - end_frame, ext) + f"{camera}_{name}": self.get_expected_aovs( + filename, name, start_frame, + end_frame, ext) }) return aovs_frames @@ -144,7 +144,7 @@ class RenderProducts(object): if render_name: for name in render_name: render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) }) @@ -158,14 +158,14 @@ class RenderProducts(object): for name in render_name: if name == "RsCryptomatte": render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) }) else: for name in render_name: render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) }) @@ -188,7 +188,7 @@ class RenderProducts(object): if render_name: for name in render_name: render_dict.update({ - name: self.get_expected_render_elements( + name: self.get_expected_aovs( output_file, name, start_frame, end_frame, img_fmt) # noqa }) @@ -251,8 +251,8 @@ class RenderProducts(object): return render_name - def get_expected_render_elements(self, folder, name, - start_frame, end_frame, fmt): + def get_expected_aovs(self, folder, name, + start_frame, end_frame, fmt): """Get all the expected render element output files. """ render_elements = [] for f in range(start_frame, end_frame): From 9f90e3a1f15bba7e26303d3fafee5ef5d9bd238e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Jul 2023 19:35:57 +0800 Subject: [PATCH 008/281] hound fix --- 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 59417a39fa..0b8c53dfa0 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -98,7 +98,7 @@ class RenderProducts(object): if render_name: for name in render_name: aovs_frames.update({ - f"{camera}_{name}": self.get_expected_arnold_product( + f"{camera}_{name}": self.get_expected_arnold_product( # noqa filename, name, start_frame, end_frame, ext) }) From c81818d09509b6b85e3259e6bac362717aa1ca1c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 14 Jul 2023 20:25:49 +0800 Subject: [PATCH 009/281] add multi camera options for render creator --- openpype/hosts/max/api/lib_rendersettings.py | 6 +-- .../hosts/max/plugins/create/create_render.py | 19 ++++++++ .../max/plugins/publish/collect_render.py | 46 ++++++++++--------- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 33cfc6dc4a..18160c66a0 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -177,8 +177,8 @@ class RenderSettings(object): render_element_list.append(aov_name) return render_element_list - def create_batch_render_layer(self, container, - output_dir, cameras): + def batch_render_layer(self, container, + output_dir, cameras): outputs = list() output = os.path.join(output_dir, container) img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa @@ -186,7 +186,7 @@ class RenderSettings(object): camera = rt.getNodeByName(cam) layer_no = rt.batchRenderMgr.FindView(cam) renderlayer = None - if layer_no is None: + if layer_no == 0: renderlayer = rt.batchRenderMgr.CreateView(camera) else: renderlayer = rt.batchRenderMgr.GetView(layer_no) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 23397e1a98..617334753a 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.hosts.max.api.lib_rendersettings import RenderSettings @@ -17,6 +18,7 @@ class CreateRender(plugin.MaxCreator): file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename + instance_data["multiCamera"] = pre_create_data.get("multi_cam") num_of_renderlayer = rt.batchRenderMgr.numViews if num_of_renderlayer > 0: rt.batchRenderMgr.DeleteView(num_of_renderlayer) @@ -29,3 +31,20 @@ class CreateRender(plugin.MaxCreator): container_name = instance.data.get("instance_node") # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) + # TODO: create multiple camera options + if self.selected_nodes: + selected_nodes_name = [] + for sel in self.selected_nodes: + name = sel.name + selected_nodes_name.append(name) + RenderSettings().batch_render_layer( + container_name, filename, + selected_nodes_name) + + def get_pre_create_attr_defs(self): + attrs = super(CreateRender, self).get_pre_create_attr_defs() + return attrs + [ + BoolDef("multi_cam", + label="Multiple Cameras Submission", + default=False), + ] diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 736ffa5865..ca2f2f444f 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -26,22 +26,7 @@ class CollectRender(pyblish.api.InstancePlugin): file = rt.maxFileName current_file = os.path.join(folder, file) filepath = current_file.replace("\\", "/") - container_name = instance.data.get("instance_node") context.data['currentFile'] = current_file - cameras = instance.data.get("members") - sel_cam = [ - c.name for c in cameras - if rt.classOf(c) in rt.Camera.classes] - render_dir = os.path.dirname(rt.rendOutputFilename) - outputs = RenderSettings().create_batch_render_layer( - container_name, render_dir, sel_cam - ) - aov_outputs = RenderSettings().get_batch_render_elements( - container_name, render_dir, sel_cam - ) - files_aov = RenderProducts().get_multiple_beauty(outputs, cameras) - aovs = RenderProducts().get_multiple_aovs(outputs, cameras) - files_aov.update(aovs) asset = get_current_asset_name() files_by_aov = RenderProducts().get_beauty(instance.name) @@ -49,11 +34,33 @@ class CollectRender(pyblish.api.InstancePlugin): aovs = RenderProducts().get_aovs(instance.name) files_by_aov.update(aovs) + if instance.data.get("multiCamera"): + cameras = instance.data.get("members") + if not cameras: + raise RuntimeError("There should be at least" + " one renderable camera in container") + sel_cam = [ + c.name for c in cameras + if rt.classOf(c) in rt.Camera.classes] + container_name = instance.data.get("instance_node") + render_dir = os.path.dirname(rt.rendOutputFilename) + outputs = RenderSettings().batch_render_layer( + container_name, render_dir, sel_cam + ) + + instance.data["cameras"] = sel_cam + + files_by_aov = RenderProducts().get_multiple_beauty( + outputs, sel_cam) + aovs = RenderProducts().get_multiple_aovs( + outputs, sel_cam) + files_by_aov.update(aovs) + if "expectedFiles" not in instance.data: instance.data["expectedFiles"] = list() instance.data["files"] = list() - instance.data["expectedFiles"].append(files_aov) - instance.data["files"].append(files_aov) + instance.data["expectedFiles"].append(files_by_aov) + instance.data["files"].append(files_by_aov) img_format = RenderProducts().image_format() project_name = context.data["projectName"] @@ -94,13 +101,10 @@ class CollectRender(pyblish.api.InstancePlugin): "renderer": renderer, "source": filepath, "plugin": "3dsmax", - "cameras": sel_cam, "frameStart": int(rt.rendStart), "frameEnd": int(rt.rendEnd), "version": version_int, - "farm": True, - "renderoutput": outputs, - "aovoutput": aov_outputs + "farm": True } instance.data.update(data) From 22524c1d8473226b7687626b30b34ab237708fb5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 17:22:09 +0800 Subject: [PATCH 010/281] add option for multiple job and plugin infos --- openpype/hosts/max/api/lib_rendersettings.py | 21 ++-- .../deadline/abstract_submit_deadline.py | 25 ++++ .../plugins/publish/submit_max_deadline.py | 113 +++++++++++++++--- 3 files changed, 136 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 18160c66a0..f2294fbf95 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -160,7 +160,7 @@ class RenderSettings(object): return orig_render_elem def get_batch_render_elements(self, container, - output_dir, cameras): + output_dir, camera): render_element_list = list() output = os.path.join(output_dir, container) render_elem = rt.maxOps.GetCurRenderElementMgr() @@ -168,15 +168,20 @@ class RenderSettings(object): if render_elem_num < 0: return img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - for cam in cameras: - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - aov_name = "{0}_{1}_{2}..{3}".format( - output, cam, renderpass, img_fmt) - render_element_list.append(aov_name) + + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + aov_name = "{0}_{1}_{2}..{3}".format( + output, camera, renderpass, img_fmt) + render_element_list.append(aov_name) return render_element_list + def get_batch_render_output(self, camera): + target_layer_no = rt.batchRenderMgr.FindView(camera) + target_layer = rt.batchRenderMgr.GetView(target_layer_no) + return target_layer.outputFilename + def batch_render_layer(self, container, output_dir, cameras): outputs = list() diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 551a2f7373..de6babf555 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -592,6 +592,31 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, return file_path + def get_job_info_through_camera(self, camera=None): + """Get the job parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with job info. + """ + pass + + def get_plugin_info_through_camera(self, camera=None): + """Get the plugin parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with plugin info. + """ + pass + + def _use_published_name_for_multiples(self, data): + """Process the parameters submission for deadline when + user enables multi-cameras option. + Args: + job_info_list (list): A list of multiple job infos + plugin_info_list (list): A list of multiple plugin infos + """ + pass + def assemble_payload( self, job_info=None, plugin_info=None, aux_files=None): """Assemble payload data from its various parts. diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 365be7b07b..6bbc956f55 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -56,7 +56,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, cls.priority) cls.chuck_size = settings.get("chunk_size", cls.chunk_size) cls.group = settings.get("group", cls.group) - + # TODO: multiple camera instance, separate job infos def get_job_info(self): job_info = DeadlineJobInfo(Plugin="3dsmax") @@ -73,7 +73,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, src_filepath = context.data["currentFile"] src_filename = os.path.basename(src_filepath) - job_info.Name = "%s - %s" % (src_filename, instance.name) job_info.BatchName = src_filename job_info.Plugin = instance.data["plugin"] @@ -179,9 +178,19 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, } self.log.debug("Submitting 3dsMax render..") - payload = self._use_published_name(payload_data) - job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info)) + #TODO: multiple camera options + if instance.data.get("multiCamera"): + payload = self._use_published_name_for_multiples(payload_data) + job_infos, plugin_infos = payload + for job_info, plugin_info in zip(job_infos, plugin_infos): + self.log.debug(f"job_info: {job_info}") + self.log.debug(f"plugin_info: {plugin_info}") + submission = self.assemble_payload(job_info, plugin_info) + self.submit(submission) + else: + payload = self._use_published_name(payload_data) + job_info, plugin_info = payload + self.submit(self.assemble_payload(job_info, plugin_info)) def _use_published_name(self, data): instance = self._instance @@ -212,16 +221,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_data["RenderOutput"] = beauty_name # as 3dsmax has version with different languages plugin_data["Language"] = "ENU" - render_cameras = instance.data["cameras"] - if render_cameras: - for i, camera in enumerate(render_cameras): - cam_name = "Camera%s" % (i + 1) - plugin_data[cam_name] = camera - # set the default camera - plugin_data["Camera"] = camera - # set empty camera of Camera 0 for the ' - # correct parameter submission - plugin_data["Camera0"] = None renderer_class = get_current_renderer() @@ -250,6 +249,90 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return job_info, plugin_info + def get_job_info_through_camera(self, camera): + instance = self._instance + context = instance.context + job_info = copy.deepcopy(self.job_info) + exp = instance.data.get("expectedFiles") + + src_filepath = context.data["currentFile"] + src_filename = os.path.basename(src_filepath) + job_info.Name = "%s - %s - %s" % ( + src_filename, instance.name, camera) + for filepath in self._iter_expected_files(exp): + if camera not in filepath: + continue + job_info.OutputDirectory += os.path.dirname(filepath) + job_info.OutputFilename += os.path.basename(filepath) + + return job_info + # set the output filepath with the relative camera + + def get_plugin_info_through_camera(self, camera): + instance = self._instance + # set the target camera + plugin_info = copy.deepcopy(self.plugin_info) + plugin_data = {} + # set the output filepath with the relative camera + files = instance.data.get("expectedFiles") + if not files: + raise RuntimeError("No render elements found") + first_file = next(self._iter_expected_files(files)) + old_output_dir = os.path.dirname(first_file) + rgb_output = RenderSettings().get_batch_render_output(camera) # noqa + rgb_bname = os.path.basename(rgb_output) + dir = os.path.dirname(first_file) + beauty_name = f"{dir}/{rgb_bname}" + beauty_name = beauty_name.replace("\\", "/") + plugin_info["RenderOutput"] = beauty_name + renderer_class = get_current_renderer() + + renderer = str(renderer_class).split(":")[0] + 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 = RenderSettings().get_batch_render_elements( + instance.name, old_output_dir, camera + ) + 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_info["RenderElementOutputFilename%d" % i] = new_elem # noqa + + if camera: + # set the default camera + plugin_data["Camera"] = camera + + plugin_data["Camera0"] = None + + plugin_info.update(plugin_data) + return plugin_info + + def _use_published_name_for_multiples(self, data): + """Process the parameters submission for deadline when + user enables multi-cameras option. + Args: + job_info_list (list): A list of multiple job infos + plugin_info_list (list): A list of multiple plugin infos + """ + job_info_list = [] + plugin_info_list = [] + instance = self._instance + cameras = instance.data.get("cameras", []) + for cam in cameras: + job_info = self.get_job_info_through_camera(cam) + plugin_info = self.get_plugin_info_through_camera(cam) + job_info_list.append(job_info) + plugin_info_list.append(plugin_info) + + return job_info_list, plugin_info_list + def from_published_scene(self, replace_in_path=True): instance = self._instance if instance.data["renderer"] == "Redshift_Renderer": From 5ad81ea6bff0544cffdf7abe04a29eb64d8ec58c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 17:44:24 +0800 Subject: [PATCH 011/281] make sure camera parameters are being collected accurately --- .../plugins/publish/submit_max_deadline.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 6bbc956f55..23a2b4c679 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -131,11 +131,11 @@ 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) + if not instance.data.get("multiCamera"): + 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) return job_info @@ -178,7 +178,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, } self.log.debug("Submitting 3dsMax render..") - #TODO: multiple camera options + if instance.data.get("multiCamera"): payload = self._use_published_name_for_multiples(payload_data) job_infos, plugin_infos = payload @@ -306,9 +306,10 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, plugin_info["RenderElementOutputFilename%d" % i] = new_elem # noqa if camera: - # set the default camera + # set the default camera and target camera + # (weird parameters from max) plugin_data["Camera"] = camera - + plugin_data["Camera1"] = camera plugin_data["Camera0"] = None plugin_info.update(plugin_data) From f5c8994fa31039b0549eddaa7bbe5ec8e5c3cee8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 20:21:34 +0800 Subject: [PATCH 012/281] get the correct naming for all render outputs --- openpype/hosts/max/api/lib_renderproducts.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 5446e4fca3..863dccd99e 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -41,6 +41,8 @@ class RenderProducts(object): beauty_output_frames = dict() for output, camera in zip(outputs, cameras): filename, ext = os.path.splitext(output) + filename = filename.replace(".", "") + ext = ext.replace(".", "") start_frame = int(rt.rendStart) end_frame = int(rt.rendEnd) + 1 new_beauty = self.get_expected_beauty( @@ -57,6 +59,8 @@ class RenderProducts(object): aovs_frames = {} for output, camera in zip(outputs, cameras): filename, ext = os.path.splitext(output) + filename = filename.replace(".", "") + ext = ext.replace(".", "") start_frame = int(rt.rendStart) end_frame = int(rt.rendEnd) + 1 From 523ad0519dcbe52d4821d0981ddc4cc6962aaf60 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Jul 2023 20:44:29 +0800 Subject: [PATCH 013/281] get all beauty as expected files in a correct manner --- openpype/hosts/max/api/lib_renderproducts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 863dccd99e..eaf5015ba8 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -48,9 +48,10 @@ class RenderProducts(object): new_beauty = self.get_expected_beauty( filename, start_frame, end_frame, ext ) - beauty_output_frames = ({ + beauty_output = ({ f"{camera}_beauty": new_beauty }) + beauty_output_frames.update(beauty_output) return beauty_output_frames def get_multiple_aovs(self, outputs, cameras): From 3bb7f871d452fdef0e46db3a92518e8dd8d94afe Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Jul 2023 17:57:04 +0800 Subject: [PATCH 014/281] bug fix camera instance doesn't include anything --- .../deadline/plugins/publish/submit_publish_job.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 01a5c55286..1665a05f1e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -361,7 +361,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) @@ -472,6 +472,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, host_name = self.context.data["hostName"] subset = instance_data["subset"] cameras = instance_data.get("cameras", []) + self.log.info(f"camera: {cameras}") instances = [] # go through aovs in expected files for aov, files in exp_files[0].items(): @@ -497,7 +498,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, task[0].upper(), task[1:], subset[0].upper(), subset[1:]) - cam = [c for c in cameras if c in col.head] + cam = [c for c in cameras if c in cols[0].head] + self.log.debug(f"cam: {cam}") if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) @@ -898,6 +900,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, for v in values: instance_skeleton_data[v] = instance.data.get(v) + # if there are cameras, get the camera + # ]data for instance_skeleton_data + cameras = instance.data.get("cameras") + if cameras: + instance_skeleton_data["cameras"] = cameras + # look into instance data if representations are not having any # which are having tag `publish_on_farm` and include them for repre in instance.data.get("representations", []): From f349e720b09c78e6541eb7b10d6eb31f9020b425 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 20 Jul 2023 23:45:08 +0800 Subject: [PATCH 015/281] exporting camera scene through instanceplugin by subprocess --- openpype/hosts/max/api/lib_rendersettings.py | 18 +++ .../publish/save_scenes_for_cameras.py | 110 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 0a6f6569bf..2d453b9712 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -182,6 +182,24 @@ class RenderSettings(object): target_layer = rt.batchRenderMgr.GetView(target_layer_no) return target_layer.outputFilename + def batch_render_elements(self, camera): + target_layer_no = rt.batchRenderMgr.FindView(camera) + target_layer = rt.batchRenderMgr.GetView(target_layer_no) + outputfilename = target_layer.outputFilename + directory = os.path.dirname(outputfilename) + render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem_num = render_elem.NumRenderElements() + if render_elem_num < 0: + return + ext = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa + + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + aov_name = "{0}_{1}_{2}..{3}".format( + directory, camera, renderpass, ext) + render_elem.SetRenderElementFileName(i, aov_name) + def batch_render_layer(self, container, output_dir, cameras): outputs = list() diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py new file mode 100644 index 0000000000..d3357ba478 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -0,0 +1,110 @@ +import pyblish.api +import os +import sys +import tempfile + +from pymxs import runtime as rt +from openpype.lib import run_subprocess +from openpype.hosts.max.api.lib import get_max_version +from openpype.hosts.max.api.lib_rendersettings import RenderSettings +from openpype.hosts.max.api.lib_renderproducts import RenderProducts + + +class SaveScenesForCamera(pyblish.api.InstancePlugin): + """Save scene files for multiple cameras before + deadline submission + + """ + + label = "Save Scene files for cameras" + order = pyblish.api.ExtractorOrder - 0.48 + hosts = ["max"] + families = ["maxrender", "workfile"] + + def process(self, instance): + if not instance.data.get("multiCamera"): + self.log.debug("Skipping instance...") + return + current_folder = rt.maxFilePath + current_filename = rt.maxFileName + current_filepath = os.path.join(current_folder, current_filename) + camera_scene_files = [] + repres_list = [] + scripts = [] + filename, ext = os.path.splitext(current_filename) + fmt = RenderProducts().image_format() + cameras = instance.data.get("cameras") + if not cameras: + return + new_folder = "{}_{}".format(current_folder, filename) + os.makedirs(new_folder, exist_ok=True) + for camera in cameras: + new_output = RenderSettings().get_batch_render_output(camera) # noqa + new_output = new_output.replace("\\", "/") + new_filename = "{}_{}{}".format( + filename, camera, ext) + new_filepath = os.path.join(new_folder, new_filename) + new_filepath = new_filepath.replace("\\", "/") + camera_scene_files.append(new_filepath) + RenderSettings().batch_render_elements(camera) + rt.rendOutputFilename = new_output + rt.saveMaxFile(current_filepath) + script = (""" +from pymxs import runtime as rt +import os +new_filepath = "{new_filepath}" +new_output = "{new_output}" +camera = "{camera}" +rt.rendOutputFilename = new_output +directory = os.path.dirname(new_output) +render_elem = rt.maxOps.GetCurRenderElementMgr() +render_elem_num = render_elem.NumRenderElements() +if render_elem_num > 0: + ext = "{ext}" + for i in range(render_elem_num): + renderlayer_name = render_elem.GetRenderElement(i) + target, renderpass = str(renderlayer_name).split(":") + aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext + render_elem.SetRenderElementFileName(i, aov_name) +rt.saveMaxFile(new_filepath) + """).format(new_filepath=new_filepath, + new_output=new_output, + camera=camera, + ext=fmt) + scripts.append(script) + + max_version = get_max_version() + maxBatch_exe = os.path.join(os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "3dsmaxbatch") + maxBatch_exe = maxBatch_exe.replace("\\", "/") + if sys.platform == "windows": + maxBatch_exe += ".exe" + maxBatch_exe = os.path.normpath(maxBatch_exe) + with tempfile.TemporaryDirectory() as tmp_dir_name: + tmp_script_path = os.path.join(tmp_dir_name, "extract_scene_files.py") + log_file =os.path.join(tmp_dir_name, "fatal.log") + self.log.info("Using script file: {}".format(tmp_script_path)) + + with open(tmp_script_path, "wt") as tmp: + for script in scripts: + tmp.write(script+"\n") + tmp.write("rt.quitMax(quiet=True)"+"\n") + tmp.write("import time"+"\n") + tmp.write("time.sleep(3)") + + try: + current_filepath = current_filepath.replace("\\","/") + log_file = log_file.replace("\\", "/") + tmp_script_path = tmp_script_path.replace("\\","/") + run_subprocess([maxBatch_exe, tmp_script_path, + "-sceneFile", current_filepath]) + except RuntimeError: + self.log.debug("Checking the scene files existing or not") + + for camera_scene in camera_scene_files: + if not os.path.exists(camera_scene): + self.log.error("Camera scene files not existed yet!") + raise RuntimeError("MaxBatch.exe doesn't run as expected") + self.log.debug(f"Found Camera scene:{camera_scene}") + + if "sceneFiles" not in instance.data: + instance.data["sceneFiles"] = camera_scene_files From f4a864c0d2b0141f5cd81e40edb25b77374b22d7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 20 Jul 2023 23:50:29 +0800 Subject: [PATCH 016/281] cosmetic fix --- .../plugins/publish/save_scenes_for_cameras.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index d3357ba478..2abdb5dba1 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -74,27 +74,27 @@ rt.saveMaxFile(new_filepath) scripts.append(script) max_version = get_max_version() - maxBatch_exe = os.path.join(os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "3dsmaxbatch") + maxBatch_exe = os.path.join( + os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "3dsmaxbatch") maxBatch_exe = maxBatch_exe.replace("\\", "/") if sys.platform == "windows": maxBatch_exe += ".exe" maxBatch_exe = os.path.normpath(maxBatch_exe) with tempfile.TemporaryDirectory() as tmp_dir_name: - tmp_script_path = os.path.join(tmp_dir_name, "extract_scene_files.py") - log_file =os.path.join(tmp_dir_name, "fatal.log") + tmp_script_path = os.path.join( + tmp_dir_name, "extract_scene_files.py") self.log.info("Using script file: {}".format(tmp_script_path)) with open(tmp_script_path, "wt") as tmp: for script in scripts: - tmp.write(script+"\n") - tmp.write("rt.quitMax(quiet=True)"+"\n") - tmp.write("import time"+"\n") + tmp.write(script + "\n") + tmp.write("rt.quitMax(quiet=True)" + "\n") + tmp.write("import time" + "\n") tmp.write("time.sleep(3)") try: - current_filepath = current_filepath.replace("\\","/") - log_file = log_file.replace("\\", "/") - tmp_script_path = tmp_script_path.replace("\\","/") + current_filepath = current_filepath.replace("\\", "/") + tmp_script_path = tmp_script_path.replace("\\", "/") run_subprocess([maxBatch_exe, tmp_script_path, "-sceneFile", current_filepath]) except RuntimeError: From 7ae8b86c58ab061daa8b9acefab478adc14651fb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 20 Jul 2023 23:51:21 +0800 Subject: [PATCH 017/281] hound fix --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 2abdb5dba1..af47959f8f 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -64,7 +64,7 @@ if render_elem_num > 0: for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext + aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) """).format(new_filepath=new_filepath, From 3377150fe65342a421107fe71d08f72303cd0151 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 16:14:36 +0800 Subject: [PATCH 018/281] clean up the save_scene_camera code --- .../plugins/publish/save_scenes_for_cameras.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index af47959f8f..c6d264de32 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -29,7 +29,6 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): current_filename = rt.maxFileName current_filepath = os.path.join(current_folder, current_filename) camera_scene_files = [] - repres_list = [] scripts = [] filename, ext = os.path.splitext(current_filename) fmt = RenderProducts().image_format() @@ -88,9 +87,6 @@ rt.saveMaxFile(new_filepath) with open(tmp_script_path, "wt") as tmp: for script in scripts: tmp.write(script + "\n") - tmp.write("rt.quitMax(quiet=True)" + "\n") - tmp.write("import time" + "\n") - tmp.write("time.sleep(3)") try: current_filepath = current_filepath.replace("\\", "/") @@ -100,11 +96,23 @@ rt.saveMaxFile(new_filepath) except RuntimeError: self.log.debug("Checking the scene files existing or not") - for camera_scene in camera_scene_files: + for camera_scene, camera in zip(camera_scene_files, cameras): if not os.path.exists(camera_scene): self.log.error("Camera scene files not existed yet!") raise RuntimeError("MaxBatch.exe doesn't run as expected") self.log.debug(f"Found Camera scene:{camera_scene}") + instance.context.data["currentFile"] = camera_scene + representation = { + "name": "max", + "ext": "max", + "files": os.path.basename(camera_scene), + "stagingDir": new_folder, + "outputName": camera + } + self.log.debug(f"representation: {representation}") + if instance.data.get("representations") is None: + instance.data["representations"] = [] + instance.data["representations"].append(representation) if "sceneFiles" not in instance.data: instance.data["sceneFiles"] = camera_scene_files From 4144ca8e18fc2c5cf1e3b4971748cffa57ff3a9a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 16:45:48 +0800 Subject: [PATCH 019/281] clean up the save_scene_camera code --- .../publish/save_scenes_for_cameras.py | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index c6d264de32..2369cf78a3 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -22,13 +22,11 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): families = ["maxrender", "workfile"] def process(self, instance): - if not instance.data.get("multiCamera"): - self.log.debug("Skipping instance...") - return current_folder = rt.maxFilePath current_filename = rt.maxFileName current_filepath = os.path.join(current_folder, current_filename) camera_scene_files = [] + repres_list = [] scripts = [] filename, ext = os.path.splitext(current_filename) fmt = RenderProducts().image_format() @@ -94,25 +92,25 @@ rt.saveMaxFile(new_filepath) run_subprocess([maxBatch_exe, tmp_script_path, "-sceneFile", current_filepath]) except RuntimeError: - self.log.debug("Checking the scene files existing or not") + self.log.debug("Checking the scene files existing") for camera_scene, camera in zip(camera_scene_files, cameras): if not os.path.exists(camera_scene): self.log.error("Camera scene files not existed yet!") raise RuntimeError("MaxBatch.exe doesn't run as expected") self.log.debug(f"Found Camera scene:{camera_scene}") - instance.context.data["currentFile"] = camera_scene representation = { - "name": "max", - "ext": "max", - "files": os.path.basename(camera_scene), - "stagingDir": new_folder, - "outputName": camera + "name": camera, + "ext": "max", + "files": os.path.basename(camera_scene), + "stagingDir": os.path.dirname(camera_scene), + "outputName": camera } - self.log.debug(f"representation: {representation}") - if instance.data.get("representations") is None: - instance.data["representations"] = [] - instance.data["representations"].append(representation) + repres_list.append(representation) if "sceneFiles" not in instance.data: instance.data["sceneFiles"] = camera_scene_files + + if instance.data.get("representations") is None: + instance.data["representations"] = [] + instance.data["representations"] = (repres_list) From d62b410efbc3776598d7705c0ee223f672e4a994 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 16:57:51 +0800 Subject: [PATCH 020/281] clean up the save_scene_camera code --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 2369cf78a3..3031c0d4cf 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -113,4 +113,4 @@ rt.saveMaxFile(new_filepath) if instance.data.get("representations") is None: instance.data["representations"] = [] - instance.data["representations"] = (repres_list) + instance.data["representations"]= repres_list From 855143d217400b813f128b0804e599332dbe95d6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 17:11:26 +0800 Subject: [PATCH 021/281] clean up --- .../plugins/publish/save_scenes_for_cameras.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 3031c0d4cf..8aff9b58dd 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -11,8 +11,8 @@ from openpype.hosts.max.api.lib_renderproducts import RenderProducts class SaveScenesForCamera(pyblish.api.InstancePlugin): - """Save scene files for multiple cameras before - deadline submission + """Save scene files for multiple cameras without + editing the original scene before deadline submission """ @@ -26,7 +26,6 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): current_filename = rt.maxFileName current_filepath = os.path.join(current_folder, current_filename) camera_scene_files = [] - repres_list = [] scripts = [] filename, ext = os.path.splitext(current_filename) fmt = RenderProducts().image_format() @@ -99,18 +98,6 @@ rt.saveMaxFile(new_filepath) self.log.error("Camera scene files not existed yet!") raise RuntimeError("MaxBatch.exe doesn't run as expected") self.log.debug(f"Found Camera scene:{camera_scene}") - representation = { - "name": camera, - "ext": "max", - "files": os.path.basename(camera_scene), - "stagingDir": os.path.dirname(camera_scene), - "outputName": camera - } - repres_list.append(representation) if "sceneFiles" not in instance.data: instance.data["sceneFiles"] = camera_scene_files - - if instance.data.get("representations") is None: - instance.data["representations"] = [] - instance.data["representations"]= repres_list From 7ecb5ba056f2e5adf5f6ebbc6af19db16b207d56 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 17:14:23 +0800 Subject: [PATCH 022/281] hound fix --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 8aff9b58dd..918b6cda43 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -93,7 +93,7 @@ rt.saveMaxFile(new_filepath) except RuntimeError: self.log.debug("Checking the scene files existing") - for camera_scene, camera in zip(camera_scene_files, cameras): + for camera_scene in camera_scene_files: if not os.path.exists(camera_scene): self.log.error("Camera scene files not existed yet!") raise RuntimeError("MaxBatch.exe doesn't run as expected") From 0a7d24ba5f2f3b8dfaaeb7b0fbf8309c27ce5c38 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 18:27:00 +0800 Subject: [PATCH 023/281] use sys.executable to find the directory of maxBatch.exe --- openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 918b6cda43..0ccc69591a 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -5,7 +5,6 @@ import tempfile from pymxs import runtime as rt from openpype.lib import run_subprocess -from openpype.hosts.max.api.lib import get_max_version from openpype.hosts.max.api.lib_rendersettings import RenderSettings from openpype.hosts.max.api.lib_renderproducts import RenderProducts @@ -69,9 +68,8 @@ rt.saveMaxFile(new_filepath) ext=fmt) scripts.append(script) - max_version = get_max_version() maxBatch_exe = os.path.join( - os.getenv(f"ADSK_3DSMAX_x64_{max_version}"), "3dsmaxbatch") + os.path.dirname(sys.executable), "3dsmaxbatch") maxBatch_exe = maxBatch_exe.replace("\\", "/") if sys.platform == "windows": maxBatch_exe += ".exe" From 7692b95f1c113c8f2da8c0ae335398c0f7740a17 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 19:37:00 +0800 Subject: [PATCH 024/281] change the scene_path command in submit 3dsmax renders and make sure render elements have the correct path --- .../publish/save_scenes_for_cameras.py | 19 +++++++++++-------- .../plugins/publish/submit_max_deadline.py | 5 ++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 0ccc69591a..9561f53996 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -18,7 +18,7 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): label = "Save Scene files for cameras" order = pyblish.api.ExtractorOrder - 0.48 hosts = ["max"] - families = ["maxrender", "workfile"] + families = ["maxrender"] def process(self, instance): current_folder = rt.maxFilePath @@ -47,11 +47,13 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): script = (""" from pymxs import runtime as rt import os +filename = "{filename}" new_filepath = "{new_filepath}" new_output = "{new_output}" camera = "{camera}" rt.rendOutputFilename = new_output -directory = os.path.dirname(new_output) +directory = os.path.dirname(rt.rendOutputFilename) +directory = os.path.join(directory, filename) render_elem = rt.maxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() if render_elem_num > 0: @@ -62,18 +64,19 @@ if render_elem_num > 0: aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) - """).format(new_filepath=new_filepath, + """).format(filename=filename, + new_filepath=new_filepath, new_output=new_output, camera=camera, ext=fmt) scripts.append(script) - maxBatch_exe = os.path.join( + maxbatch_exe = os.path.join( os.path.dirname(sys.executable), "3dsmaxbatch") - maxBatch_exe = maxBatch_exe.replace("\\", "/") + maxbatch_exe = maxbatch_exe.replace("\\", "/") if sys.platform == "windows": - maxBatch_exe += ".exe" - maxBatch_exe = os.path.normpath(maxBatch_exe) + maxbatch_exe += ".exe" + maxbatch_exe = os.path.normpath(maxbatch_exe) with tempfile.TemporaryDirectory() as tmp_dir_name: tmp_script_path = os.path.join( tmp_dir_name, "extract_scene_files.py") @@ -86,7 +89,7 @@ rt.saveMaxFile(new_filepath) try: current_filepath = current_filepath.replace("\\", "/") tmp_script_path = tmp_script_path.replace("\\", "/") - run_subprocess([maxBatch_exe, tmp_script_path, + run_subprocess([maxbatch_exe, tmp_script_path, "-sceneFile", current_filepath]) except RuntimeError: self.log.debug("Checking the scene files existing") diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 5fed10a7c5..57f4353dcc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -12,7 +12,6 @@ from openpype.pipeline import ( legacy_io, OpenPypePyblishPluginMixin ) -from openpype.settings import get_project_settings from openpype.hosts.max.api.lib import ( get_current_renderer, get_multipass_setting @@ -272,8 +271,12 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance = self._instance # set the target camera plugin_info = copy.deepcopy(self.plugin_info) + plugin_data = {} # set the output filepath with the relative camera + for camera_scene_path in instance.data.get("sceneFiles"): + if camera in camera_scene_path: + plugin_data["SceneFile"] = camera_scene_path files = instance.data.get("expectedFiles") if not files: raise RuntimeError("No render elements found") From b8952629705de15b9fbcc2ca18bba16bd83b7014 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 20:51:16 +0800 Subject: [PATCH 025/281] if multiCamera enabled, the deadline submission wont use the published workfiles to render --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 6 +++++- 1 file changed, 5 insertions(+), 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 57f4353dcc..f3d873a1db 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -349,7 +349,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if instance.data["renderer"] == "Redshift_Renderer": self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False - return replace_in_path + + if instance.data["multiCamera"] == True: + 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 5893675dc0e96dbcef99c5dc89fabc5b9831a783 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 21 Jul 2023 20:53:35 +0800 Subject: [PATCH 026/281] some cosmetic 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 f3d873a1db..904732c4b4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -350,7 +350,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False - if instance.data["multiCamera"] == True: + if instance.data.get("multiCamera"): self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False return replace_in_path From 9801ad52a0e42fbc2a69e0b5c514453d0a6d4954 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 24 Jul 2023 18:30:58 +0800 Subject: [PATCH 027/281] make sure the renderpass subset name is correct when saving scenes for camera in subprocess --- .../max/plugins/publish/save_scenes_for_cameras.py | 2 +- .../deadline/plugins/publish/submit_max_deadline.py | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 9561f53996..9382a8f4b3 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -64,7 +64,7 @@ if render_elem_num > 0: aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) - """).format(filename=filename, + """).format(filename=instance.name, new_filepath=new_filepath, new_output=new_output, camera=camera, diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 904732c4b4..822962b23e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -303,10 +303,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance.name, old_output_dir, camera ) 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_info["RenderElementOutputFilename%d" % i] = new_elem # noqa + if camera in element: + elem_bname = os.path.basename(element) + new_elem = f"{dir}/{elem_bname}" + new_elem = new_elem.replace("/", "\\") + plugin_info["RenderElementOutputFilename%d" % i] = new_elem # noqa if camera: # set the default camera and target camera @@ -349,10 +350,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if instance.data["renderer"] == "Redshift_Renderer": self.log.debug("Using Redshift...published scene wont be used..") replace_in_path = False - - if instance.data.get("multiCamera"): - self.log.debug("Using Redshift...published scene wont be used..") - replace_in_path = False return replace_in_path @staticmethod From ed55100dd19d2f99d572a78999278b73a78b1c76 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Jul 2023 17:37:21 +0800 Subject: [PATCH 028/281] ondrej's comment --- .../publish/save_scenes_for_cameras.py | 3 --- .../deadline/abstract_submit_deadline.py | 25 ------------------ .../plugins/publish/submit_max_deadline.py | 26 ++++++++++++++++--- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 9382a8f4b3..79e9088ac7 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -99,6 +99,3 @@ rt.saveMaxFile(new_filepath) self.log.error("Camera scene files not existed yet!") raise RuntimeError("MaxBatch.exe doesn't run as expected") self.log.debug(f"Found Camera scene:{camera_scene}") - - if "sceneFiles" not in instance.data: - instance.data["sceneFiles"] = camera_scene_files diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index ae568c43e3..3fa427204b 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -531,31 +531,6 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, return replace_with_published_scene_path( self._instance, replace_in_path=replace_in_path) - def get_job_info_through_camera(self, camera=None): - """Get the job parameters for deadline submission when - multi-camera is enabled. - Args: - infos(dict): a dictionary with job info. - """ - pass - - def get_plugin_info_through_camera(self, camera=None): - """Get the plugin parameters for deadline submission when - multi-camera is enabled. - Args: - infos(dict): a dictionary with plugin info. - """ - pass - - def _use_published_name_for_multiples(self, data): - """Process the parameters submission for deadline when - user enables multi-cameras option. - Args: - job_info_list (list): A list of multiple job infos - plugin_info_list (list): A list of multiple plugin infos - """ - pass - def assemble_payload( self, job_info=None, plugin_info=None, aux_files=None): """Assemble payload data from its various parts. diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 822962b23e..78e570a780 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -164,7 +164,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, def process_submission(self): instance = self._instance - filepath = self.scene_path + filepath = instance.context.data["currentFile"] files = instance.data["expectedFiles"] if not files: @@ -184,6 +184,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, self.log.debug("Submitting 3dsMax render..") project_settings = instance.context.data["project_settings"] if instance.data.get("multiCamera"): + self.log.debug("Submitting jobs for multiple cameras..") payload = self._use_published_name_for_multiples( payload_data, project_settings) job_infos, plugin_infos = payload @@ -249,6 +250,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return job_info, plugin_info def get_job_info_through_camera(self, camera): + """Get the job parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with job info. + """ instance = self._instance context = instance.context job_info = copy.deepcopy(self.job_info) @@ -268,15 +274,27 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # set the output filepath with the relative camera def get_plugin_info_through_camera(self, camera): + """Get the plugin parameters for deadline submission when + multi-camera is enabled. + Args: + infos(dict): a dictionary with plugin info. + """ instance = self._instance # set the target camera plugin_info = copy.deepcopy(self.plugin_info) plugin_data = {} # set the output filepath with the relative camera - for camera_scene_path in instance.data.get("sceneFiles"): - if camera in camera_scene_path: - plugin_data["SceneFile"] = camera_scene_path + if instance.data.get("multiCamera"): + scene_filepath = instance.context.data["currentFile"] + scene_filename = os.path.basename(scene_filepath) + scene_directory = os.path.dirname(scene_filepath) + current_filename, ext = os.path.splitext(scene_filename) + camera_scene_name = f"{current_filename}_{camera}{ext}" + camera_scene_filepath = os.path.join( + scene_directory, f"_{current_filename}", camera_scene_name) + plugin_data["SceneFile"] = camera_scene_filepath + files = instance.data.get("expectedFiles") if not files: raise RuntimeError("No render elements found") From 0d9873dd41dc71d603b100469a1faa419688a85c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Jul 2023 22:32:19 +0800 Subject: [PATCH 029/281] ondrej's comment --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 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 78e570a780..15019c2647 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -12,6 +12,7 @@ from openpype.pipeline import ( legacy_io, OpenPypePyblishPluginMixin ) +from openpype.pipeline.publish import KnownPublishError from openpype.hosts.max.api.lib import ( get_current_renderer, get_multipass_setting @@ -168,7 +169,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, files = instance.data["expectedFiles"] if not files: - raise RuntimeError("No Render Elements found!") + raise KnownPublishError("No Render Elements found!") first_file = next(self._iter_expected_files(files)) output_dir = os.path.dirname(first_file) instance.data["outputDir"] = output_dir From 3e49e6c642b1813d90111bd4007b039f46fded27 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 31 Jul 2023 23:19:31 +0800 Subject: [PATCH 030/281] use KnownPublishError instead of RuntimeError --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 4 ++-- 1 file changed, 2 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 15019c2647..29ce315bdc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -210,7 +210,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, files = instance.data.get("expectedFiles") if not files: - raise RuntimeError("No render elements found") + raise KnownPublishError("No render elements found") first_file = next(self._iter_expected_files(files)) old_output_dir = os.path.dirname(first_file) output_beauty = RenderSettings().get_render_output(instance.name, @@ -298,7 +298,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, files = instance.data.get("expectedFiles") if not files: - raise RuntimeError("No render elements found") + raise KnownPublishError("No render elements found") first_file = next(self._iter_expected_files(files)) old_output_dir = os.path.dirname(first_file) rgb_output = RenderSettings().get_batch_render_output(camera) # noqa From 1e19bfa6949c7340a7ea146d87d66d1f2bb80aa4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 16:42:58 +0800 Subject: [PATCH 031/281] fix the weird naming of publish render folder --- openpype/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index fe3ab97de8..61cceb80ad 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -583,7 +583,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: # in case of single frame cam = [c for c in cameras if c in col] - if cam: + if cam or subset != "maxrenderMain": if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) else: From 960e7a24bc68cb913ef4a6b6857d01b0006f04cd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 17:32:31 +0800 Subject: [PATCH 032/281] fixing bigroy's comment on camera subset interruption --- openpype/pipeline/farm/pyblish_functions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 61cceb80ad..fb73e46508 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -583,11 +583,15 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: # in case of single frame cam = [c for c in cameras if c in col] - if cam or subset != "maxrenderMain": + if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) + if subset == "maxrenderMain": + subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}_{}'.format(group_name, cam) + if subset == "maxrenderMain": + subset_name = '{}'.format(group_name) else: if aov: subset_name = '{}_{}'.format(group_name, aov) From 749ca64e58caa9b95df2edc0347c72d44e15c151 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 18:54:14 +0800 Subject: [PATCH 033/281] roy's comment on the subset naming based on cameras in multiple camera rendering --- openpype/pipeline/farm/pyblish_functions.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index fb73e46508..0c5d6a2712 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,18 +579,24 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name if isinstance(col, (list, tuple)): - cam = [c for c in cameras if c in col[0]] + cam = next((c for c in cameras if c in col[0]), None) else: # in case of single frame - cam = [c for c in cameras if c in col] + cam = next((cam for cam in cameras if cam in col), None) if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) if subset == "maxrenderMain": + # Max submit scenes by cameras for multiple camera + # submission, it results to include the camera name inside + # the original subset and i.e group_name subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}_{}'.format(group_name, cam) if subset == "maxrenderMain": + # Max submit scenes by cameras for multiple camera + # submission, it results to include the camera name inside + # the original subset and i.e group_name subset_name = '{}'.format(group_name) else: if aov: From 1f2e32311f71448d2cc16348871d36ebf0093f04 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 30 Aug 2023 20:34:44 +0800 Subject: [PATCH 034/281] remove camera_name in aov if there is one --- openpype/pipeline/farm/pyblish_functions.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 0c5d6a2712..58ffeb937f 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -585,19 +585,13 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, cam = next((cam for cam in cameras if cam in col), None) if cam: if aov: + # if there is duplicatd camera name found in aov, + # it would be removed + if aov.startswith(cam): + aov = aov.replace(f"{cam}_", "") subset_name = '{}_{}_{}'.format(group_name, cam, aov) - if subset == "maxrenderMain": - # Max submit scenes by cameras for multiple camera - # submission, it results to include the camera name inside - # the original subset and i.e group_name - subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}_{}'.format(group_name, cam) - if subset == "maxrenderMain": - # Max submit scenes by cameras for multiple camera - # submission, it results to include the camera name inside - # the original subset and i.e group_name - subset_name = '{}'.format(group_name) else: if aov: subset_name = '{}_{}'.format(group_name, aov) From 30ea0213b72237cdc9bd2920c691fb22cf184338 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:40:07 +0800 Subject: [PATCH 035/281] add pattern for clique.assemble function to just iterate through frame ranges --- openpype/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 58ffeb937f..3f19c3cc95 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -548,7 +548,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, instances = [] # go through AOVs in expected files for aov, files in exp_files[0].items(): - cols, rem = clique.assemble(files) + cols, rem = clique.assemble(files, patterns=[clique.PATTERNS['frames']]) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From 44391391df926316b8c7585e526ebbaffc5ab3d6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:41:00 +0800 Subject: [PATCH 036/281] hound --- openpype/pipeline/farm/pyblish_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 3f19c3cc95..5713c13c4e 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -548,7 +548,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, instances = [] # go through AOVs in expected files for aov, files in exp_files[0].items(): - cols, rem = clique.assemble(files, patterns=[clique.PATTERNS['frames']]) + cols, rem = clique.assemble( + files, patterns=[clique.PATTERNS['frames']]) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From 60e5eb74aeee01ad6a621554eac950a5852cdb90 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:43:30 +0800 Subject: [PATCH 037/281] big roy's comments on the line 582-583 --- openpype/pipeline/farm/pyblish_functions.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 5713c13c4e..0bdc2b1339 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,11 +579,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, group_name = subset # if there are multiple cameras, we need to add camera name - if isinstance(col, (list, tuple)): - cam = next((c for c in cameras if c in col[0]), None) - else: - # in case of single frame - cam = next((cam for cam in cameras if cam in col), None) + expected_filepath = col[0] if isinstance(col, (list, tuple)) else col + cam = next((cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: # if there is duplicatd camera name found in aov, From 75d17fcdffb6e10b049110be6defa023b6a5d68e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 5 Sep 2023 18:56:45 +0800 Subject: [PATCH 038/281] restore the pattern for not breaking the submit publish job --- openpype/pipeline/farm/pyblish_functions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 0bdc2b1339..181aad6cb5 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -548,8 +548,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, instances = [] # go through AOVs in expected files for aov, files in exp_files[0].items(): - cols, rem = clique.assemble( - files, patterns=[clique.PATTERNS['frames']]) + cols, rem = clique.assemble(files) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From dadc6fa85747cb4790b226e2279fdacd1744064c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 26 Sep 2023 15:12:21 +0800 Subject: [PATCH 039/281] make sure render outputs with all cameras should be in the render publish folder --- openpype/pipeline/farm/pyblish_functions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 181aad6cb5..5019d01be3 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -582,11 +582,13 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, cam = next((cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: - # if there is duplicatd camera name found in aov, - # it would be removed - if aov.startswith(cam): - aov = aov.replace(f"{cam}_", "") - subset_name = '{}_{}_{}'.format(group_name, cam, aov) + # Multiple cameras publishing in some hosts such as 3dsMax + # have aov data set to "Camera001_beauty" to differentiate + # the render output files + if not aov.startswith(cam): + subset_name = '{}_{}_{}'.format(group_name, cam, aov) + else: + subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}_{}'.format(group_name, cam) else: @@ -594,7 +596,6 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}'.format(group_name) - if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) else: From 55555e3078e3289e663f13709334e0ed4e038e72 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 22:00:38 +0800 Subject: [PATCH 040/281] iterate the camera list --- openpype/pipeline/farm/pyblish_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 5019d01be3..1a5cbbf3e2 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,7 +579,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cam = next((cam for cam in cameras if cam in expected_filepath), None) + cam = next(iter(cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: # Multiple cameras publishing in some hosts such as 3dsMax From 810b3259d1e7441bda9a147bdf4942a6a9fd101c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 27 Sep 2023 22:29:41 +0800 Subject: [PATCH 041/281] hound --- openpype/pipeline/farm/pyblish_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 1a5cbbf3e2..e5530481d2 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,7 +579,8 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cam = next(iter(cam for cam in cameras if cam in expected_filepath), None) + cam = next( + iter(cam for cam in cameras if cam in expected_filepath), None) if cam: if aov: # Multiple cameras publishing in some hosts such as 3dsMax From 824912dba1e1d6d793d872d93188312bb6e43309 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 28 Sep 2023 19:08:09 +0800 Subject: [PATCH 042/281] update camera subset name condition --- openpype/pipeline/farm/pyblish_functions.py | 22 ++++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index e5530481d2..f0d330da71 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -579,24 +579,22 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cam = next( - iter(cam for cam in cameras if cam in expected_filepath), None) - if cam: - if aov: - # Multiple cameras publishing in some hosts such as 3dsMax - # have aov data set to "Camera001_beauty" to differentiate - # the render output files - if not aov.startswith(cam): - subset_name = '{}_{}_{}'.format(group_name, cam, aov) + cams = [cam for cam in cameras if cam in expected_filepath] + if cams: + for cam in cams: + if aov: + if not aov.startswith(cam): + subset_name = '{}_{}_{}'.format(group_name, cam, aov) + else: + subset_name = "{}_{}".format(group_name, aov) else: - subset_name = '{}_{}'.format(group_name, aov) - else: - subset_name = '{}_{}'.format(group_name, cam) + subset_name = '{}_{}'.format(group_name, cam) else: if aov: subset_name = '{}_{}'.format(group_name, aov) else: subset_name = '{}'.format(group_name) + if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) else: From 5b2e5faccdbd0e425e0d9f438f0e82f96fe15c24 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 18:30:03 +0800 Subject: [PATCH 043/281] remove if condition of cameras --- openpype/pipeline/farm/pyblish_functions.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index f0d330da71..8ae46ae1a1 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -580,20 +580,17 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, # if there are multiple cameras, we need to add camera name expected_filepath = col[0] if isinstance(col, (list, tuple)) else col cams = [cam for cam in cameras if cam in expected_filepath] - if cams: - for cam in cams: - if aov: - if not aov.startswith(cam): - subset_name = '{}_{}_{}'.format(group_name, cam, aov) - else: - subset_name = "{}_{}".format(group_name, aov) - else: - subset_name = '{}_{}'.format(group_name, cam) - else: + for cam in cams: if aov: - subset_name = '{}_{}'.format(group_name, aov) + if aov.startswith(cam): + subset_name = '{}_{}_{}'.format(group_name, cam, aov) + else: + subset_name = "{}_{}".format(group_name, aov) else: - subset_name = '{}'.format(group_name) + if aov.startswith(cam): + subset_name = '{}_{}'.format(group_name, cam) + else: + subset_name = '{}'.format(group_name) if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) From 5bb6f304d381938d1effa7cc089971ab85f46352 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 19:26:42 +0800 Subject: [PATCH 044/281] use known publish error instead of runtime error --- openpype/hosts/max/plugins/publish/collect_render.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 78ab02ef5e..01d7cff32d 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -5,6 +5,7 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import get_current_asset_name +from openpype.pipeline.publish import KnownPublishError 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_rendersettings import RenderSettings @@ -45,8 +46,8 @@ class CollectRender(pyblish.api.InstancePlugin): if instance.data.get("multiCamera"): cameras = instance.data.get("members") if not cameras: - raise RuntimeError("There should be at least" - " one renderable camera in container") + raise KnownPublishError("There should be at least" + " one renderable camera in container") sel_cam = [ c.name for c in cameras if rt.classOf(c) in rt.Camera.classes] From e3e8770ffefb25a2b89388f70f51d385a10b0cb5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 20:31:06 +0800 Subject: [PATCH 045/281] restore the function code for test --- openpype/pipeline/farm/pyblish_functions.py | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index 3254fbdfda..c9e013a09a 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -578,20 +578,21 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: group_name = subset - # if there are multiple cameras, we need to add camera name - expected_filepath = col[0] if isinstance(col, (list, tuple)) else col - cams = [cam for cam in cameras if cam in expected_filepath] - for cam in cams: + if isinstance(col, (list, tuple)): + cam = [c for c in cameras if c in col[0]] + else: + # in case of single frame + cam = [c for c in cameras if c in col] + if cam: if aov: - if aov.startswith(cam): - subset_name = '{}_{}_{}'.format(group_name, cam, aov) - else: - subset_name = "{}_{}".format(group_name, aov) + subset_name = '{}_{}_{}'.format(group_name, cam, aov) else: - if aov.startswith(cam): - subset_name = '{}_{}'.format(group_name, cam) - else: - subset_name = '{}'.format(group_name) + subset_name = '{}_{}'.format(group_name, cam) + else: + if aov: + subset_name = '{}_{}'.format(group_name, aov) + else: + subset_name = '{}'.format(group_name) if isinstance(col, (list, tuple)): staging = os.path.dirname(col[0]) From 7402662161441640ba10df0d0d47556a3310eae4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2023 23:12:14 +0100 Subject: [PATCH 046/281] Remove `shelf` class and shelf build on maya `userSetup.py` --- openpype/hosts/maya/api/lib.py | 113 ----------------------- openpype/hosts/maya/startup/userSetup.py | 19 ---- 2 files changed, 132 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 7c49c837e9..f7eaf358fe 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2921,119 +2921,6 @@ def fix_incompatible_containers(): "ReferenceLoader", type="string") -def _null(*args): - pass - - -class shelf(): - '''A simple class to build shelves in maya. Since the build method is empty, - it should be extended by the derived class to build the necessary shelf - elements. By default it creates an empty shelf called "customShelf".''' - - ########################################################################### - '''This is an example shelf.''' - # class customShelf(_shelf): - # def build(self): - # self.addButon(label="button1") - # self.addButon("button2") - # self.addButon("popup") - # p = cmds.popupMenu(b=1) - # self.addMenuItem(p, "popupMenuItem1") - # self.addMenuItem(p, "popupMenuItem2") - # sub = self.addSubMenu(p, "subMenuLevel1") - # self.addMenuItem(sub, "subMenuLevel1Item1") - # sub2 = self.addSubMenu(sub, "subMenuLevel2") - # self.addMenuItem(sub2, "subMenuLevel2Item1") - # self.addMenuItem(sub2, "subMenuLevel2Item2") - # self.addMenuItem(sub, "subMenuLevel1Item2") - # self.addMenuItem(p, "popupMenuItem3") - # self.addButon("button3") - # customShelf() - ########################################################################### - - def __init__(self, name="customShelf", iconPath="", preset={}): - self.name = name - - self.iconPath = iconPath - - self.labelBackground = (0, 0, 0, 0) - self.labelColour = (.9, .9, .9) - - self.preset = preset - - self._cleanOldShelf() - cmds.setParent(self.name) - self.build() - - def build(self): - '''This method should be overwritten in derived classes to actually - build the shelf elements. Otherwise, nothing is added to the shelf.''' - for item in self.preset['items']: - if not item.get('command'): - item['command'] = self._null - if item['type'] == 'button': - self.addButon(item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'menuItem': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'subMenu': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - - def addButon(self, label, icon="commandButton.png", - command=_null, doubleCommand=_null): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - cmds.setParent(self.name) - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - cmds.shelfButton(width=37, height=37, image=icon, label=label, - command=command, dcc=doubleCommand, - imageOverlayLabel=label, olb=self.labelBackground, - olc=self.labelColour) - - def addMenuItem(self, parent, label, command=_null, icon=""): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, c=command, i="") - - def addSubMenu(self, parent, label, icon=None): - ''' - Adds a sub menu item with the specified label and icon to - the specified parent popup menu. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, i=icon, subMenu=1) - - def _cleanOldShelf(self): - ''' - Checks if the shelf exists and empties it if it does - or creates it if it does not. - ''' - if cmds.shelfLayout(self.name, ex=1): - if cmds.shelfLayout(self.name, q=1, ca=1): - for each in cmds.shelfLayout(self.name, q=1, ca=1): - cmds.deleteUI(each) - else: - cmds.shelfLayout(self.name, p="ShelfLayout") - - def _get_render_instances(): """Return all 'render-like' instances. diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index f2899cdb37..417f72a59f 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -46,24 +46,5 @@ if bool(int(os.environ.get(key, "0"))): lowestPriority=True ) -# Build a shelf. -shelf_preset = settings['maya'].get('project_shelf') -if shelf_preset: - icon_path = os.path.join( - os.environ['OPENPYPE_PROJECT_SCRIPTS'], - project_name, - "icons") - icon_path = os.path.abspath(icon_path) - - for i in shelf_preset['imports']: - import_string = "from {} import {}".format(project_name, i) - print(import_string) - exec(import_string) - - cmds.evalDeferred( - "mlib.shelf(name=shelf_preset['name'], iconPath=icon_path," - " preset=shelf_preset)" - ) - print("Finished OpenPype usersetup.") From e9005c66184c5b6145a342e5258256c1929b111a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 31 Oct 2023 12:59:24 +0800 Subject: [PATCH 047/281] make sure subset name conditions are applicable for other hosts such as maya --- openpype/pipeline/farm/pyblish_functions.py | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py index c9e013a09a..6265449515 100644 --- a/openpype/pipeline/farm/pyblish_functions.py +++ b/openpype/pipeline/farm/pyblish_functions.py @@ -578,16 +578,18 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, else: group_name = subset - if isinstance(col, (list, tuple)): - cam = [c for c in cameras if c in col[0]] - else: - # in case of single frame - cam = [c for c in cameras if c in col] - if cam: - if aov: - subset_name = '{}_{}_{}'.format(group_name, cam, aov) - else: - subset_name = '{}_{}'.format(group_name, cam) + # if there are multiple cameras, we need to add camera name + expected_filepath = col[0] if isinstance(col, (list, tuple)) else col + cams = [cam for cam in cameras if cam in expected_filepath] + if cams: + for cam in cams: + if aov: + if not aov.startswith(cam): + subset_name = '{}_{}_{}'.format(group_name, cam, aov) + else: + subset_name = "{}_{}".format(group_name, aov) + else: + subset_name = '{}_{}'.format(group_name, cam) else: if aov: subset_name = '{}_{}'.format(group_name, aov) From 6f2191128dc6b8fc14a6ba419c4f5ffa848aa132 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Dec 2023 10:42:34 +0100 Subject: [PATCH 048/281] Refactor create_editorial.py for better track handling - Refactored the code to filter tracks based on their kind being "Video" - Added a comment to clarify the purpose of media_data variable - Added a comment to explain setting track name - Removed an unnecessary blank line --- .../plugins/create/create_editorial.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index dce4a051fd..e6f29af40f 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -381,15 +381,19 @@ or updating already created. Publishing will create OTIO file. """ self.asset_name_check = [] - tracks = otio_timeline.each_child( - descended_from_type=otio.schema.Track - ) + tracks = [ + track for track in otio_timeline.each_child( + descended_from_type=otio.schema.Track) + if track.kind == "Video" + ] - # media data for audio sream and reference solving + # media data for audio stream and reference solving media_data = self._get_media_source_metadata(media_path) for track in tracks: + # set track name track.name = f"{sequence_file_name} - {otio_timeline.name}" + try: track_start_frame = ( abs(track.source_range.start_time.value) @@ -398,7 +402,6 @@ or updating already created. Publishing will create OTIO file. except AttributeError: track_start_frame = 0 - for clip in track.each_child(): if not self._validate_clip_for_processing(clip): continue From fc179befc24469b39325e0ec12517263cb313410 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Dec 2023 10:43:11 +0100 Subject: [PATCH 049/281] improving plugin label so it is easier to read --- openpype/plugins/publish/validate_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/validate_resources.py b/openpype/plugins/publish/validate_resources.py index 7911c70c2d..ce03515400 100644 --- a/openpype/plugins/publish/validate_resources.py +++ b/openpype/plugins/publish/validate_resources.py @@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder - label = "Resources" + label = "Validate Resources" def process(self, instance): From 57197cc37ab994ab814cbc1fc8b6368f64165ef7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 18 Dec 2023 15:16:10 +0000 Subject: [PATCH 050/281] Use only the final part of folderPath in the instance name --- openpype/hosts/blender/api/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 568d8f6695..18d2aa5362 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -226,7 +226,7 @@ class BaseCreator(Creator): # Create asset group if AYON_SERVER_ENABLED: - asset_name = instance_data["folderPath"] + asset_name = instance_data["folderPath"].split("/")[-1] else: asset_name = instance_data["asset"] @@ -311,6 +311,8 @@ class BaseCreator(Creator): or asset_name_key in changes.changed_keys ): asset_name = data[asset_name_key] + if AYON_SERVER_ENABLED: + asset_name = asset_name.split("/")[-1] name = prepare_scene_name( asset=asset_name, subset=data["subset"] ) From 73614e08a8de37018b0143edafed558c0cf8e879 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 18 Dec 2023 16:14:10 +0000 Subject: [PATCH 051/281] Add warning if name is too long for Blender --- openpype/hosts/blender/api/plugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 18d2aa5362..d50bfad53d 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -36,6 +36,12 @@ def prepare_scene_name( if namespace: name = f"{name}_{namespace}" name = f"{name}_{subset}" + + # Blender name for a collection or object cannot be longer than 63 + # characters. If the name is longer, it will raise an error. + if len(name) > 63: + raise ValueError(f"Asset name '{name}' is too long.") + return name From 55c3bdbd06f0baa06550654342ea7a66bd31c6e4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Dec 2023 17:30:37 +0800 Subject: [PATCH 052/281] add the contextmanager functions for 'reverting lock attributes' --- openpype/hosts/maya/api/lib.py | 139 +++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 60 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index af726409d4..64cd5b6b6d 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2565,6 +2565,66 @@ def bake_to_world_space(nodes, list: The newly created and baked node names. """ + @contextlib.contextmanager + def _revert_lock_attributes(node, new_node): + # Connect all attributes on the node except for transform + # attributes + attrs = _get_attrs(node) + attrs = set(attrs) - transform_attrs if attrs else [] + original_attrs_lock = {} + try: + for attr in attrs: + orig_node_attr = '{0}.{1}'.format(node, attr) + new_node_attr = '{0}.{1}'.format(new_node, attr) + original_attrs_lock[new_node_attr] = ( + cmds.getAttr(new_node_attr, lock=True) + ) + + # unlock to avoid connection errors + cmds.setAttr(new_node_attr, lock=False) + + cmds.connectAttr(orig_node_attr, + new_node_attr, + force=True) + yield + finally: + for attr, lock_state in original_attrs_lock.items(): + cmds.setAttr(attr, lock=lock_state) + + @contextlib.contextmanager + def _revert_lock_shape_attributes(node, new_node, shape): + # If shapes are also baked then connect those keyable attributes + if shape: + children_shapes = cmds.listRelatives(new_node, + children=True, + fullPath=True, + shapes=True) + if children_shapes: + orig_children_shapes = cmds.listRelatives(node, + children=True, + fullPath=True, + shapes=True) + original_shape_lock_attrs = {} + try: + for orig_shape, new_shape in zip(orig_children_shapes, + children_shapes): + attrs = _get_attrs(orig_shape) + for attr in attrs: + orig_node_attr = '{0}.{1}'.format(orig_shape, attr) + new_node_attr = '{0}.{1}'.format(new_shape, attr) + original_shape_lock_attrs[new_node_attr] = ( + cmds.getAttr(new_node_attr, lock=True) + ) + # unlock to avoid connection errors + cmds.setAttr(new_node_attr, lock=False) + + cmds.connectAttr(orig_node_attr, + new_node_attr, + force=True) + yield + finally: + for attr, lock_state in original_shape_lock_attrs.items(): + cmds.setAttr(attr, lock=lock_state) def _get_attrs(node): """Workaround for buggy shape attribute listing with listAttr""" @@ -2599,7 +2659,6 @@ def bake_to_world_space(nodes, world_space_nodes = [] with delete_after() as delete_bin: - # Create the duplicate nodes that are in world-space connected to # the originals for node in nodes: @@ -2610,69 +2669,29 @@ def bake_to_world_space(nodes, new_node = cmds.duplicate(node, name=new_name, renameChildren=True)[0] # noqa + with _revert_lock_attributes(node, new_node): + with _revert_lock_shape_attributes(node, new_node): + # Parent to world + if cmds.listRelatives(new_node, parent=True): + new_node = cmds.parent(new_node, world=True)[0] - # Connect all attributes on the node except for transform - # attributes - attrs = _get_attrs(node) - attrs = set(attrs) - transform_attrs if attrs else [] + # Unlock transform attributes so constraint can be created + for attr in transform_attrs: + cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False) - for attr in attrs: - orig_node_attr = '{0}.{1}'.format(node, attr) - new_node_attr = '{0}.{1}'.format(new_node, attr) + # Constraints + delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False)) + delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False)) - # unlock to avoid connection errors - cmds.setAttr(new_node_attr, lock=False) + world_space_nodes.append(new_node) - cmds.connectAttr(orig_node_attr, - new_node_attr, - force=True) - - # If shapes are also baked then connect those keyable attributes - if shape: - children_shapes = cmds.listRelatives(new_node, - children=True, - fullPath=True, - shapes=True) - if children_shapes: - orig_children_shapes = cmds.listRelatives(node, - children=True, - fullPath=True, - shapes=True) - for orig_shape, new_shape in zip(orig_children_shapes, - children_shapes): - attrs = _get_attrs(orig_shape) - for attr in attrs: - orig_node_attr = '{0}.{1}'.format(orig_shape, attr) - new_node_attr = '{0}.{1}'.format(new_shape, attr) - - # unlock to avoid connection errors - cmds.setAttr(new_node_attr, lock=False) - - cmds.connectAttr(orig_node_attr, - new_node_attr, - force=True) - - # Parent to world - if cmds.listRelatives(new_node, parent=True): - new_node = cmds.parent(new_node, world=True)[0] - - # Unlock transform attributes so constraint can be created - for attr in transform_attrs: - cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False) - - # Constraints - delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False)) - delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False)) - - world_space_nodes.append(new_node) - - bake(world_space_nodes, - frame_range=frame_range, - step=step, - simulation=simulation, - preserve_outside_keys=preserve_outside_keys, - disable_implicit_control=disable_implicit_control, - shape=shape) + bake(world_space_nodes, + frame_range=frame_range, + step=step, + simulation=simulation, + preserve_outside_keys=preserve_outside_keys, + disable_implicit_control=disable_implicit_control, + shape=shape) return world_space_nodes From ac7b2963dd928f04cce701e8d6ef3b74b26a02ed Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Dec 2023 17:34:07 +0800 Subject: [PATCH 053/281] hound --- openpype/hosts/maya/api/lib.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 64cd5b6b6d..959a615ef9 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2584,8 +2584,8 @@ def bake_to_world_space(nodes, cmds.setAttr(new_node_attr, lock=False) cmds.connectAttr(orig_node_attr, - new_node_attr, - force=True) + new_node_attr, + force=True) yield finally: for attr, lock_state in original_attrs_lock.items(): @@ -2596,18 +2596,18 @@ def bake_to_world_space(nodes, # If shapes are also baked then connect those keyable attributes if shape: children_shapes = cmds.listRelatives(new_node, - children=True, - fullPath=True, - shapes=True) + children=True, + fullPath=True, + shapes=True) if children_shapes: orig_children_shapes = cmds.listRelatives(node, - children=True, - fullPath=True, - shapes=True) + children=True, + fullPath=True, + shapes=True) original_shape_lock_attrs = {} try: for orig_shape, new_shape in zip(orig_children_shapes, - children_shapes): + children_shapes): attrs = _get_attrs(orig_shape) for attr in attrs: orig_node_attr = '{0}.{1}'.format(orig_shape, attr) @@ -2619,8 +2619,8 @@ def bake_to_world_space(nodes, cmds.setAttr(new_node_attr, lock=False) cmds.connectAttr(orig_node_attr, - new_node_attr, - force=True) + new_node_attr, + force=True) yield finally: for attr, lock_state in original_shape_lock_attrs.items(): From a75ff0f71aab21daa86ffd40ce45ff0e8b9aee20 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 21 Dec 2023 17:36:35 +0800 Subject: [PATCH 054/281] hound --- openpype/hosts/maya/api/lib.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 959a615ef9..3bc8b67a81 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2671,27 +2671,30 @@ def bake_to_world_space(nodes, renameChildren=True)[0] # noqa with _revert_lock_attributes(node, new_node): with _revert_lock_shape_attributes(node, new_node): - # Parent to world + # Parent to world if cmds.listRelatives(new_node, parent=True): new_node = cmds.parent(new_node, world=True)[0] # Unlock transform attributes so constraint can be created for attr in transform_attrs: - cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False) + cmds.setAttr( + '{0}.{1}'.format(new_node, attr), lock=False) # Constraints - delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False)) - delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False)) + delete_bin.extend( + cmds.parentConstraint(node, new_node, mo=False)) + delete_bin.extend( + cmds.scaleConstraint(node, new_node, mo=False)) world_space_nodes.append(new_node) bake(world_space_nodes, - frame_range=frame_range, - step=step, - simulation=simulation, - preserve_outside_keys=preserve_outside_keys, - disable_implicit_control=disable_implicit_control, - shape=shape) + frame_range=frame_range, + step=step, + simulation=simulation, + preserve_outside_keys=preserve_outside_keys, + disable_implicit_control=disable_implicit_control, + shape=shape) return world_space_nodes From a8b93ec8fe5f30f1f15444df90b9bcb7f0d496f0 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 10:15:05 +0000 Subject: [PATCH 055/281] Fixes long library names --- openpype/hosts/blender/plugins/load/load_animation.py | 7 ++++++- openpype/hosts/blender/plugins/load/load_blend.py | 7 ++++++- openpype/hosts/blender/plugins/load/load_blendscene.py | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_animation.py b/openpype/hosts/blender/plugins/load/load_animation.py index 3e7f808903..0f968c75e5 100644 --- a/openpype/hosts/blender/plugins/load/load_animation.py +++ b/openpype/hosts/blender/plugins/load/load_animation.py @@ -61,5 +61,10 @@ class BlendAnimationLoader(plugin.AssetLoader): bpy.data.objects.remove(container) - library = bpy.data.libraries.get(bpy.path.basename(libpath)) + filepath = bpy.path.basename(libpath) + # Blender has a limit of 63 characters for any data name. + # If the filepath is longer, it will be truncated. + if len(filepath) > 63: + filepath = filepath[:63] + library = bpy.data.libraries.get(filepath) bpy.data.libraries.remove(library) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index f437e66795..2d5ac18149 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -106,7 +106,12 @@ class BlendLoader(plugin.AssetLoader): bpy.context.scene.collection.objects.link(obj) # Remove the library from the blend file - library = bpy.data.libraries.get(bpy.path.basename(libpath)) + filepath = bpy.path.basename(libpath) + # Blender has a limit of 63 characters for any data name. + # If the filepath is longer, it will be truncated. + if len(filepath) > 63: + filepath = filepath[:63] + library = bpy.data.libraries.get(filepath) bpy.data.libraries.remove(library) return container, members diff --git a/openpype/hosts/blender/plugins/load/load_blendscene.py b/openpype/hosts/blender/plugins/load/load_blendscene.py index 6cc7f39d03..fba0245af1 100644 --- a/openpype/hosts/blender/plugins/load/load_blendscene.py +++ b/openpype/hosts/blender/plugins/load/load_blendscene.py @@ -60,7 +60,12 @@ class BlendSceneLoader(plugin.AssetLoader): bpy.context.scene.collection.children.link(container) # Remove the library from the blend file - library = bpy.data.libraries.get(bpy.path.basename(libpath)) + filepath = bpy.path.basename(libpath) + # Blender has a limit of 63 characters for any data name. + # If the filepath is longer, it will be truncated. + if len(filepath) > 63: + filepath = filepath[:63] + library = bpy.data.libraries.get(filepath) bpy.data.libraries.remove(library) return container, members From 6c04a6df1a120eead69fb90b192e26017e95f334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 21 Dec 2023 11:53:35 +0100 Subject: [PATCH 056/281] :bug: fix default render product name --- openpype/hosts/houdini/plugins/publish/collect_vray_rop.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index a1f4554726..f60d8c664c 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -67,7 +67,7 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): beauty_product = self.get_render_product_name(default_prefix) render_products.append(beauty_product) files_by_aov = { - "RGB Color": self.generate_expected_files(instance, + "": self.generate_expected_files(instance, beauty_product)} if instance.data.get("RenderElement", True): @@ -75,7 +75,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): if render_element: for aov, renderpass in render_element.items(): render_products.append(renderpass) - files_by_aov[aov] = self.generate_expected_files(instance, renderpass) # noqa + files_by_aov[aov] = self.generate_expected_files( + instance, renderpass) + for product in render_products: self.log.debug("Found render product: %s" % product) From 9ff27b51b50e6d32d047b422ea6c54ea727f962e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 21 Dec 2023 14:27:35 +0100 Subject: [PATCH 057/281] :fire: remove Muster --- openpype/lib/connections.py | 12 +- openpype/modules/base.py | 1 - .../deadline/abstract_submit_deadline.py | 8 +- .../publish/submit_publish_cache_job.py | 4 - .../plugins/publish/submit_publish_job.py | 65 +- openpype/modules/muster/__init__.py | 6 - openpype/modules/muster/muster.py | 147 ----- .../plugins/publish/submit_maya_muster.py | 555 ------------------ .../publish/validate_muster_connection.py | 96 --- openpype/modules/muster/rest_api.py | 22 - openpype/modules/muster/widget_login.py | 165 ------ .../plugins/publish/collect_farm_target.py | 2 +- .../plugins/publish/collect_rendered_files.py | 8 - openpype/settings/ayon_settings.py | 17 - .../defaults/system_settings/modules.json | 17 - openpype/settings/entities/schemas/README.md | 4 +- .../schemas/system_schema/schema_modules.json | 31 - openpype/tools/settings/settings/README.md | 4 +- server_addon/muster/server/__init__.py | 17 - server_addon/muster/server/settings.py | 41 -- server_addon/muster/server/version.py | 1 - start.py | 4 - website/docs/admin_settings_system.md | 7 - website/docs/artist_hosts_maya.md | 14 +- website/docs/dev_settings.md | 6 +- website/docs/features.md | 10 +- website/docs/module_muster.md | 10 - website/docs/pype2/admin_hosts.md | 4 + website/sidebars.js | 1 - website/src/pages/index.js | 15 +- 30 files changed, 49 insertions(+), 1245 deletions(-) delete mode 100644 openpype/modules/muster/__init__.py delete mode 100644 openpype/modules/muster/muster.py delete mode 100644 openpype/modules/muster/plugins/publish/submit_maya_muster.py delete mode 100644 openpype/modules/muster/plugins/publish/validate_muster_connection.py delete mode 100644 openpype/modules/muster/rest_api.py delete mode 100644 openpype/modules/muster/widget_login.py delete mode 100644 server_addon/muster/server/__init__.py delete mode 100644 server_addon/muster/server/settings.py delete mode 100644 server_addon/muster/server/version.py delete mode 100644 website/docs/module_muster.md diff --git a/openpype/lib/connections.py b/openpype/lib/connections.py index 91b745a4c1..6a0cf4ae1c 100644 --- a/openpype/lib/connections.py +++ b/openpype/lib/connections.py @@ -6,13 +6,13 @@ def requests_post(*args, **kwargs): """Wrap request post method. Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline or Muster server are - running with self-signed certificates and their certificate is not + variable is found. This is useful when Deadline server is + running with self-signed certificates and its certificate is not added to trusted certificates on client machines. Warning: Disabling SSL certificate validation is defeating one line - of defense SSL is providing and it is not recommended. + of defense SSL is providing, and it is not recommended. """ if "verify" not in kwargs: @@ -24,13 +24,13 @@ def requests_get(*args, **kwargs): """Wrap request get method. Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline or Muster server are - running with self-signed certificates and their certificate is not + variable is found. This is useful when Deadline server is + running with self-signed certificates and its certificate is not added to trusted certificates on client machines. Warning: Disabling SSL certificate validation is defeating one line - of defense SSL is providing and it is not recommended. + of defense SSL is providing, and it is not recommended. """ if "verify" not in kwargs: diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 1a2513b4b4..c69bff7002 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -1332,7 +1332,6 @@ class TrayModulesManager(ModulesManager): "user", "ftrack", "kitsu", - "muster", "launcher_tool", "avalon", "clockify", diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 187feb9b1a..dbf8a32f96 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -34,8 +34,8 @@ def requests_post(*args, **kwargs): """Wrap request post method. Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline or Muster server are - running with self-signed certificates and their certificate is not + variable is found. This is useful when Deadline server is + running with self-signed certificates and its certificate is not added to trusted certificates on client machines. Warning: @@ -55,8 +55,8 @@ def requests_get(*args, **kwargs): """Wrap request get method. Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment - variable is found. This is useful when Deadline or Muster server are - running with self-signed certificates and their certificate is not + variable is found. This is useful when Deadline server is + running with self-signed certificates and its certificate is not added to trusted certificates on client machines. Warning: diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py index b2ebebc303..1bb45b77cc 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -99,10 +99,6 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, def _submit_deadline_post_job(self, instance, job): """Submit publish job to Deadline. - Deadline specific code separated from :meth:`process` for sake of - more universal code. Muster post job is sent directly by Muster - submitter, so this type of code isn't necessary for it. - Returns: (str): deadline_publish_job_id """ diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index c9019b496b..f5866576e6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -59,21 +59,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, publish.ColormanagedPyblishPluginMixin): """Process Job submitted on farm. - These jobs are dependent on a deadline or muster job + These jobs are dependent on a deadline job submission prior to this plug-in. - - In case of Deadline, it creates dependent job on farm publishing - rendered image sequence. - - - In case of Muster, there is no need for such thing as dependent job, - post action will be executed and rendered sequence will be published. + It creates dependent job on farm publishing rendered image sequence. Options in instance.data: - deadlineSubmissionJob (dict, Required): The returned .json data from the job submission to deadline. - - musterSubmissionJob (dict, Required): same as deadline. - - outputDir (str, Required): The output directory where the metadata file should be generated. It's assumed that this will also be final folder containing the output files. @@ -89,7 +83,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, """ - label = "Submit image sequence jobs to Deadline or Muster" + label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.2 icon = "tractor" @@ -161,10 +155,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, def _submit_deadline_post_job(self, instance, job, instances): """Submit publish job to Deadline. - Deadline specific code separated from :meth:`process` for sake of - more universal code. Muster post job is sent directly by Muster - submitter, so this type of code isn't necessary for it. - Returns: (str): deadline_publish_job_id """ @@ -581,21 +571,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, ._____. ''' - - render_job = None - submission_type = "" - if instance.data.get("toBeRenderedOn") == "deadline": - render_job = instance.data.pop("deadlineSubmissionJob", None) - submission_type = "deadline" - - if instance.data.get("toBeRenderedOn") == "muster": - render_job = instance.data.pop("musterSubmissionJob", None) - submission_type = "muster" + render_job = instance.data.pop("deadlineSubmissionJob", None) if not render_job and instance.data.get("tileRendering") is False: - raise AssertionError(("Cannot continue without valid Deadline " - "or Muster submission.")) - + raise AssertionError(("Cannot continue without valid " + "Deadline submission.")) if not render_job: import getpass @@ -624,21 +604,19 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "FTRACK_SERVER": os.environ.get("FTRACK_SERVER"), } - deadline_publish_job_id = None - if submission_type == "deadline": - # get default deadline webservice url from deadline module - self.deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - self.deadline_url = instance.data.get("deadlineUrl") - assert self.deadline_url, "Requires Deadline Webservice URL" + # get default deadline webservice url from deadline module + self.deadline_url = instance.context.data["defaultDeadline"] + # if custom one is set in instance, use that + if instance.data.get("deadlineUrl"): + self.deadline_url = instance.data.get("deadlineUrl") + assert self.deadline_url, "Requires Deadline Webservice URL" - deadline_publish_job_id = \ - self._submit_deadline_post_job(instance, render_job, instances) + 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 + # Inject deadline url to instances. + for inst in instances: + inst["deadlineUrl"] = self.deadline_url # publish job file publish_job = { @@ -664,15 +642,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, if audio_file and os.path.isfile(audio_file): publish_job.update({"audio": audio_file}) - # pass Ftrack credentials in case of Muster - if submission_type == "muster": - ftrack = { - "FTRACK_API_USER": os.environ.get("FTRACK_API_USER"), - "FTRACK_API_KEY": os.environ.get("FTRACK_API_KEY"), - "FTRACK_SERVER": os.environ.get("FTRACK_SERVER"), - } - publish_job.update({"ftrack": ftrack}) - metadata_path, rootless_metadata_path = \ create_metadata_path(instance, anatomy) diff --git a/openpype/modules/muster/__init__.py b/openpype/modules/muster/__init__.py deleted file mode 100644 index d194f8f3c2..0000000000 --- a/openpype/modules/muster/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .muster import MusterModule - - -__all__ = ( - "MusterModule", -) diff --git a/openpype/modules/muster/muster.py b/openpype/modules/muster/muster.py deleted file mode 100644 index 0cdb1230c8..0000000000 --- a/openpype/modules/muster/muster.py +++ /dev/null @@ -1,147 +0,0 @@ -import os -import json - -import appdirs -import requests - -from openpype.modules import OpenPypeModule, ITrayModule - - -class MusterModule(OpenPypeModule, ITrayModule): - """ - Module handling Muster Render credentials. This will display dialog - asking for user credentials for Muster if not already specified. - """ - cred_folder_path = os.path.normpath( - appdirs.user_data_dir('pype-app', 'pype') - ) - cred_filename = 'muster_cred.json' - - name = "muster" - - def initialize(self, modules_settings): - muster_settings = modules_settings[self.name] - self.enabled = muster_settings["enabled"] - self.muster_url = muster_settings["MUSTER_REST_URL"] - - self.cred_path = os.path.join( - self.cred_folder_path, self.cred_filename - ) - # Tray attributes - self.widget_login = None - self.action_show_login = None - self.rest_api_obj = None - - def get_global_environments(self): - return { - "MUSTER_REST_URL": self.muster_url - } - - def tray_init(self): - from .widget_login import MusterLogin - self.widget_login = MusterLogin(self) - - def tray_start(self): - """Show login dialog if credentials not found.""" - # This should be start of module in tray - cred = self.load_credentials() - if not cred: - self.show_login() - - def tray_exit(self): - """Nothing special for Muster.""" - return - - # Definition of Tray menu - def tray_menu(self, parent): - """Add **change credentials** option to tray menu.""" - from qtpy import QtWidgets - - # Menu for Tray App - menu = QtWidgets.QMenu('Muster', parent) - menu.setProperty('submenu', 'on') - - # Actions - self.action_show_login = QtWidgets.QAction( - "Change login", menu - ) - - menu.addAction(self.action_show_login) - self.action_show_login.triggered.connect(self.show_login) - - parent.addMenu(menu) - - def load_credentials(self): - """ - Get credentials from JSON file - """ - credentials = {} - try: - file = open(self.cred_path, 'r') - credentials = json.load(file) - except Exception: - file = open(self.cred_path, 'w+') - file.close() - - return credentials - - def get_auth_token(self, username, password): - """ - Authenticate user with Muster and get authToken from server. - """ - if not self.muster_url: - raise AttributeError("Muster REST API url not set") - params = { - 'username': username, - 'password': password - } - api_entry = '/api/login' - response = self._requests_post( - self.muster_url + api_entry, params=params) - if response.status_code != 200: - self.log.error( - 'Cannot log into Muster: {}'.format(response.status_code)) - raise Exception('Cannot login into Muster.') - - try: - token = response.json()['ResponseData']['authToken'] - except ValueError as e: - self.log.error('Invalid response from Muster server {}'.format(e)) - raise Exception('Invalid response from Muster while logging in.') - - self.save_credentials(token) - - def save_credentials(self, token): - """Save credentials to JSON file.""" - - with open(self.cred_path, "w") as f: - json.dump({'token': token}, f) - - def show_login(self): - """ - Show dialog to enter credentials - """ - if self.widget_login: - self.widget_login.show() - - # Webserver module implementation - def webserver_initialization(self, server_manager): - """Add routes for Muster login.""" - if self.tray_initialized: - from .rest_api import MusterModuleRestApi - - self.rest_api_obj = MusterModuleRestApi(self, server_manager) - - def _requests_post(self, *args, **kwargs): - """ Wrapper for requests, disabling SSL certificate validation if - DONT_VERIFY_SSL environment variable is found. This is useful when - Deadline or Muster server are running with self-signed certificates - and their certificate is not added to trusted certificates on - client machines. - - WARNING: disabling SSL certificate validation is defeating one line - of defense SSL is providing and it is not recommended. - """ - if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa - return requests.post(*args, **kwargs) diff --git a/openpype/modules/muster/plugins/publish/submit_maya_muster.py b/openpype/modules/muster/plugins/publish/submit_maya_muster.py deleted file mode 100644 index f6b3bfbbfd..0000000000 --- a/openpype/modules/muster/plugins/publish/submit_maya_muster.py +++ /dev/null @@ -1,555 +0,0 @@ -import os -import json -import getpass -import platform - -import appdirs - -from maya import cmds - -import pyblish.api -from openpype.lib import requests_post -from openpype.hosts.maya.api import lib -from openpype.hosts.maya.api.lib_rendersettings import RenderSettings -from openpype.pipeline import legacy_io -from openpype.settings import get_system_settings - - -# mapping between Maya renderer names and Muster template ids -def _get_template_id(renderer): - """ - Return muster template ID based on renderer name. - - :param renderer: renderer name - :type renderer: str - :returns: muster template id - :rtype: int - """ - - # TODO: Use settings from context? - templates = get_system_settings()["modules"]["muster"]["templates_mapping"] - if not templates: - raise RuntimeError(("Muster template mapping missing in " - "pype-settings")) - try: - template_id = templates[renderer] - except KeyError: - raise RuntimeError("Unmapped renderer - missing template id") - - return template_id - - -def _get_script(): - """Get path to the image sequence script""" - try: - from openpype.scripts import publish_filesequence - except Exception: - raise RuntimeError("Expected module 'publish_deadline'" - "to be available") - - module_path = publish_filesequence.__file__ - if module_path.endswith(".pyc"): - module_path = module_path[:-len(".pyc")] + ".py" - - return module_path - - -def get_renderer_variables(renderlayer=None): - """Retrieve the extension which has been set in the VRay settings - - Will return None if the current renderer is not VRay - For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which - start with `rs`. Use the actual node name, do NOT use the `nice name` - - Args: - renderlayer (str): the node name of the renderlayer. - - Returns: - dict - """ - - renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - - padding = cmds.getAttr(RenderSettings.get_padding_attr(renderer)) - - filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] - - if renderer == "vray": - # Maya's renderSettings function does not return V-Ray file extension - # so we get the extension from vraySettings - extension = cmds.getAttr("vraySettings.imageFormatStr") - - # When V-Ray image format has not been switched once from default .png - # the getAttr command above returns None. As such we explicitly set - # it to `.png` - if extension is None: - extension = "png" - - filename_prefix = "/_/" - else: - # Get the extension, getAttr defaultRenderGlobals.imageFormat - # returns an index number. - filename_base = os.path.basename(filename_0) - extension = os.path.splitext(filename_base)[-1].strip(".") - filename_prefix = "//" - - return {"ext": extension, - "filename_prefix": filename_prefix, - "padding": padding, - "filename_0": filename_0} - - -def preview_fname(folder, scene, layer, padding, ext): - """Return output file path with #### for padding. - - Deadline requires the path to be formatted with # in place of numbers. - For example `/path/to/render.####.png` - - Args: - folder (str): The root output folder (image path) - scene (str): The scene name - layer (str): The layer name to be rendered - padding (int): The padding length - ext(str): The output file extension - - Returns: - str - - """ - - # Following hardcoded "/_/" - output = "{scene}/{layer}/{layer}.{number}.{ext}".format( - scene=scene, - layer=layer, - number="#" * padding, - ext=ext - ) - - return os.path.join(folder, output) - - -class MayaSubmitMuster(pyblish.api.InstancePlugin): - """Submit available render layers to Muster - - Renders are submitted to a Muster via HTTP API as - supplied via the environment variable ``MUSTER_REST_URL``. - - Also needed is ``MUSTER_USER`` and ``MUSTER_PASSWORD``. - """ - - label = "Submit to Muster" - order = pyblish.api.IntegratorOrder + 0.1 - hosts = ["maya"] - families = ["renderlayer"] - icon = "satellite-dish" - if not os.environ.get("MUSTER_REST_URL"): - optional = False - active = False - else: - optional = True - - _token = None - - def _load_credentials(self): - """ - Load Muster credentials from file and set `MUSTER_USER`, - `MUSTER_PASSWORD`, `MUSTER_REST_URL` is loaded from settings. - - .. todo:: - - Show login dialog if access token is invalid or missing. - """ - app_dir = os.path.normpath( - appdirs.user_data_dir('pype-app', 'pype') - ) - file_name = 'muster_cred.json' - fpath = os.path.join(app_dir, file_name) - file = open(fpath, 'r') - muster_json = json.load(file) - self._token = muster_json.get('token', None) - if not self._token: - raise RuntimeError("Invalid access token for Muster") - file.close() - self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL") - if not self.MUSTER_REST_URL: - raise AttributeError("Muster REST API url not set") - - def _get_templates(self): - """ - Get Muster templates from server. - """ - params = { - "authToken": self._token, - "select": "name" - } - api_entry = '/api/templates/list' - response = requests_post( - self.MUSTER_REST_URL + api_entry, params=params) - if response.status_code != 200: - self.log.error( - 'Cannot get templates from Muster: {}'.format( - response.status_code)) - raise Exception('Cannot get templates from Muster.') - - try: - response_templates = response.json()["ResponseData"]["templates"] - except ValueError as e: - self.log.error( - 'Muster server returned unexpected data {}'.format(e) - ) - raise Exception('Muster server returned unexpected data') - - templates = {} - for t in response_templates: - templates[t.get("name")] = t.get("id") - - self._templates = templates - - def _resolve_template(self, renderer): - """ - Returns template ID based on renderer string. - - :param renderer: Name of renderer to match against template names - :type renderer: str - :returns: ID of template - :rtype: int - :raises: Exception if template ID isn't found - """ - self.log.debug("Trying to find template for [{}]".format(renderer)) - mapped = _get_template_id(renderer) - self.log.debug("got id [{}]".format(mapped)) - return self._templates.get(mapped) - - def _submit(self, payload): - """ - Submit job to Muster - - :param payload: json with job to submit - :type payload: str - :returns: response - :raises: Exception status is wrong - """ - params = { - "authToken": self._token, - "name": "submit" - } - api_entry = '/api/queue/actions' - response = requests_post( - self.MUSTER_REST_URL + api_entry, params=params, json=payload) - - if response.status_code != 200: - self.log.error( - 'Cannot submit job to Muster: {}'.format(response.text)) - raise Exception('Cannot submit job to Muster.') - - return response - - def process(self, instance): - """ - Authenticate with Muster, collect all data, prepare path for post - render publish job and submit job to farm. - """ - # setup muster environment - self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL") - - if self.MUSTER_REST_URL is None: - self.log.error( - "\"MUSTER_REST_URL\" is not found. Skipping " - "[{}]".format(instance) - ) - raise RuntimeError("MUSTER_REST_URL not set") - - self._load_credentials() - # self._get_templates() - - context = instance.context - workspace = context.data["workspaceDir"] - project_name = context.data["projectName"] - asset_name = context.data["asset"] - - filepath = None - - allInstances = [] - for result in context.data["results"]: - if ((result["instance"] is not None) and - (result["instance"] not in allInstances)): - allInstances.append(result["instance"]) - - for inst in allInstances: - print(inst) - if inst.data['family'] == 'scene': - filepath = inst.data['destination_list'][0] - - if not filepath: - filepath = context.data["currentFile"] - - self.log.debug(filepath) - - filename = os.path.basename(filepath) - comment = context.data.get("comment", "") - scene = os.path.splitext(filename)[0] - dirname = os.path.join(workspace, "renders") - renderlayer = instance.data['renderlayer'] # rs_beauty - renderlayer_name = instance.data['subset'] # beauty - renderglobals = instance.data["renderGlobals"] - # legacy_layers = renderlayer_globals["UseLegacyRenderLayers"] - # deadline_user = context.data.get("deadlineUser", getpass.getuser()) - jobname = "%s - %s" % (filename, instance.name) - - # Get the variables depending on the renderer - render_variables = get_renderer_variables(renderlayer) - output_filename_0 = preview_fname(folder=dirname, - scene=scene, - layer=renderlayer_name, - padding=render_variables["padding"], - ext=render_variables["ext"]) - - instance.data["outputDir"] = os.path.dirname(output_filename_0) - self.log.debug("output: {}".format(filepath)) - # build path for metadata file - metadata_filename = "{}_metadata.json".format(instance.data["subset"]) - output_dir = instance.data["outputDir"] - metadata_path = os.path.join(output_dir, metadata_filename) - - pype_root = os.environ["OPENPYPE_SETUP_PATH"] - - # we must provide either full path to executable or use musters own - # python named MPython.exe, residing directly in muster bin - # directory. - if platform.system().lower() == "windows": - # for muster, those backslashes must be escaped twice - muster_python = ("\"C:\\\\Program Files\\\\Virtual Vertex\\\\" - "Muster 9\\\\MPython.exe\"") - else: - # we need to run pype as different user then Muster dispatcher - # service is running (usually root). - muster_python = ("/usr/sbin/runuser -u {}" - " -- /usr/bin/python3".format(getpass.getuser())) - - # build the path and argument. We are providing separate --pype - # argument with network path to pype as post job actions are run - # but dispatcher (Server) and not render clients. Render clients - # inherit environment from publisher including PATH, so there's - # no problem finding PYPE, but there is now way (as far as I know) - # to set environment dynamically for dispatcher. Therefore this hack. - args = [muster_python, - _get_script().replace('\\', '\\\\'), - "--paths", - metadata_path.replace('\\', '\\\\'), - "--pype", - pype_root.replace('\\', '\\\\')] - - postjob_command = " ".join(args) - - try: - # Ensure render folder exists - os.makedirs(dirname) - except OSError: - pass - - env = self.clean_environment() - - payload = { - "RequestData": { - "platform": 0, - "job": { - "jobName": jobname, - "templateId": _get_template_id( - instance.data["renderer"]), - "chunksInterleave": 2, - "chunksPriority": "0", - "chunksTimeoutValue": 320, - "department": "", - "dependIds": [""], - "dependLinkMode": 0, - "dependMode": 0, - "emergencyQueue": False, - "excludedPools": [""], - "includedPools": [renderglobals["Pool"]], - "packetSize": 4, - "packetType": 1, - "priority": 1, - "jobId": -1, - "startOn": 0, - "parentId": -1, - "project": project_name or scene, - "shot": asset_name or scene, - "camera": instance.data.get("cameras")[0], - "dependMode": 0, - "packetSize": 4, - "packetType": 1, - "priority": 1, - "maximumInstances": 0, - "assignedInstances": 0, - "attributes": { - "environmental_variables": { - "value": ", ".join("{!s}={!r}".format(k, v) - for (k, v) in env.items()), - - "state": True, - "subst": False - }, - "memo": { - "value": comment, - "state": True, - "subst": False - }, - "frames_range": { - "value": "{start}-{end}".format( - start=int(instance.data["frameStart"]), - end=int(instance.data["frameEnd"])), - "state": True, - "subst": False - }, - "job_file": { - "value": filepath, - "state": True, - "subst": True - }, - "job_project": { - "value": workspace, - "state": True, - "subst": True - }, - "output_folder": { - "value": dirname.replace("\\", "/"), - "state": True, - "subst": True - }, - "post_job_action": { - "value": postjob_command, - "state": True, - "subst": True - }, - "MAYADIGITS": { - "value": 1, - "state": True, - "subst": False - }, - "ARNOLDMODE": { - "value": "0", - "state": True, - "subst": False - }, - "ABORTRENDER": { - "value": "0", - "state": True, - "subst": True - }, - "ARNOLDLICENSE": { - "value": "0", - "state": False, - "subst": False - }, - "ADD_FLAGS": { - "value": "-rl {}".format(renderlayer), - "state": True, - "subst": True - } - } - } - } - } - - self.preflight_check(instance) - - self.log.debug("Submitting ...") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - - response = self._submit(payload) - # response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - - # Store output dir for unified publisher (filesequence) - - instance.data["musterSubmissionJob"] = response.json() - - def clean_environment(self): - """ - Clean and set environment variables for render job so render clients - work in more or less same environment as publishing machine. - - .. warning:: This is not usable for **post job action** as this is - executed on dispatcher machine (server) and not render clients. - """ - keys = [ - # This will trigger `userSetup.py` on the slave - # such that proper initialisation happens the same - # way as it does on a local machine. - # TODO(marcus): This won't work if the slaves don't - # have access to these paths, such as if slaves are - # running Linux and the submitter is on Windows. - "PYTHONPATH", - "PATH", - - "MTOA_EXTENSIONS_PATH", - "MTOA_EXTENSIONS", - "DYLD_LIBRARY_PATH", - "MAYA_RENDER_DESC_PATH", - "MAYA_MODULE_PATH", - "ARNOLD_PLUGIN_PATH", - "FTRACK_API_KEY", - "FTRACK_API_USER", - "FTRACK_SERVER", - "PYBLISHPLUGINPATH", - - # todo: This is a temporary fix for yeti variables - "PEREGRINEL_LICENSE", - "SOLIDANGLE_LICENSE", - "ARNOLD_LICENSE" - "MAYA_MODULE_PATH", - "TOOL_ENV" - ] - environment = dict({key: os.environ[key] for key in keys - if key in os.environ}, **legacy_io.Session) - # self.log.debug("enviro: {}".format(pprint(environment))) - for path in os.environ: - if path.lower().startswith('pype_'): - environment[path] = os.environ[path] - - environment["PATH"] = os.environ["PATH"] - # self.log.debug("enviro: {}".format(environment['OPENPYPE_SCRIPTS'])) - clean_environment = {} - for key, value in environment.items(): - clean_path = "" - self.log.debug("key: {}".format(key)) - if "://" in value: - clean_path = value - else: - valid_paths = [] - for path in value.split(os.pathsep): - if not path: - continue - try: - path.decode('UTF-8', 'strict') - valid_paths.append(os.path.normpath(path)) - except UnicodeDecodeError: - print('path contains non UTF characters') - - if valid_paths: - clean_path = os.pathsep.join(valid_paths) - - clean_environment[key] = clean_path - - return clean_environment - - def preflight_check(self, instance): - """Ensure the startFrame, endFrame and byFrameStep are integers""" - - for key in ("frameStart", "frameEnd", "byFrameStep"): - value = instance.data[key] - - if int(value) == value: - continue - - self.log.warning( - "%f=%d was rounded off to nearest integer" - % (value, int(value)) - ) - - -# TODO: Remove hack to avoid this plug-in in new publisher -# This plug-in should actually be in dedicated module -if not os.environ.get("MUSTER_REST_URL"): - del MayaSubmitMuster diff --git a/openpype/modules/muster/plugins/publish/validate_muster_connection.py b/openpype/modules/muster/plugins/publish/validate_muster_connection.py deleted file mode 100644 index c31ccf405c..0000000000 --- a/openpype/modules/muster/plugins/publish/validate_muster_connection.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -import json - -import appdirs - -import pyblish.api -from openpype.lib import requests_get -from openpype.pipeline.publish import ( - context_plugin_should_run, - RepairAction, -) - - -class ValidateMusterConnection(pyblish.api.ContextPlugin): - """ - Validate Muster REST API Service is running and we have valid auth token - """ - - label = "Validate Muster REST API Service" - order = pyblish.api.ValidatorOrder - hosts = ["maya"] - families = ["renderlayer"] - token = None - if not os.environ.get("MUSTER_REST_URL"): - active = False - actions = [RepairAction] - - def process(self, context): - - # Workaround bug pyblish-base#250 - if not context_plugin_should_run(self, context): - return - - # test if we have environment set (redundant as this plugin shouldn' - # be active otherwise). - try: - MUSTER_REST_URL = os.environ["MUSTER_REST_URL"] - except KeyError: - self.log.error("Muster REST API url not found.") - raise ValueError("Muster REST API url not found.") - - # Load credentials - try: - self._load_credentials() - except RuntimeError: - self.log.error("invalid or missing access token") - - assert self._token is not None, "Invalid or missing token" - - # We have token, lets do trivial query to web api to see if we can - # connect and access token is valid. - params = { - 'authToken': self._token - } - api_entry = '/api/pools/list' - response = requests_get( - MUSTER_REST_URL + api_entry, params=params) - assert response.status_code == 200, "invalid response from server" - assert response.json()['ResponseData'], "invalid data in response" - - def _load_credentials(self): - """ - Load Muster credentials from file and set `MUSTER_USER`, - `MUSTER_PASSWORD`, `MUSTER_REST_URL` is loaded from settings. - - .. todo:: - - Show login dialog if access token is invalid or missing. - """ - app_dir = os.path.normpath( - appdirs.user_data_dir('pype-app', 'pype') - ) - file_name = 'muster_cred.json' - fpath = os.path.join(app_dir, file_name) - file = open(fpath, 'r') - muster_json = json.load(file) - self._token = muster_json.get('token', None) - if not self._token: - raise RuntimeError("Invalid access token for Muster") - file.close() - self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL") - if not self.MUSTER_REST_URL: - raise AttributeError("Muster REST API url not set") - - @classmethod - def repair(cls, instance): - """ - Renew authentication token by logging into Muster - """ - api_url = "{}/muster/show_login".format( - os.environ["OPENPYPE_WEBSERVER_URL"]) - cls.log.debug(api_url) - response = requests_get(api_url, timeout=1) - if response.status_code != 200: - cls.log.error('Cannot show login form to Muster') - raise Exception('Cannot show login form to Muster') diff --git a/openpype/modules/muster/rest_api.py b/openpype/modules/muster/rest_api.py deleted file mode 100644 index bea759919d..0000000000 --- a/openpype/modules/muster/rest_api.py +++ /dev/null @@ -1,22 +0,0 @@ -from aiohttp.web_response import Response - - -class MusterModuleRestApi: - def __init__(self, user_module, server_manager): - self.module = user_module - self.server_manager = server_manager - - self.prefix = "/muster" - - self.register() - - def register(self): - self.server_manager.add_route( - "GET", - self.prefix + "/show_login", - self.show_login_widget - ) - - async def show_login_widget(self, request): - self.module.action_show_login.trigger() - return Response(status=200) diff --git a/openpype/modules/muster/widget_login.py b/openpype/modules/muster/widget_login.py deleted file mode 100644 index f38f43fb7f..0000000000 --- a/openpype/modules/muster/widget_login.py +++ /dev/null @@ -1,165 +0,0 @@ -from qtpy import QtCore, QtGui, QtWidgets -from openpype import resources, style - - -class MusterLogin(QtWidgets.QWidget): - - SIZE_W = 300 - SIZE_H = 150 - - loginSignal = QtCore.Signal(object, object, object) - - def __init__(self, module, parent=None): - - super(MusterLogin, self).__init__(parent) - - self.module = module - - # Icon - icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) - self.setWindowIcon(icon) - - self.setWindowFlags( - QtCore.Qt.WindowCloseButtonHint | - QtCore.Qt.WindowMinimizeButtonHint - ) - - self._translate = QtCore.QCoreApplication.translate - - # Font - self.font = QtGui.QFont() - self.font.setFamily("DejaVu Sans Condensed") - self.font.setPointSize(9) - self.font.setBold(True) - self.font.setWeight(50) - self.font.setKerning(True) - - # Size setting - self.resize(self.SIZE_W, self.SIZE_H) - self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) - self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100)) - self.setStyleSheet(style.load_stylesheet()) - - self.setLayout(self._main()) - self.setWindowTitle('Muster login') - - def _main(self): - self.main = QtWidgets.QVBoxLayout() - self.main.setObjectName("main") - - self.form = QtWidgets.QFormLayout() - self.form.setContentsMargins(10, 15, 10, 5) - self.form.setObjectName("form") - - self.label_username = QtWidgets.QLabel("Username:") - self.label_username.setFont(self.font) - self.label_username.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - self.label_username.setTextFormat(QtCore.Qt.RichText) - - self.input_username = QtWidgets.QLineEdit() - self.input_username.setEnabled(True) - self.input_username.setFrame(True) - self.input_username.setPlaceholderText( - self._translate("main", "e.g. John Smith") - ) - - self.label_password = QtWidgets.QLabel("Password:") - self.label_password.setFont(self.font) - self.label_password.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - self.label_password.setTextFormat(QtCore.Qt.RichText) - - self.input_password = QtWidgets.QLineEdit() - self.input_password.setEchoMode(QtWidgets.QLineEdit.Password) - self.input_password.setEnabled(True) - self.input_password.setFrame(True) - self.input_password.setPlaceholderText( - self._translate("main", "e.g. ********") - ) - - self.error_label = QtWidgets.QLabel("") - self.error_label.setFont(self.font) - self.error_label.setStyleSheet('color: #FC6000') - self.error_label.setWordWrap(True) - self.error_label.hide() - - self.form.addRow(self.label_username, self.input_username) - self.form.addRow(self.label_password, self.input_password) - self.form.addRow(self.error_label) - - self.btn_group = QtWidgets.QHBoxLayout() - self.btn_group.addStretch(1) - self.btn_group.setObjectName("btn_group") - - self.btn_ok = QtWidgets.QPushButton("Ok") - self.btn_ok.clicked.connect(self.click_ok) - - self.btn_cancel = QtWidgets.QPushButton("Cancel") - QtWidgets.QShortcut( - QtGui.QKeySequence( - QtCore.Qt.Key_Escape), self).activated.connect(self.close) - self.btn_cancel.clicked.connect(self.close) - - self.btn_group.addWidget(self.btn_ok) - self.btn_group.addWidget(self.btn_cancel) - - self.main.addLayout(self.form) - self.main.addLayout(self.btn_group) - - return self.main - - def keyPressEvent(self, key_event): - if key_event.key() == QtCore.Qt.Key_Return: - if self.input_username.hasFocus(): - self.input_password.setFocus() - - elif self.input_password.hasFocus() or self.btn_ok.hasFocus(): - self.click_ok() - - elif self.btn_cancel.hasFocus(): - self.close() - else: - super().keyPressEvent(key_event) - - def setError(self, msg): - self.error_label.setText(msg) - self.error_label.show() - - def invalid_input(self, entity): - entity.setStyleSheet("border: 1px solid red;") - - def click_ok(self): - # all what should happen - validations and saving into appsdir - username = self.input_username.text() - password = self.input_password.text() - # TODO: more robust validation. Password can be empty in muster? - if not username: - self.setError("Username cannot be empty") - self.invalid_input(self.input_username) - try: - self.save_credentials(username, password) - except Exception as e: - self.setError( - "Cannot get auth token:\n{}".format(e)) - else: - self._close_widget() - - def save_credentials(self, username, password): - self.module.get_auth_token(username, password) - - def showEvent(self, event): - super(MusterLogin, self).showEvent(event) - - # Make btns same width - max_width = max( - self.btn_ok.sizeHint().width(), - self.btn_cancel.sizeHint().width() - ) - self.btn_ok.setMinimumWidth(max_width) - self.btn_cancel.setMinimumWidth(max_width) - - def closeEvent(self, event): - event.ignore() - self._close_widget() - - def _close_widget(self): - self.hide() diff --git a/openpype/plugins/publish/collect_farm_target.py b/openpype/plugins/publish/collect_farm_target.py index adcd842b48..2f77c823d7 100644 --- a/openpype/plugins/publish/collect_farm_target.py +++ b/openpype/plugins/publish/collect_farm_target.py @@ -19,7 +19,7 @@ class CollectFarmTarget(pyblish.api.InstancePlugin): farm_name = "" op_modules = context.data.get("openPypeModules") - for farm_renderer in ["deadline", "royalrender", "muster"]: + for farm_renderer in ["deadline", "royalrender"]: op_module = op_modules.get(farm_renderer, False) if op_module and op_module.enabled: diff --git a/openpype/plugins/publish/collect_rendered_files.py b/openpype/plugins/publish/collect_rendered_files.py index 6160b4f5c8..baaf454a11 100644 --- a/openpype/plugins/publish/collect_rendered_files.py +++ b/openpype/plugins/publish/collect_rendered_files.py @@ -93,14 +93,6 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): assert ctx.get("user") == data.get("user"), ctx_err % "user" assert ctx.get("version") == data.get("version"), ctx_err % "version" - # ftrack credentials are passed as environment variables by Deadline - # to publish job, but Muster doesn't pass them. - if data.get("ftrack") and not os.environ.get("FTRACK_API_USER"): - ftrack = data.get("ftrack") - os.environ["FTRACK_API_USER"] = ftrack["FTRACK_API_USER"] - os.environ["FTRACK_API_KEY"] = ftrack["FTRACK_API_KEY"] - os.environ["FTRACK_SERVER"] = ftrack["FTRACK_SERVER"] - # now we can just add instances from json file and we are done any_staging_dir_persistent = False for instance_data in data.get("instances"): diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index a6d90d1cf0..9d3366961f 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -220,22 +220,6 @@ def _convert_deadline_system_settings( output["modules"]["deadline"] = deadline_settings -def _convert_muster_system_settings( - ayon_settings, output, addon_versions, default_settings -): - enabled = addon_versions.get("muster") is not None - muster_settings = default_settings["modules"]["muster"] - muster_settings["enabled"] = enabled - if enabled: - ayon_muster = ayon_settings["muster"] - muster_settings["MUSTER_REST_URL"] = ayon_muster["MUSTER_REST_URL"] - muster_settings["templates_mapping"] = { - item["name"]: item["value"] - for item in ayon_muster["templates_mapping"] - } - output["modules"]["muster"] = muster_settings - - def _convert_royalrender_system_settings( ayon_settings, output, addon_versions, default_settings ): @@ -261,7 +245,6 @@ def _convert_modules_system( _convert_timers_manager_system_settings, _convert_clockify_system_settings, _convert_deadline_system_settings, - _convert_muster_system_settings, _convert_royalrender_system_settings, ): func(ayon_settings, output, addon_versions, default_settings) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index bb943524f1..22daae3b34 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -164,23 +164,6 @@ "default": "http://127.0.0.1:8082" } }, - "muster": { - "enabled": false, - "MUSTER_REST_URL": "http://127.0.0.1:9890", - "templates_mapping": { - "file_layers": 7, - "mentalray": 2, - "mentalray_sf": 6, - "redshift": 55, - "renderman": 29, - "software": 1, - "software_sf": 5, - "turtle": 10, - "vector": 4, - "vray": 37, - "ffmpeg": 48 - } - }, "royalrender": { "enabled": false, "rr_paths": { diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index c333628b25..eb74dd7a9c 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -645,7 +645,7 @@ How output of the schema could look like on save: }, "is_group": true, "key": "templates_mapping", - "label": "Muster - Templates mapping", + "label": "Deadline - Templates mapping", "is_file": true } ``` @@ -657,7 +657,7 @@ How output of the schema could look like on save: "object_type": "text", "is_group": true, "key": "templates_mapping", - "label": "Muster - Templates mapping", + "label": "Deadline - Templates mapping", "is_file": true } ``` diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json index 5b189eae88..88ef6f7515 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_modules.json +++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json @@ -207,37 +207,6 @@ } ] }, - { - "type": "dict", - "key": "muster", - "label": "Muster", - "require_restart": true, - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "MUSTER_REST_URL", - "label": "Muster Rest URL" - }, - { - "type": "dict-modifiable", - "object_type": { - "type": "number", - "minimum": 0, - "maximum": 300 - }, - "is_group": true, - "key": "templates_mapping", - "label": "Templates mapping" - } - ] - }, { "type": "dict", "key": "royalrender", diff --git a/openpype/tools/settings/settings/README.md b/openpype/tools/settings/settings/README.md index c29664a907..8f4ec00a76 100644 --- a/openpype/tools/settings/settings/README.md +++ b/openpype/tools/settings/settings/README.md @@ -334,7 +334,7 @@ }, "is_group": true, "key": "templates_mapping", - "label": "Muster - Templates mapping", + "label": "Deadline - Templates mapping", "is_file": true } ``` @@ -346,7 +346,7 @@ "object_type": "text", "is_group": true, "key": "templates_mapping", - "label": "Muster - Templates mapping", + "label": "Deadline - Templates mapping", "is_file": true } ``` diff --git a/server_addon/muster/server/__init__.py b/server_addon/muster/server/__init__.py deleted file mode 100644 index 2cb8943554..0000000000 --- a/server_addon/muster/server/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Type - -from ayon_server.addons import BaseServerAddon - -from .version import __version__ -from .settings import MusterSettings, DEFAULT_VALUES - - -class MusterAddon(BaseServerAddon): - name = "muster" - version = __version__ - title = "Muster" - settings_model: Type[MusterSettings] = MusterSettings - - async def get_default_settings(self): - settings_model_cls = self.get_settings_model() - return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/muster/server/settings.py b/server_addon/muster/server/settings.py deleted file mode 100644 index e37c762870..0000000000 --- a/server_addon/muster/server/settings.py +++ /dev/null @@ -1,41 +0,0 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel - - -class TemplatesMapping(BaseSettingsModel): - _layout = "compact" - name: str = Field(title="Name") - value: int = Field(title="mapping") - - -class MusterSettings(BaseSettingsModel): - enabled: bool = True - MUSTER_REST_URL: str = Field( - "", - title="Muster Rest URL", - scope=["studio"], - ) - - templates_mapping: list[TemplatesMapping] = Field( - default_factory=list, - title="Templates mapping", - ) - - -DEFAULT_VALUES = { - "enabled": False, - "MUSTER_REST_URL": "http://127.0.0.1:9890", - "templates_mapping": [ - {"name": "file_layers", "value": 7}, - {"name": "mentalray", "value": 2}, - {"name": "mentalray_sf", "value": 6}, - {"name": "redshift", "value": 55}, - {"name": "renderman", "value": 29}, - {"name": "software", "value": 1}, - {"name": "software_sf", "value": 5}, - {"name": "turtle", "value": 10}, - {"name": "vector", "value": 4}, - {"name": "vray", "value": 37}, - {"name": "ffmpeg", "value": 48} - ] -} diff --git a/server_addon/muster/server/version.py b/server_addon/muster/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/muster/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" diff --git a/start.py b/start.py index 54a252ccf3..e4ada05822 100644 --- a/start.py +++ b/start.py @@ -1186,10 +1186,6 @@ def get_info(use_staging=None) -> list: inf.append(("Using Deadline webservice at", os.environ.get("DEADLINE_REST_URL"))) - if os.environ.get('MUSTER_REST_URL'): - inf.append(("Using Muster at", - os.environ.get("MUSTER_REST_URL"))) - # Reinitialize Logger.initialize() diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md index 8abcefd24d..aefeaf893a 100644 --- a/website/docs/admin_settings_system.md +++ b/website/docs/admin_settings_system.md @@ -95,13 +95,6 @@ Disable/Enable Standalone Publisher option **`Deadline Rest URL`** - URL to deadline webservice that. This URL must be reachable from every workstation that should be submitting render jobs to deadline via OpenPype. -### Muster - -**`Muster Rest URL`** - URL to Muster webservice that. This URL must be reachable from every -workstation that should be submitting render jobs to muster via OpenPype. - -**`templates mapping`** - you can customize Muster templates to match your existing setup here. - ### Royal Render **`Royal Render Root Paths`** - multi platform paths to Royal Render installation. diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index e36ccb77d2..364461f4b6 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -386,14 +386,11 @@ models you've put into layout. OpenPype in Maya can be used for submitting renders to render farm and for their subsequent publishing. Right now OpenPype support [AWS Thinkbox Deadline](https://www.awsthinkbox.com/deadline) -and [Virtual Vertex Muster](https://www.vvertex.com/overview/). +and [Royal Render](https://www.royalrender.de/). -* For setting up Muster support see [admin section](module_muster.md) +* For setting up Royal Render support see [admin section](module_royalrender.md) * For setting up Deadline support see [here](module_deadline.md) -:::note Muster login -Muster is now configured so every user must log in to get authentication support. If OpenPype founds out this token is missing or expired, it will ask again for credentials. -::: ### Creating basic render setup @@ -436,12 +433,7 @@ checked **Use selection** it will use your current Render Layers (if you have th if no render layers is present in scene, it will create one for you named **Main** and under it default collection with `*` selector. -No matter if you use *Deadline* or *Muster*, OpenPype will try to connect to render farm and -fetch machine pool list. - -:::note Muster login -This might fail on *Muster* in the event that you have expired authentication token. In that case, you'll be presented with login window. Nothing will be created in the scene until you log in again and do create **Render** again. -::: +OpenPype will try to connect to render farm and fetch machine pool list. So now my scene now looks like this: diff --git a/website/docs/dev_settings.md b/website/docs/dev_settings.md index 1010169a5f..831536c460 100644 --- a/website/docs/dev_settings.md +++ b/website/docs/dev_settings.md @@ -34,7 +34,7 @@ As was mentioned schema items define output type of values, how they are stored - `"is_file"` - this key is used when defaults values are stored in the file. Its value matches the filename where values are stored - key is validated, must be unique in hierarchy otherwise it won't be possible to store default values - make sense to fill it only if it's value if `true` - + - `"is_group"` - define that all values under a key in settings hierarchy will be overridden if any value is modified - this key is not allowed for all inputs as they may not have technical ability to handle it - key is validated, must be unique in hierarchy and is automatically filled on last possible item if is not defined in schemas @@ -710,7 +710,7 @@ How output of the schema could look like on save: "object_type": "text", "is_group": true, "key": "templates_mapping", - "label": "Muster - Templates mapping", + "label": "Deadline - Templates mapping", "is_file": true } ``` @@ -726,7 +726,7 @@ How output of the schema could look like on save: }, "is_group": true, "key": "templates_mapping", - "label": "Muster - Templates mapping", + "label": "Deadline - Templates mapping", "is_file": true } ``` diff --git a/website/docs/features.md b/website/docs/features.md index fd6196f71f..43fd522346 100644 --- a/website/docs/features.md +++ b/website/docs/features.md @@ -238,17 +238,11 @@ Create preview quicktimes from rendered frames publish rendered outputs to Avalon and Ftrack - ## Muster - -Publish to deadline from - +## Royal Render +Publish to Royal Render from Maya - Nuke -Create preview quicktimes from rendered frames - -publish rendered outputs to Avalon and Ftrack ## Clockify diff --git a/website/docs/module_muster.md b/website/docs/module_muster.md deleted file mode 100644 index 28c4b33aa8..0000000000 --- a/website/docs/module_muster.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -id: module_muster -title: Muster Administration -sidebar_label: Muster ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - - diff --git a/website/docs/pype2/admin_hosts.md b/website/docs/pype2/admin_hosts.md index 24efef7f05..f7aa5bd54a 100644 --- a/website/docs/pype2/admin_hosts.md +++ b/website/docs/pype2/admin_hosts.md @@ -157,6 +157,10 @@ machine setup on farm. If there is no mix of windows/linux machines on farm, the ## Virtual Vertex Muster +:::warning +Support for Muster was removed from OpenPype and AYON. +::: + Pype supports rendering with [Muster](https://www.vvertex.com/). To enable it: 1. Add `muster` to **init_env** to your `deploy.json` file: diff --git a/website/sidebars.js b/website/sidebars.js index b885181fb6..0d151433ea 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -110,7 +110,6 @@ module.exports = { "module_kitsu", "module_site_sync", "module_deadline", - "module_muster", "module_royalrender", "module_clockify", "module_slack" diff --git a/website/src/pages/index.js b/website/src/pages/index.js index 52302ec285..92ed404c35 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -29,7 +29,7 @@ const services = [ title: <>Extensible, description: ( <> - Project needs differ, clients differ and studios differ. OpenPype is designed to fit into your workflow and bend to your will. If a feature is missing, it can most probably be added. + Project needs differ, clients differ and studios differ. OpenPype is designed to fit into your workflow and bend to your will. If a feature is missing, it can most probably be added. ), }, @@ -310,7 +310,7 @@ function Home() {

Why choose openPype?

- Pipeline is the technical backbone of your production. It means, that whatever solution you use, it will cause vendor-lock to some extend. + Pipeline is the technical backbone of your production. It means, that whatever solution you use, it will cause vendor-lock to some extend. You can mitigate this risk by developing purely in-house tools, however, that just shifts the problem from a software vendor to your developers. Sooner or later, you'll hit the limits of such solution. In-house tools tend to be undocumented, narrow focused and heavily dependent on a very few or even a single developer.

@@ -332,7 +332,7 @@ function Home() { Maya - + Flame @@ -422,7 +422,7 @@ function Home() { Deadline - + Royal Render @@ -443,17 +443,12 @@ function Home() {

Planned or in development by us and OpenPype community.

- + Aquarium - - - Muster - - Hi Bob Bob From 0fc23ce2e7600c335bd05ccc642f742497a36282 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 15:06:31 +0000 Subject: [PATCH 058/281] Changed logic for creation of output node --- openpype/hosts/blender/api/render_lib.py | 70 ++++++++++++++++-------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index b437078ad8..cdccf13805 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -135,71 +135,95 @@ def set_render_passes(settings): return aov_list, custom_passes -def set_node_tree(output_path, name, aov_sep, ext, multilayer): +def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True tree = bpy.context.scene.node_tree + comp_layer_type = "CompositorNodeRLayers" + output_type = "CompositorNodeOutputFile" + # Get the Render Layers node rl_node = None for node in tree.nodes: - if node.bl_idname == "CompositorNodeRLayers": + if node.bl_idname == comp_layer_type: rl_node = node break # If there's not a Render Layers node, we create it if not rl_node: - rl_node = tree.nodes.new("CompositorNodeRLayers") + rl_node = tree.nodes.new(comp_layer_type) # Get the enabled output sockets, that are the active passes for the # render. # We also exclude some layers. - exclude_sockets = ["Image", "Alpha", "Noisy Image"] + exclude_sockets = ["Alpha", "Noisy Image"] passes = [ socket for socket in rl_node.outputs if socket.enabled and socket.name not in exclude_sockets ] - # Remove all output nodes + old_output = None + + # Remove all output nodes that inlcude "AYON" in the name. + # There should be only one. for node in tree.nodes: - if node.bl_idname == "CompositorNodeOutputFile": - tree.nodes.remove(node) + if node.bl_idname == output_type and "AYON" in node.name: + old_output = node + break # Create a new output node - output = tree.nodes.new("CompositorNodeOutputFile") + output = tree.nodes.new(output_type) image_settings = bpy.context.scene.render.image_settings output.format.file_format = image_settings.file_format + slots = None + # In case of a multilayer exr, we don't need to use the output node, # because the blender render already outputs a multilayer exr. - if ext == "exr" and multilayer: - output.layer_slots.clear() - return [] + multi_exr = ext == "exr" and multilayer + slots = output.layer_slots if multi_exr else output.file_slots + output.base_path = render_product if multi_exr else str(output_path) - output.file_slots.clear() - output.base_path = str(output_path) + slots.clear() aov_file_products = [] # For each active render pass, we add a new socket to the output node # and link it - for render_pass in passes: - filepath = f"{name}{aov_sep}{render_pass.name}.####" + for rpass in passes: + if rpass.name == "Image": + pass_name = "rgba" if multi_exr else "beauty" + else: + pass_name = rpass.name + filepath = f"{name}{aov_sep}{pass_name}.####" - output.file_slots.new(filepath) + slots.new(pass_name if multi_exr else filepath) filename = str(output_path / filepath.lstrip("/")) - aov_file_products.append((render_pass.name, filename)) + aov_file_products.append((rpass.name, filename)) - node_input = output.inputs[-1] + if not old_output: + node_input = output.inputs[-1] + tree.links.new(rpass, node_input) - tree.links.new(render_pass, node_input) + for link in tree.links: + if link.to_node == old_output: + socket_name = link.to_socket.name + new_socket = output.inputs.get(socket_name) + if new_socket: + tree.links.new(link.from_socket, new_socket) - return aov_file_products + if old_output: + output.location = old_output.location + tree.nodes.remove(old_output) + output.name = "AYON File Output" + + return [] if multi_exr else aov_file_products def imprint_render_settings(node, data): @@ -236,9 +260,11 @@ def prepare_rendering(asset_group): render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( - output_path, name, aov_sep, ext, multilayer) + output_path, render_product, name, aov_sep, ext, multilayer) - bpy.context.scene.render.filepath = render_product + # Clear the render filepath, so that the output is handled only by the + # output node in the compositor. + bpy.context.scene.render.filepath = "" render_settings = { "render_folder": render_folder, From fa5b9777a98cca34f5cd5412500f34825a6ff3d8 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 15:07:00 +0000 Subject: [PATCH 059/281] Create a new version of the workfile instead of overwriting current one --- openpype/hosts/blender/plugins/create/create_render.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/create/create_render.py b/openpype/hosts/blender/plugins/create/create_render.py index 7fb3e5eb00..e728579286 100644 --- a/openpype/hosts/blender/plugins/create/create_render.py +++ b/openpype/hosts/blender/plugins/create/create_render.py @@ -1,8 +1,10 @@ """Create render.""" import bpy +from openpype.lib import version_up from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.render_lib import prepare_rendering +from openpype.hosts.blender.api.workio import save_file class CreateRenderlayer(plugin.BaseCreator): @@ -37,6 +39,7 @@ class CreateRenderlayer(plugin.BaseCreator): # settings. Even the validator to check that the file is saved will # detect the file as saved, even if it isn't. The only solution for # now it is to force the file to be saved. - bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) + filepath = version_up(bpy.data.filepath) + save_file(filepath, copy=False) return collection From b012e169d413eb632fd347cdd8b52325034e7f50 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 21 Dec 2023 15:23:59 +0000 Subject: [PATCH 060/281] Changed error message Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/blender/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index d50bfad53d..1037854a2d 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -40,7 +40,7 @@ def prepare_scene_name( # Blender name for a collection or object cannot be longer than 63 # characters. If the name is longer, it will raise an error. if len(name) > 63: - raise ValueError(f"Asset name '{name}' is too long.") + raise ValueError(f"Scene name '{name}' would be too long.") return name From 1a39a5cc99ca18fcd2354a5e5d8e928ce67bc4df Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 22 Dec 2023 11:09:46 +0100 Subject: [PATCH 061/281] Unlock attributes only during the bake context, make sure attributes are relocked after to preserve the lock state of the original node being baked --- openpype/hosts/maya/api/lib.py | 171 +++++++++++++++++---------------- 1 file changed, 87 insertions(+), 84 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 3bc8b67a81..63f0812785 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2566,68 +2566,36 @@ def bake_to_world_space(nodes, """ @contextlib.contextmanager - def _revert_lock_attributes(node, new_node): - # Connect all attributes on the node except for transform - # attributes - attrs = _get_attrs(node) - attrs = set(attrs) - transform_attrs if attrs else [] - original_attrs_lock = {} + def _unlock_attr(attr): + """Unlock attribute during context if it is locked""" + if not cmds.getAttr(attr, lock=True): + # If not locked, do nothing + yield + return try: - for attr in attrs: - orig_node_attr = '{0}.{1}'.format(node, attr) - new_node_attr = '{0}.{1}'.format(new_node, attr) - original_attrs_lock[new_node_attr] = ( - cmds.getAttr(new_node_attr, lock=True) - ) - - # unlock to avoid connection errors - cmds.setAttr(new_node_attr, lock=False) - - cmds.connectAttr(orig_node_attr, - new_node_attr, - force=True) + cmds.setAttr(attr, lock=False) yield finally: - for attr, lock_state in original_attrs_lock.items(): - cmds.setAttr(attr, lock=lock_state) - - @contextlib.contextmanager - def _revert_lock_shape_attributes(node, new_node, shape): - # If shapes are also baked then connect those keyable attributes - if shape: - children_shapes = cmds.listRelatives(new_node, - children=True, - fullPath=True, - shapes=True) - if children_shapes: - orig_children_shapes = cmds.listRelatives(node, - children=True, - fullPath=True, - shapes=True) - original_shape_lock_attrs = {} - try: - for orig_shape, new_shape in zip(orig_children_shapes, - children_shapes): - attrs = _get_attrs(orig_shape) - for attr in attrs: - orig_node_attr = '{0}.{1}'.format(orig_shape, attr) - new_node_attr = '{0}.{1}'.format(new_shape, attr) - original_shape_lock_attrs[new_node_attr] = ( - cmds.getAttr(new_node_attr, lock=True) - ) - # unlock to avoid connection errors - cmds.setAttr(new_node_attr, lock=False) - - cmds.connectAttr(orig_node_attr, - new_node_attr, - force=True) - yield - finally: - for attr, lock_state in original_shape_lock_attrs.items(): - cmds.setAttr(attr, lock=lock_state) + cmds.setAttr(attr, lock=True) def _get_attrs(node): - """Workaround for buggy shape attribute listing with listAttr""" + """Workaround for buggy shape attribute listing with listAttr + + This will only return keyable settable attributes that have an + incoming connections (those that have a reason to be baked). + + Technically this *may* fail to return attributes driven by complex + expressions for which maya makes no connections, e.g. doing actual + `setAttr` calls in expressions. + + Arguments: + node (str): The node to list attributes for. + + Returns: + list: Keyable attributes with incoming connections. + The attribute may be locked. + + """ attrs = cmds.listAttr(node, write=True, scalar=True, @@ -2652,13 +2620,14 @@ def bake_to_world_space(nodes, return valid_attrs - transform_attrs = set(["t", "r", "s", - "tx", "ty", "tz", - "rx", "ry", "rz", - "sx", "sy", "sz"]) + transform_attrs = {"t", "r", "s", + "tx", "ty", "tz", + "rx", "ry", "rz", + "sx", "sy", "sz"} world_space_nodes = [] - with delete_after() as delete_bin: + with contextlib.ExitStack() as stack: + delete_bin = stack.enter_context(delete_after()) # Create the duplicate nodes that are in world-space connected to # the originals for node in nodes: @@ -2669,32 +2638,66 @@ def bake_to_world_space(nodes, new_node = cmds.duplicate(node, name=new_name, renameChildren=True)[0] # noqa - with _revert_lock_attributes(node, new_node): - with _revert_lock_shape_attributes(node, new_node): - # Parent to world - if cmds.listRelatives(new_node, parent=True): - new_node = cmds.parent(new_node, world=True)[0] - # Unlock transform attributes so constraint can be created - for attr in transform_attrs: - cmds.setAttr( - '{0}.{1}'.format(new_node, attr), lock=False) + # Parent new node to world + if cmds.listRelatives(new_node, parent=True): + new_node = cmds.parent(new_node, world=True)[0] - # Constraints - delete_bin.extend( - cmds.parentConstraint(node, new_node, mo=False)) - delete_bin.extend( - cmds.scaleConstraint(node, new_node, mo=False)) + # Temporarily unlock and passthrough connect all attributes + # so we can bake them over time + # Skip transform attributes because we will constrain them later + attrs = set(_get_attrs(node)) - transform_attrs + for attr in attrs: + orig_node_attr = "{}.{}".format(node, attr) + new_node_attr = "{}.{}".format(new_node, attr) - world_space_nodes.append(new_node) + # unlock during context to avoid connection errors + stack.enter_context(_unlock_attr(new_node_attr)) + cmds.connectAttr(orig_node_attr, + new_node_attr, + force=True) - bake(world_space_nodes, - frame_range=frame_range, - step=step, - simulation=simulation, - preserve_outside_keys=preserve_outside_keys, - disable_implicit_control=disable_implicit_control, - shape=shape) + # If shapes are also baked then also temporarily unlock and + # passthrough connect all shape attributes for baking + if shape: + children_shapes = cmds.listRelatives(new_node, + children=True, + fullPath=True, + shapes=True) + if children_shapes: + orig_children_shapes = cmds.listRelatives(node, + children=True, + fullPath=True, + shapes=True) + for orig_shape, new_shape in zip(orig_children_shapes, + children_shapes): + attrs = _get_attrs(orig_shape) + for attr in attrs: + orig_node_attr = "{}.{}".format(orig_shape, attr) + new_node_attr = "{}.{}".format(new_shape, attr) + + # unlock during context to avoid connection errors + stack.enter_context(_unlock_attr(new_node_attr)) + cmds.connectAttr(orig_node_attr, + new_node_attr, + force=True) + + # Constraint transforms + for attr in transform_attrs: + transform_attr = "{}.{}".format(new_node, attr) + stack.enter_context(_unlock_attr(transform_attr)) + delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False)) + delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False)) + + world_space_nodes.append(new_node) + + bake(world_space_nodes, + frame_range=frame_range, + step=step, + simulation=simulation, + preserve_outside_keys=preserve_outside_keys, + disable_implicit_control=disable_implicit_control, + shape=shape) return world_space_nodes From eaa0a8f60eeac2e3441d2bf83aa395dca4222326 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 22 Dec 2023 22:21:47 +0800 Subject: [PATCH 062/281] make sure not overindented when calling bake function --- openpype/hosts/maya/api/lib.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 63f0812785..06cefbf793 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2691,13 +2691,13 @@ def bake_to_world_space(nodes, world_space_nodes.append(new_node) - bake(world_space_nodes, - frame_range=frame_range, - step=step, - simulation=simulation, - preserve_outside_keys=preserve_outside_keys, - disable_implicit_control=disable_implicit_control, - shape=shape) + bake(world_space_nodes, + frame_range=frame_range, + step=step, + simulation=simulation, + preserve_outside_keys=preserve_outside_keys, + disable_implicit_control=disable_implicit_control, + shape=shape) return world_space_nodes From 7f4f32f450807978567360fab2faf59d19c945df Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 12:21:16 +0100 Subject: [PATCH 063/281] add 'create_at' information to report item data --- openpype/tools/publisher/control.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index b02d83e4f6..125d93b6fd 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -4,6 +4,7 @@ import logging import traceback import collections import uuid +import datetime import tempfile import shutil import inspect @@ -285,6 +286,8 @@ class PublishReportMaker: def get_report(self, publish_plugins=None): """Report data with all details of current state.""" + + now = datetime.datetime.now() instances_details = {} for instance in self._all_instances_by_id.values(): instances_details[instance.id] = self._extract_instance_data( @@ -334,7 +337,8 @@ class PublishReportMaker: "context": self._extract_context_data(self._current_context), "crashed_file_paths": crashed_file_paths, "id": uuid.uuid4().hex, - "report_version": "1.0.0" + "created_at": now.strftime("%Y-%m-%d %H:%M:%S"), + "report_version": "1.0.1", } def _extract_context_data(self, context): From 1e78259d3a3c7f7a8ca50fb470957772d6d9fdd8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 12:23:44 +0100 Subject: [PATCH 064/281] added 'created_at' with auto fix for older report items --- .../publisher/publish_report_viewer/window.py | 149 +++++++++++++++--- 1 file changed, 129 insertions(+), 20 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index dc4ad70934..533b9de15f 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -2,6 +2,7 @@ import os import json import six import uuid +import datetime import appdirs from qtpy import QtWidgets, QtCore, QtGui @@ -47,47 +48,79 @@ class PublishReportItem: """Report item representing one file in report directory.""" def __init__(self, content): - item_id = content.get("id") - changed = False - if not item_id: - item_id = str(uuid.uuid4()) - changed = True - content["id"] = item_id + changed = self._fix_content(content) - if not content.get("report_version"): - changed = True - content["report_version"] = "0.0.1" - - report_path = os.path.join(get_reports_dir(), item_id) + report_path = os.path.join(get_reports_dir(), content["id"]) file_modified = None if os.path.exists(report_path): file_modified = os.path.getmtime(report_path) + + created_at_obj = datetime.datetime.strptime( + content["created_at"], "%Y-%m-%d %H:%M:%S" + ) + created_at = self.date_obj_to_timestamp(created_at_obj) + self.content = content self.report_path = report_path self.file_modified = file_modified + self.created_at = float(created_at) self._loaded_label = content.get("label") self._changed = changed self.publish_report = PublishReport(content) @property def version(self): + """Publish report version. + + Returns: + str: Publish report version. + """ return self.content["report_version"] @property def id(self): + """Publish report id. + + Returns: + str: Publish report id. + """ + return self.content["id"] def get_label(self): + """Publish report label. + + Returns: + str: Publish report label showed in UI. + """ + return self.content.get("label") or "Unfilled label" def set_label(self, label): + """Set publish report label. + + Args: + label (str): New publish report label. + """ + if not label: self.content.pop("label", None) self.content["label"] = label label = property(get_label, set_label) + @property + def loaded_label(self): + return self._loaded_label + + def mark_as_changed(self): + """Mark report as changed.""" + + self._changed = True + def save(self): + """Save publish report to file.""" + save = False if ( self._changed @@ -109,6 +142,15 @@ class PublishReportItem: @classmethod def from_filepath(cls, filepath): + """Create report item from file. + + Args: + filepath (str): Path to report file. Content must be json. + + Returns: + PublishReportItem: Report item. + """ + if not os.path.exists(filepath): return None @@ -116,15 +158,25 @@ class PublishReportItem: with open(filepath, "r") as stream: content = json.load(stream) - return cls(content) + file_modified = os.path.getmtime(filepath) + changed = cls._fix_content(content, file_modified=file_modified) + obj = cls(content) + if changed: + obj.mark_as_changed() + return obj + except Exception: return None def remove_file(self): + """Remove report file.""" + if os.path.exists(self.report_path): os.remove(self.report_path) def update_file_content(self): + """Update report content in file.""" + if not os.path.exists(self.report_path): return @@ -148,6 +200,63 @@ class PublishReportItem: self.content = content self.file_modified = file_modified + @staticmethod + def date_obj_to_timestamp(date_obj): + if hasattr(date_obj, "timestamp"): + return date_obj.timestamp() + + # Python 2 support + epoch = datetime.datetime.fromtimestamp(0) + return (date_obj - epoch).total_seconds() + + @classmethod + def _fix_content(cls, content, file_modified=None): + """Fix content for backward compatibility of older report items. + + Args: + content (dict[str, Any]): Report content. + file_modified (Optional[float]): File modification time. + + Returns: + bool: True if content was changed, False otherwise. + """ + + # Fix created_at key + changed = cls._fix_created_at(content, file_modified) + + # NOTE backward compatibility for 'id' and 'report_version' is from + # 28.10.2022 https://github.com/ynput/OpenPype/pull/4040 + # We can probably safely remove it + + # Fix missing 'id' + item_id = content.get("id") + if not item_id: + item_id = str(uuid.uuid4()) + changed = True + content["id"] = item_id + + # Fix missing 'report_version' + if not content.get("report_version"): + changed = True + content["report_version"] = "0.0.1" + return changed + + @classmethod + def _fix_created_at(cls, content, file_modified): + # Key 'create_at' was added in report version 1.0.1 + created_at = content.get("created_at") + if created_at: + return False + + # Auto fix 'created_at', use file modification time if it is not set + # , or current time if modification could not be received. + if file_modified is not None: + created_at_obj = datetime.datetime.fromtimestamp(file_modified) + else: + created_at_obj = datetime.datetime.now() + content["created_at"] = created_at_obj.strftime("%Y-%m-%d %H:%M:%S") + return True + class PublisherReportHandler: """Class handling storing publish report tool.""" @@ -278,16 +387,16 @@ class LoadedFilesModel(QtGui.QStandardItemModel): new_items = [] for normalized_path in filtered_paths: - try: - with open(normalized_path, "r") as stream: - data = json.load(stream) - report_item = PublishReportItem(data) - except Exception: - # TODO handle errors + report_item = PublishReportItem.from_filepath(normalized_path) + if report_item is None: continue - label = data.get("label") - if not label: + # Skip already added report items + # QUESTION: Should we replace existing or skip the item? + if report_item.id in self._items_by_id: + continue + + if not report_item.loaded_label: report_item.label = ( os.path.splitext(os.path.basename(filepath))[0] ) From 5f6d6ca1b3b55011735d468f7839c2076a4c0c8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 12:24:55 +0100 Subject: [PATCH 065/281] use created at as sorting value --- .../publisher/publish_report_viewer/window.py | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 533b9de15f..0d7042a69c 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -26,6 +26,7 @@ else: ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 +ITEM_CREATED_AT_ROLE = QtCore.Qt.UserRole + 2 def get_reports_dir(): @@ -300,9 +301,16 @@ class PublisherReportHandler: class LoadedFilesModel(QtGui.QStandardItemModel): + header_labels = ("Reports", "Created") + def __init__(self, *args, **kwargs): super(LoadedFilesModel, self).__init__(*args, **kwargs) + # Column count must be set before setting header data + self.setColumnCount(len(self.header_labels)) + for col, label in enumerate(self.header_labels): + self.setHeaderData(col, QtCore.Qt.Horizontal, label) + self._items_by_id = {} self._report_items_by_id = {} @@ -311,10 +319,15 @@ class LoadedFilesModel(QtGui.QStandardItemModel): self._loading_registry = False def refresh(self): - self._handler.reset() + root_item = self.invisibleRootItem() + if root_item.rowCount(): + root_item.removeRows(0, root_item.rowCount()) + self._items_by_id = {} self._report_items_by_id = {} + self._handler.reset() + new_items = [] for report_item in self._handler.list_reports(): item = self._create_item(report_item) @@ -326,26 +339,26 @@ class LoadedFilesModel(QtGui.QStandardItemModel): root_item = self.invisibleRootItem() root_item.appendRows(new_items) - def headerData(self, section, orientation, role): - if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): - if section == 0: - return "Exports" - if section == 1: - return "Modified" - return "" - super(LoadedFilesModel, self).headerData(section, orientation, role) - def data(self, index, role=None): if role is None: role = QtCore.Qt.DisplayRole col = index.column() + if col == 1: + if role in ( + QtCore.Qt.DisplayRole, QtCore.Qt.InitialSortOrderRole + ): + role = ITEM_CREATED_AT_ROLE + if col != 0: index = self.index(index.row(), 0, index.parent()) return super(LoadedFilesModel, self).data(index, role) - def setData(self, index, value, role): + def setData(self, index, value, role=None): + if role is None: + role = QtCore.Qt.EditRole + if role == QtCore.Qt.EditRole: item_id = index.data(ITEM_ID_ROLE) report_item = self._report_items_by_id.get(item_id) @@ -356,6 +369,12 @@ class LoadedFilesModel(QtGui.QStandardItemModel): return super(LoadedFilesModel, self).setData(index, value, role) + def flags(self, index): + # Allow editable flag only for first column + if index.column() > 0: + return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled + return super(LoadedFilesModel, self).flags(index) + def _create_item(self, report_item): if report_item.id in self._items_by_id: return None @@ -363,6 +382,7 @@ class LoadedFilesModel(QtGui.QStandardItemModel): item = QtGui.QStandardItem(report_item.label) item.setColumnCount(self.columnCount()) item.setData(report_item.id, ITEM_ID_ROLE) + item.setData(report_item.created_at, ITEM_CREATED_AT_ROLE) return item @@ -444,13 +464,18 @@ class LoadedFilesView(QtWidgets.QTreeView): ) self.setIndentation(0) self.setAlternatingRowColors(True) + self.setSortingEnabled(True) model = LoadedFilesModel() - self.setModel(model) + proxy_model = QtCore.QSortFilterProxyModel() + proxy_model.setSourceModel(model) + self.setModel(proxy_model) time_delegate = PrettyTimeDelegate() self.setItemDelegateForColumn(1, time_delegate) + self.sortByColumn(1, QtCore.Qt.AscendingOrder) + remove_btn = IconButton(self) remove_icon_path = resources.get_icon_path("delete") loaded_remove_image = QtGui.QImage(remove_icon_path) @@ -465,6 +490,7 @@ class LoadedFilesView(QtWidgets.QTreeView): ) self._model = model + self._proxy_model = proxy_model self._time_delegate = time_delegate self._remove_btn = remove_btn From 8bb497e64b91936e71b5b2263e34e937ac875022 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 12:25:13 +0100 Subject: [PATCH 066/281] renamed 'remove_report_items' tp 'remove_report_item' --- .../tools/publisher/publish_report_viewer/window.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 0d7042a69c..1557f2bd0b 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -290,7 +290,15 @@ class PublisherReportHandler: self._reports_by_id = reports_by_id return reports - def remove_report_items(self, item_id): + def remove_report_item(self, item_id): + """Remove report item by id. + + Remove from cache and also remove the file with the content. + + Args: + item_id (str): Report item id. + """ + item = self._reports_by_id.get(item_id) if item: try: @@ -439,7 +447,7 @@ class LoadedFilesModel(QtGui.QStandardItemModel): if not report_item: return - self._handler.remove_report_items(item_id) + self._handler.remove_report_item(item_id) item = self._items_by_id.get(item_id) parent = self.invisibleRootItem() From 64ef575ccecee9af8715fbcdaf8aca1c10825fcc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 12:25:29 +0100 Subject: [PATCH 067/281] modified docstring --- openpype/tools/publisher/publish_report_viewer/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 1557f2bd0b..ffb2d64be6 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -260,7 +260,7 @@ class PublishReportItem: class PublisherReportHandler: - """Class handling storing publish report tool.""" + """Class handling storing publish report items.""" def __init__(self): self._reports = None From 46f05968bcabf82e05b8f9d693ce23629abbd066 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 12:25:44 +0100 Subject: [PATCH 068/281] add loaded report item only if could be created --- openpype/tools/publisher/publish_report_viewer/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index ffb2d64be6..84b25467b4 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -283,8 +283,9 @@ class PublisherReportHandler: continue filepath = os.path.join(report_dir, filename) item = PublishReportItem.from_filepath(filepath) - reports.append(item) - reports_by_id[item.id] = item + if item is not None: + reports.append(item) + reports_by_id[item.id] = item self._reports = reports self._reports_by_id = reports_by_id From 25a0e64a068f50d2f955b6e2a95c743d7d742abc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 15:51:05 +0100 Subject: [PATCH 069/281] removed unnecessary 'date_obj_to_timestamp' --- .../tools/publisher/publish_report_viewer/window.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 84b25467b4..9dbe991201 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -59,7 +59,7 @@ class PublishReportItem: created_at_obj = datetime.datetime.strptime( content["created_at"], "%Y-%m-%d %H:%M:%S" ) - created_at = self.date_obj_to_timestamp(created_at_obj) + created_at = created_at_obj.timestamp() self.content = content self.report_path = report_path @@ -201,15 +201,6 @@ class PublishReportItem: self.content = content self.file_modified = file_modified - @staticmethod - def date_obj_to_timestamp(date_obj): - if hasattr(date_obj, "timestamp"): - return date_obj.timestamp() - - # Python 2 support - epoch = datetime.datetime.fromtimestamp(0) - return (date_obj - epoch).total_seconds() - @classmethod def _fix_content(cls, content, file_modified=None): """Fix content for backward compatibility of older report items. From 6db0dcbb44cc63c83113ef04af56e6e999cd5d41 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 27 Dec 2023 15:55:39 +0100 Subject: [PATCH 070/281] Fix weird comment --- openpype/tools/publisher/publish_report_viewer/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 9dbe991201..7cc7c6366a 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -241,7 +241,7 @@ class PublishReportItem: return False # Auto fix 'created_at', use file modification time if it is not set - # , or current time if modification could not be received. + # or current time if modification could not be received. if file_modified is not None: created_at_obj = datetime.datetime.fromtimestamp(file_modified) else: From f7fba84aeec00977ef893f6696eeb7c2ab8e749d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 16:33:22 +0100 Subject: [PATCH 071/281] fix '_fill_selection' by accessing model method --- openpype/tools/publisher/publish_report_viewer/window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 7cc7c6366a..ea78571223 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -538,7 +538,8 @@ class LoadedFilesView(QtWidgets.QTreeView): if index.isValid(): return - index = self._model.index(0, 0) + model = self.model() + index = model.index(0, 0) if index.isValid(): self.setCurrentIndex(index) From 78460c2d38d4111e3b3dd0403b2148eaa3159e88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 16:43:00 +0100 Subject: [PATCH 072/281] remove items from _report_items_by_id and _items_by_id --- .../tools/publisher/publish_report_viewer/window.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index ea78571223..283d6284d6 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -435,15 +435,13 @@ class LoadedFilesModel(QtGui.QStandardItemModel): root_item.appendRows(new_items) def remove_item_by_id(self, item_id): - report_item = self._report_items_by_id.get(item_id) - if not report_item: - return - self._handler.remove_report_item(item_id) - item = self._items_by_id.get(item_id) - parent = self.invisibleRootItem() - parent.removeRow(item.row()) + self._report_items_by_id.pop(item_id, None) + item = self._items_by_id.pop(item_id) + if item is not None: + parent = self.invisibleRootItem() + parent.removeRow(item.row()) def get_report_by_id(self, item_id): report_item = self._report_items_by_id.get(item_id) From 10125a40784e97e0a46f1e87063833ff98ccd7c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 16:45:49 +0100 Subject: [PATCH 073/281] avoid usage of 'clear' method --- .../tools/publisher/publish_report_viewer/model.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/model.py b/openpype/tools/publisher/publish_report_viewer/model.py index 663a67ac70..9d3c90d18f 100644 --- a/openpype/tools/publisher/publish_report_viewer/model.py +++ b/openpype/tools/publisher/publish_report_viewer/model.py @@ -26,14 +26,15 @@ class InstancesModel(QtGui.QStandardItemModel): return self._items_by_id def set_report(self, report_item): - self.clear() + root_item = self.invisibleRootItem() + if root_item.rowCount(): + root_item.removeRows(0, root_item.rowCount()) + self._items_by_id.clear() self._plugin_items_by_id.clear() if not report_item: return - root_item = self.invisibleRootItem() - families = set(report_item.instance_items_by_family.keys()) families.remove(None) all_families = list(sorted(families)) @@ -125,14 +126,14 @@ class PluginsModel(QtGui.QStandardItemModel): return self._items_by_id def set_report(self, report_item): - self.clear() + root_item = self.invisibleRootItem() + if root_item.rowCount() > 0: + root_item.removeRows(0, root_item.rowCount()) self._items_by_id.clear() self._plugin_items_by_id.clear() if not report_item: return - root_item = self.invisibleRootItem() - labels_iter = iter(self.order_label_mapping) cur_order, cur_label = next(labels_iter) cur_plugin_items = [] From 79f592aaf375282892d5e5e9e4951073e28bd482 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 16:52:29 +0100 Subject: [PATCH 074/281] fix 'pop' of item by id --- openpype/tools/publisher/publish_report_viewer/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 283d6284d6..8422f3aeab 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -438,7 +438,7 @@ class LoadedFilesModel(QtGui.QStandardItemModel): self._handler.remove_report_item(item_id) self._report_items_by_id.pop(item_id, None) - item = self._items_by_id.pop(item_id) + item = self._items_by_id.pop(item_id, None) if item is not None: parent = self.invisibleRootItem() parent.removeRow(item.row()) From 3d7e02a550c70ffa07e68c7c4c0950431681707f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 18:21:41 +0100 Subject: [PATCH 075/281] use utc isoformat with help of 'arrow' --- openpype/tools/publisher/control.py | 5 +++-- .../publisher/publish_report_viewer/window.py | 17 +++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 125d93b6fd..23456579ca 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -11,6 +11,7 @@ import inspect from abc import ABCMeta, abstractmethod import six +import arrow import pyblish.api from openpype import AYON_SERVER_ENABLED @@ -287,7 +288,7 @@ class PublishReportMaker: def get_report(self, publish_plugins=None): """Report data with all details of current state.""" - now = datetime.datetime.now() + now = arrow.utcnow().to("local") instances_details = {} for instance in self._all_instances_by_id.values(): instances_details[instance.id] = self._extract_instance_data( @@ -337,7 +338,7 @@ class PublishReportMaker: "context": self._extract_context_data(self._current_context), "crashed_file_paths": crashed_file_paths, "id": uuid.uuid4().hex, - "created_at": now.strftime("%Y-%m-%d %H:%M:%S"), + "created_at": now.isoformat(), "report_version": "1.0.1", } diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py index 8422f3aeab..f9c8c05802 100644 --- a/openpype/tools/publisher/publish_report_viewer/window.py +++ b/openpype/tools/publisher/publish_report_viewer/window.py @@ -2,9 +2,9 @@ import os import json import six import uuid -import datetime import appdirs +import arrow from qtpy import QtWidgets, QtCore, QtGui from openpype import style @@ -56,10 +56,8 @@ class PublishReportItem: if os.path.exists(report_path): file_modified = os.path.getmtime(report_path) - created_at_obj = datetime.datetime.strptime( - content["created_at"], "%Y-%m-%d %H:%M:%S" - ) - created_at = created_at_obj.timestamp() + created_at_obj = arrow.get(content["created_at"]).to("local") + created_at = created_at_obj.float_timestamp self.content = content self.report_path = report_path @@ -243,10 +241,10 @@ class PublishReportItem: # Auto fix 'created_at', use file modification time if it is not set # or current time if modification could not be received. if file_modified is not None: - created_at_obj = datetime.datetime.fromtimestamp(file_modified) + created_at_obj = arrow.Arrow.fromtimestamp(file_modified) else: - created_at_obj = datetime.datetime.now() - content["created_at"] = created_at_obj.strftime("%Y-%m-%d %H:%M:%S") + created_at_obj = arrow.utcnow() + content["created_at"] = created_at_obj.to("local").isoformat() return True @@ -320,9 +318,8 @@ class LoadedFilesModel(QtGui.QStandardItemModel): def refresh(self): root_item = self.invisibleRootItem() - if root_item.rowCount(): + if root_item.rowCount() > 0: root_item.removeRows(0, root_item.rowCount()) - self._items_by_id = {} self._report_items_by_id = {} From 3a22c1dd154fc15409d1a80930861335e5a98b88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 18:35:27 +0100 Subject: [PATCH 076/281] use explicit condition --- openpype/tools/publisher/publish_report_viewer/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/tools/publisher/publish_report_viewer/model.py b/openpype/tools/publisher/publish_report_viewer/model.py index 9d3c90d18f..460a269f1a 100644 --- a/openpype/tools/publisher/publish_report_viewer/model.py +++ b/openpype/tools/publisher/publish_report_viewer/model.py @@ -27,9 +27,8 @@ class InstancesModel(QtGui.QStandardItemModel): def set_report(self, report_item): root_item = self.invisibleRootItem() - if root_item.rowCount(): + if root_item.rowCount() > 0: root_item.removeRows(0, root_item.rowCount()) - self._items_by_id.clear() self._plugin_items_by_id.clear() if not report_item: From 53c593d41dae53d25c879ebcb4965c558278737b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Dec 2023 18:53:09 +0100 Subject: [PATCH 077/281] removed unused import --- openpype/tools/publisher/control.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 23456579ca..47e374edf2 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -4,7 +4,6 @@ import logging import traceback import collections import uuid -import datetime import tempfile import shutil import inspect From 333433057ea2728f1646a20ee602b72c40e5c1ba Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 3 Jan 2024 21:48:38 +0800 Subject: [PATCH 078/281] cosmetic tweaks regarding to Ondrej's comment --- openpype/hosts/max/api/lib_rendersettings.py | 15 ++++++--------- .../plugins/publish/save_scenes_for_cameras.py | 7 +++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 166076f008..be50e296eb 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -80,7 +80,7 @@ class RenderSettings(object): )] except KeyError: aov_separator = "." - output_filename = "{0}..{1}".format(output, img_fmt) + output_filename = f"{output}..{img_fmt}" output_filename = output_filename.replace("{aov_separator}", aov_separator) rt.rendOutputFilename = output_filename @@ -146,13 +146,13 @@ class RenderSettings(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = "{0}_{1}..{2}".format(dir, renderpass, ext) + aov_name = f"{dir}_{renderpass}..{ext}" render_elem.SetRenderElementFileName(i, aov_name) def get_render_output(self, container, output_dir): output = os.path.join(output_dir, container) img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - output_filename = "{0}..{1}".format(output, img_fmt) + output_filename = f"{output}..{img_fmt}" return output_filename def get_render_element(self): @@ -181,8 +181,7 @@ class RenderSettings(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = "{0}_{1}_{2}..{3}".format( - output, camera, renderpass, img_fmt) + aov_name = f"{output}_{camera}_{renderpass}..{img_fmt}" render_element_list.append(aov_name) return render_element_list @@ -205,8 +204,7 @@ class RenderSettings(object): for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = "{0}_{1}_{2}..{3}".format( - directory, camera, renderpass, ext) + aov_name = f"{directory}_{camera}_{renderpass}..{ext}" render_elem.SetRenderElementFileName(i, aov_name) def batch_render_layer(self, container, @@ -224,7 +222,6 @@ class RenderSettings(object): renderlayer = rt.batchRenderMgr.GetView(layer_no) # use camera name as renderlayer name renderlayer.name = cam - renderlayer.outputFilename = "{0}_{1}..{2}".format( - output, cam, img_fmt) + renderlayer.outputFilename = f"{output}_{cam}..{img_fmt}" outputs.append(renderlayer.outputFilename) return outputs diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index 79e9088ac7..c39109417b 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -31,13 +31,12 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): cameras = instance.data.get("cameras") if not cameras: return - new_folder = "{}_{}".format(current_folder, filename) + new_folder = f"{current_folder}_{filename}" os.makedirs(new_folder, exist_ok=True) for camera in cameras: new_output = RenderSettings().get_batch_render_output(camera) # noqa new_output = new_output.replace("\\", "/") - new_filename = "{}_{}{}".format( - filename, camera, ext) + new_filename = f"{filename}_{camera}{ext}" new_filepath = os.path.join(new_folder, new_filename) new_filepath = new_filepath.replace("\\", "/") camera_scene_files.append(new_filepath) @@ -61,7 +60,7 @@ if render_elem_num > 0: for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) target, renderpass = str(renderlayer_name).split(":") - aov_name = directory + "_" + camera + "_" + renderpass + "." + "." + ext # noqa + aov_name = f"{{directory}}_{camera}_{{renderpass}}..{ext}" render_elem.SetRenderElementFileName(i, aov_name) rt.saveMaxFile(new_filepath) """).format(filename=instance.name, From 9be69c3f304428792a454f1831a6d090ddf86231 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Jan 2024 00:25:34 +0800 Subject: [PATCH 079/281] implement exitstack functions from exitstack.py --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index eb83a44cde..da34896c3f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2839,7 +2839,7 @@ def bake_to_world_space(nodes, "sx", "sy", "sz"} world_space_nodes = [] - with contextlib.ExitStack() as stack: + with ExitStack() as stack: delete_bin = stack.enter_context(delete_after()) # Create the duplicate nodes that are in world-space connected to # the originals From 44128f77e5eda00ebf91f0d31a631f687424944b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Jan 2024 16:34:58 +0100 Subject: [PATCH 080/281] implemented the same logic to keep version on switch in ayon switch tool --- .../switch_dialog/dialog.py | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py index 2ebed7f89b..fade09305a 100644 --- a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py +++ b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py @@ -1212,12 +1212,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): )) version_ids = set() - version_docs_by_parent_id = {} + version_docs_by_parent_id_and_name = collections.defaultdict(dict) for version_doc in version_docs: - parent_id = version_doc["parent"] - if parent_id not in version_docs_by_parent_id: - version_ids.add(version_doc["_id"]) - version_docs_by_parent_id[parent_id] = version_doc + subset_id = version_doc["parent"] + name = version_doc["name"] + version_docs_by_parent_id_and_name[subset_id][name] = version_doc hero_version_docs_by_parent_id = {} for hero_version_doc in hero_version_docs: @@ -1242,7 +1241,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): selected_product_name, selected_representation, product_docs_by_parent_and_name, - version_docs_by_parent_id, + version_docs_by_parent_id_and_name, hero_version_docs_by_parent_id, repre_docs_by_parent_id_by_name, ) @@ -1256,10 +1255,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): container, loader, selected_folder_id, - product_name, + selected_product_name, selected_representation, product_docs_by_parent_and_name, - version_docs_by_parent_id, + version_docs_by_parent_id_and_name, hero_version_docs_by_parent_id, repre_docs_by_parent_id_by_name, ): @@ -1272,15 +1271,18 @@ class SwitchAssetDialog(QtWidgets.QDialog): container_product_id = container_version["parent"] container_product = self._product_docs_by_id[container_product_id] + container_product_name = container_product["name"] + + container_folder_id = container_product["parent"] if selected_folder_id: folder_id = selected_folder_id else: - folder_id = container_product["parent"] + folder_id = container_folder_id products_by_name = product_docs_by_parent_and_name[folder_id] - if product_name: - product_doc = products_by_name[product_name] + if selected_product_name: + product_doc = products_by_name[selected_product_name] else: product_doc = products_by_name[container_product["name"]] @@ -1300,7 +1302,26 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_doc = _repres.get(container_repre_name) if not repre_doc: - version_doc = version_docs_by_parent_id[product_id] + version_docs_by_name = ( + version_docs_by_parent_id_and_name[product_id] + ) + # If asset or subset are selected for switching, we use latest + # version else we try to keep the current container version. + version_name = None + if ( + selected_folder_id in (None, container_folder_id) + and selected_product_name in (None, container_product_name) + ): + version_name = container_version.get("name") + + version_doc = None + if version_name is not None: + version_doc = version_docs_by_name.get(version_name) + + if version_doc is None: + version_name = max(version_docs_by_name) + version_doc = version_docs_by_name[version_name] + version_id = version_doc["_id"] repres_by_name = repre_docs_by_parent_id_by_name[version_id] if selected_representation: From 4444f17892948748d67453132ed81ef2a27ae930 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Jan 2024 16:35:11 +0100 Subject: [PATCH 081/281] make version doc resolving a little bit more safe --- openpype/tools/sceneinventory/switch_dialog.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 150e369678..695f47b4d4 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -1299,15 +1299,21 @@ class SwitchAssetDialog(QtWidgets.QDialog): # If asset or subset are selected for switching, we use latest # version else we try to keep the current container version. + version_name = None if ( - selected_asset not in (None, container_asset_name) - or selected_subset not in (None, container_subset_name) + selected_asset in (None, container_asset_name) + and selected_subset in (None, container_subset_name) ): - version_name = max(version_docs_by_name) - else: - version_name = container_version["name"] + version_name = container_version.get("name") + + version_doc = None + if version_name is not None: + version_doc = version_docs_by_name.get(version_name) + + if version_doc is None: + version_name = max(version_docs_by_name) + version_doc = version_docs_by_name[version_name] - version_doc = version_docs_by_name[version_name] version_id = version_doc["_id"] repres_docs_by_name = repre_docs_by_parent_id_by_name[ version_id From 04dfe1863f267b210f6a3dc57b8f9890957e8d3c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 Jan 2024 20:46:27 +0800 Subject: [PATCH 082/281] Implement the validator for camera attributes in Max host --- openpype/hosts/max/api/action.py | 39 +++++++++++ .../publish/validate_camera_attributes.py | 59 +++++++++++++++++ openpype/pipeline/publish/lib.py | 2 +- .../defaults/project_settings/max.json | 10 +++ .../schemas/schema_max_publish.json | 66 +++++++++++++++++++ .../max/server/settings/publishers.py | 26 +++++++- server_addon/max/server/version.py | 2 +- 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/max/api/action.py create mode 100644 openpype/hosts/max/plugins/publish/validate_camera_attributes.py diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py new file mode 100644 index 0000000000..506847652b --- /dev/null +++ b/openpype/hosts/max/api/action.py @@ -0,0 +1,39 @@ +from pymxs import runtime as rt + +import pyblish.api + +from openpype.pipeline.publish import get_errored_instances_from_context + + +class SelectInvalidAction(pyblish.api.Action): + """Select invalid objects in Blender when a publish plug-in failed.""" + label = "Select Invalid" + on = "failed" + icon = "search" + + def process(self, context, plugin): + errored_instances = get_errored_instances_from_context(context, + plugin=plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes...") + invalid = list() + for instance in errored_instances: + invalid_nodes = plugin.get_invalid(instance) + if invalid_nodes: + if isinstance(invalid_nodes, (list, tuple)): + invalid.extend(invalid_nodes) + else: + self.log.warning( + "Failed plug-in doesn't have any selectable objects." + ) + + if not invalid: + self.log.info("No invalid nodes found.") + return + invalid_names = [obj.name for obj in invalid] + self.log.info( + "Selecting invalid objects: %s", ", ".join(invalid_names) + ) + + rt.Select(invalid) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py new file mode 100644 index 0000000000..e279b41475 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -0,0 +1,59 @@ +import pyblish.api +from pymxs import runtime as rt + +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) +from openpype.hosts.max.api.action import SelectInvalidAction + + +class ValidateCameraAttributes(OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin): + """Validates Camera has no invalid attribute properties + or values.(For 3dsMax Cameras only) + + """ + + order = pyblish.api.ValidatorOrder + families = ['camera'] + hosts = ['max'] + label = 'Camera Attributes' + actions = [SelectInvalidAction] + optional = True + + DEFAULTS = ["fov", "nearrange", "farrange", + "nearclip","farclip"] + CAM_TYPE = ["Freecamera", "Targetcamera", + "Physical"] + + @classmethod + def get_invalid(cls, instance): + invalid = [] + cameras = instance.data["members"] + project_settings = instance.context.data["project_settings"].get("max") + cam_attr_settings = project_settings["publish"]["ValidateCameraAttributes"] + for camera in cameras: + if str(rt.ClassOf(camera)) not in cls.CAM_TYPE: + cls.log.debug( + "Skipping camera created from external plugin..") + continue + for attr in cls.DEFAULTS: + default_value = cam_attr_settings.get(attr) + if rt.getProperty(camera, attr) != default_value: + cls.log.error( + f"Invalid attribute value: {attr} " + f"(should be: {default_value}))") + invalid.append(camera) + + return invalid + + def process(self, instance): + if not self.is_active(instance.data): + self.log.debug("Skipping Validate Camera Attributes...") + return + invalid = self.get_invalid(instance) + + if invalid: + raise PublishValidationError( + f"Invalid camera attributes: {invalid}") diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 4ea2f932f1..87ca3323cb 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -74,7 +74,7 @@ def get_template_name_profiles( project_settings ["global"] ["publish"] - ["IntegrateAssetNew"] + ["IntegrateHeroVersion"] ["template_name_profiles"] ) if legacy_profiles: diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d1610610dc..a0a4fcf83d 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -56,6 +56,16 @@ "enabled": false, "attributes": {} }, + "ValidateCameraAttributes": { + "enabled": true, + "optional": true, + "active": false, + "fov": 45.0, + "nearrange": 0.0, + "farrange": 1000.0, + "nearclip": 1.0, + "farclip": 1000.0 + }, "ValidateLoadedPlugin": { "enabled": false, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index b4d85bda98..90a5d2fc18 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -48,6 +48,72 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateCameraAttributes", + "label": "Validate Camera Attributes", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "number", + "key": "fov", + "label": "Focal Length", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "nearrange", + "label": "Near Range", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "farrange", + "label": "Far Range", + "decimal": 1, + "minimum": 0, + "maximum": 2000.0 + }, + { + "type": "number", + "key": "nearclip", + "label": "Near Clip", + "decimal": 1, + "minimum": 0, + "maximum": 100.0 + }, + { + "type": "number", + "key": "farclip", + "label": "Far Clip", + "decimal": 1, + "minimum": 0, + "maximum": 2000.0 + } + ] + }, + { "type": "dict", "collapsible": true, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index d40d85a99b..340f344a9f 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -27,6 +27,17 @@ class ValidateAttributesModel(BaseSettingsModel): return value +class ValidateCameraAttributesModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + fov: float = Field(0.0, title="Focal Length") + nearrange: float = Field(0.0, title="Near Range") + farrange: float = Field(0.0, title="Far Range") + nearclip: float = Field(0.0, title="Near Clip") + farclip: float = Field(0.0, title="Far Clip") + + class FamilyMappingItemModel(BaseSettingsModel): product_types: list[str] = Field( default_factory=list, @@ -63,7 +74,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateAttributesModel, title="Validate Attributes" ) - + ValidateCameraAttributes: ValidateCameraAttributesModel = Field( + default_factory=ValidateCameraAttributesModel, + title="Validate Camera Attributes" + ) ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" @@ -101,6 +115,16 @@ DEFAULT_PUBLISH_SETTINGS = { "enabled": False, "attributes": "{}" }, + "ValidateCameraAttributes": { + "enabled": True, + "optional": True, + "active": False, + "fov": 45.0, + "nearrange": 0.0, + "farrange": 1000.0, + "nearclip": 1.0, + "farclip": 1000.0 + }, "ValidateLoadedPlugin": { "enabled": False, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From c29a4fdfd64dccb22375749b766b9e01aca9d815 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 Jan 2024 21:08:51 +0800 Subject: [PATCH 083/281] hound shut --- .../hosts/max/plugins/publish/validate_camera_attributes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index e279b41475..d97d5529e3 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -23,7 +23,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, optional = True DEFAULTS = ["fov", "nearrange", "farrange", - "nearclip","farclip"] + "nearclip", "farclip"] CAM_TYPE = ["Freecamera", "Targetcamera", "Physical"] @@ -32,7 +32,9 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, invalid = [] cameras = instance.data["members"] project_settings = instance.context.data["project_settings"].get("max") - cam_attr_settings = project_settings["publish"]["ValidateCameraAttributes"] + cam_attr_settings = ( + project_settings["publish"]["ValidateCameraAttributes"] + ) for camera in cameras: if str(rt.ClassOf(camera)) not in cls.CAM_TYPE: cls.log.debug( From 0a6edc648c0b5866b6025d82e091494840b17878 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 10 Jan 2024 12:03:08 +0000 Subject: [PATCH 084/281] Fix problem with AVALON_CONTAINER collection and workfile instance --- openpype/hosts/blender/api/plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 1037854a2d..b1ff3e4a09 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -311,11 +311,13 @@ class BaseCreator(Creator): ) return - # Rename the instance node in the scene if subset or asset changed + # Rename the instance node in the scene if subset or asset changed. + # Do not rename the instance if the family is workfile, as the + # workfile instance is included in the AVALON_CONTAINER collection. if ( "subset" in changes.changed_keys or asset_name_key in changes.changed_keys - ): + ) and created_instance.family != "workfile": asset_name = data[asset_name_key] if AYON_SERVER_ENABLED: asset_name = asset_name.split("/")[-1] From 3c878ccc3e34d8bb6884e731cc035881a0449947 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 13:27:32 +0800 Subject: [PATCH 085/281] make sure the validator skips checking when the value of the attribute sets to zero --- .../max/plugins/publish/validate_camera_attributes.py | 11 ++++++++--- .../projects_schema/schemas/schema_max_publish.json | 4 ++++ server_addon/max/server/settings/publishers.py | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index d97d5529e3..36f0beab65 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -18,7 +18,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, order = pyblish.api.ValidatorOrder families = ['camera'] hosts = ['max'] - label = 'Camera Attributes' + label = 'Validate Camera Attributes' actions = [SelectInvalidAction] optional = True @@ -42,9 +42,14 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) + if default_value == 0: + cls.log.debug( + f"the value of {attr} in setting set to" + " zero. Skipping the check..") + continue if rt.getProperty(camera, attr) != default_value: cls.log.error( - f"Invalid attribute value: {attr} " + f"Invalid attribute value for {camera.name}:{attr} " f"(should be: {default_value}))") invalid.append(camera) @@ -58,4 +63,4 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, if invalid: raise PublishValidationError( - f"Invalid camera attributes: {invalid}") + "Invalid camera attributes found. See log.") diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index 90a5d2fc18..1e7a7c0c73 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -79,6 +79,10 @@ "minimum": 0, "maximum": 100.0 }, + { + "type": "label", + "label": "If the value of the camera attributes set to 0, the system automatically skips checking it" + }, { "type": "number", "key": "nearrange", diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 340f344a9f..a092f54a62 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -76,7 +76,11 @@ class PublishersModel(BaseSettingsModel): ) ValidateCameraAttributes: ValidateCameraAttributesModel = Field( default_factory=ValidateCameraAttributesModel, - title="Validate Camera Attributes" + title="Validate Camera Attributes", + description=( + "If the value of the camera attributes set to 0, " + "the system automatically skips checking it" + ) ) ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( default_factory=ValidateLoadedPluginModel, From 9baff5eccfea5e2c7a5dc8c15efc4b75a916799f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 18:04:19 +0800 Subject: [PATCH 086/281] add repair actions for validate camera attributes --- .../publish/validate_camera_attributes.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index 36f0beab65..94aa5742d8 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -2,6 +2,7 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline.publish import ( + RepairAction, OptionalPyblishPluginMixin, PublishValidationError ) @@ -19,7 +20,7 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, families = ['camera'] hosts = ['max'] label = 'Validate Camera Attributes' - actions = [SelectInvalidAction] + actions = [SelectInvalidAction, RepairAction] optional = True DEFAULTS = ["fov", "nearrange", "farrange", @@ -42,10 +43,11 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) - if default_value == 0: + cls.log.debug(f"default value: {default_value}") + if default_value == float(0): cls.log.debug( f"the value of {attr} in setting set to" - " zero. Skipping the check..") + " zero. Skipping the check.") continue if rt.getProperty(camera, attr) != default_value: cls.log.error( @@ -57,10 +59,26 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, def process(self, instance): if not self.is_active(instance.data): - self.log.debug("Skipping Validate Camera Attributes...") + self.log.debug("Skipping Validate Camera Attributes.") return invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( "Invalid camera attributes found. See log.") + + @classmethod + def repair(cls, instance): + invalid_cameras = cls.get_invalid(instance) + project_settings = instance.context.data["project_settings"].get("max") + cam_attr_settings = ( + project_settings["publish"]["ValidateCameraAttributes"] + ) + for camera in invalid_cameras: + for attr in cls.DEFAULTS: + expected_value = cam_attr_settings.get(attr) + if expected_value == float(0): + cls.log.debug( + f"the value of {attr} in setting set to zero.") + continue + rt.setProperty(camera, attr, expected_value) From 15167fa0b805b03657857097db52dd554c163620 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Jan 2024 22:46:21 +0800 Subject: [PATCH 087/281] the validator posts on warning message if the users dont use generic types & round 1 decimal places for all check values --- .../max/plugins/publish/validate_camera_attributes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py index 94aa5742d8..4eec1951e5 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_attributes.py @@ -31,6 +31,11 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, @classmethod def get_invalid(cls, instance): invalid = [] + if rt.units.DisplayType != rt.Name("Generic"): + cls.log.warning( + "Generic Type is not used as a scene unit\n\n" + "sure you tweak the settings with your own values\n\n" + "before validation.") cameras = instance.data["members"] project_settings = instance.context.data["project_settings"].get("max") cam_attr_settings = ( @@ -43,13 +48,12 @@ class ValidateCameraAttributes(OptionalPyblishPluginMixin, continue for attr in cls.DEFAULTS: default_value = cam_attr_settings.get(attr) - cls.log.debug(f"default value: {default_value}") if default_value == float(0): cls.log.debug( f"the value of {attr} in setting set to" " zero. Skipping the check.") continue - if rt.getProperty(camera, attr) != default_value: + if round(rt.getProperty(camera, attr), 1) != default_value: cls.log.error( f"Invalid attribute value for {camera.name}:{attr} " f"(should be: {default_value}))") From df7b8683716ff5bb2ab6b648ab22a62e2a555fd3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Jan 2024 18:32:42 +0800 Subject: [PATCH 088/281] bugfix the thumbnail error when publishing with emissive map & maps without RGB channel --- openpype/hosts/substancepainter/api/lib.py | 50 +++++++++++++++++++ .../publish/collect_textureset_images.py | 20 +++++--- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 1cb480b552..f46426388b 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -643,3 +643,53 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh + + +def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): + """Function to check whether the texture has RGB channel. + + Args: + texture_set_name (str): Name of Texture Set + map_identifier (str): Map identifier + + Returns: + colorspace_dict: A dictionary which stores the boolean + value of textures having RGB channels + """ + texture_stack = substance_painter.textureset.Stack.from_name(texture_set_name) + # 2D_View is always True as it exports all texture maps + colorspace_dict = {"2D_View": True} + colorspace_dict["BaseColor"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.BaseColor).is_color() + colorspace_dict["Roughness"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Roughness).is_color() + colorspace_dict["Metallic"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Metallic).is_color() + colorspace_dict["Height"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Height).is_color() + colorspace_dict["Normal"] = texture_stack.get_channel( + substance_painter.textureset.ChannelType.Normal).is_color() + return colorspace_dict.get(map_identifier, False) + + +def texture_set_filtering(texture_set_same, template): + """Function to check whether some specific textures(e.g. Emissive) + are parts of the texture stack in Substance Painter + + Args: + texture_set_same (str): Name of Texture Set + template (str): texture template name + + Returns: + texture_filter: A dictionary which stores the boolean + value of whether the texture exist in the channel. + """ + texture_filter = {} + channel_stack = substance_painter.textureset.Stack.from_name( + texture_set_same) + has_emissive = channel_stack.has_channel( + substance_painter.textureset.ChannelType.Emissive) + map_identifier = strip_template(template) + if map_identifier == "Emissive": + texture_filter[map_identifier] = has_emissive + return texture_filter.get(map_identifier, True) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 316f72509e..4c3398d5b4 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -7,7 +7,9 @@ from openpype.pipeline import publish import substance_painter.textureset from openpype.hosts.substancepainter.api.lib import ( get_parsed_export_maps, - strip_template + strip_template, + has_rgb_channel_in_texture_set, + texture_set_filtering ) from openpype.pipeline.create import get_subset_name from openpype.client import get_asset_by_name @@ -39,11 +41,12 @@ class CollectTextureSet(pyblish.api.InstancePlugin): for (texture_set_name, stack_name), template_maps in maps.items(): self.log.info(f"Processing {texture_set_name}/{stack_name}") for template, outputs in template_maps.items(): - self.log.info(f"Processing {template}") - self.create_image_instance(instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + if texture_set_filtering(texture_set_name, template): + self.log.info(f"Processing {template}") + self.create_image_instance(instance, template, outputs, + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): @@ -78,7 +81,6 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Always include the map identifier map_identifier = strip_template(template) suffix += f".{map_identifier}" - image_subset = get_subset_name( # TODO: The family actually isn't 'texture' currently but for now # this is only done so the subset name starts with 'texture' @@ -132,7 +134,9 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Store color space with the instance # Note: The extractor will assign it to the representation colorspace = outputs[0].get("colorSpace") - if colorspace: + has_rgb_channel = has_rgb_channel_in_texture_set( + texture_set_name, map_identifier) + if colorspace and has_rgb_channel: self.log.debug(f"{image_subset} colorspace: {colorspace}") image_instance.data["colorspace"] = colorspace From 72848657af4e52aa6d037fbf1f35c2b4548efeee Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Jan 2024 18:44:18 +0800 Subject: [PATCH 089/281] hound shut --- openpype/hosts/substancepainter/api/lib.py | 6 ++++-- .../plugins/publish/collect_textureset_images.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index f46426388b..67229a75bf 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -656,7 +656,9 @@ def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): colorspace_dict: A dictionary which stores the boolean value of textures having RGB channels """ - texture_stack = substance_painter.textureset.Stack.from_name(texture_set_name) + texture_stack = ( + substance_painter.textureset.Stack.from_name(texture_set_name) + ) # 2D_View is always True as it exports all texture maps colorspace_dict = {"2D_View": True} colorspace_dict["BaseColor"] = texture_stack.get_channel( @@ -686,7 +688,7 @@ def texture_set_filtering(texture_set_same, template): """ texture_filter = {} channel_stack = substance_painter.textureset.Stack.from_name( - texture_set_same) + texture_set_same) has_emissive = channel_stack.has_channel( substance_painter.textureset.ChannelType.Emissive) map_identifier = strip_template(template) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 4c3398d5b4..f535bfa2a6 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -44,9 +44,9 @@ class CollectTextureSet(pyblish.api.InstancePlugin): if texture_set_filtering(texture_set_name, template): self.log.info(f"Processing {template}") self.create_image_instance(instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): From 6410f381f34625defa91250a8e529e47cb40a8a6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Jan 2024 18:45:40 +0800 Subject: [PATCH 090/281] hound shut --- .../plugins/publish/collect_textureset_images.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index f535bfa2a6..9d6aa06872 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -43,10 +43,11 @@ class CollectTextureSet(pyblish.api.InstancePlugin): for template, outputs in template_maps.items(): if texture_set_filtering(texture_set_name, template): self.log.info(f"Processing {template}") - self.create_image_instance(instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + self.create_image_instance( + instance, template, outputs, + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): From f6b2b00d10c16c0e42bd5fe736d65c8008783518 Mon Sep 17 00:00:00 2001 From: jrsndlr Date: Fri, 12 Jan 2024 11:46:32 +0100 Subject: [PATCH 091/281] Swapping version fix offset, copy IDT --- openpype/hosts/resolve/api/lib.py | 5 +++++ openpype/hosts/resolve/api/plugin.py | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index 3866477c77..96caacf8d1 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -713,6 +713,11 @@ def swap_clips(from_clip, to_clip, to_in_frame, to_out_frame): bool: True if successfully replaced """ + # copy ACES input transform from timeline clip to new media item + mediapool_item_from_timeline = from_clip.GetMediaPoolItem() + _idt = mediapool_item_from_timeline.GetClipProperty('IDT') + to_clip.SetClipProperty('IDT', _idt) + _clip_prop = to_clip.GetClipProperty to_clip_name = _clip_prop("File Name") # add clip item as take to timeline diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index a00933405f..2346739e20 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -477,14 +477,16 @@ class ClipLoader: ) _clip_property = media_pool_item.GetClipProperty - source_in = int(_clip_property("Start")) - source_out = int(_clip_property("End")) + # Read trimming from timeline item + timeline_item_in = timeline_item.GetLeftOffset() + timeline_item_len = timeline_item.GetDuration() + timeline_item_out = timeline_item_in + timeline_item_len lib.swap_clips( timeline_item, media_pool_item, - source_in, - source_out + timeline_item_in, + timeline_item_out ) print("Loading clips: `{}`".format(self.data["clip_name"])) From 1eb7e59b931e146481253af9fab501120d1d6156 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:25:00 +0100 Subject: [PATCH 092/281] Kitsu clear credentials are safe (#6116) --- openpype/modules/kitsu/utils/credentials.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index 941343cc8d..c471b56907 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -64,8 +64,10 @@ def clear_credentials(): user_registry = OpenPypeSecureRegistry("kitsu_user") # Set local settings - user_registry.delete_item("login") - user_registry.delete_item("password") + if user_registry.get_item("login", None) is not None: + user_registry.delete_item("login") + if user_registry.get_item("password", None) is not None: + user_registry.delete_item("password") def save_credentials(login: str, password: str): @@ -92,8 +94,9 @@ def load_credentials() -> Tuple[str, str]: # Get user registry user_registry = OpenPypeSecureRegistry("kitsu_user") - return user_registry.get_item("login", None), user_registry.get_item( - "password", None + return ( + user_registry.get_item("login", None), + user_registry.get_item("password", None) ) From f88ab85cc1d586e71e6a4ccfb975ed7d5c75aef8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 Jan 2024 13:50:34 +0100 Subject: [PATCH 093/281] SceneInventory: Fix site sync icon conversion (#6123) * use 'get_qt_icon' to convert icon definition * check if site sync is enabled before getting sites info * convert containers to list * Fix wrong method name --------- Co-authored-by: Petr Kalis --- openpype/tools/ayon_sceneinventory/control.py | 4 ++-- openpype/tools/ayon_sceneinventory/model.py | 5 +++-- openpype/tools/ayon_sceneinventory/models/site_sync.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/tools/ayon_sceneinventory/control.py b/openpype/tools/ayon_sceneinventory/control.py index 6111d7e43b..3b063ff72e 100644 --- a/openpype/tools/ayon_sceneinventory/control.py +++ b/openpype/tools/ayon_sceneinventory/control.py @@ -84,9 +84,9 @@ class SceneInventoryController: def get_containers(self): host = self._host if isinstance(host, ILoadHost): - return host.get_containers() + return list(host.get_containers()) elif hasattr(host, "ls"): - return host.ls() + return list(host.ls()) return [] # Site Sync methods diff --git a/openpype/tools/ayon_sceneinventory/model.py b/openpype/tools/ayon_sceneinventory/model.py index 16924b0a7e..f4450f0ac3 100644 --- a/openpype/tools/ayon_sceneinventory/model.py +++ b/openpype/tools/ayon_sceneinventory/model.py @@ -23,6 +23,7 @@ from openpype.pipeline import ( ) from openpype.style import get_default_entity_icon_color from openpype.tools.utils.models import TreeModel, Item +from openpype.tools.ayon_utils.widgets import get_qt_icon def walk_hierarchy(node): @@ -71,8 +72,8 @@ class InventoryModel(TreeModel): site_icons = self._controller.get_site_provider_icons() self._site_icons = { - provider: QtGui.QIcon(icon_path) - for provider, icon_path in site_icons.items() + provider: get_qt_icon(icon_def) + for provider, icon_def in site_icons.items() } def outdated(self, item): diff --git a/openpype/tools/ayon_sceneinventory/models/site_sync.py b/openpype/tools/ayon_sceneinventory/models/site_sync.py index 0101f6c88e..bd65ad1778 100644 --- a/openpype/tools/ayon_sceneinventory/models/site_sync.py +++ b/openpype/tools/ayon_sceneinventory/models/site_sync.py @@ -150,23 +150,23 @@ class SiteSyncModel: return self._remote_site_provider def _cache_sites(self): - site_sync = self._get_sync_server_module() active_site = None remote_site = None active_site_provider = None remote_site_provider = None - if site_sync is not None: + if self.is_sync_server_enabled(): + site_sync = self._get_sync_server_module() project_name = self._controller.get_current_project_name() active_site = site_sync.get_active_site(project_name) remote_site = site_sync.get_remote_site(project_name) active_site_provider = "studio" remote_site_provider = "studio" if active_site != "studio": - active_site_provider = site_sync.get_active_provider( + active_site_provider = site_sync.get_provider_for_site( project_name, active_site ) if remote_site != "studio": - remote_site_provider = site_sync.get_active_provider( + remote_site_provider = site_sync.get_provider_for_site( project_name, remote_site ) From 946b9318b66b96c1606e9d3805024a431e5be2a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 12 Jan 2024 13:51:08 +0100 Subject: [PATCH 094/281] add 'outputName' to thumbnail representation (#6114) --- openpype/plugins/publish/extract_thumbnail_from_source.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail_from_source.py b/openpype/plugins/publish/extract_thumbnail_from_source.py index 401a5d615d..33cbf6d9bf 100644 --- a/openpype/plugins/publish/extract_thumbnail_from_source.py +++ b/openpype/plugins/publish/extract_thumbnail_from_source.py @@ -65,7 +65,8 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin): "files": dst_filename, "stagingDir": dst_staging, "thumbnail": True, - "tags": ["thumbnail"] + "tags": ["thumbnail"], + "outputName": "thumbnail", } # adding representation From c506813c345429873e54e10c0cc42fa20517bdab Mon Sep 17 00:00:00 2001 From: Ynbot Date: Fri, 12 Jan 2024 12:59:28 +0000 Subject: [PATCH 095/281] [Automated] Release --- CHANGELOG.md | 256 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 258 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a21882008..7b51fade6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,262 @@ # Changelog +## [3.18.3](https://github.com/ynput/OpenPype/tree/3.18.3) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.2...3.18.3) + +### **🚀 Enhancements** + + +
+Maya: Apply initial viewport shader for Redshift Proxy after loading #6102 + +When the published redshift proxy is being loaded, the shader of the proxy is missing. This is different from the manual load through creating redshift proxy for files. This PR is to assign the default lambert to the redshift proxy, which replicates the same approach when the user manually loads the proxy with filepath. + + +___ + +
+ + +
+General: We should keep current subset version when we switch only the representation type #4629 + +When we switch only the representation type of subsets, we should not get the representation from the last version of the subset. + + +___ + +
+ + +
+Houdini: Add loader for redshift proxy family #5948 + +Loader for Redshift Proxy in Houdini (Thanks for @BigRoy contribution) + + +___ + +
+ + +
+AfterEffects: exposing Deadline pools fields in Publisher UI #6079 + +Deadline pools might be adhoc set by an artist during publishing. AfterEffects implementation wasn't providing this. + + +___ + +
+ + +
+Chore: Event callbacks can have order #6080 + +Event callbacks can have order in which are called, and fixed issue with getting function name and file when using `partial` function as callback. + + +___ + +
+ + +
+AYON: OpenPype addon defines runtime dependencies #6095 + +Moved runtime dependencies from ayon-launcher to openpype addon. + + +___ + +
+ + +
+Max: User's setting for scene unit scale #6097 + +Options for users to set the default scene unit scale for their scenes.AYONLegacy OP + + +___ + +
+ + +
+Chore: Remove deprecated templates profiles #6103 + +Remove deprecated usage of template profiles from settings. + + +___ + +
+ + +
+Publisher: Window is not always on top #6107 + +Goal of this PR is to avoid using `WindowStaysOnTopHint` which causes issues, especially in cases when DCC shows a popup dialog that is behind the window, in that case both Publisher and DCC are frozen and there is nothing to do. + + +___ + +
+ + +
+Houdini: add split job export support for Redshift ROP #6108 + +This is adding support for splitting of export and render jobs for Redshift as is already implemented for Vray, Mantra and Arnold. + + +___ + +
+ + +
+Fusion: automatic installation of PySide2 #6111 + +This PR adds hook which tries to check if PySide2 is installed in Python used by Fusion and if not, it tries to install it automatically. + + +___ + +
+ + +
+AYON: OpenPype addon dependencies #6113 + +Added `click` and `six` to requirements of openpype addon, and removed `Qt.py` requirement, which is not used anywhere. + + +___ + +
+ + +
+Chore: Thumbnail representation has 'outputName' #6114 + +Add thumbnail output name to thumbnail representation to prevent same output filename during integration. + + +___ + +
+ + +
+Kitsu: Clear credentials is safe #6116 + +Do not remove not existing keyring items. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: bug fix the playblast without textures #5942 + +Bug fix the texture not being displayed when users enable texture placement in the OP/AYON setting + + +___ + +
+ + +
+Blender: Workfile instance update fix #6048 + +Make sure workfile instance has always available 'instance_node' in transient data. + + +___ + +
+ + +
+Publisher: Fix issue with parenting of widgets #6106 + +Don't use publisher window parent (usually main DCC window) as parent for report widget. + + +___ + +
+ + +
+:wrench: fix and update pydocstyle configuration #6109 + +Fix pydocstyle configuration and move it to `pyproject.toml` + + +___ + +
+ + +
+Nuke: Create camera node with the latest camera node class in Nuke 14 #6118 + +Creating instance fails for certain cameras, and it seems to only exist in Nuke 14. The reason of causing that contributes to the new camera node class `Camera4` while the camera creator is working with the `Camera2` class. + + +___ + +
+ + +
+Site Sync: small fixes in Loader #6119 + +Resolves issue: +- local and studio icons were same, they should be different +- `TypeError: string indices must be integers` error when downloading/uploading workfiles + + +___ + +
+ + +
+Chore: Template data for editorial publishing #6120 + +Template data for editorial publishing are filled during `CollectInstanceAnatomyData`. The structure for editorial is determined, as it's required for ExtractHierarchy AYON/OpenPype plugins. + + +___ + +
+ + +
+SceneInventory: Fix site sync icon conversion #6123 + +Use 'get_qt_icon' to convert icon definitions from site sync. + + +___ + +
+ + + + ## [3.18.2](https://github.com/ynput/OpenPype/tree/3.18.2) diff --git a/openpype/version.py b/openpype/version.py index 279575d110..c87c143ea3 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.3-nightly.2" +__version__ = "3.18.3" diff --git a/pyproject.toml b/pyproject.toml index ee8e8017e3..bad481c889 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.2" # OpenPype +version = "3.18.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From b5b85f7b7fe533e19c2a08ebb5d277ec819d6c2c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 12 Jan 2024 13:00:24 +0000 Subject: [PATCH 096/281] 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 7d6c5650d1..3f762bd2d8 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.18.3 - 3.18.3-nightly.2 - 3.18.3-nightly.1 - 3.18.2 @@ -134,7 +135,6 @@ body: - 3.15.7-nightly.1 - 3.15.6 - 3.15.6-nightly.3 - - 3.15.6-nightly.2 validations: required: true - type: dropdown From e9d38f24f49784021bccd19f18b189fc47e91276 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 12 Jan 2024 15:03:47 +0000 Subject: [PATCH 097/281] Renamed variable --- openpype/hosts/blender/plugins/load/load_animation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_animation.py b/openpype/hosts/blender/plugins/load/load_animation.py index 0f968c75e5..fd087553f0 100644 --- a/openpype/hosts/blender/plugins/load/load_animation.py +++ b/openpype/hosts/blender/plugins/load/load_animation.py @@ -61,10 +61,10 @@ class BlendAnimationLoader(plugin.AssetLoader): bpy.data.objects.remove(container) - filepath = bpy.path.basename(libpath) + filename = bpy.path.basename(libpath) # Blender has a limit of 63 characters for any data name. - # If the filepath is longer, it will be truncated. - if len(filepath) > 63: - filepath = filepath[:63] - library = bpy.data.libraries.get(filepath) + # If the filename is longer, it will be truncated. + if len(filename) > 63: + filename = filename[:63] + library = bpy.data.libraries.get(filename) bpy.data.libraries.remove(library) From 00eb748b4b64339b00799b5fa4c41ad42426f73c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 13 Jan 2024 00:15:46 +0800 Subject: [PATCH 098/281] code tweaks on has_rgb_channel_in_texture_set function & add publish data into the imageinstance --- openpype/hosts/substancepainter/api/lib.py | 48 +++++-------------- .../publish/collect_textureset_images.py | 12 ++--- .../plugins/publish/validate_ouput_maps.py | 1 + 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 67229a75bf..896cca79b0 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -653,45 +653,23 @@ def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): map_identifier (str): Map identifier Returns: - colorspace_dict: A dictionary which stores the boolean - value of textures having RGB channels + bool: Whether the channel type identifier has RGB channel or not + in the texture stack. """ + + # 2D_View is always True as it exports all texture maps texture_stack = ( substance_painter.textureset.Stack.from_name(texture_set_name) ) # 2D_View is always True as it exports all texture maps - colorspace_dict = {"2D_View": True} - colorspace_dict["BaseColor"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.BaseColor).is_color() - colorspace_dict["Roughness"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Roughness).is_color() - colorspace_dict["Metallic"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Metallic).is_color() - colorspace_dict["Height"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Height).is_color() - colorspace_dict["Normal"] = texture_stack.get_channel( - substance_painter.textureset.ChannelType.Normal).is_color() - return colorspace_dict.get(map_identifier, False) + if map_identifier == "2D_View": + return True + channel_type = getattr( + substance_painter.textureset.ChannelType, map_identifier, None) + if channel_type is None: + return False + if not texture_stack.has_channel(channel_type): + return False -def texture_set_filtering(texture_set_same, template): - """Function to check whether some specific textures(e.g. Emissive) - are parts of the texture stack in Substance Painter - - Args: - texture_set_same (str): Name of Texture Set - template (str): texture template name - - Returns: - texture_filter: A dictionary which stores the boolean - value of whether the texture exist in the channel. - """ - texture_filter = {} - channel_stack = substance_painter.textureset.Stack.from_name( - texture_set_same) - has_emissive = channel_stack.has_channel( - substance_painter.textureset.ChannelType.Emissive) - map_identifier = strip_template(template) - if map_identifier == "Emissive": - texture_filter[map_identifier] = has_emissive - return texture_filter.get(map_identifier, True) + return texture_stack.get_channel(channel_type).is_color() diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 9d6aa06872..4468602392 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -41,13 +41,11 @@ class CollectTextureSet(pyblish.api.InstancePlugin): for (texture_set_name, stack_name), template_maps in maps.items(): self.log.info(f"Processing {texture_set_name}/{stack_name}") for template, outputs in template_maps.items(): - if texture_set_filtering(texture_set_name, template): - self.log.info(f"Processing {template}") - self.create_image_instance( - instance, template, outputs, - asset_doc=asset_doc, - texture_set_name=texture_set_name, - stack_name=stack_name) + self.log.info(f"Processing {template}") + self.create_image_instance(instance, template, outputs, + asset_doc=asset_doc, + texture_set_name=texture_set_name, + stack_name=stack_name) def create_image_instance(self, instance, template, outputs, asset_doc, texture_set_name, stack_name): diff --git a/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py index b57cf4c5a2..252683b6c8 100644 --- a/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py +++ b/openpype/hosts/substancepainter/plugins/publish/validate_ouput_maps.py @@ -80,6 +80,7 @@ class ValidateOutputMaps(pyblish.api.InstancePlugin): self.log.warning(f"Disabling texture instance: " f"{image_instance}") image_instance.data["active"] = False + image_instance.data["publish"] = False image_instance.data["integrate"] = False representation.setdefault("tags", []).append("delete") continue From 00ab2bc9f69916ab19ddd15d018dc38cba624991 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 13 Jan 2024 00:19:03 +0800 Subject: [PATCH 099/281] remove unused function --- .../plugins/publish/collect_textureset_images.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 4468602392..370e72f34b 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -8,8 +8,7 @@ import substance_painter.textureset from openpype.hosts.substancepainter.api.lib import ( get_parsed_export_maps, strip_template, - has_rgb_channel_in_texture_set, - texture_set_filtering + has_rgb_channel_in_texture_set ) from openpype.pipeline.create import get_subset_name from openpype.client import get_asset_by_name From 95d2d45e0ffe10186714bf8136f6ac085ef6c202 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 12 Jan 2024 17:07:28 +0000 Subject: [PATCH 100/281] Fix reading image sequences through oiiotool --- openpype/lib/transcoding.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 316dedbd3d..79d24e737a 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -120,6 +120,10 @@ def get_oiio_info_for_input(filepath, logger=None, subimages=False): output = [] for subimage_lines in subimages_lines: + # First line of oiiotool for xml format is "Reading path/to/file.ext". + if not subimage_lines[0].startswith(" Date: Sat, 13 Jan 2024 03:25:27 +0000 Subject: [PATCH 101/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index c87c143ea3..5981cb657a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.3" +__version__ = "3.18.4-nightly.1" From 8afd06233797bb0b949658a323bcbd0aeb3ffbd2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 13 Jan 2024 03:26:00 +0000 Subject: [PATCH 102/281] 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 3f762bd2d8..e9b68a54f1 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.18.4-nightly.1 - 3.18.3 - 3.18.3-nightly.2 - 3.18.3-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.7-nightly.2 - 3.15.7-nightly.1 - 3.15.6 - - 3.15.6-nightly.3 validations: required: true - type: dropdown From 7d94fb92c23d7ef055329f71d0333ab490a0055c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 15 Jan 2024 10:32:39 +0100 Subject: [PATCH 103/281] Fusion: new creator for image product type (#6057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduced image product type 'image' product type should result in single frame output, 'render' should be more focused on multiple frames. * Updated logging * Refactor moved generic creaor class to better location * Update openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json Co-authored-by: Jakub Ježek * Change label It might be movie type not only image sequence. * OP-7470 - fix name * OP-7470 - update docstring There were objections for setting up this creator as it seems unnecessary. There is currently no other way how to implement customer requirement but this, but in the future 'alias' product types implementation might solve this. * Implementing changes from #6060 https://github.com/ynput/OpenPype/pull/6060 * Update openpype/settings/defaults/project_settings/fusion.json Co-authored-by: Jakub Ježek * Update server_addon/fusion/server/settings.py Co-authored-by: Jakub Ježek * Update openpype/hosts/fusion/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * OP-7470 - added explicit frame field Artist can insert specific frame from which `image` instance should be created. * OP-7470 - fix name and logging Prints better message even in debug mode. * OP-7470 - update instance label It contained original frames which was confusing. * Update openpype/hosts/fusion/plugins/create/create_image_saver.py Co-authored-by: Roy Nieterau * OP-7470 - fix documentation * OP-7470 - moved frame range resolution earlier This approach is safer, as frame range is resolved sooner. * OP-7470 - added new validator for single frame * OP-7470 - Hound * OP-7470 - removed unnecessary as label * OP-7470 - use internal class anatomy * OP-7470 - add explicit settings_category to propagete values from Setting correctly apply_settings is replaced by correct value in `settings_category` * OP-7470 - typo * OP-7470 - update docstring * OP-7470 - update formatting data This probably fixes issue with missing product key in intermediate product name. * OP-7470 - moved around only proper fields Some fields (frame and frame_range) are making sense only in specific creator. * OP-7470 - added defaults to Settings * OP-7470 - fixed typo * OP-7470 - bumped up version Settings changed, so addon version should change too. 0.1.2 is in develop * Update openpype/hosts/fusion/plugins/publish/collect_instances.py Co-authored-by: Roy Nieterau * OP-7470 - removed unnecessary variables There was logic intended to use those, deemed not necessary. * OP-7470 - update to error message * OP-7470 - removed unneded method --------- Co-authored-by: Jakub Ježek Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Co-authored-by: Roy Nieterau --- openpype/hosts/fusion/api/plugin.py | 221 +++++++++++++++ .../plugins/create/create_image_saver.py | 64 +++++ .../fusion/plugins/create/create_saver.py | 257 ++---------------- .../fusion/plugins/publish/collect_inputs.py | 2 +- .../plugins/publish/collect_instances.py | 12 + .../fusion/plugins/publish/collect_render.py | 4 +- .../fusion/plugins/publish/save_scene.py | 2 +- .../publish/validate_background_depth.py | 2 +- .../plugins/publish/validate_comp_saved.py | 2 +- .../publish/validate_create_folder_checked.py | 2 +- .../validate_filename_has_extension.py | 2 +- .../plugins/publish/validate_image_frame.py | 27 ++ .../publish/validate_instance_frame_range.py | 4 +- .../publish/validate_saver_has_input.py | 2 +- .../publish/validate_saver_passthrough.py | 2 +- .../publish/validate_saver_resolution.py | 2 +- .../publish/validate_unique_subsets.py | 2 +- .../defaults/project_settings/fusion.json | 12 + .../schema_project_fusion.json | 51 +++- server_addon/fusion/server/settings.py | 67 ++++- server_addon/fusion/server/version.py | 2 +- 21 files changed, 482 insertions(+), 259 deletions(-) create mode 100644 openpype/hosts/fusion/api/plugin.py create mode 100644 openpype/hosts/fusion/plugins/create/create_image_saver.py create mode 100644 openpype/hosts/fusion/plugins/publish/validate_image_frame.py diff --git a/openpype/hosts/fusion/api/plugin.py b/openpype/hosts/fusion/api/plugin.py new file mode 100644 index 0000000000..63a74fbdb5 --- /dev/null +++ b/openpype/hosts/fusion/api/plugin.py @@ -0,0 +1,221 @@ +from copy import deepcopy +import os + +from openpype.hosts.fusion.api import ( + get_current_comp, + comp_lock_and_undo_chunk, +) + +from openpype.lib import ( + BoolDef, + EnumDef, +) +from openpype.pipeline import ( + legacy_io, + Creator, + CreatedInstance +) + + +class GenericCreateSaver(Creator): + default_variants = ["Main", "Mask"] + description = "Fusion Saver to generate image sequence" + icon = "fa5.eye" + + instance_attributes = [ + "reviewable" + ] + + settings_category = "fusion" + + image_format = "exr" + + # TODO: This should be renamed together with Nuke so it is aligned + temp_rendering_path_template = ( + "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") + + def create(self, subset_name, instance_data, pre_create_data): + self.pass_pre_attributes_to_instance(instance_data, pre_create_data) + + instance = CreatedInstance( + family=self.family, + subset_name=subset_name, + data=instance_data, + creator=self, + ) + data = instance.data_to_store() + comp = get_current_comp() + with comp_lock_and_undo_chunk(comp): + args = (-32768, -32768) # Magical position numbers + saver = comp.AddTool("Saver", *args) + + self._update_tool_with_data(saver, data=data) + + # Register the CreatedInstance + self._imprint(saver, data) + + # Insert the transient data + instance.transient_data["tool"] = saver + + self._add_instance_to_context(instance) + + return instance + + def collect_instances(self): + comp = get_current_comp() + tools = comp.GetToolList(False, "Saver").values() + for tool in tools: + data = self.get_managed_tool_data(tool) + if not data: + continue + + # Add instance + created_instance = CreatedInstance.from_existing(data, self) + + # Collect transient data + created_instance.transient_data["tool"] = tool + + self._add_instance_to_context(created_instance) + + def update_instances(self, update_list): + for created_inst, _changes in update_list: + new_data = created_inst.data_to_store() + tool = created_inst.transient_data["tool"] + self._update_tool_with_data(tool, new_data) + self._imprint(tool, new_data) + + def remove_instances(self, instances): + for instance in instances: + # Remove the tool from the scene + + tool = instance.transient_data["tool"] + if tool: + tool.Delete() + + # Remove the collected CreatedInstance to remove from UI directly + self._remove_instance_from_context(instance) + + def _imprint(self, tool, data): + # Save all data in a "openpype.{key}" = value data + + # Instance id is the tool's name so we don't need to imprint as data + data.pop("instance_id", None) + + active = data.pop("active", None) + if active is not None: + # Use active value to set the passthrough state + tool.SetAttrs({"TOOLB_PassThrough": not active}) + + for key, value in data.items(): + tool.SetData(f"openpype.{key}", value) + + def _update_tool_with_data(self, tool, data): + """Update tool node name and output path based on subset data""" + if "subset" not in data: + return + + original_subset = tool.GetData("openpype.subset") + original_format = tool.GetData( + "openpype.creator_attributes.image_format" + ) + + subset = data["subset"] + if ( + original_subset != subset + or original_format != data["creator_attributes"]["image_format"] + ): + self._configure_saver_tool(data, tool, subset) + + def _configure_saver_tool(self, data, tool, subset): + formatting_data = deepcopy(data) + + # get frame padding from anatomy templates + frame_padding = self.project_anatomy.templates["frame_padding"] + + # get output format + ext = data["creator_attributes"]["image_format"] + + # Subset change detected + workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) + formatting_data.update({ + "workdir": workdir, + "frame": "0" * frame_padding, + "ext": ext, + "product": { + "name": formatting_data["subset"], + "type": formatting_data["family"], + }, + }) + + # build file path to render + filepath = self.temp_rendering_path_template.format(**formatting_data) + + comp = get_current_comp() + tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) + + # Rename tool + if tool.Name != subset: + print(f"Renaming {tool.Name} -> {subset}") + tool.SetAttrs({"TOOLS_Name": subset}) + + def get_managed_tool_data(self, tool): + """Return data of the tool if it matches creator identifier""" + data = tool.GetData("openpype") + if not isinstance(data, dict): + return + + required = { + "id": "pyblish.avalon.instance", + "creator_identifier": self.identifier, + } + for key, value in required.items(): + if key not in data or data[key] != value: + return + + # Get active state from the actual tool state + attrs = tool.GetAttrs() + passthrough = attrs["TOOLB_PassThrough"] + data["active"] = not passthrough + + # Override publisher's UUID generation because tool names are + # already unique in Fusion in a comp + data["instance_id"] = tool.Name + + return data + + def get_instance_attr_defs(self): + """Settings for publish page""" + return self.get_pre_create_attr_defs() + + def pass_pre_attributes_to_instance(self, instance_data, pre_create_data): + creator_attrs = instance_data["creator_attributes"] = {} + for pass_key in pre_create_data.keys(): + creator_attrs[pass_key] = pre_create_data[pass_key] + + def _get_render_target_enum(self): + rendering_targets = { + "local": "Local machine rendering", + "frames": "Use existing frames", + } + if "farm_rendering" in self.instance_attributes: + rendering_targets["farm"] = "Farm rendering" + + return EnumDef( + "render_target", items=rendering_targets, label="Render target" + ) + + def _get_reviewable_bool(self): + return BoolDef( + "review", + default=("reviewable" in self.instance_attributes), + label="Review", + ) + + def _get_image_format_enum(self): + image_format_options = ["exr", "tga", "tif", "png", "jpg"] + return EnumDef( + "image_format", + items=image_format_options, + default=self.image_format, + label="Output Image Format", + ) diff --git a/openpype/hosts/fusion/plugins/create/create_image_saver.py b/openpype/hosts/fusion/plugins/create/create_image_saver.py new file mode 100644 index 0000000000..490228d488 --- /dev/null +++ b/openpype/hosts/fusion/plugins/create/create_image_saver.py @@ -0,0 +1,64 @@ +from openpype.lib import NumberDef + +from openpype.hosts.fusion.api.plugin import GenericCreateSaver +from openpype.hosts.fusion.api import get_current_comp + + +class CreateImageSaver(GenericCreateSaver): + """Fusion Saver to generate single image. + + Created to explicitly separate single ('image') or + multi frame('render) outputs. + + This might be temporary creator until 'alias' functionality will be + implemented to limit creation of additional product types with similar, but + not the same workflows. + """ + identifier = "io.openpype.creators.fusion.imagesaver" + label = "Image (saver)" + name = "image" + family = "image" + description = "Fusion Saver to generate image" + + default_frame = 0 + + def get_detail_description(self): + return """Fusion Saver to generate single image. + + This creator is expected for publishing of single frame `image` product + type. + + Artist should provide frame number (integer) to specify which frame + should be published. It must be inside of global timeline frame range. + + Supports local and deadline rendering. + + Supports selection from predefined set of output file extensions: + - exr + - tga + - png + - tif + - jpg + + Created to explicitly separate single frame ('image') or + multi frame ('render') outputs. + """ + + def get_pre_create_attr_defs(self): + """Settings for create page""" + attr_defs = [ + self._get_render_target_enum(), + self._get_reviewable_bool(), + self._get_frame_int(), + self._get_image_format_enum(), + ] + return attr_defs + + def _get_frame_int(self): + return NumberDef( + "frame", + default=self.default_frame, + label="Frame", + tooltip="Set frame to be rendered, must be inside of global " + "timeline range" + ) diff --git a/openpype/hosts/fusion/plugins/create/create_saver.py b/openpype/hosts/fusion/plugins/create/create_saver.py index 5870828b41..3a8ffe890b 100644 --- a/openpype/hosts/fusion/plugins/create/create_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_saver.py @@ -1,187 +1,42 @@ -from copy import deepcopy -import os +from openpype.lib import EnumDef -from openpype.hosts.fusion.api import ( - get_current_comp, - comp_lock_and_undo_chunk, -) - -from openpype.lib import ( - BoolDef, - EnumDef, -) -from openpype.pipeline import ( - legacy_io, - Creator as NewCreator, - CreatedInstance, - Anatomy, -) +from openpype.hosts.fusion.api.plugin import GenericCreateSaver -class CreateSaver(NewCreator): +class CreateSaver(GenericCreateSaver): + """Fusion Saver to generate image sequence of 'render' product type. + + Original Saver creator targeted for 'render' product type. It uses + original not to descriptive name because of values in Settings. + """ identifier = "io.openpype.creators.fusion.saver" label = "Render (saver)" name = "render" family = "render" - default_variants = ["Main", "Mask"] description = "Fusion Saver to generate image sequence" - icon = "fa5.eye" - instance_attributes = ["reviewable"] - image_format = "exr" + default_frame_range_option = "asset_db" - # TODO: This should be renamed together with Nuke so it is aligned - temp_rendering_path_template = ( - "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}" - ) + def get_detail_description(self): + return """Fusion Saver to generate image sequence. - def create(self, subset_name, instance_data, pre_create_data): - self.pass_pre_attributes_to_instance(instance_data, pre_create_data) + This creator is expected for publishing of image sequences for 'render' + product type. (But can publish even single frame 'render'.) - instance_data.update( - {"id": "pyblish.avalon.instance", "subset": subset_name} - ) + Select what should be source of render range: + - "Current asset context" - values set on Asset in DB (Ftrack) + - "From render in/out" - from node itself + - "From composition timeline" - from timeline - comp = get_current_comp() - with comp_lock_and_undo_chunk(comp): - args = (-32768, -32768) # Magical position numbers - saver = comp.AddTool("Saver", *args) + Supports local and farm rendering. - self._update_tool_with_data(saver, data=instance_data) - - # Register the CreatedInstance - instance = CreatedInstance( - family=self.family, - subset_name=subset_name, - data=instance_data, - creator=self, - ) - data = instance.data_to_store() - self._imprint(saver, data) - - # Insert the transient data - instance.transient_data["tool"] = saver - - self._add_instance_to_context(instance) - - return instance - - def collect_instances(self): - comp = get_current_comp() - tools = comp.GetToolList(False, "Saver").values() - for tool in tools: - data = self.get_managed_tool_data(tool) - if not data: - continue - - # Add instance - created_instance = CreatedInstance.from_existing(data, self) - - # Collect transient data - created_instance.transient_data["tool"] = tool - - self._add_instance_to_context(created_instance) - - def update_instances(self, update_list): - for created_inst, _changes in update_list: - new_data = created_inst.data_to_store() - tool = created_inst.transient_data["tool"] - self._update_tool_with_data(tool, new_data) - self._imprint(tool, new_data) - - def remove_instances(self, instances): - for instance in instances: - # Remove the tool from the scene - - tool = instance.transient_data["tool"] - if tool: - tool.Delete() - - # Remove the collected CreatedInstance to remove from UI directly - self._remove_instance_from_context(instance) - - def _imprint(self, tool, data): - # Save all data in a "openpype.{key}" = value data - - # Instance id is the tool's name so we don't need to imprint as data - data.pop("instance_id", None) - - active = data.pop("active", None) - if active is not None: - # Use active value to set the passthrough state - tool.SetAttrs({"TOOLB_PassThrough": not active}) - - for key, value in data.items(): - tool.SetData(f"openpype.{key}", value) - - def _update_tool_with_data(self, tool, data): - """Update tool node name and output path based on subset data""" - if "subset" not in data: - return - - original_subset = tool.GetData("openpype.subset") - original_format = tool.GetData( - "openpype.creator_attributes.image_format" - ) - - subset = data["subset"] - if ( - original_subset != subset - or original_format != data["creator_attributes"]["image_format"] - ): - self._configure_saver_tool(data, tool, subset) - - def _configure_saver_tool(self, data, tool, subset): - formatting_data = deepcopy(data) - - # get frame padding from anatomy templates - anatomy = Anatomy() - frame_padding = anatomy.templates["frame_padding"] - - # get output format - ext = data["creator_attributes"]["image_format"] - - # Subset change detected - workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"]) - formatting_data.update( - {"workdir": workdir, "frame": "0" * frame_padding, "ext": ext} - ) - - # build file path to render - filepath = self.temp_rendering_path_template.format(**formatting_data) - - comp = get_current_comp() - tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) - - # Rename tool - if tool.Name != subset: - print(f"Renaming {tool.Name} -> {subset}") - tool.SetAttrs({"TOOLS_Name": subset}) - - def get_managed_tool_data(self, tool): - """Return data of the tool if it matches creator identifier""" - data = tool.GetData("openpype") - if not isinstance(data, dict): - return - - required = { - "id": "pyblish.avalon.instance", - "creator_identifier": self.identifier, - } - for key, value in required.items(): - if key not in data or data[key] != value: - return - - # Get active state from the actual tool state - attrs = tool.GetAttrs() - passthrough = attrs["TOOLB_PassThrough"] - data["active"] = not passthrough - - # Override publisher's UUID generation because tool names are - # already unique in Fusion in a comp - data["instance_id"] = tool.Name - - return data + Supports selection from predefined set of output file extensions: + - exr + - tga + - png + - tif + - jpg + """ def get_pre_create_attr_defs(self): """Settings for create page""" @@ -193,29 +48,6 @@ class CreateSaver(NewCreator): ] return attr_defs - def get_instance_attr_defs(self): - """Settings for publish page""" - return self.get_pre_create_attr_defs() - - def pass_pre_attributes_to_instance(self, instance_data, pre_create_data): - creator_attrs = instance_data["creator_attributes"] = {} - for pass_key in pre_create_data.keys(): - creator_attrs[pass_key] = pre_create_data[pass_key] - - # These functions below should be moved to another file - # so it can be used by other plugins. plugin.py ? - def _get_render_target_enum(self): - rendering_targets = { - "local": "Local machine rendering", - "frames": "Use existing frames", - } - if "farm_rendering" in self.instance_attributes: - rendering_targets["farm"] = "Farm rendering" - - return EnumDef( - "render_target", items=rendering_targets, label="Render target" - ) - def _get_frame_range_enum(self): frame_range_options = { "asset_db": "Current asset context", @@ -227,42 +59,5 @@ class CreateSaver(NewCreator): "frame_range_source", items=frame_range_options, label="Frame range source", - ) - - def _get_reviewable_bool(self): - return BoolDef( - "review", - default=("reviewable" in self.instance_attributes), - label="Review", - ) - - def _get_image_format_enum(self): - image_format_options = ["exr", "tga", "tif", "png", "jpg"] - return EnumDef( - "image_format", - items=image_format_options, - default=self.image_format, - label="Output Image Format", - ) - - def apply_settings(self, project_settings): - """Method called on initialization of plugin to apply settings.""" - - # plugin settings - plugin_settings = project_settings["fusion"]["create"][ - self.__class__.__name__ - ] - - # individual attributes - self.instance_attributes = plugin_settings.get( - "instance_attributes", self.instance_attributes - ) - self.default_variants = plugin_settings.get( - "default_variants", self.default_variants - ) - self.temp_rendering_path_template = plugin_settings.get( - "temp_rendering_path_template", self.temp_rendering_path_template - ) - self.image_format = plugin_settings.get( - "image_format", self.image_format + default=self.default_frame_range_option ) diff --git a/openpype/hosts/fusion/plugins/publish/collect_inputs.py b/openpype/hosts/fusion/plugins/publish/collect_inputs.py index a6628300db..f23e4d0268 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_inputs.py +++ b/openpype/hosts/fusion/plugins/publish/collect_inputs.py @@ -95,7 +95,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): label = "Collect Inputs" order = pyblish.api.CollectorOrder + 0.2 hosts = ["fusion"] - families = ["render"] + families = ["render", "image"] def process(self, instance): diff --git a/openpype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py index 4d6da79b77..a0131248e8 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_instances.py +++ b/openpype/hosts/fusion/plugins/publish/collect_instances.py @@ -57,6 +57,18 @@ class CollectInstanceData(pyblish.api.InstancePlugin): start_with_handle = comp_start end_with_handle = comp_end + frame = instance.data["creator_attributes"].get("frame") + # explicitly publishing only single frame + if frame is not None: + frame = int(frame) + + start = frame + end = frame + handle_start = 0 + handle_end = 0 + start_with_handle = frame + end_with_handle = frame + # Include start and end render frame in label subset = instance.data["subset"] label = ( diff --git a/openpype/hosts/fusion/plugins/publish/collect_render.py b/openpype/hosts/fusion/plugins/publish/collect_render.py index a7daa0b64c..366eaa905c 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_render.py +++ b/openpype/hosts/fusion/plugins/publish/collect_render.py @@ -50,7 +50,7 @@ class CollectFusionRender( continue family = inst.data["family"] - if family != "render": + if family not in ["render", "image"]: continue task_name = context.data["task"] @@ -59,7 +59,7 @@ class CollectFusionRender( instance_families = inst.data.get("families", []) subset_name = inst.data["subset"] instance = FusionRenderInstance( - family="render", + family=family, tool=tool, workfileComp=comp, families=instance_families, diff --git a/openpype/hosts/fusion/plugins/publish/save_scene.py b/openpype/hosts/fusion/plugins/publish/save_scene.py index 0798e7c8b7..da9b6ce41f 100644 --- a/openpype/hosts/fusion/plugins/publish/save_scene.py +++ b/openpype/hosts/fusion/plugins/publish/save_scene.py @@ -7,7 +7,7 @@ class FusionSaveComp(pyblish.api.ContextPlugin): label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["fusion"] - families = ["render", "workfile"] + families = ["render", "image", "workfile"] def process(self, context): diff --git a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py index 6908889eb4..e268f8adec 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py +++ b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py @@ -17,7 +17,7 @@ class ValidateBackgroundDepth( order = pyblish.api.ValidatorOrder label = "Validate Background Depth 32 bit" hosts = ["fusion"] - families = ["render"] + families = ["render", "image"] optional = True actions = [SelectInvalidAction, publish.RepairAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py b/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py index 748047e8cf..6e6d10e09a 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py +++ b/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py @@ -9,7 +9,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] def process(self, context): diff --git a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py index 35c92163eb..d5c618af58 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -15,7 +15,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Create Folder Checked" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [RepairAction, SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py b/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py index 537e43c875..38cd578ff2 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py +++ b/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py @@ -17,7 +17,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Filename Has Extension" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_image_frame.py b/openpype/hosts/fusion/plugins/publish/validate_image_frame.py new file mode 100644 index 0000000000..734203f31c --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/validate_image_frame.py @@ -0,0 +1,27 @@ +import pyblish.api + +from openpype.pipeline import PublishValidationError + + +class ValidateImageFrame(pyblish.api.InstancePlugin): + """Validates that `image` product type contains only single frame.""" + + order = pyblish.api.ValidatorOrder + label = "Validate Image Frame" + families = ["image"] + hosts = ["fusion"] + + def process(self, instance): + render_start = instance.data["frameStartHandle"] + render_end = instance.data["frameEndHandle"] + too_many_frames = (isinstance(instance.data["expectedFiles"], list) + and len(instance.data["expectedFiles"]) > 1) + + if render_end - render_start > 0 or too_many_frames: + desc = ("Trying to render multiple frames. 'image' product type " + "is meant for single frame. Please use 'render' creator.") + raise PublishValidationError( + title="Frame range outside of comp range", + message=desc, + description=desc + ) diff --git a/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py b/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py index 06cd0ca186..edf219e752 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py +++ b/openpype/hosts/fusion/plugins/publish/validate_instance_frame_range.py @@ -7,8 +7,8 @@ class ValidateInstanceFrameRange(pyblish.api.InstancePlugin): """Validate instance frame range is within comp's global render range.""" order = pyblish.api.ValidatorOrder - label = "Validate Filename Has Extension" - families = ["render"] + label = "Validate Frame Range" + families = ["render", "image"] hosts = ["fusion"] def process(self, instance): diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py b/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py index faf2102a8b..0103e990fb 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py @@ -13,7 +13,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Has Input" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py index 9004976dc5..6019bee93a 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py @@ -9,7 +9,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Passthrough" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py b/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py index efa7295d11..f6aba170c0 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py @@ -64,7 +64,7 @@ class ValidateSaverResolution( order = pyblish.api.ValidatorOrder label = "Validate Asset Resolution" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] optional = True actions = [SelectInvalidAction] diff --git a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py index 5b6ceb2fdb..d1693ef3dc 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py +++ b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py @@ -11,7 +11,7 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["render"] + families = ["render", "image"] hosts = ["fusion"] actions = [SelectInvalidAction] diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index 8579442625..15b6bfc09b 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -32,6 +32,18 @@ "farm_rendering" ], "image_format": "exr" + }, + "CreateImageSaver": { + "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{ext}", + "default_variants": [ + "Main", + "Mask" + ], + "instance_attributes": [ + "reviewable", + "farm_rendering" + ], + "image_format": "exr" } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index fbd856b895..8669842087 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -74,7 +74,56 @@ "type": "dict", "collapsible": true, "key": "CreateSaver", - "label": "Create Saver", + "label": "Create Render Saver", + "is_group": true, + "children": [ + { + "type": "text", + "key": "temp_rendering_path_template", + "label": "Temporary rendering path template" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + }, + { + "key": "instance_attributes", + "label": "Instance attributes", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "reviewable": "Reviewable" + }, + { + "farm_rendering": "Farm rendering" + } + ] + }, + { + "key": "image_format", + "label": "Output Image Format", + "type": "enum", + "multiselect": false, + "enum_items": [ + {"exr": "exr"}, + {"tga": "tga"}, + {"png": "png"}, + {"tif": "tif"}, + {"jpg": "jpg"} + ] + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "CreateImageSaver", + "label": "Create Image Saver", "is_group": true, "children": [ { diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index 21189b390e..bf295f3064 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -25,6 +25,24 @@ def _create_saver_instance_attributes_enum(): ] +def _image_format_enum(): + return [ + {"value": "exr", "label": "exr"}, + {"value": "tga", "label": "tga"}, + {"value": "png", "label": "png"}, + {"value": "tif", "label": "tif"}, + {"value": "jpg", "label": "jpg"}, + ] + + +def _frame_range_options_enum(): + return [ + {"value": "asset_db", "label": "Current asset context"}, + {"value": "render_range", "label": "From render in/out"}, + {"value": "comp_range", "label": "From composition timeline"}, + ] + + class CreateSaverPluginModel(BaseSettingsModel): _isGroup = True temp_rendering_path_template: str = Field( @@ -59,10 +77,29 @@ class HooksModel(BaseSettingsModel): ) +class CreateSaverModel(CreateSaverPluginModel): + default_frame_range_option: str = Field( + default="asset_db", + enum_resolver=_frame_range_options_enum, + title="Default frame range source" + ) + + +class CreateImageSaverModel(CreateSaverPluginModel): + default_frame: int = Field( + 0, + title="Default rendered frame" + ) class CreatPluginsModel(BaseSettingsModel): - CreateSaver: CreateSaverPluginModel = Field( - default_factory=CreateSaverPluginModel, - title="Create Saver" + CreateSaver: CreateSaverModel = Field( + default_factory=CreateSaverModel, + title="Create Saver", + description="Creator for render product type (eg. sequence)" + ) + CreateImageSaver: CreateImageSaverModel = Field( + default_factory=CreateImageSaverModel, + title="Create Image Saver", + description="Creator for image product type (eg. single)" ) @@ -117,15 +154,21 @@ DEFAULT_VALUES = { "reviewable", "farm_rendering" ], - "output_formats": [ - "exr", - "jpg", - "jpeg", - "jpg", - "tiff", - "png", - "tga" - ] + "image_format": "exr", + "default_frame_range_option": "asset_db" + }, + "CreateImageSaver": { + "temp_rendering_path_template": "{workdir}/renders/fusion/{product[name]}/{product[name]}.{ext}", + "default_variants": [ + "Main", + "Mask" + ], + "instance_attributes": [ + "reviewable", + "farm_rendering" + ], + "image_format": "exr", + "default_frame": 0 } } } diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index b3f4756216..ae7362549b 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" From 1ac89d2efa55792e11023419e3ac5914e7b57480 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 15 Jan 2024 19:17:12 +0800 Subject: [PATCH 104/281] restore some of the codes on lib and collect images --- openpype/hosts/substancepainter/api/lib.py | 30 ------------------- .../publish/collect_textureset_images.py | 8 ++--- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/openpype/hosts/substancepainter/api/lib.py b/openpype/hosts/substancepainter/api/lib.py index 896cca79b0..1cb480b552 100644 --- a/openpype/hosts/substancepainter/api/lib.py +++ b/openpype/hosts/substancepainter/api/lib.py @@ -643,33 +643,3 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh - - -def has_rgb_channel_in_texture_set(texture_set_name, map_identifier): - """Function to check whether the texture has RGB channel. - - Args: - texture_set_name (str): Name of Texture Set - map_identifier (str): Map identifier - - Returns: - bool: Whether the channel type identifier has RGB channel or not - in the texture stack. - """ - - # 2D_View is always True as it exports all texture maps - texture_stack = ( - substance_painter.textureset.Stack.from_name(texture_set_name) - ) - # 2D_View is always True as it exports all texture maps - if map_identifier == "2D_View": - return True - - channel_type = getattr( - substance_painter.textureset.ChannelType, map_identifier, None) - if channel_type is None: - return False - if not texture_stack.has_channel(channel_type): - return False - - return texture_stack.get_channel(channel_type).is_color() diff --git a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py index 370e72f34b..316f72509e 100644 --- a/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py +++ b/openpype/hosts/substancepainter/plugins/publish/collect_textureset_images.py @@ -7,8 +7,7 @@ from openpype.pipeline import publish import substance_painter.textureset from openpype.hosts.substancepainter.api.lib import ( get_parsed_export_maps, - strip_template, - has_rgb_channel_in_texture_set + strip_template ) from openpype.pipeline.create import get_subset_name from openpype.client import get_asset_by_name @@ -79,6 +78,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Always include the map identifier map_identifier = strip_template(template) suffix += f".{map_identifier}" + image_subset = get_subset_name( # TODO: The family actually isn't 'texture' currently but for now # this is only done so the subset name starts with 'texture' @@ -132,9 +132,7 @@ class CollectTextureSet(pyblish.api.InstancePlugin): # Store color space with the instance # Note: The extractor will assign it to the representation colorspace = outputs[0].get("colorSpace") - has_rgb_channel = has_rgb_channel_in_texture_set( - texture_set_name, map_identifier) - if colorspace and has_rgb_channel: + if colorspace: self.log.debug(f"{image_subset} colorspace: {colorspace}") image_instance.data["colorspace"] = colorspace From 190840fe052d0ee35ad9585d18ebc06eec916808 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Jan 2024 12:16:50 +0000 Subject: [PATCH 105/281] Missing nuke family Windows arguments --- .../applications/server/applications.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index 35f1b4cfbb..d2e2d942c6 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -307,7 +307,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--nukeassist"], "darwin": [], "linux": [] }, @@ -329,7 +329,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--nukeassist"], "darwin": [], "linux": [] }, @@ -351,7 +351,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--nukeassist"], "darwin": [], "linux": [] }, @@ -382,7 +382,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--nukex"], "darwin": [], "linux": [] }, @@ -404,7 +404,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--nukex"], "darwin": [], "linux": [] }, @@ -426,7 +426,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--nukex"], "darwin": [], "linux": [] }, @@ -457,7 +457,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--studio"], "darwin": [], "linux": [] }, @@ -479,7 +479,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--studio"], "darwin": [], "linux": [] }, @@ -501,7 +501,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--studio"], "darwin": [], "linux": [] }, @@ -532,7 +532,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--hiero"], "darwin": [], "linux": [] }, @@ -554,7 +554,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--hiero"], "darwin": [], "linux": [] }, @@ -576,7 +576,7 @@ ] }, "arguments": { - "windows": [], + "windows": ["--hiero"], "darwin": [], "linux": [] }, From e9be330393cb5a50fcbc257ecf4ed0c8eb335804 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Jan 2024 14:56:29 +0000 Subject: [PATCH 106/281] Add Linux arguments. --- .../applications/server/applications.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index d2e2d942c6..b0b12b2003 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -309,7 +309,7 @@ "arguments": { "windows": ["--nukeassist"], "darwin": [], - "linux": [] + "linux": ["--nukeassist"] }, "environment": "{}", "use_python_2": false @@ -331,7 +331,7 @@ "arguments": { "windows": ["--nukeassist"], "darwin": [], - "linux": [] + "linux": ["--nukeassist"] }, "environment": "{}", "use_python_2": false @@ -353,7 +353,7 @@ "arguments": { "windows": ["--nukeassist"], "darwin": [], - "linux": [] + "linux": ["--nukeassist"] }, "environment": "{}", "use_python_2": false @@ -384,7 +384,7 @@ "arguments": { "windows": ["--nukex"], "darwin": [], - "linux": [] + "linux": ["--nukex"] }, "environment": "{}", "use_python_2": false @@ -406,7 +406,7 @@ "arguments": { "windows": ["--nukex"], "darwin": [], - "linux": [] + "linux": ["--nukex"] }, "environment": "{}", "use_python_2": false @@ -428,7 +428,7 @@ "arguments": { "windows": ["--nukex"], "darwin": [], - "linux": [] + "linux": ["--nukex"] }, "environment": "{}", "use_python_2": false @@ -459,7 +459,7 @@ "arguments": { "windows": ["--studio"], "darwin": [], - "linux": [] + "linux": ["--studio"] }, "environment": "{}", "use_python_2": false @@ -481,7 +481,7 @@ "arguments": { "windows": ["--studio"], "darwin": [], - "linux": [] + "linux": ["--studio"] }, "environment": "{}", "use_python_2": false @@ -503,7 +503,7 @@ "arguments": { "windows": ["--studio"], "darwin": [], - "linux": [] + "linux": ["--studio"] }, "environment": "{}", "use_python_2": false @@ -534,7 +534,7 @@ "arguments": { "windows": ["--hiero"], "darwin": [], - "linux": [] + "linux": ["--hiero"] }, "environment": "{}", "use_python_2": false @@ -556,7 +556,7 @@ "arguments": { "windows": ["--hiero"], "darwin": [], - "linux": [] + "linux": ["--hiero"] }, "environment": "{}", "use_python_2": false @@ -578,7 +578,7 @@ "arguments": { "windows": ["--hiero"], "darwin": [], - "linux": [] + "linux": ["--hiero"] }, "environment": "{}", "use_python_2": false From 7dcdd1b3b0d1799d786054da896af57f13244657 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:21:40 +0100 Subject: [PATCH 107/281] remove 'template_name_profiles' for 'IntegrateHeroVersion' (#6130) --- openpype/settings/ayon_settings.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index a6d90d1cf0..36f96c9dc7 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1291,12 +1291,6 @@ def _convert_global_project_settings(ayon_settings, output, default_settings): for extract_burnin_def in extract_burnin_defs } - ayon_integrate_hero = ayon_publish["IntegrateHeroVersion"] - for profile in ayon_integrate_hero["template_name_profiles"]: - if "product_types" not in profile: - break - profile["families"] = profile.pop("product_types") - if "IntegrateProductGroup" in ayon_publish: subset_group = ayon_publish.pop("IntegrateProductGroup") subset_group_profiles = subset_group.pop("product_grouping_profiles") From 10167c86a7c25044280df8012ec3193677749677 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:33:26 +0100 Subject: [PATCH 108/281] use instance version if context version is not set (#6117) --- openpype/plugins/publish/collect_anatomy_instance_data.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 0a34848166..34df49ea5b 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -200,9 +200,15 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self._fill_task_data(instance, project_task_types, anatomy_data) # Define version + version_number = None if self.follow_workfile_version: version_number = context.data("version") - else: + + # Even if 'follow_workfile_version' is enabled, it may not be set + # because workfile version was not collected to 'context.data' + # - that can happen e.g. in 'traypublisher' or other hosts without + # a workfile + if version_number is None: version_number = instance.data.get("version") # use latest version (+1) if already any exist From 17e861f532bbdd6b3e4e9f16f2c69639483c16b2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Jan 2024 17:42:47 +0000 Subject: [PATCH 109/281] Illicit suggestion --- openpype/lib/transcoding.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 79d24e737a..03fd91bec2 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -110,8 +110,9 @@ def get_oiio_info_for_input(filepath, logger=None, subimages=False): if line == "": subimages_lines.append(lines) lines = [] + xml_started = False - if not xml_started: + if not subimages_lines: raise ValueError( "Failed to read input file \"{}\".\nOutput:\n{}".format( filepath, output @@ -120,10 +121,6 @@ def get_oiio_info_for_input(filepath, logger=None, subimages=False): output = [] for subimage_lines in subimages_lines: - # First line of oiiotool for xml format is "Reading path/to/file.ext". - if not subimage_lines[0].startswith(" Date: Mon, 15 Jan 2024 18:27:11 +0000 Subject: [PATCH 110/281] Exposed knobs settings. --- .../nuke/server/settings/create_plugins.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py index 80aec51ae0..89ba87ad61 100644 --- a/server_addon/nuke/server/settings/create_plugins.py +++ b/server_addon/nuke/server/settings/create_plugins.py @@ -54,7 +54,10 @@ class CreateWriteRenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - + exposed_knobs: list[str] = Field( + title="Exposed Knobs", + default_factory=list + ) prenodes: list[PrenodeModel] = Field( default_factory=list, title="Preceding nodes", @@ -80,7 +83,10 @@ class CreateWritePrerenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - + exposed_knobs: list[str] = Field( + title="Exposed Knobs", + default_factory=list + ) prenodes: list[PrenodeModel] = Field( default_factory=list, title="Preceding nodes", @@ -106,7 +112,10 @@ class CreateWriteImageModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - + exposed_knobs: list[str] = Field( + title="Exposed Knobs", + default_factory=list + ) prenodes: list[PrenodeModel] = Field( default_factory=list, title="Preceding nodes", @@ -145,6 +154,7 @@ DEFAULT_CREATE_SETTINGS = { "reviewable", "farm_rendering" ], + "exposed_knobs": [], "prenodes": [ { "name": "Reformat01", @@ -178,6 +188,7 @@ DEFAULT_CREATE_SETTINGS = { "farm_rendering", "use_range_limit" ], + "exposed_knobs": [], "prenodes": [] }, "CreateWriteImage": { @@ -190,6 +201,7 @@ DEFAULT_CREATE_SETTINGS = { "instance_attributes": [ "use_range_limit" ], + "exposed_knobs": [], "prenodes": [ { "name": "FrameHold01", From 9e44545c2529536c745ecd919db472beb5380374 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Jan 2024 18:27:22 +0000 Subject: [PATCH 111/281] Validate exposed knobs. --- .../plugins/publish/validate_write_nodes.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 0539c1188f..434537022d 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -1,5 +1,7 @@ from collections import defaultdict +import nuke + import pyblish.api from openpype.pipeline.publish import get_errored_instances_from_context from openpype.hosts.nuke.api.lib import ( @@ -11,6 +13,7 @@ from openpype.hosts.nuke.api.lib import ( from openpype.pipeline.publish import ( PublishXmlValidationError, OptionalPyblishPluginMixin, + PublishValidationError ) @@ -39,6 +42,19 @@ class RepairNukeWriteNodeAction(pyblish.api.Action): set_node_knobs_from_settings(write_node, correct_data["knobs"]) + nuke_settings = instance.context.data["project_settings"]["nuke"] + create_settings = nuke_settings["create"]["CreateWriteRender"] + exposed_knobs = create_settings["exposed_knobs"] + for knob in exposed_knobs: + if knob in write_group_node.knobs(): + continue + + link = nuke.Link_Knob("") + link.makeLink(write_node.name(), knob) + link.setName(knob) + link.setFlag(0x1000) + write_group_node.addKnob(link) + self.log.debug("Node attributes were fixed") @@ -134,6 +150,27 @@ class ValidateNukeWriteNode( if check: self._make_error(check) + nuke_settings = instance.context.data["project_settings"]["nuke"] + create_settings = nuke_settings["create"]["CreateWriteRender"] + exposed_knobs = create_settings["exposed_knobs"] + unexposed_knobs = [] + for knob in exposed_knobs: + if knob not in write_group_node.knobs(): + unexposed_knobs.append(knob) + + """ + link = nuke.Link_Knob("") + link.makeLink(write_node.name(), knob) + link.setName(knob) + link.setFlag(0x1000) + write_group_node.addKnob(link) + """ + + if unexposed_knobs: + raise PublishValidationError( + "Missing exposed knobs: {}".format(unexposed_knobs) + ) + def _make_error(self, check): # sourcery skip: merge-assign-and-aug-assign, move-assign-in-block dbg_msg = "Write node's knobs values are not correct!\n" From 26a7363efe6c6eef0fbd42955c31e071941dea04 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 15 Jan 2024 18:27:35 +0000 Subject: [PATCH 112/281] Expose knobs on creation --- .../hosts/nuke/plugins/create/create_write_render.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 91acf4eabc..e622376917 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -113,6 +113,16 @@ class CreateWriteRender(napi.NukeWriteCreator): instance.data_to_store() ) + settings = self.project_settings["nuke"]["create"] + exposed_knobs = settings["CreateWriteRender"]["exposed_knobs"] + write_node = nuke.allNodes(group=instance_node, filter="Write")[0] + for knob in exposed_knobs: + link = nuke.Link_Knob("") + link.makeLink(write_node.name(), knob) + link.setName(knob) + link.setFlag(0x1000) + instance_node.addKnob(link) + return instance except Exception as er: From 7c41e3346fce0887975d5d09aa4bf123d45ab63f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 16 Jan 2024 07:46:47 +0000 Subject: [PATCH 113/281] change label to be more reable. --- server_addon/maya/server/settings/render_settings.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server_addon/maya/server/settings/render_settings.py b/server_addon/maya/server/settings/render_settings.py index b6163a04ce..75d0994759 100644 --- a/server_addon/maya/server/settings/render_settings.py +++ b/server_addon/maya/server/settings/render_settings.py @@ -298,6 +298,7 @@ class ArnoldSettingsModel(BaseSettingsModel): title="Additional Arnold Options", description=( "Add additional options - put attribute and value, like AASamples" + " and 4" ) ) @@ -325,8 +326,8 @@ class VraySettingsModel(BaseSettingsModel): default_factory=list, title="Additional Vray Options", description=( - "Add additional options - put attribute and value," - " like aaFilterSize" + "Add additional options - put attribute and value, like " + "aaFilterSize and 1.5" ) ) @@ -358,8 +359,8 @@ class RedshiftSettingsModel(BaseSettingsModel): default_factory=list, title="Additional Vray Options", description=( - "Add additional options - put attribute and value," - " like reflectionMaxTraceDepth" + "Add additional options - put attribute and value, like " + "reflectionMaxTraceDepth and 3" ) ) From b0a2c9689b3e3aec26e061663255c9c397380551 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 16 Jan 2024 10:53:40 +0100 Subject: [PATCH 114/281] make sure style object is not garbage collected --- openpype/tools/utils/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 723e71e7aa..365caaafd9 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -91,7 +91,8 @@ def set_style_property(widget, property_name, property_value): if cur_value == property_value: return widget.setProperty(property_name, property_value) - widget.style().polish(widget) + style = widget.style() + style.polish(widget) def paint_image_with_color(image, color): From a6cc0b511e4468be960e14f47aa2300a865e9035 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 16 Jan 2024 10:15:48 +0000 Subject: [PATCH 115/281] Maya: Account and ignore free image planes. (#5993) * Account and ignore for free image planes. * Incorporating BigRoy changes --------- Co-authored-by: Petr Kalis --- .../maya/plugins/publish/extract_camera_mayaScene.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 38cf00bbdd..f67e9db14f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -265,13 +265,16 @@ def transfer_image_planes(source_cameras, target_cameras, try: for source_camera, target_camera in zip(source_cameras, target_cameras): - image_planes = cmds.listConnections(source_camera, + image_plane_plug = "{}.imagePlane".format(source_camera) + image_planes = cmds.listConnections(image_plane_plug, + source=True, + destination=False, type="imagePlane") or [] # Split of the parent path they are attached - we want - # the image plane node name. + # the image plane node name if attached to a camera. # TODO: Does this still mean the image plane name is unique? - image_planes = [x.split("->", 1)[1] for x in image_planes] + image_planes = [x.split("->", 1)[-1] for x in image_planes] if not image_planes: continue @@ -282,7 +285,7 @@ def transfer_image_planes(source_cameras, target_cameras, if source_camera == target_camera: continue _attach_image_plane(target_camera, image_plane) - else: # explicitly dettaching image planes + else: # explicitly detach image planes cmds.imagePlane(image_plane, edit=True, detach=True) originals[source_camera].append(image_plane) yield From 9d5736aca78dcf9ca449f0bc5b9c73aedf96089c Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 16 Jan 2024 13:07:31 +0000 Subject: [PATCH 116/281] [Automated] Release --- CHANGELOG.md | 145 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 147 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b51fade6f..546b2c12ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,151 @@ # Changelog +## [3.18.4](https://github.com/ynput/OpenPype/tree/3.18.4) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.3...3.18.4) + +### **🚀 Enhancements** + + +
+multiple render camera supports for 3dsmax #5124 + +Supports for rendering with multiple cameras in 3dsmax +- [x] Add Batch Render Layers functions +- [x] Rewrite lib.rendersetting and lib.renderproduct +- [x] Add multi-camera options in creator. +- [x] Collector with batch render-layer when multi-camera enabled. +- [x] Add instance plugin for saving scene files with different cameras respectively by using subprocess +- [x] Refactor submit_max_deadline +- [x] Check with metadata.json in submit publish job + + +___ + +
+ + +
+Fusion: new creator for image product type #6057 + +In many DCC `render` product type is expected to be sequence of files. This PR adds new explicit creator for `image` product type which is focused on single frame image. Workflows for both product types might be a bit different, this gives artists more granularity to choose better workflow. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Account and ignore free image planes. #5993 + +Free image planes do not have the `->` path separator, so we need to account for that. + + +___ + +
+ + +
+Blender: Fix long names for instances #6070 + +Changed naming for instances to use only final part of the `folderPath`. + + +___ + +
+ + +
+Traypublisher & Chore: Instance version on follow workfile version #6117 + +If `follow_workfile_version` is enabled but context does not have filled workfile version, a version on instance is used instead. + + +___ + +
+ + +
+Substance Painter: Thumbnail errors with PBR Texture Set #6127 + +When publishing with PBR Metallic Roughness as Output Template, Emissive Map errors out because of the missing channel in the material and the map can't be generated in Substance Painter. This PR is to make sure `imagestance.data["publish"] = False` so that the related "empty" texture instance would be skipped to generate the output. + + +___ + +
+ + +
+Transcoding: Fix reading image sequences through oiiotool #6129 + +When transcoding image sequences, the second image onwards includes the invalid xml line of `Reading path/to/file.exr` of the oiiotool output.This is most likely not the best solution, but it fixes the issue and illustrates the problem.Error: +``` +ERROR:pyblish.plugin:Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "C:\Users\tokejepsen\OpenPype\openpype\plugins\publish\extract_color_transcode.py", line 152, in process + File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 1136, in convert_colorspace + input_info = get_oiio_info_for_input(input_path, logger=logger) + File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 124, in get_oiio_info_for_input + output.append(parse_oiio_xml_output(xml_text, logger=logger)) + File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 276, in parse_oiio_xml_output + tree = xml.etree.ElementTree.fromstring(xml_string) + File "xml\etree\ElementTree.py", line 1347, in XML +xml.etree.ElementTree.ParseError: syntax error: line 1, column 0 +Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "", line 152, in process + File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 1136, in convert_colorspace + input_info = get_oiio_info_for_input(input_path, logger=logger) + File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 124, in get_oiio_info_for_input + output.append(parse_oiio_xml_output(xml_text, logger=logger)) + File "C:\Users\tokejepsen\OpenPype\openpype\lib\transcoding.py", line 276, in parse_oiio_xml_output + tree = xml.etree.ElementTree.fromstring(xml_string) + File "xml\etree\ElementTree.py", line 1347, in XML +xml.etree.ElementTree.ParseError: syntax error: line 1, column 0 +``` + + + +___ + +
+ + +
+AYON: Remove 'IntegrateHeroVersion' conversion #6130 + +Remove settings conversion for `IntegrateHeroVersion`. + + +___ + +
+ + +
+Chore tools: Make sure style object is not garbage collected #6136 + +Minor fix in tool utils to make sure style C++ object is not garbage collected when not stored into variable. + + +___ + +
+ + + + ## [3.18.3](https://github.com/ynput/OpenPype/tree/3.18.3) diff --git a/openpype/version.py b/openpype/version.py index 5981cb657a..0b6584dbff 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.4-nightly.1" +__version__ = "3.18.4" diff --git a/pyproject.toml b/pyproject.toml index bad481c889..f9d5381a15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.3" # OpenPype +version = "3.18.4" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 4c9b19ed9974559bd63f6eeb593570f010983b85 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jan 2024 13:08:30 +0000 Subject: [PATCH 117/281] 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 e9b68a54f1..8df948a7c1 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.18.4 - 3.18.4-nightly.1 - 3.18.3 - 3.18.3-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.7-nightly.3 - 3.15.7-nightly.2 - 3.15.7-nightly.1 - - 3.15.6 validations: required: true - type: dropdown From 5bdad2b37c89272ff29885bc03eabadce907c0be Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Jan 2024 21:36:04 +0800 Subject: [PATCH 118/281] bug fix the limit groups from maya deadline settings unabled to be set by the username and error out --- server_addon/deadline/server/settings/publish_plugins.py | 2 +- server_addon/deadline/server/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index a989f3ad9d..dfb30f9b41 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -87,7 +87,7 @@ class MayaSubmitDeadlineModel(BaseSettingsModel): title="Disable Strict Error Check profiles" ) - @validator("limit", "scene_patches") + @validator("scene_patches") def validate_unique_names(cls, value): ensure_unique_names(value) return value diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index 0a8da88258..f1380eede2 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" From b29dee59fadbaa59876d0684eb0c30df5bba0681 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 16 Jan 2024 16:48:35 +0100 Subject: [PATCH 119/281] add addons dir only if exists --- openpype/modules/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 1a2513b4b4..41be2998f9 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -542,7 +542,8 @@ def _load_modules(): module_dirs.insert(0, current_dir) addons_dir = os.path.join(os.path.dirname(current_dir), "addons") - module_dirs.append(addons_dir) + if os.path.exists(addons_dir): + module_dirs.append(addons_dir) ignored_host_names = set(IGNORED_HOSTS_IN_AYON) ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES) From 25b33c60f0e86f7b3303bfdff92b563bc13180b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 16 Jan 2024 16:49:31 +0100 Subject: [PATCH 120/281] add missing '.tif' extension --- openpype/lib/transcoding.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 03fd91bec2..c8ddbde061 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -44,17 +44,17 @@ XML_CHAR_REF_REGEX_HEX = re.compile(r"&#x?[0-9a-fA-F]+;") ARRAY_TYPE_REGEX = re.compile(r"^(int|float|string)\[\d+\]$") IMAGE_EXTENSIONS = { - ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal", - ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits", - ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer", - ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", - ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", - ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", - ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", - ".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", ".ras", - ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", - ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", - ".xpm", ".xwd" + ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", + ".cal", ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", + ".fits", ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", + ".icer", ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", + ".jng", ".jpeg", ".jpeg-ls", ".jpeg-hdr", ".2000", ".jpg", + ".kra", ".logluv", ".mng", ".miff", ".nrrd", ".ora", + ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", + ".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", + ".ras", ".rgbe", ".sgi", ".tga", + ".tif", ".tiff", ".tiff/ep", ".tiff/it", ".ufo", ".ufp", + ".wbmp", ".webp", ".xr", ".xt", ".xbm", ".xcf", ".xpm", ".xwd" } VIDEO_EXTENSIONS = { From b468baa80677aac036472ae484894f4f8330f17b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 16 Jan 2024 16:51:23 +0100 Subject: [PATCH 121/281] use correct variable for error items --- openpype/pipeline/workfile/workfile_template_builder.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py index 9dc833061a..3096d22518 100644 --- a/openpype/pipeline/workfile/workfile_template_builder.py +++ b/openpype/pipeline/workfile/workfile_template_builder.py @@ -1971,7 +1971,6 @@ class PlaceholderCreateMixin(object): if not placeholder.data.get("keep_placeholder", True): self.delete_placeholder(placeholder) - def create_failed(self, placeholder, creator_data): if hasattr(placeholder, "create_failed"): placeholder.create_failed(creator_data) @@ -2036,7 +2035,7 @@ class CreatePlaceholderItem(PlaceholderItem): self._failed_created_publish_instances = [] def get_errors(self): - if not self._failed_representations: + if not self._failed_created_publish_instances: return [] message = ( "Failed to create {} instance using Creator {}" From dcf8805cfe2aca6f2615de1a7c18988d0efedae6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 16 Jan 2024 16:53:26 +0000 Subject: [PATCH 122/281] Add settings for effect categories --- .../hiero/server/settings/publish_plugins.py | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/server_addon/hiero/server/settings/publish_plugins.py b/server_addon/hiero/server/settings/publish_plugins.py index a85e62724b..07778b8ebe 100644 --- a/server_addon/hiero/server/settings/publish_plugins.py +++ b/server_addon/hiero/server/settings/publish_plugins.py @@ -1,5 +1,7 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from pydantic import Field, validator +from ayon_server.settings import ( + BaseSettingsModel, ensure_unique_names, normalize_name +) class CollectInstanceVersionModel(BaseSettingsModel): @@ -9,6 +11,30 @@ class CollectInstanceVersionModel(BaseSettingsModel): ) +class CollectClipEffectsDefModel(BaseSettingsModel): + _layout = "expanded" + name: str = Field("", title="Name") + effect_classes: list[str] = Field( + default_factory=list, title="Effect Classes" + ) + + @validator("name") + def validate_name(cls, value): + """Ensure name does not contain weird characters""" + return normalize_name(value) + + +class CollectClipEffectsModel(BaseSettingsModel): + effect_categories: list[CollectClipEffectsDefModel] = Field( + default_factory=list, title="Effect Categories" + ) + + @validator("effect_categories") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + class ExtractReviewCutUpVideoModel(BaseSettingsModel): enabled: bool = Field( True, @@ -25,6 +51,10 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=CollectInstanceVersionModel, title="Collect Instance Version" ) + CollectClipEffects: CollectClipEffectsModel = Field( + default_factory=CollectClipEffectsModel, + title="Collect Clip Effects" + ) """# TODO: enhance settings with host api: Rename class name and plugin name to match title (it makes more sense) From 9c8a4a7b6cccc2fd112b6c41f1e4c10d7f94f6e4 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 16 Jan 2024 16:53:51 +0000 Subject: [PATCH 123/281] Categorize effects from settings. --- .../plugins/publish/collect_clip_effects.py | 71 ++++++++++++++----- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index fcb1ab27a0..89dc66d73c 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -70,29 +70,62 @@ class CollectClipEffects(pyblish.api.InstancePlugin): subset_split.insert(0, "effect") - name = "".join(subset_split) + effect_categories = { + x["name"]: x["effect_classes"] for x in self.effect_categories + } - # create new instance and inherit data - data = {} - for key, value in instance.data.items(): - if "clipEffectItems" in key: + category_by_effect = {} + for key, values in effect_categories.items(): + for cls in values: + category_by_effect[cls] = key + + effects_categorized = {k: {} for k in effect_categories.keys()} + for key, value in effects.items(): + if key == "assignTo": continue - data[key] = value - # change names - data["subset"] = name - data["family"] = family - data["families"] = [family] - data["name"] = data["subset"] + "_" + data["asset"] - data["label"] = "{} - {}".format( - data['asset'], data["subset"] - ) - data["effects"] = effects + # Some classes can have a number in them. Like Text2. + found_cls = None + for cls in category_by_effect.keys(): + if value["class"].startswith(cls): + found_cls = cls - # create new instance - _instance = instance.context.create_instance(**data) - self.log.info("Created instance `{}`".format(_instance)) - self.log.debug("instance.data `{}`".format(_instance.data)) + if found_cls is None: + continue + + effects_categorized[category_by_effect[found_cls]][key] = value + + if effects_categorized: + for key in effects_categorized.keys(): + effects_categorized[key]["assignTo"] = effects["assignTo"] + else: + effects_categorized[""] = effects + + for category, effects in effects_categorized.items(): + name = "".join(subset_split) + name += category.capitalize() + + # create new instance and inherit data + data = {} + for key, value in instance.data.items(): + if "clipEffectItems" in key: + continue + data[key] = value + + # change names + data["subset"] = name + data["family"] = family + data["families"] = [family] + data["name"] = data["subset"] + "_" + data["asset"] + data["label"] = "{} - {}".format( + data['asset'], data["subset"] + ) + data["effects"] = effects + + # create new instance + _instance = instance.context.create_instance(**data) + self.log.info("Created instance `{}`".format(_instance)) + self.log.debug("instance.data `{}`".format(_instance.data)) def test_overlap(self, effect_t_in, effect_t_out): covering_exp = bool( From 6857082f1e0e9cab399ccb85fe1ec5a41cfc010f Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 17 Jan 2024 03:26:10 +0000 Subject: [PATCH 124/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 0b6584dbff..043b6fbebb 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.4" +__version__ = "3.18.5-nightly.1" From 4cf6ddfea215c5d747fef01ba00ffe18da2b2fb2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Jan 2024 03:26:46 +0000 Subject: [PATCH 125/281] 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 8df948a7c1..1177b9e4fe 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.18.5-nightly.1 - 3.18.4 - 3.18.4-nightly.1 - 3.18.3 @@ -134,7 +135,6 @@ body: - 3.15.7 - 3.15.7-nightly.3 - 3.15.7-nightly.2 - - 3.15.7-nightly.1 validations: required: true - type: dropdown From b7557c754385a8a966c1b7fcce804cbcfa9e8dcf Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 17 Jan 2024 09:12:14 +0000 Subject: [PATCH 126/281] Increment version --- server_addon/applications/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py index ae7362549b..bbab0242f6 100644 --- a/server_addon/applications/server/version.py +++ b/server_addon/applications/server/version.py @@ -1 +1 @@ -__version__ = "0.1.3" +__version__ = "0.1.4" From 883eb9f4f86c49bf6e387d38fac1dcc9042c93f3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 17 Jan 2024 09:13:03 +0000 Subject: [PATCH 127/281] Increment version --- server_addon/hiero/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/hiero/server/version.py b/server_addon/hiero/server/version.py index 485f44ac21..3ced3581bb 100644 --- a/server_addon/hiero/server/version.py +++ b/server_addon/hiero/server/version.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.2.1" From d1fb633ce72cf838ec29ef3a52173edb7ab06e38 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 17 Jan 2024 09:13:54 +0000 Subject: [PATCH 128/281] Increment version --- server_addon/nuke/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index 9cb17e7976..c49a95c357 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.8" +__version__ = "0.2.8" From 58978f55d8d54c79c25bbb7841e3692cb037692b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 17 Jan 2024 09:14:55 +0000 Subject: [PATCH 129/281] Increment version --- server_addon/maya/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index b87834cc35..684d830189 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.7" +__version__ = "0.1.8" From 7e3e567f002974436badeea5ff25bfa8db9461cf Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 17 Jan 2024 10:46:50 +0000 Subject: [PATCH 130/281] Use the new API for override context --- openpype/hosts/blender/api/capture.py | 11 +++--- .../hosts/blender/plugins/load/load_audio.py | 35 ++++++++++--------- .../plugins/publish/extract_abc_animation.py | 14 ++++---- .../plugins/publish/extract_camera_fbx.py | 26 +++++++------- .../blender/plugins/publish/extract_fbx.py | 18 +++++----- .../plugins/publish/extract_fbx_animation.py | 25 +++++++------ .../blender/plugins/publish/extract_layout.py | 23 ++++++------ 7 files changed, 81 insertions(+), 71 deletions(-) diff --git a/openpype/hosts/blender/api/capture.py b/openpype/hosts/blender/api/capture.py index bad6831143..9922140cea 100644 --- a/openpype/hosts/blender/api/capture.py +++ b/openpype/hosts/blender/api/capture.py @@ -127,8 +127,9 @@ def isolate_objects(window, objects): context = create_blender_context(selected=objects, window=window) - bpy.ops.view3d.view_axis(context, type="FRONT") - bpy.ops.view3d.localview(context) + with bpy.context.temp_override(**context): + bpy.ops.view3d.view_axis(type="FRONT") + bpy.ops.view3d.localview() deselect_all() @@ -270,10 +271,12 @@ def _independent_window(): """Create capture-window context.""" context = create_blender_context() current_windows = set(bpy.context.window_manager.windows) - bpy.ops.wm.window_new(context) + with bpy.context.temp_override(**context): + bpy.ops.wm.window_new() window = list(set(bpy.context.window_manager.windows) - current_windows)[0] context["window"] = window try: yield window finally: - bpy.ops.wm.window_close(context) + with bpy.context.temp_override(**context): + bpy.ops.wm.window_close() diff --git a/openpype/hosts/blender/plugins/load/load_audio.py b/openpype/hosts/blender/plugins/load/load_audio.py index 1e5bd39a32..367fff03f0 100644 --- a/openpype/hosts/blender/plugins/load/load_audio.py +++ b/openpype/hosts/blender/plugins/load/load_audio.py @@ -67,7 +67,8 @@ class AudioLoader(plugin.AssetLoader): oc = bpy.context.copy() oc["area"] = window_manager.windows[-1].screen.areas[0] - bpy.ops.sequencer.sound_strip_add(oc, filepath=libpath, frame_start=1) + with bpy.context.temp_override(**oc): + bpy.ops.sequencer.sound_strip_add(filepath=libpath, frame_start=1) window_manager.windows[-1].screen.areas[0].type = old_type @@ -156,17 +157,18 @@ class AudioLoader(plugin.AssetLoader): oc = bpy.context.copy() oc["area"] = window_manager.windows[-1].screen.areas[0] - # We deselect all sequencer strips, and then select the one we - # need to remove. - bpy.ops.sequencer.select_all(oc, action='DESELECT') - scene = bpy.context.scene - scene.sequence_editor.sequences_all[old_audio].select = True + with bpy.context.temp_override(**oc): + # We deselect all sequencer strips, and then select the one we + # need to remove. + bpy.ops.sequencer.select_all(action='DESELECT') + scene = bpy.context.scene + scene.sequence_editor.sequences_all[old_audio].select = True - bpy.ops.sequencer.delete(oc) - bpy.data.sounds.remove(bpy.data.sounds[old_audio]) + bpy.ops.sequencer.delete() + bpy.data.sounds.remove(bpy.data.sounds[old_audio]) - bpy.ops.sequencer.sound_strip_add( - oc, filepath=str(libpath), frame_start=1) + bpy.ops.sequencer.sound_strip_add( + filepath=str(libpath), frame_start=1) window_manager.windows[-1].screen.areas[0].type = old_type @@ -205,12 +207,13 @@ class AudioLoader(plugin.AssetLoader): oc = bpy.context.copy() oc["area"] = window_manager.windows[-1].screen.areas[0] - # We deselect all sequencer strips, and then select the one we - # need to remove. - bpy.ops.sequencer.select_all(oc, action='DESELECT') - bpy.context.scene.sequence_editor.sequences_all[audio].select = True - - bpy.ops.sequencer.delete(oc) + with bpy.context.temp_override(**oc): + # We deselect all sequencer strips, and then select the one we + # need to remove. + bpy.ops.sequencer.select_all(action='DESELECT') + scene = bpy.context.scene + scene.sequence_editor.sequences_all[audio].select = True + bpy.ops.sequencer.delete() window_manager.windows[-1].screen.areas[0].type = old_type diff --git a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py index 6ef9b29693..2bf75aa05e 100644 --- a/openpype/hosts/blender/plugins/publish/extract_abc_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc_animation.py @@ -55,13 +55,13 @@ class ExtractAnimationABC( context = plugin.create_blender_context( active=asset_group, selected=selected) - # We export the abc - bpy.ops.wm.alembic_export( - context, - filepath=filepath, - selected=True, - flatten=False - ) + with bpy.context.temp_override(**context): + # We export the abc + bpy.ops.wm.alembic_export( + filepath=filepath, + selected=True, + flatten=False + ) plugin.deselect_all() diff --git a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py index ee046b7d11..f904b79ddb 100644 --- a/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_camera_fbx.py @@ -50,19 +50,19 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin): scale_length = bpy.context.scene.unit_settings.scale_length bpy.context.scene.unit_settings.scale_length = 0.01 - # We export the fbx - bpy.ops.export_scene.fbx( - context, - filepath=filepath, - use_active_collection=False, - use_selection=True, - bake_anim_use_nla_strips=False, - bake_anim_use_all_actions=False, - add_leaf_bones=False, - armature_nodetype='ROOT', - object_types={'CAMERA'}, - bake_anim_simplify_factor=0.0 - ) + with bpy.context.temp_override(**context): + # We export the fbx + bpy.ops.export_scene.fbx( + filepath=filepath, + use_active_collection=False, + use_selection=True, + bake_anim_use_nla_strips=False, + bake_anim_use_all_actions=False, + add_leaf_bones=False, + armature_nodetype='ROOT', + object_types={'CAMERA'}, + bake_anim_simplify_factor=0.0 + ) bpy.context.scene.unit_settings.scale_length = scale_length diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py index 4ae6501f7d..aed6df1d3d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -57,15 +57,15 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin): scale_length = bpy.context.scene.unit_settings.scale_length bpy.context.scene.unit_settings.scale_length = 0.01 - # We export the fbx - bpy.ops.export_scene.fbx( - context, - filepath=filepath, - use_active_collection=False, - use_selection=True, - mesh_smooth_type='FACE', - add_leaf_bones=False - ) + with bpy.context.temp_override(**context): + # We export the fbx + bpy.ops.export_scene.fbx( + filepath=filepath, + use_active_collection=False, + use_selection=True, + mesh_smooth_type='FACE', + add_leaf_bones=False + ) bpy.context.scene.unit_settings.scale_length = scale_length diff --git a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 4fc8230a1b..1cb8dac0cf 100644 --- a/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -153,17 +153,20 @@ class ExtractAnimationFBX( override = plugin.create_blender_context( active=root, selected=[root, armature]) - bpy.ops.export_scene.fbx( - override, - filepath=filepath, - use_active_collection=False, - use_selection=True, - bake_anim_use_nla_strips=False, - bake_anim_use_all_actions=False, - add_leaf_bones=False, - armature_nodetype='ROOT', - object_types={'EMPTY', 'ARMATURE'} - ) + + with bpy.context.temp_override(**override): + # We export the fbx + bpy.ops.export_scene.fbx( + filepath=filepath, + use_active_collection=False, + use_selection=True, + bake_anim_use_nla_strips=False, + bake_anim_use_all_actions=False, + add_leaf_bones=False, + armature_nodetype='ROOT', + object_types={'EMPTY', 'ARMATURE'} + ) + armature.name = armature_name asset_group.name = asset_group_name root.select_set(True) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 3e8978c8d3..383c3bdcc5 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -80,17 +80,18 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin): override = plugin.create_blender_context( active=asset, selected=[asset, obj]) - bpy.ops.export_scene.fbx( - override, - filepath=filepath, - use_active_collection=False, - use_selection=True, - bake_anim_use_nla_strips=False, - bake_anim_use_all_actions=False, - add_leaf_bones=False, - armature_nodetype='ROOT', - object_types={'EMPTY', 'ARMATURE'} - ) + with bpy.context.temp_override(**override): + # We export the fbx + bpy.ops.export_scene.fbx( + filepath=filepath, + use_active_collection=False, + use_selection=True, + bake_anim_use_nla_strips=False, + bake_anim_use_all_actions=False, + add_leaf_bones=False, + armature_nodetype='ROOT', + object_types={'EMPTY', 'ARMATURE'} + ) obj.name = armature_name asset.name = asset_group_name asset.select_set(False) From 38eb8e0ae38169137316b57e90271c640b96420a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 17 Jan 2024 11:31:29 +0000 Subject: [PATCH 131/281] initial working version --- .../plugins/publish/submit_nuke_deadline.py | 43 ++++++++++++------- .../defaults/project_settings/deadline.json | 2 + .../schema_project_deadline.json | 10 +++++ .../server/settings/publish_plugins.py | 3 ++ 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index d03416ca00..ead22fe302 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -47,6 +47,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, env_allowed_keys = [] env_search_replace_values = {} workfile_dependency = True + use_published_workfile = True @classmethod def get_attribute_defs(cls): @@ -85,8 +86,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, ), BoolDef( "workfile_dependency", - default=True, + default=cls.workfile_dependency, label="Workfile Dependency" + ), + BoolDef( + "use_published_workfile", + default=cls.use_published_workfile, + label="Use Published Workfile" ) ] @@ -125,20 +131,27 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, render_path = instance.data['path'] script_path = context.data["currentFile"] - for item_ in context: - if "workfile" in item_.data["family"]: - template_data = item_.data.get("anatomyData") - rep = item_.data.get("representations")[0].get("name") - template_data["representation"] = rep - template_data["ext"] = rep - template_data["comment"] = None - anatomy_filled = context.data["anatomy"].format(template_data) - template_filled = anatomy_filled["publish"]["path"] - script_path = os.path.normpath(template_filled) - - self.log.info( - "Using published scene for render {}".format(script_path) - ) + use_published_workfile = instance.data["attributeValues"].get( + "use_published_workfile", self.use_published_workfile + ) + if use_published_workfile: + for item_ in context: + if "workfile" in item_.data["family"]: + template_data = item_.data.get("anatomyData") + rep = item_.data.get("representations")[0].get("name") + template_data["representation"] = rep + template_data["ext"] = rep + template_data["comment"] = None + anatomy_filled = context.data["anatomy"].format( + template_data + ) + template_filled = anatomy_filled["publish"]["path"] + script_path = os.path.normpath(template_filled) + self.log.info( + "Using published scene for render {}".format( + script_path + ) + ) # only add main rendering job if target is not frames_farm r_job_response_json = None diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index a19464a5c1..b02cfa8207 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -65,6 +65,8 @@ "group": "", "department": "", "use_gpu": true, + "workfile_dependency": true, + "use_published_workfile": true, "env_allowed_keys": [], "env_search_replace_values": {}, "limit_groups": {} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 1aea778e32..42dea33ef9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -362,6 +362,16 @@ "key": "use_gpu", "label": "Use GPU" }, + { + "type": "boolean", + "key": "workfile_dependency", + "label": "Workfile Dependency" + }, + { + "type": "boolean", + "key": "use_published_workfile", + "label": "Use Published Workfile" + }, { "type": "list", "key": "env_allowed_keys", diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index a989f3ad9d..8e44d8d47f 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -161,6 +161,8 @@ class NukeSubmitDeadlineModel(BaseSettingsModel): group: str = Field(title="Group") department: str = Field(title="Department") use_gpu: bool = Field(title="Use GPU") + workfile_dependency: bool = Field(title="Workfile Dependency") + use_published_workfile: bool = Field(title="Use Published Workfile") env_allowed_keys: list[str] = Field( default_factory=list, @@ -382,6 +384,7 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = { "group": "", "department": "", "use_gpu": True, + "workfile_dependency": True, "env_allowed_keys": [], "env_search_replace_values": [], "limit_groups": [] From 0534c9c55d9eb5d0f193b2c64c3a870b6082a092 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:13:24 +0000 Subject: [PATCH 132/281] Fix error when getting setting value in Ayon --- openpype/hosts/blender/api/render_lib.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index cdccf13805..904cabfc05 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -2,6 +2,7 @@ from pathlib import Path import bpy +from openpype import AYON_SERVER_ENABLED from openpype.settings import get_project_settings from openpype.pipeline import get_current_project_name @@ -124,13 +125,14 @@ def set_render_passes(settings): aovs_names = [aov.name for aov in vl.aovs] for cp in custom_passes: - cp_name = cp[0] + cp_name = cp["attribute"] if AYON_SERVER_ENABLED else cp[0] if cp_name not in aovs_names: aov = vl.aovs.add() aov.name = cp_name else: aov = vl.aovs[cp_name] - aov.type = cp[1].get("type", "VALUE") + aov.type = (cp["value"] + if AYON_SERVER_ENABLED else cp[1].get("type", "VALUE")) return aov_list, custom_passes From 1eb07bcedb97edef9bbf57fa7c605bf85794a33f Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:16:05 +0000 Subject: [PATCH 133/281] Added setting to choose render engine --- openpype/hosts/blender/api/render_lib.py | 11 ++++++++++- .../blender/server/settings/render_settings.py | 13 +++++++++++++ server_addon/blender/server/version.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 904cabfc05..06353f8e8b 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -48,6 +48,14 @@ def get_multilayer(settings): ["multilayer_exr"]) +def get_renderer(settings): + """Get renderer from blender settings.""" + + return (settings["blender"] + ["RenderSettings"] + ["renderer"]) + + def get_render_product(output_path, name, aov_sep): """ Generate the path to the render product. Blender interprets the `#` @@ -254,9 +262,10 @@ def prepare_rendering(asset_group): aov_sep = get_aov_separator(settings) ext = get_image_format(settings) multilayer = get_multilayer(settings) + renderer = get_renderer(settings) set_render_format(ext, multilayer) - aov_list, custom_passes = set_render_passes(settings) + bpy.context.scene.render.engine = renderer output_path = Path.joinpath(dirpath, render_folder, file_name) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index f62013982e..53cefd145d 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -25,6 +25,13 @@ def image_format_enum(): ] +def renderers_enum(): + return [ + {"value": "CYCLES", "label": "Cycles"}, + {"value": "BLENDER_EEVEE", "label": "Eevee"}, + ] + + def aov_list_enum(): return [ {"value": "empty", "label": "< none >"}, @@ -83,6 +90,11 @@ class RenderSettingsModel(BaseSettingsModel): multilayer_exr: bool = Field( title="Multilayer (EXR)" ) + renderer: str = Field( + "CYCLES", + title="Renderer", + enum_resolver=renderers_enum + ) aov_list: list[str] = Field( default_factory=list, enum_resolver=aov_list_enum, @@ -104,6 +116,7 @@ DEFAULT_RENDER_SETTINGS = { "aov_separator": "underscore", "image_format": "exr", "multilayer_exr": True, + "renderer": "CYCLES", "aov_list": [], "custom_passes": [] } diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py index 1276d0254f..0a8da88258 100644 --- a/server_addon/blender/server/version.py +++ b/server_addon/blender/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From 2f58708f584a63a68ffcbe2c429704dca566f43b Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 18 Jan 2024 10:16:31 +0000 Subject: [PATCH 134/281] Updated aov list --- openpype/hosts/blender/api/render_lib.py | 62 ++++++++++++++----- .../server/settings/render_settings.py | 50 ++++++++++++--- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 06353f8e8b..44ee2be208 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -100,36 +100,69 @@ def set_render_format(ext, multilayer): image_settings.file_format = "TIFF" -def set_render_passes(settings): - aov_list = (settings["blender"] - ["RenderSettings"] - ["aov_list"]) - - custom_passes = (settings["blender"] - ["RenderSettings"] - ["custom_passes"]) +def set_render_passes(settings, renderer): + aov_list = settings["blender"]["RenderSettings"]["aov_list"] + custom_passes = settings["blender"]["RenderSettings"]["custom_passes"] + # Common passes for both renderers vl = bpy.context.view_layer + # Data Passes vl.use_pass_combined = "combined" in aov_list vl.use_pass_z = "z" in aov_list vl.use_pass_mist = "mist" in aov_list vl.use_pass_normal = "normal" in aov_list + + # Light Passes vl.use_pass_diffuse_direct = "diffuse_light" in aov_list vl.use_pass_diffuse_color = "diffuse_color" in aov_list vl.use_pass_glossy_direct = "specular_light" in aov_list vl.use_pass_glossy_color = "specular_color" in aov_list - vl.eevee.use_pass_volume_direct = "volume_light" in aov_list vl.use_pass_emit = "emission" in aov_list vl.use_pass_environment = "environment" in aov_list - vl.use_pass_shadow = "shadow" in aov_list vl.use_pass_ambient_occlusion = "ao" in aov_list - cycles = vl.cycles + # Cryptomatte Passes + vl.use_pass_cryptomatte_object = "cryptomatte_object" in aov_list + vl.use_pass_cryptomatte_material = "cryptomatte_material" in aov_list + vl.use_pass_cryptomatte_asset = "cryptomatte_asset" in aov_list - cycles.denoising_store_passes = "denoising" in aov_list - cycles.use_pass_volume_direct = "volume_direct" in aov_list - cycles.use_pass_volume_indirect = "volume_indirect" in aov_list + if renderer == "BLENDER_EEVEE": + # Eevee exclusive passes + eevee = vl.eevee + + # Light Passes + vl.use_pass_shadow = "shadow" in aov_list + eevee.use_pass_volume_direct = "volume_light" in aov_list + + # Effects Passes + eevee.use_pass_bloom = "bloom" in aov_list + eevee.use_pass_transparent = "transparent" in aov_list + + # Cryptomatte Passes + vl.use_pass_cryptomatte_accurate = "cryptomatte_accurate" in aov_list + elif renderer == "CYCLES": + # Cycles exclusive passes + cycles = vl.cycles + + # Data Passes + vl.use_pass_position = "position" in aov_list + vl.use_pass_vector = "vector" in aov_list + vl.use_pass_uv = "uv" in aov_list + cycles.denoising_store_passes = "denoising" in aov_list + vl.use_pass_object_index = "object_index" in aov_list + vl.use_pass_material_index = "material_index" in aov_list + cycles.pass_debug_sample_count = "sample_count" in aov_list + + # Light Passes + vl.use_pass_diffuse_indirect = "diffuse_indirect" in aov_list + vl.use_pass_glossy_indirect = "specular_indirect" in aov_list + vl.use_pass_transmission_direct = "transmission_direct" in aov_list + vl.use_pass_transmission_indirect = "transmission_indirect" in aov_list + vl.use_pass_transmission_color = "transmission_color" in aov_list + cycles.use_pass_volume_direct = "volume_light" in aov_list + cycles.use_pass_volume_indirect = "volume_indirect" in aov_list + cycles.use_pass_shadow_catcher = "shadow" in aov_list aovs_names = [aov.name for aov in vl.aovs] for cp in custom_passes: @@ -266,6 +299,7 @@ def prepare_rendering(asset_group): set_render_format(ext, multilayer) bpy.context.scene.render.engine = renderer + aov_list, custom_passes = set_render_passes(settings, renderer) output_path = Path.joinpath(dirpath, render_folder, file_name) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index 53cefd145d..3ab720fc6a 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -39,18 +39,52 @@ def aov_list_enum(): {"value": "z", "label": "Z"}, {"value": "mist", "label": "Mist"}, {"value": "normal", "label": "Normal"}, - {"value": "diffuse_light", "label": "Diffuse Light"}, + {"value": "position", "label": "Position (Cycles Only)"}, + {"value": "vector", "label": "Vector (Cycles Only)"}, + {"value": "uv", "label": "UV (Cycles Only)"}, + {"value": "denoising", "label": "Denoising Data (Cycles Only)"}, + {"value": "object_index", "label": "Object Index (Cycles Only)"}, + {"value": "material_index", "label": "Material Index (Cycles Only)"}, + {"value": "sample_count", "label": "Sample Count (Cycles Only)"}, + {"value": "diffuse_light", "label": "Diffuse Light/Direct"}, + { + "value": "diffuse_indirect", + "label": "Diffuse Indirect (Cycles Only)" + }, {"value": "diffuse_color", "label": "Diffuse Color"}, - {"value": "specular_light", "label": "Specular Light"}, - {"value": "specular_color", "label": "Specular Color"}, - {"value": "volume_light", "label": "Volume Light"}, + {"value": "specular_light", "label": "Specular (Glossy) Light/Direct"}, + { + "value": "specular_indirect", + "label": "Specular (Glossy) Indirect (Cycles Only)" + }, + {"value": "specular_color", "label": "Specular (Glossy) Color"}, + { + "value": "transmission_light", + "label": "Transmission Light/Direct (Cycles Only)" + }, + { + "value": "transmission_indirect", + "label": "Transmission Indirect (Cycles Only)" + }, + { + "value": "transmission_color", + "label": "Transmission Color (Cycles Only)" + }, + {"value": "volume_light", "label": "Volume Light/Direct"}, + {"value": "volume_indirect", "label": "Volume Indirect (Cycles Only)"}, {"value": "emission", "label": "Emission"}, {"value": "environment", "label": "Environment"}, - {"value": "shadow", "label": "Shadow"}, + {"value": "shadow", "label": "Shadow/Shadow Catcher"}, {"value": "ao", "label": "Ambient Occlusion"}, - {"value": "denoising", "label": "Denoising"}, - {"value": "volume_direct", "label": "Direct Volumetric Scattering"}, - {"value": "volume_indirect", "label": "Indirect Volumetric Scattering"} + {"value": "bloom", "label": "Bloom (Eevee Only)"}, + {"value": "transparent", "label": "Transparent (Eevee Only)"}, + {"value": "cryptomatte_object", "label": "Cryptomatte Object"}, + {"value": "cryptomatte_material", "label": "Cryptomatte Material"}, + {"value": "cryptomatte_asset", "label": "Cryptomatte Asset"}, + { + "value": "cryptomatte_accurate", + "label": "Cryptomatte Accurate Mode (Eevee Only)" + }, ] From acd61bef12f9580f3b1b1ed189b7864ed7623316 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 18 Jan 2024 12:15:10 +0000 Subject: [PATCH 135/281] Increment version --- server_addon/nuke/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index 9cb17e7976..c49a95c357 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.8" +__version__ = "0.2.8" From 6591d883e495c7d5b6ac80da1085f556227d1859 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 18 Jan 2024 14:43:20 +0000 Subject: [PATCH 136/281] Correct version increment --- server_addon/deadline/server/version.py | 2 +- server_addon/nuke/server/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index 0a8da88258..01ef12070d 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.2.6" diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index c49a95c357..9cb17e7976 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.2.8" +__version__ = "0.1.8" From 423df8d1fdaaf5742b8aad0a7002e98cdbe1daa9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 18 Jan 2024 14:44:42 +0000 Subject: [PATCH 137/281] Fix settings defaults. --- server_addon/deadline/server/settings/publish_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index 8e44d8d47f..9fc8d4a6b7 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -385,6 +385,7 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = { "department": "", "use_gpu": True, "workfile_dependency": True, + "use_published_workfile": True, "env_allowed_keys": [], "env_search_replace_values": [], "limit_groups": [] From 5761c4e5d00147eacf77bd275be12c007684912f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 18 Jan 2024 16:15:31 +0100 Subject: [PATCH 138/281] Update server_addon/deadline/server/version.py --- server_addon/deadline/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index 6cd38b7465..9cb17e7976 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.2.7" +__version__ = "0.1.8" From 00ddff5ff19f2c17f672b77570d3e5407d621921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 18 Jan 2024 16:25:08 +0100 Subject: [PATCH 139/281] Update server_addon/nuke/server/version.py --- server_addon/nuke/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index c49a95c357..c11f861afb 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.2.8" +__version__ = "0.1.9" From 1d7f23ab72b83b94fd951426c392035a877fbbce Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 19 Jan 2024 09:47:17 +0200 Subject: [PATCH 140/281] include model product type --- openpype/hosts/houdini/plugins/load/load_fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index cac22d62d4..649c2e9995 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -16,7 +16,7 @@ class FbxLoader(load.LoaderPlugin): order = -10 - families = ["staticMesh", "fbx"] + families = ["model", "staticMesh", "fbx"] representations = ["fbx"] def load(self, context, name=None, namespace=None, data=None): From 04c072947c7c7e5f93dc21ad2afd9434c44f2b95 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 19 Jan 2024 10:47:37 +0200 Subject: [PATCH 141/281] BigRoy's Comment - Make it able to load any FBX file --- openpype/hosts/houdini/plugins/load/load_fbx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_fbx.py b/openpype/hosts/houdini/plugins/load/load_fbx.py index 649c2e9995..894ac62b3e 100644 --- a/openpype/hosts/houdini/plugins/load/load_fbx.py +++ b/openpype/hosts/houdini/plugins/load/load_fbx.py @@ -16,8 +16,9 @@ class FbxLoader(load.LoaderPlugin): order = -10 - families = ["model", "staticMesh", "fbx"] - representations = ["fbx"] + families = ["*"] + representations = ["*"] + extensions = {"fbx"} def load(self, context, name=None, namespace=None, data=None): From 91f67ca9b0a199e110650fd899d562eae3cdcd16 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 10:00:58 +0000 Subject: [PATCH 142/281] Updated default settings for render passes --- server_addon/blender/server/settings/render_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index 3ab720fc6a..580547e510 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -151,6 +151,6 @@ DEFAULT_RENDER_SETTINGS = { "image_format": "exr", "multilayer_exr": True, "renderer": "CYCLES", - "aov_list": [], + "aov_list": ["combined"], "custom_passes": [] } From dd0533ecb36b19988ccb67b0b2e83a50f7090be1 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 10:03:05 +0000 Subject: [PATCH 143/281] Added composite output --- openpype/hosts/blender/api/render_lib.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 44ee2be208..fc47f5a659 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -186,12 +186,17 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): comp_layer_type = "CompositorNodeRLayers" output_type = "CompositorNodeOutputFile" + compositor_type = "CompositorNodeComposite" - # Get the Render Layers node + # Get the Render Layer and Composite nodes rl_node = None + comp_node = None for node in tree.nodes: if node.bl_idname == comp_layer_type: rl_node = node + elif node.bl_idname == compositor_type: + comp_node = node + if rl_node and comp_node: break # If there's not a Render Layers node, we create it @@ -242,29 +247,38 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): pass_name = "rgba" if multi_exr else "beauty" else: pass_name = rpass.name - filepath = f"{name}{aov_sep}{pass_name}.####" + filename = f"{name}{aov_sep}{pass_name}.####" - slots.new(pass_name if multi_exr else filepath) + slots.new(pass_name if multi_exr else filename) - filename = str(output_path / filepath.lstrip("/")) + filepath = str(output_path / filename.lstrip("/")) - aov_file_products.append((rpass.name, filename)) + aov_file_products.append((rpass.name, filepath)) if not old_output: node_input = output.inputs[-1] tree.links.new(rpass, node_input) + # Create a new socket for the composite output + pass_name = "composite" + filename = f"{name}{aov_sep}{pass_name}.####" + comp_socket = slots.new(pass_name if multi_exr else filename) + aov_file_products.append(("Composite", filepath)) + for link in tree.links: if link.to_node == old_output: socket_name = link.to_socket.name new_socket = output.inputs.get(socket_name) if new_socket: tree.links.new(link.from_socket, new_socket) + elif comp_node and link.to_node == comp_node: + tree.links.new(link.from_socket, comp_socket) if old_output: output.location = old_output.location tree.nodes.remove(old_output) output.name = "AYON File Output" + output.label = "AYON File Output" return [] if multi_exr else aov_file_products From c911c67dae109a3c769fa4fe584f925f3afe7a2e Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 11:04:25 +0000 Subject: [PATCH 144/281] Updated OpenPype Settings --- .../defaults/project_settings/blender.json | 3 +- .../schema_project_blender.json | 43 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 385e97ef91..48f3ef8ef0 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -22,7 +22,8 @@ "aov_separator": "underscore", "image_format": "exr", "multilayer_exr": true, - "aov_list": [], + "renderer": "CYCLES", + "aov_list": ["combined"], "custom_passes": [] }, "workfile_builder": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 535d9434a3..bbed881ab0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -103,6 +103,17 @@ "type": "label", "label": "Note: Multilayer EXR is only used when output format type set to EXR." }, + { + "key": "renderer", + "label": "Renderer", + "type": "enum", + "multiselection": false, + "defaults": "CYCLES", + "enum_items": [ + {"CYCLES": "Cycles"}, + {"BLENDER_EEVEE": "Eevee"} + ] + }, { "key": "aov_list", "label": "AOVs to create", @@ -115,18 +126,34 @@ {"z": "Z"}, {"mist": "Mist"}, {"normal": "Normal"}, - {"diffuse_light": "Diffuse Light"}, + {"position": "Position (Cycles Only)"}, + {"vector": "Vector (Cycles Only)"}, + {"uv": "UV (Cycles Only)"}, + {"denoising": "Denoising Data (Cycles Only)"}, + {"object_index": "Object Index (Cycles Only)"}, + {"material_index": "Material Index (Cycles Only)"}, + {"sample_count": "Sample Count (Cycles Only)"}, + {"diffuse_light": "Diffuse Light/Direct"}, + {"diffuse_indirect": "Diffuse Indirect (Cycles Only)"}, {"diffuse_color": "Diffuse Color"}, - {"specular_light": "Specular Light"}, - {"specular_color": "Specular Color"}, - {"volume_light": "Volume Light"}, + {"specular_light": "Specular (Glossy) Light/Direct"}, + {"specular_indirect": "Specular (Glossy) Indirect (Cycles Only)"}, + {"specular_color": "Specular (Glossy) Color"}, + {"transmission_light": "Transmission Light/Direct (Cycles Only)"}, + {"transmission_indirect": "Transmission Indirect (Cycles Only)"}, + {"transmission_color": "Transmission Color (Cycles Only)"}, + {"volume_light": "Volume Light/Direct"}, + {"volume_indirect": "Volume Indirect (Cycles Only)"}, {"emission": "Emission"}, {"environment": "Environment"}, - {"shadow": "Shadow"}, + {"shadow": "Shadow/Shadow Catcher"}, {"ao": "Ambient Occlusion"}, - {"denoising": "Denoising"}, - {"volume_direct": "Direct Volumetric Scattering"}, - {"volume_indirect": "Indirect Volumetric Scattering"} + {"bloom": "Bloom (Eevee Only)"}, + {"transparent": "Transparent (Eevee Only)"}, + {"cryptomatte_object": "Cryptomatte Object"}, + {"cryptomatte_material": "Cryptomatte Material"}, + {"cryptomatte_asset": "Cryptomatte Asset"}, + {"cryptomatte_accurate": "Cryptomatte Accurate Mode (Eevee Only)"} ] }, { From 5a524b7fd99a7226e450eac2d7a12c2e239cf900 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 19 Jan 2024 16:27:33 +0000 Subject: [PATCH 145/281] Restore actions to objects after update --- .../hosts/blender/plugins/load/load_blend.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 2d5ac18149..4abe4a6afb 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -102,7 +102,6 @@ class BlendLoader(plugin.AssetLoader): # Link all the container children to the collection for obj in container.children_recursive: - print(obj) bpy.context.scene.collection.objects.link(obj) # Remove the library from the blend file @@ -194,8 +193,20 @@ class BlendLoader(plugin.AssetLoader): transform = asset_group.matrix_basis.copy() old_data = dict(asset_group.get(AVALON_PROPERTY)) + old_members = old_data.get("members", []) parent = asset_group.parent + actions = {} + objects_with_anim = [ + obj for obj in asset_group.children_recursive + if obj.animation_data] + for obj in objects_with_anim: + # Check if the object has an action and, if so, add it to a dict + # so we can restore it later. Save and restore the action only + # if it wasn't originally loaded from the current asset. + if obj.animation_data.action not in old_members: + actions[obj.name] = obj.animation_data.action + self.exec_remove(container) asset_group, members = self._process_data(libpath, group_name) @@ -206,6 +217,11 @@ class BlendLoader(plugin.AssetLoader): asset_group.matrix_basis = transform asset_group.parent = parent + # Restore the actions + for obj in asset_group.children_recursive: + if obj.name in actions: + obj.animation_data.action = actions[obj.name] + # Restore the old data, but reset memebers, as they don't exist anymore # This avoids a crash, because the memory addresses of those members # are not valid anymore From 372c74b40674ad254f5665e2b9c0e62234101746 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 19 Jan 2024 18:00:44 +0100 Subject: [PATCH 146/281] use correct variable in queue loop --- openpype/plugins/publish/collect_anatomy_instance_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 34df49ea5b..8cc1d2b5e3 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -412,7 +412,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): hierarchy_queue = collections.deque() hierarchy_queue.append(hierarchy_context) while hierarchy_queue: - item = hierarchy_context.popleft() + item = hierarchy_queue.popleft() if asset_name in item: return item[asset_name].get("tasks") or {} From 4d1907b94bcd01b916a7ddb70834232ff70af778 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 19 Jan 2024 18:00:59 +0100 Subject: [PATCH 147/281] create copy of hierarchy context before any manipulation --- openpype/plugins/publish/collect_anatomy_instance_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 8cc1d2b5e3..b1b7ecd138 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -410,7 +410,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): """ hierarchy_queue = collections.deque() - hierarchy_queue.append(hierarchy_context) + hierarchy_queue.append(copy.deepcopy(hierarchy_context)) while hierarchy_queue: item = hierarchy_queue.popleft() if asset_name in item: From 9a6f518a51bd34ca99c747eb3795b858235a6ce5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 19 Jan 2024 18:01:15 +0100 Subject: [PATCH 148/281] do not store 'hierarchyContext' to variable --- openpype/plugins/publish/extract_hierarchy_to_ayon.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_hierarchy_to_ayon.py b/openpype/plugins/publish/extract_hierarchy_to_ayon.py index b601a3fc29..9e84daca30 100644 --- a/openpype/plugins/publish/extract_hierarchy_to_ayon.py +++ b/openpype/plugins/publish/extract_hierarchy_to_ayon.py @@ -30,8 +30,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin): if not AYON_SERVER_ENABLED: return - hierarchy_context = context.data.get("hierarchyContext") - if not hierarchy_context: + if not context.data.get("hierarchyContext"): self.log.debug("Skipping ExtractHierarchyToAYON") return From e0f6497f95006f1190c4a619b566f54386dabf54 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 20 Jan 2024 03:25:30 +0000 Subject: [PATCH 149/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 043b6fbebb..5bb4b4fb0a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.5-nightly.1" +__version__ = "3.18.5-nightly.2" From c3af397348029bf3337262d1fb9f5a469292eb35 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Jan 2024 03:26:05 +0000 Subject: [PATCH 150/281] 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 1177b9e4fe..e831bf3dc1 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.18.5-nightly.2 - 3.18.5-nightly.1 - 3.18.4 - 3.18.4-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.8-nightly.1 - 3.15.7 - 3.15.7-nightly.3 - - 3.15.7-nightly.2 validations: required: true - type: dropdown From c2ea9303a3a30f491d1984b06f7b91b388f786c1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 22 Jan 2024 11:03:02 +0000 Subject: [PATCH 151/281] Collect non-categorized effects as well --- .../plugins/publish/collect_clip_effects.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index 89dc66d73c..9c147e88d6 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -74,32 +74,32 @@ class CollectClipEffects(pyblish.api.InstancePlugin): x["name"]: x["effect_classes"] for x in self.effect_categories } - category_by_effect = {} + category_by_effect = {"": ""} for key, values in effect_categories.items(): for cls in values: category_by_effect[cls] = key effects_categorized = {k: {} for k in effect_categories.keys()} + effects_categorized[""] = {} for key, value in effects.items(): if key == "assignTo": continue # Some classes can have a number in them. Like Text2. - found_cls = None + found_cls = "" for cls in category_by_effect.keys(): if value["class"].startswith(cls): found_cls = cls - if found_cls is None: - continue - effects_categorized[category_by_effect[found_cls]][key] = value - if effects_categorized: - for key in effects_categorized.keys(): - effects_categorized[key]["assignTo"] = effects["assignTo"] - else: - effects_categorized[""] = effects + categories = list(effects_categorized.keys()) + for category in categories: + if not effects_categorized[category]: + effects_categorized.pop(category) + continue + + effects_categorized[category]["assignTo"] = effects["assignTo"] for category, effects in effects_categorized.items(): name = "".join(subset_split) From 90fc4e27167052b0fd091b038ebc4593870c0668 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 15:21:30 +0100 Subject: [PATCH 152/281] implemented base of click wrapper --- openpype/click_wrap.py | 371 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 openpype/click_wrap.py diff --git a/openpype/click_wrap.py b/openpype/click_wrap.py new file mode 100644 index 0000000000..218825bf04 --- /dev/null +++ b/openpype/click_wrap.py @@ -0,0 +1,371 @@ +"""Simplified wrapper for 'click' python module. + +Module 'click' is used as main cli handler in AYON/OpenPype. Addons can +register their own subcommands with options. This wrapper allows to define +commands and options as with 'click', but without any dependency. + +Why not to use 'click' directly? Version of 'click' used in AYON/OpenPype +is not compatible with 'click' version used in some DCCs (e.g. Houdini 20+). +And updating 'click' would break other DCCs. + +How to use it? If you already have cli commands defined in addon, just replace +'click' with 'click_wrap' and it should work and modify your addon's cli +method to convert 'click_wrap' object to 'click' object. + +# Before +```python +import click +from openpype.modules import OpenPypeModule + + +class ExampleAddon(OpenPypeModule): + name = "example" + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group(ExampleAddon.name, help="Example addon") +def cli_main(): + pass + + +@cli_main.command(help="Example command") +@click.option("--arg1", help="Example argument 1", default="default1") +@click.option("--arg2", help="Example argument 2", is_flag=True) +def mycommand(arg1, arg2): + print(arg1, arg2) +``` + +# Now +``` +from openpype import click_wrap +from openpype.modules import OpenPypeModule + + +class ExampleAddon(OpenPypeModule): + name = "example" + + def cli(self, click_group): + click_group.add_command(cli_main.to_click_obj()) + + +@click_wrap.group(ExampleAddon.name, help="Example addon") +def cli_main(): + pass + + +@cli_main.command(help="Example command") +@click_wrap.option("--arg1", help="Example argument 1", default="default1") +@click_wrap.option("--arg2", help="Example argument 2", is_flag=True) +def mycommand(arg1, arg2): + print(arg1, arg2) +``` + + +Added small enhancements: +- most of the methods can be used as chained calls +- functions/methods 'command' and 'group' can be used in a way that + first argument is callback function and the rest are arguments + for click + +Example: + ```python + from openpype import click_wrap + from openpype.modules import OpenPypeModule + + + class ExampleAddon(OpenPypeModule): + name = "example" + + def cli(self, click_group): + # Define main command (name 'example') + main = click_wrap.group( + self._cli_main, name=self.name, help="Example addon" + ) + # Add subcommand (name 'mycommand') + ( + main.command( + self._cli_command, name="mycommand", help="Example command" + ) + .option( + "--arg1", help="Example argument 1", default="default1" + ) + .option( + "--arg2", help="Example argument 2", is_flag=True, + ) + ) + # Convert main command to click object and add it to parent group + click_group.add_command(main.to_click_obj()) + + def _cli_main(self): + pass + + def _cli_command(self, arg1, arg2): + print(arg1, arg2) + ``` + + ```shell + openpype_console addon example mycommand --arg1 value1 --arg2 + ``` +""" + +import collections + +FUNC_ATTR_NAME = "__ayon_cli_options__" + + +class Command(object): + def __init__(self, func, *args, **kwargs): + # Command function + self._func = func + # Command definition arguments + self._args = args + # Command definition kwargs + self._kwargs = kwargs + # Both 'options' and 'arguments' are stored to the same variable + # - keep order of options and arguments + self._options = getattr(func, FUNC_ATTR_NAME, []) + + def to_click_obj(self): + """Converts this object to click object. + + Returns: + click.Command: Click command object. + """ + + return convert_to_click(self) + + # --- Methods for 'convert_to_click' function --- + def get_args(self): + """ + Returns: + tuple: Command definition arguments. + """ + + return self._args + + def get_kwargs(self): + """ + Returns: + dict[str, Any]: Command definition kwargs. + """ + + return self._kwargs + + def get_func(self): + """ + Returns: + Function: Function to invoke on command trigger. + """ + + return self._func + + def iter_options(self): + """ + Yields: + tuple[str, tuple, dict]: Option type name with args and kwargs. + """ + + for item in self._options: + yield item + # ----------------------------------------------- + + def add_option(self, *args, **kwargs): + return self.add_option_by_type("option", *args, **kwargs) + + def add_argument(self, *args, **kwargs): + return self.add_option_by_type("argument", *args, **kwargs) + + option = add_option + argument = add_argument + + def add_option_by_type(self, option_name, *args, **kwargs): + self._options.append((option_name, args, kwargs)) + return self + + +class Group(Command): + def __init__(self, func, *args, **kwargs): + super(Group, self).__init__(func, *args, **kwargs) + # Store sub-groupd and sub-commands to the same variable + self._commands = [] + + # --- Methods for 'convert_to_click' function --- + def iter_commands(self): + for command in self._commands: + yield command + # ----------------------------------------------- + + def add_command(self, command): + """Add prepared command object as child. + + Args: + command (Command): Prepared command object. + """ + + if command not in self._commands: + self._commands.append(command) + + def add_group(self, group): + """Add prepared group object as child. + + Args: + group (Group): Prepared group object. + """ + + if group not in self._commands: + self._commands.append(group) + + def command(self, *args, **kwargs): + """Add child command. + + Returns: + Union[Command, Function]: New command object, or wrapper function. + """ + + return self._add_new(Command, *args, **kwargs) + + def group(self, *args, **kwargs): + """Add child group. + + Returns: + Union[Group, Function]: New group object, or wrapper function. + """ + + return self._add_new(Group, *args, **kwargs) + + def _add_new(self, target_cls, *args, **kwargs): + func = None + if args and callable(args[0]): + args = list(args) + func = args.pop(0) + args = tuple(args) + + def decorator(_func): + out = target_cls(_func, *args, **kwargs) + self._commands.append(out) + return out + + if func is not None: + return decorator(func) + return decorator + + +def convert_to_click(obj_to_convert): + """Convert wrapped object to click object. + + Args: + obj_to_convert (Command): Object to convert to click object. + + Returns: + click.Command: Click command object. + """ + + import click + + commands_queue = collections.deque() + commands_queue.append((obj_to_convert, None)) + top_obj = None + while commands_queue: + item = commands_queue.popleft() + command_obj, parent_obj = item + if not isinstance(command_obj, Command): + raise TypeError( + "Invalid type '{}' expected 'Command'".format(type(command_obj)) + ) + + if isinstance(command_obj, Group): + click_obj = ( + click.group( + *command_obj.get_args(), + **command_obj.get_kwargs() + )(command_obj.get_func()) + ) + + else: + click_obj = ( + click.command( + *command_obj.get_args(), + **command_obj.get_kwargs() + )(command_obj.get_func()) + ) + + for item in command_obj.iter_options(): + option_name, args, kwargs = item + if option_name == "option": + click.option(*args, **kwargs)(click_obj) + elif option_name == "argument": + click.argument(*args, **kwargs)(click_obj) + else: + raise ValueError("Invalid option name '{}'".format(option_name)) + + if top_obj is None: + top_obj = click_obj + + if parent_obj is not None: + parent_obj.add_command(click_obj) + + if isinstance(command_obj, Group): + for command in command_obj.iter_commands(): + commands_queue.append((command, click_obj)) + + return top_obj + + +def group(*args, **kwargs): + func = None + if args and callable(args[0]): + args = list(args) + func = args.pop(0) + args = tuple(args) + + def decorator(_func): + return Group(_func, *args, **kwargs) + + if func is not None: + return decorator(func) + return decorator + + +def command(*args, **kwargs): + func = None + if args and callable(args[0]): + args = list(args) + func = args.pop(0) + args = tuple(args) + + def decorator(_func): + return Command(_func, *args, **kwargs) + + if func is not None: + return decorator(func) + return decorator + + +def argument(*args, **kwargs): + def decorator(func): + return _add_option_to_func( + func, "argument", *args, **kwargs + ) + return decorator + + +def option(*args, **kwargs): + def decorator(func): + return _add_option_to_func( + func, "option", *args, **kwargs + ) + return decorator + + +def _add_option_to_func(func, option_name, *args, **kwargs): + if isinstance(func, Command): + func.add_option_by_type(option_name, *args, **kwargs) + return func + + if not hasattr(func, FUNC_ATTR_NAME): + setattr(func, FUNC_ATTR_NAME, []) + cli_options = getattr(func, FUNC_ATTR_NAME) + cli_options.append((option_name, args, kwargs)) + return func From 83c5e7d0de4a0779ce3e1ea3a2c86064f750cd94 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 15:22:24 +0100 Subject: [PATCH 153/281] use 'click_wrap' in ftrack --- openpype/modules/ftrack/ftrack_module.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index b5152ff9c4..c7df45d6a4 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -3,8 +3,7 @@ import json import collections import platform -import click - +from openpype import click_wrap from openpype.modules import ( OpenPypeModule, ITrayModule, @@ -489,7 +488,7 @@ class FtrackModule( return cred.get("username"), cred.get("api_key") def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(main.to_click_obj()) def _check_ftrack_url(url): @@ -540,24 +539,24 @@ def resolve_ftrack_url(url, logger=None): return ftrack_url -@click.group(FtrackModule.name, help="Ftrack module related commands.") +@click_wrap.group(FtrackModule.name, help="Ftrack module related commands.") def cli_main(): pass @cli_main.command() -@click.option("-d", "--debug", is_flag=True, help="Print debug messages") -@click.option("--ftrack-url", envvar="FTRACK_SERVER", +@click_wrap.option("-d", "--debug", is_flag=True, help="Print debug messages") +@click_wrap.option("--ftrack-url", envvar="FTRACK_SERVER", help="Ftrack server url") -@click.option("--ftrack-user", envvar="FTRACK_API_USER", +@click_wrap.option("--ftrack-user", envvar="FTRACK_API_USER", help="Ftrack api user") -@click.option("--ftrack-api-key", envvar="FTRACK_API_KEY", +@click_wrap.option("--ftrack-api-key", envvar="FTRACK_API_KEY", help="Ftrack api key") -@click.option("--legacy", is_flag=True, +@click_wrap.option("--legacy", is_flag=True, help="run event server without mongo storing") -@click.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY", +@click_wrap.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY", help="Clockify API key.") -@click.option("--clockify-workspace", envvar="CLOCKIFY_WORKSPACE", +@click_wrap.option("--clockify-workspace", envvar="CLOCKIFY_WORKSPACE", help="Clockify workspace") def eventserver( debug, From ade8d4d47cfa45780a63b9d376bdb1fe059a0c83 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Nov 2023 16:28:29 +0100 Subject: [PATCH 154/281] small fixes --- openpype/click_wrap.py | 8 ++++++-- openpype/modules/ftrack/ftrack_module.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/click_wrap.py b/openpype/click_wrap.py index 218825bf04..3db5037b2f 100644 --- a/openpype/click_wrap.py +++ b/openpype/click_wrap.py @@ -272,7 +272,9 @@ def convert_to_click(obj_to_convert): command_obj, parent_obj = item if not isinstance(command_obj, Command): raise TypeError( - "Invalid type '{}' expected 'Command'".format(type(command_obj)) + "Invalid type '{}' expected 'Command'".format( + type(command_obj) + ) ) if isinstance(command_obj, Group): @@ -298,7 +300,9 @@ def convert_to_click(obj_to_convert): elif option_name == "argument": click.argument(*args, **kwargs)(click_obj) else: - raise ValueError("Invalid option name '{}'".format(option_name)) + raise ValueError( + "Invalid option name '{}'".format(option_name) + ) if top_obj is None: top_obj = click_obj diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index c7df45d6a4..ed48b170a1 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -488,7 +488,7 @@ class FtrackModule( return cred.get("username"), cred.get("api_key") def cli(self, click_group): - click_group.add_command(main.to_click_obj()) + click_group.add_command(cli_main.to_click_obj()) def _check_ftrack_url(url): From eb7d264900b61892b843a68ae5238c6538795c60 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Jan 2024 17:48:58 +0100 Subject: [PATCH 155/281] moved 'click_wrap.py' to './modules' --- openpype/modules/__init__.py | 3 +++ openpype/{ => modules}/click_wrap.py | 0 2 files changed, 3 insertions(+) rename openpype/{ => modules}/click_wrap.py (100%) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 3097805353..87f3233afc 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from . import click_wrap from .interfaces import ( ILaunchHookPaths, IPluginPaths, @@ -28,6 +29,8 @@ from .base import ( __all__ = ( + "click_wrap", + "ILaunchHookPaths", "IPluginPaths", "ITrayModule", diff --git a/openpype/click_wrap.py b/openpype/modules/click_wrap.py similarity index 100% rename from openpype/click_wrap.py rename to openpype/modules/click_wrap.py From 159a2d1dbc83f82f2c7c7fec507b5d436e25bddd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Jan 2024 17:49:52 +0100 Subject: [PATCH 156/281] use new click_wrap in existing openpype modules --- openpype/hosts/standalonepublisher/addon.py | 13 ++++--- openpype/hosts/traypublisher/addon.py | 15 +++++--- openpype/hosts/webpublisher/addon.py | 34 +++++++++---------- .../example_addons/example_addon/addon.py | 6 ++-- openpype/modules/ftrack/ftrack_module.py | 2 +- openpype/modules/job_queue/module.py | 15 ++++---- openpype/modules/kitsu/kitsu_module.py | 18 +++++----- .../modules/sync_server/sync_server_module.py | 16 ++++++--- 8 files changed, 65 insertions(+), 54 deletions(-) diff --git a/openpype/hosts/standalonepublisher/addon.py b/openpype/hosts/standalonepublisher/addon.py index 67204b581b..607c4ecdae 100644 --- a/openpype/hosts/standalonepublisher/addon.py +++ b/openpype/hosts/standalonepublisher/addon.py @@ -1,10 +1,13 @@ import os -import click - from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process -from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon +from openpype.modules import ( + click_wrap, + OpenPypeModule, + ITrayAction, + IHostAddon, +) STANDALONEPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -37,10 +40,10 @@ class StandAlonePublishAddon(OpenPypeModule, ITrayAction, IHostAddon): run_detached_process(args) def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group( +@click_wrap.group( StandAlonePublishAddon.name, help="StandalonePublisher related commands.") def cli_main(): diff --git a/openpype/hosts/traypublisher/addon.py b/openpype/hosts/traypublisher/addon.py index 3b34f9e6e8..ca60760bab 100644 --- a/openpype/hosts/traypublisher/addon.py +++ b/openpype/hosts/traypublisher/addon.py @@ -1,10 +1,13 @@ import os -import click - from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process -from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon +from openpype.modules import ( + click_wrap, + OpenPypeModule, + ITrayAction, + IHostAddon, +) TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -38,10 +41,12 @@ class TrayPublishAddon(OpenPypeModule, IHostAddon, ITrayAction): run_detached_process(args) def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group(TrayPublishAddon.name, help="TrayPublisher related commands.") +@click_wrap.group( + TrayPublishAddon.name, + help="TrayPublisher related commands.") def cli_main(): pass diff --git a/openpype/hosts/webpublisher/addon.py b/openpype/hosts/webpublisher/addon.py index 4438775b03..810d9aa6c3 100644 --- a/openpype/hosts/webpublisher/addon.py +++ b/openpype/hosts/webpublisher/addon.py @@ -1,8 +1,6 @@ import os -import click - -from openpype.modules import OpenPypeModule, IHostAddon +from openpype.modules import click_wrap, OpenPypeModule, IHostAddon WEBPUBLISHER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -38,10 +36,10 @@ class WebpublisherAddon(OpenPypeModule, IHostAddon): ) def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group( +@click_wrap.group( WebpublisherAddon.name, help="Webpublisher related commands.") def cli_main(): @@ -49,10 +47,10 @@ def cli_main(): @cli_main.command() -@click.argument("path") -@click.option("-u", "--user", help="User email address") -@click.option("-p", "--project", help="Project") -@click.option("-t", "--targets", help="Targets", default=None, +@click_wrap.argument("path") +@click_wrap.option("-u", "--user", help="User email address") +@click_wrap.option("-p", "--project", help="Project") +@click_wrap.option("-t", "--targets", help="Targets", default=None, multiple=True) def publish(project, path, user=None, targets=None): """Start publishing (Inner command). @@ -67,11 +65,11 @@ def publish(project, path, user=None, targets=None): @cli_main.command() -@click.argument("path") -@click.option("-p", "--project", help="Project") -@click.option("-h", "--host", help="Host") -@click.option("-u", "--user", help="User email address") -@click.option("-t", "--targets", help="Targets", default=None, +@click_wrap.argument("path") +@click_wrap.option("-p", "--project", help="Project") +@click_wrap.option("-h", "--host", help="Host") +@click_wrap.option("-u", "--user", help="User email address") +@click_wrap.option("-t", "--targets", help="Targets", default=None, multiple=True) def publishfromapp(project, path, host, user=None, targets=None): """Start publishing through application (Inner command). @@ -86,10 +84,10 @@ def publishfromapp(project, path, host, user=None, targets=None): @cli_main.command() -@click.option("-e", "--executable", help="Executable") -@click.option("-u", "--upload_dir", help="Upload dir") -@click.option("-h", "--host", help="Host", default=None) -@click.option("-p", "--port", help="Port", default=None) +@click_wrap.option("-e", "--executable", help="Executable") +@click_wrap.option("-u", "--upload_dir", help="Upload dir") +@click_wrap.option("-h", "--host", help="Host", default=None) +@click_wrap.option("-p", "--port", help="Port", default=None) def webserver(executable, upload_dir, host=None, port=None): """Start service for communication with Webpublish Front end. diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index be1d3ff920..e9bcee85bb 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -8,9 +8,9 @@ in global space here until are required or used. """ import os -import click from openpype.modules import ( + click_wrap, JsonFilesSettingsDef, OpenPypeAddOn, ModulesManager, @@ -115,10 +115,10 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): } def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group(ExampleAddon.name, help="Example addon dynamic cli commands.") +@click_wrap.group(ExampleAddon.name, help="Example addon dynamic cli commands.") def cli_main(): pass diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index ed48b170a1..2042367a7e 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -3,8 +3,8 @@ import json import collections import platform -from openpype import click_wrap from openpype.modules import ( + click_wrap, OpenPypeModule, ITrayModule, IPluginPaths, diff --git a/openpype/modules/job_queue/module.py b/openpype/modules/job_queue/module.py index 7075fcea14..c267329a61 100644 --- a/openpype/modules/job_queue/module.py +++ b/openpype/modules/job_queue/module.py @@ -41,8 +41,7 @@ import json import copy import platform -import click -from openpype.modules import OpenPypeModule +from openpype.modules import OpenPypeModule, click_wrap from openpype.settings import get_system_settings @@ -153,7 +152,7 @@ class JobQueueModule(OpenPypeModule): return requests.get(api_path).json() def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) @classmethod def get_server_url_from_settings(cls): @@ -213,7 +212,7 @@ class JobQueueModule(OpenPypeModule): return main(str(executable), server_url) -@click.group( +@click_wrap.group( JobQueueModule.name, help="Application job server. Can be used as render farm." ) @@ -225,8 +224,8 @@ def cli_main(): "start_server", help="Start server handling workers and their jobs." ) -@click.option("--port", help="Server port") -@click.option("--host", help="Server host (ip address)") +@click_wrap.option("--port", help="Server port") +@click_wrap.option("--host", help="Server host (ip address)") def cli_start_server(port, host): JobQueueModule.start_server(port, host) @@ -236,7 +235,7 @@ def cli_start_server(port, host): "Start a worker for a specific application. (e.g. \"tvpaint/11.5\")" ) ) -@click.argument("app_name") -@click.option("--server_url", help="Server url which handle workers and jobs.") +@click_wrap.argument("app_name") +@click_wrap.option("--server_url", help="Server url which handle workers and jobs.") def cli_start_worker(app_name, server_url): JobQueueModule.start_worker(app_name, server_url) diff --git a/openpype/modules/kitsu/kitsu_module.py b/openpype/modules/kitsu/kitsu_module.py index 8d2d5ccd60..0ab627ba75 100644 --- a/openpype/modules/kitsu/kitsu_module.py +++ b/openpype/modules/kitsu/kitsu_module.py @@ -1,9 +1,9 @@ """Kitsu module.""" -import click import os from openpype.modules import ( + click_wrap, OpenPypeModule, IPluginPaths, ITrayAction, @@ -98,17 +98,17 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction): } def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) -@click.group(KitsuModule.name, help="Kitsu dynamic cli commands.") +@click_wrap.group(KitsuModule.name, help="Kitsu dynamic cli commands.") def cli_main(): pass @cli_main.command() -@click.option("--login", envvar="KITSU_LOGIN", help="Kitsu login") -@click.option( +@click_wrap.option("--login", envvar="KITSU_LOGIN", help="Kitsu login") +@click_wrap.option( "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) def push_to_zou(login, password): @@ -124,11 +124,11 @@ def push_to_zou(login, password): @cli_main.command() -@click.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login") -@click.option( +@click_wrap.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login") +@click_wrap.option( "-p", "--password", envvar="KITSU_PWD", help="Password for kitsu username" ) -@click.option( +@click_wrap.option( "-prj", "--project", "projects", @@ -136,7 +136,7 @@ def push_to_zou(login, password): default=[], help="Sync specific kitsu projects", ) -@click.option( +@click_wrap.option( "-lo", "--listen-only", "listen_only", diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 8a92697920..3d6f76ad55 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -7,7 +7,6 @@ import copy import signal from collections import deque, defaultdict -import click from bson.objectid import ObjectId from openpype.client import ( @@ -15,7 +14,12 @@ from openpype.client import ( get_representations, get_representation_by_id, ) -from openpype.modules import OpenPypeModule, ITrayModule, IPluginPaths +from openpype.modules import ( + OpenPypeModule, + ITrayModule, + IPluginPaths, + click_wrap, +) from openpype.settings import ( get_project_settings, get_system_settings, @@ -2405,7 +2409,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule, IPluginPaths): return presets[project_name]['sites'][site_name]['root'] def cli(self, click_group): - click_group.add_command(cli_main) + click_group.add_command(cli_main.to_click_obj()) # Webserver module implementation def webserver_initialization(self, server_manager): @@ -2417,13 +2421,15 @@ class SyncServerModule(OpenPypeModule, ITrayModule, IPluginPaths): ) -@click.group(SyncServerModule.name, help="SyncServer module related commands.") +@click_wrap.group( + SyncServerModule.name, + help="SyncServer module related commands.") def cli_main(): pass @cli_main.command() -@click.option( +@click_wrap.option( "-a", "--active_site", required=True, From 6f5be5ab7ff369f040413638b7afaade2829a61b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Jan 2024 10:55:07 +0100 Subject: [PATCH 157/281] fix line length --- openpype/modules/example_addons/example_addon/addon.py | 4 +++- openpype/modules/job_queue/module.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index e9bcee85bb..e9de0c1bf5 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -118,7 +118,9 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): click_group.add_command(cli_main.to_click_obj()) -@click_wrap.group(ExampleAddon.name, help="Example addon dynamic cli commands.") +@click_wrap.group( + ExampleAddon.name, + help="Example addon dynamic cli commands.") def cli_main(): pass diff --git a/openpype/modules/job_queue/module.py b/openpype/modules/job_queue/module.py index c267329a61..6792cd2aca 100644 --- a/openpype/modules/job_queue/module.py +++ b/openpype/modules/job_queue/module.py @@ -236,6 +236,8 @@ def cli_start_server(port, host): ) ) @click_wrap.argument("app_name") -@click_wrap.option("--server_url", help="Server url which handle workers and jobs.") +@click_wrap.option( + "--server_url", + help="Server url which handle workers and jobs.") def cli_start_worker(app_name, server_url): JobQueueModule.start_worker(app_name, server_url) From bfdfa78e57e8c4f3f551569c109ffa5f4c5ed474 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 23 Jan 2024 10:48:20 +0000 Subject: [PATCH 158/281] Fix problem with independent window --- openpype/hosts/blender/api/capture.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/blender/api/capture.py b/openpype/hosts/blender/api/capture.py index 9922140cea..e16a865607 100644 --- a/openpype/hosts/blender/api/capture.py +++ b/openpype/hosts/blender/api/capture.py @@ -273,10 +273,9 @@ def _independent_window(): current_windows = set(bpy.context.window_manager.windows) with bpy.context.temp_override(**context): bpy.ops.wm.window_new() - window = list(set(bpy.context.window_manager.windows) - current_windows)[0] - context["window"] = window - try: - yield window - finally: - with bpy.context.temp_override(**context): + window = list(set(bpy.context.window_manager.windows) - current_windows)[0] + context["window"] = window + try: + yield window + finally: bpy.ops.wm.window_close() From a6556b4c2cb2c901b1df54460413e77ab278f888 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 23 Jan 2024 10:51:36 +0000 Subject: [PATCH 159/281] Hound fix --- openpype/hosts/blender/api/capture.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/capture.py b/openpype/hosts/blender/api/capture.py index e16a865607..e5e6041563 100644 --- a/openpype/hosts/blender/api/capture.py +++ b/openpype/hosts/blender/api/capture.py @@ -273,7 +273,8 @@ def _independent_window(): current_windows = set(bpy.context.window_manager.windows) with bpy.context.temp_override(**context): bpy.ops.wm.window_new() - window = list(set(bpy.context.window_manager.windows) - current_windows)[0] + window = list( + set(bpy.context.window_manager.windows) - current_windows)[0] context["window"] = window try: yield window From 8b9def5b9a281d6f3522af0fb5fe30fad346611c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jan 2024 11:41:26 +0000 Subject: [PATCH 160/281] Add default settings. --- server_addon/hiero/server/settings/publish_plugins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server_addon/hiero/server/settings/publish_plugins.py b/server_addon/hiero/server/settings/publish_plugins.py index 07778b8ebe..f3d1e21fe4 100644 --- a/server_addon/hiero/server/settings/publish_plugins.py +++ b/server_addon/hiero/server/settings/publish_plugins.py @@ -74,5 +74,8 @@ DEFAULT_PUBLISH_PLUGIN_SETTINGS = { "tags_addition": [ "review" ] + }, + "CollectClipEffectsModel": { + "effect_categories": [] } } From b0484e6dab9767cc1ea8ffa044ba53a9127bf037 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jan 2024 12:02:31 +0000 Subject: [PATCH 161/281] Refactor getting published workfile path --- .../plugins/publish/submit_nuke_deadline.py | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index ead22fe302..746b009255 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -135,23 +135,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, "use_published_workfile", self.use_published_workfile ) if use_published_workfile: - for item_ in context: - if "workfile" in item_.data["family"]: - template_data = item_.data.get("anatomyData") - rep = item_.data.get("representations")[0].get("name") - template_data["representation"] = rep - template_data["ext"] = rep - template_data["comment"] = None - anatomy_filled = context.data["anatomy"].format( - template_data - ) - template_filled = anatomy_filled["publish"]["path"] - script_path = os.path.normpath(template_filled) - self.log.info( - "Using published scene for render {}".format( - script_path - ) - ) + script_path = self._get_published_workfile_path(context) # only add main rendering job if target is not frames_farm r_job_response_json = None @@ -210,6 +194,44 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, families.insert(0, "prerender") instance.data["families"] = families + def _get_published_workfile_path(self, context): + """This method is temporary while the class is not inherited from + AbstractSubmitDeadline""" + for instance in context: + if ( + instance.data["family"] != "workfile" + # Disabled instances won't be integrated + or instance.data("publish") is False + ): + continue + template_data = instance.data["anatomyData"] + # Expect workfile instance has only one representation + representation = instance.data["representations"][0] + # Get workfile extension + repre_file = representation["files"] + self.log.info(repre_file) + ext = os.path.splitext(repre_file)[1].lstrip(".") + + # Fill template data + template_data["representation"] = representation["name"] + template_data["ext"] = ext + template_data["comment"] = None + + anatomy = context.data["anatomy"] + # WARNING Hardcoded template name 'publish' > may not be used + template_obj = anatomy.templates_obj["publish"]["path"] + + template_filled = template_obj.format(template_data) + script_path = os.path.normpath(template_filled) + self.log.info( + "Using published scene for render {}".format( + script_path + ) + ) + return script_path + + return None + def payload_submit( self, instance, From 1d2f97052a527fb379c57518ecae22c6bfe12ca0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jan 2024 12:04:59 +0000 Subject: [PATCH 162/281] Add class attribute --- openpype/hosts/hiero/plugins/publish/collect_clip_effects.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index 9c147e88d6..6647459e8e 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -9,6 +9,8 @@ class CollectClipEffects(pyblish.api.InstancePlugin): label = "Collect Clip Effects Instances" families = ["clip"] + effect_categories = [] + def process(self, instance): family = "effect" effects = {} From d3276c70dfa8a59a2487e7a6d2feb16c58164652 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jan 2024 12:05:05 +0000 Subject: [PATCH 163/281] Fix version --- server_addon/hiero/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/hiero/server/version.py b/server_addon/hiero/server/version.py index 3ced3581bb..b3f4756216 100644 --- a/server_addon/hiero/server/version.py +++ b/server_addon/hiero/server/version.py @@ -1 +1 @@ -__version__ = "0.2.1" +__version__ = "0.1.2" From f69c63042f73bfaae4898fbd4ada583f648de5da Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 23 Jan 2024 14:34:44 +0000 Subject: [PATCH 164/281] Update openpype/hosts/hiero/plugins/publish/collect_clip_effects.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- openpype/hosts/hiero/plugins/publish/collect_clip_effects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index 6647459e8e..d7f646ebc9 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -90,7 +90,7 @@ class CollectClipEffects(pyblish.api.InstancePlugin): # Some classes can have a number in them. Like Text2. found_cls = "" for cls in category_by_effect.keys(): - if value["class"].startswith(cls): + if cls in value["class"]: found_cls = cls effects_categorized[category_by_effect[found_cls]][key] = value From 26409d4fecee3d1dc2b3d94ee545751111aee169 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jan 2024 17:36:53 +0000 Subject: [PATCH 165/281] Fix simple instances for Tray Publisher. --- .../plugins/publish/collect_simple_instances.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index 3fa3c3b8c8..d6e35f4d75 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -216,6 +216,11 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): instance.data["thumbnailSource"] = first_filepath review_representation["tags"].append("review") + + # Adding "review" to representation name since it can clash with main + # representation if they share the same extension. + review_representation["outputName"] = "review" + self.log.debug("Representation {} was marked for review. {}".format( review_representation["name"], review_path )) From f59e403a3dbfac41eaa8b6debacc0c8f9a746484 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jan 2024 17:37:39 +0000 Subject: [PATCH 166/281] Fix edge case for ExtractOIIOTranscode --- openpype/plugins/publish/extract_color_transcode.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index faacb7af2e..052759c10b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -189,6 +189,13 @@ class ExtractOIIOTranscode(publish.Extractor): if len(new_repre["files"]) == 1: new_repre["files"] = new_repre["files"][0] + # If the source representation has "review" tag, but its not + # part of the output defintion tags, then both the + # representations will be transcoded in ExtractReview and + # their outputs will clash in integration. + if not added_review and "review" in repre.get("tags", []): + added_review = True + new_representations.append(new_repre) added_representations = True From 741eab7140c627b19d71719e4b03424d5481209f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 23 Jan 2024 17:37:53 +0000 Subject: [PATCH 167/281] Fix thumbnail extraction --- openpype/plugins/publish/extract_thumbnail.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 2b4ea0529a..10eb261482 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -231,7 +231,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "files": jpeg_file, "stagingDir": dst_staging, "thumbnail": True, - "tags": new_repre_tags + "tags": new_repre_tags, + # If source image is jpg then there can be clash when + # integrating to making the output name explicit. + "outputName": "thumbnail" } # adding representation From 97c465c20f57aff7a87e3604ed30849a6feec09d Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 24 Jan 2024 03:25:59 +0000 Subject: [PATCH 168/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 5bb4b4fb0a..674ea2cb8a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.5-nightly.2" +__version__ = "3.18.5-nightly.3" From c8e00d4aa4cfc81d967c8d607bd409af9659b068 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 24 Jan 2024 03:26:36 +0000 Subject: [PATCH 169/281] 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 e831bf3dc1..1816ffe515 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.18.5-nightly.3 - 3.18.5-nightly.2 - 3.18.5-nightly.1 - 3.18.4 @@ -134,7 +135,6 @@ body: - 3.15.8-nightly.2 - 3.15.8-nightly.1 - 3.15.7 - - 3.15.7-nightly.3 validations: required: true - type: dropdown From 24cf858a29f742bf552df7d353052e288e13fee3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 24 Jan 2024 07:50:38 +0000 Subject: [PATCH 170/281] Remove duplicate plugin --- .../publish/validate_look_members_unique.py | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/validate_look_members_unique.py diff --git a/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py b/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py deleted file mode 100644 index 4e01b55249..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py +++ /dev/null @@ -1,77 +0,0 @@ -from collections import defaultdict - -import pyblish.api - -import openpype.hosts.maya.api.action -from openpype.pipeline.publish import ( - PublishValidationError, ValidatePipelineOrder) - - -class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): - """Validate the relational nodes of the look data to ensure every node is - unique. - - This ensures the all member ids are unique. Every node id must be from - a single node in the scene. - - That means there's only ever one of a specific node inside the look to be - published. For example if you'd have a loaded 3x the same tree and by - accident you're trying to publish them all together in a single look that - would be invalid, because they are the same tree. It should be included - inside the look instance only once. - - """ - - order = ValidatePipelineOrder - label = 'Look members unique' - hosts = ['maya'] - families = ['look'] - - actions = [openpype.hosts.maya.api.action.SelectInvalidAction, - openpype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError( - ("Members found without non-unique IDs: " - "{0}").format(invalid)) - - @staticmethod - def get_invalid(instance): - """ - Check all the relationship members of the objectSets - - Example of the lookData relationships: - {"uuid": 59b2bb27bda2cb2776206dd8:79ab0a63ffdf, - "members":[{"uuid": 59b2bb27bda2cb2776206dd8:1b158cc7496e, - "name": |model_GRP|body_GES|body_GESShape} - ..., - ...]} - - Args: - instance: - - Returns: - - """ - - # Get all members from the sets - id_nodes = defaultdict(set) - relationships = instance.data["lookData"]["relationships"] - - for relationship in relationships.values(): - for member in relationship['members']: - node_id = member["uuid"] - node = member["name"] - id_nodes[node_id].add(node) - - # Check if any id has more than 1 node - invalid = [] - for nodes in id_nodes.values(): - if len(nodes) > 1: - invalid.extend(nodes) - - return invalid From 560698bcd8e600b5ab28ce0d7a2fd6e2fabae15b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 24 Jan 2024 12:19:02 +0000 Subject: [PATCH 171/281] Missing product_names to subsets conversion. --- openpype/settings/ayon_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 36f96c9dc7..8c6524f423 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1236,6 +1236,8 @@ def _convert_global_project_settings(ayon_settings, output, default_settings): for profile in extract_oiio_transcode_profiles: new_outputs = {} name_counter = {} + if "product_names" in profile: + profile["subsets"] = profile.pop("product_names") for profile_output in profile["outputs"]: if "name" in profile_output: name = profile_output.pop("name") From 277fcd82fb474437d14c8147f4e628b145899b6e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 24 Jan 2024 15:52:35 +0100 Subject: [PATCH 172/281] Extended error message --- .../maya/plugins/publish/validate_scene_set_workspace.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py index b48d67e416..ad89b7c791 100644 --- a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -44,4 +44,7 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): if not is_subdir(scene_name, root_dir): raise PublishValidationError( - "Maya workspace is not set correctly.") + "Maya workspace is not set correctly.\n\n" + f"`{os.path.dirname(scene_name)}` is not in `{root_dir}`.\n\n" + "Please use Workfile app to re-save." + ) From 8ca681109874760d04edc11b3c314b8b736430a0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Jan 2024 16:13:42 +0800 Subject: [PATCH 173/281] bugfix the save scene for camera error out when multi camera option turned off --- .../hosts/max/plugins/publish/save_scenes_for_cameras.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py index c39109417b..f089bf663c 100644 --- a/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py +++ b/openpype/hosts/max/plugins/publish/save_scenes_for_cameras.py @@ -21,6 +21,11 @@ class SaveScenesForCamera(pyblish.api.InstancePlugin): families = ["maxrender"] def process(self, instance): + if not instance.data.get("multiCamera"): + self.log.debug( + "Multi Camera disabled. " + "Skipping to save scene files for cameras") + return current_folder = rt.maxFilePath current_filename = rt.maxFileName current_filepath = os.path.join(current_folder, current_filename) From cb9802e3fe63fa7542b463b0c613bf8480260492 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 25 Jan 2024 11:04:56 +0000 Subject: [PATCH 174/281] Update openpype/plugins/publish/extract_color_transcode.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 052759c10b..922df469fe 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -193,7 +193,7 @@ class ExtractOIIOTranscode(publish.Extractor): # part of the output defintion tags, then both the # representations will be transcoded in ExtractReview and # their outputs will clash in integration. - if not added_review and "review" in repre.get("tags", []): + if "review" in repre.get("tags", []): added_review = True new_representations.append(new_repre) From ac383a5de66e79aec84fb19a2a71ecd55b2ef224 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 25 Jan 2024 11:33:29 +0000 Subject: [PATCH 175/281] Fix missing animation data when updating blend assets --- openpype/hosts/blender/plugins/load/load_blend.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index 4abe4a6afb..1a84f5afbb 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -220,6 +220,8 @@ class BlendLoader(plugin.AssetLoader): # Restore the actions for obj in asset_group.children_recursive: if obj.name in actions: + if not obj.animation_data: + obj.animation_data_create() obj.animation_data.action = actions[obj.name] # Restore the old data, but reset memebers, as they don't exist anymore From 77a12f49f8ad91b2771908b1dcfb9f7f4721fafd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Jan 2024 12:34:35 +0100 Subject: [PATCH 176/281] fix project name duplication in project folders --- openpype/pipeline/project_folders.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py index 1bcba5c320..ecdb5cf6d3 100644 --- a/openpype/pipeline/project_folders.py +++ b/openpype/pipeline/project_folders.py @@ -28,13 +28,14 @@ def concatenate_splitted_paths(split_paths, anatomy): # backward compatibility if "__project_root__" in path_items: for root, root_path in anatomy.roots.items(): - if not os.path.exists(str(root_path)): - log.debug("Root {} path path {} not exist on \ - computer!".format(root, root_path)) + if not root_path: continue - clean_items = ["{{root[{}]}}".format(root), - r"{project[name]}"] + clean_items[1:] - output.append(os.path.normpath(os.path.sep.join(clean_items))) + root_items = [ + "{{root[{}]}}".format(root), + "{project[name]}" + ] + root_items.extend(clean_items[1:]) + output.append(os.path.normpath(os.path.sep.join(root_items))) continue output.append(os.path.normpath(os.path.sep.join(clean_items))) From 8f6d78e4eb9985efa689a7c7a1548bcde5231a14 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 25 Jan 2024 12:41:15 +0100 Subject: [PATCH 177/281] skip path exists --- openpype/pipeline/project_folders.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py index ecdb5cf6d3..608344ce03 100644 --- a/openpype/pipeline/project_folders.py +++ b/openpype/pipeline/project_folders.py @@ -28,8 +28,14 @@ def concatenate_splitted_paths(split_paths, anatomy): # backward compatibility if "__project_root__" in path_items: for root, root_path in anatomy.roots.items(): - if not root_path: + if not root_path or not os.path.exists(str(root_path)): + log.debug( + "Root {} path path {} not exist on computer!".format( + root, root_path + ) + ) continue + root_items = [ "{{root[{}]}}".format(root), "{project[name]}" From de2d70a8a34e03c27c05a085fdfee408e19a0d79 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 25 Jan 2024 13:30:16 +0100 Subject: [PATCH 178/281] Added settings for Fusion creators to legacy OP (#6162) --- .../defaults/project_settings/fusion.json | 6 ++++-- .../projects_schema/schema_project_fusion.json | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index 15b6bfc09b..f890f94b6f 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -31,7 +31,8 @@ "reviewable", "farm_rendering" ], - "image_format": "exr" + "image_format": "exr", + "default_frame_range_option": "asset_db" }, "CreateImageSaver": { "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{ext}", @@ -43,7 +44,8 @@ "reviewable", "farm_rendering" ], - "image_format": "exr" + "image_format": "exr", + "default_frame": 0 } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 8669842087..84d1efae78 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -116,6 +116,17 @@ {"tif": "tif"}, {"jpg": "jpg"} ] + }, + { + "key": "default_frame_range_option", + "label": "Default frame range source", + "type": "enum", + "multiselect": false, + "enum_items": [ + {"asset_db": "Current asset context"}, + {"render_range": "From render in/out"}, + {"comp_range": "From composition timeline"} + ] } ] }, @@ -165,6 +176,11 @@ {"tif": "tif"}, {"jpg": "jpg"} ] + }, + { + "type": "number", + "key": "default_frame", + "label": "Default rendered frame" } ] } From b783fae8e0262a9c0edc689ad84162d180b8e574 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 25 Jan 2024 13:31:12 +0100 Subject: [PATCH 179/281] Update openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py Co-authored-by: Toke Jepsen --- .../hosts/maya/plugins/publish/validate_scene_set_workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py index ad89b7c791..0c780d3a69 100644 --- a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -45,6 +45,6 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): if not is_subdir(scene_name, root_dir): raise PublishValidationError( "Maya workspace is not set correctly.\n\n" - f"`{os.path.dirname(scene_name)}` is not in `{root_dir}`.\n\n" + f"Current workfile `{scene_name}` is not inside the current Maya project root directory `{root_dir}`.\n\n" "Please use Workfile app to re-save." ) From a0faa3f130c7a806cec1d045dceceb7ee09ea3e2 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 25 Jan 2024 12:34:53 +0000 Subject: [PATCH 180/281] Update openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py --- .../hosts/maya/plugins/publish/validate_scene_set_workspace.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py index 0c780d3a69..ddcbab8931 100644 --- a/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -45,6 +45,7 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): if not is_subdir(scene_name, root_dir): raise PublishValidationError( "Maya workspace is not set correctly.\n\n" - f"Current workfile `{scene_name}` is not inside the current Maya project root directory `{root_dir}`.\n\n" + f"Current workfile `{scene_name}` is not inside the " + "current Maya project root directory `{root_dir}`.\n\n" "Please use Workfile app to re-save." ) From 20e089c30ea8834553c8d7888542ca0101787ba7 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 25 Jan 2024 14:04:28 +0000 Subject: [PATCH 181/281] [Automated] Release --- CHANGELOG.md | 215 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 217 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 546b2c12ea..14f0bc469f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,221 @@ # Changelog +## [3.18.5](https://github.com/ynput/OpenPype/tree/3.18.5) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.4...3.18.5) + +### **🚀 Enhancements** + + +
+Chore: Add addons dir only if exists #6140 + +Do not add addons directory path for addons discovery if does not exists. + + +___ + +
+ + +
+Hiero: Effect Categories - OP-7397 #6143 + +This PR introduces `Effect Categories` for the Hiero settings. This allows studios to split effect stacks into meaningful subsets. + + +___ + +
+ + +
+Nuke: Render Workfile Attributes #6146 + +`Workfile Dependency` default value can now be controlled from project settings.`Use Published Workfile` makes using published workfiles for rendering optional. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Attributes are locked after publishing if they are locked in Camera Family #6073 + +This PR is to make sure unlock attributes only during the bake context, make sure attributes are relocked after to preserve the lock state of the original node being baked. + + +___ + +
+ + +
+Missing nuke family Windows arguments #6131 + +Default Windows arguments for launching the Nuke family was missing. + + +___ + +
+ + +
+AYON: Fix the bug on the limit group not being set correctly in Maya Deadline Setting #6139 + +This PR is to bug-fix the limit groups from maya deadline settings errored out when the user tries to edit the setting. + + +___ + +
+ + +
+Chore: Transcoding extensions add missing '.tif' extension #6142 + +Image extensions in transcoding helper was missing `.tif` extension and had `.tiff` twice. + + +___ + +
+ + +
+Blender: Use the new API for override context #6145 + +Blender 4.0 disabled the old API to override context. This API updates the code to use the new API. + + +___ + +
+ + +
+BugFix: Include Model in FBX Loader in Houdini #6150 + +A quick bugfig where we can't load fbx exported from blender. The bug was reported here. + + +___ + +
+ + +
+Blender: Restore actions to objects after update #6153 + +Restore the actions assigned to objects after updating assets from blend files. + + +___ + +
+ + +
+Chore: Collect template data with hierarchy context #6154 + +Fixed queue loop where is used wrong variable to pop items from queue. + + +___ + +
+ + +
+OP-6382 - Thumbnail Integration Problem #6156 + +This ticket alerted to 3 different cases of integration issues; +- [x] Using the Tray Publisher with the same image format (extension) for representation and review representation. +- [x] Clash on publish file path from output definitions in `ExtractOIIOTranscode`. +- [x] Clash on publish file from thumbnail in `ExtractThumbnail`There might be an issue with this fix, if a studio does not use the `{output}` token in their `render` anatomy template. But thinking if they have customized it, they will be responsible to maintain these edge cases. + + +___ + +
+ + +
+Max: Bugfix saving camera scene errored out when creating render instance with multi-camera option turned off #6163 + +This PR is to make sure the integrator of saving camera scene turned off and the render submitted successfully when multi-camera options being turned off in 3dsmax + + +___ + +
+ + +
+Chore: Fix duplicated project name on create project structure #6166 + +Small fix in project folders. It is not used same variable name to change values which breaks values on any next loop. + + +___ + +
+ +### **Merged pull requests** + + +
+Maya: Remove duplicate plugin #6157 + +The two plugins below are doing the same work, so we can remove the one focused solely on lookdev.https://github.com/ynput/OpenPype/blob/develop/openpype/hosts/maya/plugins/publish/validate_look_members_unique.pyhttps://github.com/ynput/OpenPype/blob/develop/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py + + +___ + +
+ + +
+Publish report viewer: Report items sorting #6092 + +Proposal of items sorting in Publish report viewer tool. Items are sorted by report creation time. Creation time is also added to publish report data when saved from publisher tool. + + +___ + +
+ + +
+Maya: Extended error message #6161 + +Added more details to message + + +___ + +
+ + +
+Fusion: Added settings for Fusion creators to legacy OP #6162 + +Added missing OP variant of setting for new Fusion creator. + + +___ + +
+ + + + ## [3.18.4](https://github.com/ynput/OpenPype/tree/3.18.4) diff --git a/openpype/version.py b/openpype/version.py index 674ea2cb8a..ddfb3ebeeb 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.5-nightly.3" +__version__ = "3.18.5" diff --git a/pyproject.toml b/pyproject.toml index f9d5381a15..24172aa77f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.4" # OpenPype +version = "3.18.5" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 6c83642af6bf7238ae686a1fa22aa95187b42192 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 25 Jan 2024 14:05:21 +0000 Subject: [PATCH 182/281] 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 1816ffe515..fd6999604a 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.18.5 - 3.18.5-nightly.3 - 3.18.5-nightly.2 - 3.18.5-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.8-nightly.3 - 3.15.8-nightly.2 - 3.15.8-nightly.1 - - 3.15.7 validations: required: true - type: dropdown From 94702cc2cda1177cd8973e64fbd62e4635a442fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 10:56:09 +0100 Subject: [PATCH 183/281] reset loader window on reopen --- openpype/tools/ayon_loader/ui/window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/ayon_loader/ui/window.py b/openpype/tools/ayon_loader/ui/window.py index a6d40d52e7..d0455c901d 100644 --- a/openpype/tools/ayon_loader/ui/window.py +++ b/openpype/tools/ayon_loader/ui/window.py @@ -322,6 +322,7 @@ class LoaderWindow(QtWidgets.QWidget): ) def refresh(self): + self._reset_on_show = False self._controller.reset() def showEvent(self, event): @@ -332,6 +333,10 @@ class LoaderWindow(QtWidgets.QWidget): self._show_timer.start() + def closeEvent(self, event): + super(LoaderWindow, self).closeEvent(event) + self._reset_on_show = True + def keyPressEvent(self, event): modifiers = event.modifiers() ctrl_pressed = QtCore.Qt.ControlModifier & modifiers @@ -378,8 +383,7 @@ class LoaderWindow(QtWidgets.QWidget): self._show_timer.stop() if self._reset_on_show: - self._reset_on_show = False - self._controller.reset() + self.refresh() def _show_group_dialog(self): project_name = self._projects_combobox.get_selected_project_name() From d2ee1b91f55449f43b0783f7b6ab59532352606c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 11:15:08 +0100 Subject: [PATCH 184/281] deselect project on close --- openpype/tools/ayon_loader/ui/window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/ayon_loader/ui/window.py b/openpype/tools/ayon_loader/ui/window.py index d0455c901d..8982d92c0f 100644 --- a/openpype/tools/ayon_loader/ui/window.py +++ b/openpype/tools/ayon_loader/ui/window.py @@ -335,6 +335,9 @@ class LoaderWindow(QtWidgets.QWidget): def closeEvent(self, event): super(LoaderWindow, self).closeEvent(event) + # Deselect project so current context will be selected + # on next 'showEvent' + self._controller.set_selected_project(None) self._reset_on_show = True def keyPressEvent(self, event): From d387dca0272c9be2a80beb52ac8272cabb10f426 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 26 Jan 2024 10:44:13 +0000 Subject: [PATCH 185/281] Use duration from streams as its more precise --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb261482..37f7ea7737 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,7 +445,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) - duration = float(video_data["format"]["duration"]) + duration = float(video_data["streams"][0]["duration"]) cmd_args = [ "-y", From d53d8410e74e69007a91c1a1d9cda657b4b0ad72 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 12:55:14 +0100 Subject: [PATCH 186/281] removed kitsu ayon settings --- server_addon/kitsu/server/__init__.py | 19 ----- server_addon/kitsu/server/settings.py | 112 -------------------------- server_addon/kitsu/server/version.py | 1 - 3 files changed, 132 deletions(-) delete mode 100644 server_addon/kitsu/server/__init__.py delete mode 100644 server_addon/kitsu/server/settings.py delete mode 100644 server_addon/kitsu/server/version.py diff --git a/server_addon/kitsu/server/__init__.py b/server_addon/kitsu/server/__init__.py deleted file mode 100644 index 69cf812dea..0000000000 --- a/server_addon/kitsu/server/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Type - -from ayon_server.addons import BaseServerAddon - -from .version import __version__ -from .settings import KitsuSettings, DEFAULT_VALUES - - -class KitsuAddon(BaseServerAddon): - name = "kitsu" - title = "Kitsu" - version = __version__ - settings_model: Type[KitsuSettings] = KitsuSettings - frontend_scopes = {} - services = {} - - async def get_default_settings(self): - settings_model_cls = self.get_settings_model() - return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/kitsu/server/settings.py b/server_addon/kitsu/server/settings.py deleted file mode 100644 index a4d10d889d..0000000000 --- a/server_addon/kitsu/server/settings.py +++ /dev/null @@ -1,112 +0,0 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel - - -class EntityPattern(BaseSettingsModel): - episode: str = Field(title="Episode") - sequence: str = Field(title="Sequence") - shot: str = Field(title="Shot") - - -def _status_change_cond_enum(): - return [ - {"value": "equal", "label": "Equal"}, - {"value": "not_equal", "label": "Not equal"} - ] - - -class StatusChangeCondition(BaseSettingsModel): - condition: str = Field( - "equal", - enum_resolver=_status_change_cond_enum, - title="Condition" - ) - short_name: str = Field("", title="Short name") - - -class StatusChangeProductTypeRequirementModel(BaseSettingsModel): - condition: str = Field( - "equal", - enum_resolver=_status_change_cond_enum, - title="Condition" - ) - product_type: str = Field("", title="Product type") - - -class StatusChangeConditionsModel(BaseSettingsModel): - status_conditions: list[StatusChangeCondition] = Field( - default_factory=list, - title="Status conditions" - ) - product_type_requirements: list[StatusChangeProductTypeRequirementModel] = Field( - default_factory=list, - title="Product type requirements") - - -class CustomCommentTemplateModel(BaseSettingsModel): - enabled: bool = Field(True) - comment_template: str = Field("", title="Custom comment") - - -class IntegrateKitsuNotes(BaseSettingsModel): - """Kitsu supports markdown and here you can create a custom comment template. - - You can use data from your publishing instance's data. - """ - - set_status_note: bool = Field(title="Set status on note") - note_status_shortname: str = Field(title="Note shortname") - status_change_conditions: StatusChangeConditionsModel = Field( - default_factory=StatusChangeConditionsModel, - title="Status change conditions" - ) - custom_comment_template: CustomCommentTemplateModel = Field( - default_factory=CustomCommentTemplateModel, - title="Custom Comment Template", - ) - - -class PublishPlugins(BaseSettingsModel): - IntegrateKitsuNote: IntegrateKitsuNotes = Field( - default_factory=IntegrateKitsuNotes, - title="Integrate Kitsu Note" - ) - - -class KitsuSettings(BaseSettingsModel): - server: str = Field( - "", - title="Kitsu Server", - scope=["studio"], - ) - entities_naming_pattern: EntityPattern = Field( - default_factory=EntityPattern, - title="Entities naming pattern", - ) - publish: PublishPlugins = Field( - default_factory=PublishPlugins, - title="Publish plugins", - ) - - -DEFAULT_VALUES = { - "entities_naming_pattern": { - "episode": "E##", - "sequence": "SQ##", - "shot": "SH##" - }, - "publish": { - "IntegrateKitsuNote": { - "set_status_note": False, - "note_status_shortname": "wfa", - "status_change_conditions": { - "status_conditions": [], - "product_type_requirements": [] - }, - "custom_comment_template": { - "enabled": False, - "comment_template": "{comment}\n\n| | |\n|--|--|\n| version| `{version}` |\n| product type | `{product[type]}` |\n| name | `{name}` |" - } - } - } -} diff --git a/server_addon/kitsu/server/version.py b/server_addon/kitsu/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/kitsu/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" From 3ea4c29d0fa17b6a08c1f057e65c0c0df60cfd9e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 12:59:18 +0100 Subject: [PATCH 187/281] use 'SettingsField' from ayon server instead of 'Field' from pydantic This is preparation for new version of pydantic which will require to customize the field for AYON purposes and raw pydantic Field could not be used. --- .../server/settings/creator_plugins.py | 10 +- .../aftereffects/server/settings/imageio.py | 26 +- .../aftereffects/server/settings/main.py | 13 +- .../server/settings/publish_plugins.py | 28 +- .../settings/templated_workfile_build.py | 14 +- .../server/settings/workfile_builder.py | 16 +- server_addon/applications/server/settings.py | 120 ++--- .../blender/server/settings/imageio.py | 26 +- server_addon/blender/server/settings/main.py | 22 +- .../server/settings/publish_plugins.py | 72 +-- .../server/settings/render_settings.py | 20 +- server_addon/celaction/server/imageio.py | 26 +- server_addon/celaction/server/settings.py | 19 +- server_addon/clockify/server/settings.py | 5 +- server_addon/core/server/settings/main.py | 69 +-- .../core/server/settings/publish_plugins.py | 331 ++++++------- server_addon/core/server/settings/tools.py | 156 ++++--- server_addon/deadline/server/settings/main.py | 18 +- .../server/settings/publish_plugins.py | 270 ++++++----- .../flame/server/settings/create_plugins.py | 43 +- server_addon/flame/server/settings/imageio.py | 54 ++- .../flame/server/settings/loader_plugins.py | 36 +- server_addon/flame/server/settings/main.py | 10 +- .../flame/server/settings/publish_plugins.py | 58 +-- server_addon/fusion/server/imageio.py | 26 +- server_addon/fusion/server/settings.py | 36 +- .../harmony/server/settings/imageio.py | 30 +- server_addon/harmony/server/settings/main.py | 7 +- .../server/settings/publish_plugins.py | 32 +- server_addon/hiero/server/settings/common.py | 39 +- .../hiero/server/settings/create_plugins.py | 33 +- server_addon/hiero/server/settings/filters.py | 16 +- server_addon/hiero/server/settings/imageio.py | 53 +-- .../hiero/server/settings/loader_plugins.py | 11 +- server_addon/hiero/server/settings/main.py | 16 +- .../hiero/server/settings/publish_plugins.py | 25 +- .../hiero/server/settings/scriptsmenu.py | 17 +- .../houdini/server/settings/create.py | 57 ++- .../houdini/server/settings/general.py | 17 +- .../houdini/server/settings/imageio.py | 26 +- server_addon/houdini/server/settings/main.py | 13 +- .../houdini/server/settings/publish.py | 41 +- .../houdini/server/settings/shelves.py | 26 +- .../server/settings/create_review_settings.py | 22 +- server_addon/max/server/settings/imageio.py | 26 +- server_addon/max/server/settings/main.py | 25 +- .../max/server/settings/publishers.py | 40 +- .../max/server/settings/render_settings.py | 12 +- server_addon/maya/server/settings/creators.py | 169 +++---- .../settings/explicit_plugins_loading.py | 12 +- server_addon/maya/server/settings/imageio.py | 56 +-- .../maya/server/settings/include_handles.py | 16 +- server_addon/maya/server/settings/loaders.py | 52 +-- server_addon/maya/server/settings/main.py | 40 +- .../maya/server/settings/maya_dirmap.py | 14 +- .../maya/server/settings/publish_playblast.py | 235 +++++----- .../maya/server/settings/publishers.py | 436 ++++++++++-------- .../maya/server/settings/render_settings.py | 82 ++-- .../maya/server/settings/scriptsmenu.py | 20 +- .../settings/templated_workfile_settings.py | 15 +- .../settings/workfile_build_settings.py | 25 +- server_addon/muster/server/settings.py | 11 +- server_addon/nuke/server/settings/common.py | 51 +- .../nuke/server/settings/create_plugins.py | 41 +- server_addon/nuke/server/settings/dirmap.py | 11 +- server_addon/nuke/server/settings/general.py | 15 +- server_addon/nuke/server/settings/gizmo.py | 24 +- server_addon/nuke/server/settings/imageio.py | 65 +-- .../nuke/server/settings/loader_plugins.py | 25 +- server_addon/nuke/server/settings/main.py | 23 +- .../nuke/server/settings/publish_plugins.py | 127 ++--- .../nuke/server/settings/scriptsmenu.py | 17 +- .../settings/templated_workfile_build.py | 14 +- .../nuke/server/settings/workfile_builder.py | 30 +- .../server/settings/creator_plugins.py | 40 +- .../photoshop/server/settings/imageio.py | 34 +- .../photoshop/server/settings/main.py | 11 +- .../server/settings/publish_plugins.py | 58 ++- .../server/settings/workfile_builder.py | 16 +- server_addon/resolve/server/imageio.py | 34 +- server_addon/resolve/server/settings.py | 39 +- server_addon/royal_render/server/settings.py | 23 +- .../server/settings/imageio.py | 26 +- .../substancepainter/server/settings/main.py | 11 +- .../timers_manager/server/settings.py | 11 +- .../server/settings/creator_plugins.py | 12 +- .../server/settings/editorial_creators.py | 50 +- .../traypublisher/server/settings/imageio.py | 26 +- .../traypublisher/server/settings/main.py | 13 +- .../server/settings/publish_plugins.py | 14 +- .../server/settings/simple_creators.py | 26 +- .../tvpaint/server/settings/create_plugins.py | 65 ++- .../tvpaint/server/settings/filters.py | 10 +- .../tvpaint/server/settings/imageio.py | 26 +- server_addon/tvpaint/server/settings/main.py | 14 +- .../server/settings/publish_plugins.py | 40 +- .../server/settings/workfile_builder.py | 11 +- server_addon/unreal/server/imageio.py | 26 +- server_addon/unreal/server/settings.py | 19 +- 99 files changed, 2235 insertions(+), 2154 deletions(-) diff --git a/server_addon/aftereffects/server/settings/creator_plugins.py b/server_addon/aftereffects/server/settings/creator_plugins.py index 9cb03b0b26..988a036589 100644 --- a/server_addon/aftereffects/server/settings/creator_plugins.py +++ b/server_addon/aftereffects/server/settings/creator_plugins.py @@ -1,18 +1,16 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class CreateRenderPlugin(BaseSettingsModel): - mark_for_review: bool = Field(True, title="Review") - default_variants: list[str] = Field( + mark_for_review: bool = SettingsField(True, title="Review") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Variants" ) class AfterEffectsCreatorPlugins(BaseSettingsModel): - RenderCreator: CreateRenderPlugin = Field( + RenderCreator: CreateRenderPlugin = SettingsField( title="Create Render", default_factory=CreateRenderPlugin, ) diff --git a/server_addon/aftereffects/server/settings/imageio.py b/server_addon/aftereffects/server/settings/imageio.py index 55160ffd11..4657425c81 100644 --- a/server_addon/aftereffects/server/settings/imageio.py +++ b/server_addon/aftereffects/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class AfterEffectsImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/aftereffects/server/settings/main.py b/server_addon/aftereffects/server/settings/main.py index 4edc46d259..f30f4dc258 100644 --- a/server_addon/aftereffects/server/settings/main.py +++ b/server_addon/aftereffects/server/settings/main.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import AfterEffectsImageIOModel from .creator_plugins import AfterEffectsCreatorPlugins @@ -14,23 +13,23 @@ from .templated_workfile_build import TemplatedWorkfileBuildModel class AfterEffectsSettings(BaseSettingsModel): """AfterEffects Project Settings.""" - imageio: AfterEffectsImageIOModel = Field( + imageio: AfterEffectsImageIOModel = SettingsField( default_factory=AfterEffectsImageIOModel, title="OCIO config" ) - create: AfterEffectsCreatorPlugins = Field( + create: AfterEffectsCreatorPlugins = SettingsField( default_factory=AfterEffectsCreatorPlugins, title="Creator plugins" ) - publish: AfterEffectsPublishPlugins = Field( + publish: AfterEffectsPublishPlugins = SettingsField( default_factory=AfterEffectsPublishPlugins, title="Publish plugins" ) - workfile_builder: WorkfileBuilderPlugin = Field( + workfile_builder: WorkfileBuilderPlugin = SettingsField( default_factory=WorkfileBuilderPlugin, title="Workfile Builder" ) - templated_workfile_build: TemplatedWorkfileBuildModel = Field( + templated_workfile_build: TemplatedWorkfileBuildModel = SettingsField( default_factory=TemplatedWorkfileBuildModel, title="Templated Workfile Build Settings" ) diff --git a/server_addon/aftereffects/server/settings/publish_plugins.py b/server_addon/aftereffects/server/settings/publish_plugins.py index 78445d3223..61d67f26d3 100644 --- a/server_addon/aftereffects/server/settings/publish_plugins.py +++ b/server_addon/aftereffects/server/settings/publish_plugins.py @@ -1,45 +1,43 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class CollectReviewPluginModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") + enabled: bool = SettingsField(True, title="Enabled") class ValidateSceneSettingsModel(BaseSettingsModel): """Validate naming of products and layers""" # _isGroup = True - enabled: bool = Field(True, title="Enabled") - optional: bool = Field(False, title="Optional") - active: bool = Field(True, title="Active") - skip_resolution_check: list[str] = Field( + enabled: bool = SettingsField(True, title="Enabled") + optional: bool = SettingsField(False, title="Optional") + active: bool = SettingsField(True, title="Active") + skip_resolution_check: list[str] = SettingsField( default_factory=list, title="Skip Resolution Check for Tasks", ) - skip_timelines_check: list[str] = Field( + skip_timelines_check: list[str] = SettingsField( default_factory=list, title="Skip Timeline Check for Tasks", ) class ValidateContainersModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - optional: bool = Field(True, title="Optional") - active: bool = Field(True, title="Active") + enabled: bool = SettingsField(True, title="Enabled") + optional: bool = SettingsField(True, title="Optional") + active: bool = SettingsField(True, title="Active") class AfterEffectsPublishPlugins(BaseSettingsModel): - CollectReview: CollectReviewPluginModel = Field( + CollectReview: CollectReviewPluginModel = SettingsField( default_factory=CollectReviewPluginModel, title="Collect Review", ) - ValidateSceneSettings: ValidateSceneSettingsModel = Field( + ValidateSceneSettings: ValidateSceneSettingsModel = SettingsField( default_factory=ValidateSceneSettingsModel, title="Validate Scene Settings", ) - ValidateContainers: ValidateContainersModel = Field( + ValidateContainers: ValidateContainersModel = SettingsField( default_factory=ValidateContainersModel, title="Validate Containers", ) diff --git a/server_addon/aftereffects/server/settings/templated_workfile_build.py b/server_addon/aftereffects/server/settings/templated_workfile_build.py index e0245c8d06..5b71c2addc 100644 --- a/server_addon/aftereffects/server/settings/templated_workfile_build.py +++ b/server_addon/aftereffects/server/settings/templated_workfile_build.py @@ -1,33 +1,33 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, task_types_enum, + SettingsField, ) class TemplatedWorkfileProfileModel(BaseSettingsModel): - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field( + task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - path: str = Field( + path: str = SettingsField( title="Path to template" ) - keep_placeholder: bool = Field( + keep_placeholder: bool = SettingsField( False, title="Keep placeholders") - create_first_version: bool = Field( + create_first_version: bool = SettingsField( True, title="Create first version" ) class TemplatedWorkfileBuildModel(BaseSettingsModel): - profiles: list[TemplatedWorkfileProfileModel] = Field( + profiles: list[TemplatedWorkfileProfileModel] = SettingsField( default_factory=list ) diff --git a/server_addon/aftereffects/server/settings/workfile_builder.py b/server_addon/aftereffects/server/settings/workfile_builder.py index d45d3f7f24..65f5ddd893 100644 --- a/server_addon/aftereffects/server/settings/workfile_builder.py +++ b/server_addon/aftereffects/server/settings/workfile_builder.py @@ -1,25 +1,27 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel, MultiplatformPathModel +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + MultiplatformPathModel, +) class CustomBuilderTemplate(BaseSettingsModel): - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", ) - template_path: MultiplatformPathModel = Field( + template_path: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel ) class WorkfileBuilderPlugin(BaseSettingsModel): _title = "Workfile Builder" - create_first_version: bool = Field( + create_first_version: bool = SettingsField( False, title="Create first workfile" ) - custom_templates: list[CustomBuilderTemplate] = Field( + custom_templates: list[CustomBuilderTemplate] = SettingsField( default_factory=list ) diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py index 70c8b57c6a..710bbbf9ee 100644 --- a/server_addon/applications/server/settings.py +++ b/server_addon/applications/server/settings.py @@ -1,7 +1,11 @@ import json -from pydantic import Field, validator +from pydantic import validator -from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + ensure_unique_names, +) from ayon_server.exceptions import BadRequestException @@ -23,21 +27,21 @@ def validate_json_dict(value): class MultiplatformStrList(BaseSettingsModel): - windows: list[str] = Field(default_factory=list, title="Windows") - linux: list[str] = Field(default_factory=list, title="Linux") - darwin: list[str] = Field(default_factory=list, title="MacOS") + windows: list[str] = SettingsField(default_factory=list, title="Windows") + linux: list[str] = SettingsField(default_factory=list, title="Linux") + darwin: list[str] = SettingsField(default_factory=list, title="MacOS") class AppVariant(BaseSettingsModel): - name: str = Field("", title="Name") - label: str = Field("", title="Label") - executables: MultiplatformStrList = Field( + name: str = SettingsField("", title="Name") + label: str = SettingsField("", title="Label") + executables: MultiplatformStrList = SettingsField( default_factory=MultiplatformStrList, title="Executables" ) - arguments: MultiplatformStrList = Field( + arguments: MultiplatformStrList = SettingsField( default_factory=MultiplatformStrList, title="Arguments" ) - environment: str = Field("{}", title="Environment", widget="textarea") + environment: str = SettingsField("{}", title="Environment", widget="textarea") @validator("environment") def validate_json(cls, value): @@ -45,17 +49,17 @@ class AppVariant(BaseSettingsModel): class AppVariantWithPython(AppVariant): - use_python_2: bool = Field(False, title="Use Python 2") + use_python_2: bool = SettingsField(False, title="Use Python 2") class AppGroup(BaseSettingsModel): - enabled: bool = Field(True) - label: str = Field("", title="Label") - host_name: str = Field("", title="Host name") - icon: str = Field("", title="Icon") - environment: str = Field("{}", title="Environment", widget="textarea") + enabled: bool = SettingsField(True) + label: str = SettingsField("", title="Label") + host_name: str = SettingsField("", title="Host name") + icon: str = SettingsField("", title="Icon") + environment: str = SettingsField("{}", title="Environment", widget="textarea") - variants: list[AppVariant] = Field( + variants: list[AppVariant] = SettingsField( default_factory=list, title="Variants", description="Different variants of the applications", @@ -69,7 +73,7 @@ class AppGroup(BaseSettingsModel): class AppGroupWithPython(AppGroup): - variants: list[AppVariantWithPython] = Field( + variants: list[AppVariantWithPython] = SettingsField( default_factory=list, title="Variants", description="Different variants of the applications", @@ -78,14 +82,14 @@ class AppGroupWithPython(AppGroup): class AdditionalAppGroup(BaseSettingsModel): - enabled: bool = Field(True) - name: str = Field("", title="Name") - label: str = Field("", title="Label") - host_name: str = Field("", title="Host name") - icon: str = Field("", title="Icon") - environment: str = Field("{}", title="Environment", widget="textarea") + enabled: bool = SettingsField(True) + name: str = SettingsField("", title="Name") + label: str = SettingsField("", title="Label") + host_name: str = SettingsField("", title="Host name") + icon: str = SettingsField("", title="Icon") + environment: str = SettingsField("{}", title="Environment", widget="textarea") - variants: list[AppVariantWithPython] = Field( + variants: list[AppVariantWithPython] = SettingsField( default_factory=list, title="Variants", description="Different variants of the applications", @@ -99,12 +103,12 @@ class AdditionalAppGroup(BaseSettingsModel): class ToolVariantModel(BaseSettingsModel): - name: str = Field("", title="Name") - label: str = Field("", title="Label") - host_names: list[str] = Field(default_factory=list, title="Hosts") + name: str = SettingsField("", title="Name") + label: str = SettingsField("", title="Label") + host_names: list[str] = SettingsField(default_factory=list, title="Hosts") # TODO use applications enum if possible - app_variants: list[str] = Field(default_factory=list, title="Applications") - environment: str = Field("{}", title="Environments", widget="textarea") + app_variants: list[str] = SettingsField(default_factory=list, title="Applications") + environment: str = SettingsField("{}", title="Environments", widget="textarea") @validator("environment") def validate_json(cls, value): @@ -112,10 +116,10 @@ class ToolVariantModel(BaseSettingsModel): class ToolGroupModel(BaseSettingsModel): - name: str = Field("", title="Name") - label: str = Field("", title="Label") - environment: str = Field("{}", title="Environments", widget="textarea") - variants: list[ToolVariantModel] = Field(default_factory=list) + name: str = SettingsField("", title="Name") + label: str = SettingsField("", title="Label") + environment: str = SettingsField("{}", title="Environments", widget="textarea") + variants: list[ToolVariantModel] = SettingsField(default_factory=list) @validator("environment") def validate_json(cls, value): @@ -130,47 +134,47 @@ class ToolGroupModel(BaseSettingsModel): class ApplicationsSettings(BaseSettingsModel): """Applications settings""" - maya: AppGroupWithPython = Field( + maya: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Autodesk Maya") - adsk_3dsmax: AppGroupWithPython = Field( + adsk_3dsmax: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Autodesk 3ds Max") - flame: AppGroupWithPython = Field( + flame: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Autodesk Flame") - nuke: AppGroupWithPython = Field( + nuke: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Nuke") - nukeassist: AppGroupWithPython = Field( + nukeassist: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Nuke Assist") - nukex: AppGroupWithPython = Field( + nukex: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Nuke X") - nukestudio: AppGroupWithPython = Field( + nukestudio: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Nuke Studio") - hiero: AppGroupWithPython = Field( + hiero: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Hiero") - fusion: AppGroup = Field( + fusion: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Fusion") - resolve: AppGroupWithPython = Field( + resolve: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Resolve") - houdini: AppGroupWithPython = Field( + houdini: AppGroupWithPython = SettingsField( default_factory=AppGroupWithPython, title="Houdini") - blender: AppGroup = Field( + blender: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Blender") - harmony: AppGroup = Field( + harmony: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Harmony") - tvpaint: AppGroup = Field( + tvpaint: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="TVPaint") - photoshop: AppGroup = Field( + photoshop: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Adobe Photoshop") - aftereffects: AppGroup = Field( + aftereffects: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Adobe After Effects") - celaction: AppGroup = Field( + celaction: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Celaction 2D") - substancepainter: AppGroup = Field( + substancepainter: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Substance Painter") - unreal: AppGroup = Field( + unreal: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Unreal Editor") - wrap: AppGroup = Field( + wrap: AppGroup = SettingsField( default_factory=AppGroupWithPython, title="Wrap") - additional_apps: list[AdditionalAppGroup] = Field( + additional_apps: list[AdditionalAppGroup] = SettingsField( default_factory=list, title="Additional Applications") @validator("additional_apps") @@ -180,16 +184,16 @@ class ApplicationsSettings(BaseSettingsModel): class ApplicationsAddonSettings(BaseSettingsModel): - applications: ApplicationsSettings = Field( + applications: ApplicationsSettings = SettingsField( default_factory=ApplicationsSettings, title="Applications", scope=["studio"] ) - tool_groups: list[ToolGroupModel] = Field( + tool_groups: list[ToolGroupModel] = SettingsField( default_factory=list, scope=["studio"] ) - only_available: bool = Field( + only_available: bool = SettingsField( True, title="Show only available applications") @validator("tool_groups") diff --git a/server_addon/blender/server/settings/imageio.py b/server_addon/blender/server/settings/imageio.py index a6d3c5ff64..412d01638f 100644 --- a/server_addon/blender/server/settings/imageio.py +++ b/server_addon/blender/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class BlenderImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/blender/server/settings/main.py b/server_addon/blender/server/settings/main.py index 3d53162e45..aed9b5632d 100644 --- a/server_addon/blender/server/settings/main.py +++ b/server_addon/blender/server/settings/main.py @@ -1,6 +1,6 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, + SettingsField, TemplateWorkfileBaseOptions, ) @@ -16,38 +16,38 @@ from .render_settings import ( class UnitScaleSettingsModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - apply_on_opening: bool = Field( + enabled: bool = SettingsField(True, title="Enabled") + apply_on_opening: bool = SettingsField( False, title="Apply on Opening Existing Files") - base_file_unit_scale: float = Field( + base_file_unit_scale: float = SettingsField( 1.0, title="Base File Unit Scale" ) class BlenderSettings(BaseSettingsModel): - unit_scale_settings: UnitScaleSettingsModel = Field( + unit_scale_settings: UnitScaleSettingsModel = SettingsField( default_factory=UnitScaleSettingsModel, title="Set Unit Scale" ) - set_resolution_startup: bool = Field( + set_resolution_startup: bool = SettingsField( True, title="Set Resolution on Startup" ) - set_frames_startup: bool = Field( + set_frames_startup: bool = SettingsField( True, title="Set Start/End Frames and FPS on Startup" ) - imageio: BlenderImageIOModel = Field( + imageio: BlenderImageIOModel = SettingsField( default_factory=BlenderImageIOModel, title="Color Management (ImageIO)" ) - RenderSettings: RenderSettingsModel = Field( + RenderSettings: RenderSettingsModel = SettingsField( default_factory=RenderSettingsModel, title="Render Settings") - workfile_builder: TemplateWorkfileBaseOptions = Field( + workfile_builder: TemplateWorkfileBaseOptions = SettingsField( default_factory=TemplateWorkfileBaseOptions, title="Workfile Builder" ) - publish: PublishPuginsModel = Field( + publish: PublishPuginsModel = SettingsField( default_factory=PublishPuginsModel, title="Publish Plugins" ) diff --git a/server_addon/blender/server/settings/publish_plugins.py b/server_addon/blender/server/settings/publish_plugins.py index 9a1e0c681e..c2a989dd55 100644 --- a/server_addon/blender/server/settings/publish_plugins.py +++ b/server_addon/blender/server/settings/publish_plugins.py @@ -1,7 +1,7 @@ import json -from pydantic import Field, validator +from pydantic import validator from ayon_server.exceptions import BadRequestException -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField def validate_json_dict(value): @@ -21,36 +21,36 @@ def validate_json_dict(value): class ValidatePluginModel(BaseSettingsModel): - enabled: bool = Field(True) - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") + enabled: bool = SettingsField(True) + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") class ValidateFileSavedModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateFileSaved") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - exclude_families: list[str] = Field( + enabled: bool = SettingsField(title="ValidateFileSaved") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + exclude_families: list[str] = SettingsField( default_factory=list, title="Exclude product types" ) class ExtractBlendModel(BaseSettingsModel): - enabled: bool = Field(True) - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - families: list[str] = Field( + enabled: bool = SettingsField(True) + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + families: list[str] = SettingsField( default_factory=list, title="Families" ) class ExtractPlayblastModel(BaseSettingsModel): - enabled: bool = Field(True) - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - presets: str = Field("", title="Presets", widget="textarea") + enabled: bool = SettingsField(True) + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + presets: str = SettingsField("", title="Presets", widget="textarea") @validator("presets") def validate_json(cls, value): @@ -58,83 +58,83 @@ class ExtractPlayblastModel(BaseSettingsModel): class PublishPuginsModel(BaseSettingsModel): - ValidateCameraZeroKeyframe: ValidatePluginModel = Field( + ValidateCameraZeroKeyframe: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Camera Zero Keyframe", section="General Validators" ) - ValidateFileSaved: ValidateFileSavedModel = Field( + ValidateFileSaved: ValidateFileSavedModel = SettingsField( default_factory=ValidateFileSavedModel, title="Validate File Saved", ) - ValidateInstanceEmpty: ValidatePluginModel = Field( + ValidateInstanceEmpty: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Instance is not Empty" ) - ValidateMeshHasUvs: ValidatePluginModel = Field( + ValidateMeshHasUvs: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Mesh Has Uvs", section="Model Validators" ) - ValidateMeshNoNegativeScale: ValidatePluginModel = Field( + ValidateMeshNoNegativeScale: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Mesh No Negative Scale" ) - ValidateTransformZero: ValidatePluginModel = Field( + ValidateTransformZero: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Transform Zero" ) - ValidateNoColonsInName: ValidatePluginModel = Field( + ValidateNoColonsInName: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate No Colons In Name" ) - ValidateRenderCameraIsSet: ValidatePluginModel = Field( + ValidateRenderCameraIsSet: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Render Camera Is Set", section="Render Validators" ) - ValidateDeadlinePublish: ValidatePluginModel = Field( + ValidateDeadlinePublish: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Render Output for Deadline", ) - ExtractBlend: ExtractBlendModel = Field( + ExtractBlend: ExtractBlendModel = SettingsField( default_factory=ExtractBlendModel, title="Extract Blend", section="Extractors" ) - ExtractFBX: ValidatePluginModel = Field( + ExtractFBX: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Extract FBX" ) - ExtractModelABC: ValidatePluginModel = Field( + ExtractModelABC: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Extract ABC" ) - ExtractBlendAnimation: ValidatePluginModel = Field( + ExtractBlendAnimation: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Extract Blend Animation" ) - ExtractAnimationFBX: ValidatePluginModel = Field( + ExtractAnimationFBX: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Extract Animation FBX" ) - ExtractCamera: ValidatePluginModel = Field( + ExtractCamera: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Extract Camera" ) - ExtractCameraABC: ValidatePluginModel = Field( + ExtractCameraABC: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Extract Camera as ABC" ) - ExtractLayout: ValidatePluginModel = Field( + ExtractLayout: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Extract Layout (JSON)" ) - ExtractThumbnail: ExtractPlayblastModel = Field( + ExtractThumbnail: ExtractPlayblastModel = SettingsField( default_factory=ExtractPlayblastModel, title="Extract Thumbnail" ) - ExtractPlayblast: ExtractPlayblastModel = Field( + ExtractPlayblast: ExtractPlayblastModel = SettingsField( default_factory=ExtractPlayblastModel, title="Extract Playblast" ) diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index f62013982e..f91ba1627a 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -1,7 +1,5 @@ """Providing models and values for Blender Render Settings.""" -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField def aov_separators_enum(): @@ -58,8 +56,8 @@ class CustomPassesModel(BaseSettingsModel): """Custom Passes""" _layout = "compact" - attribute: str = Field("", title="Attribute name") - value: str = Field( + attribute: str = SettingsField("", title="Attribute name") + value: str = SettingsField( "COLOR", title="Type", enum_resolver=custom_passes_types_enum @@ -67,28 +65,28 @@ class CustomPassesModel(BaseSettingsModel): class RenderSettingsModel(BaseSettingsModel): - default_render_image_folder: str = Field( + default_render_image_folder: str = SettingsField( title="Default Render Image Folder" ) - aov_separator: str = Field( + aov_separator: str = SettingsField( "underscore", title="AOV Separator Character", enum_resolver=aov_separators_enum ) - image_format: str = Field( + image_format: str = SettingsField( "exr", title="Image Format", enum_resolver=image_format_enum ) - multilayer_exr: bool = Field( + multilayer_exr: bool = SettingsField( title="Multilayer (EXR)" ) - aov_list: list[str] = Field( + aov_list: list[str] = SettingsField( default_factory=list, enum_resolver=aov_list_enum, title="AOVs to create" ) - custom_passes: list[CustomPassesModel] = Field( + custom_passes: list[CustomPassesModel] = SettingsField( default_factory=list, title="Custom Passes", description=( diff --git a/server_addon/celaction/server/imageio.py b/server_addon/celaction/server/imageio.py index 72da441528..1e1ac6ff52 100644 --- a/server_addon/celaction/server/imageio.py +++ b/server_addon/celaction/server/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class CelActionImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/celaction/server/settings.py b/server_addon/celaction/server/settings.py index 68d1d2dc31..9208948a07 100644 --- a/server_addon/celaction/server/settings.py +++ b/server_addon/celaction/server/settings.py @@ -1,18 +1,17 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import CelActionImageIOModel class CollectRenderPathModel(BaseSettingsModel): - output_extension: str = Field( + output_extension: str = SettingsField( "", title="Output render file extension" ) - anatomy_template_key_render_files: str = Field( + anatomy_template_key_render_files: str = SettingsField( "", title="Anatomy template key: render files" ) - anatomy_template_key_metadata: str = Field( + anatomy_template_key_metadata: str = SettingsField( "", title="Anatomy template key: metadata job file" ) @@ -36,7 +35,7 @@ def _workfile_submit_overrides(): class WorkfileModel(BaseSettingsModel): - submission_overrides: list[str] = Field( + submission_overrides: list[str] = SettingsField( default_factory=list, title="Submission workfile overrides", enum_resolver=_workfile_submit_overrides @@ -44,21 +43,21 @@ class WorkfileModel(BaseSettingsModel): class PublishPuginsModel(BaseSettingsModel): - CollectRenderPath: CollectRenderPathModel = Field( + CollectRenderPath: CollectRenderPathModel = SettingsField( default_factory=CollectRenderPathModel, title="Collect Render Path" ) class CelActionSettings(BaseSettingsModel): - imageio: CelActionImageIOModel = Field( + imageio: CelActionImageIOModel = SettingsField( default_factory=CelActionImageIOModel, title="Color Management (ImageIO)" ) - workfile: WorkfileModel = Field( + workfile: WorkfileModel = SettingsField( title="Workfile" ) - publish: PublishPuginsModel = Field( + publish: PublishPuginsModel = SettingsField( default_factory=PublishPuginsModel, title="Publish plugins", ) diff --git a/server_addon/clockify/server/settings.py b/server_addon/clockify/server/settings.py index 9067cd4243..c01d4c1545 100644 --- a/server_addon/clockify/server/settings.py +++ b/server_addon/clockify/server/settings.py @@ -1,9 +1,8 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class ClockifySettings(BaseSettingsModel): - workspace_name: str = Field( + workspace_name: str = SettingsField( "", title="Workspace name", scope=["studio"] diff --git a/server_addon/core/server/settings/main.py b/server_addon/core/server/settings/main.py index f9e572cbf9..1ebfc5a4ca 100644 --- a/server_addon/core/server/settings/main.py +++ b/server_addon/core/server/settings/main.py @@ -1,7 +1,8 @@ import json -from pydantic import Field, validator +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, MultiplatformPathListModel, ensure_unique_names, task_types_enum, @@ -14,35 +15,35 @@ from .tools import GlobalToolsModel, DEFAULT_TOOLS_VALUES class DiskMappingItemModel(BaseSettingsModel): _layout = "expanded" - source: str = Field("", title="Source") - destination: str = Field("", title="Destination") + source: str = SettingsField("", title="Source") + destination: str = SettingsField("", title="Destination") class DiskMappingModel(BaseSettingsModel): - windows: list[DiskMappingItemModel] = Field( + windows: list[DiskMappingItemModel] = SettingsField( title="Windows", default_factory=list, ) - linux: list[DiskMappingItemModel] = Field( + linux: list[DiskMappingItemModel] = SettingsField( title="Linux", default_factory=list, ) - darwin: list[DiskMappingItemModel] = Field( + darwin: list[DiskMappingItemModel] = SettingsField( title="MacOS", default_factory=list, ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class CoreImageIOFileRulesModel(BaseSettingsModel): - activate_global_file_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_global_file_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -54,19 +55,19 @@ class CoreImageIOFileRulesModel(BaseSettingsModel): class CoreImageIOConfigModel(BaseSettingsModel): - filepath: list[str] = Field(default_factory=list, title="Config path") + filepath: list[str] = SettingsField(default_factory=list, title="Config path") class CoreImageIOBaseModel(BaseSettingsModel): - activate_global_color_management: bool = Field( + activate_global_color_management: bool = SettingsField( False, title="Enable Color Management" ) - ocio_config: CoreImageIOConfigModel = Field( + ocio_config: CoreImageIOConfigModel = SettingsField( default_factory=CoreImageIOConfigModel, title="OCIO config" ) - file_rules: CoreImageIOFileRulesModel = Field( + file_rules: CoreImageIOFileRulesModel = SettingsField( default_factory=CoreImageIOFileRulesModel, title="File Rules" ) @@ -74,28 +75,28 @@ class CoreImageIOBaseModel(BaseSettingsModel): class VersionStartCategoryProfileModel(BaseSettingsModel): _layout = "expanded" - host_names: list[str] = Field( + host_names: list[str] = SettingsField( default_factory=list, title="Host names" ) - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field( + task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - product_names: list[str] = Field( + product_names: list[str] = SettingsField( default_factory=list, title="Product names" ) - version_start: int = Field( + version_start: int = SettingsField( 1, title="Version Start", ge=0 @@ -103,52 +104,52 @@ class VersionStartCategoryProfileModel(BaseSettingsModel): class VersionStartCategoryModel(BaseSettingsModel): - profiles: list[VersionStartCategoryProfileModel] = Field( + profiles: list[VersionStartCategoryProfileModel] = SettingsField( default_factory=list, title="Profiles" ) class CoreSettings(BaseSettingsModel): - studio_name: str = Field("", title="Studio name", scope=["studio"]) - studio_code: str = Field("", title="Studio code", scope=["studio"]) - environments: str = Field( + studio_name: str = SettingsField("", title="Studio name", scope=["studio"]) + studio_code: str = SettingsField("", title="Studio code", scope=["studio"]) + environments: str = SettingsField( "{}", title="Global environment variables", widget="textarea", scope=["studio"], ) - disk_mapping: DiskMappingModel = Field( + disk_mapping: DiskMappingModel = SettingsField( default_factory=DiskMappingModel, title="Disk mapping", ) - tools: GlobalToolsModel = Field( + tools: GlobalToolsModel = SettingsField( default_factory=GlobalToolsModel, title="Tools" ) - version_start_category: VersionStartCategoryModel = Field( + version_start_category: VersionStartCategoryModel = SettingsField( default_factory=VersionStartCategoryModel, title="Version start" ) - imageio: CoreImageIOBaseModel = Field( + imageio: CoreImageIOBaseModel = SettingsField( default_factory=CoreImageIOBaseModel, title="Color Management (ImageIO)" ) - publish: PublishPuginsModel = Field( + publish: PublishPuginsModel = SettingsField( default_factory=PublishPuginsModel, title="Publish plugins" ) - project_plugins: MultiplatformPathListModel = Field( + project_plugins: MultiplatformPathListModel = SettingsField( default_factory=MultiplatformPathListModel, title="Additional Project Plugin Paths", ) - project_folder_structure: str = Field( + project_folder_structure: str = SettingsField( "{}", widget="textarea", title="Project folder structure", section="---" ) - project_environments: str = Field( + project_environments: str = SettingsField( "{}", widget="textarea", title="Project environments", diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 0c9b9c96ef..61e35e02d4 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -1,7 +1,8 @@ -from pydantic import Field, validator +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, MultiplatformPathModel, normalize_name, ensure_unique_names, @@ -13,46 +14,46 @@ from ayon_server.types import ColorRGBA_uint8 class ValidateBaseModel(BaseSettingsModel): _isGroup = True - enabled: bool = Field(True) - optional: bool = Field(True, title="Optional") - active: bool = Field(True, title="Active") + enabled: bool = SettingsField(True) + optional: bool = SettingsField(True, title="Optional") + active: bool = SettingsField(True, title="Active") class CollectAnatomyInstanceDataModel(BaseSettingsModel): _isGroup = True - follow_workfile_version: bool = Field( + follow_workfile_version: bool = SettingsField( True, title="Follow workfile version" ) class CollectAudioModel(BaseSettingsModel): _isGroup = True - enabled: bool = Field(True) - audio_product_name: str = Field( + enabled: bool = SettingsField(True) + audio_product_name: str = SettingsField( "", title="Name of audio variant" ) class CollectSceneVersionModel(BaseSettingsModel): _isGroup = True - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Host names" ) - skip_hosts_headless_publish: list[str] = Field( + skip_hosts_headless_publish: list[str] = SettingsField( default_factory=list, title="Skip for host if headless publish" ) class CollectCommentPIModel(BaseSettingsModel): - enabled: bool = Field(True) - families: list[str] = Field(default_factory=list, title="Families") + enabled: bool = SettingsField(True) + families: list[str] = SettingsField(default_factory=list, title="Families") class CollectFramesFixDefModel(BaseSettingsModel): - enabled: bool = Field(True) - rewrite_version_enable: bool = Field( + enabled: bool = SettingsField(True) + rewrite_version_enable: bool = SettingsField( True, title="Show 'Rewrite latest version' toggle" ) @@ -60,15 +61,15 @@ class CollectFramesFixDefModel(BaseSettingsModel): class ValidateIntentProfile(BaseSettingsModel): _layout = "expanded" - hosts: list[str] = Field(default_factory=list, title="Host names") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Host names") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field(default_factory=list, title="Task names") + tasks: list[str] = SettingsField(default_factory=list, title="Task names") # TODO This was 'validate' in v3 - validate_intent: bool = Field(True, title="Validate") + validate_intent: bool = SettingsField(True, title="Validate") class ValidateIntentModel(BaseSettingsModel): @@ -79,16 +80,16 @@ class ValidateIntentModel(BaseSettingsModel): """ _isGroup = True - enabled: bool = Field(False) - profiles: list[ValidateIntentProfile] = Field(default_factory=list) + enabled: bool = SettingsField(False) + profiles: list[ValidateIntentProfile] = SettingsField(default_factory=list) class ExtractThumbnailFFmpegModel(BaseSettingsModel): - input: list[str] = Field( + input: list[str] = SettingsField( default_factory=list, title="FFmpeg input arguments" ) - output: list[str] = Field( + output: list[str] = SettingsField( default_factory=list, title="FFmpeg input arguments" ) @@ -96,7 +97,7 @@ class ExtractThumbnailFFmpegModel(BaseSettingsModel): class ResizeItemModel(BaseSettingsModel): _layout = "expanded" - width: int = Field( + width: int = SettingsField( 1920, ge=0, le=100000, @@ -104,7 +105,7 @@ class ResizeItemModel(BaseSettingsModel): description="Width and Height must be both set to higher value than 0" " else source resolution is used." ) - height: int = Field( + height: int = SettingsField( 1080, title="Height", ge=0, @@ -121,7 +122,7 @@ _resize_types_enum = [ class ResizeModel(BaseSettingsModel): _layout = "expanded" - type: str = Field( + type: str = SettingsField( title="Type", description="Type of resizing", enum_resolver=lambda: _resize_types_enum, @@ -129,7 +130,7 @@ class ResizeModel(BaseSettingsModel): default="source" ) - resize: ResizeItemModel = Field( + resize: ResizeItemModel = SettingsField( default_factory=ResizeItemModel, title="Resize" ) @@ -143,18 +144,18 @@ _thumbnail_oiio_transcoding_type = [ class DisplayAndViewModel(BaseSettingsModel): _layout = "expanded" - display: str = Field( + display: str = SettingsField( "default", title="Display" ) - view: str = Field( + view: str = SettingsField( "sRGB", title="View" ) class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): - type: str = Field( + type: str = SettingsField( title="Type", description="Transcoding type", enum_resolver=lambda: _thumbnail_oiio_transcoding_type, @@ -162,11 +163,11 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): default="colorspace" ) - colorspace: str = Field( + colorspace: str = SettingsField( "", title="Colorspace" ) - display_and_view: DisplayAndViewModel = Field( + display_and_view: DisplayAndViewModel = SettingsField( default_factory=DisplayAndViewModel, title="Display&View" ) @@ -174,30 +175,30 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): class ExtractThumbnailModel(BaseSettingsModel): _isGroup = True - enabled: bool = Field(True) - integrate_thumbnail: bool = Field( + enabled: bool = SettingsField(True) + integrate_thumbnail: bool = SettingsField( True, title="Integrate Thumbnail Representation" ) - target_size: ResizeModel = Field( + target_size: ResizeModel = SettingsField( default_factory=ResizeModel, title="Target size" ) - background_color: ColorRGBA_uint8 = Field( + background_color: ColorRGBA_uint8 = SettingsField( (0, 0, 0, 0.0), title="Background color" ) - duration_split: float = Field( + duration_split: float = SettingsField( 0.5, title="Duration split", ge=0.0, le=1.0 ) - oiiotool_defaults: ExtractThumbnailOIIODefaultsModel = Field( + oiiotool_defaults: ExtractThumbnailOIIODefaultsModel = SettingsField( default_factory=ExtractThumbnailOIIODefaultsModel, title="OIIOtool defaults" ) - ffmpeg_args: ExtractThumbnailFFmpegModel = Field( + ffmpeg_args: ExtractThumbnailFFmpegModel = SettingsField( default_factory=ExtractThumbnailFFmpegModel ) @@ -210,57 +211,57 @@ def _extract_oiio_transcoding_type(): class OIIOToolArgumentsModel(BaseSettingsModel): - additional_command_args: list[str] = Field( + additional_command_args: list[str] = SettingsField( default_factory=list, title="Arguments") class ExtractOIIOTranscodeOutputModel(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Name") - extension: str = Field("", title="Extension") - transcoding_type: str = Field( + name: str = SettingsField("", title="Name") + extension: str = SettingsField("", title="Extension") + transcoding_type: str = SettingsField( "colorspace", title="Transcoding type", enum_resolver=_extract_oiio_transcoding_type ) - colorspace: str = Field("", title="Colorspace") - display: str = Field("", title="Display") - view: str = Field("", title="View") - oiiotool_args: OIIOToolArgumentsModel = Field( + colorspace: str = SettingsField("", title="Colorspace") + display: str = SettingsField("", title="Display") + view: str = SettingsField("", title="View") + oiiotool_args: OIIOToolArgumentsModel = SettingsField( default_factory=OIIOToolArgumentsModel, title="OIIOtool arguments") - tags: list[str] = Field(default_factory=list, title="Tags") - custom_tags: list[str] = Field(default_factory=list, title="Custom Tags") + tags: list[str] = SettingsField(default_factory=list, title="Tags") + custom_tags: list[str] = SettingsField(default_factory=list, title="Custom Tags") class ExtractOIIOTranscodeProfileModel(BaseSettingsModel): - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Host names" ) - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field( + task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - product_names: list[str] = Field( + product_names: list[str] = SettingsField( default_factory=list, title="Product names" ) - delete_original: bool = Field( + delete_original: bool = SettingsField( True, title="Delete Original Representation" ) - outputs: list[ExtractOIIOTranscodeOutputModel] = Field( + outputs: list[ExtractOIIOTranscodeOutputModel] = SettingsField( default_factory=list, title="Output Definitions", ) @@ -272,27 +273,27 @@ class ExtractOIIOTranscodeProfileModel(BaseSettingsModel): class ExtractOIIOTranscodeModel(BaseSettingsModel): - enabled: bool = Field(True) - profiles: list[ExtractOIIOTranscodeProfileModel] = Field( + enabled: bool = SettingsField(True) + profiles: list[ExtractOIIOTranscodeProfileModel] = SettingsField( default_factory=list, title="Profiles" ) # --- [START] Extract Review --- class ExtractReviewFFmpegModel(BaseSettingsModel): - video_filters: list[str] = Field( + video_filters: list[str] = SettingsField( default_factory=list, title="Video filters" ) - audio_filters: list[str] = Field( + audio_filters: list[str] = SettingsField( default_factory=list, title="Audio filters" ) - input: list[str] = Field( + input: list[str] = SettingsField( default_factory=list, title="Input arguments" ) - output: list[str] = Field( + output: list[str] = SettingsField( default_factory=list, title="Output arguments" ) @@ -316,11 +317,11 @@ def extract_review_filter_enum(): class ExtractReviewFilterModel(BaseSettingsModel): - families: list[str] = Field(default_factory=list, title="Families") - product_names: list[str] = Field( + families: list[str] = SettingsField(default_factory=list, title="Families") + product_names: list[str] = SettingsField( default_factory=list, title="Product names") - custom_tags: list[str] = Field(default_factory=list, title="Custom Tags") - single_frame_filter: str = Field( + custom_tags: list[str] = SettingsField(default_factory=list, title="Custom Tags") + single_frame_filter: str = SettingsField( "everytime", description=( "Use output always / only if input is 1 frame" @@ -331,24 +332,24 @@ class ExtractReviewFilterModel(BaseSettingsModel): class ExtractReviewLetterBox(BaseSettingsModel): - enabled: bool = Field(True) - ratio: float = Field( + enabled: bool = SettingsField(True) + ratio: float = SettingsField( 0.0, title="Ratio", ge=0.0, le=10000.0 ) - fill_color: ColorRGBA_uint8 = Field( + fill_color: ColorRGBA_uint8 = SettingsField( (0, 0, 0, 0.0), title="Fill Color" ) - line_thickness: int = Field( + line_thickness: int = SettingsField( 0, title="Line Thickness", ge=0, le=1000 ) - line_color: ColorRGBA_uint8 = Field( + line_color: ColorRGBA_uint8 = SettingsField( (0, 0, 0, 0.0), title="Line Color" ) @@ -356,29 +357,29 @@ class ExtractReviewLetterBox(BaseSettingsModel): class ExtractReviewOutputDefModel(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Name") - ext: str = Field("", title="Output extension") + name: str = SettingsField("", title="Name") + ext: str = SettingsField("", title="Output extension") # TODO use some different source of tags - tags: list[str] = Field(default_factory=list, title="Tags") - burnins: list[str] = Field( + tags: list[str] = SettingsField(default_factory=list, title="Tags") + burnins: list[str] = SettingsField( default_factory=list, title="Link to a burnin by name" ) - ffmpeg_args: ExtractReviewFFmpegModel = Field( + ffmpeg_args: ExtractReviewFFmpegModel = SettingsField( default_factory=ExtractReviewFFmpegModel, title="FFmpeg arguments" ) - filter: ExtractReviewFilterModel = Field( + filter: ExtractReviewFilterModel = SettingsField( default_factory=ExtractReviewFilterModel, title="Additional output filtering" ) - overscan_crop: str = Field( + overscan_crop: str = SettingsField( "", title="Overscan crop", description=( "Crop input overscan. See the documentation for more information." ) ) - overscan_color: ColorRGBA_uint8 = Field( + overscan_color: ColorRGBA_uint8 = SettingsField( (0, 0, 0, 0.0), title="Overscan color", description=( @@ -386,7 +387,7 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): " same as output aspect ratio." ) ) - width: int = Field( + width: int = SettingsField( 0, ge=0, le=100000, @@ -396,13 +397,13 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): " value than 0 else source resolution is used." ) ) - height: int = Field( + height: int = SettingsField( 0, title="Output height", ge=0, le=100000, ) - scale_pixel_aspect: bool = Field( + scale_pixel_aspect: bool = SettingsField( True, title="Scale pixel aspect", description=( @@ -410,7 +411,7 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): " Usefull for anamorph reviews." ) ) - bg_color: ColorRGBA_uint8 = Field( + bg_color: ColorRGBA_uint8 = SettingsField( (0, 0, 0, 0.0), description=( "Background color is used only when input have transparency" @@ -418,7 +419,7 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): ), title="Background color", ) - letter_box: ExtractReviewLetterBox = Field( + letter_box: ExtractReviewLetterBox = SettingsField( default_factory=ExtractReviewLetterBox, title="Letter Box" ) @@ -431,14 +432,14 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): class ExtractReviewProfileModel(BaseSettingsModel): _layout = "expanded" - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) # TODO use hosts enum - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Host names" ) - outputs: list[ExtractReviewOutputDefModel] = Field( + outputs: list[ExtractReviewOutputDefModel] = SettingsField( default_factory=list, title="Output Definitions" ) @@ -450,8 +451,8 @@ class ExtractReviewProfileModel(BaseSettingsModel): class ExtractReviewModel(BaseSettingsModel): _isGroup = True - enabled: bool = Field(True) - profiles: list[ExtractReviewProfileModel] = Field( + enabled: bool = SettingsField(True) + profiles: list[ExtractReviewProfileModel] = SettingsField( default_factory=list, title="Profiles" ) @@ -460,30 +461,30 @@ class ExtractReviewModel(BaseSettingsModel): # --- [Start] Extract Burnin --- class ExtractBurninOptionsModel(BaseSettingsModel): - font_size: int = Field(0, ge=0, title="Font size") - font_color: ColorRGBA_uint8 = Field( + font_size: int = SettingsField(0, ge=0, title="Font size") + font_color: ColorRGBA_uint8 = SettingsField( (255, 255, 255, 1.0), title="Font color" ) - bg_color: ColorRGBA_uint8 = Field( + bg_color: ColorRGBA_uint8 = SettingsField( (0, 0, 0, 1.0), title="Background color" ) - x_offset: int = Field(0, title="X Offset") - y_offset: int = Field(0, title="Y Offset") - bg_padding: int = Field(0, title="Padding around text") - font_filepath: MultiplatformPathModel = Field( + x_offset: int = SettingsField(0, title="X Offset") + y_offset: int = SettingsField(0, title="Y Offset") + bg_padding: int = SettingsField(0, title="Padding around text") + font_filepath: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel, title="Font file path" ) class ExtractBurninDefFilter(BaseSettingsModel): - families: list[str] = Field( + families: list[str] = SettingsField( default_factory=list, title="Families" ) - tags: list[str] = Field( + tags: list[str] = SettingsField( default_factory=list, title="Tags" ) @@ -492,14 +493,14 @@ class ExtractBurninDefFilter(BaseSettingsModel): class ExtractBurninDef(BaseSettingsModel): _isGroup = True _layout = "expanded" - name: str = Field("") - TOP_LEFT: str = Field("", topic="Top Left") - TOP_CENTERED: str = Field("", topic="Top Centered") - TOP_RIGHT: str = Field("", topic="Top Right") - BOTTOM_LEFT: str = Field("", topic="Bottom Left") - BOTTOM_CENTERED: str = Field("", topic="Bottom Centered") - BOTTOM_RIGHT: str = Field("", topic="Bottom Right") - filter: ExtractBurninDefFilter = Field( + name: str = SettingsField("") + TOP_LEFT: str = SettingsField("", topic="Top Left") + TOP_CENTERED: str = SettingsField("", topic="Top Centered") + TOP_RIGHT: str = SettingsField("", topic="Top Right") + BOTTOM_LEFT: str = SettingsField("", topic="Bottom Left") + BOTTOM_CENTERED: str = SettingsField("", topic="Bottom Centered") + BOTTOM_RIGHT: str = SettingsField("", topic="Bottom Right") + filter: ExtractBurninDefFilter = SettingsField( default_factory=ExtractBurninDefFilter, title="Additional filtering" ) @@ -512,28 +513,28 @@ class ExtractBurninDef(BaseSettingsModel): class ExtractBurninProfile(BaseSettingsModel): _layout = "expanded" - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Produt types" ) - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Host names" ) - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field( + task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - product_names: list[str] = Field( + product_names: list[str] = SettingsField( default_factory=list, title="Product names" ) - burnins: list[ExtractBurninDef] = Field( + burnins: list[ExtractBurninDef] = SettingsField( default_factory=list, title="Burnins" ) @@ -547,12 +548,12 @@ class ExtractBurninProfile(BaseSettingsModel): class ExtractBurninModel(BaseSettingsModel): _isGroup = True - enabled: bool = Field(True) - options: ExtractBurninOptionsModel = Field( + enabled: bool = SettingsField(True) + options: ExtractBurninOptionsModel = SettingsField( default_factory=ExtractBurninOptionsModel, title="Burnin formatting options" ) - profiles: list[ExtractBurninProfile] = Field( + profiles: list[ExtractBurninProfile] = SettingsField( default_factory=list, title="Profiles" ) @@ -561,24 +562,24 @@ class ExtractBurninModel(BaseSettingsModel): class PreIntegrateThumbnailsProfile(BaseSettingsModel): _isGroup = True - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types", ) - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Hosts", ) - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - product_names: list[str] = Field( + product_names: list[str] = SettingsField( default_factory=list, title="Product names", ) - integrate_thumbnail: bool = Field(True) + integrate_thumbnail: bool = SettingsField(True) class PreIntegrateThumbnailsModel(BaseSettingsModel): @@ -589,26 +590,26 @@ class PreIntegrateThumbnailsModel(BaseSettingsModel): """ _isGroup = True - enabled: bool = Field(True) - integrate_profiles: list[PreIntegrateThumbnailsProfile] = Field( + enabled: bool = SettingsField(True) + integrate_profiles: list[PreIntegrateThumbnailsProfile] = SettingsField( default_factory=list, title="Integrate profiles" ) class IntegrateProductGroupProfile(BaseSettingsModel): - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - hosts: list[str] = Field(default_factory=list, title="Hosts") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field(default_factory=list, title="Task names") - template: str = Field("", title="Template") + tasks: list[str] = SettingsField(default_factory=list, title="Task names") + template: str = SettingsField("", title="Template") class IntegrateProductGroupModel(BaseSettingsModel): @@ -622,163 +623,163 @@ class IntegrateProductGroupModel(BaseSettingsModel): """ _isGroup = True - product_grouping_profiles: list[IntegrateProductGroupProfile] = Field( + product_grouping_profiles: list[IntegrateProductGroupProfile] = SettingsField( default_factory=list, title="Product group profiles" ) class IntegrateANProductGroupProfileModel(BaseSettingsModel): - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Hosts" ) - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field( + tasks: list[str] = SettingsField( default_factory=list, title="Task names" ) - template: str = Field("", title="Template") + template: str = SettingsField("", title="Template") class IntegrateANTemplateNameProfileModel(BaseSettingsModel): - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Hosts" ) - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field( + tasks: list[str] = SettingsField( default_factory=list, title="Task names" ) - template_name: str = Field("", title="Template name") + template_name: str = SettingsField("", title="Template name") class IntegrateHeroTemplateNameProfileModel(BaseSettingsModel): - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - hosts: list[str] = Field( + hosts: list[str] = SettingsField( default_factory=list, title="Hosts" ) - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field( + task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - template_name: str = Field("", title="Template name") + template_name: str = SettingsField("", title="Template name") class IntegrateHeroVersionModel(BaseSettingsModel): _isGroup = True - enabled: bool = Field(True) - optional: bool = Field(False, title="Optional") - active: bool = Field(True, title="Active") - families: list[str] = Field(default_factory=list, title="Families") + enabled: bool = SettingsField(True) + optional: bool = SettingsField(False, title="Optional") + active: bool = SettingsField(True, title="Active") + families: list[str] = SettingsField(default_factory=list, title="Families") class CleanUpModel(BaseSettingsModel): _isGroup = True - paterns: list[str] = Field( + paterns: list[str] = SettingsField( default_factory=list, title="Patterns (regex)" ) - remove_temp_renders: bool = Field(False, title="Remove Temp renders") + remove_temp_renders: bool = SettingsField(False, title="Remove Temp renders") class CleanUpFarmModel(BaseSettingsModel): _isGroup = True - enabled: bool = Field(True) + enabled: bool = SettingsField(True) class PublishPuginsModel(BaseSettingsModel): - CollectAnatomyInstanceData: CollectAnatomyInstanceDataModel = Field( + CollectAnatomyInstanceData: CollectAnatomyInstanceDataModel = SettingsField( default_factory=CollectAnatomyInstanceDataModel, title="Collect Anatomy Instance Data" ) - CollectAudio: CollectAudioModel = Field( + CollectAudio: CollectAudioModel = SettingsField( default_factory=CollectAudioModel, title="Collect Audio" ) - CollectSceneVersion: CollectSceneVersionModel = Field( + CollectSceneVersion: CollectSceneVersionModel = SettingsField( default_factory=CollectSceneVersionModel, title="Collect Version from Workfile" ) - collect_comment_per_instance: CollectCommentPIModel = Field( + collect_comment_per_instance: CollectCommentPIModel = SettingsField( default_factory=CollectCommentPIModel, title="Collect comment per instance", ) - CollectFramesFixDef: CollectFramesFixDefModel = Field( + CollectFramesFixDef: CollectFramesFixDefModel = SettingsField( default_factory=CollectFramesFixDefModel, title="Collect Frames to Fix", ) - ValidateEditorialAssetName: ValidateBaseModel = Field( + ValidateEditorialAssetName: ValidateBaseModel = SettingsField( default_factory=ValidateBaseModel, title="Validate Editorial Asset Name" ) - ValidateVersion: ValidateBaseModel = Field( + ValidateVersion: ValidateBaseModel = SettingsField( default_factory=ValidateBaseModel, title="Validate Version" ) - ValidateIntent: ValidateIntentModel = Field( + ValidateIntent: ValidateIntentModel = SettingsField( default_factory=ValidateIntentModel, title="Validate Intent" ) - ExtractThumbnail: ExtractThumbnailModel = Field( + ExtractThumbnail: ExtractThumbnailModel = SettingsField( default_factory=ExtractThumbnailModel, title="Extract Thumbnail" ) - ExtractOIIOTranscode: ExtractOIIOTranscodeModel = Field( + ExtractOIIOTranscode: ExtractOIIOTranscodeModel = SettingsField( default_factory=ExtractOIIOTranscodeModel, title="Extract OIIO Transcode" ) - ExtractReview: ExtractReviewModel = Field( + ExtractReview: ExtractReviewModel = SettingsField( default_factory=ExtractReviewModel, title="Extract Review" ) - ExtractBurnin: ExtractBurninModel = Field( + ExtractBurnin: ExtractBurninModel = SettingsField( default_factory=ExtractBurninModel, title="Extract Burnin" ) - PreIntegrateThumbnails: PreIntegrateThumbnailsModel = Field( + PreIntegrateThumbnails: PreIntegrateThumbnailsModel = SettingsField( default_factory=PreIntegrateThumbnailsModel, title="Override Integrate Thumbnail Representations" ) - IntegrateProductGroup: IntegrateProductGroupModel = Field( + IntegrateProductGroup: IntegrateProductGroupModel = SettingsField( default_factory=IntegrateProductGroupModel, title="Integrate Product Group" ) - IntegrateHeroVersion: IntegrateHeroVersionModel = Field( + IntegrateHeroVersion: IntegrateHeroVersionModel = SettingsField( default_factory=IntegrateHeroVersionModel, title="Integrate Hero Version" ) - CleanUp: CleanUpModel = Field( + CleanUp: CleanUpModel = SettingsField( default_factory=CleanUpModel, title="Clean Up" ) - CleanUpFarm: CleanUpFarmModel = Field( + CleanUpFarm: CleanUpFarmModel = SettingsField( default_factory=CleanUpFarmModel, title="Clean Up Farm" ) diff --git a/server_addon/core/server/settings/tools.py b/server_addon/core/server/settings/tools.py index 0dd9d396ae..b45f9b49d4 100644 --- a/server_addon/core/server/settings/tools.py +++ b/server_addon/core/server/settings/tools.py @@ -1,6 +1,7 @@ -from pydantic import Field, validator +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, normalize_name, ensure_unique_names, task_types_enum, @@ -9,8 +10,10 @@ from ayon_server.settings import ( class ProductTypeSmartSelectModel(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Product type") - task_names: list[str] = Field(default_factory=list, title="Task names") + name: str = SettingsField("", title="Product type") + task_names: list[str] = SettingsField( + default_factory=list, title="Task names" + ) @validator("name") def normalize_value(cls, value): @@ -19,26 +22,28 @@ class ProductTypeSmartSelectModel(BaseSettingsModel): class ProductNameProfile(BaseSettingsModel): _layout = "expanded" - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - hosts: list[str] = Field(default_factory=list, title="Hosts") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field(default_factory=list, title="Task names") - template: str = Field("", title="Template") + tasks: list[str] = SettingsField(default_factory=list, title="Task names") + template: str = SettingsField("", title="Template") class CreatorToolModel(BaseSettingsModel): # TODO this was dynamic dictionary '{name: task_names}' - product_types_smart_select: list[ProductTypeSmartSelectModel] = Field( - default_factory=list, - title="Create Smart Select" + product_types_smart_select: list[ProductTypeSmartSelectModel] = ( + SettingsField( + default_factory=list, + title="Create Smart Select" + ) ) - product_name_profiles: list[ProductNameProfile] = Field( + product_name_profiles: list[ProductNameProfile] = SettingsField( default_factory=list, title="Product name profiles" ) @@ -51,29 +56,29 @@ class CreatorToolModel(BaseSettingsModel): class WorkfileTemplateProfile(BaseSettingsModel): _layout = "expanded" - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) # TODO this should use hosts enum - hosts: list[str] = Field(default_factory=list, title="Hosts") + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") # TODO this was using project anatomy template name - workfile_template: str = Field("", title="Workfile template") + workfile_template: str = SettingsField("", title="Workfile template") class LastWorkfileOnStartupProfile(BaseSettingsModel): _layout = "expanded" # TODO this should use hosts enum - hosts: list[str] = Field(default_factory=list, title="Hosts") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field(default_factory=list, title="Task names") - enabled: bool = Field(True, title="Enabled") - use_last_published_workfile: bool = Field( + tasks: list[str] = SettingsField(default_factory=list, title="Task names") + enabled: bool = SettingsField(True, title="Enabled") + use_last_published_workfile: bool = SettingsField( True, title="Use last published workfile" ) @@ -81,54 +86,60 @@ class LastWorkfileOnStartupProfile(BaseSettingsModel): class WorkfilesToolOnStartupProfile(BaseSettingsModel): _layout = "expanded" # TODO this should use hosts enum - hosts: list[str] = Field(default_factory=list, title="Hosts") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field(default_factory=list, title="Task names") - enabled: bool = Field(True, title="Enabled") + tasks: list[str] = SettingsField(default_factory=list, title="Task names") + enabled: bool = SettingsField(True, title="Enabled") class ExtraWorkFoldersProfile(BaseSettingsModel): _layout = "expanded" # TODO this should use hosts enum - hosts: list[str] = Field(default_factory=list, title="Hosts") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field(default_factory=list, title="Task names") - folders: list[str] = Field(default_factory=list, title="Folders") + task_names: list[str] = SettingsField( + default_factory=list, title="Task names" + ) + folders: list[str] = SettingsField(default_factory=list, title="Folders") class WorkfilesLockProfile(BaseSettingsModel): _layout = "expanded" # TODO this should use hosts enum - host_names: list[str] = Field(default_factory=list, title="Hosts") - enabled: bool = Field(True, title="Enabled") + host_names: list[str] = SettingsField(default_factory=list, title="Hosts") + enabled: bool = SettingsField(True, title="Enabled") class WorkfilesToolModel(BaseSettingsModel): - workfile_template_profiles: list[WorkfileTemplateProfile] = Field( + workfile_template_profiles: list[WorkfileTemplateProfile] = SettingsField( default_factory=list, title="Workfile template profiles" ) - last_workfile_on_startup: list[LastWorkfileOnStartupProfile] = Field( - default_factory=list, - title="Open last workfile on launch" + last_workfile_on_startup: list[LastWorkfileOnStartupProfile] = ( + SettingsField( + default_factory=list, + title="Open last workfile on launch" + ) ) - open_workfile_tool_on_startup: list[WorkfilesToolOnStartupProfile] = Field( - default_factory=list, - title="Open workfile tool on launch" + open_workfile_tool_on_startup: list[WorkfilesToolOnStartupProfile] = ( + SettingsField( + default_factory=list, + title="Open workfile tool on launch" + ) ) - extra_folders: list[ExtraWorkFoldersProfile] = Field( + extra_folders: list[ExtraWorkFoldersProfile] = SettingsField( default_factory=list, title="Extra work folders" ) - workfile_lock_profiles: list[WorkfilesLockProfile] = Field( + workfile_lock_profiles: list[WorkfilesLockProfile] = SettingsField( default_factory=list, title="Workfile lock profiles" ) @@ -175,95 +186,100 @@ def _product_types_enum(): class LoaderProductTypeFilterProfile(BaseSettingsModel): _layout = "expanded" # TODO this should use hosts enum - hosts: list[str] = Field(default_factory=list, title="Hosts") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - is_include: bool = Field(True, title="Exclude / Include") - filter_product_types: list[str] = Field( + is_include: bool = SettingsField(True, title="Exclude / Include") + filter_product_types: list[str] = SettingsField( default_factory=list, enum_resolver=_product_types_enum ) class LoaderToolModel(BaseSettingsModel): - product_type_filter_profiles: list[LoaderProductTypeFilterProfile] = Field( - default_factory=list, - title="Product type filtering" + product_type_filter_profiles: list[LoaderProductTypeFilterProfile] = ( + SettingsField(default_factory=list, title="Product type filtering") ) class PublishTemplateNameProfile(BaseSettingsModel): _layout = "expanded" - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) # TODO this should use hosts enum - hosts: list[str] = Field(default_factory=list, title="Hosts") - task_types: list[str] = Field( + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field(default_factory=list, title="Task names") - template_name: str = Field("", title="Template name") + task_names: list[str] = SettingsField( + default_factory=list, title="Task names" + ) + template_name: str = SettingsField("", title="Template name") class CustomStagingDirProfileModel(BaseSettingsModel): - active: bool = Field(True, title="Is active") - hosts: list[str] = Field(default_factory=list, title="Host names") - task_types: list[str] = Field( + active: bool = SettingsField(True, title="Is active") + hosts: list[str] = SettingsField(default_factory=list, title="Host names") + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field( + task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - product_names: list[str] = Field( + product_names: list[str] = SettingsField( default_factory=list, title="Product names" ) - custom_staging_dir_persistent: bool = Field( + custom_staging_dir_persistent: bool = SettingsField( False, title="Custom Staging Folder Persistent" ) - template_name: str = Field("", title="Template Name") + template_name: str = SettingsField("", title="Template Name") class PublishToolModel(BaseSettingsModel): - template_name_profiles: list[PublishTemplateNameProfile] = Field( + template_name_profiles: list[PublishTemplateNameProfile] = SettingsField( default_factory=list, title="Template name profiles" ) - hero_template_name_profiles: list[PublishTemplateNameProfile] = Field( - default_factory=list, - title="Hero template name profiles" + hero_template_name_profiles: list[PublishTemplateNameProfile] = ( + SettingsField( + default_factory=list, + title="Hero template name profiles" + ) ) - custom_staging_dir_profiles: list[CustomStagingDirProfileModel] = Field( - default_factory=list, - title="Custom Staging Dir Profiles" + custom_staging_dir_profiles: list[CustomStagingDirProfileModel] = ( + SettingsField( + default_factory=list, + title="Custom Staging Dir Profiles" + ) ) class GlobalToolsModel(BaseSettingsModel): - creator: CreatorToolModel = Field( + creator: CreatorToolModel = SettingsField( default_factory=CreatorToolModel, title="Creator" ) - Workfiles: WorkfilesToolModel = Field( + Workfiles: WorkfilesToolModel = SettingsField( default_factory=WorkfilesToolModel, title="Workfiles" ) - loader: LoaderToolModel = Field( + loader: LoaderToolModel = SettingsField( default_factory=LoaderToolModel, title="Loader" ) - publish: PublishToolModel = Field( + publish: PublishToolModel = SettingsField( default_factory=PublishToolModel, title="Publish" ) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index f766ef9db8..9537d6d550 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -1,6 +1,10 @@ -from pydantic import Field, validator +from pydantic import validator -from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + ensure_unique_names, +) from .publish_plugins import ( PublishPluginsModel, @@ -10,8 +14,8 @@ from .publish_plugins import ( class ServerListSubmodel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: str = Field(title="Value") + name: str = SettingsField(title="Name") + value: str = SettingsField(title="Value") async def defined_deadline_ws_name_enum_resolver( @@ -33,18 +37,18 @@ async def defined_deadline_ws_name_enum_resolver( class DeadlineSettings(BaseSettingsModel): - deadline_urls: list[ServerListSubmodel] = Field( + deadline_urls: list[ServerListSubmodel] = SettingsField( default_factory=list, title="System Deadline Webservice URLs", scope=["studio"], ) - deadline_server: str = Field( + deadline_server: str = SettingsField( title="Project deadline server", section="---", scope=["project"], enum_resolver=defined_deadline_ws_name_enum_resolver ) - publish: PublishPluginsModel = Field( + publish: PublishPluginsModel = SettingsField( default_factory=PublishPluginsModel, title="Publish Plugins", ) diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index dc2cd7591f..8abe59674b 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -1,26 +1,30 @@ -from pydantic import Field, validator +from pydantic import validator -from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + ensure_unique_names, +) class CollectDeadlinePoolsModel(BaseSettingsModel): """Settings Deadline default pools.""" - primary_pool: str = Field(title="Primary Pool") + primary_pool: str = SettingsField(title="Primary Pool") - secondary_pool: str = Field(title="Secondary Pool") + secondary_pool: str = SettingsField(title="Secondary Pool") class ValidateExpectedFilesModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - active: bool = Field(True, title="Active") - allow_user_override: bool = Field( + enabled: bool = SettingsField(True, title="Enabled") + active: bool = SettingsField(True, title="Active") + allow_user_override: bool = SettingsField( True, title="Allow user change frame range" ) - families: list[str] = Field( + families: list[str] = SettingsField( default_factory=list, title="Trigger on families" ) - targets: list[str] = Field( + targets: list[str] = SettingsField( default_factory=list, title="Trigger for plugins" ) @@ -45,45 +49,47 @@ def tile_assembler_enum(): class ScenePatchesSubmodel(BaseSettingsModel): _layout = "expanded" - name: str = Field(title="Patch name") - regex: str = Field(title="Patch regex") - line: str = Field(title="Patch line") + name: str = SettingsField(title="Patch name") + regex: str = SettingsField(title="Patch regex") + line: str = SettingsField(title="Patch line") class MayaSubmitDeadlineModel(BaseSettingsModel): """Maya deadline submitter settings.""" - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - use_published: bool = Field(title="Use Published scene") - import_reference: bool = Field(title="Use Scene with Imported Reference") - asset_dependencies: bool = Field(title="Use Asset dependencies") - priority: int = Field(title="Priority") - tile_priority: int = Field(title="Tile Priority") - group: str = Field(title="Group") - limit: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + use_published: bool = SettingsField(title="Use Published scene") + import_reference: bool = SettingsField( + title="Use Scene with Imported Reference" + ) + asset_dependencies: bool = SettingsField(title="Use Asset dependencies") + priority: int = SettingsField(title="Priority") + tile_priority: int = SettingsField(title="Tile Priority") + group: str = SettingsField(title="Group") + limit: list[str] = SettingsField( default_factory=list, title="Limit Groups" ) - tile_assembler_plugin: str = Field( + tile_assembler_plugin: str = SettingsField( title="Tile Assembler Plugin", enum_resolver=tile_assembler_enum, ) - jobInfo: str = Field( + jobInfo: str = SettingsField( title="Additional JobInfo data", widget="textarea", ) - pluginInfo: str = Field( + pluginInfo: str = SettingsField( title="Additional PluginInfo data", widget="textarea", ) - scene_patches: list[ScenePatchesSubmodel] = Field( + scene_patches: list[ScenePatchesSubmodel] = SettingsField( default_factory=list, title="Scene patches", ) - strict_error_checking: bool = Field( + strict_error_checking: bool = SettingsField( title="Disable Strict Error Check profiles" ) @@ -94,25 +100,25 @@ class MayaSubmitDeadlineModel(BaseSettingsModel): class MaxSubmitDeadlineModel(BaseSettingsModel): - enabled: bool = Field(True) - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - use_published: bool = Field(title="Use Published scene") - priority: int = Field(title="Priority") - chunk_size: int = Field(title="Frame per Task") - group: str = Field("", title="Group Name") + enabled: bool = SettingsField(True) + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + use_published: bool = SettingsField(title="Use Published scene") + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Frame per Task") + group: str = SettingsField("", title="Group Name") class EnvSearchReplaceSubmodel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: str = Field(title="Value") + name: str = SettingsField(title="Name") + value: str = SettingsField(title="Value") class LimitGroupsSubmodel(BaseSettingsModel): _layout = "expanded" - name: str = Field(title="Name") - value: list[str] = Field( + name: str = SettingsField(title="Name") + value: list[str] = SettingsField( default_factory=list, title="Limit Groups" ) @@ -137,14 +143,16 @@ def fusion_deadline_plugin_enum(): class FusionSubmitDeadlineModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - optional: bool = Field(False, title="Optional") - active: bool = Field(True, title="Active") - priority: int = Field(50, title="Priority") - chunk_size: int = Field(10, title="Frame per Task") - concurrent_tasks: int = Field(1, title="Number of concurrent tasks") - group: str = Field("", title="Group Name") - plugin: str = Field("Fusion", + enabled: bool = SettingsField(True, title="Enabled") + optional: bool = SettingsField(False, title="Optional") + active: bool = SettingsField(True, title="Active") + priority: int = SettingsField(50, title="Priority") + chunk_size: int = SettingsField(10, title="Frame per Task") + concurrent_tasks: int = SettingsField( + 1, title="Number of concurrent tasks" + ) + group: str = SettingsField("", title="Group Name") + plugin: str = SettingsField("Fusion", enum_resolver=fusion_deadline_plugin_enum, title="Deadline Plugin") @@ -152,34 +160,39 @@ class FusionSubmitDeadlineModel(BaseSettingsModel): class NukeSubmitDeadlineModel(BaseSettingsModel): """Nuke deadline submitter settings.""" - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - priority: int = Field(title="Priority") - chunk_size: int = Field(title="Chunk Size") - concurrent_tasks: int = Field(title="Number of concurrent tasks") - group: str = Field(title="Group") - department: str = Field(title="Department") - use_gpu: bool = Field(title="Use GPU") - workfile_dependency: bool = Field(title="Workfile Dependency") - use_published_workfile: bool = Field(title="Use Published Workfile") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + concurrent_tasks: int = SettingsField(title="Number of concurrent tasks") + group: str = SettingsField(title="Group") + department: str = SettingsField(title="Department") + use_gpu: bool = SettingsField(title="Use GPU") + workfile_dependency: bool = SettingsField(title="Workfile Dependency") + use_published_workfile: bool = SettingsField( + title="Use Published Workfile" + ) - env_allowed_keys: list[str] = Field( + env_allowed_keys: list[str] = SettingsField( default_factory=list, title="Allowed environment keys" ) - env_search_replace_values: list[EnvSearchReplaceSubmodel] = Field( + env_search_replace_values: list[EnvSearchReplaceSubmodel] = SettingsField( default_factory=list, title="Search & replace in environment values", ) - limit_groups: list[LimitGroupsSubmodel] = Field( + limit_groups: list[LimitGroupsSubmodel] = SettingsField( default_factory=list, title="Limit Groups", ) - @validator("limit_groups", "env_allowed_keys", "env_search_replace_values") + @validator( + "limit_groups", + "env_allowed_keys", + "env_search_replace_values") def validate_unique_names(cls, value): ensure_unique_names(value) return value @@ -188,58 +201,62 @@ class NukeSubmitDeadlineModel(BaseSettingsModel): class HarmonySubmitDeadlineModel(BaseSettingsModel): """Harmony deadline submitter settings.""" - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - use_published: bool = Field(title="Use Published scene") - priority: int = Field(title="Priority") - chunk_size: int = Field(title="Chunk Size") - group: str = Field(title="Group") - department: str = Field(title="Department") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + use_published: bool = SettingsField(title="Use Published scene") + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + group: str = SettingsField(title="Group") + department: str = SettingsField(title="Department") class AfterEffectsSubmitDeadlineModel(BaseSettingsModel): """After Effects deadline submitter settings.""" - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - use_published: bool = Field(title="Use Published scene") - priority: int = Field(title="Priority") - chunk_size: int = Field(title="Chunk Size") - group: str = Field(title="Group") - department: str = Field(title="Department") - multiprocess: bool = Field(title="Optional") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + use_published: bool = SettingsField(title="Use Published scene") + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Chunk Size") + group: str = SettingsField(title="Group") + department: str = SettingsField(title="Department") + multiprocess: bool = SettingsField(title="Optional") class CelactionSubmitDeadlineModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - deadline_department: str = Field("", title="Deadline apartment") - deadline_priority: int = Field(50, title="Deadline priority") - deadline_pool: str = Field("", title="Deadline pool") - deadline_pool_secondary: str = Field("", title="Deadline pool (secondary)") - deadline_group: str = Field("", title="Deadline Group") - deadline_chunk_size: int = Field(10, title="Deadline Chunk size") - deadline_job_delay: str = Field( + enabled: bool = SettingsField(True, title="Enabled") + deadline_department: str = SettingsField("", title="Deadline apartment") + deadline_priority: int = SettingsField(50, title="Deadline priority") + deadline_pool: str = SettingsField("", title="Deadline pool") + deadline_pool_secondary: str = SettingsField( + "", title="Deadline pool (secondary)" + ) + deadline_group: str = SettingsField("", title="Deadline Group") + deadline_chunk_size: int = SettingsField(10, title="Deadline Chunk size") + deadline_job_delay: str = SettingsField( "", title="Delay job (timecode dd:hh:mm:ss)" ) class BlenderSubmitDeadlineModel(BaseSettingsModel): - enabled: bool = Field(True) - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - use_published: bool = Field(title="Use Published scene") - priority: int = Field(title="Priority") - chunk_size: int = Field(title="Frame per Task") - group: str = Field("", title="Group Name") - job_delay: str = Field("", title="Delay job (timecode dd:hh:mm:ss)") + enabled: bool = SettingsField(True) + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + use_published: bool = SettingsField(title="Use Published scene") + priority: int = SettingsField(title="Priority") + chunk_size: int = SettingsField(title="Frame per Task") + group: str = SettingsField("", title="Group Name") + job_delay: str = SettingsField( + "", title="Delay job (timecode dd:hh:mm:ss)" + ) class AOVFilterSubmodel(BaseSettingsModel): _layout = "expanded" - name: str = Field(title="Host") - value: list[str] = Field( + name: str = SettingsField(title="Host") + value: list[str] = SettingsField( default_factory=list, title="AOV regex" ) @@ -248,29 +265,29 @@ class AOVFilterSubmodel(BaseSettingsModel): class ProcessCacheJobFarmModel(BaseSettingsModel): """Process submitted job on farm.""" - enabled: bool = Field(title="Enabled") - deadline_department: str = Field(title="Department") - deadline_pool: str = Field(title="Pool") - deadline_group: str = Field(title="Group") - deadline_chunk_size: int = Field(title="Chunk Size") - deadline_priority: int = Field(title="Priority") + enabled: bool = SettingsField(title="Enabled") + deadline_department: str = SettingsField(title="Department") + deadline_pool: str = SettingsField(title="Pool") + deadline_group: str = SettingsField(title="Group") + deadline_chunk_size: int = SettingsField(title="Chunk Size") + deadline_priority: int = SettingsField(title="Priority") class ProcessSubmittedJobOnFarmModel(BaseSettingsModel): """Process submitted job on farm.""" - enabled: bool = Field(title="Enabled") - deadline_department: str = Field(title="Department") - deadline_pool: str = Field(title="Pool") - deadline_group: str = Field(title="Group") - deadline_chunk_size: int = Field(title="Chunk Size") - deadline_priority: int = Field(title="Priority") - publishing_script: str = Field(title="Publishing script path") - skip_integration_repre_list: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + deadline_department: str = SettingsField(title="Department") + deadline_pool: str = SettingsField(title="Pool") + deadline_group: str = SettingsField(title="Group") + deadline_chunk_size: int = SettingsField(title="Chunk Size") + deadline_priority: int = SettingsField(title="Priority") + publishing_script: str = SettingsField(title="Publishing script path") + skip_integration_repre_list: list[str] = SettingsField( default_factory=list, title="Skip integration of representation with ext" ) - aov_filter: list[AOVFilterSubmodel] = Field( + aov_filter: list[AOVFilterSubmodel] = SettingsField( default_factory=list, title="Reviewable products filter", ) @@ -282,41 +299,44 @@ class ProcessSubmittedJobOnFarmModel(BaseSettingsModel): class PublishPluginsModel(BaseSettingsModel): - CollectDeadlinePools: CollectDeadlinePoolsModel = Field( + CollectDeadlinePools: CollectDeadlinePoolsModel = SettingsField( default_factory=CollectDeadlinePoolsModel, title="Default Pools") - ValidateExpectedFiles: ValidateExpectedFilesModel = Field( + ValidateExpectedFiles: ValidateExpectedFilesModel = SettingsField( default_factory=ValidateExpectedFilesModel, title="Validate Expected Files" ) - MayaSubmitDeadline: MayaSubmitDeadlineModel = Field( + MayaSubmitDeadline: MayaSubmitDeadlineModel = SettingsField( default_factory=MayaSubmitDeadlineModel, title="Maya Submit to deadline") - MaxSubmitDeadline: MaxSubmitDeadlineModel = Field( + MaxSubmitDeadline: MaxSubmitDeadlineModel = SettingsField( default_factory=MaxSubmitDeadlineModel, title="Max Submit to deadline") - FusionSubmitDeadline: FusionSubmitDeadlineModel = Field( + FusionSubmitDeadline: FusionSubmitDeadlineModel = SettingsField( default_factory=FusionSubmitDeadlineModel, title="Fusion submit to Deadline") - NukeSubmitDeadline: NukeSubmitDeadlineModel = Field( + NukeSubmitDeadline: NukeSubmitDeadlineModel = SettingsField( default_factory=NukeSubmitDeadlineModel, title="Nuke Submit to deadline") - HarmonySubmitDeadline: HarmonySubmitDeadlineModel = Field( + HarmonySubmitDeadline: HarmonySubmitDeadlineModel = SettingsField( default_factory=HarmonySubmitDeadlineModel, title="Harmony Submit to deadline") - AfterEffectsSubmitDeadline: AfterEffectsSubmitDeadlineModel = Field( - default_factory=AfterEffectsSubmitDeadlineModel, - title="After Effects to deadline") - CelactionSubmitDeadline: CelactionSubmitDeadlineModel = Field( + AfterEffectsSubmitDeadline: AfterEffectsSubmitDeadlineModel = ( + SettingsField( + default_factory=AfterEffectsSubmitDeadlineModel, + title="After Effects to deadline" + ) + ) + CelactionSubmitDeadline: CelactionSubmitDeadlineModel = SettingsField( default_factory=CelactionSubmitDeadlineModel, title="Celaction Submit Deadline") - BlenderSubmitDeadline: BlenderSubmitDeadlineModel = Field( + BlenderSubmitDeadline: BlenderSubmitDeadlineModel = SettingsField( default_factory=BlenderSubmitDeadlineModel, title="Blender Submit Deadline") - ProcessSubmittedCacheJobOnFarm: ProcessCacheJobFarmModel = Field( + ProcessSubmittedCacheJobOnFarm: ProcessCacheJobFarmModel = SettingsField( default_factory=ProcessCacheJobFarmModel, title="Process submitted cache Job on farm.") - ProcessSubmittedJobOnFarm: ProcessSubmittedJobOnFarmModel = Field( + ProcessSubmittedJobOnFarm: ProcessSubmittedJobOnFarmModel = SettingsField( default_factory=ProcessSubmittedJobOnFarmModel, title="Process submitted job on farm.") diff --git a/server_addon/flame/server/settings/create_plugins.py b/server_addon/flame/server/settings/create_plugins.py index 374a7368d2..44fb8a2e91 100644 --- a/server_addon/flame/server/settings/create_plugins.py +++ b/server_addon/flame/server/settings/create_plugins.py @@ -1,95 +1,94 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class CreateShotClipModel(BaseSettingsModel): - hierarchy: str = Field( + hierarchy: str = SettingsField( "shot", title="Shot parent hierarchy", section="Shot Hierarchy And Rename Settings" ) - useShotName: bool = Field( + useShotName: bool = SettingsField( True, title="Use Shot Name", ) - clipRename: bool = Field( + clipRename: bool = SettingsField( False, title="Rename clips", ) - clipName: str = Field( + clipName: str = SettingsField( "{sequence}{shot}", title="Clip name template" ) - segmentIndex: bool = Field( + segmentIndex: bool = SettingsField( True, title="Accept segment order" ) - countFrom: int = Field( + countFrom: int = SettingsField( 10, title="Count sequence from" ) - countSteps: int = Field( + countSteps: int = SettingsField( 10, title="Stepping number" ) - folder: str = Field( + folder: str = SettingsField( "shots", title="{folder}", section="Shot Template Keywords" ) - episode: str = Field( + episode: str = SettingsField( "ep01", title="{episode}" ) - sequence: str = Field( + sequence: str = SettingsField( "a", title="{sequence}" ) - track: str = Field( + track: str = SettingsField( "{_track_}", title="{track}" ) - shot: str = Field( + shot: str = SettingsField( "####", title="{shot}" ) - vSyncOn: bool = Field( + vSyncOn: bool = SettingsField( False, title="Enable Vertical Sync", section="Vertical Synchronization Of Attributes" ) - workfileFrameStart: int = Field( + workfileFrameStart: int = SettingsField( 1001, title="Workfiles Start Frame", section="Shot Attributes" ) - handleStart: int = Field( + handleStart: int = SettingsField( 10, title="Handle start (head)" ) - handleEnd: int = Field( + handleEnd: int = SettingsField( 10, title="Handle end (tail)" ) - includeHandles: bool = Field( + includeHandles: bool = SettingsField( False, title="Enable handles including" ) - retimedHandles: bool = Field( + retimedHandles: bool = SettingsField( True, title="Enable retimed handles" ) - retimedFramerange: bool = Field( + retimedFramerange: bool = SettingsField( True, title="Enable retimed shot frameranges" ) class CreatePuginsModel(BaseSettingsModel): - CreateShotClip: CreateShotClipModel = Field( + CreateShotClip: CreateShotClipModel = SettingsField( default_factory=CreateShotClipModel, title="Create Shot Clip" ) diff --git a/server_addon/flame/server/settings/imageio.py b/server_addon/flame/server/settings/imageio.py index ef1e4721d1..3f6ec31ef4 100644 --- a/server_addon/flame/server/settings/imageio.py +++ b/server_addon/flame/server/settings/imageio.py @@ -1,17 +1,21 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from pydantic import validator +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + ensure_unique_names, +) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -23,24 +27,24 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ImageIORemappingRulesModel(BaseSettingsModel): - host_native_name: str = Field( + host_native_name: str = SettingsField( title="Application native colorspace name" ) - ocio_name: str = Field(title="OCIO colorspace name") + ocio_name: str = SettingsField(title="OCIO colorspace name") class ImageIORemappingModel(BaseSettingsModel): - rules: list[ImageIORemappingRulesModel] = Field( + rules: list[ImageIORemappingRulesModel] = SettingsField( default_factory=list ) class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) @@ -49,30 +53,30 @@ class ImageIOConfigModel(BaseSettingsModel): class ProfileNamesMappingInputsModel(BaseSettingsModel): _layout = "expanded" - flameName: str = Field("", title="Flame name") - ocioName: str = Field("", title="OCIO name") + flameName: str = SettingsField("", title="Flame name") + ocioName: str = SettingsField("", title="OCIO name") class ProfileNamesMappingModel(BaseSettingsModel): _layout = "expanded" - inputs: list[ProfileNamesMappingInputsModel] = Field( + inputs: list[ProfileNamesMappingInputsModel] = SettingsField( default_factory=list, title="Profile names mapping" ) class ImageIOProjectModel(BaseSettingsModel): - colourPolicy: str = Field( + colourPolicy: str = SettingsField( "ACES 1.1", title="Colour Policy (name or path)", section="Project" ) - frameDepth: str = Field( + frameDepth: str = SettingsField( "16-bit fp", title="Image Depth" ) - fieldDominance: str = Field( + fieldDominance: str = SettingsField( "PROGRESSIVE", title="Field Dominance" ) @@ -80,18 +84,18 @@ class ImageIOProjectModel(BaseSettingsModel): class FlameImageIOModel(BaseSettingsModel): _isGroup = True - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - remapping: ImageIORemappingModel = Field( + remapping: ImageIORemappingModel = SettingsField( title="Remapping colorspace names", default_factory=ImageIORemappingModel ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) @@ -99,11 +103,11 @@ class FlameImageIOModel(BaseSettingsModel): # inconsistency with v3 settings and harder conversion handling # - it can be moved back but keep in mind that it must be handled in v3 # conversion script too - project: ImageIOProjectModel = Field( + project: ImageIOProjectModel = SettingsField( default_factory=ImageIOProjectModel, title="Project" ) - profilesMapping: ProfileNamesMappingModel = Field( + profilesMapping: ProfileNamesMappingModel = SettingsField( default_factory=ProfileNamesMappingModel, title="Profile names mapping" ) diff --git a/server_addon/flame/server/settings/loader_plugins.py b/server_addon/flame/server/settings/loader_plugins.py index 6c27b926c2..e616f442b5 100644 --- a/server_addon/flame/server/settings/loader_plugins.py +++ b/server_addon/flame/server/settings/loader_plugins.py @@ -1,60 +1,64 @@ -from ayon_server.settings import Field, BaseSettingsModel +from ayon_server.settings import SettingsField, BaseSettingsModel class LoadClipModel(BaseSettingsModel): - enabled: bool = Field(True) + enabled: bool = SettingsField(True) - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - reel_group_name: str = Field( + reel_group_name: str = SettingsField( "OpenPype_Reels", title="Reel group name" ) - reel_name: str = Field( + reel_name: str = SettingsField( "Loaded", title="Reel name" ) - clip_name_template: str = Field( + clip_name_template: str = SettingsField( "{folder[name]}_{product[name]}<_{output}>", title="Clip name template" ) - layer_rename_template: str = Field("", title="Layer name template") - layer_rename_patterns: list[str] = Field( + layer_rename_template: str = SettingsField( + "", title="Layer name template" + ) + layer_rename_patterns: list[str] = SettingsField( default_factory=list, title="Layer rename patters", ) class LoadClipBatchModel(BaseSettingsModel): - enabled: bool = Field(True) - product_types: list[str] = Field( + enabled: bool = SettingsField(True) + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - reel_name: str = Field( + reel_name: str = SettingsField( "OP_LoadedReel", title="Reel name" ) - clip_name_template: str = Field( + clip_name_template: str = SettingsField( "{batch}_{folder[name]}_{product[name]}<_{output}>", title="Clip name template" ) - layer_rename_template: str = Field("", title="Layer name template") - layer_rename_patterns: list[str] = Field( + layer_rename_template: str = SettingsField( + "", title="Layer name template" + ) + layer_rename_patterns: list[str] = SettingsField( default_factory=list, title="Layer rename patters", ) class LoaderPluginsModel(BaseSettingsModel): - LoadClip: LoadClipModel = Field( + LoadClip: LoadClipModel = SettingsField( default_factory=LoadClipModel, title="Load Clip" ) - LoadClipBatch: LoadClipBatchModel = Field( + LoadClipBatch: LoadClipBatchModel = SettingsField( default_factory=LoadClipBatchModel, title="Load as clip to current batch" ) diff --git a/server_addon/flame/server/settings/main.py b/server_addon/flame/server/settings/main.py index f28de6641b..047f5af287 100644 --- a/server_addon/flame/server/settings/main.py +++ b/server_addon/flame/server/settings/main.py @@ -1,4 +1,4 @@ -from ayon_server.settings import Field, BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import FlameImageIOModel, DEFAULT_IMAGEIO_SETTINGS from .create_plugins import CreatePuginsModel, DEFAULT_CREATE_SETTINGS @@ -7,19 +7,19 @@ from .loader_plugins import LoaderPluginsModel, DEFAULT_LOADER_SETTINGS class FlameSettings(BaseSettingsModel): - imageio: FlameImageIOModel = Field( + imageio: FlameImageIOModel = SettingsField( default_factory=FlameImageIOModel, title="Color Management (ImageIO)" ) - create: CreatePuginsModel = Field( + create: CreatePuginsModel = SettingsField( default_factory=CreatePuginsModel, title="Create plugins" ) - publish: PublishPuginsModel = Field( + publish: PublishPuginsModel = SettingsField( default_factory=PublishPuginsModel, title="Publish plugins" ) - load: LoaderPluginsModel = Field( + load: LoaderPluginsModel = SettingsField( default_factory=LoaderPluginsModel, title="Loader plugins" ) diff --git a/server_addon/flame/server/settings/publish_plugins.py b/server_addon/flame/server/settings/publish_plugins.py index ea7f109f73..2c21034c44 100644 --- a/server_addon/flame/server/settings/publish_plugins.py +++ b/server_addon/flame/server/settings/publish_plugins.py @@ -1,10 +1,14 @@ -from ayon_server.settings import Field, BaseSettingsModel, task_types_enum +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + task_types_enum, +) class XMLPresetAttrsFromCommentsModel(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Attribute name") - type: str = Field( + name: str = SettingsField("", title="Attribute name") + type: str = SettingsField( default_factory=str, title="Attribute type", enum_resolver=lambda: ["number", "float", "string"] @@ -13,13 +17,13 @@ class XMLPresetAttrsFromCommentsModel(BaseSettingsModel): class AddTasksModel(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Task name") - type: str = Field( + name: str = SettingsField("", title="Task name") + type: str = SettingsField( default_factory=str, title="Task type", enum_resolver=task_types_enum ) - create_batch_group: bool = Field( + create_batch_group: bool = SettingsField( True, title="Create batch group" ) @@ -28,11 +32,11 @@ class AddTasksModel(BaseSettingsModel): class CollectTimelineInstancesModel(BaseSettingsModel): _isGroup = True - xml_preset_attrs_from_comments: list[XMLPresetAttrsFromCommentsModel] = Field( + xml_preset_attrs_from_comments: list[XMLPresetAttrsFromCommentsModel] = SettingsField( default_factory=list, title="XML presets attributes parsable from segment comments" ) - add_tasks: list[AddTasksModel] = Field( + add_tasks: list[AddTasksModel] = SettingsField( default_factory=list, title="Add tasks" ) @@ -41,22 +45,22 @@ class CollectTimelineInstancesModel(BaseSettingsModel): class ExportPresetsMappingModel(BaseSettingsModel): _layout = "expanded" - name: str = Field( + name: str = SettingsField( ..., title="Name" ) - active: bool = Field(True, title="Is active") - export_type: str = Field( + active: bool = SettingsField(True, title="Is active") + export_type: str = SettingsField( "File Sequence", title="Eport clip type", enum_resolver=lambda: ["Movie", "File Sequence", "Sequence Publish"] ) - ext: str = Field("exr", title="Output extension") - xml_preset_file: str = Field( + ext: str = SettingsField("exr", title="Output extension") + xml_preset_file: str = SettingsField( "OpenEXR (16-bit fp DWAA).xml", title="XML preset file (with ext)" ) - colorspace_out: str = Field( + colorspace_out: str = SettingsField( "ACES - ACEScg", title="Output color (imageio)" ) @@ -65,31 +69,31 @@ class ExportPresetsMappingModel(BaseSettingsModel): # created inconsistency with v3 settings and harder conversion handling # - it can be moved back but keep in mind that it must be handled in v3 # conversion script too - xml_preset_dir: str = Field( + xml_preset_dir: str = SettingsField( "", title="XML preset directory" ) - parsed_comment_attrs: bool = Field( + parsed_comment_attrs: bool = SettingsField( True, title="Parsed comment attributes" ) - representation_add_range: bool = Field( + representation_add_range: bool = SettingsField( True, title="Add range to representation name" ) - representation_tags: list[str] = Field( + representation_tags: list[str] = SettingsField( default_factory=list, title="Representation tags" ) - load_to_batch_group: bool = Field( + load_to_batch_group: bool = SettingsField( True, title="Load to batch group reel" ) - batch_group_loader_name: str = Field( + batch_group_loader_name: str = SettingsField( "LoadClipBatch", title="Use loader name" ) - filter_path_regex: str = Field( + filter_path_regex: str = SettingsField( ".*", title="Regex in clip path" ) @@ -98,35 +102,35 @@ class ExportPresetsMappingModel(BaseSettingsModel): class ExtractProductResourcesModel(BaseSettingsModel): _isGroup = True - keep_original_representation: bool = Field( + keep_original_representation: bool = SettingsField( False, title="Publish clip's original media" ) - export_presets_mapping: list[ExportPresetsMappingModel] = Field( + export_presets_mapping: list[ExportPresetsMappingModel] = SettingsField( default_factory=list, title="Export presets mapping" ) class IntegrateBatchGroupModel(BaseSettingsModel): - enabled: bool = Field( + enabled: bool = SettingsField( False, title="Enabled" ) class PublishPuginsModel(BaseSettingsModel): - CollectTimelineInstances: CollectTimelineInstancesModel = Field( + CollectTimelineInstances: CollectTimelineInstancesModel = SettingsField( default_factory=CollectTimelineInstancesModel, title="Collect Timeline Instances" ) - ExtractProductResources: ExtractProductResourcesModel = Field( + ExtractProductResources: ExtractProductResourcesModel = SettingsField( default_factory=ExtractProductResourcesModel, title="Extract Product Resources" ) - IntegrateBatchGroup: IntegrateBatchGroupModel = Field( + IntegrateBatchGroup: IntegrateBatchGroupModel = SettingsField( default_factory=IntegrateBatchGroupModel, title="IntegrateBatchGroup" ) diff --git a/server_addon/fusion/server/imageio.py b/server_addon/fusion/server/imageio.py index fe867af424..e93dc2ae00 100644 --- a/server_addon/fusion/server/imageio.py +++ b/server_addon/fusion/server/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class FusionImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index bf295f3064..b157ce9e40 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -1,15 +1,15 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ) from .imageio import FusionImageIOModel class CopyFusionSettingsModel(BaseSettingsModel): - copy_path: str = Field("", title="Local Fusion profile directory") - copy_status: bool = Field(title="Copy profile on first launch") - force_sync: bool = Field(title="Resync profile on each launch") + copy_path: str = SettingsField("", title="Local Fusion profile directory") + copy_status: bool = SettingsField(title="Copy profile on first launch") + force_sync: bool = SettingsField(title="Resync profile on each launch") def _create_saver_instance_attributes_enum(): @@ -45,40 +45,40 @@ def _frame_range_options_enum(): class CreateSaverPluginModel(BaseSettingsModel): _isGroup = True - temp_rendering_path_template: str = Field( + temp_rendering_path_template: str = SettingsField( "", title="Temporary rendering path template" ) - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( default_factory=list, title="Default variants" ) - instance_attributes: list[str] = Field( + instance_attributes: list[str] = SettingsField( default_factory=list, enum_resolver=_create_saver_instance_attributes_enum, title="Instance attributes" ) - output_formats: list[str] = Field( + output_formats: list[str] = SettingsField( default_factory=list, title="Output formats" ) class HookOptionalModel(BaseSettingsModel): - enabled: bool = Field( + enabled: bool = SettingsField( True, title="Enabled" ) class HooksModel(BaseSettingsModel): - InstallPySideToFusion: HookOptionalModel = Field( + InstallPySideToFusion: HookOptionalModel = SettingsField( default_factory=HookOptionalModel, title="Install PySide2" ) class CreateSaverModel(CreateSaverPluginModel): - default_frame_range_option: str = Field( + default_frame_range_option: str = SettingsField( default="asset_db", enum_resolver=_frame_range_options_enum, title="Default frame range source" @@ -86,17 +86,17 @@ class CreateSaverModel(CreateSaverPluginModel): class CreateImageSaverModel(CreateSaverPluginModel): - default_frame: int = Field( + default_frame: int = SettingsField( 0, title="Default rendered frame" ) class CreatPluginsModel(BaseSettingsModel): - CreateSaver: CreateSaverModel = Field( + CreateSaver: CreateSaverModel = SettingsField( default_factory=CreateSaverModel, title="Create Saver", description="Creator for render product type (eg. sequence)" ) - CreateImageSaver: CreateImageSaverModel = Field( + CreateImageSaver: CreateImageSaverModel = SettingsField( default_factory=CreateImageSaverModel, title="Create Image Saver", description="Creator for image product type (eg. single)" @@ -104,19 +104,19 @@ class CreatPluginsModel(BaseSettingsModel): class FusionSettings(BaseSettingsModel): - imageio: FusionImageIOModel = Field( + imageio: FusionImageIOModel = SettingsField( default_factory=FusionImageIOModel, title="Color Management (ImageIO)" ) - copy_fusion_settings: CopyFusionSettingsModel = Field( + copy_fusion_settings: CopyFusionSettingsModel = SettingsField( default_factory=CopyFusionSettingsModel, title="Local Fusion profile settings" ) - hooks: HooksModel = Field( + hooks: HooksModel = SettingsField( default_factory=HooksModel, title="Hooks" ) - create: CreatPluginsModel = Field( + create: CreatPluginsModel = SettingsField( default_factory=CreatPluginsModel, title="Creator plugins" ) diff --git a/server_addon/harmony/server/settings/imageio.py b/server_addon/harmony/server/settings/imageio.py index 4e01fae3d4..a4b481f91f 100644 --- a/server_addon/harmony/server/settings/imageio.py +++ b/server_addon/harmony/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,21 +35,21 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ImageIORemappingRulesModel(BaseSettingsModel): - host_native_name: str = Field( + host_native_name: str = SettingsField( title="Application native colorspace name" ) - ocio_name: str = Field(title="OCIO colorspace name") + ocio_name: str = SettingsField(title="OCIO colorspace name") class HarmonyImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/harmony/server/settings/main.py b/server_addon/harmony/server/settings/main.py index 0936bc1fc7..9c780b63c2 100644 --- a/server_addon/harmony/server/settings/main.py +++ b/server_addon/harmony/server/settings/main.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import HarmonyImageIOModel from .publish_plugins import HarmonyPublishPlugins @@ -8,11 +7,11 @@ from .publish_plugins import HarmonyPublishPlugins class HarmonySettings(BaseSettingsModel): """Harmony Project Settings.""" - imageio: HarmonyImageIOModel = Field( + imageio: HarmonyImageIOModel = SettingsField( default_factory=HarmonyImageIOModel, title="OCIO config" ) - publish: HarmonyPublishPlugins = Field( + publish: HarmonyPublishPlugins = SettingsField( default_factory=HarmonyPublishPlugins, title="Publish plugins" ) diff --git a/server_addon/harmony/server/settings/publish_plugins.py b/server_addon/harmony/server/settings/publish_plugins.py index bdaec2bbd4..c9e7c515e4 100644 --- a/server_addon/harmony/server/settings/publish_plugins.py +++ b/server_addon/harmony/server/settings/publish_plugins.py @@ -1,12 +1,10 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class CollectPalettesPlugin(BaseSettingsModel): """Set regular expressions to filter triggering on specific task names. '.*' means on all.""" # noqa - allowed_tasks: list[str] = Field( + allowed_tasks: list[str] = SettingsField( default_factory=list, title="Allowed tasks" ) @@ -16,16 +14,16 @@ class ValidateAudioPlugin(BaseSettingsModel): """Check if scene contains audio track.""" # _isGroup = True enabled: bool = True - optional: bool = Field(False, title="Optional") - active: bool = Field(True, title="Active") + optional: bool = SettingsField(False, title="Optional") + active: bool = SettingsField(True, title="Active") class ValidateContainersPlugin(BaseSettingsModel): """Check if loaded container is scene are latest versions.""" _isGroup = True enabled: bool = True - optional: bool = Field(False, title="Optional") - active: bool = Field(True, title="Active") + optional: bool = SettingsField(False, title="Optional") + active: bool = SettingsField(True, title="Active") class ValidateSceneSettingsPlugin(BaseSettingsModel): @@ -34,20 +32,20 @@ class ValidateSceneSettingsPlugin(BaseSettingsModel): or task names.""" _isGroup = True enabled: bool = True - optional: bool = Field(False, title="Optional") - active: bool = Field(True, title="Active") + optional: bool = SettingsField(False, title="Optional") + active: bool = SettingsField(True, title="Active") - frame_check_filter: list[str] = Field( + frame_check_filter: list[str] = SettingsField( default_factory=list, title="Skip Frame check for Assets with name containing" ) - skip_resolution_check: list[str] = Field( + skip_resolution_check: list[str] = SettingsField( default_factory=list, title="Skip Resolution Check for Tasks" ) - skip_timelines_check: list[str] = Field( + skip_timelines_check: list[str] = SettingsField( default_factory=list, title="Skip Timeline Check for Tasks" ) @@ -55,22 +53,22 @@ class ValidateSceneSettingsPlugin(BaseSettingsModel): class HarmonyPublishPlugins(BaseSettingsModel): - CollectPalettes: CollectPalettesPlugin = Field( + CollectPalettes: CollectPalettesPlugin = SettingsField( title="Collect Palettes", default_factory=CollectPalettesPlugin, ) - ValidateAudio: ValidateAudioPlugin = Field( + ValidateAudio: ValidateAudioPlugin = SettingsField( title="Validate Audio", default_factory=ValidateAudioPlugin, ) - ValidateContainers: ValidateContainersPlugin = Field( + ValidateContainers: ValidateContainersPlugin = SettingsField( title="Validate Containers", default_factory=ValidateContainersPlugin, ) - ValidateSceneSettings: ValidateSceneSettingsPlugin = Field( + ValidateSceneSettings: ValidateSceneSettingsPlugin = SettingsField( title="Validate Scene Settings", default_factory=ValidateSceneSettingsPlugin, ) diff --git a/server_addon/hiero/server/settings/common.py b/server_addon/hiero/server/settings/common.py index eb4791f93e..7b5e4390c5 100644 --- a/server_addon/hiero/server/settings/common.py +++ b/server_addon/hiero/server/settings/common.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.types import ( ColorRGBA_float, ColorRGB_uint8 @@ -9,16 +8,16 @@ from ayon_server.types import ( class Vector2d(BaseSettingsModel): _layout = "compact" - x: float = Field(1.0, title="X") - y: float = Field(1.0, title="Y") + x: float = SettingsField(1.0, title="X") + y: float = SettingsField(1.0, title="Y") class Vector3d(BaseSettingsModel): _layout = "compact" - x: float = Field(1.0, title="X") - y: float = Field(1.0, title="Y") - z: float = Field(1.0, title="Z") + x: float = SettingsField(1.0, title="X") + y: float = SettingsField(1.0, title="Y") + z: float = SettingsField(1.0, title="Z") def formatable_knob_type_enum(): @@ -34,12 +33,12 @@ def formatable_knob_type_enum(): class Formatable(BaseSettingsModel): _layout = "compact" - template: str = Field( + template: str = SettingsField( "", placeholder="""{{key}} or {{key}};{{key}}""", title="Template" ) - to_type: str = Field( + to_type: str = SettingsField( "Text", title="To Knob type", enum_resolver=formatable_knob_type_enum, @@ -62,37 +61,37 @@ knob_types_enum = [ class KnobModel(BaseSettingsModel): _layout = "expanded" - type: str = Field( + type: str = SettingsField( title="Type", description="Switch between different knob types", enum_resolver=lambda: knob_types_enum, conditionalEnum=True ) - name: str = Field( + name: str = SettingsField( title="Name", placeholder="Name" ) - text: str = Field("", title="Value") - color_gui: ColorRGB_uint8 = Field( + text: str = SettingsField("", title="Value") + color_gui: ColorRGB_uint8 = SettingsField( (0, 0, 255), title="RGB Uint8", ) - boolean: bool = Field(False, title="Value") - number: int = Field(0, title="Value") - decimal_number: float = Field(0.0, title="Value") - vector_2d: Vector2d = Field( + boolean: bool = SettingsField(False, title="Value") + number: int = SettingsField(0, title="Value") + decimal_number: float = SettingsField(0.0, title="Value") + vector_2d: Vector2d = SettingsField( default_factory=Vector2d, title="Value" ) - vector_3d: Vector3d = Field( + vector_3d: Vector3d = SettingsField( default_factory=Vector3d, title="Value" ) - color: ColorRGBA_float = Field( + color: ColorRGBA_float = SettingsField( (0.0, 0.0, 1.0, 1.0), title="RGBA Float" ) - formatable: Formatable = Field( + formatable: Formatable = SettingsField( default_factory=Formatable, title="Value" ) diff --git a/server_addon/hiero/server/settings/create_plugins.py b/server_addon/hiero/server/settings/create_plugins.py index daec4a7cea..80e0b67182 100644 --- a/server_addon/hiero/server/settings/create_plugins.py +++ b/server_addon/hiero/server/settings/create_plugins.py @@ -1,75 +1,74 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class CreateShotClipModels(BaseSettingsModel): - hierarchy: str = Field( + hierarchy: str = SettingsField( "{folder}/{sequence}", title="Shot parent hierarchy", section="Shot Hierarchy And Rename Settings" ) - clipRename: bool = Field( + clipRename: bool = SettingsField( True, title="Rename clips" ) - clipName: str = Field( + clipName: str = SettingsField( "{track}{sequence}{shot}", title="Clip name template" ) - countFrom: int = Field( + countFrom: int = SettingsField( 10, title="Count sequence from" ) - countSteps: int = Field( + countSteps: int = SettingsField( 10, title="Stepping number" ) - folder: str = Field( + folder: str = SettingsField( "shots", title="{folder}", section="Shot Template Keywords" ) - episode: str = Field( + episode: str = SettingsField( "ep01", title="{episode}" ) - sequence: str = Field( + sequence: str = SettingsField( "sq01", title="{sequence}" ) - track: str = Field( + track: str = SettingsField( "{_track_}", title="{track}" ) - shot: str = Field( + shot: str = SettingsField( "sh###", title="{shot}" ) - vSyncOn: bool = Field( + vSyncOn: bool = SettingsField( False, title="Enable Vertical Sync", section="Vertical Synchronization Of Attributes" ) - workfileFrameStart: int = Field( + workfileFrameStart: int = SettingsField( 1001, title="Workfiles Start Frame", section="Shot Attributes" ) - handleStart: int = Field( + handleStart: int = SettingsField( 10, title="Handle start (head)" ) - handleEnd: int = Field( + handleEnd: int = SettingsField( 10, title="Handle end (tail)" ) class CreatorPluginsSettings(BaseSettingsModel): - CreateShotClip: CreateShotClipModels = Field( + CreateShotClip: CreateShotClipModels = SettingsField( default_factory=CreateShotClipModels, title="Create Shot Clip" ) diff --git a/server_addon/hiero/server/settings/filters.py b/server_addon/hiero/server/settings/filters.py index 7e2702b3b7..9642f93f7e 100644 --- a/server_addon/hiero/server/settings/filters.py +++ b/server_addon/hiero/server/settings/filters.py @@ -1,17 +1,21 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from pydantic import validator +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + ensure_unique_names, +) class PublishGUIFilterItemModel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: bool = Field(True, title="Active") + name: str = SettingsField(title="Name") + value: bool = SettingsField(True, title="Active") class PublishGUIFiltersModel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: list[PublishGUIFilterItemModel] = Field(default_factory=list) + name: str = SettingsField(title="Name") + value: list[PublishGUIFilterItemModel] = SettingsField(default_factory=list) @validator("value") def validate_unique_outputs(cls, value): diff --git a/server_addon/hiero/server/settings/imageio.py b/server_addon/hiero/server/settings/imageio.py index f2c2728057..f2bc71ac33 100644 --- a/server_addon/hiero/server/settings/imageio.py +++ b/server_addon/hiero/server/settings/imageio.py @@ -1,7 +1,8 @@ -from pydantic import Field, validator +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ensure_unique_names, ) @@ -39,34 +40,34 @@ class WorkfileColorspaceSettings(BaseSettingsModel): thumbnail_name = thumbnailLut """ - ocioConfigName: str = Field( + ocioConfigName: str = SettingsField( title="OpenColorIO Config", description="Switch between OCIO configs", enum_resolver=ocio_configs_switcher_enum, conditionalEnum=True ) - workingSpace: str = Field( + workingSpace: str = SettingsField( title="Working Space" ) - viewerLut: str = Field( + viewerLut: str = SettingsField( title="Viewer" ) - eightBitLut: str = Field( + eightBitLut: str = SettingsField( title="8-bit files" ) - sixteenBitLut: str = Field( + sixteenBitLut: str = SettingsField( title="16-bit files" ) - logLut: str = Field( + logLut: str = SettingsField( title="Log files" ) - floatLut: str = Field( + floatLut: str = SettingsField( title="Float files" ) - thumbnailLut: str = Field( + thumbnailLut: str = SettingsField( title="Thumnails" ) - monitorOutLut: str = Field( + monitorOutLut: str = SettingsField( title="Monitor" ) @@ -74,38 +75,38 @@ class WorkfileColorspaceSettings(BaseSettingsModel): class ClipColorspaceRulesItems(BaseSettingsModel): _layout = "expanded" - regex: str = Field("", title="Regex expression") - colorspace: str = Field("", title="Colorspace") + regex: str = SettingsField("", title="Regex expression") + colorspace: str = SettingsField("", title="Colorspace") class RegexInputsModel(BaseSettingsModel): - inputs: list[ClipColorspaceRulesItems] = Field( + inputs: list[ClipColorspaceRulesItems] = SettingsField( default_factory=list, title="Inputs" ) class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -119,18 +120,18 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ImageIOSettings(BaseSettingsModel): """Hiero color management project settings. """ _isGroup: bool = True - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) - workfile: WorkfileColorspaceSettings = Field( + workfile: WorkfileColorspaceSettings = SettingsField( default_factory=WorkfileColorspaceSettings, title="Workfile" ) @@ -140,7 +141,7 @@ class ImageIOSettings(BaseSettingsModel): - no need for `inputs` middle part. It can stay directly on `regex_inputs` """ - regexInputs: RegexInputsModel = Field( + regexInputs: RegexInputsModel = SettingsField( default_factory=RegexInputsModel, title="Assign colorspace to clips via rules" ) diff --git a/server_addon/hiero/server/settings/loader_plugins.py b/server_addon/hiero/server/settings/loader_plugins.py index 83b3564c2a..b5a81d1ae2 100644 --- a/server_addon/hiero/server/settings/loader_plugins.py +++ b/server_addon/hiero/server/settings/loader_plugins.py @@ -1,23 +1,22 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class LoadClipModel(BaseSettingsModel): - enabled: bool = Field( + enabled: bool = SettingsField( True, title="Enabled" ) - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - clip_name_template: str = Field( + clip_name_template: str = SettingsField( title="Clip name template" ) class LoaderPuginsModel(BaseSettingsModel): - LoadClip: LoadClipModel = Field( + LoadClip: LoadClipModel = SettingsField( default_factory=LoadClipModel, title="Load Clip" ) diff --git a/server_addon/hiero/server/settings/main.py b/server_addon/hiero/server/settings/main.py index 47f8110c22..b170ecafb8 100644 --- a/server_addon/hiero/server/settings/main.py +++ b/server_addon/hiero/server/settings/main.py @@ -1,6 +1,4 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ( ImageIOSettings, @@ -28,28 +26,28 @@ from .filters import PublishGUIFilterItemModel class HieroSettings(BaseSettingsModel): """Nuke addon settings.""" - imageio: ImageIOSettings = Field( + imageio: ImageIOSettings = SettingsField( default_factory=ImageIOSettings, title="Color Management (imageio)", ) - create: CreatorPluginsSettings = Field( + create: CreatorPluginsSettings = SettingsField( default_factory=CreatorPluginsSettings, title="Creator Plugins", ) - load: LoaderPuginsModel = Field( + load: LoaderPuginsModel = SettingsField( default_factory=LoaderPuginsModel, title="Loader plugins" ) - publish: PublishPuginsModel = Field( + publish: PublishPuginsModel = SettingsField( default_factory=PublishPuginsModel, title="Publish plugins" ) - scriptsmenu: ScriptsmenuSettings = Field( + scriptsmenu: ScriptsmenuSettings = SettingsField( default_factory=ScriptsmenuSettings, title="Scripts Menu Definition", ) - filters: list[PublishGUIFilterItemModel] = Field( + filters: list[PublishGUIFilterItemModel] = SettingsField( default_factory=list ) diff --git a/server_addon/hiero/server/settings/publish_plugins.py b/server_addon/hiero/server/settings/publish_plugins.py index f3d1e21fe4..c35c61c332 100644 --- a/server_addon/hiero/server/settings/publish_plugins.py +++ b/server_addon/hiero/server/settings/publish_plugins.py @@ -1,11 +1,14 @@ -from pydantic import Field, validator +from pydantic import validator from ayon_server.settings import ( - BaseSettingsModel, ensure_unique_names, normalize_name + BaseSettingsModel, + SettingsField, + ensure_unique_names, + normalize_name, ) class CollectInstanceVersionModel(BaseSettingsModel): - enabled: bool = Field( + enabled: bool = SettingsField( True, title="Enabled" ) @@ -13,8 +16,8 @@ class CollectInstanceVersionModel(BaseSettingsModel): class CollectClipEffectsDefModel(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Name") - effect_classes: list[str] = Field( + name: str = SettingsField("", title="Name") + effect_classes: list[str] = SettingsField( default_factory=list, title="Effect Classes" ) @@ -25,7 +28,7 @@ class CollectClipEffectsDefModel(BaseSettingsModel): class CollectClipEffectsModel(BaseSettingsModel): - effect_categories: list[CollectClipEffectsDefModel] = Field( + effect_categories: list[CollectClipEffectsDefModel] = SettingsField( default_factory=list, title="Effect Categories" ) @@ -36,22 +39,22 @@ class CollectClipEffectsModel(BaseSettingsModel): class ExtractReviewCutUpVideoModel(BaseSettingsModel): - enabled: bool = Field( + enabled: bool = SettingsField( True, title="Enabled" ) - tags_addition: list[str] = Field( + tags_addition: list[str] = SettingsField( default_factory=list, title="Additional tags" ) class PublishPuginsModel(BaseSettingsModel): - CollectInstanceVersion: CollectInstanceVersionModel = Field( + CollectInstanceVersion: CollectInstanceVersionModel = SettingsField( default_factory=CollectInstanceVersionModel, title="Collect Instance Version" ) - CollectClipEffects: CollectClipEffectsModel = Field( + CollectClipEffects: CollectClipEffectsModel = SettingsField( default_factory=CollectClipEffectsModel, title="Collect Clip Effects" ) @@ -59,7 +62,7 @@ class PublishPuginsModel(BaseSettingsModel): Rename class name and plugin name to match title (it makes more sense) """ - ExtractReviewCutUpVideo: ExtractReviewCutUpVideoModel = Field( + ExtractReviewCutUpVideo: ExtractReviewCutUpVideoModel = SettingsField( default_factory=ExtractReviewCutUpVideoModel, title="Exctract Review Trim" ) diff --git a/server_addon/hiero/server/settings/scriptsmenu.py b/server_addon/hiero/server/settings/scriptsmenu.py index ea898dd7ff..a627da9643 100644 --- a/server_addon/hiero/server/settings/scriptsmenu.py +++ b/server_addon/hiero/server/settings/scriptsmenu.py @@ -1,16 +1,15 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class ScriptsmenuSubmodel(BaseSettingsModel): """Item Definition""" _isGroup = True - type: str = Field(title="Type") - command: str = Field(title="Command") - sourcetype: str = Field(title="Source Type") - title: str = Field(title="Title") - tooltip: str = Field(title="Tooltip") + type: str = SettingsField(title="Type") + command: str = SettingsField(title="Command") + sourcetype: str = SettingsField(title="Source Type") + title: str = SettingsField(title="Title") + tooltip: str = SettingsField(title="Tooltip") class ScriptsmenuSettings(BaseSettingsModel): @@ -20,8 +19,8 @@ class ScriptsmenuSettings(BaseSettingsModel): """# TODO: enhance settings with host api: - in api rename key `name` to `menu_name` """ - name: str = Field(title="Menu name") - definition: list[ScriptsmenuSubmodel] = Field( + name: str = SettingsField(title="Menu name") + definition: list[ScriptsmenuSubmodel] = SettingsField( default_factory=list, title="Definition", description="Scriptmenu Items Definition") diff --git a/server_addon/houdini/server/settings/create.py b/server_addon/houdini/server/settings/create.py index a5ca4d477b..203ca4f9d6 100644 --- a/server_addon/houdini/server/settings/create.py +++ b/server_addon/houdini/server/settings/create.py @@ -1,92 +1,91 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField # Creator Plugins class CreatorModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + default_variants: list[str] = SettingsField( title="Default Products", default_factory=list, ) class CreateArnoldAssModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + default_variants: list[str] = SettingsField( title="Default Products", default_factory=list, ) - ext: str = Field(Title="Extension") + ext: str = SettingsField(Title="Extension") class CreateStaticMeshModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) - static_mesh_prefix: str = Field("S", title="Static Mesh Prefix") - collision_prefixes: list[str] = Field( + static_mesh_prefix: str = SettingsField("S", title="Static Mesh Prefix") + collision_prefixes: list[str] = SettingsField( default_factory=list, title="Collision Prefixes" ) class CreatePluginsModel(BaseSettingsModel): - CreateAlembicCamera: CreatorModel = Field( + CreateAlembicCamera: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Alembic Camera") - CreateArnoldAss: CreateArnoldAssModel = Field( + CreateArnoldAss: CreateArnoldAssModel = SettingsField( default_factory=CreateArnoldAssModel, title="Create Arnold Ass") - CreateArnoldRop: CreatorModel = Field( + CreateArnoldRop: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Arnold ROP") - CreateCompositeSequence: CreatorModel = Field( + CreateCompositeSequence: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Composite (Image Sequence)") - CreateHDA: CreatorModel = Field( + CreateHDA: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Houdini Digital Asset") - CreateKarmaROP: CreatorModel = Field( + CreateKarmaROP: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Karma ROP") - CreateMantraIFD: CreatorModel = Field( + CreateMantraIFD: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Mantra IFD") - CreateMantraROP: CreatorModel = Field( + CreateMantraROP: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Mantra ROP") - CreatePointCache: CreatorModel = Field( + CreatePointCache: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create PointCache (Abc)") - CreateBGEO: CreatorModel = Field( + CreateBGEO: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create PointCache (Bgeo)") - CreateRedshiftProxy: CreatorModel = Field( + CreateRedshiftProxy: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Redshift Proxy") - CreateRedshiftROP: CreatorModel = Field( + CreateRedshiftROP: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Redshift ROP") - CreateReview: CreatorModel = Field( + CreateReview: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create Review") # "-" is not compatible in the new model - CreateStaticMesh: CreateStaticMeshModel = Field( + CreateStaticMesh: CreateStaticMeshModel = SettingsField( default_factory=CreateStaticMeshModel, title="Create Static Mesh") - CreateUSD: CreatorModel = Field( + CreateUSD: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create USD (experimental)") - CreateUSDRender: CreatorModel = Field( + CreateUSDRender: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create USD render (experimental)") - CreateVDBCache: CreatorModel = Field( + CreateVDBCache: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create VDB Cache") - CreateVrayROP: CreatorModel = Field( + CreateVrayROP: CreatorModel = SettingsField( default_factory=CreatorModel, title="Create VRay ROP") diff --git a/server_addon/houdini/server/settings/general.py b/server_addon/houdini/server/settings/general.py index aee44f1648..b71feae554 100644 --- a/server_addon/houdini/server/settings/general.py +++ b/server_addon/houdini/server/settings/general.py @@ -1,12 +1,11 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class HoudiniVarModel(BaseSettingsModel): _layout = "expanded" - var: str = Field("", title="Var") - value: str = Field("", title="Value") - is_directory: bool = Field(False, title="Treat as directory") + var: str = SettingsField("", title="Var") + value: str = SettingsField("", title="Value") + is_directory: bool = SettingsField(False, title="Treat as directory") class UpdateHoudiniVarcontextModel(BaseSettingsModel): @@ -16,20 +15,20 @@ class UpdateHoudiniVarcontextModel(BaseSettingsModel): it will be ensured the folder exists. """ - enabled: bool = Field(title="Enabled") + enabled: bool = SettingsField(title="Enabled") # TODO this was dynamic dictionary '{var: path}' - houdini_vars: list[HoudiniVarModel] = Field( + houdini_vars: list[HoudiniVarModel] = SettingsField( default_factory=list, title="Houdini Vars" ) class GeneralSettingsModel(BaseSettingsModel): - add_self_publish_button: bool = Field( + add_self_publish_button: bool = SettingsField( False, title="Add Self Publish Button" ) - update_houdini_var_context: UpdateHoudiniVarcontextModel = Field( + update_houdini_var_context: UpdateHoudiniVarcontextModel = SettingsField( default_factory=UpdateHoudiniVarcontextModel, title="Update Houdini Vars on context change" ) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 88aa40ecd6..f4850c5df7 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class HoudiniImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/houdini/server/settings/main.py b/server_addon/houdini/server/settings/main.py index 9cfec54f22..cbb19d15b7 100644 --- a/server_addon/houdini/server/settings/main.py +++ b/server_addon/houdini/server/settings/main.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .general import ( GeneralSettingsModel, DEFAULT_GENERAL_SETTINGS @@ -17,23 +16,23 @@ from .publish import ( class HoudiniSettings(BaseSettingsModel): - general: GeneralSettingsModel = Field( + general: GeneralSettingsModel = SettingsField( default_factory=GeneralSettingsModel, title="General" ) - imageio: HoudiniImageIOModel = Field( + imageio: HoudiniImageIOModel = SettingsField( default_factory=HoudiniImageIOModel, title="Color Management (ImageIO)" ) - shelves: list[ShelvesModel] = Field( + shelves: list[ShelvesModel] = SettingsField( default_factory=list, title="Shelves Manager", ) - create: CreatePluginsModel = Field( + create: CreatePluginsModel = SettingsField( default_factory=CreatePluginsModel, title="Creator Plugins", ) - publish: PublishPluginsModel = Field( + publish: PublishPluginsModel = SettingsField( default_factory=PublishPluginsModel, title="Publish Plugins", ) diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py index f551b3a209..1741568d63 100644 --- a/server_addon/houdini/server/settings/publish.py +++ b/server_addon/houdini/server/settings/publish.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField # Publish Plugins @@ -9,64 +8,64 @@ class CollectAssetHandlesModel(BaseSettingsModel): ignore start and end handles specified in the asset data for publish instances """ - use_asset_handles: bool = Field( + use_asset_handles: bool = SettingsField( title="Use asset handles") class CollectChunkSizeModel(BaseSettingsModel): """Collect Chunk Size.""" - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - chunk_size: int = Field( + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + chunk_size: int = SettingsField( title="Frames Per Task") class ValidateWorkfilePathsModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - node_types: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + node_types: list[str] = SettingsField( default_factory=list, title="Node Types" ) - prohibited_vars: list[str] = Field( + prohibited_vars: list[str] = SettingsField( default_factory=list, title="Prohibited Variables" ) class BasicValidateModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") class PublishPluginsModel(BaseSettingsModel): - CollectAssetHandles: CollectAssetHandlesModel = Field( + CollectAssetHandles: CollectAssetHandlesModel = SettingsField( default_factory=CollectAssetHandlesModel, title="Collect Asset Handles.", section="Collectors" ) - CollectChunkSize: CollectChunkSizeModel = Field( + CollectChunkSize: CollectChunkSizeModel = SettingsField( default_factory=CollectChunkSizeModel, title="Collect Chunk Size." ) - ValidateContainers: BasicValidateModel = Field( + ValidateContainers: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Latest Containers.", section="Validators") - ValidateMeshIsStatic: BasicValidateModel = Field( + ValidateMeshIsStatic: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh is Static.") - ValidateReviewColorspace: BasicValidateModel = Field( + ValidateReviewColorspace: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Review Colorspace.") - ValidateSubsetName: BasicValidateModel = Field( + ValidateSubsetName: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Subset Name.") - ValidateUnrealStaticMeshName: BasicValidateModel = Field( + ValidateUnrealStaticMeshName: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Unreal Static Mesh Name.") - ValidateWorkfilePaths: ValidateWorkfilePathsModel = Field( + ValidateWorkfilePaths: ValidateWorkfilePathsModel = SettingsField( default_factory=ValidateWorkfilePathsModel, title="Validate workfile paths settings.") diff --git a/server_addon/houdini/server/settings/shelves.py b/server_addon/houdini/server/settings/shelves.py index 133c18f77c..f6d7f1d06c 100644 --- a/server_addon/houdini/server/settings/shelves.py +++ b/server_addon/houdini/server/settings/shelves.py @@ -1,37 +1,37 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, + SettingsField, MultiplatformPathModel ) class ShelfToolsModel(BaseSettingsModel): """Name and Script Path are mandatory.""" - label: str = Field(title="Name") - script: str = Field(title="Script Path") - icon: str = Field("", title="Icon Path") - help: str = Field("", title="Help text") + label: str = SettingsField(title="Name") + script: str = SettingsField(title="Script Path") + icon: str = SettingsField("", title="Icon Path") + help: str = SettingsField("", title="Help text") class ShelfDefinitionModel(BaseSettingsModel): _layout = "expanded" - shelf_name: str = Field(title="Shelf name") - tools_list: list[ShelfToolsModel] = Field( + shelf_name: str = SettingsField(title="Shelf name") + tools_list: list[ShelfToolsModel] = SettingsField( default_factory=list, title="Shelf Tools" ) class AddShelfFileModel(BaseSettingsModel): - shelf_set_source_path: MultiplatformPathModel = Field( + shelf_set_source_path: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel, title="Shelf Set Path" ) class AddSetAndDefinitionsModel(BaseSettingsModel): - shelf_set_name: str = Field("", title="Shelf Set Name") - shelf_definition: list[ShelfDefinitionModel] = Field( + shelf_set_name: str = SettingsField("", title="Shelf Set Name") + shelf_definition: list[ShelfDefinitionModel] = SettingsField( default_factory=list, title="Shelves Definitions" ) @@ -51,17 +51,17 @@ def shelves_enum_options(): class ShelvesModel(BaseSettingsModel): - options: str = Field( + options: str = SettingsField( title="Options", description="Switch between shelves manager options", enum_resolver=shelves_enum_options, conditionalEnum=True ) - add_shelf_file: AddShelfFileModel = Field( + add_shelf_file: AddShelfFileModel = SettingsField( title="Add a .shelf file", default_factory=AddShelfFileModel ) - add_set_and_definitions: AddSetAndDefinitionsModel = Field( + add_set_and_definitions: AddSetAndDefinitionsModel = SettingsField( title="Add Shelf Set Name and Shelves Definitions", default_factory=AddSetAndDefinitionsModel ) diff --git a/server_addon/max/server/settings/create_review_settings.py b/server_addon/max/server/settings/create_review_settings.py index 43dac0730a..807976a391 100644 --- a/server_addon/max/server/settings/create_review_settings.py +++ b/server_addon/max/server/settings/create_review_settings.py @@ -1,6 +1,4 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField def image_format_enum(): @@ -57,27 +55,27 @@ def anti_aliasing_enum(): class CreateReviewModel(BaseSettingsModel): - review_width: int = Field(1920, title="Review Width") - review_height: int = Field(1080, title="Review Height") - percentSize: float = Field(100.0, title="Percent of Output") - keep_images: bool = Field(False, title="Keep Image Sequences") - image_format: str = Field( + review_width: int = SettingsField(1920, title="Review Width") + review_height: int = SettingsField(1080, title="Review Height") + percentSize: float = SettingsField(100.0, title="Percent of Output") + keep_images: bool = SettingsField(False, title="Keep Image Sequences") + image_format: str = SettingsField( enum_resolver=image_format_enum, title="Image Format Options" ) - visual_style: str = Field( + visual_style: str = SettingsField( enum_resolver=visual_style_enum, title="Preference" ) - viewport_preset: str = Field( + viewport_preset: str = SettingsField( enum_resolver=preview_preset_enum, title="Preview Preset" ) - anti_aliasing: str = Field( + anti_aliasing: str = SettingsField( enum_resolver=anti_aliasing_enum, title="Anti-aliasing Quality" ) - vp_texture: bool = Field(True, title="Viewport Texture") + vp_texture: bool = SettingsField(True, title="Viewport Texture") DEFAULT_CREATE_REVIEW_SETTINGS = { diff --git a/server_addon/max/server/settings/imageio.py b/server_addon/max/server/settings/imageio.py index 5e46104fa7..221f85a41f 100644 --- a/server_addon/max/server/settings/imageio.py +++ b/server_addon/max/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ImageIOSettings(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/max/server/settings/main.py b/server_addon/max/server/settings/main.py index cad6024cf7..7b0bfc6421 100644 --- a/server_addon/max/server/settings/main.py +++ b/server_addon/max/server/settings/main.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ImageIOSettings from .render_settings import ( RenderSettingsModel, DEFAULT_RENDER_SETTINGS @@ -23,8 +22,8 @@ def unit_scale_enum(): class UnitScaleSettings(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - scene_unit_scale: str = Field( + enabled: bool = SettingsField(True, title="Enabled") + scene_unit_scale: str = SettingsField( "Centimeters", title="Scene Unit Scale", enum_resolver=unit_scale_enum @@ -33,37 +32,37 @@ class UnitScaleSettings(BaseSettingsModel): class PRTAttributesModel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: str = Field(title="Attribute") + name: str = SettingsField(title="Name") + value: str = SettingsField(title="Attribute") class PointCloudSettings(BaseSettingsModel): - attribute: list[PRTAttributesModel] = Field( + attribute: list[PRTAttributesModel] = SettingsField( default_factory=list, title="Channel Attribute") class MaxSettings(BaseSettingsModel): - unit_scale_settings: UnitScaleSettings = Field( + unit_scale_settings: UnitScaleSettings = SettingsField( default_factory=UnitScaleSettings, title="Set Unit Scale" ) - imageio: ImageIOSettings = Field( + imageio: ImageIOSettings = SettingsField( default_factory=ImageIOSettings, title="Color Management (ImageIO)" ) - RenderSettings: RenderSettingsModel = Field( + RenderSettings: RenderSettingsModel = SettingsField( default_factory=RenderSettingsModel, title="Render Settings" ) - CreateReview: CreateReviewModel = Field( + CreateReview: CreateReviewModel = SettingsField( default_factory=CreateReviewModel, title="Create Review" ) - PointCloud: PointCloudSettings = Field( + PointCloud: PointCloudSettings = SettingsField( default_factory=PointCloudSettings, title="Point Cloud" ) - publish: PublishersModel = Field( + publish: PublishersModel = SettingsField( default_factory=PublishersModel, title="Publish Plugins") diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index d40d85a99b..da782cb494 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -1,13 +1,13 @@ import json -from pydantic import Field, validator +from pydantic import validator -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.exceptions import BadRequestException class ValidateAttributesModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateAttributes") - attributes: str = Field( + enabled: bool = SettingsField(title="ValidateAttributes") + attributes: str = SettingsField( "{}", title="Attributes", widget="textarea") @validator("attributes") @@ -28,64 +28,64 @@ class ValidateAttributesModel(BaseSettingsModel): class FamilyMappingItemModel(BaseSettingsModel): - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product Types" ) - plugins: list[str] = Field( + plugins: list[str] = SettingsField( default_factory=list, title="Plugins" ) class ValidateLoadedPluginModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - family_plugins_mapping: list[FamilyMappingItemModel] = Field( + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + family_plugins_mapping: list[FamilyMappingItemModel] = SettingsField( default_factory=list, title="Family Plugins Mapping" ) class BasicValidateModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") class PublishersModel(BaseSettingsModel): - ValidateFrameRange: BasicValidateModel = Field( + ValidateFrameRange: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Frame Range", section="Validators" ) - ValidateAttributes: ValidateAttributesModel = Field( + ValidateAttributes: ValidateAttributesModel = SettingsField( default_factory=ValidateAttributesModel, title="Validate Attributes" ) - ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( + ValidateLoadedPlugin: ValidateLoadedPluginModel = SettingsField( default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" ) - ExtractModelObj: BasicValidateModel = Field( + ExtractModelObj: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract OBJ", section="Extractors" ) - ExtractModelFbx: BasicValidateModel = Field( + ExtractModelFbx: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract FBX" ) - ExtractModelUSD: BasicValidateModel = Field( + ExtractModelUSD: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract Geometry (USD)" ) - ExtractModel: BasicValidateModel = Field( + ExtractModel: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract Geometry (Alembic)" ) - ExtractMaxSceneRaw: BasicValidateModel = Field( + ExtractMaxSceneRaw: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract Max Scene (Raw)" ) diff --git a/server_addon/max/server/settings/render_settings.py b/server_addon/max/server/settings/render_settings.py index c00cb5e436..19d36dd0f8 100644 --- a/server_addon/max/server/settings/render_settings.py +++ b/server_addon/max/server/settings/render_settings.py @@ -1,6 +1,4 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField def aov_separators_enum(): @@ -26,19 +24,19 @@ def image_format_enum(): class RenderSettingsModel(BaseSettingsModel): - default_render_image_folder: str = Field( + default_render_image_folder: str = SettingsField( title="Default render image folder" ) - aov_separator: str = Field( + aov_separator: str = SettingsField( "underscore", title="AOV Separator character", enum_resolver=aov_separators_enum ) - image_format: str = Field( + image_format: str = SettingsField( enum_resolver=image_format_enum, title="Output Image Format" ) - multipass: bool = Field(title="multipass") + multipass: bool = SettingsField(title="multipass") DEFAULT_RENDER_SETTINGS = { diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 34a54832af..6b5583e726 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -1,232 +1,233 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel -from ayon_server.settings import task_types_enum +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + task_types_enum, +) class CreateLookModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - make_tx: bool = Field(title="Make tx files") - rs_tex: bool = Field(title="Make Redshift texture files") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + make_tx: bool = SettingsField(title="Make tx files") + rs_tex: bool = SettingsField(title="Make Redshift texture files") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) class BasicCreatorModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) class CreateUnrealStaticMeshModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) - static_mesh_prefix: str = Field("S", title="Static Mesh Prefix") - collision_prefixes: list[str] = Field( + static_mesh_prefix: str = SettingsField("S", title="Static Mesh Prefix") + collision_prefixes: list[str] = SettingsField( default_factory=list, title="Collision Prefixes" ) class CreateUnrealSkeletalMeshModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products") - joint_hints: str = Field("jnt_org", title="Joint root hint") + joint_hints: str = SettingsField("jnt_org", title="Joint root hint") class CreateMultiverseLookModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - publish_mip_map: bool = Field(title="publish_mip_map") + enabled: bool = SettingsField(title="Enabled") + publish_mip_map: bool = SettingsField(title="publish_mip_map") class BasicExportMeshModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - write_color_sets: bool = Field(title="Write Color Sets") - write_face_sets: bool = Field(title="Write Face Sets") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + write_color_sets: bool = SettingsField(title="Write Color Sets") + write_face_sets: bool = SettingsField(title="Write Face Sets") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) class CreateAnimationModel(BaseSettingsModel): - write_color_sets: bool = Field(title="Write Color Sets") - write_face_sets: bool = Field(title="Write Face Sets") - include_parent_hierarchy: bool = Field( + write_color_sets: bool = SettingsField(title="Write Color Sets") + write_face_sets: bool = SettingsField(title="Write Face Sets") + include_parent_hierarchy: bool = SettingsField( title="Include Parent Hierarchy") - include_user_defined_attributes: bool = Field( + include_user_defined_attributes: bool = SettingsField( title="Include User Defined Attributes") - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) class CreatePointCacheModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - write_color_sets: bool = Field(title="Write Color Sets") - write_face_sets: bool = Field(title="Write Face Sets") - include_user_defined_attributes: bool = Field( + enabled: bool = SettingsField(title="Enabled") + write_color_sets: bool = SettingsField(title="Write Color Sets") + write_face_sets: bool = SettingsField(title="Write Face Sets") + include_user_defined_attributes: bool = SettingsField( title="Include User Defined Attributes" ) - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) class CreateProxyAlembicModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - write_color_sets: bool = Field(title="Write Color Sets") - write_face_sets: bool = Field(title="Write Face Sets") - default_variants: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + write_color_sets: bool = SettingsField(title="Write Color Sets") + write_face_sets: bool = SettingsField(title="Write Face Sets") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products" ) class CreateAssModel(BasicCreatorModel): - expandProcedurals: bool = Field(title="Expand Procedurals") - motionBlur: bool = Field(title="Motion Blur") - motionBlurKeys: int = Field(2, title="Motion Blur Keys") - motionBlurLength: float = Field(0.5, title="Motion Blur Length") - maskOptions: bool = Field(title="Mask Options") - maskCamera: bool = Field(title="Mask Camera") - maskLight: bool = Field(title="Mask Light") - maskShape: bool = Field(title="Mask Shape") - maskShader: bool = Field(title="Mask Shader") - maskOverride: bool = Field(title="Mask Override") - maskDriver: bool = Field(title="Mask Driver") - maskFilter: bool = Field(title="Mask Filter") - maskColor_manager: bool = Field(title="Mask Color Manager") - maskOperator: bool = Field(title="Mask Operator") + expandProcedurals: bool = SettingsField(title="Expand Procedurals") + motionBlur: bool = SettingsField(title="Motion Blur") + motionBlurKeys: int = SettingsField(2, title="Motion Blur Keys") + motionBlurLength: float = SettingsField(0.5, title="Motion Blur Length") + maskOptions: bool = SettingsField(title="Mask Options") + maskCamera: bool = SettingsField(title="Mask Camera") + maskLight: bool = SettingsField(title="Mask Light") + maskShape: bool = SettingsField(title="Mask Shape") + maskShader: bool = SettingsField(title="Mask Shader") + maskOverride: bool = SettingsField(title="Mask Override") + maskDriver: bool = SettingsField(title="Mask Driver") + maskFilter: bool = SettingsField(title="Mask Filter") + maskColor_manager: bool = SettingsField(title="Mask Color Manager") + maskOperator: bool = SettingsField(title="Mask Operator") class CreateReviewModel(BasicCreatorModel): - useMayaTimeline: bool = Field(title="Use Maya Timeline for Frame Range.") + useMayaTimeline: bool = SettingsField(title="Use Maya Timeline for Frame Range.") class CreateVrayProxyModel(BaseSettingsModel): - enabled: bool = Field(True) - vrmesh: bool = Field(title="VrMesh") - alembic: bool = Field(title="Alembic") - default_variants: list[str] = Field( + enabled: bool = SettingsField(True) + vrmesh: bool = SettingsField(title="VrMesh") + alembic: bool = SettingsField(title="Alembic") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Products") class CreateMultishotLayout(BasicCreatorModel): - shotParent: str = Field(title="Shot Parent Folder") - groupLoadedAssets: bool = Field(title="Group Loaded Assets") - task_type: list[str] = Field( + shotParent: str = SettingsField(title="Shot Parent Folder") + groupLoadedAssets: bool = SettingsField(title="Group Loaded Assets") + task_type: list[str] = SettingsField( title="Task types", enum_resolver=task_types_enum ) - task_name: str = Field(title="Task name (regex)") + task_name: str = SettingsField(title="Task name (regex)") class CreatorsModel(BaseSettingsModel): - CreateLook: CreateLookModel = Field( + CreateLook: CreateLookModel = SettingsField( default_factory=CreateLookModel, title="Create Look" ) - CreateRender: BasicCreatorModel = Field( + CreateRender: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Render" ) # "-" is not compatible in the new model - CreateUnrealStaticMesh: CreateUnrealStaticMeshModel = Field( + CreateUnrealStaticMesh: CreateUnrealStaticMeshModel = SettingsField( default_factory=CreateUnrealStaticMeshModel, title="Create Unreal_Static Mesh" ) # "-" is not compatible in the new model - CreateUnrealSkeletalMesh: CreateUnrealSkeletalMeshModel = Field( + CreateUnrealSkeletalMesh: CreateUnrealSkeletalMeshModel = SettingsField( default_factory=CreateUnrealSkeletalMeshModel, title="Create Unreal_Skeletal Mesh" ) - CreateMultiverseLook: CreateMultiverseLookModel = Field( + CreateMultiverseLook: CreateMultiverseLookModel = SettingsField( default_factory=CreateMultiverseLookModel, title="Create Multiverse Look" ) - CreateAnimation: CreateAnimationModel = Field( + CreateAnimation: CreateAnimationModel = SettingsField( default_factory=CreateAnimationModel, title="Create Animation" ) - CreateModel: BasicExportMeshModel = Field( + CreateModel: BasicExportMeshModel = SettingsField( default_factory=BasicExportMeshModel, title="Create Model" ) - CreatePointCache: CreatePointCacheModel = Field( + CreatePointCache: CreatePointCacheModel = SettingsField( default_factory=CreatePointCacheModel, title="Create Point Cache" ) - CreateProxyAlembic: CreateProxyAlembicModel = Field( + CreateProxyAlembic: CreateProxyAlembicModel = SettingsField( default_factory=CreateProxyAlembicModel, title="Create Proxy Alembic" ) - CreateMultiverseUsd: BasicCreatorModel = Field( + CreateMultiverseUsd: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Multiverse USD" ) - CreateMultiverseUsdComp: BasicCreatorModel = Field( + CreateMultiverseUsdComp: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Multiverse USD Composition" ) - CreateMultiverseUsdOver: BasicCreatorModel = Field( + CreateMultiverseUsdOver: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Multiverse USD Override" ) - CreateAss: CreateAssModel = Field( + CreateAss: CreateAssModel = SettingsField( default_factory=CreateAssModel, title="Create Ass" ) - CreateAssembly: BasicCreatorModel = Field( + CreateAssembly: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Assembly" ) - CreateCamera: BasicCreatorModel = Field( + CreateCamera: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Camera" ) - CreateLayout: BasicCreatorModel = Field( + CreateLayout: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Layout" ) - CreateMayaScene: BasicCreatorModel = Field( + CreateMayaScene: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Maya Scene" ) - CreateRenderSetup: BasicCreatorModel = Field( + CreateRenderSetup: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Render Setup" ) - CreateReview: CreateReviewModel = Field( + CreateReview: CreateReviewModel = SettingsField( default_factory=CreateReviewModel, title="Create Review" ) - CreateRig: BasicCreatorModel = Field( + CreateRig: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Rig" ) - CreateSetDress: BasicCreatorModel = Field( + CreateSetDress: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Set Dress" ) - CreateVrayProxy: CreateVrayProxyModel = Field( + CreateVrayProxy: CreateVrayProxyModel = SettingsField( default_factory=CreateVrayProxyModel, title="Create VRay Proxy" ) - CreateVRayScene: BasicCreatorModel = Field( + CreateVRayScene: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create VRay Scene" ) - CreateYetiRig: BasicCreatorModel = Field( + CreateYetiRig: BasicCreatorModel = SettingsField( default_factory=BasicCreatorModel, title="Create Yeti Rig" ) diff --git a/server_addon/maya/server/settings/explicit_plugins_loading.py b/server_addon/maya/server/settings/explicit_plugins_loading.py index 394adb728f..cda5154b90 100644 --- a/server_addon/maya/server/settings/explicit_plugins_loading.py +++ b/server_addon/maya/server/settings/explicit_plugins_loading.py @@ -1,19 +1,17 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class PluginsModel(BaseSettingsModel): _layout = "expanded" - enabled: bool = Field(title="Enabled") - name: str = Field("", title="Name") + enabled: bool = SettingsField(title="Enabled") + name: str = SettingsField("", title="Name") class ExplicitPluginsLoadingModel(BaseSettingsModel): """Maya Explicit Plugins Loading.""" _isGroup: bool = True - enabled: bool = Field(title="enabled") - plugins_to_load: list[PluginsModel] = Field( + enabled: bool = SettingsField(title="enabled") + plugins_to_load: list[PluginsModel] = SettingsField( default_factory=list, title="Plugins To Load" ) diff --git a/server_addon/maya/server/settings/imageio.py b/server_addon/maya/server/settings/imageio.py index 946a14c866..34338b24e4 100644 --- a/server_addon/maya/server/settings/imageio.py +++ b/server_addon/maya/server/settings/imageio.py @@ -2,32 +2,36 @@ Note: Names were changed to get rid of the versions in class names. """ -from pydantic import Field, validator +from pydantic import validator -from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + ensure_unique_names, +) class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -44,25 +48,25 @@ class ColorManagementPreferenceV2Model(BaseSettingsModel): Please migrate all to 'imageio/workfile' and enable it. """ - enabled: bool = Field(True, title="Use Color Management Preference v2") + enabled: bool = SettingsField(True, title="Use Color Management Preference v2") - renderSpace: str = Field(title="Rendering Space") - displayName: str = Field(title="Display") - viewName: str = Field(title="View") + renderSpace: str = SettingsField(title="Rendering Space") + displayName: str = SettingsField(title="Display") + viewName: str = SettingsField(title="View") class ColorManagementPreferenceModel(BaseSettingsModel): """Color Management Preference (legacy).""" - renderSpace: str = Field(title="Rendering Space") - viewTransform: str = Field(title="Viewer Transform ") + renderSpace: str = SettingsField(title="Rendering Space") + viewTransform: str = SettingsField(title="Viewer Transform ") class WorkfileImageIOModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - renderSpace: str = Field(title="Rendering Space") - displayName: str = Field(title="Display") - viewName: str = Field(title="View") + enabled: bool = SettingsField(True, title="Enabled") + renderSpace: str = SettingsField(title="Rendering Space") + displayName: str = SettingsField(title="Display") + viewName: str = SettingsField(title="View") class ImageIOSettings(BaseSettingsModel): @@ -72,27 +76,27 @@ class ImageIOSettings(BaseSettingsModel): """ _isGroup: bool = True - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) - workfile: WorkfileImageIOModel = Field( + workfile: WorkfileImageIOModel = SettingsField( default_factory=WorkfileImageIOModel, title="Workfile" ) # Deprecated - colorManagementPreference_v2: ColorManagementPreferenceV2Model = Field( + colorManagementPreference_v2: ColorManagementPreferenceV2Model = SettingsField( default_factory=ColorManagementPreferenceV2Model, title="DEPRECATED: Color Management Preference v2 (Maya 2022+)" ) - colorManagementPreference: ColorManagementPreferenceModel = Field( + colorManagementPreference: ColorManagementPreferenceModel = SettingsField( default_factory=ColorManagementPreferenceModel, title="DEPRECATED: Color Management Preference (legacy)" ) diff --git a/server_addon/maya/server/settings/include_handles.py b/server_addon/maya/server/settings/include_handles.py index 3ba6aca66b..931222ad2d 100644 --- a/server_addon/maya/server/settings/include_handles.py +++ b/server_addon/maya/server/settings/include_handles.py @@ -1,24 +1,26 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel, task_types_enum +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + task_types_enum, +) class IncludeByTaskTypeModel(BaseSettingsModel): - task_type: list[str] = Field( + task_type: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - include_handles: bool = Field(True, title="Include handles") + include_handles: bool = SettingsField(True, title="Include handles") class IncludeHandlesModel(BaseSettingsModel): """Maya dirmap settings.""" # _layout = "expanded" - include_handles_default: bool = Field( + include_handles_default: bool = SettingsField( True, title="Include handles by default" ) - per_task_type: list[IncludeByTaskTypeModel] = Field( + per_task_type: list[IncludeByTaskTypeModel] = SettingsField( default_factory=list, title="Include/exclude handles by task type" ) diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py index ed6b6fd2ac..1d5b972056 100644 --- a/server_addon/maya/server/settings/loaders.py +++ b/server_addon/maya/server/settings/loaders.py @@ -1,66 +1,64 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.types import ColorRGBA_uint8 class ColorsSetting(BaseSettingsModel): - model: ColorRGBA_uint8 = Field( + model: ColorRGBA_uint8 = SettingsField( (209, 132, 30, 1.0), title="Model:") - rig: ColorRGBA_uint8 = Field( + rig: ColorRGBA_uint8 = SettingsField( (59, 226, 235, 1.0), title="Rig:") - pointcache: ColorRGBA_uint8 = Field( + pointcache: ColorRGBA_uint8 = SettingsField( (94, 209, 30, 1.0), title="Pointcache:") - animation: ColorRGBA_uint8 = Field( + animation: ColorRGBA_uint8 = SettingsField( (94, 209, 30, 1.0), title="Animation:") - ass: ColorRGBA_uint8 = Field( + ass: ColorRGBA_uint8 = SettingsField( (249, 135, 53, 1.0), title="Arnold StandIn:") - camera: ColorRGBA_uint8 = Field( + camera: ColorRGBA_uint8 = SettingsField( (136, 114, 244, 1.0), title="Camera:") - fbx: ColorRGBA_uint8 = Field( + fbx: ColorRGBA_uint8 = SettingsField( (215, 166, 255, 1.0), title="FBX:") - mayaAscii: ColorRGBA_uint8 = Field( + mayaAscii: ColorRGBA_uint8 = SettingsField( (67, 174, 255, 1.0), title="Maya Ascii:") - mayaScene: ColorRGBA_uint8 = Field( + mayaScene: ColorRGBA_uint8 = SettingsField( (67, 174, 255, 1.0), title="Maya Scene:") - setdress: ColorRGBA_uint8 = Field( + setdress: ColorRGBA_uint8 = SettingsField( (255, 250, 90, 1.0), title="Set Dress:") - layout: ColorRGBA_uint8 = Field(( + layout: ColorRGBA_uint8 = SettingsField(( 255, 250, 90, 1.0), title="Layout:") - vdbcache: ColorRGBA_uint8 = Field( + vdbcache: ColorRGBA_uint8 = SettingsField( (249, 54, 0, 1.0), title="VDB Cache:") - vrayproxy: ColorRGBA_uint8 = Field( + vrayproxy: ColorRGBA_uint8 = SettingsField( (255, 150, 12, 1.0), title="VRay Proxy:") - vrayscene_layer: ColorRGBA_uint8 = Field( + vrayscene_layer: ColorRGBA_uint8 = SettingsField( (255, 150, 12, 1.0), title="VRay Scene:") - yeticache: ColorRGBA_uint8 = Field( + yeticache: ColorRGBA_uint8 = SettingsField( (99, 206, 220, 1.0), title="Yeti Cache:") - yetiRig: ColorRGBA_uint8 = Field( + yetiRig: ColorRGBA_uint8 = SettingsField( (0, 205, 125, 1.0), title="Yeti Rig:") class ReferenceLoaderModel(BaseSettingsModel): - namespace: str = Field(title="Namespace") - group_name: str = Field(title="Group name") - display_handle: bool = Field(title="Display Handle On Load References") + namespace: str = SettingsField(title="Namespace") + group_name: str = SettingsField(title="Group name") + display_handle: bool = SettingsField(title="Display Handle On Load References") class ImportLoaderModel(BaseSettingsModel): - namespace: str = Field(title="Namespace") - group_name: str = Field(title="Group name") + namespace: str = SettingsField(title="Namespace") + group_name: str = SettingsField(title="Group name") class LoadersModel(BaseSettingsModel): - colors: ColorsSetting = Field( + colors: ColorsSetting = SettingsField( default_factory=ColorsSetting, title="Loaded Products Outliner Colors") - reference_loader: ReferenceLoaderModel = Field( + reference_loader: ReferenceLoaderModel = SettingsField( default_factory=ReferenceLoaderModel, title="Reference Loader" ) - import_loader: ImportLoaderModel = Field( + import_loader: ImportLoaderModel = SettingsField( default_factory=ImportLoaderModel, title="Import Loader" ) diff --git a/server_addon/maya/server/settings/main.py b/server_addon/maya/server/settings/main.py index 3d084312e9..ddfb797f8a 100644 --- a/server_addon/maya/server/settings/main.py +++ b/server_addon/maya/server/settings/main.py @@ -1,5 +1,9 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from pydantic import validator +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + ensure_unique_names, +) from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS from .maya_dirmap import MayaDirmapModel, DEFAULT_MAYA_DIRMAP_SETTINGS from .include_handles import IncludeHandlesModel, DEFAULT_INCLUDE_HANDLES @@ -19,44 +23,44 @@ from .templated_workfile_settings import ( class ExtMappingItemModel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Product type") - value: str = Field(title="Extension") + name: str = SettingsField(title="Product type") + value: str = SettingsField(title="Extension") class MayaSettings(BaseSettingsModel): """Maya Project Settings.""" - open_workfile_post_initialization: bool = Field( + open_workfile_post_initialization: bool = SettingsField( True, title="Open Workfile Post Initialization") - explicit_plugins_loading: ExplicitPluginsLoadingModel = Field( + explicit_plugins_loading: ExplicitPluginsLoadingModel = SettingsField( default_factory=ExplicitPluginsLoadingModel, title="Explicit Plugins Loading") - imageio: ImageIOSettings = Field( + imageio: ImageIOSettings = SettingsField( default_factory=ImageIOSettings, title="Color Management (imageio)") - mel_workspace: str = Field(title="Maya MEL Workspace", widget="textarea") - ext_mapping: list[ExtMappingItemModel] = Field( + mel_workspace: str = SettingsField(title="Maya MEL Workspace", widget="textarea") + ext_mapping: list[ExtMappingItemModel] = SettingsField( default_factory=list, title="Extension Mapping") - maya_dirmap: MayaDirmapModel = Field( + maya_dirmap: MayaDirmapModel = SettingsField( default_factory=MayaDirmapModel, title="Maya dirmap Settings") - include_handles: IncludeHandlesModel = Field( + include_handles: IncludeHandlesModel = SettingsField( default_factory=IncludeHandlesModel, title="Include/Exclude Handles in default playback & render range" ) - scriptsmenu: ScriptsmenuModel = Field( + scriptsmenu: ScriptsmenuModel = SettingsField( default_factory=ScriptsmenuModel, title="Scriptsmenu Settings" ) - render_settings: RenderSettingsModel = Field( + render_settings: RenderSettingsModel = SettingsField( default_factory=RenderSettingsModel, title="Render Settings") - create: CreatorsModel = Field( + create: CreatorsModel = SettingsField( default_factory=CreatorsModel, title="Creators") - publish: PublishersModel = Field( + publish: PublishersModel = SettingsField( default_factory=PublishersModel, title="Publishers") - load: LoadersModel = Field( + load: LoadersModel = SettingsField( default_factory=LoadersModel, title="Loaders") - workfile_build: ProfilesModel = Field( + workfile_build: ProfilesModel = SettingsField( default_factory=ProfilesModel, title="Workfile Build Settings") - templated_workfile_build: TemplatedProfilesModel = Field( + templated_workfile_build: TemplatedProfilesModel = SettingsField( default_factory=TemplatedProfilesModel, title="Templated Workfile Build Settings") diff --git a/server_addon/maya/server/settings/maya_dirmap.py b/server_addon/maya/server/settings/maya_dirmap.py index 243261dc87..f68028cd79 100644 --- a/server_addon/maya/server/settings/maya_dirmap.py +++ b/server_addon/maya/server/settings/maya_dirmap.py @@ -1,14 +1,12 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class MayaDirmapPathsSubmodel(BaseSettingsModel): _layout = "compact" - source_path: list[str] = Field( + source_path: list[str] = SettingsField( default_factory=list, title="Source Paths" ) - destination_path: list[str] = Field( + destination_path: list[str] = SettingsField( default_factory=list, title="Destination Paths" ) @@ -18,13 +16,13 @@ class MayaDirmapModel(BaseSettingsModel): # _layout = "expanded" _isGroup: bool = True - enabled: bool = Field(title="enabled") + enabled: bool = SettingsField(title="enabled") # Use ${} placeholder instead of absolute value of a root in # referenced filepaths. - use_env_var_as_root: bool = Field( + use_env_var_as_root: bool = SettingsField( title="Use env var placeholder in referenced paths" ) - paths: MayaDirmapPathsSubmodel = Field( + paths: MayaDirmapPathsSubmodel = SettingsField( default_factory=MayaDirmapPathsSubmodel, title="Dirmap Paths" ) diff --git a/server_addon/maya/server/settings/publish_playblast.py b/server_addon/maya/server/settings/publish_playblast.py index 0abc9f7110..0461a18cc8 100644 --- a/server_addon/maya/server/settings/publish_playblast.py +++ b/server_addon/maya/server/settings/publish_playblast.py @@ -1,7 +1,8 @@ -from pydantic import Field, validator +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ensure_unique_names, task_types_enum, ) @@ -43,36 +44,40 @@ def plugin_objects_default(): class CodecSetting(BaseSettingsModel): _layout = "expanded" - compression: str = Field("png", title="Encoding") - format: str = Field("image", title="Format") - quality: int = Field(95, title="Quality", ge=0, le=100) + compression: str = SettingsField("png", title="Encoding") + format: str = SettingsField("image", title="Format") + quality: int = SettingsField(95, title="Quality", ge=0, le=100) class DisplayOptionsSetting(BaseSettingsModel): _layout = "expanded" - override_display: bool = Field(True, title="Override display options") - background: ColorRGBA_uint8 = Field( + override_display: bool = SettingsField( + True, title="Override display options" + ) + background: ColorRGBA_uint8 = SettingsField( (125, 125, 125, 1.0), title="Background Color" ) - displayGradient: bool = Field(True, title="Display background gradient") - backgroundTop: ColorRGBA_uint8 = Field( + displayGradient: bool = SettingsField( + True, title="Display background gradient" + ) + backgroundTop: ColorRGBA_uint8 = SettingsField( (125, 125, 125, 1.0), title="Background Top" ) - backgroundBottom: ColorRGBA_uint8 = Field( + backgroundBottom: ColorRGBA_uint8 = SettingsField( (125, 125, 125, 1.0), title="Background Bottom" ) class GenericSetting(BaseSettingsModel): _layout = "expanded" - isolate_view: bool = Field(True, title="Isolate View") - off_screen: bool = Field(True, title="Off Screen") - pan_zoom: bool = Field(False, title="2D Pan/Zoom") + isolate_view: bool = SettingsField(True, title="Isolate View") + off_screen: bool = SettingsField(True, title="Off Screen") + pan_zoom: bool = SettingsField(False, title="2D Pan/Zoom") class RendererSetting(BaseSettingsModel): _layout = "expanded" - rendererName: str = Field( + rendererName: str = SettingsField( "vp2Renderer", enum_resolver=renderer_enum, title="Renderer name" @@ -81,100 +86,112 @@ class RendererSetting(BaseSettingsModel): class ResolutionSetting(BaseSettingsModel): _layout = "expanded" - width: int = Field(0, title="Width") - height: int = Field(0, title="Height") + width: int = SettingsField(0, title="Width") + height: int = SettingsField(0, title="Height") class PluginObjectsModel(BaseSettingsModel): - name: str = Field("", title="Name") - value: bool = Field(True, title="Enabled") + name: str = SettingsField("", title="Name") + value: bool = SettingsField(True, title="Enabled") class ViewportOptionsSetting(BaseSettingsModel): - override_viewport_options: bool = Field( + override_viewport_options: bool = SettingsField( True, title="Override viewport options" ) - displayLights: str = Field( + displayLights: str = SettingsField( "default", enum_resolver=displayLights_enum, title="Display Lights" ) - displayTextures: bool = Field(True, title="Display Textures") - textureMaxResolution: int = Field(1024, title="Texture Clamp Resolution") - renderDepthOfField: bool = Field( + displayTextures: bool = SettingsField(True, title="Display Textures") + textureMaxResolution: int = SettingsField( + 1024, title="Texture Clamp Resolution" + ) + renderDepthOfField: bool = SettingsField( True, title="Depth of Field", section="Depth of Field" ) - shadows: bool = Field(True, title="Display Shadows") - twoSidedLighting: bool = Field(True, title="Two Sided Lighting") - lineAAEnable: bool = Field( + shadows: bool = SettingsField(True, title="Display Shadows") + twoSidedLighting: bool = SettingsField(True, title="Two Sided Lighting") + lineAAEnable: bool = SettingsField( True, title="Enable Anti-Aliasing", section="Anti-Aliasing" ) - multiSample: int = Field(8, title="Anti Aliasing Samples") - loadTextures: bool = Field(False, title="Load Textures") - useDefaultMaterial: bool = Field(False, title="Use Default Material") - wireframeOnShaded: bool = Field(False, title="Wireframe On Shaded") - xray: bool = Field(False, title="X-Ray") - jointXray: bool = Field(False, title="X-Ray Joints") - backfaceCulling: bool = Field(False, title="Backface Culling") - ssaoEnable: bool = Field( + multiSample: int = SettingsField(8, title="Anti Aliasing Samples") + loadTextures: bool = SettingsField(False, title="Load Textures") + useDefaultMaterial: bool = SettingsField( + False, title="Use Default Material" + ) + wireframeOnShaded: bool = SettingsField(False, title="Wireframe On Shaded") + xray: bool = SettingsField(False, title="X-Ray") + jointXray: bool = SettingsField(False, title="X-Ray Joints") + backfaceCulling: bool = SettingsField(False, title="Backface Culling") + ssaoEnable: bool = SettingsField( False, title="Screen Space Ambient Occlusion", section="SSAO" ) - ssaoAmount: int = Field(1, title="SSAO Amount") - ssaoRadius: int = Field(16, title="SSAO Radius") - ssaoFilterRadius: int = Field(16, title="SSAO Filter Radius") - ssaoSamples: int = Field(16, title="SSAO Samples") - fogging: bool = Field(False, title="Enable Hardware Fog", section="Fog") - hwFogFalloff: str = Field( + ssaoAmount: int = SettingsField(1, title="SSAO Amount") + ssaoRadius: int = SettingsField(16, title="SSAO Radius") + ssaoFilterRadius: int = SettingsField(16, title="SSAO Filter Radius") + ssaoSamples: int = SettingsField(16, title="SSAO Samples") + fogging: bool = SettingsField( + False, title="Enable Hardware Fog", section="Fog" + ) + hwFogFalloff: str = SettingsField( "0", enum_resolver=hardware_falloff_enum, title="Hardware Falloff" ) - hwFogDensity: float = Field(0.0, title="Fog Density") - hwFogStart: int = Field(0, title="Fog Start") - hwFogEnd: int = Field(100, title="Fog End") - hwFogAlpha: int = Field(0, title="Fog Alpha") - hwFogColorR: float = Field(1.0, title="Fog Color R") - hwFogColorG: float = Field(1.0, title="Fog Color G") - hwFogColorB: float = Field(1.0, title="Fog Color B") - motionBlurEnable: bool = Field( + hwFogDensity: float = SettingsField(0.0, title="Fog Density") + hwFogStart: int = SettingsField(0, title="Fog Start") + hwFogEnd: int = SettingsField(100, title="Fog End") + hwFogAlpha: int = SettingsField(0, title="Fog Alpha") + hwFogColorR: float = SettingsField(1.0, title="Fog Color R") + hwFogColorG: float = SettingsField(1.0, title="Fog Color G") + hwFogColorB: float = SettingsField(1.0, title="Fog Color B") + motionBlurEnable: bool = SettingsField( False, title="Enable Motion Blur", section="Motion Blur" ) - motionBlurSampleCount: int = Field(8, title="Motion Blur Sample Count") - motionBlurShutterOpenFraction: float = Field( + motionBlurSampleCount: int = SettingsField( + 8, title="Motion Blur Sample Count" + ) + motionBlurShutterOpenFraction: float = SettingsField( 0.2, title="Shutter Open Fraction" ) - cameras: bool = Field(False, title="Cameras", section="Show") - clipGhosts: bool = Field(False, title="Clip Ghosts") - deformers: bool = Field(False, title="Deformers") - dimensions: bool = Field(False, title="Dimensions") - dynamicConstraints: bool = Field(False, title="Dynamic Constraints") - dynamics: bool = Field(False, title="Dynamics") - fluids: bool = Field(False, title="Fluids") - follicles: bool = Field(False, title="Follicles") - greasePencils: bool = Field(False, title="Grease Pencils") - grid: bool = Field(False, title="Grid") - hairSystems: bool = Field(True, title="Hair Systems") - handles: bool = Field(False, title="Handles") - headsUpDisplay: bool = Field(False, title="HUD") - ikHandles: bool = Field(False, title="IK Handles") - imagePlane: bool = Field(True, title="Image Plane") - joints: bool = Field(False, title="Joints") - lights: bool = Field(False, title="Lights") - locators: bool = Field(False, title="Locators") - manipulators: bool = Field(False, title="Manipulators") - motionTrails: bool = Field(False, title="Motion Trails") - nCloths: bool = Field(False, title="nCloths") - nParticles: bool = Field(False, title="nParticles") - nRigids: bool = Field(False, title="nRigids") - controlVertices: bool = Field(False, title="NURBS CVs") - nurbsCurves: bool = Field(False, title="NURBS Curves") - hulls: bool = Field(False, title="NURBS Hulls") - nurbsSurfaces: bool = Field(False, title="NURBS Surfaces") - particleInstancers: bool = Field(False, title="Particle Instancers") - pivots: bool = Field(False, title="Pivots") - planes: bool = Field(False, title="Planes") - pluginShapes: bool = Field(False, title="Plugin Shapes") - polymeshes: bool = Field(True, title="Polygons") - strokes: bool = Field(False, title="Strokes") - subdivSurfaces: bool = Field(False, title="Subdiv Surfaces") - textures: bool = Field(False, title="Texture Placements") - pluginObjects: list[PluginObjectsModel] = Field( + cameras: bool = SettingsField(False, title="Cameras", section="Show") + clipGhosts: bool = SettingsField(False, title="Clip Ghosts") + deformers: bool = SettingsField(False, title="Deformers") + dimensions: bool = SettingsField(False, title="Dimensions") + dynamicConstraints: bool = SettingsField( + False, title="Dynamic Constraints" + ) + dynamics: bool = SettingsField(False, title="Dynamics") + fluids: bool = SettingsField(False, title="Fluids") + follicles: bool = SettingsField(False, title="Follicles") + greasePencils: bool = SettingsField(False, title="Grease Pencils") + grid: bool = SettingsField(False, title="Grid") + hairSystems: bool = SettingsField(True, title="Hair Systems") + handles: bool = SettingsField(False, title="Handles") + headsUpDisplay: bool = SettingsField(False, title="HUD") + ikHandles: bool = SettingsField(False, title="IK Handles") + imagePlane: bool = SettingsField(True, title="Image Plane") + joints: bool = SettingsField(False, title="Joints") + lights: bool = SettingsField(False, title="Lights") + locators: bool = SettingsField(False, title="Locators") + manipulators: bool = SettingsField(False, title="Manipulators") + motionTrails: bool = SettingsField(False, title="Motion Trails") + nCloths: bool = SettingsField(False, title="nCloths") + nParticles: bool = SettingsField(False, title="nParticles") + nRigids: bool = SettingsField(False, title="nRigids") + controlVertices: bool = SettingsField(False, title="NURBS CVs") + nurbsCurves: bool = SettingsField(False, title="NURBS Curves") + hulls: bool = SettingsField(False, title="NURBS Hulls") + nurbsSurfaces: bool = SettingsField(False, title="NURBS Surfaces") + particleInstancers: bool = SettingsField( + False, title="Particle Instancers" + ) + pivots: bool = SettingsField(False, title="Pivots") + planes: bool = SettingsField(False, title="Planes") + pluginShapes: bool = SettingsField(False, title="Plugin Shapes") + polymeshes: bool = SettingsField(True, title="Polygons") + strokes: bool = SettingsField(False, title="Strokes") + subdivSurfaces: bool = SettingsField(False, title="Subdiv Surfaces") + textures: bool = SettingsField(False, title="Texture Placements") + pluginObjects: list[PluginObjectsModel] = SettingsField( default_factory=plugin_objects_default, title="Plugin Objects" ) @@ -186,67 +203,71 @@ class ViewportOptionsSetting(BaseSettingsModel): class CameraOptionsSetting(BaseSettingsModel): - displayGateMask: bool = Field(False, title="Display Gate Mask") - displayResolution: bool = Field(False, title="Display Resolution") - displayFilmGate: bool = Field(False, title="Display Film Gate") - displayFieldChart: bool = Field(False, title="Display Field Chart") - displaySafeAction: bool = Field(False, title="Display Safe Action") - displaySafeTitle: bool = Field(False, title="Display Safe Title") - displayFilmPivot: bool = Field(False, title="Display Film Pivot") - displayFilmOrigin: bool = Field(False, title="Display Film Origin") - overscan: int = Field(1.0, title="Overscan") + displayGateMask: bool = SettingsField(False, title="Display Gate Mask") + displayResolution: bool = SettingsField(False, title="Display Resolution") + displayFilmGate: bool = SettingsField(False, title="Display Film Gate") + displayFieldChart: bool = SettingsField(False, title="Display Field Chart") + displaySafeAction: bool = SettingsField(False, title="Display Safe Action") + displaySafeTitle: bool = SettingsField(False, title="Display Safe Title") + displayFilmPivot: bool = SettingsField(False, title="Display Film Pivot") + displayFilmOrigin: bool = SettingsField(False, title="Display Film Origin") + overscan: int = SettingsField(1.0, title="Overscan") class CapturePresetSetting(BaseSettingsModel): - Codec: CodecSetting = Field( + Codec: CodecSetting = SettingsField( default_factory=CodecSetting, title="Codec", section="Codec") - DisplayOptions: DisplayOptionsSetting = Field( + DisplayOptions: DisplayOptionsSetting = SettingsField( default_factory=DisplayOptionsSetting, title="Display Options", section="Display Options") - Generic: GenericSetting = Field( + Generic: GenericSetting = SettingsField( default_factory=GenericSetting, title="Generic", section="Generic") - Renderer: RendererSetting = Field( + Renderer: RendererSetting = SettingsField( default_factory=RendererSetting, title="Renderer", section="Renderer") - Resolution: ResolutionSetting = Field( + Resolution: ResolutionSetting = SettingsField( default_factory=ResolutionSetting, title="Resolution", section="Resolution") - ViewportOptions: ViewportOptionsSetting = Field( + ViewportOptions: ViewportOptionsSetting = SettingsField( default_factory=ViewportOptionsSetting, title="Viewport Options") - CameraOptions: CameraOptionsSetting = Field( + CameraOptions: CameraOptionsSetting = SettingsField( default_factory=CameraOptionsSetting, title="Camera Options") class ProfilesModel(BaseSettingsModel): _layout = "expanded" - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field(default_factory=list, title="Task names") - product_names: list[str] = Field(default_factory=list, title="Products names") - capture_preset: CapturePresetSetting = Field( + task_names: list[str] = SettingsField( + default_factory=list, title="Task names" + ) + product_names: list[str] = SettingsField( + default_factory=list, title="Products names" + ) + capture_preset: CapturePresetSetting = SettingsField( default_factory=CapturePresetSetting, title="Capture Preset" ) class ExtractPlayblastSetting(BaseSettingsModel): - capture_preset: CapturePresetSetting = Field( + capture_preset: CapturePresetSetting = SettingsField( default_factory=CapturePresetSetting, title="DEPRECATED! Please use \"Profiles\" below. Capture Preset" ) - profiles: list[ProfilesModel] = Field( + profiles: list[ProfilesModel] = SettingsField( default_factory=list, title="Profiles" ) diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index e823efe681..3a6de2eb44 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1,7 +1,8 @@ import json -from pydantic import Field, validator +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, MultiplatformPathModel, ensure_unique_names, ) @@ -35,9 +36,9 @@ def angular_unit_enum(): class BasicValidateModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") class ValidateMeshUVSetMap1Model(BasicValidateModel): @@ -51,22 +52,24 @@ class ValidateNoAnimationModel(BasicValidateModel): class ValidateRigOutSetNodeIdsModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateSkinclusterDeformerSet") - optional: bool = Field(title="Optional") - allow_history_only: bool = Field(title="Allow history only") + enabled: bool = SettingsField(title="ValidateSkinclusterDeformerSet") + optional: bool = SettingsField(title="Optional") + allow_history_only: bool = SettingsField(title="Allow history only") class ValidateModelNameModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - database: bool = Field(title="Use database shader name definitions") - material_file: MultiplatformPathModel = Field( + enabled: bool = SettingsField(title="Enabled") + database: bool = SettingsField( + title="Use database shader name definitions" + ) + material_file: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel, title="Material File", description=( "Path to material file defining list of material names to check." ) ) - regex: str = Field( + regex: str = SettingsField( "(.*)_(\\d)*_(?P.*)_(GEO)", title="Validation regex", description=( @@ -74,7 +77,7 @@ class ValidateModelNameModel(BaseSettingsModel): " named capturing groups:(?P.*) for Asset name" ) ) - top_level_regex: str = Field( + top_level_regex: str = SettingsField( ".*_GRP", title="Top level group name regex", description=( @@ -85,15 +88,15 @@ class ValidateModelNameModel(BaseSettingsModel): class ValidateModelContentModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - validate_top_group: bool = Field(title="Validate one top group") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + validate_top_group: bool = SettingsField(title="Validate one top group") class ValidateTransformNamingSuffixModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - SUFFIX_NAMING_TABLE: str = Field( + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + SUFFIX_NAMING_TABLE: str = SettingsField( "{}", title="Suffix Naming Tables", widget="textarea", @@ -118,34 +121,34 @@ class ValidateTransformNamingSuffixModel(BaseSettingsModel): "The text can't be parsed as json object" ) return value - ALLOW_IF_NOT_IN_SUFFIX_TABLE: bool = Field( + ALLOW_IF_NOT_IN_SUFFIX_TABLE: bool = SettingsField( title="Allow if suffix not in table" ) class CollectMayaRenderModel(BaseSettingsModel): - sync_workfile_version: bool = Field( + sync_workfile_version: bool = SettingsField( title="Sync render version with workfile" ) class CollectFbxAnimationModel(BaseSettingsModel): - enabled: bool = Field(title="Collect Fbx Animation") + enabled: bool = SettingsField(title="Collect Fbx Animation") class CollectFbxCameraModel(BaseSettingsModel): - enabled: bool = Field(title="CollectFbxCamera") + enabled: bool = SettingsField(title="CollectFbxCamera") class CollectGLTFModel(BaseSettingsModel): - enabled: bool = Field(title="CollectGLTF") + enabled: bool = SettingsField(title="CollectGLTF") class ValidateFrameRangeModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateFrameRange") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - exclude_product_types: list[str] = Field( + enabled: bool = SettingsField(title="ValidateFrameRange") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + exclude_product_types: list[str] = SettingsField( default_factory=list, title="Exclude product types" ) @@ -155,15 +158,18 @@ class ValidateShaderNameModel(BaseSettingsModel): """ Shader name regex can use named capture group asset to validate against current asset name. """ - enabled: bool = Field(title="ValidateShaderName") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - regex: str = Field("(?P.*)_(.*)_SHD", title="Validation regex") + enabled: bool = SettingsField(title="ValidateShaderName") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + regex: str = SettingsField( + "(?P.*)_(.*)_SHD", + title="Validation regex" + ) class ValidateAttributesModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateAttributes") - attributes: str = Field( + enabled: bool = SettingsField(title="ValidateAttributes") + attributes: str = SettingsField( "{}", title="Attributes", widget="textarea") @validator("attributes") @@ -184,46 +190,50 @@ class ValidateAttributesModel(BaseSettingsModel): class ValidateLoadedPluginModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateLoadedPlugin") - optional: bool = Field(title="Optional") - whitelist_native_plugins: bool = Field( + enabled: bool = SettingsField(title="ValidateLoadedPlugin") + optional: bool = SettingsField(title="Optional") + whitelist_native_plugins: bool = SettingsField( title="Whitelist Maya Native Plugins" ) - authorized_plugins: list[str] = Field( + authorized_plugins: list[str] = SettingsField( default_factory=list, title="Authorized plugins" ) class ValidateMayaUnitsModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateMayaUnits") - optional: bool = Field(title="Optional") - validate_linear_units: bool = Field(title="Validate linear units") - linear_units: str = Field( + enabled: bool = SettingsField(title="ValidateMayaUnits") + optional: bool = SettingsField(title="Optional") + validate_linear_units: bool = SettingsField(title="Validate linear units") + linear_units: str = SettingsField( enum_resolver=linear_unit_enum, title="Linear Units" ) - validate_angular_units: bool = Field(title="Validate angular units") - angular_units: str = Field( + validate_angular_units: bool = SettingsField( + title="Validate angular units" + ) + angular_units: str = SettingsField( enum_resolver=angular_unit_enum, title="Angular units" ) - validate_fps: bool = Field(title="Validate fps") + validate_fps: bool = SettingsField(title="Validate fps") class ValidateUnrealStaticMeshNameModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateUnrealStaticMeshName") - optional: bool = Field(title="Optional") - validate_mesh: bool = Field(title="Validate mesh names") - validate_collision: bool = Field(title="Validate collison names") + enabled: bool = SettingsField(title="ValidateUnrealStaticMeshName") + optional: bool = SettingsField(title="Optional") + validate_mesh: bool = SettingsField(title="Validate mesh names") + validate_collision: bool = SettingsField(title="Validate collison names") class ValidateCycleErrorModel(BaseSettingsModel): - enabled: bool = Field(title="ValidateCycleError") - optional: bool = Field(title="Optional") - families: list[str] = Field(default_factory=list, title="Families") + enabled: bool = SettingsField(title="ValidateCycleError") + optional: bool = SettingsField(title="Optional") + families: list[str] = SettingsField( + default_factory=list, title="Families" + ) class ValidatePluginPathAttributesAttrModel(BaseSettingsModel): - name: str = Field(title="Node type") - value: str = Field(title="Attribute") + name: str = SettingsField(title="Node type") + value: str = SettingsField(title="Attribute") class ValidatePluginPathAttributesModel(BaseSettingsModel): @@ -234,9 +244,9 @@ class ValidatePluginPathAttributesModel(BaseSettingsModel): """ enabled: bool = True - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - attribute: list[ValidatePluginPathAttributesAttrModel] = Field( + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + attribute: list[ValidatePluginPathAttributesAttrModel] = SettingsField( default_factory=list, title="File Attribute" ) @@ -250,66 +260,68 @@ class ValidatePluginPathAttributesModel(BaseSettingsModel): # Validate Render Setting class RendererAttributesModel(BaseSettingsModel): _layout = "compact" - type: str = Field(title="Type") - value: str = Field(title="Value") + type: str = SettingsField(title="Type") + value: str = SettingsField(title="Value") class ValidateRenderSettingsModel(BaseSettingsModel): - arnold_render_attributes: list[RendererAttributesModel] = Field( + arnold_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Arnold Render Attributes") - vray_render_attributes: list[RendererAttributesModel] = Field( + vray_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="VRay Render Attributes") - redshift_render_attributes: list[RendererAttributesModel] = Field( + redshift_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Redshift Render Attributes") - renderman_render_attributes: list[RendererAttributesModel] = Field( + renderman_render_attributes: list[RendererAttributesModel] = SettingsField( default_factory=list, title="Renderman Render Attributes") class BasicValidateModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") class ValidateCameraContentsModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - validate_shapes: bool = Field(title="Validate presence of shapes") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + validate_shapes: bool = SettingsField(title="Validate presence of shapes") class ExtractProxyAlembicModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - families: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + families: list[str] = SettingsField( default_factory=list, title="Families") class ExtractAlembicModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - families: list[str] = Field( + enabled: bool = SettingsField(title="Enabled") + families: list[str] = SettingsField( default_factory=list, title="Families") class ExtractObjModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") class ExtractMayaSceneRawModel(BaseSettingsModel): """Add loaded instances to those published families:""" - enabled: bool = Field(title="ExtractMayaSceneRaw") - add_for_families: list[str] = Field(default_factory=list, title="Families") + enabled: bool = SettingsField(title="ExtractMayaSceneRaw") + add_for_families: list[str] = SettingsField( + default_factory=list, title="Families" + ) class ExtractCameraAlembicModel(BaseSettingsModel): """ List of attributes that will be added to the baked alembic camera. Needs to be written in python list syntax. """ - enabled: bool = Field(title="ExtractCameraAlembic") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - bake_attributes: str = Field( + enabled: bool = SettingsField(title="ExtractCameraAlembic") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + bake_attributes: str = SettingsField( "[]", title="Base Attributes", widget="textarea" ) @@ -332,17 +344,19 @@ class ExtractCameraAlembicModel(BaseSettingsModel): class ExtractGLBModel(BaseSettingsModel): enabled: bool = True - active: bool = Field(title="Active") - ogsfx_path: str = Field(title="GLSL Shader Directory") + active: bool = SettingsField(title="Active") + ogsfx_path: str = SettingsField(title="GLSL Shader Directory") class ExtractLookArgsModel(BaseSettingsModel): - argument: str = Field(title="Argument") - parameters: list[str] = Field(default_factory=list, title="Parameters") + argument: str = SettingsField(title="Argument") + parameters: list[str] = SettingsField( + default_factory=list, title="Parameters" + ) class ExtractLookModel(BaseSettingsModel): - maketx_arguments: list[ExtractLookArgsModel] = Field( + maketx_arguments: list[ExtractLookArgsModel] = SettingsField( default_factory=list, title="Extra arguments for maketx command line" ) @@ -350,423 +364,437 @@ class ExtractLookModel(BaseSettingsModel): class ExtractGPUCacheModel(BaseSettingsModel): enabled: bool = True - families: list[str] = Field(default_factory=list, title="Families") - step: float = Field(1.0, ge=1.0, title="Step") - stepSave: int = Field(1, ge=1, title="Step Save") - optimize: bool = Field(title="Optimize Hierarchy") - optimizationThreshold: int = Field(1, ge=1, title="Optimization Threshold") - optimizeAnimationsForMotionBlur: bool = Field( + families: list[str] = SettingsField(default_factory=list, title="Families") + step: float = SettingsField(1.0, ge=1.0, title="Step") + stepSave: int = SettingsField(1, ge=1, title="Step Save") + optimize: bool = SettingsField(title="Optimize Hierarchy") + optimizationThreshold: int = SettingsField( + 1, ge=1, title="Optimization Threshold" + ) + optimizeAnimationsForMotionBlur: bool = SettingsField( title="Optimize Animations For Motion Blur" ) - writeMaterials: bool = Field(title="Write Materials") - useBaseTessellation: bool = Field(title="User Base Tesselation") + writeMaterials: bool = SettingsField(title="Write Materials") + useBaseTessellation: bool = SettingsField(title="User Base Tesselation") class PublishersModel(BaseSettingsModel): - CollectMayaRender: CollectMayaRenderModel = Field( + CollectMayaRender: CollectMayaRenderModel = SettingsField( default_factory=CollectMayaRenderModel, title="Collect Render Layers", section="Collectors" ) - CollectFbxAnimation: CollectFbxAnimationModel = Field( + CollectFbxAnimation: CollectFbxAnimationModel = SettingsField( default_factory=CollectFbxAnimationModel, title="Collect FBX Animation", ) - CollectFbxCamera: CollectFbxCameraModel = Field( + CollectFbxCamera: CollectFbxCameraModel = SettingsField( default_factory=CollectFbxCameraModel, title="Collect Camera for FBX export", ) - CollectGLTF: CollectGLTFModel = Field( + CollectGLTF: CollectGLTFModel = SettingsField( default_factory=CollectGLTFModel, title="Collect Assets for GLB/GLTF export" ) - ValidateInstanceInContext: BasicValidateModel = Field( + ValidateInstanceInContext: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Instance In Context", section="Validators" ) - ValidateContainers: BasicValidateModel = Field( + ValidateContainers: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Containers" ) - ValidateFrameRange: ValidateFrameRangeModel = Field( + ValidateFrameRange: ValidateFrameRangeModel = SettingsField( default_factory=ValidateFrameRangeModel, title="Validate Frame Range" ) - ValidateShaderName: ValidateShaderNameModel = Field( + ValidateShaderName: ValidateShaderNameModel = SettingsField( default_factory=ValidateShaderNameModel, title="Validate Shader Name" ) - ValidateShadingEngine: BasicValidateModel = Field( + ValidateShadingEngine: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Look Shading Engine Naming" ) - ValidateMayaColorSpace: BasicValidateModel = Field( + ValidateMayaColorSpace: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Colorspace" ) - ValidateAttributes: ValidateAttributesModel = Field( + ValidateAttributes: ValidateAttributesModel = SettingsField( default_factory=ValidateAttributesModel, title="Validate Attributes" ) - ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( + ValidateLoadedPlugin: ValidateLoadedPluginModel = SettingsField( default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" ) - ValidateMayaUnits: ValidateMayaUnitsModel = Field( + ValidateMayaUnits: ValidateMayaUnitsModel = SettingsField( default_factory=ValidateMayaUnitsModel, title="Validate Maya Units" ) - ValidateUnrealStaticMeshName: ValidateUnrealStaticMeshNameModel = Field( - default_factory=ValidateUnrealStaticMeshNameModel, - title="Validate Unreal Static Mesh Name" + ValidateUnrealStaticMeshName: ValidateUnrealStaticMeshNameModel = ( + SettingsField( + default_factory=ValidateUnrealStaticMeshNameModel, + title="Validate Unreal Static Mesh Name" + ) ) - ValidateCycleError: ValidateCycleErrorModel = Field( + ValidateCycleError: ValidateCycleErrorModel = SettingsField( default_factory=ValidateCycleErrorModel, title="Validate Cycle Error" ) - ValidatePluginPathAttributes: ValidatePluginPathAttributesModel = Field( - default_factory=ValidatePluginPathAttributesModel, - title="Plug-in Path Attributes" + ValidatePluginPathAttributes: ValidatePluginPathAttributesModel = ( + SettingsField( + default_factory=ValidatePluginPathAttributesModel, + title="Plug-in Path Attributes" + ) ) - ValidateRenderSettings: ValidateRenderSettingsModel = Field( + ValidateRenderSettings: ValidateRenderSettingsModel = SettingsField( default_factory=ValidateRenderSettingsModel, title="Validate Render Settings" ) - ValidateResolution: BasicValidateModel = Field( + ValidateResolution: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Resolution Setting" ) - ValidateCurrentRenderLayerIsRenderable: BasicValidateModel = Field( - default_factory=BasicValidateModel, - title="Validate Current Render Layer Has Renderable Camera" + ValidateCurrentRenderLayerIsRenderable: BasicValidateModel = ( + SettingsField( + default_factory=BasicValidateModel, + title="Validate Current Render Layer Has Renderable Camera" + ) ) - ValidateGLSLMaterial: BasicValidateModel = Field( + ValidateGLSLMaterial: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate GLSL Material" ) - ValidateGLSLPlugin: BasicValidateModel = Field( + ValidateGLSLPlugin: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate GLSL Plugin" ) - ValidateRenderImageRule: BasicValidateModel = Field( + ValidateRenderImageRule: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Render Image Rule (Workspace)" ) - ValidateRenderNoDefaultCameras: BasicValidateModel = Field( + ValidateRenderNoDefaultCameras: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate No Default Cameras Renderable" ) - ValidateRenderSingleCamera: BasicValidateModel = Field( + ValidateRenderSingleCamera: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Render Single Camera " ) - ValidateRenderLayerAOVs: BasicValidateModel = Field( + ValidateRenderLayerAOVs: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Render Passes/AOVs Are Registered" ) - ValidateStepSize: BasicValidateModel = Field( + ValidateStepSize: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Step Size" ) - ValidateVRayDistributedRendering: BasicValidateModel = Field( + ValidateVRayDistributedRendering: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="VRay Distributed Rendering" ) - ValidateVrayReferencedAOVs: BasicValidateModel = Field( + ValidateVrayReferencedAOVs: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="VRay Referenced AOVs" ) - ValidateVRayTranslatorEnabled: BasicValidateModel = Field( + ValidateVRayTranslatorEnabled: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="VRay Translator Settings" ) - ValidateVrayProxy: BasicValidateModel = Field( + ValidateVrayProxy: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="VRay Proxy Settings" ) - ValidateVrayProxyMembers: BasicValidateModel = Field( + ValidateVrayProxyMembers: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="VRay Proxy Members" ) - ValidateYetiRenderScriptCallbacks: BasicValidateModel = Field( + ValidateYetiRenderScriptCallbacks: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Yeti Render Script Callbacks" ) - ValidateYetiRigCacheState: BasicValidateModel = Field( + ValidateYetiRigCacheState: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Yeti Rig Cache State" ) - ValidateYetiRigInputShapesInInstance: BasicValidateModel = Field( + ValidateYetiRigInputShapesInInstance: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Yeti Rig Input Shapes In Instance" ) - ValidateYetiRigSettings: BasicValidateModel = Field( + ValidateYetiRigSettings: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Yeti Rig Settings" ) # Model - START - ValidateModelName: ValidateModelNameModel = Field( + ValidateModelName: ValidateModelNameModel = SettingsField( default_factory=ValidateModelNameModel, title="Validate Model Name", section="Model", ) - ValidateModelContent: ValidateModelContentModel = Field( + ValidateModelContent: ValidateModelContentModel = SettingsField( default_factory=ValidateModelContentModel, title="Validate Model Content", ) - ValidateTransformNamingSuffix: ValidateTransformNamingSuffixModel = Field( - default_factory=ValidateTransformNamingSuffixModel, - title="Validate Transform Naming Suffix", + ValidateTransformNamingSuffix: ValidateTransformNamingSuffixModel = ( + SettingsField( + default_factory=ValidateTransformNamingSuffixModel, + title="Validate Transform Naming Suffix", + ) ) - ValidateColorSets: BasicValidateModel = Field( + ValidateColorSets: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Color Sets", ) - ValidateMeshHasOverlappingUVs: BasicValidateModel = Field( + ValidateMeshHasOverlappingUVs: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Has Overlapping UVs", ) - ValidateMeshArnoldAttributes: BasicValidateModel = Field( + ValidateMeshArnoldAttributes: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Arnold Attributes", ) - ValidateMeshShaderConnections: BasicValidateModel = Field( + ValidateMeshShaderConnections: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Shader Connections", ) - ValidateMeshSingleUVSet: BasicValidateModel = Field( + ValidateMeshSingleUVSet: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Single UV Set", ) - ValidateMeshHasUVs: BasicValidateModel = Field( + ValidateMeshHasUVs: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Has UVs", ) - ValidateMeshLaminaFaces: BasicValidateModel = Field( + ValidateMeshLaminaFaces: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Lamina Faces", ) - ValidateMeshNgons: BasicValidateModel = Field( + ValidateMeshNgons: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Ngons", ) - ValidateMeshNonManifold: BasicValidateModel = Field( + ValidateMeshNonManifold: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Non-Manifold", ) - ValidateMeshNoNegativeScale: BasicValidateModel = Field( + ValidateMeshNoNegativeScale: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh No Negative Scale", ) - ValidateMeshNonZeroEdgeLength: BasicValidateModel = Field( + ValidateMeshNonZeroEdgeLength: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Edge Length Non Zero", ) - ValidateMeshNormalsUnlocked: BasicValidateModel = Field( + ValidateMeshNormalsUnlocked: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Normals Unlocked", ) - ValidateMeshUVSetMap1: ValidateMeshUVSetMap1Model = Field( + ValidateMeshUVSetMap1: ValidateMeshUVSetMap1Model = SettingsField( default_factory=ValidateMeshUVSetMap1Model, title="Validate Mesh UV Set Map 1", ) - ValidateMeshVerticesHaveEdges: BasicValidateModel = Field( + ValidateMeshVerticesHaveEdges: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Mesh Vertices Have Edges", ) - ValidateNoAnimation: ValidateNoAnimationModel = Field( + ValidateNoAnimation: ValidateNoAnimationModel = SettingsField( default_factory=ValidateNoAnimationModel, title="Validate No Animation", ) - ValidateNoNamespace: BasicValidateModel = Field( + ValidateNoNamespace: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate No Namespace", ) - ValidateNoNullTransforms: BasicValidateModel = Field( + ValidateNoNullTransforms: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate No Null Transforms", ) - ValidateNoUnknownNodes: BasicValidateModel = Field( + ValidateNoUnknownNodes: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate No Unknown Nodes", ) - ValidateNodeNoGhosting: BasicValidateModel = Field( + ValidateNodeNoGhosting: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Node No Ghosting", ) - ValidateShapeDefaultNames: BasicValidateModel = Field( + ValidateShapeDefaultNames: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Shape Default Names", ) - ValidateShapeRenderStats: BasicValidateModel = Field( + ValidateShapeRenderStats: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Shape Render Stats", ) - ValidateShapeZero: BasicValidateModel = Field( + ValidateShapeZero: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Shape Zero", ) - ValidateTransformZero: BasicValidateModel = Field( + ValidateTransformZero: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Transform Zero", ) - ValidateUniqueNames: BasicValidateModel = Field( + ValidateUniqueNames: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Unique Names", ) - ValidateNoVRayMesh: BasicValidateModel = Field( + ValidateNoVRayMesh: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate No V-Ray Proxies (VRayMesh)", ) - ValidateUnrealMeshTriangulated: BasicValidateModel = Field( + ValidateUnrealMeshTriangulated: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate if Mesh is Triangulated", ) - ValidateAlembicVisibleOnly: BasicValidateModel = Field( + ValidateAlembicVisibleOnly: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Alembic Visible Node", ) - ExtractProxyAlembic: ExtractProxyAlembicModel = Field( + ExtractProxyAlembic: ExtractProxyAlembicModel = SettingsField( default_factory=ExtractProxyAlembicModel, title="Extract Proxy Alembic", section="Model Extractors", ) - ExtractAlembic: ExtractAlembicModel = Field( + ExtractAlembic: ExtractAlembicModel = SettingsField( default_factory=ExtractAlembicModel, title="Extract Alembic", ) - ExtractObj: ExtractObjModel = Field( + ExtractObj: ExtractObjModel = SettingsField( default_factory=ExtractObjModel, title="Extract OBJ" ) # Model - END # Rig - START - ValidateRigContents: BasicValidateModel = Field( + ValidateRigContents: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Rig Contents", section="Rig", ) - ValidateRigJointsHidden: BasicValidateModel = Field( + ValidateRigJointsHidden: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Rig Joints Hidden", ) - ValidateRigControllers: BasicValidateModel = Field( + ValidateRigControllers: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Rig Controllers", ) - ValidateAnimatedReferenceRig: BasicValidateModel = Field( + ValidateAnimatedReferenceRig: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Animated Reference Rig", ) - ValidateAnimationContent: BasicValidateModel = Field( + ValidateAnimationContent: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Animation Content", ) - ValidateOutRelatedNodeIds: BasicValidateModel = Field( + ValidateOutRelatedNodeIds: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Animation Out Set Related Node Ids", ) - ValidateRigControllersArnoldAttributes: BasicValidateModel = Field( - default_factory=BasicValidateModel, - title="Validate Rig Controllers (Arnold Attributes)", + ValidateRigControllersArnoldAttributes: BasicValidateModel = ( + SettingsField( + default_factory=BasicValidateModel, + title="Validate Rig Controllers (Arnold Attributes)", + ) ) - ValidateSkeletalMeshHierarchy: BasicValidateModel = Field( + ValidateSkeletalMeshHierarchy: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Skeletal Mesh Top Node", ) - ValidateSkeletonRigContents: BasicValidateModel = Field( + ValidateSkeletonRigContents: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Skeleton Rig Contents" ) - ValidateSkeletonRigControllers: BasicValidateModel = Field( + ValidateSkeletonRigControllers: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Skeleton Rig Controllers" ) - ValidateSkinclusterDeformerSet: BasicValidateModel = Field( + ValidateSkinclusterDeformerSet: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Skincluster Deformer Relationships", ) - ValidateSkeletonRigOutputIds: BasicValidateModel = Field( + ValidateSkeletonRigOutputIds: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Skeleton Rig Output Ids" ) - ValidateSkeletonTopGroupHierarchy: BasicValidateModel = Field( + ValidateSkeletonTopGroupHierarchy: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Skeleton Top Group Hierarchy", ) - ValidateRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field( + ValidateRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = SettingsField( default_factory=ValidateRigOutSetNodeIdsModel, title="Validate Rig Out Set Node Ids", ) - ValidateSkeletonRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field( - default_factory=ValidateRigOutSetNodeIdsModel, - title="Validate Skeleton Rig Out Set Node Ids", + ValidateSkeletonRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = ( + SettingsField( + default_factory=ValidateRigOutSetNodeIdsModel, + title="Validate Skeleton Rig Out Set Node Ids", + ) ) # Rig - END - ValidateCameraAttributes: BasicValidateModel = Field( + ValidateCameraAttributes: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Camera Attributes" ) - ValidateAssemblyName: BasicValidateModel = Field( + ValidateAssemblyName: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Assembly Name" ) - ValidateAssemblyNamespaces: BasicValidateModel = Field( + ValidateAssemblyNamespaces: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Assembly Namespaces" ) - ValidateAssemblyModelTransforms: BasicValidateModel = Field( + ValidateAssemblyModelTransforms: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Assembly Model Transforms" ) - ValidateAssRelativePaths: BasicValidateModel = Field( + ValidateAssRelativePaths: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Ass Relative Paths" ) - ValidateInstancerContent: BasicValidateModel = Field( + ValidateInstancerContent: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Instancer Content" ) - ValidateInstancerFrameRanges: BasicValidateModel = Field( + ValidateInstancerFrameRanges: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Instancer Cache Frame Ranges" ) - ValidateNoDefaultCameras: BasicValidateModel = Field( + ValidateNoDefaultCameras: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate No Default Cameras" ) - ValidateUnrealUpAxis: BasicValidateModel = Field( + ValidateUnrealUpAxis: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Validate Unreal Up-Axis Check" ) - ValidateCameraContents: ValidateCameraContentsModel = Field( + ValidateCameraContents: ValidateCameraContentsModel = SettingsField( default_factory=ValidateCameraContentsModel, title="Validate Camera Content" ) - ExtractPlayblast: ExtractPlayblastSetting = Field( + ExtractPlayblast: ExtractPlayblastSetting = SettingsField( default_factory=ExtractPlayblastSetting, title="Extract Playblast Settings", section="Extractors" ) - ExtractMayaSceneRaw: ExtractMayaSceneRawModel = Field( + ExtractMayaSceneRaw: ExtractMayaSceneRawModel = SettingsField( default_factory=ExtractMayaSceneRawModel, title="Maya Scene(Raw)" ) - ExtractCameraAlembic: ExtractCameraAlembicModel = Field( + ExtractCameraAlembic: ExtractCameraAlembicModel = SettingsField( default_factory=ExtractCameraAlembicModel, title="Extract Camera Alembic" ) - ExtractGLB: ExtractGLBModel = Field( + ExtractGLB: ExtractGLBModel = SettingsField( default_factory=ExtractGLBModel, title="Extract GLB" ) - ExtractLook: ExtractLookModel = Field( + ExtractLook: ExtractLookModel = SettingsField( default_factory=ExtractLookModel, title="Extract Look" ) - ExtractGPUCache: ExtractGPUCacheModel = Field( + ExtractGPUCache: ExtractGPUCacheModel = SettingsField( default_factory=ExtractGPUCacheModel, title="Extract GPU Cache", ) diff --git a/server_addon/maya/server/settings/render_settings.py b/server_addon/maya/server/settings/render_settings.py index b6163a04ce..15ff177e7f 100644 --- a/server_addon/maya/server/settings/render_settings.py +++ b/server_addon/maya/server/settings/render_settings.py @@ -1,7 +1,5 @@ """Providing models and values for Maya Render Settings.""" -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField def aov_separators_enum(): @@ -278,22 +276,22 @@ class AdditionalOptionsModel(BaseSettingsModel): """Additional Option""" _layout = "compact" - attribute: str = Field("", title="Attribute name") - value: str = Field("", title="Value") + attribute: str = SettingsField("", title="Attribute name") + value: str = SettingsField("", title="Value") class ArnoldSettingsModel(BaseSettingsModel): - image_prefix: str = Field(title="Image prefix template") - image_format: str = Field( + image_prefix: str = SettingsField(title="Image prefix template") + image_format: str = SettingsField( enum_resolver=arnold_image_format_enum, title="Output Image Format") - multilayer_exr: bool = Field(title="Multilayer (exr)") - tiled: bool = Field(title="Tiled (tif, exr)") - aov_list: list[str] = Field( + multilayer_exr: bool = SettingsField(title="Multilayer (exr)") + tiled: bool = SettingsField(title="Tiled (tif, exr)") + aov_list: list[str] = SettingsField( default_factory=list, enum_resolver=arnold_aov_list_enum, title="AOVs to create" ) - additional_options: list[AdditionalOptionsModel] = Field( + additional_options: list[AdditionalOptionsModel] = SettingsField( default_factory=list, title="Additional Arnold Options", description=( @@ -303,25 +301,25 @@ class ArnoldSettingsModel(BaseSettingsModel): class VraySettingsModel(BaseSettingsModel): - image_prefix: str = Field(title="Image prefix template") + image_prefix: str = SettingsField(title="Image prefix template") # engine was str because of JSON limitation (key must be string) - engine: str = Field( + engine: str = SettingsField( enum_resolver=lambda: [ {"label": "V-Ray", "value": "1"}, {"label": "V-Ray GPU", "value": "2"} ], title="Production Engine" ) - image_format: str = Field( + image_format: str = SettingsField( enum_resolver=vray_image_output_enum, title="Output Image Format" ) - aov_list: list[str] = Field( + aov_list: list[str] = SettingsField( default_factory=list, enum_resolver=vray_aov_list_enum, title="AOVs to create" ) - additional_options: list[AdditionalOptionsModel] = Field( + additional_options: list[AdditionalOptionsModel] = SettingsField( default_factory=list, title="Additional Vray Options", description=( @@ -332,29 +330,29 @@ class VraySettingsModel(BaseSettingsModel): class RedshiftSettingsModel(BaseSettingsModel): - image_prefix: str = Field(title="Image prefix template") + image_prefix: str = SettingsField(title="Image prefix template") # both engines are using the same enumerator, # both were originally str because of JSON limitation. - primary_gi_engine: str = Field( + primary_gi_engine: str = SettingsField( enum_resolver=redshift_engine_enum, title="Primary GI Engine" ) - secondary_gi_engine: str = Field( + secondary_gi_engine: str = SettingsField( enum_resolver=redshift_engine_enum, title="Secondary GI Engine" ) - image_format: str = Field( + image_format: str = SettingsField( enum_resolver=redshift_image_output_enum, title="Output Image Format" ) - multilayer_exr: bool = Field(title="Multilayer (exr)") - force_combine: bool = Field(title="Force combine beauty and AOVs") - aov_list: list[str] = Field( + multilayer_exr: bool = SettingsField(title="Multilayer (exr)") + force_combine: bool = SettingsField(title="Force combine beauty and AOVs") + aov_list: list[str] = SettingsField( default_factory=list, enum_resolver=redshift_aov_list_enum, title="AOVs to create" ) - additional_options: list[AdditionalOptionsModel] = Field( + additional_options: list[AdditionalOptionsModel] = SettingsField( default_factory=list, title="Additional Vray Options", description=( @@ -396,61 +394,61 @@ def renderman_sample_filters_enum(): class RendermanSettingsModel(BaseSettingsModel): - image_prefix: str = Field( + image_prefix: str = SettingsField( "", title="Image prefix template") - image_dir: str = Field( + image_dir: str = SettingsField( "", title="Image Output Directory") - display_filters: list[str] = Field( + display_filters: list[str] = SettingsField( default_factory=list, title="Display Filters", enum_resolver=renderman_display_filters ) - imageDisplay_dir: str = Field( + imageDisplay_dir: str = SettingsField( "", title="Image Display Filter Directory") - sample_filters: list[str] = Field( + sample_filters: list[str] = SettingsField( default_factory=list, title="Sample Filters", enum_resolver=renderman_sample_filters_enum ) - cryptomatte_dir: str = Field( + cryptomatte_dir: str = SettingsField( "", title="Cryptomatte Output Directory") - watermark_dir: str = Field( + watermark_dir: str = SettingsField( "", title="Watermark Filter Directory") - additional_options: list[AdditionalOptionsModel] = Field( + additional_options: list[AdditionalOptionsModel] = SettingsField( default_factory=list, title="Additional Renderer Options" ) class RenderSettingsModel(BaseSettingsModel): - apply_render_settings: bool = Field( + apply_render_settings: bool = SettingsField( title="Apply Render Settings on creation" ) - default_render_image_folder: str = Field( + default_render_image_folder: str = SettingsField( title="Default render image folder" ) - enable_all_lights: bool = Field( + enable_all_lights: bool = SettingsField( title="Include all lights in Render Setup Layers by default" ) - aov_separator: str = Field( + aov_separator: str = SettingsField( "underscore", title="AOV Separator character", enum_resolver=aov_separators_enum ) - reset_current_frame: bool = Field( + reset_current_frame: bool = SettingsField( title="Reset Current Frame") - remove_aovs: bool = Field( + remove_aovs: bool = SettingsField( title="Remove existing AOVs") - arnold_renderer: ArnoldSettingsModel = Field( + arnold_renderer: ArnoldSettingsModel = SettingsField( default_factory=ArnoldSettingsModel, title="Arnold Renderer") - vray_renderer: VraySettingsModel = Field( + vray_renderer: VraySettingsModel = SettingsField( default_factory=VraySettingsModel, title="Vray Renderer") - redshift_renderer: RedshiftSettingsModel = Field( + redshift_renderer: RedshiftSettingsModel = SettingsField( default_factory=RedshiftSettingsModel, title="Redshift Renderer") - renderman_renderer: RendermanSettingsModel = Field( + renderman_renderer: RendermanSettingsModel = SettingsField( default_factory=RendermanSettingsModel, title="Renderman Renderer") diff --git a/server_addon/maya/server/settings/scriptsmenu.py b/server_addon/maya/server/settings/scriptsmenu.py index 4ac2263f7a..6de43b5278 100644 --- a/server_addon/maya/server/settings/scriptsmenu.py +++ b/server_addon/maya/server/settings/scriptsmenu.py @@ -1,24 +1,22 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class ScriptsmenuSubmodel(BaseSettingsModel): """Item Definition""" _isGroup = True - type: str = Field(title="Type") - command: str = Field(title="Command") - sourcetype: str = Field(title="Source Type") - title: str = Field(title="Title") - tooltip: str = Field(title="Tooltip") - tags: list[str] = Field(default_factory=list, title="A list of tags") + type: str = SettingsField(title="Type") + command: str = SettingsField(title="Command") + sourcetype: str = SettingsField(title="Source Type") + title: str = SettingsField(title="Title") + tooltip: str = SettingsField(title="Tooltip") + tags: list[str] = SettingsField(default_factory=list, title="A list of tags") class ScriptsmenuModel(BaseSettingsModel): _isGroup = True - name: str = Field(title="Menu Name") - definition: list[ScriptsmenuSubmodel] = Field( + name: str = SettingsField(title="Menu Name") + definition: list[ScriptsmenuSubmodel] = SettingsField( default_factory=list, title="Menu Definition", description="Scriptmenu Items Definition" diff --git a/server_addon/maya/server/settings/templated_workfile_settings.py b/server_addon/maya/server/settings/templated_workfile_settings.py index ef81b31a07..f61f52f9ea 100644 --- a/server_addon/maya/server/settings/templated_workfile_settings.py +++ b/server_addon/maya/server/settings/templated_workfile_settings.py @@ -1,20 +1,23 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel, task_types_enum +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + task_types_enum, +) class WorkfileBuildProfilesModel(BaseSettingsModel): _layout = "expanded" - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field(default_factory=list, title="Task names") - path: str = Field("", title="Path to template") + task_names: list[str] = SettingsField(default_factory=list, title="Task names") + path: str = SettingsField("", title="Path to template") class TemplatedProfilesModel(BaseSettingsModel): - profiles: list[WorkfileBuildProfilesModel] = Field( + profiles: list[WorkfileBuildProfilesModel] = SettingsField( default_factory=list, title="Profiles" ) diff --git a/server_addon/maya/server/settings/workfile_build_settings.py b/server_addon/maya/server/settings/workfile_build_settings.py index 2c7fea85c4..ee0b793405 100644 --- a/server_addon/maya/server/settings/workfile_build_settings.py +++ b/server_addon/maya/server/settings/workfile_build_settings.py @@ -1,38 +1,41 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel, task_types_enum +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + task_types_enum, +) class ContextItemModel(BaseSettingsModel): _layout = "expanded" - product_name_filters: list[str] = Field( + product_name_filters: list[str] = SettingsField( default_factory=list, title="Product name Filters") - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types") - repre_names: list[str] = Field( + repre_names: list[str] = SettingsField( default_factory=list, title="Repre Names") - loaders: list[str] = Field( + loaders: list[str] = SettingsField( default_factory=list, title="Loaders") class WorkfileSettingModel(BaseSettingsModel): _layout = "expanded" - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, enum_resolver=task_types_enum, title="Task types") - tasks: list[str] = Field( + tasks: list[str] = SettingsField( default_factory=list, title="Task names") - current_context: list[ContextItemModel] = Field( + current_context: list[ContextItemModel] = SettingsField( default_factory=list, title="Current Context") - linked_assets: list[ContextItemModel] = Field( + linked_assets: list[ContextItemModel] = SettingsField( default_factory=list, title="Linked Assets") class ProfilesModel(BaseSettingsModel): - profiles: list[WorkfileSettingModel] = Field( + profiles: list[WorkfileSettingModel] = SettingsField( default_factory=list, title="Profiles" ) diff --git a/server_addon/muster/server/settings.py b/server_addon/muster/server/settings.py index e37c762870..e4a487c799 100644 --- a/server_addon/muster/server/settings.py +++ b/server_addon/muster/server/settings.py @@ -1,22 +1,21 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class TemplatesMapping(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: int = Field(title="mapping") + name: str = SettingsField(title="Name") + value: int = SettingsField(title="mapping") class MusterSettings(BaseSettingsModel): enabled: bool = True - MUSTER_REST_URL: str = Field( + MUSTER_REST_URL: str = SettingsField( "", title="Muster Rest URL", scope=["studio"], ) - templates_mapping: list[TemplatesMapping] = Field( + templates_mapping: list[TemplatesMapping] = SettingsField( default_factory=list, title="Templates mapping", ) diff --git a/server_addon/nuke/server/settings/common.py b/server_addon/nuke/server/settings/common.py index 2bc3c9be81..e0ee2b7b3d 100644 --- a/server_addon/nuke/server/settings/common.py +++ b/server_addon/nuke/server/settings/common.py @@ -1,7 +1,6 @@ import json -from pydantic import Field from ayon_server.exceptions import BadRequestException -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.types import ( ColorRGBA_float, ColorRGB_uint8 @@ -27,25 +26,25 @@ def validate_json_dict(value): class Vector2d(BaseSettingsModel): _layout = "compact" - x: float = Field(1.0, title="X") - y: float = Field(1.0, title="Y") + x: float = SettingsField(1.0, title="X") + y: float = SettingsField(1.0, title="Y") class Vector3d(BaseSettingsModel): _layout = "compact" - x: float = Field(1.0, title="X") - y: float = Field(1.0, title="Y") - z: float = Field(1.0, title="Z") + x: float = SettingsField(1.0, title="X") + y: float = SettingsField(1.0, title="Y") + z: float = SettingsField(1.0, title="Z") class Box(BaseSettingsModel): _layout = "compact" - x: float = Field(1.0, title="X") - y: float = Field(1.0, title="Y") - r: float = Field(1.0, title="R") - t: float = Field(1.0, title="T") + x: float = SettingsField(1.0, title="X") + y: float = SettingsField(1.0, title="Y") + r: float = SettingsField(1.0, title="R") + t: float = SettingsField(1.0, title="T") def formatable_knob_type_enum(): @@ -61,12 +60,12 @@ def formatable_knob_type_enum(): class Formatable(BaseSettingsModel): _layout = "compact" - template: str = Field( + template: str = SettingsField( "", placeholder="""{{key}} or {{key}};{{key}}""", title="Template" ) - to_type: str = Field( + to_type: str = SettingsField( "Text", title="To Knob type", enum_resolver=formatable_knob_type_enum, @@ -91,46 +90,46 @@ knob_types_enum = [ class KnobModel(BaseSettingsModel): _layout = "expanded" - type: str = Field( + type: str = SettingsField( title="Type", description="Switch between different knob types", enum_resolver=lambda: knob_types_enum, conditionalEnum=True ) - name: str = Field( + name: str = SettingsField( title="Name", placeholder="Name" ) - text: str = Field("", title="Value") - color_gui: ColorRGB_uint8 = Field( + text: str = SettingsField("", title="Value") + color_gui: ColorRGB_uint8 = SettingsField( (0, 0, 255), title="RGB Uint8", ) - boolean: bool = Field(False, title="Value") - number: int = Field(0, title="Value") - decimal_number: float = Field(0.0, title="Value") - vector_2d: Vector2d = Field( + boolean: bool = SettingsField(False, title="Value") + number: int = SettingsField(0, title="Value") + decimal_number: float = SettingsField(0.0, title="Value") + vector_2d: Vector2d = SettingsField( default_factory=Vector2d, title="Value" ) - vector_3d: Vector3d = Field( + vector_3d: Vector3d = SettingsField( default_factory=Vector3d, title="Value" ) - color: ColorRGBA_float = Field( + color: ColorRGBA_float = SettingsField( (0.0, 0.0, 1.0, 1.0), title="RGBA Float" ) - box: Box = Field( + box: Box = SettingsField( default_factory=Box, title="Value" ) - formatable: Formatable = Field( + formatable: Formatable = SettingsField( default_factory=Formatable, title="Formatable" ) - expression: str = Field( + expression: str = SettingsField( "", title="Expression" ) diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py index 80aec51ae0..378d449b0b 100644 --- a/server_addon/nuke/server/settings/create_plugins.py +++ b/server_addon/nuke/server/settings/create_plugins.py @@ -1,6 +1,7 @@ -from pydantic import validator, Field +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ensure_unique_names ) from .common import KnobModel @@ -16,20 +17,20 @@ def instance_attributes_enum(): class PrenodeModel(BaseSettingsModel): - name: str = Field( + name: str = SettingsField( title="Node name" ) - nodeclass: str = Field( + nodeclass: str = SettingsField( "", title="Node class" ) - dependent: str = Field( + dependent: str = SettingsField( "", title="Incoming dependency" ) - knobs: list[KnobModel] = Field( + knobs: list[KnobModel] = SettingsField( default_factory=list, title="Knobs", ) @@ -42,20 +43,20 @@ class PrenodeModel(BaseSettingsModel): class CreateWriteRenderModel(BaseSettingsModel): - temp_rendering_path_template: str = Field( + temp_rendering_path_template: str = SettingsField( title="Temporary rendering path template" ) - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( title="Default variants", default_factory=list ) - instance_attributes: list[str] = Field( + instance_attributes: list[str] = SettingsField( default_factory=list, enum_resolver=instance_attributes_enum, title="Instance attributes" ) - prenodes: list[PrenodeModel] = Field( + prenodes: list[PrenodeModel] = SettingsField( default_factory=list, title="Preceding nodes", ) @@ -68,20 +69,20 @@ class CreateWriteRenderModel(BaseSettingsModel): class CreateWritePrerenderModel(BaseSettingsModel): - temp_rendering_path_template: str = Field( + temp_rendering_path_template: str = SettingsField( title="Temporary rendering path template" ) - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( title="Default variants", default_factory=list ) - instance_attributes: list[str] = Field( + instance_attributes: list[str] = SettingsField( default_factory=list, enum_resolver=instance_attributes_enum, title="Instance attributes" ) - prenodes: list[PrenodeModel] = Field( + prenodes: list[PrenodeModel] = SettingsField( default_factory=list, title="Preceding nodes", ) @@ -94,20 +95,20 @@ class CreateWritePrerenderModel(BaseSettingsModel): class CreateWriteImageModel(BaseSettingsModel): - temp_rendering_path_template: str = Field( + temp_rendering_path_template: str = SettingsField( title="Temporary rendering path template" ) - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( title="Default variants", default_factory=list ) - instance_attributes: list[str] = Field( + instance_attributes: list[str] = SettingsField( default_factory=list, enum_resolver=instance_attributes_enum, title="Instance attributes" ) - prenodes: list[PrenodeModel] = Field( + prenodes: list[PrenodeModel] = SettingsField( default_factory=list, title="Preceding nodes", ) @@ -120,15 +121,15 @@ class CreateWriteImageModel(BaseSettingsModel): class CreatorPluginsSettings(BaseSettingsModel): - CreateWriteRender: CreateWriteRenderModel = Field( + CreateWriteRender: CreateWriteRenderModel = SettingsField( default_factory=CreateWriteRenderModel, title="Create Write Render" ) - CreateWritePrerender: CreateWritePrerenderModel = Field( + CreateWritePrerender: CreateWritePrerenderModel = SettingsField( default_factory=CreateWritePrerenderModel, title="Create Write Prerender" ) - CreateWriteImage: CreateWriteImageModel = Field( + CreateWriteImage: CreateWriteImageModel = SettingsField( default_factory=CreateWriteImageModel, title="Create Write Image" ) diff --git a/server_addon/nuke/server/settings/dirmap.py b/server_addon/nuke/server/settings/dirmap.py index 7e3c443957..3e1bac0739 100644 --- a/server_addon/nuke/server/settings/dirmap.py +++ b/server_addon/nuke/server/settings/dirmap.py @@ -1,14 +1,13 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class DirmapPathsSubmodel(BaseSettingsModel): _layout = "compact" - source_path: list[str] = Field( + source_path: list[str] = SettingsField( default_factory=list, title="Source Paths" ) - destination_path: list[str] = Field( + destination_path: list[str] = SettingsField( default_factory=list, title="Destination Paths" ) @@ -18,8 +17,8 @@ class DirmapSettings(BaseSettingsModel): """Nuke color management project settings.""" _isGroup: bool = True - enabled: bool = Field(title="enabled") - paths: DirmapPathsSubmodel = Field( + enabled: bool = SettingsField(title="enabled") + paths: DirmapPathsSubmodel = SettingsField( default_factory=DirmapPathsSubmodel, title="Dirmap Paths" ) diff --git a/server_addon/nuke/server/settings/general.py b/server_addon/nuke/server/settings/general.py index bcbb183952..d54c725dc1 100644 --- a/server_addon/nuke/server/settings/general.py +++ b/server_addon/nuke/server/settings/general.py @@ -1,23 +1,22 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class MenuShortcut(BaseSettingsModel): """Nuke general project settings.""" - create: str = Field( + create: str = SettingsField( title="Create..." ) - publish: str = Field( + publish: str = SettingsField( title="Publish..." ) - load: str = Field( + load: str = SettingsField( title="Load..." ) - manage: str = Field( + manage: str = SettingsField( title="Manage..." ) - build_workfile: str = Field( + build_workfile: str = SettingsField( title="Build Workfile..." ) @@ -25,7 +24,7 @@ class MenuShortcut(BaseSettingsModel): class GeneralSettings(BaseSettingsModel): """Nuke general project settings.""" - menu: MenuShortcut = Field( + menu: MenuShortcut = SettingsField( default_factory=MenuShortcut, title="Menu Shortcuts", ) diff --git a/server_addon/nuke/server/settings/gizmo.py b/server_addon/nuke/server/settings/gizmo.py index 4cdd614da8..ddb56f891c 100644 --- a/server_addon/nuke/server/settings/gizmo.py +++ b/server_addon/nuke/server/settings/gizmo.py @@ -1,52 +1,52 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, + SettingsField, MultiplatformPathModel, MultiplatformPathListModel, ) class SubGizmoItem(BaseSettingsModel): - title: str = Field( + title: str = SettingsField( title="Label" ) - sourcetype: str = Field( + sourcetype: str = SettingsField( title="Type of usage" ) - command: str = Field( + command: str = SettingsField( title="Python command" ) - icon: str = Field( + icon: str = SettingsField( title="Icon Path" ) - shortcut: str = Field( + shortcut: str = SettingsField( title="Hotkey" ) class GizmoDefinitionItem(BaseSettingsModel): - gizmo_toolbar_path: str = Field( + gizmo_toolbar_path: str = SettingsField( title="Gizmo Menu" ) - sub_gizmo_list: list[SubGizmoItem] = Field( + sub_gizmo_list: list[SubGizmoItem] = SettingsField( default_factory=list, title="Sub Gizmo List") class GizmoItem(BaseSettingsModel): """Nuke gizmo item """ - toolbar_menu_name: str = Field( + toolbar_menu_name: str = SettingsField( title="Toolbar Menu Name" ) - gizmo_source_dir: MultiplatformPathListModel = Field( + gizmo_source_dir: MultiplatformPathListModel = SettingsField( default_factory=MultiplatformPathListModel, title="Gizmo Directory Path" ) - toolbar_icon_path: MultiplatformPathModel = Field( + toolbar_icon_path: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel, title="Toolbar Icon Path" ) - gizmo_definition: list[GizmoDefinitionItem] = Field( + gizmo_definition: list[GizmoDefinitionItem] = SettingsField( default_factory=list, title="Gizmo Definition") diff --git a/server_addon/nuke/server/settings/imageio.py b/server_addon/nuke/server/settings/imageio.py index 19ad5ff24a..1b84457133 100644 --- a/server_addon/nuke/server/settings/imageio.py +++ b/server_addon/nuke/server/settings/imageio.py @@ -1,7 +1,8 @@ from typing import Literal -from pydantic import validator, Field +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ensure_unique_names, ) @@ -10,17 +11,17 @@ from .common import KnobModel class NodesModel(BaseSettingsModel): _layout = "expanded" - plugins: list[str] = Field( + plugins: list[str] = SettingsField( default_factory=list, title="Used in plugins" ) - nuke_node_class: str = Field( + nuke_node_class: str = SettingsField( title="Nuke Node Class", ) class RequiredNodesModel(NodesModel): - knobs: list[KnobModel] = Field( + knobs: list[KnobModel] = SettingsField( default_factory=list, title="Knobs", ) @@ -33,12 +34,12 @@ class RequiredNodesModel(NodesModel): class OverrideNodesModel(NodesModel): - subsets: list[str] = Field( + subsets: list[str] = SettingsField( default_factory=list, title="Subsets" ) - knobs: list[KnobModel] = Field( + knobs: list[KnobModel] = SettingsField( default_factory=list, title="Knobs", ) @@ -51,11 +52,11 @@ class OverrideNodesModel(NodesModel): class NodesSetting(BaseSettingsModel): - required_nodes: list[RequiredNodesModel] = Field( + required_nodes: list[RequiredNodesModel] = SettingsField( title="Plugin required", default_factory=list ) - override_nodes: list[OverrideNodesModel] = Field( + override_nodes: list[OverrideNodesModel] = SettingsField( title="Plugin's node overrides", default_factory=list ) @@ -82,21 +83,21 @@ def ocio_configs_switcher_enum(): class WorkfileColorspaceSettings(BaseSettingsModel): """Nuke workfile colorspace preset. """ - color_management: Literal["Nuke", "OCIO"] = Field( + color_management: Literal["Nuke", "OCIO"] = SettingsField( title="Color Management Workflow" ) - native_ocio_config: str = Field( + native_ocio_config: str = SettingsField( title="Native OpenColorIO Config", description="Switch between native OCIO configs", enum_resolver=ocio_configs_switcher_enum, conditionalEnum=True ) - working_space: str = Field( + working_space: str = SettingsField( title="Working Space" ) - thumbnail_space: str = Field( + thumbnail_space: str = SettingsField( title="Thumbnail Space" ) @@ -104,44 +105,44 @@ class WorkfileColorspaceSettings(BaseSettingsModel): class ReadColorspaceRulesItems(BaseSettingsModel): _layout = "expanded" - regex: str = Field("", title="Regex expression") - colorspace: str = Field("", title="Colorspace") + regex: str = SettingsField("", title="Regex expression") + colorspace: str = SettingsField("", title="Colorspace") class RegexInputsModel(BaseSettingsModel): - inputs: list[ReadColorspaceRulesItems] = Field( + inputs: list[ReadColorspaceRulesItems] = SettingsField( default_factory=list, title="Inputs" ) class ViewProcessModel(BaseSettingsModel): - viewerProcess: str = Field( + viewerProcess: str = SettingsField( title="Viewer Process Name" ) class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -162,17 +163,17 @@ class ImageIOSettings(BaseSettingsModel): now: nuke/imageio/viewer/viewerProcess future: nuke/imageio/viewer """ - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management") - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) - viewer: ViewProcessModel = Field( + viewer: ViewProcessModel = SettingsField( default_factory=ViewProcessModel, title="Viewer", description="""Viewer profile is used during @@ -185,19 +186,19 @@ class ImageIOSettings(BaseSettingsModel): now: nuke/imageio/baking/viewerProcess future: nuke/imageio/baking """ - baking: ViewProcessModel = Field( + baking: ViewProcessModel = SettingsField( default_factory=ViewProcessModel, title="Baking", description="""Baking profile is used during publishing baked colorspace data at knob viewerProcess""" ) - workfile: WorkfileColorspaceSettings = Field( + workfile: WorkfileColorspaceSettings = SettingsField( default_factory=WorkfileColorspaceSettings, title="Workfile" ) - nodes: NodesSetting = Field( + nodes: NodesSetting = SettingsField( default_factory=NodesSetting, title="Nodes" ) @@ -205,7 +206,7 @@ class ImageIOSettings(BaseSettingsModel): - [ ] no need for `inputs` middle part. It can stay directly on `regex_inputs` """ - regex_inputs: RegexInputsModel = Field( + regex_inputs: RegexInputsModel = SettingsField( default_factory=RegexInputsModel, title="Assign colorspace to read nodes via rules" ) diff --git a/server_addon/nuke/server/settings/loader_plugins.py b/server_addon/nuke/server/settings/loader_plugins.py index 51e2c2149b..a5c3315fd4 100644 --- a/server_addon/nuke/server/settings/loader_plugins.py +++ b/server_addon/nuke/server/settings/loader_plugins.py @@ -1,54 +1,53 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class LoadImageModel(BaseSettingsModel): - enabled: bool = Field( + enabled: bool = SettingsField( title="Enabled" ) - representations_include: list[str] = Field( + representations_include: list[str] = SettingsField( default_factory=list, title="Include representations" ) - node_name_template: str = Field( + node_name_template: str = SettingsField( title="Read node name template" ) class LoadClipOptionsModel(BaseSettingsModel): - start_at_workfile: bool = Field( + start_at_workfile: bool = SettingsField( title="Start at workfile's start frame" ) - add_retime: bool = Field( + add_retime: bool = SettingsField( title="Add retime" ) class LoadClipModel(BaseSettingsModel): - enabled: bool = Field( + enabled: bool = SettingsField( title="Enabled" ) - representations_include: list[str] = Field( + representations_include: list[str] = SettingsField( default_factory=list, title="Include representations" ) - node_name_template: str = Field( + node_name_template: str = SettingsField( title="Read node name template" ) - options_defaults: LoadClipOptionsModel = Field( + options_defaults: LoadClipOptionsModel = SettingsField( default_factory=LoadClipOptionsModel, title="Loader option defaults" ) class LoaderPuginsModel(BaseSettingsModel): - LoadImage: LoadImageModel = Field( + LoadImage: LoadImageModel = SettingsField( default_factory=LoadImageModel, title="Load Image" ) - LoadClip: LoadClipModel = Field( + LoadClip: LoadClipModel = SettingsField( default_factory=LoadClipModel, title="Load Clip" ) diff --git a/server_addon/nuke/server/settings/main.py b/server_addon/nuke/server/settings/main.py index b6729e7c2f..2cfc539e71 100644 --- a/server_addon/nuke/server/settings/main.py +++ b/server_addon/nuke/server/settings/main.py @@ -1,7 +1,8 @@ -from pydantic import validator, Field +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ensure_unique_names ) @@ -49,50 +50,50 @@ from .templated_workfile_build import ( class NukeSettings(BaseSettingsModel): """Nuke addon settings.""" - general: GeneralSettings = Field( + general: GeneralSettings = SettingsField( default_factory=GeneralSettings, title="General", ) - imageio: ImageIOSettings = Field( + imageio: ImageIOSettings = SettingsField( default_factory=ImageIOSettings, title="Color Management (imageio)", ) - dirmap: DirmapSettings = Field( + dirmap: DirmapSettings = SettingsField( default_factory=DirmapSettings, title="Nuke Directory Mapping", ) - scriptsmenu: ScriptsmenuSettings = Field( + scriptsmenu: ScriptsmenuSettings = SettingsField( default_factory=ScriptsmenuSettings, title="Scripts Menu Definition", ) - gizmo: list[GizmoItem] = Field( + gizmo: list[GizmoItem] = SettingsField( default_factory=list, title="Gizmo Menu") - create: CreatorPluginsSettings = Field( + create: CreatorPluginsSettings = SettingsField( default_factory=CreatorPluginsSettings, title="Creator Plugins", ) - publish: PublishPuginsModel = Field( + publish: PublishPuginsModel = SettingsField( default_factory=PublishPuginsModel, title="Publish Plugins", ) - load: LoaderPuginsModel = Field( + load: LoaderPuginsModel = SettingsField( default_factory=LoaderPuginsModel, title="Loader Plugins", ) - workfile_builder: WorkfileBuilderModel = Field( + workfile_builder: WorkfileBuilderModel = SettingsField( default_factory=WorkfileBuilderModel, title="Workfile Builder", ) - templated_workfile_build: TemplatedWorkfileBuildModel = Field( + templated_workfile_build: TemplatedWorkfileBuildModel = SettingsField( title="Templated Workfile Build", default_factory=TemplatedWorkfileBuildModel ) diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index 84457d2484..0d785e6505 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -1,6 +1,7 @@ -from pydantic import validator, Field +from pydantic import validator from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ensure_unique_names, task_types_enum ) @@ -28,18 +29,18 @@ def nuke_product_types_enum(): class NodeModel(BaseSettingsModel): - name: str = Field( + name: str = SettingsField( title="Node name" ) - nodeclass: str = Field( + nodeclass: str = SettingsField( "", title="Node class" ) - dependent: str = Field( + dependent: str = SettingsField( "", title="Incoming dependency" ) - knobs: list[KnobModel] = Field( + knobs: list[KnobModel] = SettingsField( default_factory=list, title="Knobs", ) @@ -52,7 +53,7 @@ class NodeModel(BaseSettingsModel): class CollectInstanceDataModel(BaseSettingsModel): - sync_workfile_version_on_product_types: list[str] = Field( + sync_workfile_version_on_product_types: list[str] = SettingsField( default_factory=list, enum_resolver=nuke_product_types_enum, title="Sync workfile versions for familes" @@ -60,14 +61,14 @@ class CollectInstanceDataModel(BaseSettingsModel): class OptionalPluginModel(BaseSettingsModel): - enabled: bool = Field(True) - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") + enabled: bool = SettingsField(True) + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") class ValidateKnobsModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - knobs: str = Field( + enabled: bool = SettingsField(title="Enabled") + knobs: str = SettingsField( "{}", title="Knobs", widget="textarea", @@ -79,31 +80,31 @@ class ValidateKnobsModel(BaseSettingsModel): class ExtractReviewDataModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") + enabled: bool = SettingsField(title="Enabled") class ExtractReviewDataLutModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") + enabled: bool = SettingsField(title="Enabled") class BakingStreamFilterModel(BaseSettingsModel): - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, enum_resolver=nuke_render_publish_types_enum, title="Sync workfile versions for familes" ) - product_names: list[str] = Field( + product_names: list[str] = SettingsField( default_factory=list, title="Product names") class ReformatNodesRepositionNodes(BaseSettingsModel): - node_class: str = Field(title="Node class") - knobs: list[KnobModel] = Field( + node_class: str = SettingsField(title="Node class") + knobs: list[KnobModel] = SettingsField( default_factory=list, title="Node knobs") @@ -115,41 +116,41 @@ class ReformatNodesConfigModel(BaseSettingsModel): Order of reformat nodes is important. First reformat node will be applied first and last reformat node will be applied last. """ - enabled: bool = Field(False) - reposition_nodes: list[ReformatNodesRepositionNodes] = Field( + enabled: bool = SettingsField(False) + reposition_nodes: list[ReformatNodesRepositionNodes] = SettingsField( default_factory=list, title="Reposition knobs" ) class IntermediateOutputModel(BaseSettingsModel): - name: str = Field(title="Output name") - filter: BakingStreamFilterModel = Field( + name: str = SettingsField(title="Output name") + filter: BakingStreamFilterModel = SettingsField( title="Filter", default_factory=BakingStreamFilterModel) - read_raw: bool = Field( + read_raw: bool = SettingsField( False, title="Read raw switch" ) - viewer_process_override: str = Field( + viewer_process_override: str = SettingsField( "", title="Viewer process override" ) - bake_viewer_process: bool = Field( + bake_viewer_process: bool = SettingsField( True, title="Bake viewer process" ) - bake_viewer_input_process: bool = Field( + bake_viewer_input_process: bool = SettingsField( True, title="Bake viewer input process node (LUT)" ) - reformat_nodes_config: ReformatNodesConfigModel = Field( + reformat_nodes_config: ReformatNodesConfigModel = SettingsField( default_factory=ReformatNodesConfigModel, title="Reformat Nodes") - extension: str = Field( + extension: str = SettingsField( "mov", title="File extension" ) - add_custom_tags: list[str] = Field( + add_custom_tags: list[str] = SettingsField( title="Custom tags", default_factory=list) @@ -157,123 +158,123 @@ class ExtractReviewDataMovModel(BaseSettingsModel): """[deprecated] use Extract Review Data Baking Streams instead. """ - enabled: bool = Field(title="Enabled") - viewer_lut_raw: bool = Field(title="Viewer lut raw") - outputs: list[IntermediateOutputModel] = Field( + enabled: bool = SettingsField(title="Enabled") + viewer_lut_raw: bool = SettingsField(title="Viewer lut raw") + outputs: list[IntermediateOutputModel] = SettingsField( default_factory=list, title="Baking streams" ) class ExtractReviewIntermediatesModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - viewer_lut_raw: bool = Field(title="Viewer lut raw") - outputs: list[IntermediateOutputModel] = Field( + enabled: bool = SettingsField(title="Enabled") + viewer_lut_raw: bool = SettingsField(title="Viewer lut raw") + outputs: list[IntermediateOutputModel] = SettingsField( default_factory=list, title="Baking streams" ) class FSubmissionNoteModel(BaseSettingsModel): - enabled: bool = Field(title="enabled") - template: str = Field(title="Template") + enabled: bool = SettingsField(title="enabled") + template: str = SettingsField(title="Template") class FSubmistingForModel(BaseSettingsModel): - enabled: bool = Field(title="enabled") - template: str = Field(title="Template") + enabled: bool = SettingsField(title="enabled") + template: str = SettingsField(title="Template") class FVFXScopeOfWorkModel(BaseSettingsModel): - enabled: bool = Field(title="enabled") - template: str = Field(title="Template") + enabled: bool = SettingsField(title="enabled") + template: str = SettingsField(title="Template") class ExctractSlateFrameParamModel(BaseSettingsModel): - f_submission_note: FSubmissionNoteModel = Field( + f_submission_note: FSubmissionNoteModel = SettingsField( title="f_submission_note", default_factory=FSubmissionNoteModel ) - f_submitting_for: FSubmistingForModel = Field( + f_submitting_for: FSubmistingForModel = SettingsField( title="f_submitting_for", default_factory=FSubmistingForModel ) - f_vfx_scope_of_work: FVFXScopeOfWorkModel = Field( + f_vfx_scope_of_work: FVFXScopeOfWorkModel = SettingsField( title="f_vfx_scope_of_work", default_factory=FVFXScopeOfWorkModel ) class ExtractSlateFrameModel(BaseSettingsModel): - viewer_lut_raw: bool = Field(title="Viewer lut raw") - key_value_mapping: ExctractSlateFrameParamModel = Field( + viewer_lut_raw: bool = SettingsField(title="Viewer lut raw") + key_value_mapping: ExctractSlateFrameParamModel = SettingsField( title="Key value mapping", default_factory=ExctractSlateFrameParamModel ) class IncrementScriptVersionModel(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") class PublishPuginsModel(BaseSettingsModel): - CollectInstanceData: CollectInstanceDataModel = Field( + CollectInstanceData: CollectInstanceDataModel = SettingsField( title="Collect Instance Version", default_factory=CollectInstanceDataModel, section="Collectors" ) - ValidateCorrectAssetContext: OptionalPluginModel = Field( + ValidateCorrectAssetContext: OptionalPluginModel = SettingsField( title="Validate Correct Folder Name", default_factory=OptionalPluginModel, section="Validators" ) - ValidateContainers: OptionalPluginModel = Field( + ValidateContainers: OptionalPluginModel = SettingsField( title="Validate Containers", default_factory=OptionalPluginModel ) - ValidateKnobs: ValidateKnobsModel = Field( + ValidateKnobs: ValidateKnobsModel = SettingsField( title="Validate Knobs", default_factory=ValidateKnobsModel ) - ValidateOutputResolution: OptionalPluginModel = Field( + ValidateOutputResolution: OptionalPluginModel = SettingsField( title="Validate Output Resolution", default_factory=OptionalPluginModel ) - ValidateGizmo: OptionalPluginModel = Field( + ValidateGizmo: OptionalPluginModel = SettingsField( title="Validate Gizmo", default_factory=OptionalPluginModel ) - ValidateBackdrop: OptionalPluginModel = Field( + ValidateBackdrop: OptionalPluginModel = SettingsField( title="Validate Backdrop", default_factory=OptionalPluginModel ) - ValidateScriptAttributes: OptionalPluginModel = Field( + ValidateScriptAttributes: OptionalPluginModel = SettingsField( title="Validate workfile attributes", default_factory=OptionalPluginModel ) - ExtractReviewData: ExtractReviewDataModel = Field( + ExtractReviewData: ExtractReviewDataModel = SettingsField( title="Extract Review Data", default_factory=ExtractReviewDataModel ) - ExtractReviewDataLut: ExtractReviewDataLutModel = Field( + ExtractReviewDataLut: ExtractReviewDataLutModel = SettingsField( title="Extract Review Data Lut", default_factory=ExtractReviewDataLutModel ) - ExtractReviewDataMov: ExtractReviewDataMovModel = Field( + ExtractReviewDataMov: ExtractReviewDataMovModel = SettingsField( title="Extract Review Data Mov", default_factory=ExtractReviewDataMovModel ) - ExtractReviewIntermediates: ExtractReviewIntermediatesModel = Field( + ExtractReviewIntermediates: ExtractReviewIntermediatesModel = SettingsField( title="Extract Review Intermediates", default_factory=ExtractReviewIntermediatesModel ) - ExtractSlateFrame: ExtractSlateFrameModel = Field( + ExtractSlateFrame: ExtractSlateFrameModel = SettingsField( title="Extract Slate Frame", default_factory=ExtractSlateFrameModel ) - IncrementScriptVersion: IncrementScriptVersionModel = Field( + IncrementScriptVersion: IncrementScriptVersionModel = SettingsField( title="Increment Workfile Version", default_factory=IncrementScriptVersionModel, section="Integrators" diff --git a/server_addon/nuke/server/settings/scriptsmenu.py b/server_addon/nuke/server/settings/scriptsmenu.py index 3dd6765920..7ffd6841d5 100644 --- a/server_addon/nuke/server/settings/scriptsmenu.py +++ b/server_addon/nuke/server/settings/scriptsmenu.py @@ -1,24 +1,23 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class ScriptsmenuSubmodel(BaseSettingsModel): """Item Definition""" _isGroup = True - type: str = Field(title="Type") - command: str = Field(title="Command") - sourcetype: str = Field(title="Source Type") - title: str = Field(title="Title") - tooltip: str = Field(title="Tooltip") + type: str = SettingsField(title="Type") + command: str = SettingsField(title="Command") + sourcetype: str = SettingsField(title="Source Type") + title: str = SettingsField(title="Title") + tooltip: str = SettingsField(title="Tooltip") class ScriptsmenuSettings(BaseSettingsModel): """Nuke script menu project settings.""" _isGroup = True - name: str = Field(title="Menu Name") - definition: list[ScriptsmenuSubmodel] = Field( + name: str = SettingsField(title="Menu Name") + definition: list[ScriptsmenuSubmodel] = SettingsField( default_factory=list, title="Definition", description="Scriptmenu Items Definition" diff --git a/server_addon/nuke/server/settings/templated_workfile_build.py b/server_addon/nuke/server/settings/templated_workfile_build.py index 0899be841e..12ebedf570 100644 --- a/server_addon/nuke/server/settings/templated_workfile_build.py +++ b/server_addon/nuke/server/settings/templated_workfile_build.py @@ -1,27 +1,27 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, + SettingsField, task_types_enum, ) class TemplatedWorkfileProfileModel(BaseSettingsModel): - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = Field( + task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - path: str = Field( + path: str = SettingsField( title="Path to template" ) - keep_placeholder: bool = Field( + keep_placeholder: bool = SettingsField( False, title="Keep placeholders") - create_first_version: bool = Field( + create_first_version: bool = SettingsField( True, title="Create first version" ) @@ -29,6 +29,6 @@ class TemplatedWorkfileProfileModel(BaseSettingsModel): class TemplatedWorkfileBuildModel(BaseSettingsModel): """Settings for templated workfile builder.""" - profiles: list[TemplatedWorkfileProfileModel] = Field( + profiles: list[TemplatedWorkfileProfileModel] = SettingsField( default_factory=list ) diff --git a/server_addon/nuke/server/settings/workfile_builder.py b/server_addon/nuke/server/settings/workfile_builder.py index 3ae3b08788..97961655f3 100644 --- a/server_addon/nuke/server/settings/workfile_builder.py +++ b/server_addon/nuke/server/settings/workfile_builder.py @@ -1,57 +1,57 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, + SettingsField, task_types_enum, MultiplatformPathModel, ) class CustomTemplateModel(BaseSettingsModel): - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - path: MultiplatformPathModel = Field( + path: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel, title="Gizmo Directory Path" ) class BuilderProfileItemModel(BaseSettingsModel): - product_name_filters: list[str] = Field( + product_name_filters: list[str] = SettingsField( default_factory=list, title="Product name" ) - product_types: list[str] = Field( + product_types: list[str] = SettingsField( default_factory=list, title="Product types" ) - repre_names: list[str] = Field( + repre_names: list[str] = SettingsField( default_factory=list, title="Representations" ) - loaders: list[str] = Field( + loaders: list[str] = SettingsField( default_factory=list, title="Loader plugins" ) class BuilderProfileModel(BaseSettingsModel): - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - tasks: list[str] = Field( + tasks: list[str] = SettingsField( default_factory=list, title="Task names" ) - current_context: list[BuilderProfileItemModel] = Field( + current_context: list[BuilderProfileItemModel] = SettingsField( default_factory=list, title="Current context" ) - linked_assets: list[BuilderProfileItemModel] = Field( + linked_assets: list[BuilderProfileItemModel] = SettingsField( default_factory=list, title="Linked assets/shots" ) @@ -60,17 +60,17 @@ class BuilderProfileModel(BaseSettingsModel): class WorkfileBuilderModel(BaseSettingsModel): """[deprecated] use Template Workfile Build Settings instead. """ - create_first_version: bool = Field( + create_first_version: bool = SettingsField( title="Create first workfile") - custom_templates: list[CustomTemplateModel] = Field( + custom_templates: list[CustomTemplateModel] = SettingsField( default_factory=list, title="Custom templates" ) - builder_on_start: bool = Field( + builder_on_start: bool = SettingsField( default=False, title="Run Builder at first workfile" ) - profiles: list[BuilderProfileModel] = Field( + profiles: list[BuilderProfileModel] = SettingsField( default_factory=list, title="Builder profiles" ) diff --git a/server_addon/photoshop/server/settings/creator_plugins.py b/server_addon/photoshop/server/settings/creator_plugins.py index 2fe63a7e3a..8acc213866 100644 --- a/server_addon/photoshop/server/settings/creator_plugins.py +++ b/server_addon/photoshop/server/settings/creator_plugins.py @@ -1,51 +1,49 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class CreateImagePluginModel(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - active_on_create: bool = Field(True, title="Active by default") - mark_for_review: bool = Field(False, title="Review by default") - default_variants: list[str] = Field( + enabled: bool = SettingsField(True, title="Enabled") + active_on_create: bool = SettingsField(True, title="Active by default") + mark_for_review: bool = SettingsField(False, title="Review by default") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Variants" ) class AutoImageCreatorPluginModel(BaseSettingsModel): - enabled: bool = Field(False, title="Enabled") - active_on_create: bool = Field(True, title="Active by default") - mark_for_review: bool = Field(False, title="Review by default") - default_variant: str = Field("", title="Default Variants") + enabled: bool = SettingsField(False, title="Enabled") + active_on_create: bool = SettingsField(True, title="Active by default") + mark_for_review: bool = SettingsField(False, title="Review by default") + default_variant: str = SettingsField("", title="Default Variants") class CreateReviewPlugin(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - active_on_create: bool = Field(True, title="Active by default") - default_variant: str = Field("", title="Default Variants") + enabled: bool = SettingsField(True, title="Enabled") + active_on_create: bool = SettingsField(True, title="Active by default") + default_variant: str = SettingsField("", title="Default Variants") class CreateWorkfilelugin(BaseSettingsModel): - enabled: bool = Field(True, title="Enabled") - active_on_create: bool = Field(True, title="Active by default") - default_variant: str = Field("", title="Default Variants") + enabled: bool = SettingsField(True, title="Enabled") + active_on_create: bool = SettingsField(True, title="Active by default") + default_variant: str = SettingsField("", title="Default Variants") class PhotoshopCreatorPlugins(BaseSettingsModel): - ImageCreator: CreateImagePluginModel = Field( + ImageCreator: CreateImagePluginModel = SettingsField( title="Create Image", default_factory=CreateImagePluginModel, ) - AutoImageCreator: AutoImageCreatorPluginModel = Field( + AutoImageCreator: AutoImageCreatorPluginModel = SettingsField( title="Create Flatten Image", default_factory=AutoImageCreatorPluginModel, ) - ReviewCreator: CreateReviewPlugin = Field( + ReviewCreator: CreateReviewPlugin = SettingsField( title="Create Review", default_factory=CreateReviewPlugin, ) - WorkfileCreator: CreateWorkfilelugin = Field( + WorkfileCreator: CreateWorkfilelugin = SettingsField( title="Create Workfile", default_factory=CreateWorkfilelugin, ) diff --git a/server_addon/photoshop/server/settings/imageio.py b/server_addon/photoshop/server/settings/imageio.py index 56b7f2fa32..9178497c6c 100644 --- a/server_addon/photoshop/server/settings/imageio.py +++ b/server_addon/photoshop/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,30 +35,30 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ImageIORemappingRulesModel(BaseSettingsModel): - host_native_name: str = Field( + host_native_name: str = SettingsField( title="Application native colorspace name" ) - ocio_name: str = Field(title="OCIO colorspace name") + ocio_name: str = SettingsField(title="OCIO colorspace name") class ImageIORemappingModel(BaseSettingsModel): - rules: list[ImageIORemappingRulesModel] = Field( + rules: list[ImageIORemappingRulesModel] = SettingsField( default_factory=list) class PhotoshopImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - remapping: ImageIORemappingModel = Field( + remapping: ImageIORemappingModel = SettingsField( title="Remapping colorspace names", default_factory=ImageIORemappingModel ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/photoshop/server/settings/main.py b/server_addon/photoshop/server/settings/main.py index ae7705b3db..b6474d6d29 100644 --- a/server_addon/photoshop/server/settings/main.py +++ b/server_addon/photoshop/server/settings/main.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import PhotoshopImageIOModel from .creator_plugins import PhotoshopCreatorPlugins, DEFAULT_CREATE_SETTINGS @@ -10,22 +9,22 @@ from .workfile_builder import WorkfileBuilderPlugin class PhotoshopSettings(BaseSettingsModel): """Photoshop Project Settings.""" - imageio: PhotoshopImageIOModel = Field( + imageio: PhotoshopImageIOModel = SettingsField( default_factory=PhotoshopImageIOModel, title="OCIO config" ) - create: PhotoshopCreatorPlugins = Field( + create: PhotoshopCreatorPlugins = SettingsField( default_factory=PhotoshopCreatorPlugins, title="Creator plugins" ) - publish: PhotoshopPublishPlugins = Field( + publish: PhotoshopPublishPlugins = SettingsField( default_factory=PhotoshopPublishPlugins, title="Publish plugins" ) - workfile_builder: WorkfileBuilderPlugin = Field( + workfile_builder: WorkfileBuilderPlugin = SettingsField( default_factory=WorkfileBuilderPlugin, title="Workfile Builder" ) diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py index 21e7d670f0..c59526135c 100644 --- a/server_addon/photoshop/server/settings/publish_plugins.py +++ b/server_addon/photoshop/server/settings/publish_plugins.py @@ -1,6 +1,4 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField create_flatten_image_enum = [ @@ -22,30 +20,30 @@ color_code_enum = [ class ColorCodeMappings(BaseSettingsModel): - color_code: list[str] = Field( + color_code: list[str] = SettingsField( title="Color codes for layers", default_factory=list, enum_resolver=lambda: color_code_enum, ) - layer_name_regex: list[str] = Field( + layer_name_regex: list[str] = SettingsField( default_factory=list, title="Layer name regex" ) - product_type: str = Field( + product_type: str = SettingsField( "", title="Resulting product type" ) - product_name_template: str = Field( + product_name_template: str = SettingsField( "", title="Product name template" ) class ExtractedOptions(BaseSettingsModel): - tags: list[str] = Field( + tags: list[str] = SettingsField( title="Tags", default_factory=list ) @@ -57,19 +55,19 @@ class CollectColorCodedInstancesPlugin(BaseSettingsModel): instances. (Applicable only for remote publishing!)""" - enabled: bool = Field(True, title="Enabled") - create_flatten_image: str = Field( + enabled: bool = SettingsField(True, title="Enabled") + create_flatten_image: str = SettingsField( "", title="Create flatten image", enum_resolver=lambda: create_flatten_image_enum, ) - flatten_product_type_template: str = Field( + flatten_product_type_template: str = SettingsField( "", title="Subset template for flatten image" ) - color_code_mapping: list[ColorCodeMappings] = Field( + color_code_mapping: list[ColorCodeMappings] = SettingsField( title="Color code mappings", default_factory=ColorCodeMappings, ) @@ -77,30 +75,30 @@ class CollectColorCodedInstancesPlugin(BaseSettingsModel): class CollectReviewPlugin(BaseSettingsModel): """Should review product be created""" - enabled: bool = Field(True, title="Enabled") + enabled: bool = SettingsField(True, title="Enabled") class CollectVersionPlugin(BaseSettingsModel): """Synchronize version for image and review instances by workfile version""" # noqa - enabled: bool = Field(True, title="Enabled") + enabled: bool = SettingsField(True, title="Enabled") class ValidateContainersPlugin(BaseSettingsModel): """Check that workfile contains latest version of loaded items""" # noqa _isGroup = True enabled: bool = True - optional: bool = Field(False, title="Optional") - active: bool = Field(True, title="Active") + optional: bool = SettingsField(False, title="Optional") + active: bool = SettingsField(True, title="Active") class ValidateNamingPlugin(BaseSettingsModel): """Validate naming of products and layers""" # noqa - invalid_chars: str = Field( + invalid_chars: str = SettingsField( '', title="Regex pattern of invalid characters" ) - replace_char: str = Field( + replace_char: str = SettingsField( '', title="Replacement character" ) @@ -108,19 +106,19 @@ class ValidateNamingPlugin(BaseSettingsModel): class ExtractImagePlugin(BaseSettingsModel): """Currently only jpg and png are supported""" - formats: list[str] = Field( + formats: list[str] = SettingsField( title="Extract Formats", default_factory=list, ) class ExtractReviewPlugin(BaseSettingsModel): - make_image_sequence: bool = Field( + make_image_sequence: bool = SettingsField( False, title="Make an image sequence instead of flatten image" ) - max_downscale_size: int = Field( + max_downscale_size: int = SettingsField( 8192, title="Maximum size of sources for review", description="FFMpeg can only handle limited resolution for creation of review and/or thumbnail", # noqa @@ -128,48 +126,48 @@ class ExtractReviewPlugin(BaseSettingsModel): le=16384, # less or equal ) - jpg_options: ExtractedOptions = Field( + jpg_options: ExtractedOptions = SettingsField( title="Extracted jpg Options", default_factory=ExtractedOptions ) - mov_options: ExtractedOptions = Field( + mov_options: ExtractedOptions = SettingsField( title="Extracted mov Options", default_factory=ExtractedOptions ) class PhotoshopPublishPlugins(BaseSettingsModel): - CollectColorCodedInstances: CollectColorCodedInstancesPlugin = Field( + CollectColorCodedInstances: CollectColorCodedInstancesPlugin = SettingsField( title="Collect Color Coded Instances", default_factory=CollectColorCodedInstancesPlugin, ) - CollectReview: CollectReviewPlugin = Field( + CollectReview: CollectReviewPlugin = SettingsField( title="Collect Review", default_factory=CollectReviewPlugin, ) - CollectVersion: CollectVersionPlugin = Field( + CollectVersion: CollectVersionPlugin = SettingsField( title="Collect Version", default_factory=CollectVersionPlugin, ) - ValidateContainers: ValidateContainersPlugin = Field( + ValidateContainers: ValidateContainersPlugin = SettingsField( title="Validate Containers", default_factory=ValidateContainersPlugin, ) - ValidateNaming: ValidateNamingPlugin = Field( + ValidateNaming: ValidateNamingPlugin = SettingsField( title="Validate naming of products and layers", default_factory=ValidateNamingPlugin, ) - ExtractImage: ExtractImagePlugin = Field( + ExtractImage: ExtractImagePlugin = SettingsField( title="Extract Image", default_factory=ExtractImagePlugin, ) - ExtractReview: ExtractReviewPlugin = Field( + ExtractReview: ExtractReviewPlugin = SettingsField( title="Extract Review", default_factory=ExtractReviewPlugin, ) diff --git a/server_addon/photoshop/server/settings/workfile_builder.py b/server_addon/photoshop/server/settings/workfile_builder.py index 68db05270d..4b00b99272 100644 --- a/server_addon/photoshop/server/settings/workfile_builder.py +++ b/server_addon/photoshop/server/settings/workfile_builder.py @@ -1,16 +1,18 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel, MultiplatformPathModel +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + MultiplatformPathModel, +) class CustomBuilderTemplate(BaseSettingsModel): _layout = "expanded" - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", ) - path: MultiplatformPathModel = Field( + path: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel, title="Template path" ) @@ -18,12 +20,12 @@ class CustomBuilderTemplate(BaseSettingsModel): class WorkfileBuilderPlugin(BaseSettingsModel): _title = "Workfile Builder" - create_first_version: bool = Field( + create_first_version: bool = SettingsField( False, title="Create first workfile" ) - custom_templates: list[CustomBuilderTemplate] = Field( + custom_templates: list[CustomBuilderTemplate] = SettingsField( default_factory=CustomBuilderTemplate, title="Template profiles" ) diff --git a/server_addon/resolve/server/imageio.py b/server_addon/resolve/server/imageio.py index c2bfcd40d0..9540f5d1d9 100644 --- a/server_addon/resolve/server/imageio.py +++ b/server_addon/resolve/server/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,30 +35,30 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ImageIORemappingRulesModel(BaseSettingsModel): - host_native_name: str = Field( + host_native_name: str = SettingsField( title="Application native colorspace name" ) - ocio_name: str = Field(title="OCIO colorspace name") + ocio_name: str = SettingsField(title="OCIO colorspace name") class ImageIORemappingModel(BaseSettingsModel): - rules: list[ImageIORemappingRulesModel] = Field( + rules: list[ImageIORemappingRulesModel] = SettingsField( default_factory=list) class ResolveImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - remapping: ImageIORemappingModel = Field( + remapping: ImageIORemappingModel = SettingsField( title="Remapping colorspace names", default_factory=ImageIORemappingModel ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/resolve/server/settings.py b/server_addon/resolve/server/settings.py index 326f6bea1e..dcdb2f1b27 100644 --- a/server_addon/resolve/server/settings.py +++ b/server_addon/resolve/server/settings.py @@ -1,91 +1,90 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ResolveImageIOModel class CreateShotClipModels(BaseSettingsModel): - hierarchy: str = Field( + hierarchy: str = SettingsField( "{folder}/{sequence}", title="Shot parent hierarchy", section="Shot Hierarchy And Rename Settings" ) - clipRename: bool = Field( + clipRename: bool = SettingsField( True, title="Rename clips" ) - clipName: str = Field( + clipName: str = SettingsField( "{track}{sequence}{shot}", title="Clip name template" ) - countFrom: int = Field( + countFrom: int = SettingsField( 10, title="Count sequence from" ) - countSteps: int = Field( + countSteps: int = SettingsField( 10, title="Stepping number" ) - folder: str = Field( + folder: str = SettingsField( "shots", title="{folder}", section="Shot Template Keywords" ) - episode: str = Field( + episode: str = SettingsField( "ep01", title="{episode}" ) - sequence: str = Field( + sequence: str = SettingsField( "sq01", title="{sequence}" ) - track: str = Field( + track: str = SettingsField( "{_track_}", title="{track}" ) - shot: str = Field( + shot: str = SettingsField( "sh###", title="{shot}" ) - vSyncOn: bool = Field( + vSyncOn: bool = SettingsField( False, title="Enable Vertical Sync", section="Vertical Synchronization Of Attributes" ) - workfileFrameStart: int = Field( + workfileFrameStart: int = SettingsField( 1001, title="Workfiles Start Frame", section="Shot Attributes" ) - handleStart: int = Field( + handleStart: int = SettingsField( 10, title="Handle start (head)" ) - handleEnd: int = Field( + handleEnd: int = SettingsField( 10, title="Handle end (tail)" ) class CreatorPuginsModel(BaseSettingsModel): - CreateShotClip: CreateShotClipModels = Field( + CreateShotClip: CreateShotClipModels = SettingsField( default_factory=CreateShotClipModels, title="Create Shot Clip" ) class ResolveSettings(BaseSettingsModel): - launch_openpype_menu_on_start: bool = Field( + launch_openpype_menu_on_start: bool = SettingsField( False, title="Launch OpenPype menu on start of Resolve" ) - imageio: ResolveImageIOModel = Field( + imageio: ResolveImageIOModel = SettingsField( default_factory=ResolveImageIOModel, title="Color Management (ImageIO)" ) - create: CreatorPuginsModel = Field( + create: CreatorPuginsModel = SettingsField( default_factory=CreatorPuginsModel, title="Creator plugins", ) diff --git a/server_addon/royal_render/server/settings.py b/server_addon/royal_render/server/settings.py index 677d7e2671..6e077feb3e 100644 --- a/server_addon/royal_render/server/settings.py +++ b/server_addon/royal_render/server/settings.py @@ -1,5 +1,8 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel, MultiplatformPathModel +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + MultiplatformPathModel, +) class CustomPath(MultiplatformPathModel): @@ -8,18 +11,20 @@ class CustomPath(MultiplatformPathModel): class ServerListSubmodel(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Name") - value: CustomPath = Field( + name: str = SettingsField("", title="Name") + value: CustomPath = SettingsField( default_factory=CustomPath ) class CollectSequencesFromJobModel(BaseSettingsModel): - review: bool = Field(True, title="Generate reviews from sequences") + review: bool = SettingsField( + True, title="Generate reviews from sequences" + ) class PublishPluginsModel(BaseSettingsModel): - CollectSequencesFromJob: CollectSequencesFromJobModel = Field( + CollectSequencesFromJob: CollectSequencesFromJobModel = SettingsField( default_factory=CollectSequencesFromJobModel, title="Collect Sequences from the Job" ) @@ -31,19 +36,19 @@ class RoyalRenderSettings(BaseSettingsModel): # - both system and project settings contained 'rr_path' # where project settings did choose one of rr_path from system settings # that is not possible in AYON - rr_paths: list[ServerListSubmodel] = Field( + rr_paths: list[ServerListSubmodel] = SettingsField( default_factory=list, title="Royal Render Root Paths", scope=["studio"], ) # This was 'rr_paths' in project settings and should be enum of # 'rr_paths' from system settings, but that's not possible in AYON - selected_rr_paths: list[str] = Field( + selected_rr_paths: list[str] = SettingsField( default_factory=list, title="Selected Royal Render Paths", section="---", ) - publish: PublishPluginsModel = Field( + publish: PublishPluginsModel = SettingsField( default_factory=PublishPluginsModel, title="Publish plugins", ) diff --git a/server_addon/substancepainter/server/settings/imageio.py b/server_addon/substancepainter/server/settings/imageio.py index e301d3d865..ea685047b0 100644 --- a/server_addon/substancepainter/server/settings/imageio.py +++ b/server_addon/substancepainter/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class ImageIOSettings(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/substancepainter/server/settings/main.py b/server_addon/substancepainter/server/settings/main.py index f8397c3c08..f80fa9fe1e 100644 --- a/server_addon/substancepainter/server/settings/main.py +++ b/server_addon/substancepainter/server/settings/main.py @@ -1,20 +1,19 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS class ShelvesSettingsModel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: str = Field(title="Path") + name: str = SettingsField(title="Name") + value: str = SettingsField(title="Path") class SubstancePainterSettings(BaseSettingsModel): - imageio: ImageIOSettings = Field( + imageio: ImageIOSettings = SettingsField( default_factory=ImageIOSettings, title="Color Management (ImageIO)" ) - shelves: list[ShelvesSettingsModel] = Field( + shelves: list[ShelvesSettingsModel] = SettingsField( default_factory=list, title="Shelves" ) diff --git a/server_addon/timers_manager/server/settings.py b/server_addon/timers_manager/server/settings.py index a5c5721a57..774940730c 100644 --- a/server_addon/timers_manager/server/settings.py +++ b/server_addon/timers_manager/server/settings.py @@ -1,24 +1,23 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class TimersManagerSettings(BaseSettingsModel): - auto_stop: bool = Field( + auto_stop: bool = SettingsField( True, title="Auto stop timer", scope=["studio"], ) - full_time: int = Field( + full_time: int = SettingsField( 15, title="Max idle time", scope=["studio"], ) - message_time: float = Field( + message_time: float = SettingsField( 0.5, title="When dialog will show", scope=["studio"], ) - disregard_publishing: bool = Field( + disregard_publishing: bool = SettingsField( False, title="Disregard publishing", scope=["studio"], diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py index 345cb92e63..bf66d9a088 100644 --- a/server_addon/traypublisher/server/settings/creator_plugins.py +++ b/server_addon/traypublisher/server/settings/creator_plugins.py @@ -1,6 +1,4 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class BatchMovieCreatorPlugin(BaseSettingsModel): @@ -8,24 +6,24 @@ class BatchMovieCreatorPlugin(BaseSettingsModel): asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')""" - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( title="Default variants", default_factory=list ) - default_tasks: list[str] = Field( + default_tasks: list[str] = SettingsField( title="Default tasks", default_factory=list ) - extensions: list[str] = Field( + extensions: list[str] = SettingsField( title="Extensions", default_factory=list ) class TrayPublisherCreatePluginsModel(BaseSettingsModel): - BatchMovieCreator: BatchMovieCreatorPlugin = Field( + BatchMovieCreator: BatchMovieCreatorPlugin = SettingsField( title="Batch Movie Creator", default_factory=BatchMovieCreatorPlugin ) diff --git a/server_addon/traypublisher/server/settings/editorial_creators.py b/server_addon/traypublisher/server/settings/editorial_creators.py index ac0ff0afc7..d9f5e302a4 100644 --- a/server_addon/traypublisher/server/settings/editorial_creators.py +++ b/server_addon/traypublisher/server/settings/editorial_creators.py @@ -1,18 +1,20 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel, task_types_enum +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, + task_types_enum, +) class ClipNameTokenizerItem(BaseSettingsModel): _layout = "expanded" - name: str = Field("", title="Tokenizer name") - regex: str = Field("", title="Tokenizer regex") + name: str = SettingsField("", title="Tokenizer name") + regex: str = SettingsField("", title="Tokenizer regex") class ShotAddTasksItem(BaseSettingsModel): _layout = "expanded" - name: str = Field('', title="Key") - task_type: str = Field( + name: str = SettingsField('', title="Key") + task_type: str = SettingsField( title="Task type", enum_resolver=task_types_enum ) @@ -20,7 +22,7 @@ class ShotAddTasksItem(BaseSettingsModel): class ShotRenameSubmodel(BaseSettingsModel): enabled: bool = True - shot_rename_template: str = Field( + shot_rename_template: str = SettingsField( "", title="Shot rename template" ) @@ -36,16 +38,16 @@ parent_type_enum = [ class TokenToParentConvertorItem(BaseSettingsModel): # TODO - was 'type' must be renamed in code to `parent_type` - parent_type: str = Field( + parent_type: str = SettingsField( "Project", enum_resolver=lambda: parent_type_enum ) - name: str = Field( + name: str = SettingsField( "", title="Parent token name", description="Unique name used in `Parent path template`" ) - value: str = Field( + value: str = SettingsField( "", title="Parent token value", description="Template where any text, Anatomy keys and Tokens could be used" # noqa @@ -54,12 +56,12 @@ class TokenToParentConvertorItem(BaseSettingsModel): class ShotHierarchySubmodel(BaseSettingsModel): enabled: bool = True - parents_path: str = Field( + parents_path: str = SettingsField( "", title="Parents path template", description="Using keys from \"Token to parent convertor\" or tokens directly" # noqa ) - parents: list[TokenToParentConvertorItem] = Field( + parents: list[TokenToParentConvertorItem] = SettingsField( default_factory=TokenToParentConvertorItem, title="Token to parent convertor" ) @@ -73,22 +75,22 @@ output_file_type = [ class ProductTypePresetItem(BaseSettingsModel): - product_type: str = Field("", title="Product type") + product_type: str = SettingsField("", title="Product type") # TODO add placeholder '< Inherited >' - variant: str = Field("", title="Variant") - review: bool = Field(True, title="Review") - output_file_type: str = Field( + variant: str = SettingsField("", title="Variant") + review: bool = SettingsField(True, title="Review") + output_file_type: str = SettingsField( ".mp4", enum_resolver=lambda: output_file_type ) class EditorialSimpleCreatorPlugin(BaseSettingsModel): - default_variants: list[str] = Field( + default_variants: list[str] = SettingsField( default_factory=list, title="Default Variants" ) - clip_name_tokenizer: list[ClipNameTokenizerItem] = Field( + clip_name_tokenizer: list[ClipNameTokenizerItem] = SettingsField( default_factory=ClipNameTokenizerItem, description=( "Using Regex expression to create tokens. \nThose can be used" @@ -96,25 +98,25 @@ class EditorialSimpleCreatorPlugin(BaseSettingsModel): "\n\nTokens should be decorated with \"_\" on each side" ) ) - shot_rename: ShotRenameSubmodel = Field( + shot_rename: ShotRenameSubmodel = SettingsField( title="Shot Rename", default_factory=ShotRenameSubmodel ) - shot_hierarchy: ShotHierarchySubmodel = Field( + shot_hierarchy: ShotHierarchySubmodel = SettingsField( title="Shot Hierarchy", default_factory=ShotHierarchySubmodel ) - shot_add_tasks: list[ShotAddTasksItem] = Field( + shot_add_tasks: list[ShotAddTasksItem] = SettingsField( title="Add tasks to shot", default_factory=ShotAddTasksItem ) - product_type_presets: list[ProductTypePresetItem] = Field( + product_type_presets: list[ProductTypePresetItem] = SettingsField( default_factory=list ) class TraypublisherEditorialCreatorPlugins(BaseSettingsModel): - editorial_simple: EditorialSimpleCreatorPlugin = Field( + editorial_simple: EditorialSimpleCreatorPlugin = SettingsField( title="Editorial simple creator", default_factory=EditorialSimpleCreatorPlugin, ) diff --git a/server_addon/traypublisher/server/settings/imageio.py b/server_addon/traypublisher/server/settings/imageio.py index 3df0d2f2fb..06a18a39ca 100644 --- a/server_addon/traypublisher/server/settings/imageio.py +++ b/server_addon/traypublisher/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class TrayPublisherImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/traypublisher/server/settings/main.py b/server_addon/traypublisher/server/settings/main.py index fad96bef2f..760c529f49 100644 --- a/server_addon/traypublisher/server/settings/main.py +++ b/server_addon/traypublisher/server/settings/main.py @@ -1,5 +1,4 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import TrayPublisherImageIOModel from .simple_creators import ( @@ -22,23 +21,23 @@ from .publish_plugins import ( class TraypublisherSettings(BaseSettingsModel): """Traypublisher Project Settings.""" - imageio: TrayPublisherImageIOModel = Field( + imageio: TrayPublisherImageIOModel = SettingsField( default_factory=TrayPublisherImageIOModel, title="Color Management (ImageIO)" ) - simple_creators: list[SimpleCreatorPlugin] = Field( + simple_creators: list[SimpleCreatorPlugin] = SettingsField( title="Simple Create Plugins", default_factory=SimpleCreatorPlugin, ) - editorial_creators: TraypublisherEditorialCreatorPlugins = Field( + editorial_creators: TraypublisherEditorialCreatorPlugins = SettingsField( title="Editorial Creators", default_factory=TraypublisherEditorialCreatorPlugins, ) - create: TrayPublisherCreatePluginsModel = Field( + create: TrayPublisherCreatePluginsModel = SettingsField( title="Create", default_factory=TrayPublisherCreatePluginsModel ) - publish: TrayPublisherPublishPlugins = Field( + publish: TrayPublisherPublishPlugins = SettingsField( title="Publish Plugins", default_factory=TrayPublisherPublishPlugins ) diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py index 8c844f29f2..f413c86227 100644 --- a/server_addon/traypublisher/server/settings/publish_plugins.py +++ b/server_addon/traypublisher/server/settings/publish_plugins.py @@ -1,13 +1,11 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class ValidatePluginModel(BaseSettingsModel): _isGroup = True enabled: bool = True - optional: bool = Field(True, title="Optional") - active: bool = Field(True, title="Active") + optional: bool = SettingsField(True, title="Optional") + active: bool = SettingsField(True, title="Active") class ValidateFrameRangeModel(ValidatePluginModel): @@ -17,15 +15,15 @@ class ValidateFrameRangeModel(ValidatePluginModel): class TrayPublisherPublishPlugins(BaseSettingsModel): - CollectFrameDataFromAssetEntity: ValidatePluginModel = Field( + CollectFrameDataFromAssetEntity: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Collect Frame Data From Folder Entity", ) - ValidateFrameRange: ValidateFrameRangeModel = Field( + ValidateFrameRange: ValidateFrameRangeModel = SettingsField( title="Validate Frame Range", default_factory=ValidateFrameRangeModel, ) - ValidateExistingVersion: ValidatePluginModel = Field( + ValidateExistingVersion: ValidatePluginModel = SettingsField( title="Validate Existing Version", default_factory=ValidatePluginModel, ) diff --git a/server_addon/traypublisher/server/settings/simple_creators.py b/server_addon/traypublisher/server/settings/simple_creators.py index 8335b9d34e..924eeedd23 100644 --- a/server_addon/traypublisher/server/settings/simple_creators.py +++ b/server_addon/traypublisher/server/settings/simple_creators.py @@ -1,42 +1,40 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class SimpleCreatorPlugin(BaseSettingsModel): _layout = "expanded" - product_type: str = Field("", title="Product type") + product_type: str = SettingsField("", title="Product type") # TODO add placeholder - identifier: str = Field("", title="Identifier") - label: str = Field("", title="Label") - icon: str = Field("", title="Icon") - default_variants: list[str] = Field( + identifier: str = SettingsField("", title="Identifier") + label: str = SettingsField("", title="Label") + icon: str = SettingsField("", title="Icon") + default_variants: list[str] = SettingsField( default_factory=list, title="Default Variants" ) - description: str = Field( + description: str = SettingsField( "", title="Description", widget="textarea" ) - detailed_description: str = Field( + detailed_description: str = SettingsField( "", title="Detailed Description", widget="textarea" ) - allow_sequences: bool = Field( + allow_sequences: bool = SettingsField( False, title="Allow sequences" ) - allow_multiple_items: bool = Field( + allow_multiple_items: bool = SettingsField( False, title="Allow multiple items" ) - allow_version_control: bool = Field( + allow_version_control: bool = SettingsField( False, title="Allow version control" ) - extensions: list[str] = Field( + extensions: list[str] = SettingsField( default_factory=list, title="Extensions" ) diff --git a/server_addon/tvpaint/server/settings/create_plugins.py b/server_addon/tvpaint/server/settings/create_plugins.py index 349bfdd288..89c3a52774 100644 --- a/server_addon/tvpaint/server/settings/create_plugins.py +++ b/server_addon/tvpaint/server/settings/create_plugins.py @@ -1,44 +1,43 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class CreateWorkfileModel(BaseSettingsModel): - enabled: bool = Field(True) - default_variant: str = Field(title="Default variant") - default_variants: list[str] = Field( + enabled: bool = SettingsField(True) + default_variant: str = SettingsField(title="Default variant") + default_variants: list[str] = SettingsField( default_factory=list, title="Default variants") class CreateReviewModel(BaseSettingsModel): - enabled: bool = Field(True) - active_on_create: bool = Field(True, title="Active by default") - default_variant: str = Field(title="Default variant") - default_variants: list[str] = Field( + enabled: bool = SettingsField(True) + active_on_create: bool = SettingsField(True, title="Active by default") + default_variant: str = SettingsField(title="Default variant") + default_variants: list[str] = SettingsField( default_factory=list, title="Default variants") class CreateRenderSceneModel(BaseSettingsModel): - enabled: bool = Field(True) - active_on_create: bool = Field(True, title="Active by default") - mark_for_review: bool = Field(True, title="Review by default") - default_pass_name: str = Field(title="Default beauty pass") - default_variant: str = Field(title="Default variant") - default_variants: list[str] = Field( + enabled: bool = SettingsField(True) + active_on_create: bool = SettingsField(True, title="Active by default") + mark_for_review: bool = SettingsField(True, title="Review by default") + default_pass_name: str = SettingsField(title="Default beauty pass") + default_variant: str = SettingsField(title="Default variant") + default_variants: list[str] = SettingsField( default_factory=list, title="Default variants") class CreateRenderLayerModel(BaseSettingsModel): - mark_for_review: bool = Field(True, title="Review by default") - default_pass_name: str = Field(title="Default beauty pass") - default_variant: str = Field(title="Default variant") - default_variants: list[str] = Field( + mark_for_review: bool = SettingsField(True, title="Review by default") + default_pass_name: str = SettingsField(title="Default beauty pass") + default_variant: str = SettingsField(title="Default variant") + default_variants: list[str] = SettingsField( default_factory=list, title="Default variants") class CreateRenderPassModel(BaseSettingsModel): - mark_for_review: bool = Field(True, title="Review by default") - default_variant: str = Field(title="Default variant") - default_variants: list[str] = Field( + mark_for_review: bool = SettingsField(True, title="Review by default") + default_variant: str = SettingsField(title="Default variant") + default_variants: list[str] = SettingsField( default_factory=list, title="Default variants") @@ -58,35 +57,35 @@ class AutoDetectCreateRenderModel(BaseSettingsModel): Would create group names "L010", "L020", ... """ - enabled: bool = Field(True) - allow_group_rename: bool = Field(title="Allow group rename") - group_name_template: str = Field(title="Group name template") - group_idx_offset: int = Field(1, title="Group index Offset", ge=1) - group_idx_padding: int = Field(4, title="Group index Padding", ge=1) + enabled: bool = SettingsField(True) + allow_group_rename: bool = SettingsField(title="Allow group rename") + group_name_template: str = SettingsField(title="Group name template") + group_idx_offset: int = SettingsField(1, title="Group index Offset", ge=1) + group_idx_padding: int = SettingsField(4, title="Group index Padding", ge=1) class CreatePluginsModel(BaseSettingsModel): - create_workfile: CreateWorkfileModel = Field( + create_workfile: CreateWorkfileModel = SettingsField( default_factory=CreateWorkfileModel, title="Create Workfile" ) - create_review: CreateReviewModel = Field( + create_review: CreateReviewModel = SettingsField( default_factory=CreateReviewModel, title="Create Review" ) - create_render_scene: CreateRenderSceneModel = Field( + create_render_scene: CreateRenderSceneModel = SettingsField( default_factory=CreateReviewModel, title="Create Render Scene" ) - create_render_layer: CreateRenderLayerModel= Field( + create_render_layer: CreateRenderLayerModel= SettingsField( default_factory=CreateRenderLayerModel, title="Create Render Layer" ) - create_render_pass: CreateRenderPassModel = Field( + create_render_pass: CreateRenderPassModel = SettingsField( default_factory=CreateRenderPassModel, title="Create Render Pass" ) - auto_detect_render: AutoDetectCreateRenderModel = Field( + auto_detect_render: AutoDetectCreateRenderModel = SettingsField( default_factory=AutoDetectCreateRenderModel, title="Auto-Detect Create Render", ) diff --git a/server_addon/tvpaint/server/settings/filters.py b/server_addon/tvpaint/server/settings/filters.py index 009febae06..9720e82281 100644 --- a/server_addon/tvpaint/server/settings/filters.py +++ b/server_addon/tvpaint/server/settings/filters.py @@ -1,12 +1,10 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField class FiltersSubmodel(BaseSettingsModel): _layout = "compact" - name: str = Field(title="Name") - value: str = Field( + name: str = SettingsField(title="Name") + value: str = SettingsField( "", title="Textarea", widget="textarea", @@ -14,6 +12,6 @@ class FiltersSubmodel(BaseSettingsModel): class PublishFiltersModel(BaseSettingsModel): - env_search_replace_values: list[FiltersSubmodel] = Field( + env_search_replace_values: list[FiltersSubmodel] = SettingsField( default_factory=list ) diff --git a/server_addon/tvpaint/server/settings/imageio.py b/server_addon/tvpaint/server/settings/imageio.py index 50f8b7eef4..aaf3fbf34e 100644 --- a/server_addon/tvpaint/server/settings/imageio.py +++ b/server_addon/tvpaint/server/settings/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class TVPaintImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/tvpaint/server/settings/main.py b/server_addon/tvpaint/server/settings/main.py index 102acfaf3d..c6b6c9ab12 100644 --- a/server_addon/tvpaint/server/settings/main.py +++ b/server_addon/tvpaint/server/settings/main.py @@ -1,6 +1,6 @@ -from pydantic import Field from ayon_server.settings import ( BaseSettingsModel, + SettingsField, ensure_unique_names, ) @@ -15,23 +15,23 @@ from .publish_plugins import ( class TvpaintSettings(BaseSettingsModel): - imageio: TVPaintImageIOModel = Field( + imageio: TVPaintImageIOModel = SettingsField( default_factory=TVPaintImageIOModel, title="Color Management (ImageIO)" ) - stop_timer_on_application_exit: bool = Field( + stop_timer_on_application_exit: bool = SettingsField( title="Stop timer on application exit") - create: CreatePluginsModel = Field( + create: CreatePluginsModel = SettingsField( default_factory=CreatePluginsModel, title="Create plugins" ) - publish: PublishPluginsModel = Field( + publish: PublishPluginsModel = SettingsField( default_factory=PublishPluginsModel, title="Publish plugins") - load: LoadPluginsModel = Field( + load: LoadPluginsModel = SettingsField( default_factory=LoadPluginsModel, title="Load plugins") - workfile_builder: WorkfileBuilderPlugin = Field( + workfile_builder: WorkfileBuilderPlugin = SettingsField( default_factory=WorkfileBuilderPlugin, title="Workfile Builder" ) diff --git a/server_addon/tvpaint/server/settings/publish_plugins.py b/server_addon/tvpaint/server/settings/publish_plugins.py index 76c7eaac01..0623524c92 100644 --- a/server_addon/tvpaint/server/settings/publish_plugins.py +++ b/server_addon/tvpaint/server/settings/publish_plugins.py @@ -1,11 +1,9 @@ -from pydantic import Field - -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.types import ColorRGBA_uint8 class CollectRenderInstancesModel(BaseSettingsModel): - ignore_render_pass_transparency: bool = Field( + ignore_render_pass_transparency: bool = SettingsField( title="Ignore Render Pass opacity" ) @@ -13,15 +11,15 @@ class CollectRenderInstancesModel(BaseSettingsModel): class ExtractSequenceModel(BaseSettingsModel): """Review BG color is used for whole scene review and for thumbnails.""" # TODO Use alpha color - review_bg: ColorRGBA_uint8 = Field( + review_bg: ColorRGBA_uint8 = SettingsField( (255, 255, 255, 1.0), title="Review BG color") class ValidatePluginModel(BaseSettingsModel): enabled: bool = True - optional: bool = Field(True, title="Optional") - active: bool = Field(True, title="Active") + optional: bool = SettingsField(True, title="Optional") + active: bool = SettingsField(True, title="Active") def compression_enum(): @@ -44,7 +42,7 @@ class ExtractConvertToEXRModel(BaseSettingsModel): enabled: bool = False replace_pngs: bool = True - exr_compression: str = Field( + exr_compression: str = SettingsField( "ZIP", enum_resolver=compression_enum, title="EXR Compression" @@ -53,46 +51,46 @@ class ExtractConvertToEXRModel(BaseSettingsModel): class LoadImageDefaultModel(BaseSettingsModel): _layout = "expanded" - stretch: bool = Field(title="Stretch") - timestretch: bool = Field(title="TimeStretch") - preload: bool = Field(title="Preload") + stretch: bool = SettingsField(title="Stretch") + timestretch: bool = SettingsField(title="TimeStretch") + preload: bool = SettingsField(title="Preload") class LoadImageModel(BaseSettingsModel): - defaults: LoadImageDefaultModel = Field( + defaults: LoadImageDefaultModel = SettingsField( default_factory=LoadImageDefaultModel ) class PublishPluginsModel(BaseSettingsModel): - CollectRenderInstances: CollectRenderInstancesModel = Field( + CollectRenderInstances: CollectRenderInstancesModel = SettingsField( default_factory=CollectRenderInstancesModel, title="Collect Render Instances") - ExtractSequence: ExtractSequenceModel = Field( + ExtractSequence: ExtractSequenceModel = SettingsField( default_factory=ExtractSequenceModel, title="Extract Sequence") - ValidateProjectSettings: ValidatePluginModel = Field( + ValidateProjectSettings: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Project Settings") - ValidateMarks: ValidatePluginModel = Field( + ValidateMarks: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate MarkIn/Out") - ValidateStartFrame: ValidatePluginModel = Field( + ValidateStartFrame: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Scene Start Frame") - ValidateAssetName: ValidatePluginModel = Field( + ValidateAssetName: ValidatePluginModel = SettingsField( default_factory=ValidatePluginModel, title="Validate Folder Name") - ExtractConvertToEXR: ExtractConvertToEXRModel = Field( + ExtractConvertToEXR: ExtractConvertToEXRModel = SettingsField( default_factory=ExtractConvertToEXRModel, title="Extract Convert To EXR") class LoadPluginsModel(BaseSettingsModel): - LoadImage: LoadImageModel = Field( + LoadImage: LoadImageModel = SettingsField( default_factory=LoadImageModel, title="Load Image") - ImportImage: LoadImageModel = Field( + ImportImage: LoadImageModel = SettingsField( default_factory=LoadImageModel, title="Import Image") diff --git a/server_addon/tvpaint/server/settings/workfile_builder.py b/server_addon/tvpaint/server/settings/workfile_builder.py index e0aba5da7e..0799497bf9 100644 --- a/server_addon/tvpaint/server/settings/workfile_builder.py +++ b/server_addon/tvpaint/server/settings/workfile_builder.py @@ -1,30 +1,29 @@ -from pydantic import Field - from ayon_server.settings import ( BaseSettingsModel, + SettingsField, MultiplatformPathModel, task_types_enum, ) class CustomBuilderTemplate(BaseSettingsModel): - task_types: list[str] = Field( + task_types: list[str] = SettingsField( default_factory=list, title="Task types", enum_resolver=task_types_enum ) - template_path: MultiplatformPathModel = Field( + template_path: MultiplatformPathModel = SettingsField( default_factory=MultiplatformPathModel ) class WorkfileBuilderPlugin(BaseSettingsModel): _title = "Workfile Builder" - create_first_version: bool = Field( + create_first_version: bool = SettingsField( False, title="Create first workfile" ) - custom_templates: list[CustomBuilderTemplate] = Field( + custom_templates: list[CustomBuilderTemplate] = SettingsField( default_factory=CustomBuilderTemplate ) diff --git a/server_addon/unreal/server/imageio.py b/server_addon/unreal/server/imageio.py index dde042ba47..853d476587 100644 --- a/server_addon/unreal/server/imageio.py +++ b/server_addon/unreal/server/imageio.py @@ -1,29 +1,29 @@ -from pydantic import Field, validator -from ayon_server.settings import BaseSettingsModel +from pydantic import validator +from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.settings.validators import ensure_unique_names class ImageIOConfigModel(BaseSettingsModel): - override_global_config: bool = Field( + override_global_config: bool = SettingsField( False, title="Override global OCIO config" ) - filepath: list[str] = Field( + filepath: list[str] = SettingsField( default_factory=list, title="Config path" ) class ImageIOFileRuleModel(BaseSettingsModel): - name: str = Field("", title="Rule name") - pattern: str = Field("", title="Regex pattern") - colorspace: str = Field("", title="Colorspace name") - ext: str = Field("", title="File extension") + name: str = SettingsField("", title="Rule name") + pattern: str = SettingsField("", title="Regex pattern") + colorspace: str = SettingsField("", title="Colorspace name") + ext: str = SettingsField("", title="File extension") class ImageIOFileRulesModel(BaseSettingsModel): - activate_host_rules: bool = Field(False) - rules: list[ImageIOFileRuleModel] = Field( + activate_host_rules: bool = SettingsField(False) + rules: list[ImageIOFileRuleModel] = SettingsField( default_factory=list, title="Rules" ) @@ -35,14 +35,14 @@ class ImageIOFileRulesModel(BaseSettingsModel): class UnrealImageIOModel(BaseSettingsModel): - activate_host_color_management: bool = Field( + activate_host_color_management: bool = SettingsField( True, title="Enable Color Management" ) - ocio_config: ImageIOConfigModel = Field( + ocio_config: ImageIOConfigModel = SettingsField( default_factory=ImageIOConfigModel, title="OCIO config" ) - file_rules: ImageIOFileRulesModel = Field( + file_rules: ImageIOFileRulesModel = SettingsField( default_factory=ImageIOFileRulesModel, title="File Rules" ) diff --git a/server_addon/unreal/server/settings.py b/server_addon/unreal/server/settings.py index 110ccc563a..5f54fb6c75 100644 --- a/server_addon/unreal/server/settings.py +++ b/server_addon/unreal/server/settings.py @@ -1,11 +1,10 @@ -from pydantic import Field -from ayon_server.settings import BaseSettingsModel +from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import UnrealImageIOModel class ProjectSetup(BaseSettingsModel): - dev_mode: bool = Field( + dev_mode: bool = SettingsField( False, title="Dev mode" ) @@ -21,32 +20,32 @@ def _render_format_enum(): class UnrealSettings(BaseSettingsModel): - imageio: UnrealImageIOModel = Field( + imageio: UnrealImageIOModel = SettingsField( default_factory=UnrealImageIOModel, title="Color Management (ImageIO)" ) - level_sequences_for_layouts: bool = Field( + level_sequences_for_layouts: bool = SettingsField( False, title="Generate level sequences when loading layouts" ) - delete_unmatched_assets: bool = Field( + delete_unmatched_assets: bool = SettingsField( False, title="Delete assets that are not matched" ) - render_config_path: str = Field( + render_config_path: str = SettingsField( "", title="Render Config Path" ) - preroll_frames: int = Field( + preroll_frames: int = SettingsField( 0, title="Pre-roll frames" ) - render_format: str = Field( + render_format: str = SettingsField( "png", title="Render format", enum_resolver=_render_format_enum ) - project_setup: ProjectSetup = Field( + project_setup: ProjectSetup = SettingsField( default_factory=ProjectSetup, title="Project Setup", ) From dadf8989be620616cdebc0bc3ee99dc3ba9d9ebd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 16:45:44 +0100 Subject: [PATCH 188/281] Use QDialog as super class and pass parent to init --- openpype/tools/publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5dd6998b24..de1a6249ad 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -42,7 +42,7 @@ from .widgets import ( ) -class PublisherWindow(QtWidgets.QWidget): +class PublisherWindow(QtWidgets.QDialog): """Main window of publisher.""" default_width = 1300 default_height = 800 @@ -50,7 +50,7 @@ class PublisherWindow(QtWidgets.QWidget): publish_footer_spacer = 2 def __init__(self, parent=None, controller=None, reset_on_show=None): - super(PublisherWindow, self).__init__() + super(PublisherWindow, self).__init__(parent) self.setObjectName("PublishWindow") From 34b42c132d56a69c190681b6b3aad0c4c8f06369 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 16:46:03 +0100 Subject: [PATCH 189/281] ignore enter and return event key --- openpype/tools/publisher/window.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index de1a6249ad..ab337c9b32 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -328,7 +328,6 @@ class PublisherWindow(QtWidgets.QDialog): "copy_report.request", self._copy_report ) - # Store extra header widget for TrayPublisher # - can be used to add additional widgets to header between context # label and help button @@ -492,7 +491,11 @@ class PublisherWindow(QtWidgets.QDialog): def keyPressEvent(self, event): # Ignore escape button to close window - if event.key() == QtCore.Qt.Key_Escape: + if event.key() in { + QtCore.Qt.Key_Escape, + QtCore.Qt.Key_Enter, + QtCore.Qt.Key_Return, + }: event.accept() return From 4709676b511e96a287abe3cf93e5085d98d3a88a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 26 Jan 2024 17:24:41 +0100 Subject: [PATCH 190/281] Fusion: provide better logging for validate saver crash due type error (#6082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * OP-7467 - move get_tool_resolution to classmethod Doesn't make much sense to have it outside of class. Debugging of it is impossible there (because of missing logger). Imho. * OP-7467 - add check for frame Limits uncaught error when resolution info is None, which could happen when saver is not connected. * OP-7467 - remove debugging messages * OP-7467 - enhance get_invalid Handle exception to select broken saver in the Publisher UI. * OP-7467 - refactor check Here it makes more sense. We try to run some expression, but it might still result in None. --------- Co-authored-by: Libor Batek <112623825+LiborBatek@users.noreply.github.com> Co-authored-by: Jakub Ježek --- .../publish/validate_saver_resolution.py | 115 ++++++++++-------- 1 file changed, 63 insertions(+), 52 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py b/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py index f6aba170c0..b28af3409d 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py +++ b/openpype/hosts/fusion/plugins/publish/validate_saver_resolution.py @@ -8,55 +8,6 @@ from openpype.hosts.fusion.api.action import SelectInvalidAction from openpype.hosts.fusion.api import comp_lock_and_undo_chunk -def get_tool_resolution(tool, frame): - """Return the 2D input resolution to a Fusion tool - - If the current tool hasn't been rendered its input resolution - hasn't been saved. To combat this, add an expression in - the comments field to read the resolution - - Args - tool (Fusion Tool): The tool to query input resolution - frame (int): The frame to query the resolution on. - - Returns: - tuple: width, height as 2-tuple of integers - - """ - comp = tool.Composition - - # False undo removes the undo-stack from the undo list - with comp_lock_and_undo_chunk(comp, "Read resolution", False): - # Save old comment - old_comment = "" - has_expression = False - if tool["Comments"][frame] != "": - if tool["Comments"].GetExpression() is not None: - has_expression = True - old_comment = tool["Comments"].GetExpression() - tool["Comments"].SetExpression(None) - else: - old_comment = tool["Comments"][frame] - tool["Comments"][frame] = "" - - # Get input width - tool["Comments"].SetExpression("self.Input.OriginalWidth") - width = int(tool["Comments"][frame]) - - # Get input height - tool["Comments"].SetExpression("self.Input.OriginalHeight") - height = int(tool["Comments"][frame]) - - # Reset old comment - tool["Comments"].SetExpression(None) - if has_expression: - tool["Comments"].SetExpression(old_comment) - else: - tool["Comments"][frame] = old_comment - - return width, height - - class ValidateSaverResolution( pyblish.api.InstancePlugin, OptionalPyblishPluginMixin ): @@ -87,19 +38,79 @@ class ValidateSaverResolution( @classmethod def get_invalid(cls, instance): - resolution = cls.get_resolution(instance) + saver = instance.data["tool"] + try: + resolution = cls.get_resolution(instance) + except PublishValidationError: + resolution = None expected_resolution = cls.get_expected_resolution(instance) if resolution != expected_resolution: - saver = instance.data["tool"] return [saver] @classmethod def get_resolution(cls, instance): saver = instance.data["tool"] first_frame = instance.data["frameStartHandle"] - return get_tool_resolution(saver, frame=first_frame) + return cls.get_tool_resolution(saver, frame=first_frame) @classmethod def get_expected_resolution(cls, instance): data = instance.data["assetEntity"]["data"] return data["resolutionWidth"], data["resolutionHeight"] + + @classmethod + def get_tool_resolution(cls, tool, frame): + """Return the 2D input resolution to a Fusion tool + + If the current tool hasn't been rendered its input resolution + hasn't been saved. To combat this, add an expression in + the comments field to read the resolution + + Args + tool (Fusion Tool): The tool to query input resolution + frame (int): The frame to query the resolution on. + + Returns: + tuple: width, height as 2-tuple of integers + + """ + comp = tool.Composition + + # False undo removes the undo-stack from the undo list + with comp_lock_and_undo_chunk(comp, "Read resolution", False): + # Save old comment + old_comment = "" + has_expression = False + + if tool["Comments"][frame] not in ["", None]: + if tool["Comments"].GetExpression() is not None: + has_expression = True + old_comment = tool["Comments"].GetExpression() + tool["Comments"].SetExpression(None) + else: + old_comment = tool["Comments"][frame] + tool["Comments"][frame] = "" + # Get input width + tool["Comments"].SetExpression("self.Input.OriginalWidth") + if tool["Comments"][frame] is None: + raise PublishValidationError( + "Cannot get resolution info for frame '{}'.\n\n " + "Please check that saver has connected input.".format( + frame + ) + ) + + width = int(tool["Comments"][frame]) + + # Get input height + tool["Comments"].SetExpression("self.Input.OriginalHeight") + height = int(tool["Comments"][frame]) + + # Reset old comment + tool["Comments"].SetExpression(None) + if has_expression: + tool["Comments"].SetExpression(old_comment) + else: + tool["Comments"][frame] = old_comment + + return width, height From 87b4b6e483de6dfc5db677a8fbc848f24cedda3a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 17:43:09 +0100 Subject: [PATCH 191/281] removed '_make_sure_on_top' --- openpype/tools/publisher/window.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index ab337c9b32..5b56802055 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -294,12 +294,6 @@ class PublisherWindow(QtWidgets.QDialog): controller.event_system.add_callback( "publish.process.stopped", self._on_publish_stop ) - controller.event_system.add_callback( - "publish.process.instance.changed", self._on_instance_change - ) - controller.event_system.add_callback( - "publish.process.plugin.changed", self._on_plugin_change - ) controller.event_system.add_callback( "show.card.message", self._on_overlay_message ) @@ -561,18 +555,6 @@ class PublisherWindow(QtWidgets.QDialog): self._reset_on_show = False self.reset() - def _make_sure_on_top(self): - """Raise window to top and activate it. - - This may not work for some DCCs without Qt. - """ - - if not self._window_is_visible: - self.show() - - self.setWindowState(QtCore.Qt.WindowActive) - self.raise_() - def _checks_before_save(self, explicit_save): """Save of changes may trigger some issues. @@ -885,12 +867,6 @@ class PublisherWindow(QtWidgets.QDialog): if self._is_on_create_tab(): self._go_to_publish_tab() - def _on_instance_change(self): - self._make_sure_on_top() - - def _on_plugin_change(self): - self._make_sure_on_top() - def _on_publish_validated_change(self, event): if event["value"]: self._validate_btn.setEnabled(False) @@ -901,7 +877,6 @@ class PublisherWindow(QtWidgets.QDialog): self._comment_input.setText("") def _on_publish_stop(self): - self._make_sure_on_top() self._set_publish_overlay_visibility(False) self._reset_btn.setEnabled(True) self._stop_btn.setEnabled(False) From 781bd615a6506dd36d5c69c90393596a3a35e299 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 17:53:30 +0100 Subject: [PATCH 192/281] add comments to enter and return keys --- openpype/tools/publisher/window.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5b56802055..82c7b167b7 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -484,9 +484,11 @@ class PublisherWindow(QtWidgets.QDialog): app.removeEventFilter(self) def keyPressEvent(self, event): - # Ignore escape button to close window if event.key() in { + # Ignore escape button to close window QtCore.Qt.Key_Escape, + # Ignore enter keyboard event which by default triggers + # first available button in QDialog QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return, }: From f39c6e9ef9eb5544572c407b55ee31b7d702684a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 18:01:18 +0100 Subject: [PATCH 193/281] formatting changes --- server_addon/applications/server/settings.py | 24 ++++++++++++++----- server_addon/core/server/settings/main.py | 4 +++- .../core/server/settings/publish_plugins.py | 8 ++++--- .../flame/server/settings/publish_plugins.py | 8 ++++--- server_addon/hiero/server/settings/filters.py | 4 +++- server_addon/maya/server/settings/creators.py | 4 +++- server_addon/maya/server/settings/imageio.py | 12 ++++++---- server_addon/maya/server/settings/loaders.py | 4 +++- server_addon/maya/server/settings/main.py | 4 +++- .../maya/server/settings/scriptsmenu.py | 4 +++- .../settings/templated_workfile_settings.py | 4 +++- server_addon/nuke/server/settings/main.py | 2 -- 12 files changed, 57 insertions(+), 25 deletions(-) diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py index 710bbbf9ee..e0a59604c8 100644 --- a/server_addon/applications/server/settings.py +++ b/server_addon/applications/server/settings.py @@ -41,7 +41,9 @@ class AppVariant(BaseSettingsModel): arguments: MultiplatformStrList = SettingsField( default_factory=MultiplatformStrList, title="Arguments" ) - environment: str = SettingsField("{}", title="Environment", widget="textarea") + environment: str = SettingsField( + "{}", title="Environment", widget="textarea" + ) @validator("environment") def validate_json(cls, value): @@ -57,7 +59,9 @@ class AppGroup(BaseSettingsModel): label: str = SettingsField("", title="Label") host_name: str = SettingsField("", title="Host name") icon: str = SettingsField("", title="Icon") - environment: str = SettingsField("{}", title="Environment", widget="textarea") + environment: str = SettingsField( + "{}", title="Environment", widget="textarea" + ) variants: list[AppVariant] = SettingsField( default_factory=list, @@ -87,7 +91,9 @@ class AdditionalAppGroup(BaseSettingsModel): label: str = SettingsField("", title="Label") host_name: str = SettingsField("", title="Host name") icon: str = SettingsField("", title="Icon") - environment: str = SettingsField("{}", title="Environment", widget="textarea") + environment: str = SettingsField( + "{}", title="Environment", widget="textarea" + ) variants: list[AppVariantWithPython] = SettingsField( default_factory=list, @@ -107,8 +113,12 @@ class ToolVariantModel(BaseSettingsModel): label: str = SettingsField("", title="Label") host_names: list[str] = SettingsField(default_factory=list, title="Hosts") # TODO use applications enum if possible - app_variants: list[str] = SettingsField(default_factory=list, title="Applications") - environment: str = SettingsField("{}", title="Environments", widget="textarea") + app_variants: list[str] = SettingsField( + default_factory=list, title="Applications" + ) + environment: str = SettingsField( + "{}", title="Environments", widget="textarea" + ) @validator("environment") def validate_json(cls, value): @@ -118,7 +128,9 @@ class ToolVariantModel(BaseSettingsModel): class ToolGroupModel(BaseSettingsModel): name: str = SettingsField("", title="Name") label: str = SettingsField("", title="Label") - environment: str = SettingsField("{}", title="Environments", widget="textarea") + environment: str = SettingsField( + "{}", title="Environments", widget="textarea" + ) variants: list[ToolVariantModel] = SettingsField(default_factory=list) @validator("environment") diff --git a/server_addon/core/server/settings/main.py b/server_addon/core/server/settings/main.py index 1ebfc5a4ca..1bdfcefe19 100644 --- a/server_addon/core/server/settings/main.py +++ b/server_addon/core/server/settings/main.py @@ -55,7 +55,9 @@ class CoreImageIOFileRulesModel(BaseSettingsModel): class CoreImageIOConfigModel(BaseSettingsModel): - filepath: list[str] = SettingsField(default_factory=list, title="Config path") + filepath: list[str] = SettingsField( + default_factory=list, title="Config path" + ) class CoreImageIOBaseModel(BaseSettingsModel): diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 61e35e02d4..7dfb01a215 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -715,9 +715,11 @@ class CleanUpFarmModel(BaseSettingsModel): class PublishPuginsModel(BaseSettingsModel): - CollectAnatomyInstanceData: CollectAnatomyInstanceDataModel = SettingsField( - default_factory=CollectAnatomyInstanceDataModel, - title="Collect Anatomy Instance Data" + CollectAnatomyInstanceData: CollectAnatomyInstanceDataModel = ( + SettingsField( + default_factory=CollectAnatomyInstanceDataModel, + title="Collect Anatomy Instance Data" + ) ) CollectAudio: CollectAudioModel = SettingsField( default_factory=CollectAudioModel, diff --git a/server_addon/flame/server/settings/publish_plugins.py b/server_addon/flame/server/settings/publish_plugins.py index 2c21034c44..decb00fcfa 100644 --- a/server_addon/flame/server/settings/publish_plugins.py +++ b/server_addon/flame/server/settings/publish_plugins.py @@ -32,9 +32,11 @@ class AddTasksModel(BaseSettingsModel): class CollectTimelineInstancesModel(BaseSettingsModel): _isGroup = True - xml_preset_attrs_from_comments: list[XMLPresetAttrsFromCommentsModel] = SettingsField( - default_factory=list, - title="XML presets attributes parsable from segment comments" + xml_preset_attrs_from_comments: list[XMLPresetAttrsFromCommentsModel] = ( + SettingsField( + default_factory=list, + title="XML presets attributes parsable from segment comments" + ) ) add_tasks: list[AddTasksModel] = SettingsField( default_factory=list, diff --git a/server_addon/hiero/server/settings/filters.py b/server_addon/hiero/server/settings/filters.py index 9642f93f7e..095d30a004 100644 --- a/server_addon/hiero/server/settings/filters.py +++ b/server_addon/hiero/server/settings/filters.py @@ -15,7 +15,9 @@ class PublishGUIFilterItemModel(BaseSettingsModel): class PublishGUIFiltersModel(BaseSettingsModel): _layout = "compact" name: str = SettingsField(title="Name") - value: list[PublishGUIFilterItemModel] = SettingsField(default_factory=list) + value: list[PublishGUIFilterItemModel] = SettingsField( + default_factory=list + ) @validator("value") def validate_unique_outputs(cls, value): diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py index 6b5583e726..5f3b850a1f 100644 --- a/server_addon/maya/server/settings/creators.py +++ b/server_addon/maya/server/settings/creators.py @@ -111,7 +111,9 @@ class CreateAssModel(BasicCreatorModel): class CreateReviewModel(BasicCreatorModel): - useMayaTimeline: bool = SettingsField(title="Use Maya Timeline for Frame Range.") + useMayaTimeline: bool = SettingsField( + title="Use Maya Timeline for Frame Range." + ) class CreateVrayProxyModel(BaseSettingsModel): diff --git a/server_addon/maya/server/settings/imageio.py b/server_addon/maya/server/settings/imageio.py index 34338b24e4..f4cdf3fbff 100644 --- a/server_addon/maya/server/settings/imageio.py +++ b/server_addon/maya/server/settings/imageio.py @@ -48,7 +48,9 @@ class ColorManagementPreferenceV2Model(BaseSettingsModel): Please migrate all to 'imageio/workfile' and enable it. """ - enabled: bool = SettingsField(True, title="Use Color Management Preference v2") + enabled: bool = SettingsField( + True, title="Use Color Management Preference v2" + ) renderSpace: str = SettingsField(title="Rendering Space") displayName: str = SettingsField(title="Display") @@ -92,9 +94,11 @@ class ImageIOSettings(BaseSettingsModel): title="Workfile" ) # Deprecated - colorManagementPreference_v2: ColorManagementPreferenceV2Model = SettingsField( - default_factory=ColorManagementPreferenceV2Model, - title="DEPRECATED: Color Management Preference v2 (Maya 2022+)" + colorManagementPreference_v2: ColorManagementPreferenceV2Model = ( + SettingsField( + default_factory=ColorManagementPreferenceV2Model, + title="DEPRECATED: Color Management Preference v2 (Maya 2022+)" + ) ) colorManagementPreference: ColorManagementPreferenceModel = SettingsField( default_factory=ColorManagementPreferenceModel, diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py index 1d5b972056..15d4275b80 100644 --- a/server_addon/maya/server/settings/loaders.py +++ b/server_addon/maya/server/settings/loaders.py @@ -40,7 +40,9 @@ class ColorsSetting(BaseSettingsModel): class ReferenceLoaderModel(BaseSettingsModel): namespace: str = SettingsField(title="Namespace") group_name: str = SettingsField(title="Group name") - display_handle: bool = SettingsField(title="Display Handle On Load References") + display_handle: bool = SettingsField( + title="Display Handle On Load References" + ) class ImportLoaderModel(BaseSettingsModel): diff --git a/server_addon/maya/server/settings/main.py b/server_addon/maya/server/settings/main.py index ddfb797f8a..f7f62e219d 100644 --- a/server_addon/maya/server/settings/main.py +++ b/server_addon/maya/server/settings/main.py @@ -37,7 +37,9 @@ class MayaSettings(BaseSettingsModel): title="Explicit Plugins Loading") imageio: ImageIOSettings = SettingsField( default_factory=ImageIOSettings, title="Color Management (imageio)") - mel_workspace: str = SettingsField(title="Maya MEL Workspace", widget="textarea") + mel_workspace: str = SettingsField( + title="Maya MEL Workspace", widget="textarea" + ) ext_mapping: list[ExtMappingItemModel] = SettingsField( default_factory=list, title="Extension Mapping") maya_dirmap: MayaDirmapModel = SettingsField( diff --git a/server_addon/maya/server/settings/scriptsmenu.py b/server_addon/maya/server/settings/scriptsmenu.py index 6de43b5278..d01dff1621 100644 --- a/server_addon/maya/server/settings/scriptsmenu.py +++ b/server_addon/maya/server/settings/scriptsmenu.py @@ -9,7 +9,9 @@ class ScriptsmenuSubmodel(BaseSettingsModel): sourcetype: str = SettingsField(title="Source Type") title: str = SettingsField(title="Title") tooltip: str = SettingsField(title="Tooltip") - tags: list[str] = SettingsField(default_factory=list, title="A list of tags") + tags: list[str] = SettingsField( + default_factory=list, title="A list of tags" + ) class ScriptsmenuModel(BaseSettingsModel): diff --git a/server_addon/maya/server/settings/templated_workfile_settings.py b/server_addon/maya/server/settings/templated_workfile_settings.py index f61f52f9ea..1baa2c895c 100644 --- a/server_addon/maya/server/settings/templated_workfile_settings.py +++ b/server_addon/maya/server/settings/templated_workfile_settings.py @@ -12,7 +12,9 @@ class WorkfileBuildProfilesModel(BaseSettingsModel): title="Task types", enum_resolver=task_types_enum ) - task_names: list[str] = SettingsField(default_factory=list, title="Task names") + task_names: list[str] = SettingsField( + default_factory=list, title="Task names" + ) path: str = SettingsField("", title="Path to template") diff --git a/server_addon/nuke/server/settings/main.py b/server_addon/nuke/server/settings/main.py index 2cfc539e71..2b269f1fce 100644 --- a/server_addon/nuke/server/settings/main.py +++ b/server_addon/nuke/server/settings/main.py @@ -1,5 +1,3 @@ -from pydantic import validator - from ayon_server.settings import ( BaseSettingsModel, SettingsField, From 41d0d0bd3b604b0185c931a510936e449d95e8b0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 26 Jan 2024 18:03:26 +0100 Subject: [PATCH 194/281] formatting changes 2 --- .../core/server/settings/publish_plugins.py | 20 +++++++++++++------ .../nuke/server/settings/publish_plugins.py | 8 +++++--- .../server/settings/publish_plugins.py | 8 +++++--- .../tvpaint/server/settings/create_plugins.py | 10 +++++++--- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 7dfb01a215..7aa86aafa6 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -232,7 +232,9 @@ class ExtractOIIOTranscodeOutputModel(BaseSettingsModel): title="OIIOtool arguments") tags: list[str] = SettingsField(default_factory=list, title="Tags") - custom_tags: list[str] = SettingsField(default_factory=list, title="Custom Tags") + custom_tags: list[str] = SettingsField( + default_factory=list, title="Custom Tags" + ) class ExtractOIIOTranscodeProfileModel(BaseSettingsModel): @@ -320,7 +322,9 @@ class ExtractReviewFilterModel(BaseSettingsModel): families: list[str] = SettingsField(default_factory=list, title="Families") product_names: list[str] = SettingsField( default_factory=list, title="Product names") - custom_tags: list[str] = SettingsField(default_factory=list, title="Custom Tags") + custom_tags: list[str] = SettingsField( + default_factory=list, title="Custom Tags" + ) single_frame_filter: str = SettingsField( "everytime", description=( @@ -623,9 +627,11 @@ class IntegrateProductGroupModel(BaseSettingsModel): """ _isGroup = True - product_grouping_profiles: list[IntegrateProductGroupProfile] = SettingsField( - default_factory=list, - title="Product group profiles" + product_grouping_profiles: list[IntegrateProductGroupProfile] = ( + SettingsField( + default_factory=list, + title="Product group profiles" + ) ) @@ -706,7 +712,9 @@ class CleanUpModel(BaseSettingsModel): default_factory=list, title="Patterns (regex)" ) - remove_temp_renders: bool = SettingsField(False, title="Remove Temp renders") + remove_temp_renders: bool = SettingsField( + False, title="Remove Temp renders" + ) class CleanUpFarmModel(BaseSettingsModel): diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index 0d785e6505..02ee9b3bab 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -266,9 +266,11 @@ class PublishPuginsModel(BaseSettingsModel): title="Extract Review Data Mov", default_factory=ExtractReviewDataMovModel ) - ExtractReviewIntermediates: ExtractReviewIntermediatesModel = SettingsField( - title="Extract Review Intermediates", - default_factory=ExtractReviewIntermediatesModel + ExtractReviewIntermediates: ExtractReviewIntermediatesModel = ( + SettingsField( + title="Extract Review Intermediates", + default_factory=ExtractReviewIntermediatesModel + ) ) ExtractSlateFrame: ExtractSlateFrameModel = SettingsField( title="Extract Slate Frame", diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py index c59526135c..c4a392d490 100644 --- a/server_addon/photoshop/server/settings/publish_plugins.py +++ b/server_addon/photoshop/server/settings/publish_plugins.py @@ -138,9 +138,11 @@ class ExtractReviewPlugin(BaseSettingsModel): class PhotoshopPublishPlugins(BaseSettingsModel): - CollectColorCodedInstances: CollectColorCodedInstancesPlugin = SettingsField( - title="Collect Color Coded Instances", - default_factory=CollectColorCodedInstancesPlugin, + CollectColorCodedInstances: CollectColorCodedInstancesPlugin = ( + SettingsField( + title="Collect Color Coded Instances", + default_factory=CollectColorCodedInstancesPlugin, + ) ) CollectReview: CollectReviewPlugin = SettingsField( title="Collect Review", diff --git a/server_addon/tvpaint/server/settings/create_plugins.py b/server_addon/tvpaint/server/settings/create_plugins.py index 89c3a52774..b3351dca28 100644 --- a/server_addon/tvpaint/server/settings/create_plugins.py +++ b/server_addon/tvpaint/server/settings/create_plugins.py @@ -60,8 +60,12 @@ class AutoDetectCreateRenderModel(BaseSettingsModel): enabled: bool = SettingsField(True) allow_group_rename: bool = SettingsField(title="Allow group rename") group_name_template: str = SettingsField(title="Group name template") - group_idx_offset: int = SettingsField(1, title="Group index Offset", ge=1) - group_idx_padding: int = SettingsField(4, title="Group index Padding", ge=1) + group_idx_offset: int = SettingsField( + 1, title="Group index Offset", ge=1 + ) + group_idx_padding: int = SettingsField( + 4, title="Group index Padding", ge=1 + ) class CreatePluginsModel(BaseSettingsModel): @@ -77,7 +81,7 @@ class CreatePluginsModel(BaseSettingsModel): default_factory=CreateReviewModel, title="Create Render Scene" ) - create_render_layer: CreateRenderLayerModel= SettingsField( + create_render_layer: CreateRenderLayerModel = SettingsField( default_factory=CreateRenderLayerModel, title="Create Render Layer" ) From 867193d889a5e7b653fad2c46de5b789a4aa8a0a Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 27 Jan 2024 03:24:20 +0000 Subject: [PATCH 195/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index ddfb3ebeeb..2f2fc517b8 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.5" +__version__ = "3.18.6-nightly.1" From eacb3431e2e58c3b489a87cfb48b795338129724 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 27 Jan 2024 03:25:03 +0000 Subject: [PATCH 196/281] 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 fd6999604a..039b42bff3 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.18.6-nightly.1 - 3.18.5 - 3.18.5-nightly.3 - 3.18.5-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.8 - 3.15.8-nightly.3 - 3.15.8-nightly.2 - - 3.15.8-nightly.1 validations: required: true - type: dropdown From e6818f94f5fead64b6ff8767462064fef8e10a41 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jan 2024 09:17:07 +0000 Subject: [PATCH 197/281] Illicit suggestion --- openpype/plugins/publish/extract_thumbnail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 37f7ea7737..8a1e9fd12d 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,7 +445,11 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) - duration = float(video_data["streams"][0]["duration"]) + duration = max( + float(stream.get("duration", 0)) + for stream in video_data["streams"] + if stream.get("codec_type") == "video" + ) cmd_args = [ "-y", From 3f8830e27d9c7f4002985ebcf05f7fe395fc3e17 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 29 Jan 2024 10:13:18 +0000 Subject: [PATCH 198/281] Update openpype/plugins/publish/extract_thumbnail.py --- openpype/plugins/publish/extract_thumbnail.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 8a1e9fd12d..2e272b061b 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,6 +445,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) + # Use duration of the individual streams since it is returned with + # higher decimal precision than 'format.duration'. We need this + # more precise value for calculating the correct amount of frames + # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS duration = max( float(stream.get("duration", 0)) for stream in video_data["streams"] From 3807172faed3addc22790c6831065d7424e08035 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 29 Jan 2024 10:13:40 +0000 Subject: [PATCH 199/281] Update openpype/plugins/publish/extract_thumbnail.py --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 2e272b061b..cbeada6bac 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -448,7 +448,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Use duration of the individual streams since it is returned with # higher decimal precision than 'format.duration'. We need this # more precise value for calculating the correct amount of frames - # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS + # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS duration = max( float(stream.get("duration", 0)) for stream in video_data["streams"] From 0bbe25fab1b7259c118c03f37748a4bc570eb530 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 29 Jan 2024 16:57:15 +0100 Subject: [PATCH 200/281] Refactor code for creating and publishing editorial clips - renaming variable to specify correctly value type - Added a condition in `collect_shot_instances.py` to check if the parent kind is "Video" before collecting shot instances. --- .../plugins/create/create_editorial.py | 13 +++++++------ .../plugins/publish/collect_shot_instances.py | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index e6f29af40f..3898635254 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -402,18 +402,19 @@ or updating already created. Publishing will create OTIO file. except AttributeError: track_start_frame = 0 - for clip in track.each_child(): - if not self._validate_clip_for_processing(clip): + for otio_clip in track.each_child(): + if not self._validate_clip_for_processing(otio_clip): continue + # get available frames info to clip data - self._create_otio_reference(clip, media_path, media_data) + self._create_otio_reference(otio_clip, media_path, media_data) # convert timeline range to source range - self._restore_otio_source_range(clip) + self._restore_otio_source_range(otio_clip) base_instance_data = self._get_base_instance_data( - clip, + otio_clip, instance_data, track_start_frame ) @@ -432,7 +433,7 @@ or updating already created. Publishing will create OTIO file. continue instance = self._make_subset_instance( - clip, + otio_clip, _fpreset, deepcopy(base_instance_data), parenting_data diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index b99b634da1..43f6518374 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -79,6 +79,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin): clip for clip in otio_timeline.each_child( descended_from_type=otio.schema.Clip) if clip.name == otio_clip.name + if clip.parent().kind == "Video" ] otio_clip = clips.pop() From c5c4ef175494568d946d7b8a8cc5ae981149f9d2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jan 2024 17:36:28 +0000 Subject: [PATCH 201/281] Settings, refactor, validate - Explicit settings label. - Refactor for code sharing between render, prerender and image families. - Validate typos on settings knob names. --- openpype/hosts/nuke/api/__init__.py | 4 +- openpype/hosts/nuke/api/lib.py | 24 ++++++ openpype/hosts/nuke/api/plugin.py | 11 ++- .../nuke/plugins/create/create_write_image.py | 5 ++ .../plugins/create/create_write_prerender.py | 5 ++ .../plugins/create/create_write_render.py | 13 +--- .../plugins/publish/validate_exposed_knobs.py | 77 +++++++++++++++++++ .../plugins/publish/validate_write_nodes.py | 39 +--------- .../nuke/server/settings/create_plugins.py | 12 +-- 9 files changed, 135 insertions(+), 55 deletions(-) create mode 100644 openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index c6ccd0baf1..2536230637 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -43,7 +43,8 @@ from .lib import ( get_node_data, set_node_data, update_node_data, - create_write_node + create_write_node, + link_knobs ) from .utils import ( colorspace_exists_on_node, @@ -95,6 +96,7 @@ __all__ = ( "set_node_data", "update_node_data", "create_write_node", + "link_knobs", "colorspace_exists_on_node", "get_colorspace_list", diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 7ba53caead..cdefd05c11 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3499,3 +3499,27 @@ def create_camera_node_by_version(): return nuke.createNode("Camera4") else: return nuke.createNode("Camera2") + + +def link_knobs(knobs, node, group_node): + """Link knobs from inside `group_node`""" + + missing_knobs = [] + for knob in knobs: + if knob in group_node.knobs(): + continue + + if knob not in node.knobs().keys(): + missing_knobs.append(knob) + + link = nuke.Link_Knob("") + link.makeLink(node.name(), knob) + link.setName(knob) + link.setFlag(0x1000) + group_node.addKnob(link) + + if missing_knobs: + raise ValueError( + "Write node exposed knobs missing:\n\n{}\n\nPlease review" + " project settings.".format("\n".join(missing_knobs)) + ) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 15d7bfc4b9..c8301b81fd 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -44,7 +44,8 @@ from .lib import ( get_view_process_node, get_viewer_config_from_string, deprecated, - get_filenames_without_hash + get_filenames_without_hash, + link_knobs ) from .pipeline import ( list_instances, @@ -1344,3 +1345,11 @@ def _remove_old_knobs(node): node.removeKnob(knob) except ValueError: pass + + +def exposed_write_knobs(settings, plugin_name, instance_node): + exposed_knobs = settings["nuke"]["create"][plugin_name]["exposed_knobs"] + if exposed_knobs: + instance_node.addKnob(nuke.Text_Knob('', 'Write Knobs')) + write_node = nuke.allNodes(group=instance_node, filter="Write")[0] + link_knobs(exposed_knobs, write_node, instance_node) diff --git a/openpype/hosts/nuke/plugins/create/create_write_image.py b/openpype/hosts/nuke/plugins/create/create_write_image.py index 8c18739587..f21d871c9f 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_image.py +++ b/openpype/hosts/nuke/plugins/create/create_write_image.py @@ -12,6 +12,7 @@ from openpype.lib import ( EnumDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.plugin import exposed_write_knobs class CreateWriteImage(napi.NukeWriteCreator): @@ -132,6 +133,10 @@ class CreateWriteImage(napi.NukeWriteCreator): instance.data_to_store() ) + exposed_write_knobs( + self.project_settings, self.__class__.__name__, instance_node + ) + return instance except Exception as er: diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 395c3b002f..742bfb20ad 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -9,6 +9,7 @@ from openpype.lib import ( BoolDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.plugin import exposed_write_knobs class CreateWritePrerender(napi.NukeWriteCreator): @@ -119,6 +120,10 @@ class CreateWritePrerender(napi.NukeWriteCreator): instance.data_to_store() ) + exposed_write_knobs( + self.project_settings, self.__class__.__name__, instance_node + ) + return instance except Exception as er: diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index e622376917..fc16876f75 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -9,6 +9,7 @@ from openpype.lib import ( BoolDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.plugin import exposed_write_knobs class CreateWriteRender(napi.NukeWriteCreator): @@ -113,15 +114,9 @@ class CreateWriteRender(napi.NukeWriteCreator): instance.data_to_store() ) - settings = self.project_settings["nuke"]["create"] - exposed_knobs = settings["CreateWriteRender"]["exposed_knobs"] - write_node = nuke.allNodes(group=instance_node, filter="Write")[0] - for knob in exposed_knobs: - link = nuke.Link_Knob("") - link.makeLink(write_node.name(), knob) - link.setName(knob) - link.setFlag(0x1000) - instance_node.addKnob(link) + exposed_write_knobs( + self.project_settings, self.__class__.__name__, instance_node + ) return instance diff --git a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py new file mode 100644 index 0000000000..fe5644f0c9 --- /dev/null +++ b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py @@ -0,0 +1,77 @@ +import pyblish.api + +from openpype.pipeline.publish import get_errored_instances_from_context +from openpype.hosts.nuke.api.lib import link_knobs +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError +) + + +class RepairExposedKnobs(pyblish.api.Action): + label = "Repair" + on = "failed" + icon = "wrench" + + def process(self, context, plugin): + instances = get_errored_instances_from_context(context) + + for instance in instances: + child_nodes = ( + instance.data.get("transientData", {}).get("childNodes") + or instance + ) + + write_group_node = instance.data["transientData"]["node"] + # get write node from inside of group + write_node = None + for x in child_nodes: + if x.Class() == "Write": + write_node = x + + plugin_name = plugin.families_mapping[instance.data["family"]] + nuke_settings = instance.context.data["project_settings"]["nuke"] + create_settings = nuke_settings["create"][plugin_name] + exposed_knobs = create_settings["exposed_knobs"] + link_knobs(exposed_knobs, write_node, write_group_node) + + +class ValidateExposedKnobs( + OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin +): + """ Validate write node exposed knobs. + + Compare exposed linked knobs to settings. + """ + + order = pyblish.api.ValidatorOrder + optional = True + families = ["render", "prerender", "image"] + label = "Validate Exposed Knobs" + actions = [RepairExposedKnobs] + hosts = ["nuke"] + families_mapping = { + "render": "CreateWriteRender", + "prerender": "CreateWritePrerender", + "image": "CreateWriteImage" + } + + def process(self, instance): + if not self.is_active(instance.data): + return + + plugin = self.families_mapping[instance.data["family"]] + group_node = instance.data["transientData"]["node"] + nuke_settings = instance.context.data["project_settings"]["nuke"] + create_settings = nuke_settings["create"][plugin] + exposed_knobs = create_settings["exposed_knobs"] + unexposed_knobs = [] + for knob in exposed_knobs: + if knob not in group_node.knobs(): + unexposed_knobs.append(knob) + + if unexposed_knobs: + raise PublishValidationError( + "Missing exposed knobs: {}".format(unexposed_knobs) + ) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index 434537022d..f490b580d6 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -1,7 +1,5 @@ from collections import defaultdict -import nuke - import pyblish.api from openpype.pipeline.publish import get_errored_instances_from_context from openpype.hosts.nuke.api.lib import ( @@ -12,8 +10,7 @@ from openpype.hosts.nuke.api.lib import ( from openpype.pipeline.publish import ( PublishXmlValidationError, - OptionalPyblishPluginMixin, - PublishValidationError + OptionalPyblishPluginMixin ) @@ -42,19 +39,6 @@ class RepairNukeWriteNodeAction(pyblish.api.Action): set_node_knobs_from_settings(write_node, correct_data["knobs"]) - nuke_settings = instance.context.data["project_settings"]["nuke"] - create_settings = nuke_settings["create"]["CreateWriteRender"] - exposed_knobs = create_settings["exposed_knobs"] - for knob in exposed_knobs: - if knob in write_group_node.knobs(): - continue - - link = nuke.Link_Knob("") - link.makeLink(write_node.name(), knob) - link.setName(knob) - link.setFlag(0x1000) - write_group_node.addKnob(link) - self.log.debug("Node attributes were fixed") @@ -150,27 +134,6 @@ class ValidateNukeWriteNode( if check: self._make_error(check) - nuke_settings = instance.context.data["project_settings"]["nuke"] - create_settings = nuke_settings["create"]["CreateWriteRender"] - exposed_knobs = create_settings["exposed_knobs"] - unexposed_knobs = [] - for knob in exposed_knobs: - if knob not in write_group_node.knobs(): - unexposed_knobs.append(knob) - - """ - link = nuke.Link_Knob("") - link.makeLink(write_node.name(), knob) - link.setName(knob) - link.setFlag(0x1000) - write_group_node.addKnob(link) - """ - - if unexposed_knobs: - raise PublishValidationError( - "Missing exposed knobs: {}".format(unexposed_knobs) - ) - def _make_error(self, check): # sourcery skip: merge-assign-and-aug-assign, move-assign-in-block dbg_msg = "Write node's knobs values are not correct!\n" diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py index 30719c06f6..6bdc5ee5ad 100644 --- a/server_addon/nuke/server/settings/create_plugins.py +++ b/server_addon/nuke/server/settings/create_plugins.py @@ -55,8 +55,8 @@ class CreateWriteRenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - exposed_knobs: list[str] = Field( - title="Exposed Knobs", + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", default_factory=list ) prenodes: list[PrenodeModel] = SettingsField( @@ -84,8 +84,8 @@ class CreateWritePrerenderModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - exposed_knobs: list[str] = Field( - title="Exposed Knobs", + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", default_factory=list ) prenodes: list[PrenodeModel] = SettingsField( @@ -113,8 +113,8 @@ class CreateWriteImageModel(BaseSettingsModel): enum_resolver=instance_attributes_enum, title="Instance attributes" ) - exposed_knobs: list[str] = Field( - title="Exposed Knobs", + exposed_knobs: list[str] = SettingsField( + title="Write Node Exposed Knobs", default_factory=list ) prenodes: list[PrenodeModel] = SettingsField( From 449e601a5fe37617d7c663b47f9d0bd45b083aec Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jan 2024 17:39:28 +0000 Subject: [PATCH 202/281] Show message with error on action failure. --- openpype/tools/publisher/control.py | 15 +++++++++++++++ openpype/tools/publisher/window.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 47e374edf2..0137d5c95f 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2329,6 +2329,21 @@ class PublisherController(BasePublisherController): result = pyblish.plugin.process( plugin, self._publish_context, None, action.id ) + exception = result.get("error") + if exception: + self._emit_event( + "action.failed", + { + "title": "Action failed", + "message": "Action failed.", + "traceback": "".join( + traceback.format_exception(exception) + ), + "label": "", + "identifier": action.__name__ + } + ) + self._publish_report.add_action_result(action, result) def _publish_next_process(self): diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 5dd6998b24..e5b47f4309 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -321,6 +321,9 @@ class PublisherWindow(QtWidgets.QWidget): controller.event_system.add_callback( "convertors.find.failed", self._on_convertor_error ) + controller.event_system.add_callback( + "action.failed", self._on_action_error + ) controller.event_system.add_callback( "export_report.request", self._export_report ) @@ -1012,6 +1015,18 @@ class PublisherWindow(QtWidgets.QWidget): event["title"], new_failed_info, "Convertor:" ) + def _on_action_error(self, event): + self.add_error_message_dialog( + event["title"], + [{ + "message": event["message"], + "traceback": event["traceback"], + "label": event["label"], + "identifier": event["identifier"] + }], + "Action:" + ) + def _update_create_overlay_size(self): metrics = self._create_overlay_button.fontMetrics() height = int(metrics.height()) From 40f8578d793de90caf83fc7c565f7ec54c973dc3 Mon Sep 17 00:00:00 2001 From: farrizabalaga Date: Mon, 29 Jan 2024 17:28:35 -0700 Subject: [PATCH 203/281] Fix traypublisher asset/folderPath bug when updating instances --- openpype/hosts/traypublisher/api/plugin.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 14c66fa08f..6859b85a46 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -221,9 +221,16 @@ class SettingsCreator(TrayPublishCreator): ): filtered_instance_data.append(instance) - asset_names = { - instance["asset"] - for instance in filtered_instance_data} + if AYON_SERVER_ENABLED: + asset_names = { + instance["folderPath"] + for instance in filtered_instance_data + } + else: + asset_names = { + instance["asset"] + for instance in filtered_instance_data + } subset_names = { instance["subset"] for instance in filtered_instance_data} @@ -231,7 +238,10 @@ class SettingsCreator(TrayPublishCreator): asset_names, subset_names ) for instance in filtered_instance_data: - asset_name = instance["asset"] + if AYON_SERVER_ENABLED: + asset_name = instance["folderPath"] + else: + asset_name = instance["asset"] subset_name = instance["subset"] version = subset_docs_by_asset_id[asset_name][subset_name] instance["creator_attributes"]["version_to_use"] = version From 2b3160773332c53cc4e923e897ea532e06d7c968 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Jan 2024 10:57:59 +0000 Subject: [PATCH 204/281] Feedback action finished. --- openpype/tools/publisher/control.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 0137d5c95f..61528c7c88 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2346,6 +2346,8 @@ class PublisherController(BasePublisherController): self._publish_report.add_action_result(action, result) + self.emit_card_message("Action finished.") + def _publish_next_process(self): # Validations of progress before using iterator # - same conditions may be inside iterator but they may be used From 246731f1dcc0d7931a9016bbc5a5b12a1e517126 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 31 Jan 2024 03:24:55 +0000 Subject: [PATCH 205/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 2f2fc517b8..6cbe5ba6cd 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.6-nightly.1" +__version__ = "3.18.6-nightly.2" From 9a129100fc6fdf2828792440f8bf9aa3d501b19b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 Jan 2024 03:25:30 +0000 Subject: [PATCH 206/281] 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 039b42bff3..cd81171b73 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.18.6-nightly.2 - 3.18.6-nightly.1 - 3.18.5 - 3.18.5-nightly.3 @@ -134,7 +135,6 @@ body: - 3.15.9-nightly.1 - 3.15.8 - 3.15.8-nightly.3 - - 3.15.8-nightly.2 validations: required: true - type: dropdown From d0a09bcad47299e341152cb47a600abc3d269f43 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 31 Jan 2024 07:15:36 +0000 Subject: [PATCH 207/281] Illicit suggestion --- openpype/tools/publisher/control.py | 2 +- openpype/tools/publisher/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 61528c7c88..9bae0deac8 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2332,7 +2332,7 @@ class PublisherController(BasePublisherController): exception = result.get("error") if exception: self._emit_event( - "action.failed", + "publish.action.failed", { "title": "Action failed", "message": "Action failed.", diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index e5b47f4309..193b6948a5 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -322,7 +322,7 @@ class PublisherWindow(QtWidgets.QWidget): "convertors.find.failed", self._on_convertor_error ) controller.event_system.add_callback( - "action.failed", self._on_action_error + "publish.action.failed", self._on_action_error ) controller.event_system.add_callback( "export_report.request", self._export_report From 149f53daff8a4267fc0a45370bcd414db00d7981 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 31 Jan 2024 09:15:11 +0000 Subject: [PATCH 208/281] identifier > name --- openpype/tools/publisher/control.py | 2 +- openpype/tools/publisher/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 9bae0deac8..23ef6ae6c1 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2340,7 +2340,7 @@ class PublisherController(BasePublisherController): traceback.format_exception(exception) ), "label": "", - "identifier": action.__name__ + "name": action.__name__ } ) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 193b6948a5..f4de3a08f7 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -1022,7 +1022,7 @@ class PublisherWindow(QtWidgets.QWidget): "message": event["message"], "traceback": event["traceback"], "label": event["label"], - "identifier": event["identifier"] + "identifier": event["name"] }], "Action:" ) From b39c41f98a5ece76b6466ab08e8e55e7462df8a3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 31 Jan 2024 10:01:02 +0000 Subject: [PATCH 209/281] Use label --- openpype/tools/publisher/control.py | 4 ++-- openpype/tools/publisher/window.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 23ef6ae6c1..13d007dd35 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2339,8 +2339,8 @@ class PublisherController(BasePublisherController): "traceback": "".join( traceback.format_exception(exception) ), - "label": "", - "name": action.__name__ + "label": action.__name__, + "identifier": action.id } ) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index f4de3a08f7..193b6948a5 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -1022,7 +1022,7 @@ class PublisherWindow(QtWidgets.QWidget): "message": event["message"], "traceback": event["traceback"], "label": event["label"], - "identifier": event["name"] + "identifier": event["identifier"] }], "Action:" ) From 44282f86df721a271a291bc005abd07f896acb49 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 1 Feb 2024 10:42:34 +0000 Subject: [PATCH 210/281] Fixed deadline validator to check compositor tree output --- .../publish/validate_deadline_publish.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py index bb243f08cc..f7860dd75d 100644 --- a/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/blender/plugins/publish/validate_deadline_publish.py @@ -28,15 +28,27 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, def process(self, instance): if not self.is_active(instance.data): return + + tree = bpy.context.scene.node_tree + output_type = "CompositorNodeOutputFile" + output_node = None + # Remove all output nodes that inlcude "AYON" in the name. + # There should be only one. + for node in tree.nodes: + if node.bl_idname == output_type and "AYON" in node.name: + output_node = node + break + if not output_node: + raise PublishValidationError( + "No output node found in the compositor tree." + ) filepath = bpy.data.filepath file = os.path.basename(filepath) filename, ext = os.path.splitext(file) - if filename not in bpy.context.scene.render.filepath: + if filename not in output_node.base_path: raise PublishValidationError( - "Render output folder " - "doesn't match the blender scene name! " - "Use Repair action to " - "fix the folder file path." + "Render output folder doesn't match the blender scene name! " + "Use Repair action to fix the folder file path." ) @classmethod From 27f869e204662ba9a0dd78400269499145a60ec1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 1 Feb 2024 11:49:25 +0000 Subject: [PATCH 211/281] Fix error report --- openpype/hosts/nuke/plugins/publish/validate_write_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index f490b580d6..648025adad 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -129,7 +129,7 @@ class ValidateNukeWriteNode( and key != "file" and key != "tile_color" ): - check.append([key, node_value, write_node[key].value()]) + check.append([key, fixed_values, write_node[key].value()]) if check: self._make_error(check) @@ -137,7 +137,7 @@ class ValidateNukeWriteNode( def _make_error(self, check): # sourcery skip: merge-assign-and-aug-assign, move-assign-in-block dbg_msg = "Write node's knobs values are not correct!\n" - msg_add = "Knob '{0}' > Correct: `{1}` > Wrong: `{2}`" + msg_add = "Knob '{0}' > Expected: `{1}` > Current: `{2}`" details = [ msg_add.format(item[0], item[1], item[2]) From b26ea7a62039ba766085377bf51b8b521f8014a9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 1 Feb 2024 12:18:46 +0000 Subject: [PATCH 212/281] Fix getting node and node name. --- openpype/hosts/nuke/plugins/load/load_camera_abc.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 898c5e4e7b..7f0452d6d4 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -112,8 +112,6 @@ class AlembicCameraLoader(load.LoaderPlugin): project_name = get_current_project_name() version_doc = get_version_by_id(project_name, representation["parent"]) - object_name = container["node"] - # get main variables version_data = version_doc.get("data", {}) vname = version_doc.get("name", None) @@ -139,7 +137,7 @@ class AlembicCameraLoader(load.LoaderPlugin): file = get_representation_path(representation).replace("\\", "/") with maintained_selection(): - camera_node = nuke.toNode(object_name) + camera_node = container["node"] camera_node['selected'].setValue(True) # collect input output dependencies @@ -154,9 +152,10 @@ class AlembicCameraLoader(load.LoaderPlugin): xpos = camera_node.xpos() ypos = camera_node.ypos() nuke.nodeCopy("%clipboard%") + camera_name = camera_node.name() nuke.delete(camera_node) nuke.nodePaste("%clipboard%") - camera_node = nuke.toNode(object_name) + camera_node = nuke.toNode(camera_name) camera_node.setXYpos(xpos, ypos) # link to original input nodes From d64eb3ccc2eba2a622631bac8041df66722648a1 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:26:38 +0000 Subject: [PATCH 213/281] refactor: replaced legacy function has_unsaved_changes seems to be legacy as indicated by the base class. It was already unused/implemented. Replaced with working version workfiles_has_unsaved_changes --- openpype/hosts/max/api/pipeline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index d0ae854dc8..ce4afd2e8b 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,9 +60,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) - def has_unsaved_changes(self): - # TODO: how to get it from 3dsmax? - return True + def workfiles_has_unsaved_changes(self): + return rt.getSaveRequired() def get_workfile_extensions(self): return [".max"] From 895ca1b57221e395d0f205e1ed8c45832c55e2a2 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:28:08 +0000 Subject: [PATCH 214/281] feat: implemented new work_file_has_unsaved_changes function on host class mirrored Houdini implementation --- .../hosts/max/plugins/publish/save_scene.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py index a40788ab41..54f9f8d8eb 100644 --- a/openpype/hosts/max/plugins/publish/save_scene.py +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -1,21 +1,24 @@ import pyblish.api -import os +from openpype.pipeline import registered_host class SaveCurrentScene(pyblish.api.ContextPlugin): - """Save current scene - - """ + """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 - rt.saveMaxFile(current) + host = registered_host() + current_file = host.get_current_workfile() + + assert context.data["currentFile"] == current_file + + if host.workfile_has_unsaved_changes(): + self.log.info(f"Saving current file: {current_file}") + host.save_workfile(current_file) + else: + self.log.debug("No unsaved changes, skipping file save..") + From 14baeb65c8820c90e15f90295bd32e238e13c60c Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:29:59 +0000 Subject: [PATCH 215/281] feat: added line to restore menu to 3dsmax default --- openpype/hosts/max/startup/startup.ms | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/startup/startup.ms b/openpype/hosts/max/startup/startup.ms index b80ead4b74..3a4e76b3cf 100644 --- a/openpype/hosts/max/startup/startup.ms +++ b/openpype/hosts/max/startup/startup.ms @@ -7,6 +7,9 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath + + # opens the create menu on startup to ensure users are presented with a useful default view. + max create mode python.ExecuteFile startup -) \ No newline at end of file +) From 4999550167e3668b7be617c2842cddaee100a9a2 Mon Sep 17 00:00:00 2001 From: Jack P Date: Thu, 1 Feb 2024 13:50:04 +0000 Subject: [PATCH 216/281] fix: hound --- openpype/hosts/max/plugins/publish/save_scene.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py index 54f9f8d8eb..fa571be835 100644 --- a/openpype/hosts/max/plugins/publish/save_scene.py +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -9,16 +9,15 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): order = pyblish.api.ExtractorOrder - 0.49 hosts = ["max"] families = ["maxrender", "workfile"] - + def process(self, context): host = registered_host() current_file = host.get_current_workfile() assert context.data["currentFile"] == current_file - + if host.workfile_has_unsaved_changes(): self.log.info(f"Saving current file: {current_file}") host.save_workfile(current_file) else: self.log.debug("No unsaved changes, skipping file save..") - From f709a86db49274d33a41d9b5b182fa711275d942 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Feb 2024 15:43:48 +0100 Subject: [PATCH 217/281] use bundle name as variant in dev mode --- openpype/settings/ayon_settings.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py index 4948f2431c..2c851c054d 100644 --- a/openpype/settings/ayon_settings.py +++ b/openpype/settings/ayon_settings.py @@ -1458,7 +1458,7 @@ class _AyonSettingsCache: variant = "production" if is_dev_mode_enabled(): - variant = cls._get_dev_mode_settings_variant() + variant = cls._get_bundle_name() elif is_staging_enabled(): variant = "staging" @@ -1474,28 +1474,6 @@ class _AyonSettingsCache: def _get_bundle_name(cls): return os.environ["AYON_BUNDLE_NAME"] - @classmethod - def _get_dev_mode_settings_variant(cls): - """Develop mode settings variant. - - Returns: - str: Name of settings variant. - """ - - con = get_ayon_server_api_connection() - bundles = con.get_bundles() - user = con.get_user() - username = user["name"] - for bundle in bundles["bundles"]: - if ( - bundle.get("isDev") - and bundle.get("activeUser") == username - ): - return bundle["name"] - # Return fake variant - distribution logic will tell user that he - # does not have set any dev bundle - return "dev" - @classmethod def get_value_by_project(cls, project_name): cache_item = _AyonSettingsCache.cache_by_project_name[project_name] From 7be9463e5169f260686b214b616820df6d70c5f7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Feb 2024 15:48:43 +0100 Subject: [PATCH 218/281] removed djvview group from default applications the djvview does not have model and is unused, probably forgotten --- .../applications/server/applications.json | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index b0b12b2003..82dfd3b8d3 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -1175,30 +1175,6 @@ } ] }, - "djvview": { - "enabled": true, - "label": "DJV View", - "icon": "{}/app_icons/djvView.png", - "host_name": "", - "environment": "{}", - "variants": [ - { - "name": "1-1", - "label": "1.1", - "executables": { - "windows": [], - "darwin": [], - "linux": [] - }, - "arguments": { - "windows": [], - "darwin": [], - "linux": [] - }, - "environment": "{}" - } - ] - }, "wrap": { "enabled": true, "label": "Wrap", From 1ee89a0afacc8779cac51cafdabd984922d6acc5 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 1 Feb 2024 15:47:50 +0000 Subject: [PATCH 219/281] [Automated] Release --- CHANGELOG.md | 126 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 128 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f0bc469f..009150ae7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,132 @@ # Changelog +## [3.18.6](https://github.com/ynput/OpenPype/tree/3.18.6) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.5...3.18.6) + +### **🚀 Enhancements** + + +
+AYON: Use `SettingsField` from ayon server #6173 + +This is preparation for new version of pydantic which will require to customize the field class for AYON purposes as raw pydantic Field could not be used. + + +___ + +
+ + +
+Nuke: Expose write knobs - OP-7592 #6137 + +This PR adds `exposed_knobs` to the creator plugins settings at `ayon+settings://nuke/create/CreateWriteRender/exposed_knobs`.When exposed knobs will be linked from the write node to the outside publish group, for users to adjust. + + +___ + +
+ + +
+AYON: Remove kitsu addon #6172 + +Removed kitsu addon from server addons because already has own repository. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Fusion: provide better logging for validate saver crash due type error #6082 + +Handles reported issue for `NoneType` error thrown in conversion `int(tool["Comments"][frame])`. It is most likely happening when saver node has no input connections.There is a validator for that, but it might be not obvious, that this error is caused by missing input connections and it has been already reported by `"Validate Saver Has Input"`. + + +___ + +
+ + +
+Workfile Template Builder: Use correct variable in create placeholder #6141 + +Use correct variable where failed instances are stored for validation. + + +___ + +
+ + +
+ExtractOIIOTranscode: Missing product_names to subsets conversion #6159 + +The `Product Names` filtering should be fixed with this. + + +___ + +
+ + +
+Blender: Fix missing animation data when updating blend assets #6165 + +Fix missing animation data when updating blend assets. + + +___ + +
+ + +
+TrayPublisher: Pre-fill of version works in AYON #6180 + +Use `folderPath` instead of `asset` in AYON mode to calculate next available version. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Chore: remove Muster #6085 + +Muster isn't maintained for a long time and it wasn't working anyway. This is removing related code from the code base. If there is renewed interest in Muster, it needs to be re-implemented in modern AYON compatible way. + + +___ + +
+ +### **Merged pull requests** + + +
+Maya: change label in the render settings to be more readable #6134 + +AYON replacement for #5713. + + +___ + +
+ + + + ## [3.18.5](https://github.com/ynput/OpenPype/tree/3.18.5) diff --git a/openpype/version.py b/openpype/version.py index 6cbe5ba6cd..078500cd3e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.6-nightly.2" +__version__ = "3.18.6" diff --git a/pyproject.toml b/pyproject.toml index 24172aa77f..453833aae2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.5" # OpenPype +version = "3.18.6" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 56772fe3f755786c5d4083cf0682f09d0170af9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 1 Feb 2024 15:48:42 +0000 Subject: [PATCH 220/281] 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 cd81171b73..e5dd558409 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.18.6 - 3.18.6-nightly.2 - 3.18.6-nightly.1 - 3.18.5 @@ -134,7 +135,6 @@ body: - 3.15.9-nightly.2 - 3.15.9-nightly.1 - 3.15.8 - - 3.15.8-nightly.3 validations: required: true - type: dropdown From 86cebe84365180d8a1ddbaf4a8cdd511cdf7eb40 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Feb 2024 19:32:39 +0100 Subject: [PATCH 221/281] use 'get_openpype_qt_app' to create Qt application 'get_openpype_qt_app' prepares QApplication a little bit better for scaling issues --- openpype/hosts/photoshop/api/lib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/photoshop/api/lib.py b/openpype/hosts/photoshop/api/lib.py index ff520348f0..d4d4995e6d 100644 --- a/openpype/hosts/photoshop/api/lib.py +++ b/openpype/hosts/photoshop/api/lib.py @@ -3,12 +3,11 @@ import sys import contextlib import traceback -from qtpy import QtWidgets - from openpype.lib import env_value_to_bool, Logger from openpype.modules import ModulesManager from openpype.pipeline import install_host from openpype.tools.utils import host_tools +from openpype.tools.utils import get_openpype_qt_app from openpype.tests.lib import is_in_tests from .launch_logic import ProcessLauncher, stub @@ -30,7 +29,7 @@ def main(*subprocess_args): # coloring in StdOutBroker os.environ["OPENPYPE_LOG_NO_COLORS"] = "False" - app = QtWidgets.QApplication([]) + app = get_openpype_qt_app() app.setQuitOnLastWindowClosed(False) launcher = ProcessLauncher(subprocess_args) From be70b52285c1d0c1d4542c20b0d91de7b889c174 Mon Sep 17 00:00:00 2001 From: Sponge96 Date: Fri, 2 Feb 2024 09:26:30 +0000 Subject: [PATCH 222/281] fix: comment using wrong syntax Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/startup/startup.ms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/startup/startup.ms b/openpype/hosts/max/startup/startup.ms index 3a4e76b3cf..5e79901cdd 100644 --- a/openpype/hosts/max/startup/startup.ms +++ b/openpype/hosts/max/startup/startup.ms @@ -8,7 +8,7 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath - # opens the create menu on startup to ensure users are presented with a useful default view. + /*opens the create menu on startup to ensure users are presented with a useful default view.*/ max create mode python.ExecuteFile startup From 2c3761ca37fd9ed7b815f03d086de70e84e83b57 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 2 Feb 2024 17:16:55 +0000 Subject: [PATCH 223/281] Make values for project_settings/ftrack/events/status_update case insensitive --- openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index ac4e499e41..5c780a51c4 100644 --- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -131,6 +131,8 @@ class PostFtrackHook(PostLaunchHook): for key, value in status_mapping.items(): if key in already_tested: continue + + value = value.lower() if actual_status in value or "__any__" in value: if key != "__ignore__": next_status_name = key From a939593c14d23918d0dfe84beee6dafeee9d3ac5 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 3 Feb 2024 03:25:40 +0000 Subject: [PATCH 224/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 078500cd3e..db6da9f656 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.6" +__version__ = "3.18.7-nightly.1" From 29d169e2b124aa9171698e540fc2b52f704af299 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 3 Feb 2024 03:26:23 +0000 Subject: [PATCH 225/281] 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 e5dd558409..54a9d69bdc 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.18.7-nightly.1 - 3.18.6 - 3.18.6-nightly.2 - 3.18.6-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.9 - 3.15.9-nightly.2 - 3.15.9-nightly.1 - - 3.15.8 validations: required: true - type: dropdown From ed339ed516391071948271d49a55a8e6564cceb8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Feb 2024 10:26:55 +0100 Subject: [PATCH 226/281] General: added fallback for broken ffprobe return (#6189) * OP-8090 - added fallback for ffprobe issue Customer provided .exr returned width and height equal to 0 which caused error in extract_thumbnail. This tries to use oiiotool to get metadata about file, in our case it read it correctly. * OP-8090 - extract logic `get_rescaled_command_arguments` is long enough right now, new method is better testable too. * Update openpype/lib/transcoding.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --------- Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/lib/transcoding.py | 50 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index c8ddbde061..1cfe9ac14b 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1227,12 +1227,8 @@ def get_rescaled_command_arguments( target_par = target_par or 1.0 input_par = 1.0 - # ffmpeg command - input_file_metadata = get_ffprobe_data(input_path, logger=log) - stream = input_file_metadata["streams"][0] - input_width = int(stream["width"]) - input_height = int(stream["height"]) - stream_input_par = stream.get("sample_aspect_ratio") + input_height, input_width, stream_input_par = _get_image_dimensions( + application, input_path, log) if stream_input_par: input_par = ( float(stream_input_par.split(":")[0]) @@ -1345,6 +1341,48 @@ def get_rescaled_command_arguments( return command_args +def _get_image_dimensions(application, input_path, log): + """Uses 'ffprobe' first and then 'oiiotool' if available to get dim. + + Args: + application (str): "oiiotool"|"ffmpeg" + input_path (str): path to image file + log (Optional[logging.Logger]): Logger used for logging. + Returns: + (tuple) (int, int, dict) - (height, width, sample_aspect_ratio) + Raises: + RuntimeError if image dimensions couldn't be parsed out. + """ + # ffmpeg command + input_file_metadata = get_ffprobe_data(input_path, logger=log) + input_width = input_height = 0 + stream = next( + ( + s for s in input_file_metadata["streams"] + if s.get("codec_type") == "video" + ), + {} + ) + if stream: + input_width = int(stream["width"]) + input_height = int(stream["height"]) + + # fallback for weird files with width=0, height=0 + if (input_width == 0 or input_height == 0) and application == "oiiotool": + # Load info about file from oiio tool + input_info = get_oiio_info_for_input(input_path, logger=log) + if input_info: + input_width = int(input_info["width"]) + input_height = int(input_info["height"]) + + if input_width == 0 or input_height == 0: + raise RuntimeError("Couldn't read {} either " + "with ffprobe or oiiotool".format(input_path)) + + stream_input_par = stream.get("sample_aspect_ratio") + return input_height, input_width, stream_input_par + + def convert_color_values(application, color_value): """Get color mapping for ffmpeg and oiiotool. Args: From d377b28f9e45036e1cc4892838c85f7d5196d5d6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Feb 2024 10:32:18 +0100 Subject: [PATCH 227/281] OP-8104 - fix unwanted change to field name (#6193) It should be image_format but in previous refactoring PR it fell back to original output_formats --- server_addon/fusion/server/settings.py | 8 +++++--- server_addon/fusion/server/version.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index b157ce9e40..a913db16da 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -57,9 +57,9 @@ class CreateSaverPluginModel(BaseSettingsModel): enum_resolver=_create_saver_instance_attributes_enum, title="Instance attributes" ) - output_formats: list[str] = SettingsField( - default_factory=list, - title="Output formats" + image_format: str = SettingsField( + enum_resolver=_image_format_enum, + title="Output Image Format" ) @@ -90,6 +90,8 @@ class CreateImageSaverModel(CreateSaverPluginModel): 0, title="Default rendered frame" ) + + class CreatPluginsModel(BaseSettingsModel): CreateSaver: CreateSaverModel = SettingsField( default_factory=CreateSaverModel, diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index ae7362549b..bbab0242f6 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.3" +__version__ = "0.1.4" From 907b5fb606675c5758ede110cfbb2ad64bafbccb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 10:38:57 +0100 Subject: [PATCH 228/281] modified docstrings for sphinx --- openpype/modules/click_wrap.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/openpype/modules/click_wrap.py b/openpype/modules/click_wrap.py index 3db5037b2f..ed67035ec8 100644 --- a/openpype/modules/click_wrap.py +++ b/openpype/modules/click_wrap.py @@ -12,7 +12,7 @@ How to use it? If you already have cli commands defined in addon, just replace 'click' with 'click_wrap' and it should work and modify your addon's cli method to convert 'click_wrap' object to 'click' object. -# Before +Before ```python import click from openpype.modules import OpenPypeModule @@ -37,7 +37,7 @@ def mycommand(arg1, arg2): print(arg1, arg2) ``` -# Now +Now ``` from openpype import click_wrap from openpype.modules import OpenPypeModule @@ -133,7 +133,6 @@ class Command(object): Returns: click.Command: Click command object. """ - return convert_to_click(self) # --- Methods for 'convert_to_click' function --- @@ -142,7 +141,6 @@ class Command(object): Returns: tuple: Command definition arguments. """ - return self._args def get_kwargs(self): @@ -150,7 +148,6 @@ class Command(object): Returns: dict[str, Any]: Command definition kwargs. """ - return self._kwargs def get_func(self): @@ -158,7 +155,6 @@ class Command(object): Returns: Function: Function to invoke on command trigger. """ - return self._func def iter_options(self): @@ -166,7 +162,6 @@ class Command(object): Yields: tuple[str, tuple, dict]: Option type name with args and kwargs. """ - for item in self._options: yield item # ----------------------------------------------- @@ -203,7 +198,6 @@ class Group(Command): Args: command (Command): Prepared command object. """ - if command not in self._commands: self._commands.append(command) @@ -213,7 +207,6 @@ class Group(Command): Args: group (Group): Prepared group object. """ - if group not in self._commands: self._commands.append(group) @@ -223,7 +216,6 @@ class Group(Command): Returns: Union[Command, Function]: New command object, or wrapper function. """ - return self._add_new(Command, *args, **kwargs) def group(self, *args, **kwargs): @@ -232,7 +224,6 @@ class Group(Command): Returns: Union[Group, Function]: New group object, or wrapper function. """ - return self._add_new(Group, *args, **kwargs) def _add_new(self, target_cls, *args, **kwargs): @@ -261,7 +252,6 @@ def convert_to_click(obj_to_convert): Returns: click.Command: Click command object. """ - import click commands_queue = collections.deque() From fe5ef4aa8c39b851b548064b9219027d0741311f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 11:23:44 +0100 Subject: [PATCH 229/281] store version id to versions set --- openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py index fade09305a..1d1bd1adbc 100644 --- a/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py +++ b/openpype/tools/ayon_sceneinventory/switch_dialog/dialog.py @@ -1214,9 +1214,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_ids = set() version_docs_by_parent_id_and_name = collections.defaultdict(dict) for version_doc in version_docs: - subset_id = version_doc["parent"] + version_ids.add(version_doc["_id"]) + product_id = version_doc["parent"] name = version_doc["name"] - version_docs_by_parent_id_and_name[subset_id][name] = version_doc + version_docs_by_parent_id_and_name[product_id][name] = version_doc hero_version_docs_by_parent_id = {} for hero_version_doc in hero_version_docs: From f5c38eb8d794fc6f3f3517322a796d18d8036d82 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 5 Feb 2024 10:25:01 +0000 Subject: [PATCH 230/281] Thumbnail subset filtering --- openpype/plugins/publish/extract_thumbnail.py | 30 +++++++++++++++++-- .../defaults/project_settings/global.json | 1 + .../schemas/schema_global_publish.json | 6 ++++ .../core/server/settings/publish_plugins.py | 5 ++++ server_addon/core/server/version.py | 2 +- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb261482..6008b8323d 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -2,6 +2,7 @@ import copy import os import subprocess import tempfile +import re import pyblish.api from openpype.lib import ( @@ -14,9 +15,10 @@ from openpype.lib import ( path_to_subprocess_arg, run_subprocess, ) -from openpype.lib.transcoding import convert_colorspace - -from openpype.lib.transcoding import VIDEO_EXTENSIONS +from openpype.lib.transcoding import ( + convert_colorspace, + VIDEO_EXTENSIONS, +) class ExtractThumbnail(pyblish.api.InstancePlugin): @@ -49,6 +51,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # attribute presets from settings oiiotool_defaults = None ffmpeg_args = None + subsets = [] + product_names = [] def process(self, instance): # run main process @@ -103,6 +107,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.debug("Skipping crypto passes.") return + # We only want to process the subsets needed from settings. + def validate_string_against_patterns(input_str, patterns): + for pattern in patterns: + if re.match(pattern, input_str): + return True + return False + + product_names = self.subsets + self.product_names + if product_names: + result = validate_string_against_patterns( + instance.data["subset"], product_names + ) + if not result: + self.log.debug( + "Subset \"{}\" did not match any valid subsets: {}".format( + instance.data["subset"], product_names + ) + ) + return + # first check for any explicitly marked representations for thumbnail explicit_repres = self._get_explicit_repres_for_thumbnail(instance) if explicit_repres: diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index bb7e3266bd..782fff1052 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -70,6 +70,7 @@ }, "ExtractThumbnail": { "enabled": true, + "subsets": [], "integrate_thumbnail": false, "background_color": [ 0, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 64f292a140..226a190dd4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -202,6 +202,12 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "list", + "object_type": "text", + "key": "subsets", + "label": "Subsets" + }, { "type": "boolean", "key": "integrate_thumbnail", diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py index 7aa86aafa6..8506801e7e 100644 --- a/server_addon/core/server/settings/publish_plugins.py +++ b/server_addon/core/server/settings/publish_plugins.py @@ -176,6 +176,10 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel): class ExtractThumbnailModel(BaseSettingsModel): _isGroup = True enabled: bool = SettingsField(True) + product_names: list[str] = SettingsField( + default_factory=list, + title="Product names" + ) integrate_thumbnail: bool = SettingsField( True, title="Integrate Thumbnail Representation" @@ -844,6 +848,7 @@ DEFAULT_PUBLISH_VALUES = { }, "ExtractThumbnail": { "enabled": True, + "product_names": [], "integrate_thumbnail": True, "target_size": { "type": "source" diff --git a/server_addon/core/server/version.py b/server_addon/core/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/core/server/version.py +++ b/server_addon/core/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From 7a7a4b1e9a084956d6923658848eeb896f3d88c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 12:04:55 +0100 Subject: [PATCH 231/281] handle empty project in 'get_project_product_types' --- openpype/tools/ayon_loader/abstract.py | 3 +++ openpype/tools/ayon_loader/models/products.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/tools/ayon_loader/abstract.py b/openpype/tools/ayon_loader/abstract.py index bf3e81d485..1d93716e07 100644 --- a/openpype/tools/ayon_loader/abstract.py +++ b/openpype/tools/ayon_loader/abstract.py @@ -531,6 +531,9 @@ class FrontendLoaderController(_BaseLoaderController): Product types have defined if are checked for filtering or not. + Args: + project_name (Union[str, None]): Project name. + Returns: list[ProductTypeItem]: List of product type items for a project. """ diff --git a/openpype/tools/ayon_loader/models/products.py b/openpype/tools/ayon_loader/models/products.py index 135f28df97..40b6474d12 100644 --- a/openpype/tools/ayon_loader/models/products.py +++ b/openpype/tools/ayon_loader/models/products.py @@ -179,12 +179,15 @@ class ProductsModel: """Product type items for project. Args: - project_name (str): Project name. + project_name (Union[str, None]): Project name. Returns: list[ProductTypeItem]: Product type items. """ + if not project_name: + return [] + cache = self._product_type_items_cache[project_name] if not cache.is_valid: product_types = ayon_api.get_project_product_types(project_name) From 6290343ce7b9b1facebcb7148fc41f775d9ae5fe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 Feb 2024 12:24:48 +0100 Subject: [PATCH 232/281] Use better resolution of Ayon apps on 4k display Special get_qt_app is used instead of shared get_openpype_qt_app as we don't want to set application icon. --- openpype/hosts/fusion/api/menu.py | 32 ++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 0b9ad1a43b..53ff5af767 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -173,8 +173,38 @@ class OpenPypeMenu(QtWidgets.QWidget): set_asset_framerange() +def get_qt_app(): + """Main Qt application.""" + + app = QtWidgets.QApplication.instance() + if app is None: + for attr_name in ( + "AA_EnableHighDpiScaling", + "AA_UseHighDpiPixmaps", + ): + attr = getattr(QtCore.Qt, attr_name, None) + if attr is not None: + QtWidgets.QApplication.setAttribute(attr) + + policy = os.getenv("QT_SCALE_FACTOR_ROUNDING_POLICY") + if ( + hasattr( + QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy" + ) + and not policy + ): + QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy( + QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough + ) + + app = QtWidgets.QApplication(sys.argv) + + return app + + def launch_openpype_menu(): - app = QtWidgets.QApplication(sys.argv) + + app = get_qt_app() pype_menu = OpenPypeMenu() From 78a3ec2118c9963e31ea0b4ca2907d0b84a14b39 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Mon, 5 Feb 2024 12:06:19 +0000 Subject: [PATCH 233/281] Fixed issue with double entries in the json manifest --- openpype/hosts/blender/api/render_lib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index fc47f5a659..c2792103e5 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -244,7 +244,9 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # and link it for rpass in passes: if rpass.name == "Image": - pass_name = "rgba" if multi_exr else "beauty" + if not multi_exr: + continue + pass_name = "rgba" else: pass_name = rpass.name filename = f"{name}{aov_sep}{pass_name}.####" @@ -263,6 +265,7 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): pass_name = "composite" filename = f"{name}{aov_sep}{pass_name}.####" comp_socket = slots.new(pass_name if multi_exr else filename) + filepath = str(output_path / filename.lstrip("/")) aov_file_products.append(("Composite", filepath)) for link in tree.links: From 9452cdb06d289aced41d4bffb0104f6fc24d2ab5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 5 Feb 2024 13:45:04 +0100 Subject: [PATCH 234/281] define 'get_qt_app' in tools utils --- openpype/hosts/fusion/api/menu.py | 30 +----------------------------- openpype/tools/utils/__init__.py | 1 + openpype/tools/utils/lib.py | 23 +++++++++++++++++++---- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py index 53ff5af767..ff4a734928 100644 --- a/openpype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -15,6 +15,7 @@ from openpype.hosts.fusion.api.lib import ( ) from openpype.pipeline import get_current_asset_name from openpype.resources import get_openpype_icon_filepath +from openpype.tools.utils import get_qt_app from .pipeline import FusionEventHandler from .pulse import FusionPulse @@ -173,35 +174,6 @@ class OpenPypeMenu(QtWidgets.QWidget): set_asset_framerange() -def get_qt_app(): - """Main Qt application.""" - - app = QtWidgets.QApplication.instance() - if app is None: - for attr_name in ( - "AA_EnableHighDpiScaling", - "AA_UseHighDpiPixmaps", - ): - attr = getattr(QtCore.Qt, attr_name, None) - if attr is not None: - QtWidgets.QApplication.setAttribute(attr) - - policy = os.getenv("QT_SCALE_FACTOR_ROUNDING_POLICY") - if ( - hasattr( - QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy" - ) - and not policy - ): - QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy( - QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough - ) - - app = QtWidgets.QApplication(sys.argv) - - return app - - def launch_openpype_menu(): app = get_qt_app() diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index 50d50f467a..74702a2a10 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -32,6 +32,7 @@ from .lib import ( set_style_property, DynamicQThread, qt_app_context, + get_qt_app, get_openpype_qt_app, get_asset_icon, get_asset_icon_by_name, diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 365caaafd9..c7f92dd26e 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -154,11 +154,15 @@ def qt_app_context(): yield app -def get_openpype_qt_app(): - """Main Qt application initialized for OpenPype processed. +def get_qt_app(): + """Get Qt application. - This function should be used only inside OpenPype process and never inside - other processes. + The function initializes new Qt application if it is not already + initialized. It also sets some attributes to the application to + ensure that it will work properly on high DPI displays. + + Returns: + QtWidgets.QApplication: Current Qt application. """ app = QtWidgets.QApplication.instance() @@ -184,6 +188,17 @@ def get_openpype_qt_app(): app = QtWidgets.QApplication(sys.argv) + return app + + +def get_openpype_qt_app(): + """Main Qt application initialized for OpenPype processed. + + This function should be used only inside OpenPype process and never inside + other processes. + """ + + app = get_qt_app() app.setWindowIcon(QtGui.QIcon(get_app_icon_path())) return app From f83d0f974958ffef089e4fb362cf6f13514fe104 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Feb 2024 14:25:12 +0800 Subject: [PATCH 235/281] AYON menu would be registered when the workspace has been changed --- openpype/hosts/max/api/pipeline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index d0ae854dc8..18e287266a 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -59,6 +59,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) + rt.callbacks.addScript(rt.Name('postWorkspaceChange'), + self._deferred_menu_creation) def has_unsaved_changes(self): # TODO: how to get it from 3dsmax? From f2429680ff5aa68b358ececeba02d24e9d86ad0c Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 6 Feb 2024 15:43:18 +0000 Subject: [PATCH 236/281] Fix old links and duplicated entries in the manifest --- openpype/hosts/blender/api/render_lib.py | 114 ++++++++++++----------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index c2792103e5..17b9d926ec 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -178,6 +178,14 @@ def set_render_passes(settings, renderer): return aov_list, custom_passes +def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): + filename = f"{name}{aov_sep}{rpass_name}.####" + slot = slots.new(rpass_name if multi_exr else filename) + filepath = str(output_path / filename.lstrip("/")) + + return slot, filepath + + def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True @@ -188,40 +196,34 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): output_type = "CompositorNodeOutputFile" compositor_type = "CompositorNodeComposite" - # Get the Render Layer and Composite nodes - rl_node = None - comp_node = None + # Get the Render Layer, Composite and the previous output nodes + render_layer_node = None + composite_node = None + old_output_node = None for node in tree.nodes: if node.bl_idname == comp_layer_type: - rl_node = node + render_layer_node = node elif node.bl_idname == compositor_type: - comp_node = node - if rl_node and comp_node: + composite_node = node + elif node.bl_idname == output_type and "AYON" in node.name: + old_output_node = node + if render_layer_node and composite_node and old_output_node: break # If there's not a Render Layers node, we create it - if not rl_node: - rl_node = tree.nodes.new(comp_layer_type) + if not render_layer_node: + render_layer_node = tree.nodes.new(comp_layer_type) # Get the enabled output sockets, that are the active passes for the # render. # We also exclude some layers. - exclude_sockets = ["Alpha", "Noisy Image"] + exclude_sockets = ["Image", "Alpha", "Noisy Image"] passes = [ socket - for socket in rl_node.outputs + for socket in render_layer_node.outputs if socket.enabled and socket.name not in exclude_sockets ] - old_output = None - - # Remove all output nodes that inlcude "AYON" in the name. - # There should be only one. - for node in tree.nodes: - if node.bl_idname == output_type and "AYON" in node.name: - old_output = node - break - # Create a new output node output = tree.nodes.new(output_type) @@ -240,46 +242,54 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): aov_file_products = [] - # For each active render pass, we add a new socket to the output node - # and link it - for rpass in passes: - if rpass.name == "Image": - if not multi_exr: - continue - pass_name = "rgba" - else: - pass_name = rpass.name - filename = f"{name}{aov_sep}{pass_name}.####" + old_links = { + link.from_socket.name: link for link in tree.links + if link.to_node == old_output_node} - slots.new(pass_name if multi_exr else filename) - - filepath = str(output_path / filename.lstrip("/")) - - aov_file_products.append((rpass.name, filepath)) - - if not old_output: - node_input = output.inputs[-1] - tree.links.new(rpass, node_input) + # Create a new socket for the beauty output + pass_name = "rgba" if multi_exr else "beauty" + slot, _ = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) + tree.links.new(render_layer_node.outputs["Image"], slot) # Create a new socket for the composite output pass_name = "composite" - filename = f"{name}{aov_sep}{pass_name}.####" - comp_socket = slots.new(pass_name if multi_exr else filename) - filepath = str(output_path / filename.lstrip("/")) + comp_socket, filepath = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) aov_file_products.append(("Composite", filepath)) - for link in tree.links: - if link.to_node == old_output: - socket_name = link.to_socket.name - new_socket = output.inputs.get(socket_name) - if new_socket: - tree.links.new(link.from_socket, new_socket) - elif comp_node and link.to_node == comp_node: - tree.links.new(link.from_socket, comp_socket) + # For each active render pass, we add a new socket to the output node + # and link it + for rpass in passes: + slot, filepath = _create_aov_slot( + name, aov_sep, slots, rpass.name, multi_exr, output_path) + aov_file_products.append((rpass.name, filepath)) + + # If the rpass was not connected with the old output node, we connect + # it with the new one. + if not old_links.get(rpass.name): + tree.links.new(rpass, slot) + + for link in list(old_links.values()): + # Check if the socket is still available in the new output node. + socket = output.inputs.get(link.to_socket.name) + # If it is, we connect it with the new output node. + if socket: + tree.links.new(link.from_socket, socket) + # Then, we remove the old link. + tree.links.remove(link) + + # If there's a composite node, we connect its input with the new output + if composite_node: + for link in tree.links: + if link.to_node == composite_node: + tree.links.new(link.from_socket, comp_socket) + break + + if old_output_node: + output.location = old_output_node.location + tree.nodes.remove(old_output_node) - if old_output: - output.location = old_output.location - tree.nodes.remove(old_output) output.name = "AYON File Output" output.label = "AYON File Output" From 1c9c125dc2dcb6662c69cff95f881263b49711c5 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Wed, 7 Feb 2024 11:00:33 +1300 Subject: [PATCH 237/281] enhancement/OP-8033 --- openpype/modules/timers_manager/timers_manager.py | 5 +++-- openpype/modules/timers_manager/widget_user_idle.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 674d834a1d..e684737d5e 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -162,6 +162,7 @@ class TimersManager( def tray_start(self, *_a, **_kw): if self._idle_manager: self._idle_manager.start() + self.show_message() def tray_exit(self): if self._idle_manager: @@ -373,8 +374,8 @@ class TimersManager( ).format(module.name)) def show_message(self): - if self.is_running is False: - return + # if self.is_running is False: + # return if not self._widget_user_idle.is_showed(): self._widget_user_idle.reset_countdown() self._widget_user_idle.show() diff --git a/openpype/modules/timers_manager/widget_user_idle.py b/openpype/modules/timers_manager/widget_user_idle.py index 9df328e6b2..8cc78cf102 100644 --- a/openpype/modules/timers_manager/widget_user_idle.py +++ b/openpype/modules/timers_manager/widget_user_idle.py @@ -17,6 +17,7 @@ class WidgetUserIdle(QtWidgets.QWidget): self.setWindowFlags( QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint + | QtCore.Qt.WindowStaysOnTopHint ) self._is_showed = False From 1d3c1c7c6c0bb5a0a4bd3c62e91c7a58e1a72c86 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Wed, 7 Feb 2024 11:06:28 +1300 Subject: [PATCH 238/281] enhancement/OP-8033 --- openpype/modules/timers_manager/timers_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index e684737d5e..674d834a1d 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -162,7 +162,6 @@ class TimersManager( def tray_start(self, *_a, **_kw): if self._idle_manager: self._idle_manager.start() - self.show_message() def tray_exit(self): if self._idle_manager: @@ -374,8 +373,8 @@ class TimersManager( ).format(module.name)) def show_message(self): - # if self.is_running is False: - # return + if self.is_running is False: + return if not self._widget_user_idle.is_showed(): self._widget_user_idle.reset_countdown() self._widget_user_idle.show() From bac4f6d9bd2b53b97c01eb7d40bda7415b062e0b Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 7 Feb 2024 03:25:17 +0000 Subject: [PATCH 239/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index db6da9f656..d105b0169e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.1" +__version__ = "3.18.7-nightly.2" From 4243ee427cd24d9eb67fdd38c051c2388eb45e27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 7 Feb 2024 03:25:56 +0000 Subject: [PATCH 240/281] 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 54a9d69bdc..f751a54116 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.18.7-nightly.2 - 3.18.7-nightly.1 - 3.18.6 - 3.18.6-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.10-nightly.1 - 3.15.9 - 3.15.9-nightly.2 - - 3.15.9-nightly.1 validations: required: true - type: dropdown From 5b9b26050d071b48c5141faffa46d439473f5f20 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Feb 2024 10:47:41 +0100 Subject: [PATCH 241/281] feat: Add settings category for Tray Publisher This commit adds a new settings category for the Tray Publisher plugin in order to organize its configuration options more effectively. --- openpype/hosts/traypublisher/api/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 6859b85a46..a6075f0eb5 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -32,6 +32,7 @@ SHARED_DATA_KEY = "openpype.traypublisher.instances" class HiddenTrayPublishCreator(HiddenCreator): host_name = "traypublisher" + settings_category = "traypublisher" def collect_instances(self): instances_by_identifier = cache_and_get_instances( @@ -68,6 +69,7 @@ class HiddenTrayPublishCreator(HiddenCreator): class TrayPublishCreator(Creator): create_allow_context_change = True host_name = "traypublisher" + settings_category = "traypublisher" def collect_instances(self): instances_by_identifier = cache_and_get_instances( From 14fc4ce6be35f02096ecc56cc865300ef67a705c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 17:51:52 +0800 Subject: [PATCH 242/281] Implementation of workfile creator --- .../max/plugins/create/create_workfile.py | 130 ++++++++++++++++++ .../max/plugins/publish/collect_members.py | 5 +- .../max/plugins/publish/collect_workfile.py | 14 +- 3 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/max/plugins/create/create_workfile.py diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py new file mode 100644 index 0000000000..b43a353136 --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating workfiles.""" +from openpype import AYON_SERVER_ENABLED +from openpype.pipeline import CreatedInstance, AutoCreator, CreatorError +from openpype.client import get_asset_by_name, get_asset_name_identifier +from openpype.hosts.max.api import plugin +from openpype.hosts.max.api.lib import read, imprint +from pymxs import runtime as rt + + +class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): + """Workfile auto-creator.""" + identifier = "io.openpype.creators.max.workfile" + label = "Workfile" + family = "workfile" + icon = "fa5.file" + + default_variant = "Main" + + def create(self): + variant = self.default_variant + current_instance = next( + ( + instance for instance in self.create_context.instances + if instance.creator_identifier == self.identifier + ), None) + project_name = self.project_name + asset_name = self.create_context.get_current_asset_name() + task_name = self.create_context.get_current_task_name() + host_name = self.create_context.host_name + + if current_instance is None: + current_instance_asset = None + elif AYON_SERVER_ENABLED: + current_instance_asset = current_instance["folderPath"] + else: + current_instance_asset = current_instance["asset"] + + if current_instance is None: + asset_doc = get_asset_by_name(project_name, asset_name) + data = { + "task": task_name, + "variant": variant + } + if AYON_SERVER_ENABLED: + data["folderPath"] = asset_name + else: + data["asset"] = asset_name + + data.update( + self.get_dynamic_data( + variant, task_name, asset_doc, + project_name, host_name, current_instance) + ) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + self.log.info("Auto-creating workfile instance...") + instance_node = self.create_node(subset_name) + data["instance_node"] = instance_node.name + current_instance = CreatedInstance( + self.family, subset_name, data, self + ) + self._add_instance_to_context(current_instance) + imprint(instance_node.name, current_instance.data) + elif ( + current_instance_asset != asset_name + or current_instance["task"] != task_name + ): + # Update instance context if is not the same + asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) + asset_name = get_asset_name_identifier(asset_doc) + + if AYON_SERVER_ENABLED: + current_instance["folderPath"] = asset_name + else: + current_instance["asset"] = asset_name + current_instance["task"] = task_name + current_instance["subset"] = subset_name + + def collect_instances(self): + self.cache_subsets(self.collection_shared_data) + for instance in self.collection_shared_data["max_cached_subsets"].get(self.identifier, []): # noqa + if not rt.getNodeByName(instance): + continue + created_instance = CreatedInstance.from_existing( + read(rt.GetNodeByName(instance)), self + ) + self._add_instance_to_context(created_instance) + + + def update_instances(self, update_list): + for created_inst, changes in update_list: + instance_node = created_inst.get("instance_node") + new_values = { + key: changes[key].new_value + for key in changes.changed_keys + } + + imprint( + instance_node, + created_inst.data_to_store() + ) + + def remove_instances(self, instances): + """Remove specified instance from the scene. + + This is only removing `id` parameter so instance is no longer + instance, because it might contain valuable data for artist. + + """ + for instance in instances: + instance_node = rt.GetNodeByName( + instance.data.get("instance_node")) + if instance_node: + rt.Delete(instance_node) + + self._remove_instance_from_context(instance) + + + def create_node(self, subset_name): + if rt.getNodeByName(subset_name): + node = rt.getNodeByName(subset_name) + return node + node = rt.Container(name=subset_name) + node.isHidden = True + return node diff --git a/openpype/hosts/max/plugins/publish/collect_members.py b/openpype/hosts/max/plugins/publish/collect_members.py index 2970cf0e24..7cd646e0e7 100644 --- a/openpype/hosts/max/plugins/publish/collect_members.py +++ b/openpype/hosts/max/plugins/publish/collect_members.py @@ -12,7 +12,10 @@ class CollectMembers(pyblish.api.InstancePlugin): hosts = ['max'] def process(self, instance): - + if instance.data["family"] == "workfile": + self.log.debug("Skipping Actions for workfile family.") + self.log.debug("{}".format(instance.data["subset"])) + return if instance.data.get("instance_node"): container = rt.GetNodeByName(instance.data["instance_node"]) instance.data["members"] = [ diff --git a/openpype/hosts/max/plugins/publish/collect_workfile.py b/openpype/hosts/max/plugins/publish/collect_workfile.py index 0eb4bb731e..446175c0ed 100644 --- a/openpype/hosts/max/plugins/publish/collect_workfile.py +++ b/openpype/hosts/max/plugins/publish/collect_workfile.py @@ -6,15 +6,16 @@ import pyblish.api from pymxs import runtime as rt -class CollectWorkfile(pyblish.api.ContextPlugin): +class CollectWorkfile(pyblish.api.InstancePlugin): """Inject the current working file into context""" order = pyblish.api.CollectorOrder - 0.01 label = "Collect 3dsmax Workfile" hosts = ['max'] - def process(self, context): + def process(self, instance): """Inject the current working file.""" + context = instance.context folder = rt.maxFilePath file = rt.maxFileName if not folder or not file: @@ -23,15 +24,12 @@ class CollectWorkfile(pyblish.api.ContextPlugin): context.data['currentFile'] = current_file - filename, ext = os.path.splitext(file) - - task = context.data["task"] + ext = os.path.splitext(file)[-1].lstrip(".") data = {} # create instance - instance = context.create_instance(name=filename) - subset = 'workfile' + task.capitalize() + subset = instance.data["subset"] data.update({ "subset": subset, @@ -55,7 +53,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): }] instance.data.update(data) - + self.log.info('Collected data: {}'.format(data)) self.log.info('Collected instance: {}'.format(file)) self.log.info('Scene path: {}'.format(current_file)) self.log.info('staging Dir: {}'.format(folder)) From 8bac54fcf0745645489a29fc2e78bac1d9d4c284 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 17:54:39 +0800 Subject: [PATCH 243/281] move back the subset name before dynamic data --- openpype/hosts/max/plugins/create/create_workfile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index b43a353136..70944b3e2c 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -38,6 +38,9 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): if current_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) + subset_name = self.get_subset_name( + variant, task_name, asset_doc, project_name, host_name + ) data = { "task": task_name, "variant": variant @@ -52,9 +55,6 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): variant, task_name, asset_doc, project_name, host_name, current_instance) ) - subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name - ) self.log.info("Auto-creating workfile instance...") instance_node = self.create_node(subset_name) data["instance_node"] = instance_node.name From af022f3561ac96b3d891b52238219ddd913d5f5c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 17:59:20 +0800 Subject: [PATCH 244/281] hound shut --- openpype/hosts/max/plugins/create/create_workfile.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index 70944b3e2c..25213871d8 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating workfiles.""" from openpype import AYON_SERVER_ENABLED -from openpype.pipeline import CreatedInstance, AutoCreator, CreatorError +from openpype.pipeline import CreatedInstance, AutoCreator from openpype.client import get_asset_by_name, get_asset_name_identifier from openpype.hosts.max.api import plugin from openpype.hosts.max.api.lib import read, imprint @@ -91,15 +91,9 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): ) self._add_instance_to_context(created_instance) - def update_instances(self, update_list): for created_inst, changes in update_list: instance_node = created_inst.get("instance_node") - new_values = { - key: changes[key].new_value - for key in changes.changed_keys - } - imprint( instance_node, created_inst.data_to_store() @@ -120,7 +114,6 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): self._remove_instance_from_context(instance) - def create_node(self, subset_name): if rt.getNodeByName(subset_name): node = rt.getNodeByName(subset_name) From 2248240306fdf01dc2f756982b11304c48304cc5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 18:00:08 +0800 Subject: [PATCH 245/281] hound shut --- openpype/hosts/max/plugins/create/create_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index 25213871d8..4ec3c6d3ad 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -92,7 +92,7 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): self._add_instance_to_context(created_instance) def update_instances(self, update_list): - for created_inst, changes in update_list: + for created_inst, _ in update_list: instance_node = created_inst.get("instance_node") imprint( instance_node, From bd8135f4dcea13240c8a894b9f70472b7a535851 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 7 Feb 2024 18:01:15 +0800 Subject: [PATCH 246/281] hound shut --- openpype/hosts/max/plugins/create/create_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_workfile.py b/openpype/hosts/max/plugins/create/create_workfile.py index 4ec3c6d3ad..30692ccd06 100644 --- a/openpype/hosts/max/plugins/create/create_workfile.py +++ b/openpype/hosts/max/plugins/create/create_workfile.py @@ -93,7 +93,7 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): def update_instances(self, update_list): for created_inst, _ in update_list: - instance_node = created_inst.get("instance_node") + instance_node = created_inst.get("instance_node") imprint( instance_node, created_inst.data_to_store() From 3c47798d121215505c7b0349482f7947f1b79dbe Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Feb 2024 20:07:16 +0800 Subject: [PATCH 247/281] edit the action.py too regarding to #6164 --- openpype/hosts/max/api/action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py index 506847652b..c3c1957af1 100644 --- a/openpype/hosts/max/api/action.py +++ b/openpype/hosts/max/api/action.py @@ -31,7 +31,10 @@ class SelectInvalidAction(pyblish.api.Action): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid] + invalid_names = [obj.name for obj in invalid if isinstance(obj, str)] + if not invalid_names: + invalid_names = [obj.name for obj, _ in invalid] + invalid = [obj for obj, _ in invalid] self.log.info( "Selecting invalid objects: %s", ", ".join(invalid_names) ) From 1fcdde0a9cc3a8b31697540ce320286d83d30e99 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 8 Feb 2024 13:46:56 +0100 Subject: [PATCH 248/281] AfterEffects: added toggle for applying values from DB during creation (#6204) * OP-8130 - After Effects added flag to force values from Asset to composition during creation This allows controlling setting of values (resolution, duration) from Asset (DB) to the created instance. Default is to set it automatically. * OP-8130 - Ayon version of Settings for AE creator --- openpype/hosts/aftereffects/plugins/create/create_render.py | 6 +++++- .../settings/defaults/project_settings/aftereffects.json | 3 ++- .../projects_schema/schema_project_aftereffects.json | 6 ++++++ .../aftereffects/server/settings/creator_plugins.py | 2 ++ server_addon/aftereffects/server/version.py | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index fadfc0c206..b4fb20f922 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -29,6 +29,7 @@ class RenderCreator(Creator): # Settings mark_for_review = True + force_setting_values = True def create(self, subset_name_from_ui, data, pre_create_data): stub = api.get_stub() # only after After Effects is up @@ -96,7 +97,9 @@ class RenderCreator(Creator): self._add_instance_to_context(new_instance) stub.rename_item(comp.id, subset_name) - set_settings(True, True, [comp.id], print_msg=False) + + if self.force_setting_values: + set_settings(True, True, [comp.id], print_msg=False) def get_pre_create_attr_defs(self): output = [ @@ -173,6 +176,7 @@ class RenderCreator(Creator): ) self.mark_for_review = plugin_settings["mark_for_review"] + self.force_setting_values = plugin_settings["force_setting_values"] self.default_variants = plugin_settings.get( "default_variants", plugin_settings.get("defaults") or [] diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index 77ccb74410..9e2ab7334b 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -15,7 +15,8 @@ "default_variants": [ "Main" ], - "mark_for_review": true + "mark_for_review": true, + "force_setting_values": true } }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 72f09a641d..b0f8a7357f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -42,6 +42,12 @@ "key": "mark_for_review", "label": "Review", "default": true + }, + { + "type": "boolean", + "key": "force_setting_values", + "label": "Force resolution and duration values from Asset", + "default": true } ] } diff --git a/server_addon/aftereffects/server/settings/creator_plugins.py b/server_addon/aftereffects/server/settings/creator_plugins.py index 988a036589..5d4ba30cd0 100644 --- a/server_addon/aftereffects/server/settings/creator_plugins.py +++ b/server_addon/aftereffects/server/settings/creator_plugins.py @@ -7,6 +7,8 @@ class CreateRenderPlugin(BaseSettingsModel): default_factory=list, title="Default Variants" ) + force_setting_values: bool = SettingsField( + True, title="Force resolution and duration values from Asset") class AfterEffectsCreatorPlugins(BaseSettingsModel): diff --git a/server_addon/aftereffects/server/version.py b/server_addon/aftereffects/server/version.py index df0c92f1e2..e57ad00718 100644 --- a/server_addon/aftereffects/server/version.py +++ b/server_addon/aftereffects/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.2" +__version__ = "0.1.3" From b70b418dee0a08b320eed557490edc237a48a0ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 8 Feb 2024 14:34:41 +0100 Subject: [PATCH 249/281] update ayon unreal plugin --- openpype/hosts/unreal/integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/integration b/openpype/hosts/unreal/integration index 63266607ce..a4755d2869 160000 --- a/openpype/hosts/unreal/integration +++ b/openpype/hosts/unreal/integration @@ -1 +1 @@ -Subproject commit 63266607ceb972a61484f046634ddfc9eb0b5757 +Subproject commit a4755d2869694fcf58c98119298cde8d204e2ce4 From d502a26bfe55b8062dc383a00b61c8f21b490c99 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:47:37 +0100 Subject: [PATCH 250/281] Update CONTRIBUTING.md --- CONTRIBUTING.md | 55 +++++++------------------------------------------ 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 644a74c1f7..2898c13acd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,53 +1,12 @@ -## How to contribute to Pype +## How to contribute to OpenPype -We are always happy for any contributions for OpenPype improvements. Before making a PR and starting working on an issue, please read these simple guidelines. +OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON a the same time. -#### **Did you find a bug?** +## Getting my PR merged during this period -1. Check in the issues and our [bug triage[(https://github.com/pypeclub/pype/projects/2) to make sure it wasn't reported already. -2. Ask on our [discord](http://pype.community/chat) Often, what appears as a bug, might be the intended behaviour for someone else. -3. Create a new issue. -4. Use the issue template for you PR please. +- Each OpenPype PR MUST have a corresponding AYON PR in github. Without AYON compatibility features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from OpenPype to AYON should be really easy. +- Please keep the corresponding OpenPype and AYON PR names the same so they can be easily identified. +Inside each PR, put a link to the corresponding PR from the other product. OpenPype PRs should point to AYON PR and vice versa. -#### **Did you write a patch that fixes a bug?** - -- Open a new GitHub pull request with the patch. -- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. - -#### **Do you intend to add a new feature or change an existing one?** - -- Open a new thread in the [github discussions](https://github.com/pypeclub/pype/discussions/new) -- Do not open issue until the suggestion is discussed. We will convert accepted suggestions into backlog and point them to the relevant discussion thread to keep the context. -- If you are already working on a new feature and you'd like it eventually merged to the main codebase, please consider making a DRAFT PR as soon as possible. This makes it a lot easier to give feedback, discuss the code and functionalit, plus it prevents multiple people tackling the same problem independently. - -#### **Do you have questions about the source code?** - -Open a new question on [github discussions](https://github.com/pypeclub/pype/discussions/new) - -## Branching Strategy - -As we move to 3.x as the primary supported version of pype and only keep 2.15 on bug bugfixes and client sponsored feature requests, we need to be very careful with merging strategy. - -We also use this opportunity to switch the branch naming. 3.0 production branch will no longer be called MASTER, but will be renamed to MAIN. Develop will stay as it is. - -A few important notes about 2.x and 3.x development: - -- 3.x features are not backported to 2.x unless specifically requested -- 3.x bugs and hotfixes can be ported to 2.x if they are relevant or severe -- 2.x features and bugs MUST be ported to 3.x at the same time - -## Pull Requests - -- Each 2.x PR MUST have a corresponding 3.x PR in github. Without 3.x PR, 2.x features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from 2.x to 3.x should be really easy. -- Please keep the corresponding 2 and 3 PR names the same so they can be easily identified from the PR list page. -- Each 2.x PR should be labeled with `2.x-dev` label. - -Inside each PR, put a link to the corresponding PR for the other version - -Of course if you want to contribute, feel free to make a PR to only 2.x/develop or develop, based on what you are using. While reviewing the PRs, we might convert the code to corresponding PR for the other release ourselves. - -We might also change the target of you PR to and intermediate branch, rather than `develop` if we feel it requires some extra work on our end. That way, we preserve all your commits so you don't loose out on the contribution credits. - - -If a PR is targeted at 2.x release it must be labelled with 2x-dev label in Github. +AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository you AYON equivalent PR should target, feel free to make OpenPype PR first and ask. From 39f3f777e5f05a40f7b31c7036c1ac79beae71d1 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:55:31 +0100 Subject: [PATCH 251/281] Update CONTRIBUTING.md Co-authored-by: Petr Kalis --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2898c13acd..5f3fb90dfa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## How to contribute to OpenPype -OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON a the same time. +OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON at the same time. ## Getting my PR merged during this period From f2add8f7f158e1d8237dd21816e08a018118832b Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 17:55:36 +0100 Subject: [PATCH 252/281] Update CONTRIBUTING.md Co-authored-by: Petr Kalis --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f3fb90dfa..27294b19be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,4 +9,4 @@ OpenPype has reached the end of its life and is now in a limited maintenance mod Inside each PR, put a link to the corresponding PR from the other product. OpenPype PRs should point to AYON PR and vice versa. -AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository you AYON equivalent PR should target, feel free to make OpenPype PR first and ask. +AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository your AYON equivalent PR should target, feel free to make OpenPype PR first and ask. From 91917f20c21324e5be8fc69534d4a3dad23f50fe Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 8 Feb 2024 19:02:45 +0100 Subject: [PATCH 253/281] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a79b9f2582..5b8d3692dc 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,13 @@ OpenPype ## Important Notice! -OpenPype as a standalone product has reach end of it's life and this repository is now used as a pipeline core code for [AYON](https://ynput.io/ayon/). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 +OpenPype as a standalone product has reach end of it's life and this repository is now being phased out in favour of [ayon-core](https://github.com/ynput/ayon-core). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877 +As such, we no longer accept Pull Requests that are not ported to AYON at the same time! + +``` +Please refer to https://github.com/ynput/OpenPype/blob/develop/CONTRIBUTING.md for more information about the current PR process. +``` Introduction ------------ From f17588b51d59f71eaffdbaf317773794a70608ff Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Feb 2024 15:44:08 +0000 Subject: [PATCH 254/281] Fix getting non-existent settings --- openpype/hosts/nuke/api/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index c8301b81fd..fe89a79096 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -1348,7 +1348,9 @@ def _remove_old_knobs(node): def exposed_write_knobs(settings, plugin_name, instance_node): - exposed_knobs = settings["nuke"]["create"][plugin_name]["exposed_knobs"] + exposed_knobs = settings["nuke"]["create"][plugin_name].get( + "exposed_knobs", [] + ) if exposed_knobs: instance_node.addKnob(nuke.Text_Knob('', 'Write Knobs')) write_node = nuke.allNodes(group=instance_node, filter="Write")[0] From f49781aae15060be76838016be437e74cb1441bd Mon Sep 17 00:00:00 2001 From: JackP Date: Fri, 9 Feb 2024 16:00:57 +0000 Subject: [PATCH 255/281] fix: typo in class function --- openpype/hosts/max/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index ce4afd2e8b..1b74b8131c 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -60,7 +60,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('filePostOpen'), lib.check_colorspace) - def workfiles_has_unsaved_changes(self): + def workfile_has_unsaved_changes(self): return rt.getSaveRequired() def get_workfile_extensions(self): From 960de700dd3ba6fb9c75fbff7abda39d60ab7c56 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 9 Feb 2024 17:33:30 +0100 Subject: [PATCH 256/281] OP-8165 - fix AE local render doesnt push thumbnail to Ftrack (#6212) Without thumbnail review is not clickable from main Versions list --- openpype/plugins/publish/extract_thumbnail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb261482..291345abb1 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -35,6 +35,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "traypublisher", "substancepainter", "nuke", + "aftereffects" ] enabled = False From 20e12d613e4c700d981f381044818c1ce6b746e5 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Feb 2024 16:33:48 +0000 Subject: [PATCH 257/281] Fix exposed knobs validator --- openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py index fe5644f0c9..f592fc4a44 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py +++ b/openpype/hosts/nuke/plugins/publish/validate_exposed_knobs.py @@ -65,7 +65,7 @@ class ValidateExposedKnobs( group_node = instance.data["transientData"]["node"] nuke_settings = instance.context.data["project_settings"]["nuke"] create_settings = nuke_settings["create"][plugin] - exposed_knobs = create_settings["exposed_knobs"] + exposed_knobs = create_settings.get("exposed_knobs", []) unexposed_knobs = [] for knob in exposed_knobs: if knob not in group_node.knobs(): From 0e58b3efc665e7e4e5581c4238164cb3a95bac7b Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 10 Feb 2024 03:24:54 +0000 Subject: [PATCH 258/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index d105b0169e..39fb10bb6e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.2" +__version__ = "3.18.7-nightly.3" From ecaf8a6f6b1ebd256ad7dc45d934eaa4ee10ef40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Feb 2024 03:25:29 +0000 Subject: [PATCH 259/281] 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 f751a54116..7cf51713e4 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.18.7-nightly.3 - 3.18.7-nightly.2 - 3.18.7-nightly.1 - 3.18.6 @@ -134,7 +135,6 @@ body: - 3.15.10-nightly.2 - 3.15.10-nightly.1 - 3.15.9 - - 3.15.9-nightly.2 validations: required: true - type: dropdown From cf4bb19ad6dbf7221bc514d32422b609d3ccb489 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 12 Feb 2024 11:30:35 +1300 Subject: [PATCH 260/281] enhancement/OP-7723_hidden_jonts_validator --- .../hosts/maya/plugins/publish/validate_rig_joints_hidden.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index 30d95128a2..24762a4232 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -7,6 +7,7 @@ from openpype.hosts.maya.api import lib from openpype.pipeline.publish import ( RepairAction, ValidateContentsOrder, + PublishValidationError ) @@ -38,7 +39,7 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Visible joints found: {0}".format(invalid)) + raise PublishValidationError("Visible joints found: {0}".format(invalid)) @classmethod def repair(cls, instance): From 7f040bd32240198a988b027fa144dbf2bcbef1a9 Mon Sep 17 00:00:00 2001 From: Braden Jennings Date: Mon, 12 Feb 2024 11:36:06 +1300 Subject: [PATCH 261/281] enhancement/OP-7723_hidden_jonts_validator --- .../hosts/maya/plugins/publish/validate_rig_joints_hidden.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index 24762a4232..2bb5036f8b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -39,7 +39,8 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise PublishValidationError("Visible joints found: {0}".format(invalid)) + raise PublishValidationError( + "Visible joints found: {0}".format(invalid)) @classmethod def repair(cls, instance): From 858a90335fc792b20a34179b281f1859af7d94b6 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 14 Feb 2024 03:25:48 +0000 Subject: [PATCH 262/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 39fb10bb6e..9e1bd39b3a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.3" +__version__ = "3.18.7-nightly.4" From b15643e2cd8d64f06a7cfa238aed705eaea4de2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Feb 2024 03:26:23 +0000 Subject: [PATCH 263/281] 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 7cf51713e4..bc0e00f740 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.18.7-nightly.4 - 3.18.7-nightly.3 - 3.18.7-nightly.2 - 3.18.7-nightly.1 @@ -134,7 +135,6 @@ body: - 3.15.10 - 3.15.10-nightly.2 - 3.15.10-nightly.1 - - 3.15.9 validations: required: true - type: dropdown From bcd216946859bfdb38b2d88df4541c9fe6319da7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Feb 2024 10:11:37 +0000 Subject: [PATCH 264/281] Cast aov_list to set to improve performance --- openpype/hosts/blender/api/render_lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index 17b9d926ec..bb35dd44ea 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -101,7 +101,7 @@ def set_render_format(ext, multilayer): def set_render_passes(settings, renderer): - aov_list = settings["blender"]["RenderSettings"]["aov_list"] + aov_list = set(settings["blender"]["RenderSettings"]["aov_list"]) custom_passes = settings["blender"]["RenderSettings"]["custom_passes"] # Common passes for both renderers @@ -175,7 +175,7 @@ def set_render_passes(settings, renderer): aov.type = (cp["value"] if AYON_SERVER_ENABLED else cp[1].get("type", "VALUE")) - return aov_list, custom_passes + return list(aov_list), custom_passes def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): From 6002da8235c32ac74eeb5e7c40f5153b8326f59d Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 14 Feb 2024 10:18:52 +0000 Subject: [PATCH 265/281] Added an option to disable composite output --- openpype/hosts/blender/api/render_lib.py | 29 ++++++++++++++----- .../defaults/project_settings/blender.json | 1 + .../schema_project_blender.json | 5 ++++ .../server/settings/render_settings.py | 4 +++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/api/render_lib.py b/openpype/hosts/blender/api/render_lib.py index bb35dd44ea..c09cfce502 100644 --- a/openpype/hosts/blender/api/render_lib.py +++ b/openpype/hosts/blender/api/render_lib.py @@ -56,6 +56,14 @@ def get_renderer(settings): ["renderer"]) +def get_compositing(settings): + """Get compositing from blender settings.""" + + return (settings["blender"] + ["RenderSettings"] + ["compositing"]) + + def get_render_product(output_path, name, aov_sep): """ Generate the path to the render product. Blender interprets the `#` @@ -186,7 +194,9 @@ def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path): return slot, filepath -def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): +def set_node_tree( + output_path, render_product, name, aov_sep, ext, multilayer, compositing +): # Set the scene to use the compositor node tree to render bpy.context.scene.use_nodes = True @@ -252,11 +262,12 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): name, aov_sep, slots, pass_name, multi_exr, output_path) tree.links.new(render_layer_node.outputs["Image"], slot) - # Create a new socket for the composite output - pass_name = "composite" - comp_socket, filepath = _create_aov_slot( - name, aov_sep, slots, pass_name, multi_exr, output_path) - aov_file_products.append(("Composite", filepath)) + if compositing: + # Create a new socket for the composite output + pass_name = "composite" + comp_socket, filepath = _create_aov_slot( + name, aov_sep, slots, pass_name, multi_exr, output_path) + aov_file_products.append(("Composite", filepath)) # For each active render pass, we add a new socket to the output node # and link it @@ -280,7 +291,7 @@ def set_node_tree(output_path, render_product, name, aov_sep, ext, multilayer): tree.links.remove(link) # If there's a composite node, we connect its input with the new output - if composite_node: + if compositing and composite_node: for link in tree.links: if link.to_node == composite_node: tree.links.new(link.from_socket, comp_socket) @@ -323,6 +334,7 @@ def prepare_rendering(asset_group): ext = get_image_format(settings) multilayer = get_multilayer(settings) renderer = get_renderer(settings) + compositing = get_compositing(settings) set_render_format(ext, multilayer) bpy.context.scene.render.engine = renderer @@ -332,7 +344,8 @@ def prepare_rendering(asset_group): render_product = get_render_product(output_path, name, aov_sep) aov_file_product = set_node_tree( - output_path, render_product, name, aov_sep, ext, multilayer) + output_path, render_product, name, aov_sep, + ext, multilayer, compositing) # Clear the render filepath, so that the output is handled only by the # output node in the compositor. diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 48f3ef8ef0..03a5400ced 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -23,6 +23,7 @@ "image_format": "exr", "multilayer_exr": true, "renderer": "CYCLES", + "compositing": true, "aov_list": ["combined"], "custom_passes": [] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index bbed881ab0..13e460b74c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -114,6 +114,11 @@ {"BLENDER_EEVEE": "Eevee"} ] }, + { + "key": "compositing", + "type": "boolean", + "label": "Enable Compositing" + }, { "key": "aov_list", "label": "AOVs to create", diff --git a/server_addon/blender/server/settings/render_settings.py b/server_addon/blender/server/settings/render_settings.py index a1f6d3114a..f992ea6fcc 100644 --- a/server_addon/blender/server/settings/render_settings.py +++ b/server_addon/blender/server/settings/render_settings.py @@ -127,6 +127,9 @@ class RenderSettingsModel(BaseSettingsModel): title="Renderer", enum_resolver=renderers_enum ) + compositing: bool = SettingsField( + title="Enable Compositing" + ) aov_list: list[str] = SettingsField( default_factory=list, enum_resolver=aov_list_enum, @@ -149,6 +152,7 @@ DEFAULT_RENDER_SETTINGS = { "image_format": "exr", "multilayer_exr": True, "renderer": "CYCLES", + "compositing": True, "aov_list": ["combined"], "custom_passes": [] } From ea00109d86dd5f40422ee0eabe046ed65d50586a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 12:35:05 +0000 Subject: [PATCH 266/281] Expose families transfer setting. --- openpype/settings/defaults/project_settings/deadline.json | 1 + .../schemas/projects_schema/schema_project_deadline.json | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index b02cfa8207..0c4b282d10 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -129,6 +129,7 @@ "deadline_priority": 50, "publishing_script": "", "skip_integration_repre_list": [], + "families_transfer": ["render3d", "render2d", "ftrack", "slate"], "aov_filter": { "maya": [ ".*([Bb]eauty).*" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 42dea33ef9..bb8e0b5cd4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -693,6 +693,14 @@ "type": "text" } }, + { + "type": "list", + "key": "families_transfer", + "label": "List of family names to transfer\nto generated instances (AOVs for example).", + "object_type": { + "type": "text" + } + }, { "type": "dict-modifiable", "docstring": "Regular expression to filter for which subset review should be created in publish job.", From 3520f5cc90a03d882460513e63283b0b43d54f32 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 15:25:34 +0000 Subject: [PATCH 267/281] Add AVALON_DB to Deadline submissions --- .../deadline/plugins/publish/submit_aftereffects_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_blender_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_fusion_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_harmony_deadline.py | 1 + .../deadline/plugins/publish/submit_houdini_cache_deadline.py | 1 + .../deadline/plugins/publish/submit_houdini_render_deadline.py | 1 + openpype/modules/deadline/plugins/publish/submit_max_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_maya_deadline.py | 1 + .../plugins/publish/submit_maya_remote_publish_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_nuke_deadline.py | 1 + .../modules/deadline/plugins/publish/submit_publish_cache_job.py | 1 + openpype/modules/deadline/plugins/publish/submit_publish_job.py | 1 + 12 files changed, 12 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index 009375e87e..d40c371de0 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -82,6 +82,7 @@ class AfterEffectsSubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py index 8f9e9a7425..58e69d0aea 100644 --- a/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -104,6 +104,7 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py index 9a718aa089..dcb79588a7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -223,6 +223,7 @@ class FusionSubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 17e672334c..73bc10465d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -275,6 +275,7 @@ class HarmonySubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index ada69575a8..bef93b3947 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -110,6 +110,7 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index bf7fb45a8b..6ed9e66ce0 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -205,6 +205,7 @@ class HoudiniSubmitDeadline( "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index f06bd4dbe6..e31de0a101 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -108,6 +108,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 5591db151a..4cd417b83b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -201,6 +201,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 41a2a64ab5..a9fb10de8b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -108,6 +108,7 @@ class MayaSubmitRemotePublishDeadline( if key in os.environ}, **legacy_io.Session) # TODO replace legacy_io with context.data + environment["AVALON_DB"] = os.environ.get("AVALON_DB") environment["AVALON_PROJECT"] = project_name environment["AVALON_ASSET"] = instance.context.data["asset"] environment["AVALON_TASK"] = instance.context.data["task"] diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 746b009255..9c2d212806 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -376,6 +376,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, keys = [ "PYTHONPATH", "PATH", + "AVALON_DB", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py index 1bb45b77cc..434a823cfe 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -131,6 +131,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, create_metadata_path(instance, anatomy) environment = { + "AVALON_DB": os.environ["AVALON_DB"], "AVALON_PROJECT": instance.context.data["projectName"], "AVALON_ASSET": instance.context.data["asset"], "AVALON_TASK": instance.context.data["task"], diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 82971daee5..f622ec9a00 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -187,6 +187,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, create_metadata_path(instance, anatomy) environment = { + "AVALON_DB": os.environ["AVALON_DB"], "AVALON_PROJECT": instance.context.data["projectName"], "AVALON_ASSET": instance.context.data["asset"], "AVALON_TASK": instance.context.data["task"], From 89d69f7c94db6558b8da06014ef0a9861130b585 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 16 Feb 2024 16:02:50 +0000 Subject: [PATCH 268/281] Remove redundant instance_skeleton_data code. --- .../plugins/publish/submit_publish_job.py | 146 ------------------ 1 file changed, 146 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 82971daee5..4e9df976cd 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -321,7 +321,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, return deadline_publish_job_id - def process(self, instance): # type: (pyblish.api.Instance) -> None """Process plugin. @@ -338,151 +337,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Skipping local instance.") return - data = instance.data.copy() - context = instance.context - self.context = context - self.anatomy = instance.context.data["anatomy"] - - asset = data.get("asset") or context.data["asset"] - subset = data.get("subset") - - start = instance.data.get("frameStart") - if start is None: - start = context.data["frameStart"] - - end = instance.data.get("frameEnd") - if end is None: - end = context.data["frameEnd"] - - handle_start = instance.data.get("handleStart") - if handle_start is None: - handle_start = context.data["handleStart"] - - handle_end = instance.data.get("handleEnd") - if handle_end is None: - handle_end = context.data["handleEnd"] - - fps = instance.data.get("fps") - if fps is None: - fps = context.data["fps"] - - if data.get("extendFrames", False): - start, end = self._extend_frames( - asset, - subset, - start, - end, - data["overrideExistingFrame"]) - - try: - source = data["source"] - except KeyError: - source = context.data["currentFile"] - - success, rootless_path = ( - self.anatomy.find_root_template_from_path(source) - ) - if success: - source = rootless_path - - else: - # `rootless_path` is not set to `source` if none of roots match - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues." - ).format(source)) - - family = "render" - if ("prerender" in instance.data["families"] or - "prerender.farm" in instance.data["families"]): - family = "prerender" - families = [family] - - # pass review to families if marked as review - do_not_add_review = False - if data.get("review"): - families.append("review") - elif data.get("review") is False: - self.log.debug("Instance has review explicitly disabled.") - do_not_add_review = True - - instance_skeleton_data = { - "family": family, - "subset": subset, - "families": families, - "asset": asset, - "frameStart": start, - "frameEnd": end, - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStartHandle": start - handle_start, - "frameEndHandle": end + handle_end, - "comment": instance.data["comment"], - "fps": fps, - "source": source, - "extendFrames": data.get("extendFrames"), - "overrideExistingFrame": data.get("overrideExistingFrame"), - "pixelAspect": data.get("pixelAspect", 1), - "resolutionWidth": data.get("resolutionWidth", 1920), - "resolutionHeight": data.get("resolutionHeight", 1080), - "multipartExr": data.get("multipartExr", False), - "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True), - # map inputVersions `ObjectId` -> `str` so json supports it - "inputVersions": list(map(str, data.get("inputVersions", []))), - "colorspace": instance.data.get("colorspace"), - "stagingDir_persistent": instance.data.get( - "stagingDir_persistent", False - ) - } - - # skip locking version if we are creating v01 - instance_version = instance.data.get("version") # take this if exists - if instance_version != 1: - instance_skeleton_data["version"] = instance_version - - # transfer specific families from original instance to new render - for item in self.families_transfer: - if item in instance.data.get("families", []): - instance_skeleton_data["families"] += [item] - - # transfer specific properties from original instance based on - # mapping dictionary `instance_transfer` - for key, values in self.instance_transfer.items(): - if key in instance.data.get("families", []): - for v in values: - instance_skeleton_data[v] = instance.data.get(v) - - # look into instance data if representations are not having any - # which are having tag `publish_on_farm` and include them - for repre in instance.data.get("representations", []): - staging_dir = repre.get("stagingDir") - if staging_dir: - success, rootless_staging_dir = ( - self.anatomy.find_root_template_from_path( - staging_dir - ) - ) - if success: - repre["stagingDir"] = rootless_staging_dir - else: - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(staging_dir)) - repre["stagingDir"] = staging_dir - - if "publish_on_farm" in repre.get("tags"): - # create representations attribute of not there - if "representations" not in instance_skeleton_data.keys(): - instance_skeleton_data["representations"] = [] - - instance_skeleton_data["representations"].append(repre) - - instances = None - assert data.get("expectedFiles"), ("Submission from old Pype version" - " - missing expectedFiles") - anatomy = instance.context.data["anatomy"] instance_skeleton_data = create_skeleton_instance( From 9d612e1f0f9c5deb442931ba2cf633c112088518 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 17 Feb 2024 03:25:05 +0000 Subject: [PATCH 269/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 9e1bd39b3a..64d8075c4a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.4" +__version__ = "3.18.7-nightly.5" From 6657b847b8ffd91e77b37e9639cef0b839dc720c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Feb 2024 03:25:42 +0000 Subject: [PATCH 270/281] 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 bc0e00f740..4d48212d4a 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.18.7-nightly.5 - 3.18.7-nightly.4 - 3.18.7-nightly.3 - 3.18.7-nightly.2 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.1 - 3.15.10 - 3.15.10-nightly.2 - - 3.15.10-nightly.1 validations: required: true - type: dropdown From 6aa534dbc4af1a8f59381617332c7db16c8dd40c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 19 Feb 2024 16:44:17 +0100 Subject: [PATCH 271/281] fix value lowering in postlaunch hook --- openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index 5c780a51c4..1876ff20eb 100644 --- a/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -132,7 +132,7 @@ class PostFtrackHook(PostLaunchHook): if key in already_tested: continue - value = value.lower() + value = [i.lower() for i in value] if actual_status in value or "__any__" in value: if key != "__ignore__": next_status_name = key From e1f5bdb5a9a2c7225583ba7b2a164f548d76d8d4 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Tue, 20 Feb 2024 11:27:12 +0000 Subject: [PATCH 272/281] Removed redundant option in aov list --- .../entities/schemas/projects_schema/schema_project_blender.json | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 13e460b74c..2ffdc6070d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -126,7 +126,6 @@ "multiselection": true, "defaults": "empty", "enum_items": [ - {"empty": "< empty >"}, {"combined": "Combined"}, {"z": "Z"}, {"mist": "Mist"}, From b0499edb1a53f17ffa4785909cefd444b3702d3a Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 20 Feb 2024 13:05:15 +0000 Subject: [PATCH 273/281] [Automated] Release --- CHANGELOG.md | 401 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 403 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 009150ae7d..4ec3448570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,407 @@ # Changelog +## [3.18.7](https://github.com/ynput/OpenPype/tree/3.18.7) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.6...3.18.7) + +### **🆕 New features** + + +
+Chore: Wrapper for click proposal #5928 + +This is a proposal how to resolve issues with `click` python module. Issue https://github.com/ynput/OpenPype/issues/5921 reported that in Houdini 20+ is our click clashing with click in houdini, where is expected higher version. We can't update our version to support older pythons (NOTE older Python 3). + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Add repair action to hidden joints validator #6214 + +Joints Hidden is missing repair action, this adds it back + + +___ + +
+ + +
+Blender: output node and EXR #6086 + +Output node now works correctly for Multilayer EXR and keeps existing links. The output now is handled entirely by the compositor node tree. + + +___ + +
+ + +
+AYON Switch tool: Keep version after switch #6104 + +Keep version if only representation did change. The AYON variant of https://github.com/ynput/OpenPype/pull/4629 + + +___ + +
+ + +
+Loader AYON: Reset loader window on open #6170 + +Make sure loader tool is reset on each show. + + +___ + +
+ + +
+Publisher: Show message with error on action failure #6179 + +This PR adds support for the publisher to show error message from running actions.Errors from actions will otherwise be hidden from user in various console outputs.Also include card for when action is finished. + + +___ + +
+ + +
+AYON Applications: Remove djvview group from default applications #6188 + +The djv does not have group defined in models so the values are not used anywhere. + + +___ + +
+ + +
+General: added fallback for broken ffprobe return #6189 + +Customer provided .exr returned width and height equal to 0 which caused error in `extract_thumbnail`. This tries to use oiiotool to get metadata about file, in our case it read it correctly. + + +___ + +
+ + +
+Photoshop: High scaling in UIs #6190 + +Use `get_openpype_qt_app` to create `QApplication` in Photoshop. + + +___ + +
+ + +
+Ftrack: Status update settings are not case insensitive. #6195 + +Make values for project_settings/ftrack/events/status_update case insensitive. + + +___ + +
+ + +
+Thumbnail product filtering #6197 + +This PR introduces subset filtering for thumbnail extraction. This is to skip passes like zdepth which is not needed and can cause issues with extraction. Also speeds up publishing. + + +___ + +
+ + +
+TimersManager: Idle dialog always on top #6201 + +Make stop timer dialog always on tophttps://app.clickup.com/t/6658547/OP-8033 + + +___ + +
+ + +
+AfterEffects: added toggle for applying values from DB during creation #6204 + +Previously values (resolution, duration) from Asset (eg. DB) were applied explicitly when instance of `render` product type was created. This PR adds toggle to Settings to disable this. (This allows artist to publish non standard length of composition, disabling of `Validate Scene Settings` is still required.) + + +___ + +
+ + +
+Unreal: Update plugin commit #6208 + +Updated unreal plugin to latest main. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Traypublisher: editorial avoid audio tracks processing #6038 + +Avoiding audio tracks from EDL editorial publishing. + + +___ + +
+ + +
+Resolve Inventory offsets clips when swapping versions #6128 + +Swapped version retain the offset and IDT of the timelime clip.closes: https://github.com/ynput/OpenPype/issues/6125 + + +___ + +
+ + +
+Publisher window as dialog #6176 + +Changing back Publisher window to QDialog. + + +___ + +
+ + +
+Nuke: Validate write node fix error report - OP-8088 #6183 + +Report error was not printing the expected values from settings, but instead the values on the write node, leading to confusing messages like: +``` +Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 135, in process + self._make_error(check) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 149, in _make_error + raise PublishXmlValidationError( +openpype.pipeline.publish.publish_plugins.PublishXmlValidationError: Write node's knobs values are not correct! +Knob 'channels' > Correct: `rgb` > Wrong: `rgb` +``` +This PR changes the error report to: +``` +Traceback (most recent call last): + File "C:\Users\tokejepsen\AppData\Local\Ynput\AYON\dependency_packages\ayon_2310271602_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process + runner(*args) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 135, in process + self._make_error(check) + File "C:\Users\tokejepsen\OpenPype\openpype\hosts\nuke\plugins\publish\validate_write_nodes.py", line 149, in _make_error + raise PublishXmlValidationError( +openpype.pipeline.publish.publish_plugins.PublishXmlValidationError: Write node's knobs values are not correct! +Knob 'channels' > Expected: `['rg']` > Current: `rgb` +``` + + + +___ + +
+ + +
+Nuke: Camera product type loaded is not updating - OP-7973 #6184 + +When updating the camera this error would appear: +``` +(...)openpype/hosts/nuke/plugins/load/load_camera_abc.py", line 142, in update + camera_node = nuke.toNode(object_name) +TypeError: toNode() argument 1 must be str, not Node +``` + + + +___ + +
+ + +
+AYON settings: Use bundle name as variant in dev mode #6187 + +Make sure the bundle name is used in dev mode for settings variant. + + +___ + +
+ + +
+Fusion: fix unwanted change to field name in Settings #6193 + +It should be `image_format` but in previous refactoring PR it fell back to original `output_formats` which caused enum not to show up and propagate into plugin. + + +___ + +
+ + +
+Bugfix: AYON menu disappeared when the workspace has been changed in 3dsMax #6200 + +AYON plugins are not correctly registered when switching to different workspaces. + + +___ + +
+ + +
+TrayPublisher: adding settings category to base creator classes #6202 + +Settings are resolving correctly as they suppose to. + + +___ + +
+ + +
+Nuke: expose knobs backward compatibility fix - OP-8164 #6211 + +Fix backwards compatibility for settings `project_settings/nuke/create/CreateWriteRender/exposed_knobs`. + + +___ + +
+ + +
+AE: fix local render doesn't push thumbnail to Ftrack #6212 + +Without thumbnail review is not clickable from main Versions list + + +___ + +
+ + +
+Nuke: openpype expose knobs validator - OP-8166 #6213 + +Fix exposed knobs validator for backwards compatibility with missing settings. + + +___ + +
+ + +
+Ftrack: Post-launch hook fix value lowering #6221 + +Fix lowerin of values in status mapping. + + +___ + +
+ +### **🔀 Refactored code** + + +
+Maya: Remove `shelf` class and shelf build on maya `userSetup.py` #5837 + +Remove shelf builder logic. It appeared to be unused and had bugs. + + +___ + +
+ +### **Merged pull requests** + + +
+Max: updated implementation of save_scene + small QOL improvements to host #6186 + +- Removed `has_unsaved_changes` from Max host as it looks to have been unused and unimplemented. +- Added and implemented `workfile_has_unsaved_changes` to Max host. +- Mirrored the Houdini host to implement the above into `save_scene` publish for Max. +- Added a line to `startup.ms` which opens the usual 'default' menu inside of Max (see screenshots).Current (Likely opens this menu due to one or more of the startup scripts used to insert OP menu):New: + + +___ + +
+ + +
+Fusion: Use better resolution of Ayon apps on 4k display #6199 + +Changes size (makes it smaller) of Ayon apps (Workfiles, Loader) in Fusion on high definitions displays. + + +___ + +
+ + +
+Update CONTRIBUTING.md #6210 + +Updating contributing guidelines to reflect the EOL state of repository +___ + +
+ + +
+Deadline: Remove redundant instance_skeleton_data code - OP-8269 #6219 + +This PR https://github.com/ynput/OpenPype/pull/5186 re-introduced code about for the `instance_skeleton_data` but its actually not used since this variable gets overwritten later. + + +___ + +
+ + + + ## [3.18.6](https://github.com/ynput/OpenPype/tree/3.18.6) diff --git a/openpype/version.py b/openpype/version.py index 64d8075c4a..a389280775 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7-nightly.5" +__version__ = "3.18.7" diff --git a/pyproject.toml b/pyproject.toml index 453833aae2..eef6a2e978 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.18.6" # OpenPype +version = "3.18.7" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From f06658af081435a879f336b6d962dbc669aafe13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Feb 2024 13:06:09 +0000 Subject: [PATCH 274/281] 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 4d48212d4a..7fe6c79259 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.18.7 - 3.18.7-nightly.5 - 3.18.7-nightly.4 - 3.18.7-nightly.3 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.2 - 3.15.11-nightly.1 - 3.15.10 - - 3.15.10-nightly.2 validations: required: true - type: dropdown From 7ea09acb3307507576ea248eb996f5431ce71c2c Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 21 Feb 2024 03:24:33 +0000 Subject: [PATCH 275/281] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index a389280775..95203e17c9 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.18.7" +__version__ = "3.18.8-nightly.1" From 51026fbca803ec1281fdc30bc4c55314f36825fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Feb 2024 03:25:06 +0000 Subject: [PATCH 276/281] 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 7fe6c79259..c01ab5122c 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.18.8-nightly.1 - 3.18.7 - 3.18.7-nightly.5 - 3.18.7-nightly.4 @@ -134,7 +135,6 @@ body: - 3.15.11-nightly.3 - 3.15.11-nightly.2 - 3.15.11-nightly.1 - - 3.15.10 validations: required: true - type: dropdown From 669e0a79550766030d7e573b45c41176bb9d1266 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Feb 2024 13:16:30 +0000 Subject: [PATCH 277/281] Add OP settings and convert in plugin --- .../plugins/publish/collect_clip_effects.py | 10 +++++--- .../defaults/project_settings/hiero.json | 4 +++ .../projects_schema/schema_project_hiero.json | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index d7f646ebc9..44767e458a 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -72,9 +72,13 @@ class CollectClipEffects(pyblish.api.InstancePlugin): subset_split.insert(0, "effect") - effect_categories = { - x["name"]: x["effect_classes"] for x in self.effect_categories - } + # Need to convert to dict for AYON settings. This isinstance check can + # be removed in the future when OpenPype is no longer. + effect_categories = self.effect_categories + if isinstance(self.effect_categories, list): + effect_categories = { + x["name"]: x["effect_classes"] for x in self.effect_categories + } category_by_effect = {"": ""} for key, values in effect_categories.items(): diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 9c83733b09..efd80a8876 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -69,6 +69,10 @@ "tags_addition": [ "review" ] + }, + "CollectClipEffects": { + "enabled": true, + "effect_categories": {} } }, "filters": {}, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index d80edf902b..73bd475815 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -312,6 +312,31 @@ "label": "Tags addition" } ] + }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "CollectClipEffects", + "label": "Collect Clip Effects", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "dict-modifiable", + "key": "effect_categories", + "label": "Effect Categories", + "object_type": { + "type": "list", + "key": "effects_classes", + "object_type": "text" + } + } + ] } ] }, From 81c657227ef19487c03a91d92bebca1705cbb830 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Fri, 23 Feb 2024 17:12:51 +0000 Subject: [PATCH 278/281] Use folderpath when collecting the render instance --- .../hosts/unreal/plugins/publish/collect_render_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py index dad0310dfc..d7b9191fa3 100644 --- a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py @@ -64,7 +64,7 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): new_data = new_instance.data - new_data["asset"] = seq_name + new_data["asset"] = f"/{s.get('output')}" new_data["setMembers"] = seq_name new_data["family"] = "render" new_data["families"] = ["render", "review"] From 1dcdb311054a42b8a85c56664553dd3118370aec Mon Sep 17 00:00:00 2001 From: Jose Caraballo Date: Mon, 26 Feb 2024 17:39:55 +0100 Subject: [PATCH 279/281] Pass correct arguments to function. --- openpype/tools/publisher/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 13d007dd35..41039a5be0 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2337,7 +2337,7 @@ class PublisherController(BasePublisherController): "title": "Action failed", "message": "Action failed.", "traceback": "".join( - traceback.format_exception(exception) + traceback.format_exception(type(exception), exception, exception.__traceback__) ), "label": action.__name__, "identifier": action.id From 98deab1f638ac83b03c9b34d82e634db16690f83 Mon Sep 17 00:00:00 2001 From: Jose Caraballo Date: Mon, 26 Feb 2024 17:51:48 +0100 Subject: [PATCH 280/281] Fix line length. --- openpype/tools/publisher/control.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 41039a5be0..f9b8bcc512 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -2337,7 +2337,11 @@ class PublisherController(BasePublisherController): "title": "Action failed", "message": "Action failed.", "traceback": "".join( - traceback.format_exception(type(exception), exception, exception.__traceback__) + traceback.format_exception( + type(exception), + exception, + exception.__traceback__ + ) ), "label": action.__name__, "identifier": action.id From 3a3b535001460d4e20cbccc3e05ca265872c4037 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 5 Mar 2024 11:01:59 +0000 Subject: [PATCH 281/281] Fix submit_max_deadline imports (#6235) --- .../deadline/plugins/publish/submit_max_deadline.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index e31de0a101..07bbb1cacb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -16,11 +16,6 @@ from openpype.pipeline.publish.lib import ( replace_with_published_scene_path ) from openpype.pipeline.publish import KnownPublishError -from openpype.hosts.max.api.lib import ( - get_current_renderer, - get_multipass_setting -) -from openpype.hosts.max.api.lib_rendersettings import RenderSettings from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.lib import is_running_from_build @@ -294,6 +289,9 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, Args: infos(dict): a dictionary with plugin info. """ + from openpype.hosts.max.api.lib import get_current_renderer + from openpype.hosts.max.api.lib_rendersettings import RenderSettings + instance = self._instance # set the target camera plugin_info = copy.deepcopy(self.plugin_info) @@ -359,6 +357,8 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, job_info_list (list): A list of multiple job infos plugin_info_list (list): A list of multiple plugin infos """ + from openpype.hosts.max.api.lib import get_multipass_setting + job_info_list = [] plugin_info_list = [] instance = self._instance