1# ##### BEGIN GPL LICENSE BLOCK #####
2
3#
4#  This program is free software; you can redistribute it and/or
5#  modify it under the terms of the GNU General Public License
6#  as published by the Free Software Foundation; either version 2
7#  of the License, or (at your option) any later version.
8#
9#  This program is distributed in the hope that it will be useful,
10#  but WITHOUT ANY WARRANTY; without even the implied warranty of
11#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12#  GNU General Public License for more details.
13#
14#  You should have received a copy of the GNU General Public License
15#  along with this program; if not, write to the Free Software Foundation,
16#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17#
18# ##### END GPL LICENSE BLOCK #####
19
20# <pep8 compliant>
21import bpy
22from bpy.types import Menu, Panel, UIList
23from bl_ui.utils import PresetPanel
24
25from bpy.app.translations import pgettext_tip as tip_
26
27
28class RENDER_PT_presets(PresetPanel, Panel):
29    bl_label = "Render Presets"
30    preset_subdir = "render"
31    preset_operator = "script.execute_preset"
32    preset_add_operator = "render.preset_add"
33
34
35class RENDER_PT_ffmpeg_presets(PresetPanel, Panel):
36    bl_label = "FFMPEG Presets"
37    preset_subdir = "ffmpeg"
38    preset_operator = "script.python_file_run"
39
40
41class RENDER_MT_framerate_presets(Menu):
42    bl_label = "Frame Rate Presets"
43    preset_subdir = "framerate"
44    preset_operator = "script.execute_preset"
45    draw = Menu.draw_preset
46
47
48class RenderOutputButtonsPanel:
49    bl_space_type = 'PROPERTIES'
50    bl_region_type = 'WINDOW'
51    bl_context = "output"
52    # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
53
54    @classmethod
55    def poll(cls, context):
56        return (context.engine in cls.COMPAT_ENGINES)
57
58
59class RENDER_PT_dimensions(RenderOutputButtonsPanel, Panel):
60    bl_label = "Dimensions"
61    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
62
63    _frame_rate_args_prev = None
64    _preset_class = None
65
66    def draw_header_preset(self, _context):
67        RENDER_PT_presets.draw_panel_header(self.layout)
68
69    @staticmethod
70    def _draw_framerate_label(*args):
71        # avoids re-creating text string each draw
72        if RENDER_PT_dimensions._frame_rate_args_prev == args:
73            return RENDER_PT_dimensions._frame_rate_ret
74
75        fps, fps_base, preset_label = args
76
77        if fps_base == 1.0:
78            fps_rate = round(fps)
79        else:
80            fps_rate = round(fps / fps_base, 2)
81
82        # TODO: Change the following to iterate over existing presets
83        custom_framerate = (fps_rate not in {23.98, 24, 25, 29.97, 30, 50, 59.94, 60})
84
85        if custom_framerate is True:
86            fps_label_text = tip_("Custom (%.4g fps)") % fps_rate
87            show_framerate = True
88        else:
89            fps_label_text = tip_("%.4g fps") % fps_rate
90            show_framerate = (preset_label == "Custom")
91
92        RENDER_PT_dimensions._frame_rate_args_prev = args
93        RENDER_PT_dimensions._frame_rate_ret = args = (fps_label_text, show_framerate)
94        return args
95
96    @staticmethod
97    def draw_framerate(layout, rd):
98        if RENDER_PT_dimensions._preset_class is None:
99            RENDER_PT_dimensions._preset_class = bpy.types.RENDER_MT_framerate_presets
100
101        args = rd.fps, rd.fps_base, RENDER_PT_dimensions._preset_class.bl_label
102        fps_label_text, show_framerate = RENDER_PT_dimensions._draw_framerate_label(*args)
103
104        layout.menu("RENDER_MT_framerate_presets", text=fps_label_text)
105
106        if show_framerate:
107            col = layout.column(align=True)
108            col.prop(rd, "fps")
109            col.prop(rd, "fps_base", text="Base")
110
111    def draw(self, context):
112        layout = self.layout
113        layout.use_property_split = True
114        layout.use_property_decorate = False  # No animation.
115
116        scene = context.scene
117        rd = scene.render
118
119        col = layout.column(align=True)
120        col.prop(rd, "resolution_x", text="Resolution X")
121        col.prop(rd, "resolution_y", text="Y")
122        col.prop(rd, "resolution_percentage", text="%")
123
124        col = layout.column(align=True)
125        col.prop(rd, "pixel_aspect_x", text="Aspect X")
126        col.prop(rd, "pixel_aspect_y", text="Y")
127
128        col = layout.column(align=True)
129        col.prop(rd, "use_border")
130        sub = col.column(align=True)
131        sub.active = rd.use_border
132        sub.prop(rd, "use_crop_to_border")
133
134        col = layout.column(align=True)
135        col.prop(scene, "frame_start", text="Frame Start")
136        col.prop(scene, "frame_end", text="End")
137        col.prop(scene, "frame_step", text="Step")
138
139        col = layout.column(heading="Frame Rate")
140        self.draw_framerate(col, rd)
141
142
143class RENDER_PT_frame_remapping(RenderOutputButtonsPanel, Panel):
144    bl_label = "Time Remapping"
145    bl_parent_id = "RENDER_PT_dimensions"
146    bl_options = {'DEFAULT_CLOSED'}
147    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
148
149    def draw(self, context):
150        layout = self.layout
151        layout.use_property_split = True
152        layout.use_property_decorate = False  # No animation.
153
154        rd = context.scene.render
155
156        col = layout.column(align=True)
157        col.prop(rd, "frame_map_old", text="Old")
158        col.prop(rd, "frame_map_new", text="New")
159
160
161class RENDER_PT_post_processing(RenderOutputButtonsPanel, Panel):
162    bl_label = "Post Processing"
163    bl_options = {'DEFAULT_CLOSED'}
164    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
165
166    def draw(self, context):
167        layout = self.layout
168        layout.use_property_split = True
169
170        rd = context.scene.render
171
172        col = layout.column(heading="Pipeline")
173        col.prop(rd, "use_compositing")
174        col.prop(rd, "use_sequencer")
175
176        layout.prop(rd, "dither_intensity", text="Dither", slider=True)
177
178
179class RENDER_PT_stamp(RenderOutputButtonsPanel, Panel):
180    bl_label = "Metadata"
181    bl_options = {'DEFAULT_CLOSED'}
182    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
183
184    def draw(self, context):
185        layout = self.layout
186        layout.use_property_split = True
187        layout.use_property_decorate = False  # No animation.
188
189        rd = context.scene.render
190
191        if rd.use_sequencer:
192            layout.prop(rd, "metadata_input")
193
194        col = layout.column(heading="Include")
195        col.prop(rd, "use_stamp_date", text="Date")
196        col.prop(rd, "use_stamp_time", text="Time")
197        col.prop(rd, "use_stamp_render_time", text="Render Time")
198        col.prop(rd, "use_stamp_frame", text="Frame")
199        col.prop(rd, "use_stamp_frame_range", text="Frame Range")
200        col.prop(rd, "use_stamp_memory", text="Memory")
201        col.prop(rd, "use_stamp_hostname", text="Hostname")
202        col.prop(rd, "use_stamp_camera", text="Camera")
203        col.prop(rd, "use_stamp_lens", text="Lens")
204        col.prop(rd, "use_stamp_scene", text="Scene")
205        col.prop(rd, "use_stamp_marker", text="Marker")
206        col.prop(rd, "use_stamp_filename", text="Filename")
207
208
209class RENDER_PT_stamp_note(RenderOutputButtonsPanel, Panel):
210    bl_label = "Note"
211    bl_parent_id = "RENDER_PT_stamp"
212    bl_options = {'DEFAULT_CLOSED'}
213    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
214
215    def draw_header(self, context):
216        rd = context.scene.render
217
218        self.layout.prop(rd, "use_stamp_note", text="")
219
220    def draw(self, context):
221        layout = self.layout
222
223        rd = context.scene.render
224
225        layout.active = rd.use_stamp_note
226        layout.prop(rd, "stamp_note_text", text="")
227
228
229class RENDER_PT_stamp_burn(RenderOutputButtonsPanel, Panel):
230    bl_label = "Burn Into Image"
231    bl_parent_id = "RENDER_PT_stamp"
232    bl_options = {'DEFAULT_CLOSED'}
233    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
234
235    def draw_header(self, context):
236        rd = context.scene.render
237
238        self.layout.prop(rd, "use_stamp", text="")
239
240    def draw(self, context):
241        layout = self.layout
242
243        rd = context.scene.render
244
245        layout.use_property_split = True
246
247        col = layout.column()
248        col.active = rd.use_stamp
249        col.prop(rd, "stamp_font_size", text="Font Size")
250        col.column().prop(rd, "stamp_foreground", slider=True)
251        col.column().prop(rd, "stamp_background", slider=True)
252        col.prop(rd, "use_stamp_labels", text="Include Labels")
253
254
255class RENDER_PT_output(RenderOutputButtonsPanel, Panel):
256    bl_label = "Output"
257    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
258
259    def draw(self, context):
260        layout = self.layout
261        layout.use_property_split = False
262        layout.use_property_decorate = False  # No animation.
263
264        rd = context.scene.render
265        image_settings = rd.image_settings
266
267        layout.prop(rd, "filepath", text="")
268
269        layout.use_property_split = True
270
271        col = layout.column(heading="Saving")
272        col.prop(rd, "use_file_extension")
273        col.prop(rd, "use_render_cache")
274
275        layout.template_image_settings(image_settings, color_management=False)
276
277        if not rd.is_movie_format:
278            col = layout.column(heading="Image Sequence")
279            col.prop(rd, "use_overwrite")
280            col.prop(rd, "use_placeholder")
281
282
283class RENDER_PT_output_views(RenderOutputButtonsPanel, Panel):
284    bl_label = "Views"
285    bl_parent_id = "RENDER_PT_output"
286    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
287
288    @classmethod
289    def poll(cls, context):
290        rd = context.scene.render
291        return rd.use_multiview
292
293    def draw(self, context):
294        layout = self.layout
295        layout.use_property_split = False
296        layout.use_property_decorate = False  # No animation.
297
298        rd = context.scene.render
299        layout.template_image_views(rd.image_settings)
300
301
302class RENDER_PT_encoding(RenderOutputButtonsPanel, Panel):
303    bl_label = "Encoding"
304    bl_parent_id = "RENDER_PT_output"
305    bl_options = {'DEFAULT_CLOSED'}
306    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
307
308    def draw_header_preset(self, _context):
309        RENDER_PT_ffmpeg_presets.draw_panel_header(self.layout)
310
311    @classmethod
312    def poll(cls, context):
313        rd = context.scene.render
314        return rd.image_settings.file_format in {'FFMPEG', 'XVID', 'H264', 'THEORA'}
315
316    def draw(self, context):
317        layout = self.layout
318        layout.use_property_split = True
319        layout.use_property_decorate = False
320
321        rd = context.scene.render
322        ffmpeg = rd.ffmpeg
323
324        layout.prop(rd.ffmpeg, "format")
325        layout.prop(ffmpeg, "use_autosplit")
326
327
328class RENDER_PT_encoding_video(RenderOutputButtonsPanel, Panel):
329    bl_label = "Video"
330    bl_parent_id = "RENDER_PT_encoding"
331    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
332
333    @classmethod
334    def poll(cls, context):
335        rd = context.scene.render
336        return rd.image_settings.file_format in {'FFMPEG', 'XVID', 'H264', 'THEORA'}
337
338    def draw(self, context):
339        layout = self.layout
340        layout.use_property_split = True
341        layout.use_property_decorate = False
342
343        self.draw_vcodec(context)
344
345    def draw_vcodec(self, context):
346        """Video codec options."""
347        layout = self.layout
348        ffmpeg = context.scene.render.ffmpeg
349
350        needs_codec = ffmpeg.format in {'AVI', 'QUICKTIME', 'MKV', 'OGG', 'MPEG4', 'WEBM'}
351        if needs_codec:
352            layout.prop(ffmpeg, "codec")
353
354        if needs_codec and ffmpeg.codec == 'NONE':
355            return
356
357        if ffmpeg.codec == 'DNXHD':
358            layout.prop(ffmpeg, "use_lossless_output")
359
360        # Output quality
361        use_crf = needs_codec and ffmpeg.codec in {'H264', 'MPEG4', 'WEBM'}
362        if use_crf:
363            layout.prop(ffmpeg, "constant_rate_factor")
364
365        # Encoding speed
366        layout.prop(ffmpeg, "ffmpeg_preset")
367        # I-frames
368        layout.prop(ffmpeg, "gopsize")
369        # B-Frames
370        row = layout.row(align=True, heading="Max B-frames")
371        row.prop(ffmpeg, "use_max_b_frames", text="")
372        sub = row.row(align=True)
373        sub.active = ffmpeg.use_max_b_frames
374        sub.prop(ffmpeg, "max_b_frames", text="")
375
376        if not use_crf or ffmpeg.constant_rate_factor == 'NONE':
377            col = layout.column()
378
379            sub = col.column(align=True)
380            sub.prop(ffmpeg, "video_bitrate")
381            sub.prop(ffmpeg, "minrate", text="Minimum")
382            sub.prop(ffmpeg, "maxrate", text="Maximum")
383
384            col.prop(ffmpeg, "buffersize", text="Buffer")
385
386            col.separator()
387
388            col.prop(ffmpeg, "muxrate", text="Mux Rate")
389            col.prop(ffmpeg, "packetsize", text="Mux Packet Size")
390
391
392class RENDER_PT_encoding_audio(RenderOutputButtonsPanel, Panel):
393    bl_label = "Audio"
394    bl_parent_id = "RENDER_PT_encoding"
395    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
396
397    @classmethod
398    def poll(cls, context):
399        rd = context.scene.render
400        return rd.image_settings.file_format in {'FFMPEG', 'XVID', 'H264', 'THEORA'}
401
402    def draw(self, context):
403        layout = self.layout
404        layout.use_property_split = True
405        layout.use_property_decorate = False
406
407        rd = context.scene.render
408        ffmpeg = rd.ffmpeg
409
410        if ffmpeg.format != 'MP3':
411            layout.prop(ffmpeg, "audio_codec", text="Audio Codec")
412
413        if ffmpeg.audio_codec != 'NONE':
414            layout.prop(ffmpeg, "audio_channels")
415            layout.prop(ffmpeg, "audio_mixrate", text="Sample Rate")
416            layout.prop(ffmpeg, "audio_bitrate")
417            layout.prop(ffmpeg, "audio_volume", slider=True)
418
419
420class RENDER_UL_renderviews(UIList):
421    def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, index):
422        view = item
423        if self.layout_type in {'DEFAULT', 'COMPACT'}:
424            if view.name in {"left", "right"}:
425                layout.label(text=view.name, icon_value=icon + (not view.use))
426            else:
427                layout.prop(view, "name", text="", index=index, icon_value=icon, emboss=False)
428            layout.prop(view, "use", text="", index=index)
429
430        elif self.layout_type == 'GRID':
431            layout.alignment = 'CENTER'
432            layout.label(text="", icon_value=icon + (not view.use))
433
434
435class RENDER_PT_stereoscopy(RenderOutputButtonsPanel, Panel):
436    bl_label = "Stereoscopy"
437    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
438    bl_options = {'DEFAULT_CLOSED'}
439
440    def draw_header(self, context):
441        rd = context.scene.render
442        self.layout.prop(rd, "use_multiview", text="")
443
444    def draw(self, context):
445        layout = self.layout
446
447        scene = context.scene
448        rd = scene.render
449        rv = rd.views.active
450
451        layout.active = rd.use_multiview
452        basic_stereo = rd.views_format == 'STEREO_3D'
453
454        row = layout.row()
455        layout.row().prop(rd, "views_format", expand=True)
456
457        if basic_stereo:
458            row = layout.row()
459            row.template_list("RENDER_UL_renderviews", "name", rd, "stereo_views", rd.views, "active_index", rows=2)
460
461            row = layout.row()
462            row.use_property_split = True
463            row.use_property_decorate = False
464            row.prop(rv, "file_suffix")
465
466        else:
467            row = layout.row()
468            row.template_list("RENDER_UL_renderviews", "name", rd, "views", rd.views, "active_index", rows=2)
469
470            col = row.column(align=True)
471            col.operator("scene.render_view_add", icon='ADD', text="")
472            col.operator("scene.render_view_remove", icon='REMOVE', text="")
473
474            row = layout.row()
475            row.use_property_split = True
476            row.use_property_decorate = False
477            row.prop(rv, "camera_suffix")
478
479
480classes = (
481    RENDER_PT_presets,
482    RENDER_PT_ffmpeg_presets,
483    RENDER_MT_framerate_presets,
484    RENDER_PT_dimensions,
485    RENDER_PT_frame_remapping,
486    RENDER_PT_stereoscopy,
487    RENDER_PT_output,
488    RENDER_PT_output_views,
489    RENDER_PT_encoding,
490    RENDER_PT_encoding_video,
491    RENDER_PT_encoding_audio,
492    RENDER_PT_stamp,
493    RENDER_PT_stamp_note,
494    RENDER_PT_stamp_burn,
495    RENDER_UL_renderviews,
496    RENDER_PT_post_processing,
497)
498
499if __name__ == "__main__":  # only for live edit.
500    from bpy.utils import register_class
501    for cls in classes:
502        register_class(cls)
503