From b06935efb74a6a0bb4c9e1865e61bc64d2e6be12 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 11 Oct 2023 17:13:18 +0800 Subject: [PATCH 01/42] Enhancement on the setting in review family --- openpype/hosts/max/api/lib.py | 3 + .../hosts/max/plugins/create/create_review.py | 46 +++++++++---- .../max/plugins/publish/collect_review.py | 7 +- .../publish/extract_review_animation.py | 65 +++++++++++++++---- .../max/plugins/publish/extract_thumbnail.py | 11 +++- 5 files changed, 102 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 8b70b3ced7..6f41cf9260 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -324,6 +324,7 @@ def is_headless(): @contextlib.contextmanager def viewport_camera(camera): original = rt.viewport.getCamera() + has_autoplay = rt.preferences.playPreviewWhenDone if not original: # if there is no original camera # use the current camera as original @@ -331,9 +332,11 @@ def viewport_camera(camera): review_camera = rt.getNodeByName(camera) try: rt.viewport.setCamera(review_camera) + rt.preferences.playPreviewWhenDone = False yield finally: rt.viewport.setCamera(original) + rt.preferences.playPreviewWhenDone = has_autoplay def set_timeline(frameStart, frameEnd): diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 5737114fcc..5638327d72 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -17,27 +17,41 @@ class CreateReview(plugin.MaxCreator): instance_data["imageFormat"] = pre_create_data.get("imageFormat") instance_data["keepImages"] = pre_create_data.get("keepImages") instance_data["percentSize"] = pre_create_data.get("percentSize") - instance_data["rndLevel"] = pre_create_data.get("rndLevel") + instance_data["visualStyleMode"] = pre_create_data.get("visualStyleMode") + # Transfer settings from pre create to instance + creator_attributes = instance_data.setdefault( + "creator_attributes", dict()) + for key in ["imageFormat", + "keepImages", + "percentSize", + "visualStyleMode", + "viewportPreset"]: + if key in pre_create_data: + creator_attributes[key] = pre_create_data[key] super(CreateReview, self).create( subset_name, instance_data, pre_create_data) - def get_pre_create_attr_defs(self): - attrs = super(CreateReview, self).get_pre_create_attr_defs() - + def get_instance_attr_defs(self): image_format_enum = [ "bmp", "cin", "exr", "jpg", "hdr", "rgb", "png", "rla", "rpf", "dds", "sgi", "tga", "tif", "vrimg" ] - rndLevel_enum = [ - "smoothhighlights", "smooth", "facethighlights", - "facet", "flat", "litwireframe", "wireframe", "box" + visual_style_preset_enum = [ + "Realistic", "Shaded", "Facets", + "ConsistentColors", "HiddenLine", + "Wireframe", "BoundingBox", "Ink", + "ColorInk", "Acrylic", "Tech", "Graphite", + "ColorPencil", "Pastel", "Clay", "ModelAssist" ] + preview_preset_enum = [ + "Quality", "Standard", "Performance", + "DXMode", "Customize"] - return attrs + [ + return [ BoolDef("keepImages", label="Keep Image Sequences", default=False), @@ -50,8 +64,16 @@ class CreateReview(plugin.MaxCreator): default=100, minimum=1, decimals=0), - EnumDef("rndLevel", - rndLevel_enum, - default="smoothhighlights", - label="Preference") + EnumDef("visualStyleMode", + visual_style_preset_enum, + default="Realistic", + label="Preference"), + EnumDef("viewportPreset", + preview_preset_enum, + default="Quality", + label="Pre-View Preset") ] + + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attributes + return self.get_instance_attr_defs() diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 8e27a857d7..1f0dca5329 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -25,10 +25,15 @@ class CollectReview(pyblish.api.InstancePlugin, if rt.classOf(node) in rt.Camera.classes: camera_name = node.name focal_length = node.fov - + creator_attrs = instance.data["creator_attributes"] attr_values = self.get_attr_values_from_data(instance.data) data = { "review_camera": camera_name, + "imageFormat": creator_attrs["imageFormat"], + "keepImages": creator_attrs["keepImages"], + "percentSize": creator_attrs["percentSize"], + "visualStyleMode": creator_attrs["visualStyleMode"], + "viewportPreset": creator_attrs["viewportPreset"], "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], "fps": instance.context.data["fps"], diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 8e06e52b5c..64ecbe5d85 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -1,8 +1,12 @@ import os +import contextlib import pyblish.api from pymxs import runtime as rt from openpype.pipeline import publish -from openpype.hosts.max.api.lib import viewport_camera, get_max_version +from openpype.hosts.max.api.lib import ( + viewport_camera, + get_max_version +) class ExtractReviewAnimation(publish.Extractor): @@ -32,10 +36,24 @@ class ExtractReviewAnimation(publish.Extractor): " '%s' to '%s'" % (filename, staging_dir)) review_camera = instance.data["review_camera"] - with viewport_camera(review_camera): - preview_arg = self.set_preview_arg( - instance, filepath, start, end, fps) - rt.execute(preview_arg) + if get_max_version() >= 2024: + with viewport_camera(review_camera): + preview_arg = self.set_preview_arg( + instance, filepath, start, end, fps) + rt.execute(preview_arg) + else: + visual_style_preset = instance.data.get("visualStyleMode") + nitrousGraphicMgr = rt.NitrousGraphicsManager + viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() + with viewport_camera(review_camera) and ( + self._visual_style_option( + viewport_setting, visual_style_preset) + ): + viewport_setting.VisualStyleMode = rt.Name( + visual_style_preset) + preview_arg = self.set_preview_arg( + instance, filepath, start, end, fps) + rt.execute(preview_arg) tags = ["review"] if not instance.data.get("keepImages"): @@ -76,10 +94,6 @@ class ExtractReviewAnimation(publish.Extractor): job_args.append(default_option) frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa job_args.append(frame_option) - rndLevel = instance.data.get("rndLevel") - if rndLevel: - option = f"rndLevel:#{rndLevel}" - job_args.append(option) options = [ "percentSize", "dspGeometry", "dspShapes", "dspLights", "dspCameras", "dspHelpers", "dspParticles", @@ -90,13 +104,36 @@ class ExtractReviewAnimation(publish.Extractor): enabled = instance.data.get(key) if enabled: job_args.append(f"{key}:{enabled}") - - if get_max_version() == 2024: - # hardcoded for current stage - auto_play_option = "autoPlay:false" - job_args.append(auto_play_option) + if get_max_version() >= 2024: + visual_style_preset = instance.data.get("visualStyleMode") + if visual_style_preset == "Realistic": + visual_style_preset = "defaultshading" + else: + visual_style_preset = visual_style_preset.lower() + # new argument exposed for Max 2024 for visual style + visual_style_option = f"vpStyle:#{visual_style_preset}" + job_args.append(visual_style_option) job_str = " ".join(job_args) self.log.debug(job_str) return job_str + + @contextlib.contextmanager + def _visual_style_option(self, viewport_setting, visual_style): + """Function to set visual style options + + Args: + visual_style (str): visual style for active viewport + + Returns: + list: the argument which can set visual style + """ + current_setting = viewport_setting.VisualStyleMode + if visual_style != current_setting: + try: + viewport_setting.VisualStyleMode = rt.Name( + visual_style) + yield + finally: + viewport_setting.VisualStyleMode = current_setting diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 82f4fc7a8b..4f9f3de6ab 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -81,9 +81,14 @@ class ExtractThumbnail(publish.Extractor): if enabled: job_args.append(f"{key}:{enabled}") if get_max_version() == 2024: - # hardcoded for current stage - auto_play_option = "autoPlay:false" - job_args.append(auto_play_option) + visual_style_preset = instance.data.get("visualStyleMode") + if visual_style_preset == "Realistic": + visual_style_preset = "defaultshading" + else: + visual_style_preset = visual_style_preset.lower() + # new argument exposed for Max 2024 for visual style + visual_style_option = f"vpStyle:#{visual_style_preset}" + job_args.append(visual_style_option) job_str = " ".join(job_args) self.log.debug(job_str) From 3314605db8447d5e92b3e31735cba80de179241e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 11 Oct 2023 18:59:07 +0800 Subject: [PATCH 02/42] move the preview arguments into the lib.py --- openpype/hosts/max/api/lib.py | 54 +++++++++++++++++++ .../hosts/max/plugins/create/create_review.py | 5 -- .../publish/extract_review_animation.py | 41 ++------------ .../max/plugins/publish/extract_thumbnail.py | 45 +++------------- 4 files changed, 66 insertions(+), 79 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 6f41cf9260..1ca2da81f8 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -500,3 +500,57 @@ def get_plugins() -> list: plugin_info_list.append(plugin_info) return plugin_info_list + +def set_preview_arg(instance, filepath, + start, end, fps): + """Function to set up preview arguments in MaxScript. + + Args: + instance (str): instance + filepath (str): output of the preview animation + start (int): startFrame + end (int): endFrame + fps (float): fps value + + Returns: + list: job arguments + """ + job_args = list() + default_option = f'CreatePreview filename:"{filepath}"' + job_args.append(default_option) + frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa + job_args.append(frame_option) + options = [ + "percentSize", "dspGeometry", "dspShapes", + "dspLights", "dspCameras", "dspHelpers", "dspParticles", + "dspBones", "dspBkg", "dspGrid", "dspSafeFrame", "dspFrameNums" + ] + + for key in options: + enabled = instance.data.get(key) + if enabled: + job_args.append(f"{key}:{enabled}") + if get_max_version() >= 2024: + visual_style_preset = instance.data.get("visualStyleMode") + if visual_style_preset == "Realistic": + visual_style_preset = "defaultshading" + else: + visual_style_preset = visual_style_preset.lower() + # new argument exposed for Max 2024 for visual style + visual_style_option = f"vpStyle:#{visual_style_preset}" + job_args.append(visual_style_option) + # new argument for pre-view preset exposed in Max 2024 + preview_preset = instance.data.get("viewportPreset") + if preview_preset == "Quality": + preview_preset = "highquality" + elif preview_preset == "Customize": + preview_preset = "userdefined" + else: + preview_preset = preview_preset.lower() + preview_preset.option = f"vpPreset:#{visual_style_preset}" + job_args.append(preview_preset) + + job_str = " ".join(job_args) + log.debug(job_str) + + return job_str diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 5638327d72..e654783a33 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -13,11 +13,6 @@ class CreateReview(plugin.MaxCreator): icon = "video-camera" def create(self, subset_name, instance_data, pre_create_data): - - instance_data["imageFormat"] = pre_create_data.get("imageFormat") - instance_data["keepImages"] = pre_create_data.get("keepImages") - instance_data["percentSize"] = pre_create_data.get("percentSize") - instance_data["visualStyleMode"] = pre_create_data.get("visualStyleMode") # Transfer settings from pre create to instance creator_attributes = instance_data.setdefault( "creator_attributes", dict()) diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 64ecbe5d85..9c26ef7e7d 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -5,7 +5,8 @@ from pymxs import runtime as rt from openpype.pipeline import publish from openpype.hosts.max.api.lib import ( viewport_camera, - get_max_version + get_max_version, + set_preview_arg ) @@ -25,7 +26,7 @@ class ExtractReviewAnimation(publish.Extractor): filename = "{0}..{1}".format(instance.name, ext) start = int(instance.data["frameStart"]) end = int(instance.data["frameEnd"]) - fps = int(instance.data["fps"]) + fps = float(instance.data["fps"]) filepath = os.path.join(staging_dir, filename) filepath = filepath.replace("\\", "/") filenames = self.get_files( @@ -38,7 +39,7 @@ class ExtractReviewAnimation(publish.Extractor): review_camera = instance.data["review_camera"] if get_max_version() >= 2024: with viewport_camera(review_camera): - preview_arg = self.set_preview_arg( + preview_arg = set_preview_arg( instance, filepath, start, end, fps) rt.execute(preview_arg) else: @@ -51,7 +52,7 @@ class ExtractReviewAnimation(publish.Extractor): ): viewport_setting.VisualStyleMode = rt.Name( visual_style_preset) - preview_arg = self.set_preview_arg( + preview_arg = set_preview_arg( instance, filepath, start, end, fps) rt.execute(preview_arg) @@ -87,38 +88,6 @@ class ExtractReviewAnimation(publish.Extractor): return file_list - def set_preview_arg(self, instance, filepath, - start, end, fps): - job_args = list() - default_option = f'CreatePreview filename:"{filepath}"' - job_args.append(default_option) - frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa - job_args.append(frame_option) - options = [ - "percentSize", "dspGeometry", "dspShapes", - "dspLights", "dspCameras", "dspHelpers", "dspParticles", - "dspBones", "dspBkg", "dspGrid", "dspSafeFrame", "dspFrameNums" - ] - - for key in options: - enabled = instance.data.get(key) - if enabled: - job_args.append(f"{key}:{enabled}") - if get_max_version() >= 2024: - visual_style_preset = instance.data.get("visualStyleMode") - if visual_style_preset == "Realistic": - visual_style_preset = "defaultshading" - else: - visual_style_preset = visual_style_preset.lower() - # new argument exposed for Max 2024 for visual style - visual_style_option = f"vpStyle:#{visual_style_preset}" - job_args.append(visual_style_option) - - job_str = " ".join(job_args) - self.log.debug(job_str) - - return job_str - @contextlib.contextmanager def _visual_style_option(self, viewport_setting, visual_style): """Function to set visual style options diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 4f9f3de6ab..22c45f3e11 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -3,7 +3,11 @@ import tempfile import pyblish.api from pymxs import runtime as rt from openpype.pipeline import publish -from openpype.hosts.max.api.lib import viewport_camera, get_max_version +from openpype.hosts.max.api.lib import ( + viewport_camera, + get_max_version, + set_preview_arg +) class ExtractThumbnail(publish.Extractor): @@ -23,7 +27,7 @@ class ExtractThumbnail(publish.Extractor): self.log.debug( f"Create temp directory {tmp_staging} for thumbnail" ) - fps = int(instance.data["fps"]) + fps = float(instance.data["fps"]) frame = int(instance.data["frameStart"]) instance.context.data["cleanupFullPaths"].append(tmp_staging) filename = "{name}_thumbnail..png".format(**instance.data) @@ -36,7 +40,7 @@ class ExtractThumbnail(publish.Extractor): " '%s' to '%s'" % (filename, tmp_staging)) review_camera = instance.data["review_camera"] with viewport_camera(review_camera): - preview_arg = self.set_preview_arg( + preview_arg = set_preview_arg( instance, filepath, fps, frame) rt.execute(preview_arg) @@ -59,38 +63,3 @@ class ExtractThumbnail(publish.Extractor): filename, target_frame ) return thumbnail_name - - def set_preview_arg(self, instance, filepath, fps, frame): - job_args = list() - default_option = f'CreatePreview filename:"{filepath}"' - job_args.append(default_option) - frame_option = f"outputAVI:false start:{frame} end:{frame} fps:{fps}" # noqa - job_args.append(frame_option) - rndLevel = instance.data.get("rndLevel") - if rndLevel: - option = f"rndLevel:#{rndLevel}" - job_args.append(option) - options = [ - "percentSize", "dspGeometry", "dspShapes", - "dspLights", "dspCameras", "dspHelpers", "dspParticles", - "dspBones", "dspBkg", "dspGrid", "dspSafeFrame", "dspFrameNums" - ] - - for key in options: - enabled = instance.data.get(key) - if enabled: - job_args.append(f"{key}:{enabled}") - if get_max_version() == 2024: - visual_style_preset = instance.data.get("visualStyleMode") - if visual_style_preset == "Realistic": - visual_style_preset = "defaultshading" - else: - visual_style_preset = visual_style_preset.lower() - # new argument exposed for Max 2024 for visual style - visual_style_option = f"vpStyle:#{visual_style_preset}" - job_args.append(visual_style_option) - - job_str = " ".join(job_args) - self.log.debug(job_str) - - return job_str From 7e5ce50fe1536675bdb35b5de43cb4c331007495 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 11 Oct 2023 19:00:29 +0800 Subject: [PATCH 03/42] hound --- openpype/hosts/max/api/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 1ca2da81f8..68baf720bc 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -501,6 +501,7 @@ def get_plugins() -> list: return plugin_info_list + def set_preview_arg(instance, filepath, start, end, fps): """Function to set up preview arguments in MaxScript. From 7035e1e0145f11f832eee08754f681e3304e591d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 12 Oct 2023 13:50:31 +0800 Subject: [PATCH 04/42] clean up the codes in thummbnail and preview animation extractor --- openpype/hosts/max/api/lib.py | 32 ++++++++++++++++++- .../publish/extract_review_animation.py | 32 ++++--------------- .../max/plugins/publish/extract_thumbnail.py | 26 ++++++++++++--- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 68baf720bc..fe2742cdb0 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -322,7 +322,7 @@ def is_headless(): @contextlib.contextmanager -def viewport_camera(camera): +def viewport_setup_updated(camera): original = rt.viewport.getCamera() has_autoplay = rt.preferences.playPreviewWhenDone if not original: @@ -339,6 +339,36 @@ def viewport_camera(camera): rt.preferences.playPreviewWhenDone = has_autoplay +@contextlib.contextmanager +def viewport_setup(viewport_setting, visual_style, camera): + """Function to set visual style options + + Args: + visual_style (str): visual style for active viewport + + Returns: + list: the argument which can set visual style + """ + original = rt.viewport.getCamera() + has_autoplay = rt.preferences.playPreviewWhenDone + if not original: + # if there is no original camera + # use the current camera as original + original = rt.getNodeByName(camera) + review_camera = rt.getNodeByName(camera) + current_setting = viewport_setting.VisualStyleMode + try: + rt.viewport.setCamera(review_camera) + viewport_setting.VisualStyleMode = rt.Name( + visual_style) + rt.preferences.playPreviewWhenDone = False + yield + finally: + rt.viewport.setCamera(original) + viewport_setting.VisualStyleMode = current_setting + rt.preferences.playPreviewWhenDone = has_autoplay + + def set_timeline(frameStart, frameEnd): """Set frame range for timeline editor in Max """ diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 9c26ef7e7d..24e7785b2b 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -4,7 +4,8 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import publish from openpype.hosts.max.api.lib import ( - viewport_camera, + viewport_setup_updated, + viewport_setup, get_max_version, set_preview_arg ) @@ -38,7 +39,7 @@ class ExtractReviewAnimation(publish.Extractor): review_camera = instance.data["review_camera"] if get_max_version() >= 2024: - with viewport_camera(review_camera): + with viewport_setup_updated(review_camera): preview_arg = set_preview_arg( instance, filepath, start, end, fps) rt.execute(preview_arg) @@ -46,10 +47,10 @@ class ExtractReviewAnimation(publish.Extractor): visual_style_preset = instance.data.get("visualStyleMode") nitrousGraphicMgr = rt.NitrousGraphicsManager viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - with viewport_camera(review_camera) and ( - self._visual_style_option( - viewport_setting, visual_style_preset) - ): + with viewport_setup( + viewport_setting, + visual_style_preset, + review_camera): viewport_setting.VisualStyleMode = rt.Name( visual_style_preset) preview_arg = set_preview_arg( @@ -87,22 +88,3 @@ class ExtractReviewAnimation(publish.Extractor): file_list.append(actual_name) return file_list - - @contextlib.contextmanager - def _visual_style_option(self, viewport_setting, visual_style): - """Function to set visual style options - - Args: - visual_style (str): visual style for active viewport - - Returns: - list: the argument which can set visual style - """ - current_setting = viewport_setting.VisualStyleMode - if visual_style != current_setting: - try: - viewport_setting.VisualStyleMode = rt.Name( - visual_style) - yield - finally: - viewport_setting.VisualStyleMode = current_setting diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 22c45f3e11..731dac74e3 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -4,12 +4,14 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import publish from openpype.hosts.max.api.lib import ( - viewport_camera, + viewport_setup_updated, + viewport_setup, get_max_version, set_preview_arg ) + class ExtractThumbnail(publish.Extractor): """ Extract Thumbnail for Review @@ -39,10 +41,24 @@ class ExtractThumbnail(publish.Extractor): "Writing Thumbnail to" " '%s' to '%s'" % (filename, tmp_staging)) review_camera = instance.data["review_camera"] - with viewport_camera(review_camera): - preview_arg = set_preview_arg( - instance, filepath, fps, frame) - rt.execute(preview_arg) + if get_max_version() >= 2024: + with viewport_setup_updated(review_camera): + preview_arg = set_preview_arg( + instance, filepath, frame, frame, fps) + rt.execute(preview_arg) + else: + visual_style_preset = instance.data.get("visualStyleMode") + nitrousGraphicMgr = rt.NitrousGraphicsManager + viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() + with viewport_setup( + viewport_setting, + visual_style_preset, + review_camera): + viewport_setting.VisualStyleMode = rt.Name( + visual_style_preset) + preview_arg = set_preview_arg( + instance, filepath, frame, frame, fps) + rt.execute(preview_arg) representation = { "name": "thumbnail", From 7d98ddfbe143fb92f64bbf4aea37bba937a7127d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 12 Oct 2023 13:52:04 +0800 Subject: [PATCH 05/42] hound --- .../hosts/max/plugins/publish/extract_review_animation.py | 7 +++---- openpype/hosts/max/plugins/publish/extract_thumbnail.py | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 24e7785b2b..acabd74958 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -1,5 +1,4 @@ import os -import contextlib import pyblish.api from pymxs import runtime as rt from openpype.pipeline import publish @@ -48,9 +47,9 @@ class ExtractReviewAnimation(publish.Extractor): nitrousGraphicMgr = rt.NitrousGraphicsManager viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() with viewport_setup( - viewport_setting, - visual_style_preset, - review_camera): + viewport_setting, + visual_style_preset, + review_camera): viewport_setting.VisualStyleMode = rt.Name( visual_style_preset) preview_arg = set_preview_arg( diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 731dac74e3..f0f349cd77 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -51,9 +51,9 @@ class ExtractThumbnail(publish.Extractor): nitrousGraphicMgr = rt.NitrousGraphicsManager viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() with viewport_setup( - viewport_setting, - visual_style_preset, - review_camera): + viewport_setting, + visual_style_preset, + review_camera): viewport_setting.VisualStyleMode = rt.Name( visual_style_preset) preview_arg = set_preview_arg( From 32820f4bfa7574c366caae4295828f4efdcb0370 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 12 Oct 2023 16:24:13 +0800 Subject: [PATCH 06/42] add viewport Tetxure support for preview --- openpype/hosts/max/api/lib.py | 8 ++++++-- openpype/hosts/max/plugins/publish/collect_review.py | 12 ++++++++++-- .../hosts/max/plugins/publish/extract_thumbnail.py | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index fe2742cdb0..26ca5ed1d8 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -578,8 +578,12 @@ def set_preview_arg(instance, filepath, preview_preset = "userdefined" else: preview_preset = preview_preset.lower() - preview_preset.option = f"vpPreset:#{visual_style_preset}" - job_args.append(preview_preset) + preview_preset_option = f"vpPreset:#{visual_style_preset}" + job_args.append(preview_preset_option) + viewport_texture = instance.data.get("vpTexture", True) + if viewport_texture: + viewport_texture_option = f"vpTexture:{viewport_texture}" + job_args.append(viewport_texture_option) job_str = " ".join(job_args) log.debug(job_str) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 1f0dca5329..8b9a777c63 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -59,6 +59,7 @@ class CollectReview(pyblish.api.InstancePlugin, instance.data["colorspaceConfig"] = colorspace_mgr.OCIOConfigPath instance.data["colorspaceDisplay"] = display instance.data["colorspaceView"] = view_transform + instance.data["vpTexture"] = attr_values.get("vpTexture") # Enable ftrack functionality instance.data.setdefault("families", []).append('ftrack') @@ -66,12 +67,19 @@ class CollectReview(pyblish.api.InstancePlugin, burnin_members = instance.data.setdefault("burninDataMembers", {}) burnin_members["focalLength"] = focal_length - self.log.debug(f"data:{data}") instance.data.update(data) + self.log.debug(f"data:{data}") @classmethod def get_attribute_defs(cls): - return [ + additional_attrs = [] + if int(get_max_version()) >= 2024: + additional_attrs.append( + BoolDef("vpTexture", + label="Viewport Texture", + default=True), + ) + return additional_attrs + [ BoolDef("dspGeometry", label="Geometry", default=True), diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index f0f349cd77..890ee24f8e 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -41,7 +41,7 @@ class ExtractThumbnail(publish.Extractor): "Writing Thumbnail to" " '%s' to '%s'" % (filename, tmp_staging)) review_camera = instance.data["review_camera"] - if get_max_version() >= 2024: + if int(get_max_version()) >= 2024: with viewport_setup_updated(review_camera): preview_arg = set_preview_arg( instance, filepath, frame, frame, fps) From 6d04bcd7acc8fb304163795f4f74b9d866add5ae Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 12 Oct 2023 16:40:31 +0800 Subject: [PATCH 07/42] make sure get max version is integer --- openpype/hosts/max/plugins/publish/extract_review_animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index acabd74958..da3f4155c1 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -37,7 +37,7 @@ class ExtractReviewAnimation(publish.Extractor): " '%s' to '%s'" % (filename, staging_dir)) review_camera = instance.data["review_camera"] - if get_max_version() >= 2024: + if int(get_max_version()) >= 2024: with viewport_setup_updated(review_camera): preview_arg = set_preview_arg( instance, filepath, start, end, fps) From a1a4898a731547db13a884f40df5640b48d65b6a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 17 Oct 2023 18:31:22 +0800 Subject: [PATCH 08/42] updated the publish review animation for 2023 and 2024 3dsMax respectively --- openpype/hosts/max/api/lib.py | 159 ++++++++++++++---- .../hosts/max/plugins/create/create_review.py | 8 +- .../max/plugins/publish/collect_review.py | 11 +- .../publish/extract_review_animation.py | 24 +-- .../publish/validate_resolution_setting.py | 2 +- 5 files changed, 143 insertions(+), 61 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 26ca5ed1d8..6817160ce7 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Library of functions useful for 3dsmax pipeline.""" +import os import contextlib import logging import json @@ -323,6 +324,11 @@ def is_headless(): @contextlib.contextmanager def viewport_setup_updated(camera): + """Function to set viewport camera during context + ***For 3dsMax 2024+ + Args: + camera (str): viewport camera for review render + """ original = rt.viewport.getCamera() has_autoplay = rt.preferences.playPreviewWhenDone if not original: @@ -340,32 +346,59 @@ def viewport_setup_updated(camera): @contextlib.contextmanager -def viewport_setup(viewport_setting, visual_style, camera): - """Function to set visual style options +def viewport_setup(instance, viewport_setting, camera): + """Function to set camera and other viewport options + during context + ****For Max Version < 2024 Args: - visual_style (str): visual style for active viewport + instance (str): instance + viewport_setting (str): active viewport setting + camera (str): viewport camera - Returns: - list: the argument which can set visual style """ original = rt.viewport.getCamera() + has_vp_btn = rt.ViewportButtonMgr.EnableButtons has_autoplay = rt.preferences.playPreviewWhenDone if not original: # if there is no original camera # use the current camera as original original = rt.getNodeByName(camera) review_camera = rt.getNodeByName(camera) - current_setting = viewport_setting.VisualStyleMode + + current_visualStyle = viewport_setting.VisualStyleMode + current_visualPreset = viewport_setting.ViewportPreset + current_useTexture = viewport_setting.UseTextureEnabled + orig_vp_grid = rt.viewport.getGridVisibility(1) + orig_vp_bkg = rt.viewport.IsSolidBackgroundColorMode() + + visualStyle = instance.data.get("visualStyleMode") + viewportPreset = instance.data.get("viewportPreset") + useTexture = instance.data.get("vpTexture") + has_grid_viewport = instance.data.get("dspGrid") + bkg_color_viewport = instance.data.get("dspBkg") + try: rt.viewport.setCamera(review_camera) - viewport_setting.VisualStyleMode = rt.Name( - visual_style) + rt.viewport.setGridVisibility(1, has_grid_viewport) rt.preferences.playPreviewWhenDone = False + rt.ViewportButtonMgr.EnableButtons = False + rt.viewport.EnableSolidBackgroundColorMode( + bkg_color_viewport) + viewport_setting.VisualStyleMode = rt.Name( + visualStyle) + viewport_setting.ViewportPreset = rt.Name( + viewportPreset) + viewport_setting.UseTextureEnabled = useTexture yield finally: rt.viewport.setCamera(original) - viewport_setting.VisualStyleMode = current_setting + rt.viewport.setGridVisibility(1, orig_vp_grid) + rt.viewport.EnableSolidBackgroundColorMode(orig_vp_bkg) + viewport_setting.VisualStyleMode = current_visualStyle + viewport_setting.ViewportPreset = current_visualPreset + viewport_setting.UseTextureEnabled = current_useTexture + rt.ViewportButtonMgr.EnableButtons = has_vp_btn rt.preferences.playPreviewWhenDone = has_autoplay @@ -532,9 +565,10 @@ def get_plugins() -> list: return plugin_info_list -def set_preview_arg(instance, filepath, - start, end, fps): +def publish_review_animation(instance, filepath, + start, end, fps): """Function to set up preview arguments in MaxScript. + ****For 3dsMax 2024+ Args: instance (str): instance @@ -561,31 +595,88 @@ def set_preview_arg(instance, filepath, enabled = instance.data.get(key) if enabled: job_args.append(f"{key}:{enabled}") - if get_max_version() >= 2024: - visual_style_preset = instance.data.get("visualStyleMode") - if visual_style_preset == "Realistic": - visual_style_preset = "defaultshading" - else: - visual_style_preset = visual_style_preset.lower() - # new argument exposed for Max 2024 for visual style - visual_style_option = f"vpStyle:#{visual_style_preset}" - job_args.append(visual_style_option) - # new argument for pre-view preset exposed in Max 2024 - preview_preset = instance.data.get("viewportPreset") - if preview_preset == "Quality": - preview_preset = "highquality" - elif preview_preset == "Customize": - preview_preset = "userdefined" - else: - preview_preset = preview_preset.lower() - preview_preset_option = f"vpPreset:#{visual_style_preset}" - job_args.append(preview_preset_option) - viewport_texture = instance.data.get("vpTexture", True) - if viewport_texture: - viewport_texture_option = f"vpTexture:{viewport_texture}" - job_args.append(viewport_texture_option) + + visual_style_preset = instance.data.get("visualStyleMode") + if visual_style_preset == "Realistic": + visual_style_preset = "defaultshading" + else: + visual_style_preset = visual_style_preset.lower() + # new argument exposed for Max 2024 for visual style + visual_style_option = f"vpStyle:#{visual_style_preset}" + job_args.append(visual_style_option) + # new argument for pre-view preset exposed in Max 2024 + preview_preset = instance.data.get("viewportPreset") + if preview_preset == "Quality": + preview_preset = "highquality" + elif preview_preset == "Customize": + preview_preset = "userdefined" + else: + preview_preset = preview_preset.lower() + preview_preset_option = f"vpPreset:#{preview_preset}" + job_args.append(preview_preset_option) + viewport_texture = instance.data.get("vpTexture", True) + if viewport_texture: + viewport_texture_option = f"vpTexture:{viewport_texture}" + job_args.append(viewport_texture_option) job_str = " ".join(job_args) log.debug(job_str) return job_str + +def publish_preview_sequences(staging_dir, filename, + startFrame, endFrame, ext): + """publish preview animation by creating bitmaps + ***For 3dsMax Version <2024 + + Args: + staging_dir (str): staging directory + filename (str): filename + startFrame (int): start frame + endFrame (int): end frame + ext (str): image extension + """ + # get the screenshot + rt.forceCompleteRedraw() + rt.enableSceneRedraw() + res_width = rt.renderWidth + res_height = rt.renderHeight + + viewportRatio = float(res_width / res_height) + + for i in range(startFrame, endFrame + 1): + rt.sliderTime = i + fname = "{}.{:04}.{}".format(filename, i, ext) + filepath = os.path.join(staging_dir, fname) + filepath = filepath.replace("\\", "/") + preview_res = rt.bitmap( + res_width, res_height, filename=filepath) + dib = rt.gw.getViewportDib() + dib_width = float(dib.width) + dib_height = float(dib.height) + renderRatio = float(dib_width / dib_height) + if viewportRatio <= renderRatio: + heightCrop = (dib_width / renderRatio) + topEdge = int((dib_height - heightCrop) / 2.0) + tempImage_bmp = rt.bitmap(dib_width, heightCrop) + src_box_value = rt.Box2(0, topEdge, dib_width, heightCrop) + else: + widthCrop = dib_height * renderRatio + leftEdge = int((dib_width - widthCrop) / 2.0) + tempImage_bmp = rt.bitmap(widthCrop, dib_height) + src_box_value = rt.Box2(0, leftEdge, dib_width, dib_height) + rt.pasteBitmap(dib, tempImage_bmp, src_box_value, rt.Point2(0, 0)) + + # copy the bitmap and close it + rt.copy(tempImage_bmp, preview_res) + rt.close(tempImage_bmp) + + rt.save(preview_res) + rt.close(preview_res) + + rt.close(dib) + + if rt.keyboard.escPressed: + rt.exit() + # clean up the cache + rt.gc() diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index e654783a33..ea56123c79 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -20,7 +20,8 @@ class CreateReview(plugin.MaxCreator): "keepImages", "percentSize", "visualStyleMode", - "viewportPreset"]: + "viewportPreset", + "vpTexture"]: if key in pre_create_data: creator_attributes[key] = pre_create_data[key] @@ -66,7 +67,10 @@ class CreateReview(plugin.MaxCreator): EnumDef("viewportPreset", preview_preset_enum, default="Quality", - label="Pre-View Preset") + label="Pre-View Preset"), + BoolDef("vpTexture", + label="Viewport Texture", + default=False) ] def get_pre_create_attr_defs(self): diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 8b9a777c63..9ab1d6f3a8 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -34,6 +34,7 @@ class CollectReview(pyblish.api.InstancePlugin, "percentSize": creator_attrs["percentSize"], "visualStyleMode": creator_attrs["visualStyleMode"], "viewportPreset": creator_attrs["viewportPreset"], + "vpTexture": creator_attrs["vpTexture"], "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], "fps": instance.context.data["fps"], @@ -59,7 +60,6 @@ class CollectReview(pyblish.api.InstancePlugin, instance.data["colorspaceConfig"] = colorspace_mgr.OCIOConfigPath instance.data["colorspaceDisplay"] = display instance.data["colorspaceView"] = view_transform - instance.data["vpTexture"] = attr_values.get("vpTexture") # Enable ftrack functionality instance.data.setdefault("families", []).append('ftrack') @@ -72,14 +72,7 @@ class CollectReview(pyblish.api.InstancePlugin, @classmethod def get_attribute_defs(cls): - additional_attrs = [] - if int(get_max_version()) >= 2024: - additional_attrs.append( - BoolDef("vpTexture", - label="Viewport Texture", - default=True), - ) - return additional_attrs + [ + return [ BoolDef("dspGeometry", label="Geometry", default=True), diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index da3f4155c1..a77f6213fa 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -6,7 +6,8 @@ from openpype.hosts.max.api.lib import ( viewport_setup_updated, viewport_setup, get_max_version, - set_preview_arg + publish_review_animation, + publish_preview_sequences ) @@ -37,22 +38,15 @@ class ExtractReviewAnimation(publish.Extractor): " '%s' to '%s'" % (filename, staging_dir)) review_camera = instance.data["review_camera"] - if int(get_max_version()) >= 2024: - with viewport_setup_updated(review_camera): - preview_arg = set_preview_arg( - instance, filepath, start, end, fps) - rt.execute(preview_arg) - else: - visual_style_preset = instance.data.get("visualStyleMode") + if int(get_max_version()) < 2024: nitrousGraphicMgr = rt.NitrousGraphicsManager viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - with viewport_setup( - viewport_setting, - visual_style_preset, - review_camera): - viewport_setting.VisualStyleMode = rt.Name( - visual_style_preset) - preview_arg = set_preview_arg( + with viewport_setup(instance, viewport_setting, review_camera): + publish_preview_sequences( + staging_dir, instance.name, start, end, ext) + else: + with viewport_setup_updated(review_camera): + preview_arg = publish_review_animation( instance, filepath, start, end, fps) rt.execute(preview_arg) diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py index 5ac41b10a0..969db0da2d 100644 --- a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -12,7 +12,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, """Validate the resolution setting aligned with DB""" order = pyblish.api.ValidatorOrder - 0.01 - families = ["maxrender"] + families = ["maxrender", "review"] hosts = ["max"] label = "Validate Resolution Setting" optional = True From fcbe4616018c780102bd752f43a83956dcae6961 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 17 Oct 2023 18:34:50 +0800 Subject: [PATCH 09/42] hound fix for the last commit --- openpype/hosts/max/api/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 6817160ce7..69dfd600a5 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -624,6 +624,7 @@ def publish_review_animation(instance, filepath, return job_str + def publish_preview_sequences(staging_dir, filename, startFrame, endFrame, ext): """publish preview animation by creating bitmaps From ab2241aebb62de3489c13458f2d118b5e49e9886 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 17 Oct 2023 19:11:23 +0800 Subject: [PATCH 10/42] fix the viewport setting issue when the first frame is flickering with different setups --- openpype/hosts/max/api/lib.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 69dfd600a5..736b0fb544 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -385,11 +385,14 @@ def viewport_setup(instance, viewport_setting, camera): rt.ViewportButtonMgr.EnableButtons = False rt.viewport.EnableSolidBackgroundColorMode( bkg_color_viewport) - viewport_setting.VisualStyleMode = rt.Name( - visualStyle) - viewport_setting.ViewportPreset = rt.Name( - viewportPreset) - viewport_setting.UseTextureEnabled = useTexture + if visualStyle != current_visualStyle: + viewport_setting.VisualStyleMode = rt.Name( + visualStyle) + elif viewportPreset != current_visualPreset: + viewport_setting.ViewportPreset = rt.Name( + viewportPreset) + elif useTexture != current_useTexture: + viewport_setting.UseTextureEnabled = useTexture yield finally: rt.viewport.setCamera(original) @@ -402,6 +405,7 @@ def viewport_setup(instance, viewport_setting, camera): rt.preferences.playPreviewWhenDone = has_autoplay + def set_timeline(frameStart, frameEnd): """Set frame range for timeline editor in Max """ From c49289bb8a30e22b020e2b35a7c60b73dddfa69e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 19:20:31 +0800 Subject: [PATCH 11/42] add use selection back to the creator option & cleanup the viewport setting code --- openpype/hosts/max/api/lib.py | 96 +++++++++---------- .../hosts/max/plugins/create/create_review.py | 3 +- .../max/plugins/publish/collect_review.py | 57 +++++++---- .../publish/extract_review_animation.py | 18 ++-- 4 files changed, 98 insertions(+), 76 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 736b0fb544..48656842de 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -323,7 +323,7 @@ def is_headless(): @contextlib.contextmanager -def viewport_setup_updated(camera): +def viewport_camera(camera): """Function to set viewport camera during context ***For 3dsMax 2024+ Args: @@ -346,64 +346,55 @@ def viewport_setup_updated(camera): @contextlib.contextmanager -def viewport_setup(instance, viewport_setting, camera): - """Function to set camera and other viewport options - during context - ****For Max Version < 2024 - - Args: - instance (str): instance - viewport_setting (str): active viewport setting - camera (str): viewport camera - - """ - original = rt.viewport.getCamera() - has_vp_btn = rt.ViewportButtonMgr.EnableButtons - has_autoplay = rt.preferences.playPreviewWhenDone - if not original: +def viewport_preference_setting(camera, + general_viewport, + nitrous_viewport, + vp_button_mgr, + preview_preferences): + original_camera = rt.viewport.getCamera() + if not original_camera: # if there is no original camera # use the current camera as original - original = rt.getNodeByName(camera) + original_camera = rt.getNodeByName(camera) review_camera = rt.getNodeByName(camera) - - current_visualStyle = viewport_setting.VisualStyleMode - current_visualPreset = viewport_setting.ViewportPreset - current_useTexture = viewport_setting.UseTextureEnabled orig_vp_grid = rt.viewport.getGridVisibility(1) orig_vp_bkg = rt.viewport.IsSolidBackgroundColorMode() - visualStyle = instance.data.get("visualStyleMode") - viewportPreset = instance.data.get("viewportPreset") - useTexture = instance.data.get("vpTexture") - has_grid_viewport = instance.data.get("dspGrid") - bkg_color_viewport = instance.data.get("dspBkg") - + nitrousGraphicMgr = rt.NitrousGraphicsManager + viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() + vp_button_mgr_original = { + key: getattr(rt.ViewportButtonMgr, key) for key in vp_button_mgr + } + nitrous_viewport_original = { + key: getattr(viewport_setting, key) for key in nitrous_viewport + } + preview_preferences_original = { + key: getattr(rt.preferences, key) for key in preview_preferences + } try: rt.viewport.setCamera(review_camera) - rt.viewport.setGridVisibility(1, has_grid_viewport) - rt.preferences.playPreviewWhenDone = False - rt.ViewportButtonMgr.EnableButtons = False - rt.viewport.EnableSolidBackgroundColorMode( - bkg_color_viewport) - if visualStyle != current_visualStyle: - viewport_setting.VisualStyleMode = rt.Name( - visualStyle) - elif viewportPreset != current_visualPreset: - viewport_setting.ViewportPreset = rt.Name( - viewportPreset) - elif useTexture != current_useTexture: - viewport_setting.UseTextureEnabled = useTexture + rt.viewport.setGridVisibility(1, general_viewport["dspGrid"]) + rt.viewport.EnableSolidBackgroundColorMode(general_viewport["dspBkg"]) + for key, value in vp_button_mgr.items(): + setattr(rt.ViewportButtonMgr, key, value) + for key, value in nitrous_viewport.items(): + if nitrous_viewport[key] != nitrous_viewport_original[key]: + setattr(viewport_setting, key, value) + for key, value in preview_preferences.items(): + setattr(rt.preferences, key, value) yield + finally: - rt.viewport.setCamera(original) + rt.viewport.setCamera(review_camera) rt.viewport.setGridVisibility(1, orig_vp_grid) rt.viewport.EnableSolidBackgroundColorMode(orig_vp_bkg) - viewport_setting.VisualStyleMode = current_visualStyle - viewport_setting.ViewportPreset = current_visualPreset - viewport_setting.UseTextureEnabled = current_useTexture - rt.ViewportButtonMgr.EnableButtons = has_vp_btn - rt.preferences.playPreviewWhenDone = has_autoplay - + for key, value in vp_button_mgr_original.items(): + setattr(rt.ViewportButtonMgr, key, value) + for key, value in nitrous_viewport_original.items(): + setattr(viewport_setting, key, value) + for key, value in preview_preferences_original.items(): + setattr(rt.preferences, key, value) + rt.completeRedraw() def set_timeline(frameStart, frameEnd): @@ -630,7 +621,8 @@ def publish_review_animation(instance, filepath, def publish_preview_sequences(staging_dir, filename, - startFrame, endFrame, ext): + startFrame, endFrame, + percentSize, ext): """publish preview animation by creating bitmaps ***For 3dsMax Version <2024 @@ -639,13 +631,15 @@ def publish_preview_sequences(staging_dir, filename, filename (str): filename startFrame (int): start frame endFrame (int): end frame + percentSize (int): percentage of the resolution ext (str): image extension """ # get the screenshot rt.forceCompleteRedraw() rt.enableSceneRedraw() - res_width = rt.renderWidth - res_height = rt.renderHeight + resolution_percentage = float(percentSize) / 100 + res_width = rt.renderWidth * resolution_percentage + res_height = rt.renderHeight * resolution_percentage viewportRatio = float(res_width / res_height) @@ -684,4 +678,4 @@ def publish_preview_sequences(staging_dir, filename, if rt.keyboard.escPressed: rt.exit() # clean up the cache - rt.gc() + rt.gc(delayed=True) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index ea56123c79..977c018f5c 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -75,4 +75,5 @@ class CreateReview(plugin.MaxCreator): def get_pre_create_attr_defs(self): # Use same attributes as for instance attributes - return self.get_instance_attr_defs() + attrs = super().get_pre_create_attr_defs() + return attrs + self.get_instance_attr_defs() diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 9ab1d6f3a8..6e9a6c870e 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -27,28 +27,15 @@ class CollectReview(pyblish.api.InstancePlugin, focal_length = node.fov creator_attrs = instance.data["creator_attributes"] attr_values = self.get_attr_values_from_data(instance.data) - data = { + + general_preview_data = { "review_camera": camera_name, "imageFormat": creator_attrs["imageFormat"], "keepImages": creator_attrs["keepImages"], "percentSize": creator_attrs["percentSize"], - "visualStyleMode": creator_attrs["visualStyleMode"], - "viewportPreset": creator_attrs["viewportPreset"], - "vpTexture": creator_attrs["vpTexture"], "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], "fps": instance.context.data["fps"], - "dspGeometry": attr_values.get("dspGeometry"), - "dspShapes": attr_values.get("dspShapes"), - "dspLights": attr_values.get("dspLights"), - "dspCameras": attr_values.get("dspCameras"), - "dspHelpers": attr_values.get("dspHelpers"), - "dspParticles": attr_values.get("dspParticles"), - "dspBones": attr_values.get("dspBones"), - "dspBkg": attr_values.get("dspBkg"), - "dspGrid": attr_values.get("dspGrid"), - "dspSafeFrame": attr_values.get("dspSafeFrame"), - "dspFrameNums": attr_values.get("dspFrameNums") } if int(get_max_version()) >= 2024: @@ -61,14 +48,50 @@ class CollectReview(pyblish.api.InstancePlugin, instance.data["colorspaceDisplay"] = display instance.data["colorspaceView"] = view_transform + preview_data = { + "visualStyleMode": creator_attrs["visualStyleMode"], + "viewportPreset": creator_attrs["viewportPreset"], + "vpTexture": creator_attrs["vpTexture"], + "dspGeometry": attr_values.get("dspGeometry"), + "dspShapes": attr_values.get("dspShapes"), + "dspLights": attr_values.get("dspLights"), + "dspCameras": attr_values.get("dspCameras"), + "dspHelpers": attr_values.get("dspHelpers"), + "dspParticles": attr_values.get("dspParticles"), + "dspBones": attr_values.get("dspBones"), + "dspBkg": attr_values.get("dspBkg"), + "dspGrid": attr_values.get("dspGrid"), + "dspSafeFrame": attr_values.get("dspSafeFrame"), + "dspFrameNums": attr_values.get("dspFrameNums") + } + else: + preview_data = {} + general_viewport = { + "dspBkg": attr_values.get("dspBkg"), + "dspGrid": attr_values.get("dspGrid") + } + nitrous_viewport = { + "VisualStyleMode": creator_attrs["visualStyleMode"], + "ViewportPreset": creator_attrs["viewportPreset"], + "UseTextureEnabled": creator_attrs["vpTexture"] + } + preview_data["general_viewport"] = general_viewport + preview_data["nitrous_viewport"] = nitrous_viewport + preview_data["vp_button_manager"] = { + "EnableButtons" : False + } + preview_data["preferences"] = { + "playPreviewWhenDone": False + } + # Enable ftrack functionality instance.data.setdefault("families", []).append('ftrack') burnin_members = instance.data.setdefault("burninDataMembers", {}) burnin_members["focalLength"] = focal_length - instance.data.update(data) - self.log.debug(f"data:{data}") + instance.data.update(general_preview_data) + instance.data.update(preview_data) @classmethod def get_attribute_defs(cls): diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index a77f6213fa..2fbcb157a3 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -3,8 +3,8 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import publish from openpype.hosts.max.api.lib import ( - viewport_setup_updated, - viewport_setup, + viewport_camera, + viewport_preference_setting, get_max_version, publish_review_animation, publish_preview_sequences @@ -39,13 +39,17 @@ class ExtractReviewAnimation(publish.Extractor): review_camera = instance.data["review_camera"] if int(get_max_version()) < 2024: - nitrousGraphicMgr = rt.NitrousGraphicsManager - viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - with viewport_setup(instance, viewport_setting, review_camera): + with viewport_preference_setting(review_camera, + instance.data["general_viewport"], + instance.data["nitrous_viewport"], + instance.data["vp_button_manager"], + instance.data["preferences"]): + percentSize = instance.data.get("percentSize") publish_preview_sequences( - staging_dir, instance.name, start, end, ext) + staging_dir, instance.name, + start, end, percentSize, ext) else: - with viewport_setup_updated(review_camera): + with viewport_camera(review_camera): preview_arg = publish_review_animation( instance, filepath, start, end, fps) rt.execute(preview_arg) From d0b397f130b997660b843bd6d7fdd79fa2295b7a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 19:23:30 +0800 Subject: [PATCH 12/42] hound --- openpype/hosts/max/plugins/publish/collect_review.py | 6 +++--- .../hosts/max/plugins/publish/extract_review_animation.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 6e9a6c870e..21f63a8c73 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -66,7 +66,7 @@ class CollectReview(pyblish.api.InstancePlugin, } else: preview_data = {} - general_viewport = { + general_viewport = { "dspBkg": attr_values.get("dspBkg"), "dspGrid": attr_values.get("dspGrid") } @@ -77,8 +77,8 @@ class CollectReview(pyblish.api.InstancePlugin, } preview_data["general_viewport"] = general_viewport preview_data["nitrous_viewport"] = nitrous_viewport - preview_data["vp_button_manager"] = { - "EnableButtons" : False + preview_data["vp_btn_mgr"] = { + "EnableButtons": False } preview_data["preferences"] = { "playPreviewWhenDone": False diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 2fbcb157a3..ccd641f619 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -42,7 +42,7 @@ class ExtractReviewAnimation(publish.Extractor): with viewport_preference_setting(review_camera, instance.data["general_viewport"], instance.data["nitrous_viewport"], - instance.data["vp_button_manager"], + instance.data["vp_btn_mgr"], instance.data["preferences"]): percentSize = instance.data.get("percentSize") publish_preview_sequences( From 6f2718ee4d64532380ee56774f0c0339a9fa3465 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 19:29:43 +0800 Subject: [PATCH 13/42] add missing docstrings --- openpype/hosts/max/api/lib.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 48656842de..8103eaecc5 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -351,6 +351,16 @@ def viewport_preference_setting(camera, nitrous_viewport, vp_button_mgr, preview_preferences): + """Function to set viewport setting during context + + Args: + camera (str): Viewport camera for review render + general_viewport (dict): General viewport setting + nitrous_viewport (dict): Nitrous setting for + preview animation + vp_button_mgr (dict): Viewport button manager Setting + preview_preferences (dict): Preview Preferences Setting + """ original_camera = rt.viewport.getCamera() if not original_camera: # if there is no original camera From a993a2999ed8d32dbcfa53c055e4bd73971fe2f7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 19:30:12 +0800 Subject: [PATCH 14/42] add missing docstrings --- openpype/hosts/max/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 8103eaecc5..8c0bacf792 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -352,7 +352,7 @@ def viewport_preference_setting(camera, vp_button_mgr, preview_preferences): """Function to set viewport setting during context - + ***For Max Version < 2024 Args: camera (str): Viewport camera for review render general_viewport (dict): General viewport setting From 519a99adff9e7b5735be59cc62bf4f4dc75bd7ca Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 21:39:42 +0800 Subject: [PATCH 15/42] bug fix some of the unsupported arguments --- openpype/hosts/max/api/lib.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 8c0bacf792..fc74d78f05 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -331,6 +331,9 @@ def viewport_camera(camera): """ original = rt.viewport.getCamera() has_autoplay = rt.preferences.playPreviewWhenDone + nitrousGraphicMgr = rt.NitrousGraphicsManager + viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() + orig_preset = viewport_setting.ViewportPreset if not original: # if there is no original camera # use the current camera as original @@ -343,6 +346,8 @@ def viewport_camera(camera): finally: rt.viewport.setCamera(original) rt.preferences.playPreviewWhenDone = has_autoplay + viewport_setting.ViewportPreset = orig_preset + rt.completeRedraw() @contextlib.contextmanager @@ -604,6 +609,15 @@ def publish_review_animation(instance, filepath, visual_style_preset = instance.data.get("visualStyleMode") if visual_style_preset == "Realistic": visual_style_preset = "defaultshading" + elif visual_style_preset == "Shaded": + visual_style_preset = "defaultshading" + log.warning( + "'Shaded' Mode not supported in " + "preview animation in Max 2024..\n\n" + "Using 'defaultshading' instead") + + elif visual_style_preset == "ConsistentColors": + visual_style_preset = "flatcolor" else: visual_style_preset = visual_style_preset.lower() # new argument exposed for Max 2024 for visual style From 52a086c2b19b9d6137a291827a67d29dc4942a43 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 23:10:37 +0800 Subject: [PATCH 16/42] integrate both functions into a function for extract preview action --- openpype/hosts/max/api/lib.py | 72 +++++++++++++------ .../max/plugins/publish/collect_review.py | 2 +- .../publish/extract_review_animation.py | 29 ++------ 3 files changed, 55 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index fc74d78f05..4266db1e7f 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -347,15 +347,22 @@ def viewport_camera(camera): rt.viewport.setCamera(original) rt.preferences.playPreviewWhenDone = has_autoplay viewport_setting.ViewportPreset = orig_preset - rt.completeRedraw() @contextlib.contextmanager -def viewport_preference_setting(camera, - general_viewport, +def play_preview_when_done(has_autoplay): + current_playback = rt.preferences.playPreviewWhenDone + try: + rt.preferences.playPreviewWhenDone = has_autoplay + yield + finally: + rt.preferences.playPreviewWhenDone = current_playback + + +@contextlib.contextmanager +def viewport_preference_setting(general_viewport, nitrous_viewport, - vp_button_mgr, - preview_preferences): + vp_button_mgr): """Function to set viewport setting during context ***For Max Version < 2024 Args: @@ -366,12 +373,6 @@ def viewport_preference_setting(camera, vp_button_mgr (dict): Viewport button manager Setting preview_preferences (dict): Preview Preferences Setting """ - original_camera = rt.viewport.getCamera() - if not original_camera: - # if there is no original camera - # use the current camera as original - original_camera = rt.getNodeByName(camera) - review_camera = rt.getNodeByName(camera) orig_vp_grid = rt.viewport.getGridVisibility(1) orig_vp_bkg = rt.viewport.IsSolidBackgroundColorMode() @@ -383,11 +384,8 @@ def viewport_preference_setting(camera, nitrous_viewport_original = { key: getattr(viewport_setting, key) for key in nitrous_viewport } - preview_preferences_original = { - key: getattr(rt.preferences, key) for key in preview_preferences - } + try: - rt.viewport.setCamera(review_camera) rt.viewport.setGridVisibility(1, general_viewport["dspGrid"]) rt.viewport.EnableSolidBackgroundColorMode(general_viewport["dspBkg"]) for key, value in vp_button_mgr.items(): @@ -395,21 +393,15 @@ def viewport_preference_setting(camera, for key, value in nitrous_viewport.items(): if nitrous_viewport[key] != nitrous_viewport_original[key]: setattr(viewport_setting, key, value) - for key, value in preview_preferences.items(): - setattr(rt.preferences, key, value) yield finally: - rt.viewport.setCamera(review_camera) rt.viewport.setGridVisibility(1, orig_vp_grid) rt.viewport.EnableSolidBackgroundColorMode(orig_vp_bkg) for key, value in vp_button_mgr_original.items(): setattr(rt.ViewportButtonMgr, key, value) for key, value in nitrous_viewport_original.items(): setattr(viewport_setting, key, value) - for key, value in preview_preferences_original.items(): - setattr(rt.preferences, key, value) - rt.completeRedraw() def set_timeline(frameStart, frameEnd): @@ -638,6 +630,9 @@ def publish_review_animation(instance, filepath, viewport_texture_option = f"vpTexture:{viewport_texture}" job_args.append(viewport_texture_option) + auto_play_option = "autoPlay:false" + job_args.append(auto_play_option) + job_str = " ".join(job_args) log.debug(job_str) @@ -659,8 +654,6 @@ def publish_preview_sequences(staging_dir, filename, ext (str): image extension """ # get the screenshot - rt.forceCompleteRedraw() - rt.enableSceneRedraw() resolution_percentage = float(percentSize) / 100 res_width = rt.renderWidth * resolution_percentage res_height = rt.renderHeight * resolution_percentage @@ -703,3 +696,36 @@ def publish_preview_sequences(staging_dir, filename, rt.exit() # clean up the cache rt.gc(delayed=True) + +def publish_preview_animation(instance, staging_dir, filepath, + startFrame, endFrame, review_camera): + """Publish Reivew Animation + + Args: + instance (pyblish.api.instance): Instance + staging_dir (str): staging directory + filepath (str): filepath + startFrame (int): start frame + endFrame (int): end frame + review_camera (str): viewport camera for + preview render + """ + with play_preview_when_done(False): + with viewport_camera(review_camera): + if int(get_max_version()) < 2024: + with viewport_preference_setting( + instance.data["general_viewport"], + instance.data["nitrous_viewport"], + instance.data["vp_btn_mgr"]): + percentSize = instance.data.get("percentSize") + ext = instance.data.get("imageFormat") + rt.completeRedraw() + publish_preview_sequences( + staging_dir, instance.name, + startFrame, endFrame, percentSize, ext) + else: + fps = instance.data["fps"] + rt.completeRedraw() + preview_arg = publish_review_animation( + instance, filepath, startFrame, endFrame, fps) + rt.execute(preview_arg) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 21f63a8c73..c3985a2ded 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -35,7 +35,7 @@ class CollectReview(pyblish.api.InstancePlugin, "percentSize": creator_attrs["percentSize"], "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], - "fps": instance.context.data["fps"], + "fps": instance.context.data["fps"] } if int(get_max_version()) >= 2024: diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index ccd641f619..27a86323eb 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -1,14 +1,7 @@ import os import pyblish.api -from pymxs import runtime as rt from openpype.pipeline import publish -from openpype.hosts.max.api.lib import ( - viewport_camera, - viewport_preference_setting, - get_max_version, - publish_review_animation, - publish_preview_sequences -) +from openpype.hosts.max.api.lib import publish_preview_animation class ExtractReviewAnimation(publish.Extractor): @@ -27,7 +20,6 @@ class ExtractReviewAnimation(publish.Extractor): filename = "{0}..{1}".format(instance.name, ext) start = int(instance.data["frameStart"]) end = int(instance.data["frameEnd"]) - fps = float(instance.data["fps"]) filepath = os.path.join(staging_dir, filename) filepath = filepath.replace("\\", "/") filenames = self.get_files( @@ -38,21 +30,10 @@ class ExtractReviewAnimation(publish.Extractor): " '%s' to '%s'" % (filename, staging_dir)) review_camera = instance.data["review_camera"] - if int(get_max_version()) < 2024: - with viewport_preference_setting(review_camera, - instance.data["general_viewport"], - instance.data["nitrous_viewport"], - instance.data["vp_btn_mgr"], - instance.data["preferences"]): - percentSize = instance.data.get("percentSize") - publish_preview_sequences( - staging_dir, instance.name, - start, end, percentSize, ext) - else: - with viewport_camera(review_camera): - preview_arg = publish_review_animation( - instance, filepath, start, end, fps) - rt.execute(preview_arg) + publish_preview_animation( + instance, staging_dir, + filepath, start, end, + review_camera) tags = ["review"] if not instance.data.get("keepImages"): From 3e75b9ec796791cdd19617e1dddf68db31420063 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 23:13:03 +0800 Subject: [PATCH 17/42] hound --- openpype/hosts/max/api/lib.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 4266db1e7f..2027c88214 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -697,6 +697,7 @@ def publish_preview_sequences(staging_dir, filename, # clean up the cache rt.gc(delayed=True) + def publish_preview_animation(instance, staging_dir, filepath, startFrame, endFrame, review_camera): """Publish Reivew Animation @@ -707,22 +708,21 @@ def publish_preview_animation(instance, staging_dir, filepath, filepath (str): filepath startFrame (int): start frame endFrame (int): end frame - review_camera (str): viewport camera for - preview render + review_camera (str): viewport camera for preview render """ with play_preview_when_done(False): with viewport_camera(review_camera): if int(get_max_version()) < 2024: - with viewport_preference_setting( + with viewport_preference_setting( instance.data["general_viewport"], instance.data["nitrous_viewport"], instance.data["vp_btn_mgr"]): - percentSize = instance.data.get("percentSize") - ext = instance.data.get("imageFormat") - rt.completeRedraw() - publish_preview_sequences( - staging_dir, instance.name, - startFrame, endFrame, percentSize, ext) + percentSize = instance.data.get("percentSize") + ext = instance.data.get("imageFormat") + rt.completeRedraw() + publish_preview_sequences( + staging_dir, instance.name, + startFrame, endFrame, percentSize, ext) else: fps = instance.data["fps"] rt.completeRedraw() From eae470977570aecb8cd26d248d56f4a5ebd4cdcd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 23:39:47 +0800 Subject: [PATCH 18/42] refactored the preview animation publish --- openpype/hosts/max/api/lib.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 2027c88214..23a6ab0717 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -567,8 +567,8 @@ def get_plugins() -> list: return plugin_info_list -def publish_review_animation(instance, filepath, - start, end, fps): +def publish_review_animation(instance, staging_dir, start, + end, ext, fps): """Function to set up preview arguments in MaxScript. ****For 3dsMax 2024+ @@ -583,6 +583,9 @@ def publish_review_animation(instance, filepath, list: job arguments """ job_args = list() + filename = "{0}..{1}".format(instance.name, ext) + filepath = os.path.join(staging_dir, filename) + filepath = filepath.replace("\\", "/") default_option = f'CreatePreview filename:"{filepath}"' job_args.append(default_option) frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa @@ -698,13 +701,14 @@ def publish_preview_sequences(staging_dir, filename, rt.gc(delayed=True) -def publish_preview_animation(instance, staging_dir, filepath, - startFrame, endFrame, review_camera): - """Publish Reivew Animation +def publish_preview_animation( + instance, staging_dir, + startFrame, endFrame, + ext, review_camera): + """Render camera review animation Args: instance (pyblish.api.instance): Instance - staging_dir (str): staging directory filepath (str): filepath startFrame (int): start frame endFrame (int): end frame @@ -718,7 +722,6 @@ def publish_preview_animation(instance, staging_dir, filepath, instance.data["nitrous_viewport"], instance.data["vp_btn_mgr"]): percentSize = instance.data.get("percentSize") - ext = instance.data.get("imageFormat") rt.completeRedraw() publish_preview_sequences( staging_dir, instance.name, @@ -726,6 +729,7 @@ def publish_preview_animation(instance, staging_dir, filepath, else: fps = instance.data["fps"] rt.completeRedraw() - preview_arg = publish_review_animation( - instance, filepath, startFrame, endFrame, fps) + preview_arg = publish_review_animation(instance, staging_dir, + startFrame, endFrame, + ext, fps) rt.execute(preview_arg) From f650ecc20e0892ee9c72a1ae6160e443e9d0b726 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 23:58:33 +0800 Subject: [PATCH 19/42] refactored the preview animation publish --- openpype/hosts/max/api/lib.py | 8 ++++++-- .../hosts/max/plugins/publish/extract_review_animation.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 23a6ab0717..9c5eccb215 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -703,8 +703,8 @@ def publish_preview_sequences(staging_dir, filename, def publish_preview_animation( instance, staging_dir, - startFrame, endFrame, - ext, review_camera): + ext, review_camera, + startFrame=None, endFrame=None): """Render camera review animation Args: @@ -714,6 +714,10 @@ def publish_preview_animation( endFrame (int): end frame review_camera (str): viewport camera for preview render """ + if start_frame is None: + start_frame = int(rt.animationRange.start) + if end_frame is None: + end_frame = int(rt.animationRange.end) with play_preview_when_done(False): with viewport_camera(review_camera): if int(get_max_version()) < 2024: diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 27a86323eb..d57ed44d65 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -32,8 +32,8 @@ class ExtractReviewAnimation(publish.Extractor): review_camera = instance.data["review_camera"] publish_preview_animation( instance, staging_dir, - filepath, start, end, - review_camera) + ext, review_camera, + startFrame=start, endFrame=end) tags = ["review"] if not instance.data.get("keepImages"): From f96b7dbb198f0b5ca23abf5470ca72829039bc5b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 18 Oct 2023 23:59:39 +0800 Subject: [PATCH 20/42] hound --- openpype/hosts/max/api/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 9c5eccb215..eb6cd3cabc 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -714,10 +714,10 @@ def publish_preview_animation( endFrame (int): end frame review_camera (str): viewport camera for preview render """ - if start_frame is None: - start_frame = int(rt.animationRange.start) - if end_frame is None: - end_frame = int(rt.animationRange.end) + if startFrame is None: + startFrame = int(rt.animationRange.start) + if endFrame is None: + endFrame = int(rt.animationRange.end) with play_preview_when_done(False): with viewport_camera(review_camera): if int(get_max_version()) < 2024: From 2c460ed64701a51812d02f67e27529978f8b4ad4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 18:23:55 +0800 Subject: [PATCH 21/42] move functions to preview_animation.py --- openpype/hosts/max/api/lib.py | 136 +++++--- openpype/hosts/max/api/preview_animation.py | 306 ++++++++++++++++++ .../max/plugins/publish/collect_review.py | 15 +- .../publish/extract_review_animation.py | 8 +- 4 files changed, 406 insertions(+), 59 deletions(-) create mode 100644 openpype/hosts/max/api/preview_animation.py diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index eb6cd3cabc..5e55daceb2 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -568,7 +568,7 @@ def get_plugins() -> list: def publish_review_animation(instance, staging_dir, start, - end, ext, fps): + end, ext, fps, viewport_options): """Function to set up preview arguments in MaxScript. ****For 3dsMax 2024+ @@ -578,6 +578,7 @@ def publish_review_animation(instance, staging_dir, start, start (int): startFrame end (int): endFrame fps (float): fps value + viewport_options (dict): viewport setting options Returns: list: job arguments @@ -590,48 +591,34 @@ def publish_review_animation(instance, staging_dir, start, job_args.append(default_option) frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa job_args.append(frame_option) - options = [ - "percentSize", "dspGeometry", "dspShapes", - "dspLights", "dspCameras", "dspHelpers", "dspParticles", - "dspBones", "dspBkg", "dspGrid", "dspSafeFrame", "dspFrameNums" - ] - for key in options: - enabled = instance.data.get(key) - if enabled: - job_args.append(f"{key}:{enabled}") + for key, value in viewport_options.items(): + if isinstance(value, bool): + if value: + job_args.append(f"{key}:{value}") - visual_style_preset = instance.data.get("visualStyleMode") - if visual_style_preset == "Realistic": - visual_style_preset = "defaultshading" - elif visual_style_preset == "Shaded": - visual_style_preset = "defaultshading" - log.warning( - "'Shaded' Mode not supported in " - "preview animation in Max 2024..\n\n" - "Using 'defaultshading' instead") - - elif visual_style_preset == "ConsistentColors": - visual_style_preset = "flatcolor" - else: - visual_style_preset = visual_style_preset.lower() - # new argument exposed for Max 2024 for visual style - visual_style_option = f"vpStyle:#{visual_style_preset}" - job_args.append(visual_style_option) - # new argument for pre-view preset exposed in Max 2024 - preview_preset = instance.data.get("viewportPreset") - if preview_preset == "Quality": - preview_preset = "highquality" - elif preview_preset == "Customize": - preview_preset = "userdefined" - else: - preview_preset = preview_preset.lower() - preview_preset_option = f"vpPreset:#{preview_preset}" - job_args.append(preview_preset_option) - viewport_texture = instance.data.get("vpTexture", True) - if viewport_texture: - viewport_texture_option = f"vpTexture:{viewport_texture}" - job_args.append(viewport_texture_option) + elif isinstance(value, str): + if key == "vpStyle": + if viewport_options[key] == "Realistic": + value = "defaultshading" + elif viewport_options[key] == "Shaded": + log.warning( + "'Shaded' Mode not supported in " + "preview animation in Max 2024..\n" + "Using 'defaultshading' instead") + value = "defaultshading" + elif viewport_options[key] == "ConsistentColors": + value = "flatcolor" + else: + value = value.lower() + elif key == "vpPreset": + if viewport_options[key] == "Quality": + value = "highquality" + elif viewport_options[key] == "Customize": + value = "userdefined" + else: + value = value.lower() + job_args.append(f"{key}: #{value}") auto_play_option = "autoPlay:false" job_args.append(auto_play_option) @@ -704,29 +691,34 @@ def publish_preview_sequences(staging_dir, filename, def publish_preview_animation( instance, staging_dir, ext, review_camera, - startFrame=None, endFrame=None): + startFrame=None, endFrame=None, + viewport_options=None): """Render camera review animation Args: instance (pyblish.api.instance): Instance filepath (str): filepath + review_camera (str): viewport camera for preview render startFrame (int): start frame endFrame (int): end frame - review_camera (str): viewport camera for preview render + viewport_options (dict): viewport setting options """ + if startFrame is None: startFrame = int(rt.animationRange.start) if endFrame is None: endFrame = int(rt.animationRange.end) + if viewport_options is None: + viewport_options = viewport_options_for_preview_animation() with play_preview_when_done(False): with viewport_camera(review_camera): if int(get_max_version()) < 2024: with viewport_preference_setting( - instance.data["general_viewport"], - instance.data["nitrous_viewport"], - instance.data["vp_btn_mgr"]): - percentSize = instance.data.get("percentSize") - rt.completeRedraw() + viewport_options["general_viewport"], + viewport_options["nitrous_viewport"], + viewport_options["vp_btn_mgr"]): + percentSize = viewport_options.get("percentSize", 100) + publish_preview_sequences( staging_dir, instance.name, startFrame, endFrame, percentSize, ext) @@ -735,5 +727,51 @@ def publish_preview_animation( rt.completeRedraw() preview_arg = publish_review_animation(instance, staging_dir, startFrame, endFrame, - ext, fps) + ext, fps, viewport_options) rt.execute(preview_arg) + + rt.completeRedraw() + +def viewport_options_for_preview_animation(): + """ + Function to store the default data of viewport options + Returns: + dict: viewport setting options + + """ + # viewport_options should be the dictionary + if int(get_max_version()) < 2024: + return { + "visualStyleMode": "defaultshading", + "viewportPreset": "highquality", + "percentSize": 100, + "vpTexture": False, + "dspGeometry": True, + "dspShapes": False, + "dspLights": False, + "dspCameras": False, + "dspHelpers": False, + "dspParticles": True, + "dspBones": False, + "dspBkg": True, + "dspGrid": False, + "dspSafeFrame":False, + "dspFrameNums": False + } + else: + viewport_options = {} + viewport_options.update({"percentSize": 100}) + general_viewport = { + "dspBkg": True, + "dspGrid": False + } + nitrous_viewport = { + "VisualStyleMode": "defaultshading", + "ViewportPreset": "highquality", + "UseTextureEnabled": False + } + viewport_options["general_viewport"] = general_viewport + viewport_options["nitrous_viewport"] = nitrous_viewport + viewport_options["vp_btn_mgr"] = { + "EnableButtons": False} + return viewport_options diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py new file mode 100644 index 0000000000..bb2f1410d4 --- /dev/null +++ b/openpype/hosts/max/api/preview_animation.py @@ -0,0 +1,306 @@ +import os +import logging +import contextlib +from pymxs import runtime as rt +from .lib import get_max_version + +log = logging.getLogger("openpype.hosts.max") + + +@contextlib.contextmanager +def play_preview_when_done(has_autoplay): + """Function to set preview playback option during + context + + Args: + has_autoplay (bool): autoplay during creating + preview animation + """ + current_playback = rt.preferences.playPreviewWhenDone + try: + rt.preferences.playPreviewWhenDone = has_autoplay + yield + finally: + rt.preferences.playPreviewWhenDone = current_playback + + +@contextlib.contextmanager +def viewport_camera(camera): + """Function to set viewport camera during context + ***For 3dsMax 2024+ + Args: + camera (str): viewport camera for review render + """ + original = rt.viewport.getCamera() + has_autoplay = rt.preferences.playPreviewWhenDone + nitrousGraphicMgr = rt.NitrousGraphicsManager + viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() + orig_preset = viewport_setting.ViewportPreset + if not original: + # if there is no original camera + # use the current camera as original + original = rt.getNodeByName(camera) + review_camera = rt.getNodeByName(camera) + try: + rt.viewport.setCamera(review_camera) + rt.preferences.playPreviewWhenDone = False + yield + finally: + rt.viewport.setCamera(original) + rt.preferences.playPreviewWhenDone = has_autoplay + viewport_setting.ViewportPreset = orig_preset + + +@contextlib.contextmanager +def viewport_preference_setting(general_viewport, + nitrous_viewport, + vp_button_mgr): + """Function to set viewport setting during context + ***For Max Version < 2024 + Args: + camera (str): Viewport camera for review render + general_viewport (dict): General viewport setting + nitrous_viewport (dict): Nitrous setting for + preview animation + vp_button_mgr (dict): Viewport button manager Setting + preview_preferences (dict): Preview Preferences Setting + """ + orig_vp_grid = rt.viewport.getGridVisibility(1) + orig_vp_bkg = rt.viewport.IsSolidBackgroundColorMode() + + nitrousGraphicMgr = rt.NitrousGraphicsManager + viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() + vp_button_mgr_original = { + key: getattr(rt.ViewportButtonMgr, key) for key in vp_button_mgr + } + nitrous_viewport_original = { + key: getattr(viewport_setting, key) for key in nitrous_viewport + } + + try: + rt.viewport.setGridVisibility(1, general_viewport["dspGrid"]) + rt.viewport.EnableSolidBackgroundColorMode(general_viewport["dspBkg"]) + for key, value in vp_button_mgr.items(): + setattr(rt.ViewportButtonMgr, key, value) + for key, value in nitrous_viewport.items(): + if nitrous_viewport[key] != nitrous_viewport_original[key]: + setattr(viewport_setting, key, value) + yield + + finally: + rt.viewport.setGridVisibility(1, orig_vp_grid) + rt.viewport.EnableSolidBackgroundColorMode(orig_vp_bkg) + for key, value in vp_button_mgr_original.items(): + setattr(rt.ViewportButtonMgr, key, value) + for key, value in nitrous_viewport_original.items(): + setattr(viewport_setting, key, value) + +def publish_review_animation(instance, staging_dir, start, + end, ext, fps, viewport_options): + """Function to set up preview arguments in MaxScript. + ****For 3dsMax 2024+ + + Args: + instance (str): instance + filepath (str): output of the preview animation + start (int): startFrame + end (int): endFrame + fps (float): fps value + viewport_options (dict): viewport setting options + + Returns: + list: job arguments + """ + job_args = list() + filename = "{0}..{1}".format(instance.name, ext) + filepath = os.path.join(staging_dir, filename) + filepath = filepath.replace("\\", "/") + default_option = f'CreatePreview filename:"{filepath}"' + job_args.append(default_option) + frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa + job_args.append(frame_option) + + for key, value in viewport_options.items(): + if isinstance(value, bool): + if value: + job_args.append(f"{key}:{value}") + + elif isinstance(value, str): + if key == "vpStyle": + if viewport_options[key] == "Realistic": + value = "defaultshading" + elif viewport_options[key] == "Shaded": + log.warning( + "'Shaded' Mode not supported in " + "preview animation in Max 2024..\n" + "Using 'defaultshading' instead") + value = "defaultshading" + elif viewport_options[key] == "ConsistentColors": + value = "flatcolor" + else: + value = value.lower() + elif key == "vpPreset": + if viewport_options[key] == "Quality": + value = "highquality" + elif viewport_options[key] == "Customize": + value = "userdefined" + else: + value = value.lower() + job_args.append(f"{key}: #{value}") + + auto_play_option = "autoPlay:false" + job_args.append(auto_play_option) + + job_str = " ".join(job_args) + log.debug(job_str) + + return job_str + + +def publish_preview_sequences(staging_dir, filename, + startFrame, endFrame, + percentSize, ext): + """publish preview animation by creating bitmaps + ***For 3dsMax Version <2024 + + Args: + staging_dir (str): staging directory + filename (str): filename + startFrame (int): start frame + endFrame (int): end frame + percentSize (int): percentage of the resolution + ext (str): image extension + """ + # get the screenshot + resolution_percentage = float(percentSize) / 100 + res_width = rt.renderWidth * resolution_percentage + res_height = rt.renderHeight * resolution_percentage + + viewportRatio = float(res_width / res_height) + + for i in range(startFrame, endFrame + 1): + rt.sliderTime = i + fname = "{}.{:04}.{}".format(filename, i, ext) + filepath = os.path.join(staging_dir, fname) + filepath = filepath.replace("\\", "/") + preview_res = rt.bitmap( + res_width, res_height, filename=filepath) + dib = rt.gw.getViewportDib() + dib_width = float(dib.width) + dib_height = float(dib.height) + renderRatio = float(dib_width / dib_height) + if viewportRatio <= renderRatio: + heightCrop = (dib_width / renderRatio) + topEdge = int((dib_height - heightCrop) / 2.0) + tempImage_bmp = rt.bitmap(dib_width, heightCrop) + src_box_value = rt.Box2(0, topEdge, dib_width, heightCrop) + else: + widthCrop = dib_height * renderRatio + leftEdge = int((dib_width - widthCrop) / 2.0) + tempImage_bmp = rt.bitmap(widthCrop, dib_height) + src_box_value = rt.Box2(0, leftEdge, dib_width, dib_height) + rt.pasteBitmap(dib, tempImage_bmp, src_box_value, rt.Point2(0, 0)) + + # copy the bitmap and close it + rt.copy(tempImage_bmp, preview_res) + rt.close(tempImage_bmp) + + rt.save(preview_res) + rt.close(preview_res) + + rt.close(dib) + + if rt.keyboard.escPressed: + rt.exit() + # clean up the cache + rt.gc(delayed=True) + + +def publish_preview_animation( + instance, staging_dir, + ext, review_camera, + startFrame=None, endFrame=None, + viewport_options=None): + """Render camera review animation + + Args: + instance (pyblish.api.instance): Instance + filepath (str): filepath + review_camera (str): viewport camera for preview render + startFrame (int): start frame + endFrame (int): end frame + viewport_options (dict): viewport setting options + """ + + if startFrame is None: + startFrame = int(rt.animationRange.start) + if endFrame is None: + endFrame = int(rt.animationRange.end) + if viewport_options is None: + viewport_options = viewport_options_for_preview_animation() + with play_preview_when_done(False): + with viewport_camera(review_camera): + if int(get_max_version()) < 2024: + with viewport_preference_setting( + viewport_options["general_viewport"], + viewport_options["nitrous_viewport"], + viewport_options["vp_btn_mgr"]): + percentSize = viewport_options.get("percentSize", 100) + + publish_preview_sequences( + staging_dir, instance.name, + startFrame, endFrame, percentSize, ext) + else: + fps = instance.data["fps"] + rt.completeRedraw() + preview_arg = publish_review_animation(instance, staging_dir, + startFrame, endFrame, + ext, fps, viewport_options) + rt.execute(preview_arg) + + rt.completeRedraw() + + +def viewport_options_for_preview_animation(): + """ + Function to store the default data of viewport options + Returns: + dict: viewport setting options + + """ + # viewport_options should be the dictionary + if int(get_max_version()) < 2024: + return { + "visualStyleMode": "defaultshading", + "viewportPreset": "highquality", + "percentSize": 100, + "vpTexture": False, + "dspGeometry": True, + "dspShapes": False, + "dspLights": False, + "dspCameras": False, + "dspHelpers": False, + "dspParticles": True, + "dspBones": False, + "dspBkg": True, + "dspGrid": False, + "dspSafeFrame":False, + "dspFrameNums": False + } + else: + viewport_options = {} + viewport_options.update({"percentSize": 100}) + general_viewport = { + "dspBkg": True, + "dspGrid": False + } + nitrous_viewport = { + "VisualStyleMode": "defaultshading", + "ViewportPreset": "highquality", + "UseTextureEnabled": False + } + viewport_options["general_viewport"] = general_viewport + viewport_options["nitrous_viewport"] = nitrous_viewport + viewport_options["vp_btn_mgr"] = { + "EnableButtons": False} + return viewport_options diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index c3985a2ded..904a4eab0f 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -32,7 +32,6 @@ class CollectReview(pyblish.api.InstancePlugin, "review_camera": camera_name, "imageFormat": creator_attrs["imageFormat"], "keepImages": creator_attrs["keepImages"], - "percentSize": creator_attrs["percentSize"], "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], "fps": instance.context.data["fps"] @@ -49,9 +48,10 @@ class CollectReview(pyblish.api.InstancePlugin, instance.data["colorspaceView"] = view_transform preview_data = { - "visualStyleMode": creator_attrs["visualStyleMode"], - "viewportPreset": creator_attrs["viewportPreset"], - "vpTexture": creator_attrs["vpTexture"], + "vpStyle": creator_attrs["visualStyleMode"], + "vpPreset": creator_attrs["viewportPreset"], + "percentSize": creator_attrs["percentSize"], + "vpTextures": creator_attrs["vpTexture"], "dspGeometry": attr_values.get("dspGeometry"), "dspShapes": attr_values.get("dspShapes"), "dspLights": attr_values.get("dspLights"), @@ -66,6 +66,8 @@ class CollectReview(pyblish.api.InstancePlugin, } else: preview_data = {} + preview_data.update({ + "percentSize": creator_attrs["percentSize"]}) general_viewport = { "dspBkg": attr_values.get("dspBkg"), "dspGrid": attr_values.get("dspGrid") @@ -80,9 +82,6 @@ class CollectReview(pyblish.api.InstancePlugin, preview_data["vp_btn_mgr"] = { "EnableButtons": False } - preview_data["preferences"] = { - "playPreviewWhenDone": False - } # Enable ftrack functionality instance.data.setdefault("families", []).append('ftrack') @@ -91,7 +90,7 @@ class CollectReview(pyblish.api.InstancePlugin, burnin_members["focalLength"] = focal_length instance.data.update(general_preview_data) - instance.data.update(preview_data) + instance.data["viewport_options"] = preview_data @classmethod def get_attribute_defs(cls): diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index d57ed44d65..d2de981236 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -1,7 +1,9 @@ import os import pyblish.api from openpype.pipeline import publish -from openpype.hosts.max.api.lib import publish_preview_animation +from openpype.hosts.max.api.preview_animation import ( + publish_preview_animation +) class ExtractReviewAnimation(publish.Extractor): @@ -30,10 +32,12 @@ class ExtractReviewAnimation(publish.Extractor): " '%s' to '%s'" % (filename, staging_dir)) review_camera = instance.data["review_camera"] + viewport_options = instance.data.get("viewport_options", {}) publish_preview_animation( instance, staging_dir, ext, review_camera, - startFrame=start, endFrame=end) + startFrame=start, endFrame=end, + viewport_options=viewport_options) tags = ["review"] if not instance.data.get("keepImages"): From 0aa5b59384992ffc9f97554a57c606436f34fb3c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 18:24:15 +0800 Subject: [PATCH 22/42] move functions to preview_animation.py --- openpype/hosts/max/api/lib.py | 292 ---------------------------------- 1 file changed, 292 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 5e55daceb2..166a66ce48 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -322,88 +322,6 @@ def is_headless(): return rt.maxops.isInNonInteractiveMode() -@contextlib.contextmanager -def viewport_camera(camera): - """Function to set viewport camera during context - ***For 3dsMax 2024+ - Args: - camera (str): viewport camera for review render - """ - original = rt.viewport.getCamera() - has_autoplay = rt.preferences.playPreviewWhenDone - nitrousGraphicMgr = rt.NitrousGraphicsManager - viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - orig_preset = viewport_setting.ViewportPreset - if not original: - # if there is no original camera - # use the current camera as original - original = rt.getNodeByName(camera) - review_camera = rt.getNodeByName(camera) - try: - rt.viewport.setCamera(review_camera) - rt.preferences.playPreviewWhenDone = False - yield - finally: - rt.viewport.setCamera(original) - rt.preferences.playPreviewWhenDone = has_autoplay - viewport_setting.ViewportPreset = orig_preset - - -@contextlib.contextmanager -def play_preview_when_done(has_autoplay): - current_playback = rt.preferences.playPreviewWhenDone - try: - rt.preferences.playPreviewWhenDone = has_autoplay - yield - finally: - rt.preferences.playPreviewWhenDone = current_playback - - -@contextlib.contextmanager -def viewport_preference_setting(general_viewport, - nitrous_viewport, - vp_button_mgr): - """Function to set viewport setting during context - ***For Max Version < 2024 - Args: - camera (str): Viewport camera for review render - general_viewport (dict): General viewport setting - nitrous_viewport (dict): Nitrous setting for - preview animation - vp_button_mgr (dict): Viewport button manager Setting - preview_preferences (dict): Preview Preferences Setting - """ - orig_vp_grid = rt.viewport.getGridVisibility(1) - orig_vp_bkg = rt.viewport.IsSolidBackgroundColorMode() - - nitrousGraphicMgr = rt.NitrousGraphicsManager - viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - vp_button_mgr_original = { - key: getattr(rt.ViewportButtonMgr, key) for key in vp_button_mgr - } - nitrous_viewport_original = { - key: getattr(viewport_setting, key) for key in nitrous_viewport - } - - try: - rt.viewport.setGridVisibility(1, general_viewport["dspGrid"]) - rt.viewport.EnableSolidBackgroundColorMode(general_viewport["dspBkg"]) - for key, value in vp_button_mgr.items(): - setattr(rt.ViewportButtonMgr, key, value) - for key, value in nitrous_viewport.items(): - if nitrous_viewport[key] != nitrous_viewport_original[key]: - setattr(viewport_setting, key, value) - yield - - finally: - rt.viewport.setGridVisibility(1, orig_vp_grid) - rt.viewport.EnableSolidBackgroundColorMode(orig_vp_bkg) - for key, value in vp_button_mgr_original.items(): - setattr(rt.ViewportButtonMgr, key, value) - for key, value in nitrous_viewport_original.items(): - setattr(viewport_setting, key, value) - - def set_timeline(frameStart, frameEnd): """Set frame range for timeline editor in Max """ @@ -565,213 +483,3 @@ def get_plugins() -> list: plugin_info_list.append(plugin_info) return plugin_info_list - - -def publish_review_animation(instance, staging_dir, start, - end, ext, fps, viewport_options): - """Function to set up preview arguments in MaxScript. - ****For 3dsMax 2024+ - - Args: - instance (str): instance - filepath (str): output of the preview animation - start (int): startFrame - end (int): endFrame - fps (float): fps value - viewport_options (dict): viewport setting options - - Returns: - list: job arguments - """ - job_args = list() - filename = "{0}..{1}".format(instance.name, ext) - filepath = os.path.join(staging_dir, filename) - filepath = filepath.replace("\\", "/") - default_option = f'CreatePreview filename:"{filepath}"' - job_args.append(default_option) - frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa - job_args.append(frame_option) - - for key, value in viewport_options.items(): - if isinstance(value, bool): - if value: - job_args.append(f"{key}:{value}") - - elif isinstance(value, str): - if key == "vpStyle": - if viewport_options[key] == "Realistic": - value = "defaultshading" - elif viewport_options[key] == "Shaded": - log.warning( - "'Shaded' Mode not supported in " - "preview animation in Max 2024..\n" - "Using 'defaultshading' instead") - value = "defaultshading" - elif viewport_options[key] == "ConsistentColors": - value = "flatcolor" - else: - value = value.lower() - elif key == "vpPreset": - if viewport_options[key] == "Quality": - value = "highquality" - elif viewport_options[key] == "Customize": - value = "userdefined" - else: - value = value.lower() - job_args.append(f"{key}: #{value}") - - auto_play_option = "autoPlay:false" - job_args.append(auto_play_option) - - job_str = " ".join(job_args) - log.debug(job_str) - - return job_str - - -def publish_preview_sequences(staging_dir, filename, - startFrame, endFrame, - percentSize, ext): - """publish preview animation by creating bitmaps - ***For 3dsMax Version <2024 - - Args: - staging_dir (str): staging directory - filename (str): filename - startFrame (int): start frame - endFrame (int): end frame - percentSize (int): percentage of the resolution - ext (str): image extension - """ - # get the screenshot - resolution_percentage = float(percentSize) / 100 - res_width = rt.renderWidth * resolution_percentage - res_height = rt.renderHeight * resolution_percentage - - viewportRatio = float(res_width / res_height) - - for i in range(startFrame, endFrame + 1): - rt.sliderTime = i - fname = "{}.{:04}.{}".format(filename, i, ext) - filepath = os.path.join(staging_dir, fname) - filepath = filepath.replace("\\", "/") - preview_res = rt.bitmap( - res_width, res_height, filename=filepath) - dib = rt.gw.getViewportDib() - dib_width = float(dib.width) - dib_height = float(dib.height) - renderRatio = float(dib_width / dib_height) - if viewportRatio <= renderRatio: - heightCrop = (dib_width / renderRatio) - topEdge = int((dib_height - heightCrop) / 2.0) - tempImage_bmp = rt.bitmap(dib_width, heightCrop) - src_box_value = rt.Box2(0, topEdge, dib_width, heightCrop) - else: - widthCrop = dib_height * renderRatio - leftEdge = int((dib_width - widthCrop) / 2.0) - tempImage_bmp = rt.bitmap(widthCrop, dib_height) - src_box_value = rt.Box2(0, leftEdge, dib_width, dib_height) - rt.pasteBitmap(dib, tempImage_bmp, src_box_value, rt.Point2(0, 0)) - - # copy the bitmap and close it - rt.copy(tempImage_bmp, preview_res) - rt.close(tempImage_bmp) - - rt.save(preview_res) - rt.close(preview_res) - - rt.close(dib) - - if rt.keyboard.escPressed: - rt.exit() - # clean up the cache - rt.gc(delayed=True) - - -def publish_preview_animation( - instance, staging_dir, - ext, review_camera, - startFrame=None, endFrame=None, - viewport_options=None): - """Render camera review animation - - Args: - instance (pyblish.api.instance): Instance - filepath (str): filepath - review_camera (str): viewport camera for preview render - startFrame (int): start frame - endFrame (int): end frame - viewport_options (dict): viewport setting options - """ - - if startFrame is None: - startFrame = int(rt.animationRange.start) - if endFrame is None: - endFrame = int(rt.animationRange.end) - if viewport_options is None: - viewport_options = viewport_options_for_preview_animation() - with play_preview_when_done(False): - with viewport_camera(review_camera): - if int(get_max_version()) < 2024: - with viewport_preference_setting( - viewport_options["general_viewport"], - viewport_options["nitrous_viewport"], - viewport_options["vp_btn_mgr"]): - percentSize = viewport_options.get("percentSize", 100) - - publish_preview_sequences( - staging_dir, instance.name, - startFrame, endFrame, percentSize, ext) - else: - fps = instance.data["fps"] - rt.completeRedraw() - preview_arg = publish_review_animation(instance, staging_dir, - startFrame, endFrame, - ext, fps, viewport_options) - rt.execute(preview_arg) - - rt.completeRedraw() - -def viewport_options_for_preview_animation(): - """ - Function to store the default data of viewport options - Returns: - dict: viewport setting options - - """ - # viewport_options should be the dictionary - if int(get_max_version()) < 2024: - return { - "visualStyleMode": "defaultshading", - "viewportPreset": "highquality", - "percentSize": 100, - "vpTexture": False, - "dspGeometry": True, - "dspShapes": False, - "dspLights": False, - "dspCameras": False, - "dspHelpers": False, - "dspParticles": True, - "dspBones": False, - "dspBkg": True, - "dspGrid": False, - "dspSafeFrame":False, - "dspFrameNums": False - } - else: - viewport_options = {} - viewport_options.update({"percentSize": 100}) - general_viewport = { - "dspBkg": True, - "dspGrid": False - } - nitrous_viewport = { - "VisualStyleMode": "defaultshading", - "ViewportPreset": "highquality", - "UseTextureEnabled": False - } - viewport_options["general_viewport"] = general_viewport - viewport_options["nitrous_viewport"] = nitrous_viewport - viewport_options["vp_btn_mgr"] = { - "EnableButtons": False} - return viewport_options From f695ea72848d2df538ccbdcc14a3db482dd711f8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 19:24:13 +0800 Subject: [PATCH 23/42] cleanup the code --- openpype/hosts/max/api/preview_animation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index bb2f1410d4..c6dd8737a7 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -33,9 +33,6 @@ def viewport_camera(camera): """ original = rt.viewport.getCamera() has_autoplay = rt.preferences.playPreviewWhenDone - nitrousGraphicMgr = rt.NitrousGraphicsManager - viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - orig_preset = viewport_setting.ViewportPreset if not original: # if there is no original camera # use the current camera as original @@ -48,7 +45,6 @@ def viewport_camera(camera): finally: rt.viewport.setCamera(original) rt.preferences.playPreviewWhenDone = has_autoplay - viewport_setting.ViewportPreset = orig_preset @contextlib.contextmanager @@ -95,6 +91,7 @@ def viewport_preference_setting(general_viewport, for key, value in nitrous_viewport_original.items(): setattr(viewport_setting, key, value) + def publish_review_animation(instance, staging_dir, start, end, ext, fps, viewport_options): """Function to set up preview arguments in MaxScript. From 349cf6d35d8544b84d28e4f90e771557dc25da6b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 20:25:16 +0800 Subject: [PATCH 24/42] support resolution settings --- openpype/hosts/max/api/preview_animation.py | 58 ++++++++++++++----- .../hosts/max/plugins/create/create_review.py | 12 ++++ .../max/plugins/publish/collect_review.py | 4 +- .../publish/extract_review_animation.py | 2 + 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index c6dd8737a7..601ff65c81 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -24,6 +24,26 @@ def play_preview_when_done(has_autoplay): rt.preferences.playPreviewWhenDone = current_playback +@contextlib.contextmanager +def render_resolution(width, height): + """Function to set render resolution option during + context + + Args: + width (int): render width + height (int): render height + """ + current_renderWidth = rt.renderWidth + current_renderHeight = rt.renderHeight + try: + rt.renderWidth = width + rt.renderHeight = height + yield + finally: + rt.renderWidth = current_renderWidth + rt.renderHeight = current_renderHeight + + @contextlib.contextmanager def viewport_camera(camera): """Function to set viewport camera during context @@ -217,6 +237,7 @@ def publish_preview_animation( instance, staging_dir, ext, review_camera, startFrame=None, endFrame=None, + resolution=None, viewport_options=None): """Render camera review animation @@ -235,25 +256,30 @@ def publish_preview_animation( endFrame = int(rt.animationRange.end) if viewport_options is None: viewport_options = viewport_options_for_preview_animation() + if resolution is None: + resolution = (1920, 1080) with play_preview_when_done(False): with viewport_camera(review_camera): - if int(get_max_version()) < 2024: - with viewport_preference_setting( - viewport_options["general_viewport"], - viewport_options["nitrous_viewport"], - viewport_options["vp_btn_mgr"]): - percentSize = viewport_options.get("percentSize", 100) + width, height = resolution + with render_resolution(width, height): + if int(get_max_version()) < 2024: + with viewport_preference_setting( + viewport_options["general_viewport"], + viewport_options["nitrous_viewport"], + viewport_options["vp_btn_mgr"]): + percentSize = viewport_options.get("percentSize", 100) - publish_preview_sequences( - staging_dir, instance.name, - startFrame, endFrame, percentSize, ext) - else: - fps = instance.data["fps"] - rt.completeRedraw() - preview_arg = publish_review_animation(instance, staging_dir, - startFrame, endFrame, - ext, fps, viewport_options) - rt.execute(preview_arg) + publish_preview_sequences( + staging_dir, instance.name, + startFrame, endFrame, percentSize, ext) + else: + fps = instance.data["fps"] + rt.completeRedraw() + preview_arg = publish_review_animation( + instance, staging_dir, + startFrame, endFrame, + ext, fps, viewport_options) + rt.execute(preview_arg) rt.completeRedraw() diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 977c018f5c..bbcdce90b7 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -18,6 +18,8 @@ class CreateReview(plugin.MaxCreator): "creator_attributes", dict()) for key in ["imageFormat", "keepImages", + "review_width", + "review_height", "percentSize", "visualStyleMode", "viewportPreset", @@ -48,6 +50,16 @@ class CreateReview(plugin.MaxCreator): "DXMode", "Customize"] return [ + NumberDef("review_width", + label="Review width", + decimals=0, + minimum=0, + default=1920), + NumberDef("review_height", + label="Review height", + decimals=0, + minimum=0, + default=1080), BoolDef("keepImages", label="Keep Image Sequences", default=False), diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 904a4eab0f..cfd48edb15 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -34,7 +34,9 @@ class CollectReview(pyblish.api.InstancePlugin, "keepImages": creator_attrs["keepImages"], "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], - "fps": instance.context.data["fps"] + "fps": instance.context.data["fps"], + "resolution": (creator_attrs["review_width"], + creator_attrs["review_height"]) } if int(get_max_version()) >= 2024: diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index d2de981236..c308aadfdb 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -33,10 +33,12 @@ class ExtractReviewAnimation(publish.Extractor): review_camera = instance.data["review_camera"] viewport_options = instance.data.get("viewport_options", {}) + resolution = instance.data.get("resolution", ()) publish_preview_animation( instance, staging_dir, ext, review_camera, startFrame=start, endFrame=end, + resolution=resolution, viewport_options=viewport_options) tags = ["review"] From 977d0144d83f5ae3f0f49a4052db022c4cdd6024 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 21:24:50 +0800 Subject: [PATCH 25/42] move render resolution function to lib --- openpype/hosts/max/api/lib.py | 20 +++++++++++++++++++ openpype/hosts/max/api/preview_animation.py | 22 +-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 166a66ce48..e6b669f82f 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -483,3 +483,23 @@ def get_plugins() -> list: plugin_info_list.append(plugin_info) return plugin_info_list + + +@contextlib.contextmanager +def render_resolution(width, height): + """Function to set render resolution option during + context + + Args: + width (int): render width + height (int): render height + """ + current_renderWidth = rt.renderWidth + current_renderHeight = rt.renderHeight + try: + rt.renderWidth = width + rt.renderHeight = height + yield + finally: + rt.renderWidth = current_renderWidth + rt.renderHeight = current_renderHeight diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 601ff65c81..3d66d278f0 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -2,7 +2,7 @@ import os import logging import contextlib from pymxs import runtime as rt -from .lib import get_max_version +from .lib import get_max_version, render_resolution log = logging.getLogger("openpype.hosts.max") @@ -24,26 +24,6 @@ def play_preview_when_done(has_autoplay): rt.preferences.playPreviewWhenDone = current_playback -@contextlib.contextmanager -def render_resolution(width, height): - """Function to set render resolution option during - context - - Args: - width (int): render width - height (int): render height - """ - current_renderWidth = rt.renderWidth - current_renderHeight = rt.renderHeight - try: - rt.renderWidth = width - rt.renderHeight = height - yield - finally: - rt.renderWidth = current_renderWidth - rt.renderHeight = current_renderHeight - - @contextlib.contextmanager def viewport_camera(camera): """Function to set viewport camera during context From f674b7b10835a1bd75ad91f76082f14afd4a9f20 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 21:35:33 +0800 Subject: [PATCH 26/42] hound --- openpype/hosts/max/api/lib.py | 1 - openpype/hosts/max/api/preview_animation.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index e6b669f82f..5a54abd141 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- """Library of functions useful for 3dsmax pipeline.""" -import os import contextlib import logging import json diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 3d66d278f0..caa4f60475 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -287,7 +287,7 @@ def viewport_options_for_preview_animation(): "dspBones": False, "dspBkg": True, "dspGrid": False, - "dspSafeFrame":False, + "dspSafeFrame": False, "dspFrameNums": False } else: From 71e9d6cc13c1b147abd67213d4d1ae234842a1f8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 19 Oct 2023 22:53:56 +0800 Subject: [PATCH 27/42] rename render_preview_animation --- openpype/hosts/max/api/preview_animation.py | 2 +- .../hosts/max/plugins/publish/extract_review_animation.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index caa4f60475..260d18893e 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -213,7 +213,7 @@ def publish_preview_sequences(staging_dir, filename, rt.gc(delayed=True) -def publish_preview_animation( +def render_preview_animation( instance, staging_dir, ext, review_camera, startFrame=None, endFrame=None, diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index c308aadfdb..979cbc828c 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -2,7 +2,7 @@ import os import pyblish.api from openpype.pipeline import publish from openpype.hosts.max.api.preview_animation import ( - publish_preview_animation + render_preview_animation ) @@ -34,7 +34,7 @@ class ExtractReviewAnimation(publish.Extractor): review_camera = instance.data["review_camera"] viewport_options = instance.data.get("viewport_options", {}) resolution = instance.data.get("resolution", ()) - publish_preview_animation( + render_preview_animation( instance, staging_dir, ext, review_camera, startFrame=start, endFrame=end, From e24140715b386e34bbefdf6350d6f75c0c388e8e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 20 Oct 2023 00:30:24 +0800 Subject: [PATCH 28/42] clean up code for preview animation --- openpype/hosts/max/api/preview_animation.py | 157 +++++++++--------- .../publish/extract_review_animation.py | 26 ++- 2 files changed, 90 insertions(+), 93 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 260d18893e..171d335ba4 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -92,96 +92,92 @@ def viewport_preference_setting(general_viewport, setattr(viewport_setting, key, value) -def publish_review_animation(instance, staging_dir, start, - end, ext, fps, viewport_options): +def _render_preview_animation_max_2024( + filepath, start, end, ext, viewport_options): """Function to set up preview arguments in MaxScript. ****For 3dsMax 2024+ - Args: - instance (str): instance - filepath (str): output of the preview animation + filepath (str): filepath for render output without frame number and + extension, for example: /path/to/file start (int): startFrame end (int): endFrame - fps (float): fps value - viewport_options (dict): viewport setting options - + viewport_options (dict): viewport setting options, e.g. + {"vpStyle": "defaultshading", "vpPreset": "highquality"} Returns: - list: job arguments + list: Created files """ - job_args = list() - filename = "{0}..{1}".format(instance.name, ext) - filepath = os.path.join(staging_dir, filename) filepath = filepath.replace("\\", "/") + filepath = f"{filepath}..{ext}" + frame_template = f"{filepath}.{{:04d}}.{ext}" + job_args = list() default_option = f'CreatePreview filename:"{filepath}"' job_args.append(default_option) - frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa + frame_option = f"outputAVI:false start:{start} end:{end}" job_args.append(frame_option) - for key, value in viewport_options.items(): if isinstance(value, bool): if value: job_args.append(f"{key}:{value}") - elif isinstance(value, str): if key == "vpStyle": - if viewport_options[key] == "Realistic": + if value == "Realistic": value = "defaultshading" - elif viewport_options[key] == "Shaded": + elif value == "Shaded": log.warning( "'Shaded' Mode not supported in " - "preview animation in Max 2024..\n" - "Using 'defaultshading' instead") + "preview animation in Max 2024.\n" + "Using 'defaultshading' instead.") value = "defaultshading" - elif viewport_options[key] == "ConsistentColors": + elif value == "ConsistentColors": value = "flatcolor" else: value = value.lower() elif key == "vpPreset": - if viewport_options[key] == "Quality": + if value == "Quality": value = "highquality" - elif viewport_options[key] == "Customize": + elif value == "Customize": value = "userdefined" else: value = value.lower() job_args.append(f"{key}: #{value}") - auto_play_option = "autoPlay:false" job_args.append(auto_play_option) - job_str = " ".join(job_args) log.debug(job_str) - - return job_str + rt.completeRedraw() + rt.execute(job_str) + # Return the created files + return [frame_template.format(frame) for frame in range(start, end + 1)] -def publish_preview_sequences(staging_dir, filename, - startFrame, endFrame, - percentSize, ext): - """publish preview animation by creating bitmaps +def _render_preview_animation_max_pre_2024( + filepath, startFrame, endFrame, percentSize, ext): + """Render viewport animation by creating bitmaps ***For 3dsMax Version <2024 - Args: - staging_dir (str): staging directory - filename (str): filename + filepath (str): filepath without frame numbers and extension startFrame (int): start frame endFrame (int): end frame percentSize (int): percentage of the resolution ext (str): image extension + Returns: + list: Created filepaths """ # get the screenshot resolution_percentage = float(percentSize) / 100 res_width = rt.renderWidth * resolution_percentage res_height = rt.renderHeight * resolution_percentage - viewportRatio = float(res_width / res_height) - - for i in range(startFrame, endFrame + 1): - rt.sliderTime = i - fname = "{}.{:04}.{}".format(filename, i, ext) - filepath = os.path.join(staging_dir, fname) - filepath = filepath.replace("\\", "/") + frame_template = "{}.{{:04}}.{}".format(filepath, ext) + frame_template.replace("\\", "/") + files = [] + user_cancelled = False + for frame in range(startFrame, endFrame + 1): + rt.sliderTime = frame + filepath = frame_template.format(frame) preview_res = rt.bitmap( - res_width, res_height, filename=filepath) + res_width, res_height, filename=filepath + ) dib = rt.gw.getViewportDib() dib_width = float(dib.width) dib_height = float(dib.height) @@ -197,71 +193,78 @@ def publish_preview_sequences(staging_dir, filename, tempImage_bmp = rt.bitmap(widthCrop, dib_height) src_box_value = rt.Box2(0, leftEdge, dib_width, dib_height) rt.pasteBitmap(dib, tempImage_bmp, src_box_value, rt.Point2(0, 0)) - # copy the bitmap and close it rt.copy(tempImage_bmp, preview_res) rt.close(tempImage_bmp) - rt.save(preview_res) rt.close(preview_res) - rt.close(dib) - - if rt.keyboard.escPressed: - rt.exit() + files.append(filepath) # clean up the cache + if rt.keyboard.escPressed: + user_cancelled = True + break rt.gc(delayed=True) + if user_cancelled: + raise RuntimeError("User cancelled rendering of viewport animation.") + return files def render_preview_animation( - instance, staging_dir, - ext, review_camera, - startFrame=None, endFrame=None, - resolution=None, + filepath, + ext, + review_camera, + start_frame=None, + end_frame=None, + width=1920, + height=1080, viewport_options=None): """Render camera review animation - Args: - instance (pyblish.api.instance): Instance - filepath (str): filepath + filepath (str): filepath to render to, without frame number and + extension + ext (str): output file extension review_camera (str): viewport camera for preview render - startFrame (int): start frame - endFrame (int): end frame + start_frame (int): start frame + end_frame (int): end frame + width (int): render resolution width + height (int): render resolution height viewport_options (dict): viewport setting options + Returns: + list: Rendered output files """ + if start_frame is None: + start_frame = int(rt.animationRange.start) + if end_frame is None: + end_frame = int(rt.animationRange.end) - if startFrame is None: - startFrame = int(rt.animationRange.start) - if endFrame is None: - endFrame = int(rt.animationRange.end) if viewport_options is None: viewport_options = viewport_options_for_preview_animation() - if resolution is None: - resolution = (1920, 1080) with play_preview_when_done(False): with viewport_camera(review_camera): - width, height = resolution with render_resolution(width, height): if int(get_max_version()) < 2024: with viewport_preference_setting( viewport_options["general_viewport"], viewport_options["nitrous_viewport"], - viewport_options["vp_btn_mgr"]): + viewport_options["vp_btn_mgr"] + ): percentSize = viewport_options.get("percentSize", 100) - - publish_preview_sequences( - staging_dir, instance.name, - startFrame, endFrame, percentSize, ext) + return _render_preview_animation_max_pre_2024( + filepath, + start_frame, + end_frame, + percentSize, + ext + ) else: - fps = instance.data["fps"] - rt.completeRedraw() - preview_arg = publish_review_animation( - instance, staging_dir, - startFrame, endFrame, - ext, fps, viewport_options) - rt.execute(preview_arg) - - rt.completeRedraw() + return _render_preview_animation_max_2024( + filepath, + start_frame, + end_frame, + ext, + viewport_options + ) def viewport_options_for_preview_animation(): diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 979cbc828c..df1f2b4182 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -24,8 +24,6 @@ class ExtractReviewAnimation(publish.Extractor): end = int(instance.data["frameEnd"]) filepath = os.path.join(staging_dir, filename) filepath = filepath.replace("\\", "/") - filenames = self.get_files( - instance.name, start, end, ext) self.log.debug( "Writing Review Animation to" @@ -34,13 +32,18 @@ class ExtractReviewAnimation(publish.Extractor): review_camera = instance.data["review_camera"] viewport_options = instance.data.get("viewport_options", {}) resolution = instance.data.get("resolution", ()) - render_preview_animation( - instance, staging_dir, - ext, review_camera, - startFrame=start, endFrame=end, - resolution=resolution, + files = render_preview_animation( + os.path.join(staging_dir, instance.name), + ext, + review_camera, + start, + end, + width=resolution[0], + height=resolution[1], viewport_options=viewport_options) + filenames = [os.path.basename(path) for path in files] + tags = ["review"] if not instance.data.get("keepImages"): tags.append("delete") @@ -63,12 +66,3 @@ class ExtractReviewAnimation(publish.Extractor): if "representations" not in instance.data: instance.data["representations"] = [] instance.data["representations"].append(representation) - - def get_files(self, filename, start, end, ext): - file_list = [] - for frame in range(int(start), int(end) + 1): - actual_name = "{}.{:04}.{}".format( - filename, frame, ext) - file_list.append(actual_name) - - return file_list From 4c390a62391c65ec9dd8e403686708e5ee1d3ebd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 20 Oct 2023 00:32:27 +0800 Subject: [PATCH 29/42] hound --- openpype/hosts/max/api/preview_animation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 171d335ba4..1d7211443a 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -1,4 +1,3 @@ -import os import logging import contextlib from pymxs import runtime as rt From 151881e1b3b5b13dab81a360d297bdf4491b73d2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 20 Oct 2023 16:29:42 +0800 Subject: [PATCH 30/42] clean up code for review families --- openpype/hosts/max/api/preview_animation.py | 11 ++-- .../max/plugins/publish/collect_review.py | 16 +++-- .../publish/extract_review_animation.py | 15 ++--- .../max/plugins/publish/extract_thumbnail.py | 60 ++++++------------- .../publish/validate_resolution_setting.py | 4 +- 5 files changed, 37 insertions(+), 69 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 1d7211443a..4c878cc33a 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -106,10 +106,10 @@ def _render_preview_animation_max_2024( list: Created files """ filepath = filepath.replace("\\", "/") - filepath = f"{filepath}..{ext}" + preview_output = f"{filepath}..{ext}" frame_template = f"{filepath}.{{:04d}}.{ext}" job_args = list() - default_option = f'CreatePreview filename:"{filepath}"' + default_option = f'CreatePreview filename:"{preview_output}"' job_args.append(default_option) frame_option = f"outputAVI:false start:{start} end:{end}" job_args.append(frame_option) @@ -199,10 +199,10 @@ def _render_preview_animation_max_pre_2024( rt.close(preview_res) rt.close(dib) files.append(filepath) - # clean up the cache if rt.keyboard.escPressed: user_cancelled = True break + # clean up the cache rt.gc(delayed=True) if user_cancelled: raise RuntimeError("User cancelled rendering of viewport animation.") @@ -267,11 +267,10 @@ def render_preview_animation( def viewport_options_for_preview_animation(): - """ - Function to store the default data of viewport options + """Function to store the default data of viewport options + Returns: dict: viewport setting options - """ # viewport_options should be the dictionary if int(get_max_version()) < 2024: diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index cfd48edb15..8b782344eb 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -35,8 +35,8 @@ class CollectReview(pyblish.api.InstancePlugin, "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], "fps": instance.context.data["fps"], - "resolution": (creator_attrs["review_width"], - creator_attrs["review_height"]) + "review_width": creator_attrs["review_width"], + "review_height": creator_attrs["review_height"], } if int(get_max_version()) >= 2024: @@ -67,9 +67,6 @@ class CollectReview(pyblish.api.InstancePlugin, "dspFrameNums": attr_values.get("dspFrameNums") } else: - preview_data = {} - preview_data.update({ - "percentSize": creator_attrs["percentSize"]}) general_viewport = { "dspBkg": attr_values.get("dspBkg"), "dspGrid": attr_values.get("dspGrid") @@ -79,10 +76,11 @@ class CollectReview(pyblish.api.InstancePlugin, "ViewportPreset": creator_attrs["viewportPreset"], "UseTextureEnabled": creator_attrs["vpTexture"] } - preview_data["general_viewport"] = general_viewport - preview_data["nitrous_viewport"] = nitrous_viewport - preview_data["vp_btn_mgr"] = { - "EnableButtons": False + preview_data = { + "percentSize": creator_attrs["percentSize"], + "general_viewport": general_viewport, + "nitrous_viewport": nitrous_viewport, + "vp_btn_mgr": {"EnableButtons": False} } # Enable ftrack functionality diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index df1f2b4182..8391346e40 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -19,27 +19,22 @@ class ExtractReviewAnimation(publish.Extractor): def process(self, instance): staging_dir = self.staging_dir(instance) ext = instance.data.get("imageFormat") - filename = "{0}..{1}".format(instance.name, ext) start = int(instance.data["frameStart"]) end = int(instance.data["frameEnd"]) - filepath = os.path.join(staging_dir, filename) - filepath = filepath.replace("\\", "/") - + filepath = os.path.join(staging_dir, instance.name) self.log.debug( - "Writing Review Animation to" - " '%s' to '%s'" % (filename, staging_dir)) + "Writing Review Animation to '{}'".format(filepath)) review_camera = instance.data["review_camera"] viewport_options = instance.data.get("viewport_options", {}) - resolution = instance.data.get("resolution", ()) files = render_preview_animation( - os.path.join(staging_dir, instance.name), + filepath, ext, review_camera, start, end, - width=resolution[0], - height=resolution[1], + width=instance.data["review_width"], + height=instance.data["review_height"], viewport_options=viewport_options) filenames = [os.path.basename(path) for path in files] diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 890ee24f8e..fdedb3d0fc 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -1,20 +1,12 @@ import os import tempfile import pyblish.api -from pymxs import runtime as rt from openpype.pipeline import publish -from openpype.hosts.max.api.lib import ( - viewport_setup_updated, - viewport_setup, - get_max_version, - set_preview_arg -) - +from openpype.hosts.max.api.preview_animation import render_preview_animation class ExtractThumbnail(publish.Extractor): - """ - Extract Thumbnail for Review + """Extract Thumbnail for Review """ order = pyblish.api.ExtractorOrder @@ -29,36 +21,26 @@ class ExtractThumbnail(publish.Extractor): self.log.debug( f"Create temp directory {tmp_staging} for thumbnail" ) - fps = float(instance.data["fps"]) + ext = instance.data.get("imageFormat") frame = int(instance.data["frameStart"]) instance.context.data["cleanupFullPaths"].append(tmp_staging) - filename = "{name}_thumbnail..png".format(**instance.data) - filepath = os.path.join(tmp_staging, filename) - filepath = filepath.replace("\\", "/") - thumbnail = self.get_filename(instance.name, frame) + filepath = os.path.join(tmp_staging, instance.name) + + self.log.debug("Writing Thumbnail to '{}'".format(filepath)) - self.log.debug( - "Writing Thumbnail to" - " '%s' to '%s'" % (filename, tmp_staging)) review_camera = instance.data["review_camera"] - if int(get_max_version()) >= 2024: - with viewport_setup_updated(review_camera): - preview_arg = set_preview_arg( - instance, filepath, frame, frame, fps) - rt.execute(preview_arg) - else: - visual_style_preset = instance.data.get("visualStyleMode") - nitrousGraphicMgr = rt.NitrousGraphicsManager - viewport_setting = nitrousGraphicMgr.GetActiveViewportSetting() - with viewport_setup( - viewport_setting, - visual_style_preset, - review_camera): - viewport_setting.VisualStyleMode = rt.Name( - visual_style_preset) - preview_arg = set_preview_arg( - instance, filepath, frame, frame, fps) - rt.execute(preview_arg) + viewport_options = instance.data.get("viewport_options", {}) + files = render_preview_animation( + filepath, + ext, + review_camera, + frame, + frame, + width=instance.data["review_width"], + height=instance.data["review_height"], + viewport_options=viewport_options) + + thumbnail = next(os.path.basename(path) for path in files) representation = { "name": "thumbnail", @@ -73,9 +55,3 @@ class ExtractThumbnail(publish.Extractor): if "representations" not in instance.data: instance.data["representations"] = [] instance.data["representations"].append(representation) - - def get_filename(self, filename, target_frame): - thumbnail_name = "{}_thumbnail.{:04}.png".format( - filename, target_frame - ) - return thumbnail_name diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py index 969db0da2d..7d91a7b991 100644 --- a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -12,7 +12,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, """Validate the resolution setting aligned with DB""" order = pyblish.api.ValidatorOrder - 0.01 - families = ["maxrender", "review"] + families = ["maxrender"] hosts = ["max"] label = "Validate Resolution Setting" optional = True @@ -21,7 +21,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, if not self.is_active(instance.data): return width, height = self.get_db_resolution(instance) - current_width = rt.renderwidth + current_width = rt.renderWidth current_height = rt.renderHeight if current_width != width and current_height != height: raise PublishValidationError("Resolution Setting " From 2b335af1080f4ad1357ca55ed4c936a332be9d3e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 20 Oct 2023 17:22:51 +0800 Subject: [PATCH 31/42] make the calculation of the resolution with precent size more accurate and clean up the code on thumbnail extractor --- openpype/hosts/max/api/preview_animation.py | 13 ++++++------- .../max/plugins/publish/extract_thumbnail.py | 18 ++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 4c878cc33a..15fef1b428 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -157,15 +157,14 @@ def _render_preview_animation_max_pre_2024( filepath (str): filepath without frame numbers and extension startFrame (int): start frame endFrame (int): end frame - percentSize (int): percentage of the resolution ext (str): image extension Returns: list: Created filepaths """ # get the screenshot - resolution_percentage = float(percentSize) / 100 - res_width = rt.renderWidth * resolution_percentage - res_height = rt.renderHeight * resolution_percentage + percent = percentSize / 100.0 + res_width = int(round(rt.renderWidth * percent)) + res_height = int(round(rt.renderHeight * percent)) viewportRatio = float(res_width / res_height) frame_template = "{}.{{:04}}.{}".format(filepath, ext) frame_template.replace("\\", "/") @@ -212,7 +211,7 @@ def _render_preview_animation_max_pre_2024( def render_preview_animation( filepath, ext, - review_camera, + camera, start_frame=None, end_frame=None, width=1920, @@ -223,7 +222,7 @@ def render_preview_animation( filepath (str): filepath to render to, without frame number and extension ext (str): output file extension - review_camera (str): viewport camera for preview render + camera (str): viewport camera for preview render start_frame (int): start frame end_frame (int): end frame width (int): render resolution width @@ -240,7 +239,7 @@ def render_preview_animation( if viewport_options is None: viewport_options = viewport_options_for_preview_animation() with play_preview_when_done(False): - with viewport_camera(review_camera): + with viewport_camera(camera): with render_resolution(width, height): if int(get_max_version()) < 2024: with viewport_preference_setting( diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index fdedb3d0fc..05a5156cd3 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -15,17 +15,11 @@ class ExtractThumbnail(publish.Extractor): families = ["review"] def process(self, instance): - # TODO: Create temp directory for thumbnail - # - this is to avoid "override" of source file - tmp_staging = tempfile.mkdtemp(prefix="pyblish_tmp_") - self.log.debug( - f"Create temp directory {tmp_staging} for thumbnail" - ) ext = instance.data.get("imageFormat") frame = int(instance.data["frameStart"]) - instance.context.data["cleanupFullPaths"].append(tmp_staging) - filepath = os.path.join(tmp_staging, instance.name) - + staging_dir = self.staging_dir(instance) + filepath = os.path.join( + staging_dir, f"{instance.name}_thumbnail") self.log.debug("Writing Thumbnail to '{}'".format(filepath)) review_camera = instance.data["review_camera"] @@ -34,8 +28,8 @@ class ExtractThumbnail(publish.Extractor): filepath, ext, review_camera, - frame, - frame, + start_frame=frame, + end_frame=frame, width=instance.data["review_width"], height=instance.data["review_height"], viewport_options=viewport_options) @@ -46,7 +40,7 @@ class ExtractThumbnail(publish.Extractor): "name": "thumbnail", "ext": "png", "files": thumbnail, - "stagingDir": tmp_staging, + "stagingDir": staging_dir, "thumbnail": True } From d9dd36d77aebf2714719dfeb64992cdb206243a5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 23 Oct 2023 17:24:18 +0800 Subject: [PATCH 32/42] clean up code & make sure the ext of thumbnail representation aligns with the image format data setting --- openpype/hosts/max/api/preview_animation.py | 3 --- openpype/hosts/max/plugins/publish/extract_thumbnail.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index 15fef1b428..bb3ad4a7af 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -31,7 +31,6 @@ def viewport_camera(camera): camera (str): viewport camera for review render """ original = rt.viewport.getCamera() - has_autoplay = rt.preferences.playPreviewWhenDone if not original: # if there is no original camera # use the current camera as original @@ -39,11 +38,9 @@ def viewport_camera(camera): review_camera = rt.getNodeByName(camera) try: rt.viewport.setCamera(review_camera) - rt.preferences.playPreviewWhenDone = False yield finally: rt.viewport.setCamera(original) - rt.preferences.playPreviewWhenDone = has_autoplay @contextlib.contextmanager diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index fb391d09e1..e9d37d0be5 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -37,7 +37,7 @@ class ExtractThumbnail(publish.Extractor): representation = { "name": "thumbnail", - "ext": "png", + "ext": ext, "files": thumbnail, "stagingDir": staging_dir, "thumbnail": True From 0b0e359632a9b89f804560e868aa9f15a2720281 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 23 Oct 2023 18:24:40 +0800 Subject: [PATCH 33/42] docstring tweak --- openpype/hosts/max/api/preview_animation.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index bb3ad4a7af..b8564a9bd4 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -8,8 +8,7 @@ log = logging.getLogger("openpype.hosts.max") @contextlib.contextmanager def play_preview_when_done(has_autoplay): - """Function to set preview playback option during - context + """Set preview playback option during context Args: has_autoplay (bool): autoplay during creating @@ -25,10 +24,10 @@ def play_preview_when_done(has_autoplay): @contextlib.contextmanager def viewport_camera(camera): - """Function to set viewport camera during context + """Set viewport camera during context ***For 3dsMax 2024+ Args: - camera (str): viewport camera for review render + camera (str): viewport camera """ original = rt.viewport.getCamera() if not original: @@ -90,7 +89,7 @@ def viewport_preference_setting(general_viewport, def _render_preview_animation_max_2024( filepath, start, end, ext, viewport_options): - """Function to set up preview arguments in MaxScript. + """Render viewport preview with MaxScript using `CreateAnimation`. ****For 3dsMax 2024+ Args: filepath (str): filepath for render output without frame number and @@ -263,7 +262,7 @@ def render_preview_animation( def viewport_options_for_preview_animation(): - """Function to store the default data of viewport options + """Get default viewport options for `render_preview_animation`. Returns: dict: viewport setting options From 113b0664ad7f86709b402de5b778df2f0b31050a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 23 Oct 2023 18:37:29 +0800 Subject: [PATCH 34/42] docstring tweak --- openpype/hosts/max/api/lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 4515133d50..cbaf8a0c33 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -498,8 +498,7 @@ def get_plugins() -> list: @contextlib.contextmanager def render_resolution(width, height): - """Function to set render resolution option during - context + """Set render resolution option during context Args: width (int): render width From 473e09761bd5bb8b3b3de4677cb773a9189f57aa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 23 Oct 2023 21:56:31 +0800 Subject: [PATCH 35/42] make sure percentSize works for adjusting resolution of preview animation --- openpype/hosts/max/api/preview_animation.py | 27 ++++++++++++------- .../max/plugins/publish/collect_review.py | 3 +-- .../publish/extract_review_animation.py | 1 + .../max/plugins/publish/extract_thumbnail.py | 1 + 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index b8564a9bd4..eb832c1d1c 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -88,7 +88,7 @@ def viewport_preference_setting(general_viewport, def _render_preview_animation_max_2024( - filepath, start, end, ext, viewport_options): + filepath, start, end, percentSize, ext, viewport_options): """Render viewport preview with MaxScript using `CreateAnimation`. ****For 3dsMax 2024+ Args: @@ -96,19 +96,25 @@ def _render_preview_animation_max_2024( extension, for example: /path/to/file start (int): startFrame end (int): endFrame + percentSize (float): render resolution multiplier by 100 + e.g. 100.0 is 1x, 50.0 is 0.5x, 150.0 is 1.5x viewport_options (dict): viewport setting options, e.g. {"vpStyle": "defaultshading", "vpPreset": "highquality"} Returns: list: Created files """ + # the percentSize argument must be integer + percent = int(percentSize) filepath = filepath.replace("\\", "/") preview_output = f"{filepath}..{ext}" frame_template = f"{filepath}.{{:04d}}.{ext}" job_args = list() default_option = f'CreatePreview filename:"{preview_output}"' job_args.append(default_option) - frame_option = f"outputAVI:false start:{start} end:{end}" - job_args.append(frame_option) + output_res_option = f"outputAVI:false percentSize:{percent}" + job_args.append(output_res_option) + frame_range_options = f"start:{start} end:{end}" + job_args.append(frame_range_options) for key, value in viewport_options.items(): if isinstance(value, bool): if value: @@ -153,6 +159,8 @@ def _render_preview_animation_max_pre_2024( filepath (str): filepath without frame numbers and extension startFrame (int): start frame endFrame (int): end frame + percentSize (float): render resolution multiplier by 100 + e.g. 100.0 is 1x, 50.0 is 0.5x, 150.0 is 1.5x ext (str): image extension Returns: list: Created filepaths @@ -210,6 +218,7 @@ def render_preview_animation( camera, start_frame=None, end_frame=None, + percentSize=100.0, width=1920, height=1080, viewport_options=None): @@ -221,6 +230,8 @@ def render_preview_animation( camera (str): viewport camera for preview render start_frame (int): start frame end_frame (int): end frame + percentSize (float): render resolution multiplier by 100 + e.g. 100.0 is 1x, 50.0 is 0.5x, 150.0 is 1.5x width (int): render resolution width height (int): render resolution height viewport_options (dict): viewport setting options @@ -243,7 +254,6 @@ def render_preview_animation( viewport_options["nitrous_viewport"], viewport_options["vp_btn_mgr"] ): - percentSize = viewport_options.get("percentSize", 100) return _render_preview_animation_max_pre_2024( filepath, start_frame, @@ -256,6 +266,7 @@ def render_preview_animation( filepath, start_frame, end_frame, + percentSize, ext, viewport_options ) @@ -272,7 +283,6 @@ def viewport_options_for_preview_animation(): return { "visualStyleMode": "defaultshading", "viewportPreset": "highquality", - "percentSize": 100, "vpTexture": False, "dspGeometry": True, "dspShapes": False, @@ -288,18 +298,15 @@ def viewport_options_for_preview_animation(): } else: viewport_options = {} - viewport_options.update({"percentSize": 100}) - general_viewport = { + viewport_options["general_viewport"] = { "dspBkg": True, "dspGrid": False } - nitrous_viewport = { + viewport_options["nitrous_viewport"] = { "VisualStyleMode": "defaultshading", "ViewportPreset": "highquality", "UseTextureEnabled": False } - viewport_options["general_viewport"] = general_viewport - viewport_options["nitrous_viewport"] = nitrous_viewport viewport_options["vp_btn_mgr"] = { "EnableButtons": False} return viewport_options diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index ea606f8b9e..1f488f8180 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -32,6 +32,7 @@ class CollectReview(pyblish.api.InstancePlugin, "review_camera": camera_name, "frameStart": instance.data["frameStartHandle"], "frameEnd": instance.data["frameEndHandle"], + "percentSize": creator_attrs["percentSize"], "imageFormat": creator_attrs["imageFormat"], "keepImages": creator_attrs["keepImages"], "fps": instance.context.data["fps"], @@ -52,7 +53,6 @@ class CollectReview(pyblish.api.InstancePlugin, preview_data = { "vpStyle": creator_attrs["visualStyleMode"], "vpPreset": creator_attrs["viewportPreset"], - "percentSize": creator_attrs["percentSize"], "vpTextures": creator_attrs["vpTexture"], "dspGeometry": attr_values.get("dspGeometry"), "dspShapes": attr_values.get("dspShapes"), @@ -77,7 +77,6 @@ class CollectReview(pyblish.api.InstancePlugin, "UseTextureEnabled": creator_attrs["vpTexture"] } preview_data = { - "percentSize": creator_attrs["percentSize"], "general_viewport": general_viewport, "nitrous_viewport": nitrous_viewport, "vp_btn_mgr": {"EnableButtons": False} diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index ae7744ac19..99dc5c5cdc 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -33,6 +33,7 @@ class ExtractReviewAnimation(publish.Extractor): review_camera, start, end, + percentSize=instance.data["percentSize"], width=instance.data["review_width"], height=instance.data["review_height"], viewport_options=viewport_options) diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index e9d37d0be5..02fa75e032 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -29,6 +29,7 @@ class ExtractThumbnail(publish.Extractor): review_camera, start_frame=frame, end_frame=frame, + percentSize=instance.data["percentSize"], width=instance.data["review_width"], height=instance.data["review_height"], viewport_options=viewport_options) From 75864eee2130068092eac5da6cb8a08ed373819c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 24 Oct 2023 12:55:18 +0800 Subject: [PATCH 36/42] clean up the job_argument code for max 2024 --- openpype/hosts/max/api/preview_animation.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/api/preview_animation.py b/openpype/hosts/max/api/preview_animation.py index eb832c1d1c..1bf99b86d0 100644 --- a/openpype/hosts/max/api/preview_animation.py +++ b/openpype/hosts/max/api/preview_animation.py @@ -108,13 +108,7 @@ def _render_preview_animation_max_2024( filepath = filepath.replace("\\", "/") preview_output = f"{filepath}..{ext}" frame_template = f"{filepath}.{{:04d}}.{ext}" - job_args = list() - default_option = f'CreatePreview filename:"{preview_output}"' - job_args.append(default_option) - output_res_option = f"outputAVI:false percentSize:{percent}" - job_args.append(output_res_option) - frame_range_options = f"start:{start} end:{end}" - job_args.append(frame_range_options) + job_args = [] for key, value in viewport_options.items(): if isinstance(value, bool): if value: @@ -141,10 +135,13 @@ def _render_preview_animation_max_2024( else: value = value.lower() job_args.append(f"{key}: #{value}") - auto_play_option = "autoPlay:false" - job_args.append(auto_play_option) - job_str = " ".join(job_args) - log.debug(job_str) + + job_str = ( + f'CreatePreview filename:"{preview_output}" outputAVI:false ' + f"percentSize:{percent} start:{start} end:{end} " + f"{' '.join(job_args)} " + "autoPlay:false" + ) rt.completeRedraw() rt.execute(job_str) # Return the created files From 439afe4adbbd524b252184cd7c4033b7ad817cb2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 25 Oct 2023 15:21:27 +0800 Subject: [PATCH 37/42] narrow down to the 4 image formats support for review --- openpype/hosts/max/plugins/create/create_review.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index bbcdce90b7..4b1149faa1 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -33,10 +33,7 @@ class CreateReview(plugin.MaxCreator): pre_create_data) def get_instance_attr_defs(self): - image_format_enum = [ - "bmp", "cin", "exr", "jpg", "hdr", "rgb", "png", - "rla", "rpf", "dds", "sgi", "tga", "tif", "vrimg" - ] + image_format_enum = ["exr", "jpg", "png", "tif"] visual_style_preset_enum = [ "Realistic", "Shaded", "Facets", From 76864ad8f5c886804153496e8f49a02ce86b0cd3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 25 Oct 2023 16:56:34 +0800 Subject: [PATCH 38/42] raise publish error in collect review due to the loaded abc camera doesn't support fov attribute properties and narrow down the image format to 3 for reviews --- openpype/hosts/max/plugins/create/create_review.py | 2 +- .../hosts/max/plugins/publish/collect_review.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 4b1149faa1..8052b74f06 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -33,7 +33,7 @@ class CreateReview(plugin.MaxCreator): pre_create_data) def get_instance_attr_defs(self): - image_format_enum = ["exr", "jpg", "png", "tif"] + image_format_enum = ["exr", "jpg", "png"] visual_style_preset_enum = [ "Realistic", "Shaded", "Facets", diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 1f488f8180..beecd391a5 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -5,7 +5,10 @@ import pyblish.api from pymxs import runtime as rt from openpype.lib import BoolDef from openpype.hosts.max.api.lib import get_max_version -from openpype.pipeline.publish import OpenPypePyblishPluginMixin +from openpype.pipeline.publish import ( + OpenPypePyblishPluginMixin, + KnownPublishError +) class CollectReview(pyblish.api.InstancePlugin, @@ -24,7 +27,13 @@ class CollectReview(pyblish.api.InstancePlugin, for node in nodes: if rt.classOf(node) in rt.Camera.classes: camera_name = node.name - focal_length = node.fov + if rt.isProperty(node, "fov"): + focal_length = node.fov + else: + raise KnownPublishError( + "Invalid object found in 'Review' container." + " Only native max Camera supported" + ) creator_attrs = instance.data["creator_attributes"] attr_values = self.get_attr_values_from_data(instance.data) From 26a575d5941df35cb3e08d45275321cab8cc0819 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 25 Oct 2023 17:34:53 +0800 Subject: [PATCH 39/42] improve the code for camera check on the node --- .../max/plugins/publish/collect_review.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index beecd391a5..8bf41c13ab 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -22,18 +22,26 @@ class CollectReview(pyblish.api.InstancePlugin, def process(self, instance): nodes = instance.data["members"] - focal_length = None - camera_name = None - for node in nodes: - if rt.classOf(node) in rt.Camera.classes: - camera_name = node.name - if rt.isProperty(node, "fov"): - focal_length = node.fov - else: - raise KnownPublishError( - "Invalid object found in 'Review' container." - " Only native max Camera supported" - ) + def is_camera(node): + is_camera_class = rt.classOf(node) in rt.Camera.classes + return is_camera_class and rt.isProperty(node, "fov") + + # Use first camera in instance + cameras = [node for node in nodes if is_camera(node)] + if cameras: + if len(cameras) > 1: + self.log.warning( + "Found more than one camera in instance, using first " + f"one found: {cameras[0]}" + ) + camera = cameras[0] + camera_name = camera.name + focal_length = camera.fov + else: + raise KnownPublishError( + "Invalid object found in 'Review' container." + " Only native max Camera supported" + ) creator_attrs = instance.data["creator_attributes"] attr_values = self.get_attr_values_from_data(instance.data) From 8a2e9af88662ef96daec2906ccd9caa5559f0e96 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 25 Oct 2023 17:35:48 +0800 Subject: [PATCH 40/42] hound --- openpype/hosts/max/plugins/publish/collect_review.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 8bf41c13ab..0d48cc1ff3 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -22,6 +22,7 @@ class CollectReview(pyblish.api.InstancePlugin, def process(self, instance): nodes = instance.data["members"] + def is_camera(node): is_camera_class = rt.classOf(node) in rt.Camera.classes return is_camera_class and rt.isProperty(node, "fov") From 40fe7391b2c8470b0880852bf09e95b706e37ed3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 25 Oct 2023 20:46:28 +0800 Subject: [PATCH 41/42] knownPublishError comment tweaks --- openpype/hosts/max/plugins/publish/collect_review.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 0d48cc1ff3..2e3df5b116 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -40,8 +40,9 @@ class CollectReview(pyblish.api.InstancePlugin, focal_length = camera.fov else: raise KnownPublishError( - "Invalid object found in 'Review' container." - " Only native max Camera supported" + "Unable to find a valid camera in 'Review' container." + " Only native max Camera supported. " + f"Found objects: {cameras}" ) creator_attrs = instance.data["creator_attributes"] attr_values = self.get_attr_values_from_data(instance.data) From cd6a2941d2410c2ca314589bae4f79182ccd6f41 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 25 Oct 2023 21:15:52 +0800 Subject: [PATCH 42/42] comment update for knownpublisherror --- openpype/hosts/max/plugins/publish/collect_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 2e3df5b116..b1d9c2d25e 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -42,7 +42,7 @@ class CollectReview(pyblish.api.InstancePlugin, raise KnownPublishError( "Unable to find a valid camera in 'Review' container." " Only native max Camera supported. " - f"Found objects: {cameras}" + f"Found objects: {nodes}" ) creator_attrs = instance.data["creator_attributes"] attr_values = self.get_attr_values_from_data(instance.data)