173 lines
5.2 KiB
Python
173 lines
5.2 KiB
Python
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
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 multi-layer 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)
|