Cleanup: Store "is_active" instead of pointer property in panel type
[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 from bpy.app.translations import pgettext_tip as tip_
24
25
26 class CYCLES_OT_use_shading_nodes(Operator):
27     """Enable nodes on a material, world or light"""
28     bl_idname = "cycles.use_shading_nodes"
29     bl_label = "Use Nodes"
30
31     @classmethod
32     def poll(cls, context):
33         return (getattr(context, "material", False) or getattr(context, "world", False) or
34                 getattr(context, "light", False))
35
36     def execute(self, context):
37         if context.material:
38             context.material.use_nodes = True
39         elif context.world:
40             context.world.use_nodes = True
41         elif context.light:
42             context.light.use_nodes = True
43
44         return {'FINISHED'}
45
46
47 class CYCLES_OT_add_aov(bpy.types.Operator):
48     """Add an AOV pass"""
49     bl_idname = "cycles.add_aov"
50     bl_label = "Add AOV"
51
52     def execute(self, context):
53         view_layer = context.view_layer
54         cycles_view_layer = view_layer.cycles
55
56         cycles_view_layer.aovs.add()
57
58         view_layer.update_render_passes()
59         return {'FINISHED'}
60
61
62 class CYCLES_OT_remove_aov(bpy.types.Operator):
63     """Remove an AOV pass"""
64     bl_idname = "cycles.remove_aov"
65     bl_label = "Remove AOV"
66
67     def execute(self, context):
68         view_layer = context.view_layer
69         cycles_view_layer = view_layer.cycles
70
71         cycles_view_layer.aovs.remove(cycles_view_layer.active_aov)
72
73         view_layer.update_render_passes()
74         return {'FINISHED'}
75
76
77 class CYCLES_OT_denoise_animation(Operator):
78     "Denoise rendered animation sequence using current scene and view " \
79     "layer settings. Requires denoising data passes and output to " \
80     "OpenEXR multilayer files"
81     bl_idname = "cycles.denoise_animation"
82     bl_label = "Denoise Animation"
83
84     input_filepath: StringProperty(
85         name='Input Filepath',
86         description='File path for image to denoise. If not specified, uses the render file path and frame range from the scene',
87         default='',
88         subtype='FILE_PATH')
89
90     output_filepath: StringProperty(
91         name='Output Filepath',
92         description='If not specified, renders will be denoised in-place',
93         default='',
94         subtype='FILE_PATH')
95
96     def execute(self, context):
97         import os
98
99         preferences = context.preferences
100         scene = context.scene
101         view_layer = context.view_layer
102
103         in_filepath = self.input_filepath
104         out_filepath = self.output_filepath
105
106         in_filepaths = []
107         out_filepaths = []
108
109         if in_filepath != '':
110             # Denoise a single file
111             if out_filepath == '':
112                 out_filepath = in_filepath
113
114             in_filepaths.append(in_filepath)
115             out_filepaths.append(out_filepath)
116         else:
117             # Denoise animation sequence with expanded frames matching
118             # Blender render output file naming.
119             in_filepath = scene.render.filepath
120             if out_filepath == '':
121                 out_filepath = in_filepath
122
123             # Backup since we will overwrite the scene path temporarily
124             original_filepath = scene.render.filepath
125
126             for frame in range(scene.frame_start, scene.frame_end + 1):
127                 scene.render.filepath = in_filepath
128                 filepath = scene.render.frame_path(frame=frame)
129                 in_filepaths.append(filepath)
130
131                 if not os.path.isfile(filepath):
132                     scene.render.filepath = original_filepath
133                     err_msg = tip_("Frame '%s' not found, animation must be complete") % filepath
134                     self.report({'ERROR'}, err_msg)
135                     return {'CANCELLED'}
136
137                 scene.render.filepath = out_filepath
138                 filepath = scene.render.frame_path(frame=frame)
139                 out_filepaths.append(filepath)
140
141             scene.render.filepath = original_filepath
142
143         # Run denoiser
144         # TODO: support cancel and progress reports.
145         import _cycles
146         try:
147             _cycles.denoise(preferences.as_pointer(),
148                             scene.as_pointer(),
149                             view_layer.as_pointer(),
150                             input=in_filepaths,
151                             output=out_filepaths)
152         except Exception as e:
153             self.report({'ERROR'}, str(e))
154             return {'FINISHED'}
155
156         self.report({'INFO'}, "Denoising completed")
157         return {'FINISHED'}
158
159
160 class CYCLES_OT_merge_images(Operator):
161     "Combine OpenEXR multilayer images rendered with different sample " \
162     "ranges into one image with reduced noise"
163     bl_idname = "cycles.merge_images"
164     bl_label = "Merge Images"
165
166     input_filepath1: StringProperty(
167         name='Input Filepath',
168         description='File path for image to merge',
169         default='',
170         subtype='FILE_PATH')
171
172     input_filepath2: StringProperty(
173         name='Input Filepath',
174         description='File path for image to merge',
175         default='',
176         subtype='FILE_PATH')
177
178     output_filepath: StringProperty(
179         name='Output Filepath',
180         description='File path for merged image',
181         default='',
182         subtype='FILE_PATH')
183
184     def execute(self, context):
185         in_filepaths = [self.input_filepath1, self.input_filepath2]
186         out_filepath = self.output_filepath
187
188         import _cycles
189         try:
190             _cycles.merge(input=in_filepaths, output=out_filepath)
191         except Exception as e:
192             self.report({'ERROR'}, str(e))
193             return {'FINISHED'}
194
195         return {'FINISHED'}
196
197
198 classes = (
199     CYCLES_OT_use_shading_nodes,
200     CYCLES_OT_add_aov,
201     CYCLES_OT_remove_aov,
202     CYCLES_OT_denoise_animation,
203     CYCLES_OT_merge_images
204 )
205
206
207 def register():
208     from bpy.utils import register_class
209     for cls in classes:
210         register_class(cls)
211
212
213 def unregister():
214     from bpy.utils import unregister_class
215     for cls in classes:
216         unregister_class(cls)