# SPDX-License-Identifier: Apache-2.0 # Copyright 2011-2022 Blender Foundation from __future__ import annotations import bpy from bpy.types import Operator from bpy.props import StringProperty from bpy.app.translations import pgettext_tip as tip_ class CYCLES_OT_use_shading_nodes(Operator): """Enable nodes on a material, world or light""" bl_idname = "cycles.use_shading_nodes" bl_label = "Use Nodes" @classmethod def poll(cls, context): return (getattr(context, "material", False) or getattr(context, "world", False) or getattr(context, "light", False)) def execute(self, context): if context.material: context.material.use_nodes = True elif context.world: context.world.use_nodes = True elif context.light: context.light.use_nodes = True return {'FINISHED'} class CYCLES_OT_denoise_animation(Operator): "Denoise rendered animation sequence using current scene and view " \ "layer settings. Requires denoising data passes and output to " \ "OpenEXR multilayer files" bl_idname = "cycles.denoise_animation" bl_label = "Denoise Animation" input_filepath: StringProperty( name='Input Filepath', description='File path for image to denoise. If not specified, uses the render file path and frame range from the scene', default='', subtype='FILE_PATH') output_filepath: StringProperty( name='Output Filepath', description='If not specified, renders will be denoised in-place', default='', subtype='FILE_PATH') def execute(self, context): import os preferences = context.preferences scene = context.scene view_layer = context.view_layer in_filepath = self.input_filepath out_filepath = self.output_filepath in_filepaths = [] out_filepaths = [] if in_filepath != '': # Denoise a single file if out_filepath == '': out_filepath = in_filepath in_filepaths.append(in_filepath) out_filepaths.append(out_filepath) else: # Denoise animation sequence with expanded frames matching # Blender render output file naming. in_filepath = scene.render.filepath if out_filepath == '': out_filepath = in_filepath # Backup since we will overwrite the scene path temporarily original_filepath = scene.render.filepath for frame in range(scene.frame_start, scene.frame_end + 1): scene.render.filepath = in_filepath filepath = scene.render.frame_path(frame=frame) in_filepaths.append(filepath) if not os.path.isfile(filepath): scene.render.filepath = original_filepath err_msg = tip_("Frame '%s' not found, animation must be complete") % filepath self.report({'ERROR'}, err_msg) return {'CANCELLED'} scene.render.filepath = out_filepath filepath = scene.render.frame_path(frame=frame) out_filepaths.append(filepath) scene.render.filepath = original_filepath # Run denoiser # TODO: support cancel and progress reports. import _cycles try: _cycles.denoise(preferences.as_pointer(), scene.as_pointer(), view_layer.as_pointer(), input=in_filepaths, output=out_filepaths) except Exception as e: self.report({'ERROR'}, str(e)) return {'FINISHED'} self.report({'INFO'}, "Denoising completed") return {'FINISHED'} class CYCLES_OT_merge_images(Operator): "Combine OpenEXR multilayer images rendered with different sample " \ "ranges into one image with reduced noise" bl_idname = "cycles.merge_images" bl_label = "Merge Images" input_filepath1: StringProperty( name='Input Filepath', description='File path for image to merge', default='', subtype='FILE_PATH') input_filepath2: StringProperty( name='Input Filepath', description='File path for image to merge', default='', subtype='FILE_PATH') output_filepath: StringProperty( name='Output Filepath', description='File path for merged image', default='', subtype='FILE_PATH') def execute(self, context): in_filepaths = [self.input_filepath1, self.input_filepath2] out_filepath = self.output_filepath import _cycles try: _cycles.merge(input=in_filepaths, output=out_filepath) except Exception as e: self.report({'ERROR'}, str(e)) return {'FINISHED'} return {'FINISHED'} classes = ( CYCLES_OT_use_shading_nodes, CYCLES_OT_denoise_animation, CYCLES_OT_merge_images ) def register(): from bpy.utils import register_class for cls in classes: register_class(cls) def unregister(): from bpy.utils import unregister_class for cls in classes: unregister_class(cls)