From b012b0aaeb21d6ffe1e36e90a9743567694c196d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 22 May 2023 20:14:39 +0800 Subject: [PATCH 01/18] implement review in 3dsmax --- openpype/hosts/max/api/lib.py | 5 +- .../hosts/max/plugins/create/create_review.py | 58 ++++++++ .../max/plugins/publish/collect_review.py | 95 ++++++++++++ .../publish/extract_review_animation.py | 135 ++++++++++++++++++ .../publish/validate_camera_contents.py | 2 +- openpype/plugins/publish/extract_burnin.py | 3 +- openpype/plugins/publish/extract_review.py | 1 + .../defaults/project_settings/global.json | 3 +- 8 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 openpype/hosts/max/plugins/create/create_review.py create mode 100644 openpype/hosts/max/plugins/publish/collect_review.py create mode 100644 openpype/hosts/max/plugins/publish/extract_review_animation.py diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index d9213863b1..c1d1f097bd 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -249,10 +249,7 @@ def reset_frame_range(fps: bool = True): frame_range["handleStart"] ) frame_end_handle = frame_range["frameEnd"] + int(frame_range["handleEnd"]) - frange_cmd = ( - f"animationRange = interval {frame_start_handle} {frame_end_handle}" - ) - rt.execute(frange_cmd) + rt.interval(frame_start_handle, frame_end_handle) set_render_frame_range(frame_start_handle, frame_end_handle) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py new file mode 100644 index 0000000000..9939b2e30e --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating review in Max.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import CreatedInstance +from openpype.lib import BoolDef, EnumDef, NumberDef + + +class CreateReview(plugin.MaxCreator): + """Review in 3dsMax""" + + identifier = "io.openpype.creators.max.review" + label = "Review" + family = "review" + 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["rndLevel"] = pre_create_data.get("rndLevel") + + _ = super(CreateReview, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + + def get_pre_create_attr_defs(self): + attrs = super(CreateReview, self).get_pre_create_attr_defs() + + 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" + ] + + return attrs + [ + BoolDef("keepImages", + label="Keep Image Sequences", + default=False), + EnumDef("imageFormat", + image_format_enum, + default="png", + label="Image Format Options"), + NumberDef("percentSize", + label="Percent of Output", + default=100, + minimum=1, + decimals=0), + EnumDef("rndLevel", + rndLevel_enum, + default="png", + label="Preference") + ] diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py new file mode 100644 index 0000000000..916fc60bcc --- /dev/null +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -0,0 +1,95 @@ +# dont forget getting the focal length for burnin +"""Collect Review""" +import pyblish.api + +from pymxs import runtime as rt +from openpype.hosts.max.api.lib import get_all_children +from openpype.lib import BoolDef +from openpype.pipeline.publish import OpenPypePyblishPluginMixin + + +class CollectReview(pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin): + """Collect Review Data for Preview Animation""" + + order = pyblish.api.CollectorOrder + label = "Collect Review Data" + hosts = ['max'] + families = ["review"] + + def process(self, instance): + nodes = get_all_children( + rt.getNodeByName(instance.data["instance_node"])) + focal_length = None + camera = None + for node in nodes: + if rt.classOf(node) in rt.Camera.classes: + rt.viewport.setCamera(node) + camera = node.name + focal_length = node.fov + + attr_values = self.get_attr_values_from_data(instance.data) + data = { + "review_camera": camera, + "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") + } + # Enable ftrack functionality + instance.data.setdefault("families", []).append('ftrack') + + burnin_members = instance.data.setdefault("burninDataMembers", {}) + burnin_members["focalLength"] = focal_length + + self.log.debug(f"data:{data}") + instance.data.update(data) + + @classmethod + def get_attribute_defs(cls): + + return [ + BoolDef("dspGeometry", + label="Geometry", + default=True), + BoolDef("dspShapes", + label="Shapes", + default=False), + BoolDef("dspLights", + label="Lights", + default=False), + BoolDef("dspCameras", + label="Cameras", + default=False), + BoolDef("dspHelpers", + label="Helpers", + default=False), + BoolDef("dspParticles", + label="Particle Systems", + default=True), + BoolDef("dspBones", + label="Bone Objects", + default=False), + BoolDef("dspBkg", + label="Background", + default=True), + BoolDef("dspGrid", + label="Active Grid", + default=False), + BoolDef("dspSafeFrame", + label="Safe Frames", + default=False), + BoolDef("dspFrameNums", + label="Frame Numbers", + default=False) + ] diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py new file mode 100644 index 0000000000..1732a1d69f --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -0,0 +1,135 @@ +import os +import pyblish.api +from openpype.pipeline import publish +from pymxs import runtime as rt + + +class ExtractReviewAnimation(publish.Extractor): + """ + Extract Review by Review Animation + """ + + order = pyblish.api.ExtractorOrder + label = "Extract Review Animation" + hosts = ["max"] + families = ["review"] + + def process(self, instance): + self.log.info("Extracting Review Animation ...") + 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"]) + fps = int(instance.data["fps"]) + filepath = os.path.join(staging_dir, filename) + filepath = filepath.replace("\\", "/") + filenames = self.get_files( + instance.name, start, end, ext) + + self.log.info( + "Writing Review Animation to" + " '%s' to '%s'" % (filename, staging_dir)) + + preview_arg = self.set_preview_arg( + instance, filepath, start, end, fps) + rt.execute(preview_arg) + + tags = ["review"] + if not instance.data.get("keepImages"): + tags.append("delete") + + self.log.info("Performing Extraction ...") + + representation = { + "name": instance.data["imageFormat"], + "ext": instance.data["imageFormat"], + "files": filenames, + "stagingDir": staging_dir, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + "tags": tags, + "preview": True, + "camera_name": instance.data["review_camera"] + } + self.log.debug(f"{representation}") + + 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 + + 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) + rndLevel = instance.data.get("rndLevel") + if rndLevel: + option = f"rndLevel:#{rndLevel}" + job_args.append(option) + percentSize = instance.data.get("percentSize") + if percentSize: + size = int(percentSize) + option = f"percentSize:{size}" + job_args.append(option) + dspGeometry = instance.data.get("dspGeometry") + if dspGeometry: + option = f"dspGeometry:{dspGeometry}" + job_args.append(option) + dspShapes = instance.data.get("dspShapes") + if dspShapes: + option = f"dspShapes:{dspShapes}" + job_args.append(option) + dspLights = instance.data.get("dspLights") + if dspLights: + option = f"dspShapes:{dspLights}" + job_args.append(option) + dspCameras = instance.data.get("dspCameras") + if dspCameras: + option = f"dspCameras:{dspCameras}" + job_args.append(option) + dspHelpers = instance.data.get("dspHelpers") + if dspHelpers: + option = f"dspHelpers:{dspHelpers}" + job_args.append(option) + dspParticles = instance.data.get("dspParticles") + if dspParticles: + option = f"dspParticles:{dspParticles}" + job_args.append(option) + dspBones = instance.data.get("dspBones") + if dspBones: + option = f"dspBones:{dspBones}" + job_args.append(option) + dspBkg = instance.data.get("dspBkg") + if dspBkg: + option = f"dspBkg:{dspBkg}" + job_args.append(option) + dspGrid = instance.data.get("dspGrid") + if dspGrid: + option = f"dspBkg:{dspBkg}" + job_args.append(option) + dspSafeFrame = instance.data.get("dspSafeFrame") + if dspSafeFrame: + option = f"dspSafeFrame:{dspSafeFrame}" + job_args.append(option) + dspFrameNums = instance.data.get("dspFrameNums") + if dspFrameNums: + option = f"dspFrameNums:{dspFrameNums}" + job_args.append(option) + + job_str = " ".join(job_args) + self.log.info(f"{job_str}") + + return job_str diff --git a/openpype/hosts/max/plugins/publish/validate_camera_contents.py b/openpype/hosts/max/plugins/publish/validate_camera_contents.py index c81e28a61f..700966959b 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_contents.py @@ -11,7 +11,7 @@ class ValidateCameraContent(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["camera"] + families = ["camera", "review"] hosts = ["max"] label = "Camera Contents" camera_type = ["$Free_Camera", "$Target_Camera", diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index a12e8d18b4..61961ce4ae 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -50,7 +50,8 @@ class ExtractBurnin(publish.Extractor): "aftereffects", "photoshop", "flame", - "houdini" + "houdini", + "max" # "resolve" ] diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 1062683319..8420bd018f 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -45,6 +45,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "maya", "blender", "houdini", + "max" "shell", "hiero", "premiere", diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 75f335f1de..7d5897b925 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -255,7 +255,8 @@ "families": ["review"], "hosts": [ "maya", - "houdini" + "houdini", + "max" ], "task_types": [], "task_names": [], From 8516172dedbf12b6eef5c0b6ae01ce78fec93133 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 23 May 2023 22:50:27 +0800 Subject: [PATCH 02/18] add comma back to the 'max' --- openpype/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 8420bd018f..d397ce8812 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -45,7 +45,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "maya", "blender", "houdini", - "max" + "max", "shell", "hiero", "premiere", From 930f827036e4905783199e9a04c17576f86c2e18 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 13:33:41 +0800 Subject: [PATCH 03/18] add thumbnail extractor --- .../max/plugins/publish/extract_thumbnail.py | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 openpype/hosts/max/plugins/publish/extract_thumbnail.py diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py new file mode 100644 index 0000000000..5ffeb8c0ca --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -0,0 +1,114 @@ +import os +import pyblish.api +from openpype.pipeline import publish +from pymxs import runtime as rt + + +class ExtractThumbnail(publish.Extractor): + """ + Extract Thumbnail for Review + """ + + order = pyblish.api.ExtractorOrder + label = "Extract Thumbnail" + hosts = ["max"] + families = ["review"] + + def process(self, instance): + self.log.info("Extracting Thumbnail ...") + staging_dir = self.staging_dir(instance) + filename = "{name}..jpg".format(**instance.data) + filepath = os.path.join(staging_dir, filename) + filepath = filepath.replace("\\", "/") + thumbnail = self.get_filename(instance.name) + + self.log.info( + "Writing Thumbnail to" + " '%s' to '%s'" % (filename, staging_dir)) + + preview_arg = self.set_preview_arg( + instance, filepath) + rt.execute(preview_arg) + + representation = { + "name": "thumbnail", + "ext": "jpg", + "files": thumbnail, + "stagingDir": staging_dir, + "thumbnail": True + } + + self.log.debug(f"{representation}") + + if "representations" not in instance.data: + instance.data["representations"] = [] + instance.data["representations"].append(representation) + + def get_filename(self, filename): + return f"{filename}.0001.jpg" + + def set_preview_arg(self, instance, filepath): + job_args = list() + default_option = f'CreatePreview filename:"{filepath}"' + job_args.append(default_option) + + frame_option = f"outputAVI:false start:1 end:1" # noqa + job_args.append(frame_option) + rndLevel = instance.data.get("rndLevel") + if rndLevel: + option = f"rndLevel:#{rndLevel}" + job_args.append(option) + percentSize = instance.data.get("percentSize") + if percentSize: + size = int(percentSize) + option = f"percentSize:{size}" + job_args.append(option) + dspGeometry = instance.data.get("dspGeometry") + if dspGeometry: + option = f"dspGeometry:{dspGeometry}" + job_args.append(option) + dspShapes = instance.data.get("dspShapes") + if dspShapes: + option = f"dspShapes:{dspShapes}" + job_args.append(option) + dspLights = instance.data.get("dspLights") + if dspLights: + option = f"dspShapes:{dspLights}" + job_args.append(option) + dspCameras = instance.data.get("dspCameras") + if dspCameras: + option = f"dspCameras:{dspCameras}" + job_args.append(option) + dspHelpers = instance.data.get("dspHelpers") + if dspHelpers: + option = f"dspHelpers:{dspHelpers}" + job_args.append(option) + dspParticles = instance.data.get("dspParticles") + if dspParticles: + option = f"dspParticles:{dspParticles}" + job_args.append(option) + dspBones = instance.data.get("dspBones") + if dspBones: + option = f"dspBones:{dspBones}" + job_args.append(option) + dspBkg = instance.data.get("dspBkg") + if dspBkg: + option = f"dspBkg:{dspBkg}" + job_args.append(option) + dspGrid = instance.data.get("dspGrid") + if dspGrid: + option = f"dspBkg:{dspBkg}" + job_args.append(option) + dspSafeFrame = instance.data.get("dspSafeFrame") + if dspSafeFrame: + option = f"dspSafeFrame:{dspSafeFrame}" + job_args.append(option) + dspFrameNums = instance.data.get("dspFrameNums") + if dspFrameNums: + option = f"dspFrameNums:{dspFrameNums}" + job_args.append(option) + + job_str = " ".join(job_args) + self.log.info(f"{job_str}") + + return job_str From 277fd3e3423f636240867d81167f0c2c63e22b48 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 15:09:09 +0800 Subject: [PATCH 04/18] create temp directory for thumbnail --- .../max/plugins/publish/extract_thumbnail.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 5ffeb8c0ca..8c78f972f7 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -1,4 +1,5 @@ import os +import tempfile import pyblish.api from openpype.pipeline import publish from pymxs import runtime as rt @@ -16,15 +17,22 @@ class ExtractThumbnail(publish.Extractor): def process(self, instance): self.log.info("Extracting Thumbnail ...") - staging_dir = self.staging_dir(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" + ) + instance.context.data["cleanupFullPaths"].append(tmp_staging) filename = "{name}..jpg".format(**instance.data) - filepath = os.path.join(staging_dir, filename) + filepath = os.path.join(tmp_staging, filename) filepath = filepath.replace("\\", "/") thumbnail = self.get_filename(instance.name) self.log.info( "Writing Thumbnail to" - " '%s' to '%s'" % (filename, staging_dir)) + " '%s' to '%s'" % (filename, tmp_staging)) preview_arg = self.set_preview_arg( instance, filepath) @@ -34,7 +42,7 @@ class ExtractThumbnail(publish.Extractor): "name": "thumbnail", "ext": "jpg", "files": thumbnail, - "stagingDir": staging_dir, + "stagingDir": tmp_staging, "thumbnail": True } From 8abfa57b1be9506fbb3a2599c1317d0f96fca275 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Jun 2023 18:26:43 +0800 Subject: [PATCH 05/18] roy's comment --- .../publish/extract_review_animation.py | 63 +++---------------- 1 file changed, 9 insertions(+), 54 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 1732a1d69f..8d7dfdeea9 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -72,63 +72,18 @@ class ExtractReviewAnimation(publish.Extractor): job_args = list() default_option = f'CreatePreview filename:"{filepath}"' job_args.append(default_option) + options = [ + "rndLevel", "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}") 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) - percentSize = instance.data.get("percentSize") - if percentSize: - size = int(percentSize) - option = f"percentSize:{size}" - job_args.append(option) - dspGeometry = instance.data.get("dspGeometry") - if dspGeometry: - option = f"dspGeometry:{dspGeometry}" - job_args.append(option) - dspShapes = instance.data.get("dspShapes") - if dspShapes: - option = f"dspShapes:{dspShapes}" - job_args.append(option) - dspLights = instance.data.get("dspLights") - if dspLights: - option = f"dspShapes:{dspLights}" - job_args.append(option) - dspCameras = instance.data.get("dspCameras") - if dspCameras: - option = f"dspCameras:{dspCameras}" - job_args.append(option) - dspHelpers = instance.data.get("dspHelpers") - if dspHelpers: - option = f"dspHelpers:{dspHelpers}" - job_args.append(option) - dspParticles = instance.data.get("dspParticles") - if dspParticles: - option = f"dspParticles:{dspParticles}" - job_args.append(option) - dspBones = instance.data.get("dspBones") - if dspBones: - option = f"dspBones:{dspBones}" - job_args.append(option) - dspBkg = instance.data.get("dspBkg") - if dspBkg: - option = f"dspBkg:{dspBkg}" - job_args.append(option) - dspGrid = instance.data.get("dspGrid") - if dspGrid: - option = f"dspBkg:{dspBkg}" - job_args.append(option) - dspSafeFrame = instance.data.get("dspSafeFrame") - if dspSafeFrame: - option = f"dspSafeFrame:{dspSafeFrame}" - job_args.append(option) - dspFrameNums = instance.data.get("dspFrameNums") - if dspFrameNums: - option = f"dspFrameNums:{dspFrameNums}" - job_args.append(option) - job_str = " ".join(job_args) self.log.info(f"{job_str}") From e2d35dedb961fc5bc0495072be1371ffe3cd22e0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Jun 2023 18:28:29 +0800 Subject: [PATCH 06/18] hound fix --- 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 8d7dfdeea9..940068cc51 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -79,7 +79,7 @@ class ExtractReviewAnimation(publish.Extractor): ] for key in options: - enabled= instance.data.get(key) + enabled = instance.data.get(key) if enabled: job_args.append(f"{key}:{enabled}") frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa From 41dd7e06f951d42ccc1d305647840ba07d545416 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Jun 2023 19:33:40 +0800 Subject: [PATCH 07/18] update the args for review animation --- openpype/hosts/max/plugins/create/create_review.py | 2 +- .../max/plugins/publish/extract_review_animation.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index 9939b2e30e..aec70dbe15 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -53,6 +53,6 @@ class CreateReview(plugin.MaxCreator): decimals=0), EnumDef("rndLevel", rndLevel_enum, - default="png", + default="smoothhighlights", label="Preference") ] diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 940068cc51..fc70701eba 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -9,7 +9,7 @@ class ExtractReviewAnimation(publish.Extractor): Extract Review by Review Animation """ - order = pyblish.api.ExtractorOrder + order = pyblish.api.ExtractorOrder + 0.001 label = "Extract Review Animation" hosts = ["max"] families = ["review"] @@ -72,8 +72,14 @@ class ExtractReviewAnimation(publish.Extractor): 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) + rndLevel = instance.data.get("rndLevel") + if rndLevel: + option = f"rndLevel:#{rndLevel}" + job_args.append(option) options = [ - "rndLevel", "percentSize", "dspGeometry", "dspShapes", + "percentSize", "dspGeometry", "dspShapes", "dspLights", "dspCameras", "dspHelpers", "dspParticles", "dspBones", "dspBkg", "dspGrid", "dspSafeFrame", "dspFrameNums" ] @@ -82,8 +88,6 @@ class ExtractReviewAnimation(publish.Extractor): enabled = instance.data.get(key) if enabled: job_args.append(f"{key}:{enabled}") - frame_option = f"outputAVI:false start:{start} end:{end} fps:{fps}" # noqa - job_args.append(frame_option) job_str = " ".join(job_args) self.log.info(f"{job_str}") From 9fd1321cb9860af5bf0f157b1e5a88199ea909b3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 22 Jun 2023 21:16:14 +0800 Subject: [PATCH 08/18] roy's comment and make sure fps for preview animation in thumbnail extractor is the same as preview animation --- .../publish/extract_review_animation.py | 7 +- .../max/plugins/publish/extract_thumbnail.py | 76 +++++-------------- 2 files changed, 20 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index fc70701eba..98ffa5c1d3 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -15,7 +15,6 @@ class ExtractReviewAnimation(publish.Extractor): families = ["review"] def process(self, instance): - self.log.info("Extracting Review Animation ...") staging_dir = self.staging_dir(instance) ext = instance.data.get("imageFormat") filename = "{0}..{1}".format(instance.name, ext) @@ -27,7 +26,7 @@ class ExtractReviewAnimation(publish.Extractor): filenames = self.get_files( instance.name, start, end, ext) - self.log.info( + self.log.debug( "Writing Review Animation to" " '%s' to '%s'" % (filename, staging_dir)) @@ -39,7 +38,7 @@ class ExtractReviewAnimation(publish.Extractor): if not instance.data.get("keepImages"): tags.append("delete") - self.log.info("Performing Extraction ...") + self.log.debug("Performing Extraction ...") representation = { "name": instance.data["imageFormat"], @@ -89,6 +88,6 @@ class ExtractReviewAnimation(publish.Extractor): if enabled: job_args.append(f"{key}:{enabled}") job_str = " ".join(job_args) - self.log.info(f"{job_str}") + self.log.debug(job_str) return job_str diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 8c78f972f7..3f3a804250 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -16,26 +16,25 @@ class ExtractThumbnail(publish.Extractor): families = ["review"] def process(self, instance): - self.log.info("Extracting Thumbnail ...") - # 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" ) + fps = int(instance.data["fps"]) instance.context.data["cleanupFullPaths"].append(tmp_staging) - filename = "{name}..jpg".format(**instance.data) + filename = "{name}_thumbnail..jpg".format(**instance.data) filepath = os.path.join(tmp_staging, filename) filepath = filepath.replace("\\", "/") thumbnail = self.get_filename(instance.name) - self.log.info( + self.log.debug( "Writing Thumbnail to" " '%s' to '%s'" % (filename, tmp_staging)) preview_arg = self.set_preview_arg( - instance, filepath) + instance, filepath, fps) rt.execute(preview_arg) representation = { @@ -53,70 +52,29 @@ class ExtractThumbnail(publish.Extractor): instance.data["representations"].append(representation) def get_filename(self, filename): - return f"{filename}.0001.jpg" + return f"{filename}_thumbnail.0001.jpg" - def set_preview_arg(self, instance, filepath): + def set_preview_arg(self, instance, filepath, fps): job_args = list() default_option = f'CreatePreview filename:"{filepath}"' job_args.append(default_option) - - frame_option = f"outputAVI:false start:1 end:1" # noqa + frame_option = f"outputAVI:false start:1 end:1 fps:{fps}" # noqa job_args.append(frame_option) rndLevel = instance.data.get("rndLevel") if rndLevel: option = f"rndLevel:#{rndLevel}" job_args.append(option) - percentSize = instance.data.get("percentSize") - if percentSize: - size = int(percentSize) - option = f"percentSize:{size}" - job_args.append(option) - dspGeometry = instance.data.get("dspGeometry") - if dspGeometry: - option = f"dspGeometry:{dspGeometry}" - job_args.append(option) - dspShapes = instance.data.get("dspShapes") - if dspShapes: - option = f"dspShapes:{dspShapes}" - job_args.append(option) - dspLights = instance.data.get("dspLights") - if dspLights: - option = f"dspShapes:{dspLights}" - job_args.append(option) - dspCameras = instance.data.get("dspCameras") - if dspCameras: - option = f"dspCameras:{dspCameras}" - job_args.append(option) - dspHelpers = instance.data.get("dspHelpers") - if dspHelpers: - option = f"dspHelpers:{dspHelpers}" - job_args.append(option) - dspParticles = instance.data.get("dspParticles") - if dspParticles: - option = f"dspParticles:{dspParticles}" - job_args.append(option) - dspBones = instance.data.get("dspBones") - if dspBones: - option = f"dspBones:{dspBones}" - job_args.append(option) - dspBkg = instance.data.get("dspBkg") - if dspBkg: - option = f"dspBkg:{dspBkg}" - job_args.append(option) - dspGrid = instance.data.get("dspGrid") - if dspGrid: - option = f"dspBkg:{dspBkg}" - job_args.append(option) - dspSafeFrame = instance.data.get("dspSafeFrame") - if dspSafeFrame: - option = f"dspSafeFrame:{dspSafeFrame}" - job_args.append(option) - dspFrameNums = instance.data.get("dspFrameNums") - if dspFrameNums: - option = f"dspFrameNums:{dspFrameNums}" - 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}") job_str = " ".join(job_args) - self.log.info(f"{job_str}") + self.log.debug(job_str) return job_str From 45f02e8db3ed462b914c846de7042803f446da30 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 22 Jun 2023 22:54:15 +0800 Subject: [PATCH 09/18] roy's comment --- openpype/hosts/max/api/lib.py | 10 ++++++++++ openpype/hosts/max/plugins/create/create_review.py | 4 ++-- .../hosts/max/plugins/publish/collect_review.py | 7 +++---- .../max/plugins/publish/extract_review_animation.py | 13 ++++++++----- .../hosts/max/plugins/publish/extract_thumbnail.py | 7 ++++--- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 161c73e9a4..83bc597be2 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -282,3 +282,13 @@ def get_max_version(): """ max_info = rt.MaxVersion() return max_info[7] + +@contextlib.contextmanager +def viewport_camera(camera): + original = rt.viewport.getCamera() + review_camera = rt.getNodeByName(camera) + try: + rt.viewport.setCamera(review_camera) + yield + finally: + rt.viewport.setCamera(original) diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index aec70dbe15..d5fc31ce50 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -20,10 +20,10 @@ class CreateReview(plugin.MaxCreator): instance_data["percentSize"] = pre_create_data.get("percentSize") instance_data["rndLevel"] = pre_create_data.get("rndLevel") - _ = super(CreateReview, self).create( + super(CreateReview, self).create( subset_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) def get_pre_create_attr_defs(self): attrs = super(CreateReview, self).get_pre_create_attr_defs() diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index b2a187116a..7aeb45f46b 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -19,16 +19,15 @@ class CollectReview(pyblish.api.InstancePlugin, def process(self, instance): nodes = instance.data["members"] focal_length = None - camera = None + camera_name = None for node in nodes: if rt.classOf(node) in rt.Camera.classes: - rt.viewport.setCamera(node) - camera = node.name + camera_name = node.name focal_length = node.fov attr_values = self.get_attr_values_from_data(instance.data) data = { - "review_camera": camera, + "review_camera": camera_name, "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 98ffa5c1d3..4e19daddf4 100644 --- a/openpype/hosts/max/plugins/publish/extract_review_animation.py +++ b/openpype/hosts/max/plugins/publish/extract_review_animation.py @@ -1,7 +1,8 @@ import os import pyblish.api -from openpype.pipeline import publish from pymxs import runtime as rt +from openpype.pipeline import publish +from openpype.hosts.max.api.lib import viewport_camera class ExtractReviewAnimation(publish.Extractor): @@ -30,9 +31,11 @@ class ExtractReviewAnimation(publish.Extractor): "Writing Review Animation to" " '%s' to '%s'" % (filename, staging_dir)) - preview_arg = self.set_preview_arg( - instance, filepath, start, end, fps) - rt.execute(preview_arg) + 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) tags = ["review"] if not instance.data.get("keepImages"): @@ -49,7 +52,7 @@ class ExtractReviewAnimation(publish.Extractor): "frameEnd": instance.data["frameEnd"], "tags": tags, "preview": True, - "camera_name": instance.data["review_camera"] + "camera_name": review_camera } self.log.debug(f"{representation}") diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 3f3a804250..faa09bdad9 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -23,6 +23,7 @@ class ExtractThumbnail(publish.Extractor): f"Create temp directory {tmp_staging} for thumbnail" ) fps = int(instance.data["fps"]) + frame = int(instance.data["frameStart"]) instance.context.data["cleanupFullPaths"].append(tmp_staging) filename = "{name}_thumbnail..jpg".format(**instance.data) filepath = os.path.join(tmp_staging, filename) @@ -34,7 +35,7 @@ class ExtractThumbnail(publish.Extractor): " '%s' to '%s'" % (filename, tmp_staging)) preview_arg = self.set_preview_arg( - instance, filepath, fps) + instance, filepath, fps, frame) rt.execute(preview_arg) representation = { @@ -54,11 +55,11 @@ class ExtractThumbnail(publish.Extractor): def get_filename(self, filename): return f"{filename}_thumbnail.0001.jpg" - def set_preview_arg(self, instance, filepath, fps): + 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:1 end:1 fps:{fps}" # noqa + frame_option = f"outputAVI:false start:{frame} end:{frame} fps:{fps}" # noqa job_args.append(frame_option) rndLevel = instance.data.get("rndLevel") if rndLevel: From 5210d9bde3e787929d1bf319dd3ef7532261981d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 22 Jun 2023 22:55:24 +0800 Subject: [PATCH 10/18] hound fix --- openpype/hosts/max/api/lib.py | 1 + openpype/hosts/max/plugins/create/create_review.py | 1 - openpype/hosts/max/plugins/publish/collect_review.py | 3 +++ openpype/hosts/max/plugins/publish/extract_thumbnail.py | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 83bc597be2..88f1b35a14 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -283,6 +283,7 @@ def get_max_version(): max_info = rt.MaxVersion() return max_info[7] + @contextlib.contextmanager def viewport_camera(camera): original = rt.viewport.getCamera() diff --git a/openpype/hosts/max/plugins/create/create_review.py b/openpype/hosts/max/plugins/create/create_review.py index d5fc31ce50..5737114fcc 100644 --- a/openpype/hosts/max/plugins/create/create_review.py +++ b/openpype/hosts/max/plugins/create/create_review.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating review in Max.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance from openpype.lib import BoolDef, EnumDef, NumberDef diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 7aeb45f46b..5b01c7ddf7 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -20,13 +20,16 @@ class CollectReview(pyblish.api.InstancePlugin, nodes = instance.data["members"] focal_length = None camera_name = None + camera = None for node in nodes: if rt.classOf(node) in rt.Camera.classes: + camera = node camera_name = node.name focal_length = node.fov attr_values = self.get_attr_values_from_data(instance.data) data = { + "camera_node": camera, "review_camera": camera_name, "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index faa09bdad9..8de15a00d4 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -14,6 +14,7 @@ class ExtractThumbnail(publish.Extractor): label = "Extract Thumbnail" hosts = ["max"] families = ["review"] + start def process(self, instance): # TODO: Create temp directory for thumbnail From 173845859e171d86443e596af44f8caec0482722 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 22 Jun 2023 22:56:41 +0800 Subject: [PATCH 11/18] hound fix --- openpype/hosts/max/plugins/publish/extract_thumbnail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 8de15a00d4..faa09bdad9 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -14,7 +14,6 @@ class ExtractThumbnail(publish.Extractor): label = "Extract Thumbnail" hosts = ["max"] families = ["review"] - start def process(self, instance): # TODO: Create temp directory for thumbnail From 991bcec434c6d26bd0618c2193839af21ac26995 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 23 Jun 2023 15:26:57 +0800 Subject: [PATCH 12/18] add viewport_function into thumbnail extractor --- .../hosts/max/plugins/publish/extract_thumbnail.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index faa09bdad9..33f705fefd 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -1,8 +1,9 @@ import os import tempfile import pyblish.api -from openpype.pipeline import publish from pymxs import runtime as rt +from openpype.pipeline import publish +from openpype.hosts.max.api.lib import viewport_camera class ExtractThumbnail(publish.Extractor): @@ -33,10 +34,11 @@ class ExtractThumbnail(publish.Extractor): self.log.debug( "Writing Thumbnail to" " '%s' to '%s'" % (filename, tmp_staging)) - - preview_arg = self.set_preview_arg( - instance, filepath, fps, frame) - rt.execute(preview_arg) + review_camera = instance.data["review_camera"] + with viewport_camera(review_camera): + preview_arg = self.set_preview_arg( + instance, filepath, fps, frame) + rt.execute(preview_arg) representation = { "name": "thumbnail", From ab1cdf5cef7bff2895f1cd0ca132f81f66b64bdd Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 23 Jun 2023 17:14:07 +0800 Subject: [PATCH 13/18] add validation to make sure there must be camera instance included --- .../hosts/max/plugins/publish/validate_camera_contents.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_contents.py b/openpype/hosts/max/plugins/publish/validate_camera_contents.py index 0c61e6431d..4a09f415e1 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_contents.py @@ -18,6 +18,10 @@ class ValidateCameraContent(pyblish.api.InstancePlugin): "$Physical_Camera", "$Target"] def process(self, instance): + selection_list = instance.data["members"] + if not selection_list: + raise PublishValidationError("No camera instance found..") + invalid = self.get_invalid(instance) if invalid: raise PublishValidationError(("Camera instance must only include" From 44482c51a673cc63b9f3009fe4490956dce28b6e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 23 Jun 2023 17:30:17 +0800 Subject: [PATCH 14/18] add review instance into no max content --- .../hosts/max/plugins/publish/validate_camera_contents.py | 4 ---- openpype/hosts/max/plugins/publish/validate_no_max_content.py | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_contents.py b/openpype/hosts/max/plugins/publish/validate_camera_contents.py index 4a09f415e1..0c61e6431d 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_contents.py @@ -18,10 +18,6 @@ class ValidateCameraContent(pyblish.api.InstancePlugin): "$Physical_Camera", "$Target"] def process(self, instance): - selection_list = instance.data["members"] - if not selection_list: - raise PublishValidationError("No camera instance found..") - invalid = self.get_invalid(instance) if invalid: raise PublishValidationError(("Camera instance must only include" diff --git a/openpype/hosts/max/plugins/publish/validate_no_max_content.py b/openpype/hosts/max/plugins/publish/validate_no_max_content.py index ba4a6882c2..c6a27dace3 100644 --- a/openpype/hosts/max/plugins/publish/validate_no_max_content.py +++ b/openpype/hosts/max/plugins/publish/validate_no_max_content.py @@ -13,7 +13,8 @@ class ValidateMaxContents(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder families = ["camera", "maxScene", - "maxrender"] + "maxrender", + "review"] hosts = ["max"] label = "Max Scene Contents" From c2faeb251f6789701d2d9b1bdcedad397432bbc1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 23 Jun 2023 17:33:59 +0800 Subject: [PATCH 15/18] remove adding unused instance data --- openpype/hosts/max/plugins/publish/collect_review.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_review.py b/openpype/hosts/max/plugins/publish/collect_review.py index 5b01c7ddf7..7aeb45f46b 100644 --- a/openpype/hosts/max/plugins/publish/collect_review.py +++ b/openpype/hosts/max/plugins/publish/collect_review.py @@ -20,16 +20,13 @@ class CollectReview(pyblish.api.InstancePlugin, nodes = instance.data["members"] focal_length = None camera_name = None - camera = None for node in nodes: if rt.classOf(node) in rt.Camera.classes: - camera = node camera_name = node.name focal_length = node.fov attr_values = self.get_attr_values_from_data(instance.data) data = { - "camera_node": camera, "review_camera": camera_name, "frameStart": instance.context.data["frameStart"], "frameEnd": instance.context.data["frameEnd"], From c00f1b6449a0afdf636a776fa3c8eaee7f8e96d5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 24 Jun 2023 13:54:10 +0800 Subject: [PATCH 16/18] remove hardcoded part from thumbnail extractor and add timeline validator for max review --- openpype/hosts/max/api/lib.py | 13 ++++- .../max/plugins/publish/extract_thumbnail.py | 11 +++-- .../publish/validate_animation_timeline.py | 47 +++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 openpype/hosts/max/plugins/publish/validate_animation_timeline.py diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 88f1b35a14..995c35792a 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -250,7 +250,7 @@ def reset_frame_range(fps: bool = True): frame_range["handleStart"] ) frame_end_handle = frame_range["frameEnd"] + int(frame_range["handleEnd"]) - rt.interval(frame_start_handle, frame_end_handle) + set_timeline(frame_start_handle, frame_end_handle) set_render_frame_range(frame_start_handle, frame_end_handle) @@ -287,9 +287,20 @@ def get_max_version(): @contextlib.contextmanager def viewport_camera(camera): original = rt.viewport.getCamera() + 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) yield finally: rt.viewport.setCamera(original) + + +def set_timeline(frameStart, frameEnd): + """Set frame range for timeline editor in Max + """ + rt.animationRange = rt.interval(frameStart, frameEnd) + return rt.animationRange diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index 33f705fefd..cc943f388f 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -29,7 +29,7 @@ class ExtractThumbnail(publish.Extractor): filename = "{name}_thumbnail..jpg".format(**instance.data) filepath = os.path.join(tmp_staging, filename) filepath = filepath.replace("\\", "/") - thumbnail = self.get_filename(instance.name) + thumbnail = self.get_filename(instance.name, frame) self.log.debug( "Writing Thumbnail to" @@ -42,7 +42,7 @@ class ExtractThumbnail(publish.Extractor): representation = { "name": "thumbnail", - "ext": "jpg", + "ext": "png", "files": thumbnail, "stagingDir": tmp_staging, "thumbnail": True @@ -54,8 +54,11 @@ class ExtractThumbnail(publish.Extractor): instance.data["representations"] = [] instance.data["representations"].append(representation) - def get_filename(self, filename): - return f"{filename}_thumbnail.0001.jpg" + def get_filename(self, filename, target_frame): + thumbnail_name = "{}_thumbnail.{:04}.png".format( + filename, target_frame + ) + return thumbnail_name def set_preview_arg(self, instance, filepath, fps, frame): job_args = list() diff --git a/openpype/hosts/max/plugins/publish/validate_animation_timeline.py b/openpype/hosts/max/plugins/publish/validate_animation_timeline.py new file mode 100644 index 0000000000..249451680c --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_animation_timeline.py @@ -0,0 +1,47 @@ +import pyblish.api + +from pymxs import runtime as rt +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError +) +from openpype.hosts.max.api.lib import get_frame_range, set_timeline + + +class ValidateAnimationTimeline(pyblish.api.InstancePlugin): + """ + Validates Animation Timeline for Preview Animation in Max + """ + + label = "Animation Timeline for Review" + order = ValidateContentsOrder + families = ["review"] + hosts = ["max"] + actions = [RepairAction] + + def process(self, instance): + frame_range = get_frame_range() + frame_start_handle = frame_range["frameStart"] - int( + frame_range["handleStart"] + ) + frame_end_handle = frame_range["frameEnd"] + int( + frame_range["handleEnd"] + ) + if rt.animationRange != rt.interval( + frame_start_handle, frame_end_handle): + raise PublishValidationError("Incorrect animation timeline" + "set for preview animation.. " + "\nYou can use repair action to " + "the correct animation timeline") + + @classmethod + def repair(cls, instance): + frame_range = get_frame_range() + frame_start_handle = frame_range["frameStart"] - int( + frame_range["handleStart"] + ) + frame_end_handle = frame_range["frameEnd"] + int( + frame_range["handleEnd"] + ) + set_timeline(frame_start_handle, frame_end_handle) From 5abbceeace24f3b183507d6d91f8aa7a4cb6bd95 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 24 Jun 2023 13:55:20 +0800 Subject: [PATCH 17/18] hound fix --- .../hosts/max/plugins/publish/validate_animation_timeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_animation_timeline.py b/openpype/hosts/max/plugins/publish/validate_animation_timeline.py index 249451680c..8e49c94aa7 100644 --- a/openpype/hosts/max/plugins/publish/validate_animation_timeline.py +++ b/openpype/hosts/max/plugins/publish/validate_animation_timeline.py @@ -28,8 +28,8 @@ class ValidateAnimationTimeline(pyblish.api.InstancePlugin): frame_end_handle = frame_range["frameEnd"] + int( frame_range["handleEnd"] ) - if rt.animationRange != rt.interval( - frame_start_handle, frame_end_handle): + if rt.animationRange != rt.interval(frame_start_handle, + frame_end_handle): raise PublishValidationError("Incorrect animation timeline" "set for preview animation.. " "\nYou can use repair action to " From 0d590cb5728e37087c1dcbadbc4a47abe0b0038b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 24 Jun 2023 17:17:42 +0800 Subject: [PATCH 18/18] viewer not popup in the max2024 but will popup in max 2023 --- .../max/plugins/publish/extract_review_animation.py | 8 +++++++- openpype/hosts/max/plugins/publish/extract_thumbnail.py | 9 +++++++-- .../max/plugins/publish/validate_animation_timeline.py | 7 ++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_review_animation.py b/openpype/hosts/max/plugins/publish/extract_review_animation.py index 4e19daddf4..8e06e52b5c 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 pymxs import runtime as rt from openpype.pipeline import publish -from openpype.hosts.max.api.lib import viewport_camera +from openpype.hosts.max.api.lib import viewport_camera, get_max_version class ExtractReviewAnimation(publish.Extractor): @@ -90,6 +90,12 @@ 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) + job_str = " ".join(job_args) self.log.debug(job_str) diff --git a/openpype/hosts/max/plugins/publish/extract_thumbnail.py b/openpype/hosts/max/plugins/publish/extract_thumbnail.py index cc943f388f..82f4fc7a8b 100644 --- a/openpype/hosts/max/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/max/plugins/publish/extract_thumbnail.py @@ -3,7 +3,7 @@ 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 +from openpype.hosts.max.api.lib import viewport_camera, get_max_version class ExtractThumbnail(publish.Extractor): @@ -26,7 +26,7 @@ class ExtractThumbnail(publish.Extractor): fps = int(instance.data["fps"]) frame = int(instance.data["frameStart"]) instance.context.data["cleanupFullPaths"].append(tmp_staging) - filename = "{name}_thumbnail..jpg".format(**instance.data) + filename = "{name}_thumbnail..png".format(**instance.data) filepath = os.path.join(tmp_staging, filename) filepath = filepath.replace("\\", "/") thumbnail = self.get_filename(instance.name, frame) @@ -80,6 +80,11 @@ class ExtractThumbnail(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) + job_str = " ".join(job_args) self.log.debug(job_str) diff --git a/openpype/hosts/max/plugins/publish/validate_animation_timeline.py b/openpype/hosts/max/plugins/publish/validate_animation_timeline.py index 8e49c94aa7..2a9483c763 100644 --- a/openpype/hosts/max/plugins/publish/validate_animation_timeline.py +++ b/openpype/hosts/max/plugins/publish/validate_animation_timeline.py @@ -28,9 +28,10 @@ class ValidateAnimationTimeline(pyblish.api.InstancePlugin): frame_end_handle = frame_range["frameEnd"] + int( frame_range["handleEnd"] ) - if rt.animationRange != rt.interval(frame_start_handle, - frame_end_handle): - raise PublishValidationError("Incorrect animation timeline" + if rt.animationRange.start != frame_start_handle or ( + rt.animationRange.end != frame_end_handle + ): + raise PublishValidationError("Incorrect animation timeline " "set for preview animation.. " "\nYou can use repair action to " "the correct animation timeline")