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