From b012b0aaeb21d6ffe1e36e90a9743567694c196d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 22 May 2023 20:14:39 +0800 Subject: [PATCH] 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": [],