Cycles: add animation denoising test, fix operator to work with single frames.
[blender.git] / intern / cycles / blender / addon / operators.py
1 #
2 # Copyright 2011-2019 Blender Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16
17 # <pep8 compliant>
18
19 import bpy
20 from bpy.types import Operator
21 from bpy.props import StringProperty
22
23
24 class CYCLES_OT_use_shading_nodes(Operator):
25     """Enable nodes on a material, world or light"""
26     bl_idname = "cycles.use_shading_nodes"
27     bl_label = "Use Nodes"
28
29     @classmethod
30     def poll(cls, context):
31         return (getattr(context, "material", False) or getattr(context, "world", False) or
32                 getattr(context, "light", False))
33
34     def execute(self, context):
35         if context.material:
36             context.material.use_nodes = True
37         elif context.world:
38             context.world.use_nodes = True
39         elif context.light:
40             context.light.use_nodes = True
41
42         return {'FINISHED'}
43
44
45 class CYCLES_OT_denoise_animation(Operator):
46     "Denoise rendered animation sequence using current scene and view " \
47     "layer settings. Requires denoising data passes and output to " \
48     "OpenEXR multilayer files"
49     bl_idname = "cycles.denoise_animation"
50     bl_label = "Denoise Animation"
51
52     input_filepath = StringProperty(
53         name='Input Filepath',
54         description='File path for image to denoise. If not specified, uses the render file path and frame range from the scene',
55         default='',
56         subtype='FILE_PATH')
57
58     output_filepath = StringProperty(
59         name='Output Filepath',
60         description='If not specified, renders will be denoised in-place',
61         default='',
62         subtype='FILE_PATH')
63
64     def execute(self, context):
65         import os
66
67         preferences = context.user_preferences
68         scene = context.scene
69         render_layer = scene.render.layers.active
70
71         in_filepath = self.input_filepath
72         out_filepath = self.output_filepath
73
74         in_filepaths = []
75         out_filepaths = []
76
77         if in_filepath != '':
78             # Denoise a single file
79             if out_filepath == '':
80                 out_filepath = in_filepath
81
82             in_filepaths.append(in_filepath)
83             out_filepaths.append(out_filepath)
84         else:
85             # Denoise animation sequence with expanded frames matching
86             # Blender render output file naming.
87             in_filepath = scene.render.filepath
88             if out_filepath == '':
89                 out_filepath = in_filepath
90
91             # Backup since we will overwrite the scene path temporarily
92             original_filepath = scene.render.filepath
93
94             for frame in range(scene.frame_start, scene.frame_end + 1):
95                 scene.render.filepath = in_filepath
96                 filepath = scene.render.frame_path(frame=frame)
97                 in_filepaths.append(filepath)
98
99                 if not os.path.isfile(filepath):
100                     scene.render.filepath = original_filepath
101                     self.report({'ERROR'}, f"Frame '{filepath}' not found, animation must be complete.")
102                     return {'CANCELLED'}
103
104                 scene.render.filepath = out_filepath
105                 filepath = scene.render.frame_path(frame=frame)
106                 out_filepaths.append(filepath)
107
108             scene.render.filepath = original_filepath
109
110         # Run denoiser
111         # TODO: support cancel and progress reports.
112         import _cycles
113         try:
114             _cycles.denoise(preferences.as_pointer(),
115                             scene.as_pointer(),
116                             render_layer.as_pointer(),
117                             input=in_filepaths,
118                             output=out_filepaths)
119         except Exception as e:
120             self.report({'ERROR'}, str(e))
121             return {'FINISHED'}
122
123         self.report({'INFO'}, "Denoising completed.")
124         return {'FINISHED'}
125
126
127 classes = (
128     CYCLES_OT_use_shading_nodes,
129     CYCLES_OT_denoise_animation
130 )
131
132 def register():
133     from bpy.utils import register_class
134     for cls in classes:
135         register_class(cls)
136
137
138 def unregister():
139     from bpy.utils import unregister_class
140     for cls in classes:
141         unregister_class(cls)