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