2021-05-17 19:24:14 +00:00
|
|
|
# render_profile.py
|
|
|
|
"""
|
|
|
|
Blender Python code to set parameters based on render profiles.
|
2021-05-30 06:48:38 +00:00
|
|
|
|
|
|
|
The purpose of the "Render Profiles" feature is to simplify setting up
|
|
|
|
Blender to render animation according to a small number of standardized,
|
|
|
|
named profiles, instead of having to control each setting separately.
|
|
|
|
|
|
|
|
They're sort of like predefined radio buttons for your render settings.
|
|
|
|
|
|
|
|
I wrote this because I kept having to repeat the same steps to go from
|
|
|
|
quick "GL" or "Paint" renders at low frame rates to fully-configured
|
|
|
|
final renders, and I found the process was error-prone.
|
|
|
|
|
|
|
|
In particular, it was very easy to accidentally forget to change the render
|
|
|
|
filepath and have a previous render get overwritten! Or, alternatively, I
|
|
|
|
might forget to set things back up for a final render after I did a previz
|
|
|
|
animation.
|
2021-05-17 19:24:14 +00:00
|
|
|
"""
|
|
|
|
|
2021-06-29 18:53:48 +00:00
|
|
|
import os
|
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
import bpy
|
2021-05-17 19:24:14 +00:00
|
|
|
import bpy, bpy.types, bpy.utils, bpy.props
|
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
from abx import ink_paint
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-07-08 21:05:20 +00:00
|
|
|
from . import abx_context
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-06-29 18:53:48 +00:00
|
|
|
class RenderProfileMap(dict):
|
|
|
|
"""
|
|
|
|
Specialized dictionary for mapping Render profile names to profiles.
|
|
|
|
"""
|
|
|
|
def __init__(self, profile_map=None):
|
|
|
|
self._blender_enum = []
|
|
|
|
if not profile_map:
|
|
|
|
profile_map = {}
|
|
|
|
for key in profile_map:
|
|
|
|
self[key] = RenderProfile(key, profile_map[key])
|
|
|
|
|
|
|
|
for key in self.keys():
|
|
|
|
self._blender_enum.append((key, self[key].name, self[key].desc))
|
|
|
|
|
|
|
|
def keys(self):
|
|
|
|
return sorted(super().keys())
|
|
|
|
|
|
|
|
def blender_enum(self):
|
|
|
|
return self._blender_enum
|
|
|
|
|
|
|
|
def apply(self, scene, key):
|
|
|
|
self[key].apply(scene)
|
|
|
|
|
|
|
|
def blender_enum_lookup(self, context):
|
|
|
|
from abx import BlendFile
|
|
|
|
return RenderProfileMap(BlendFile.render_profiles).blender_enum()
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
class RenderProfile(object):
|
2021-05-30 06:48:38 +00:00
|
|
|
"""
|
|
|
|
A named set of render settings for Blender.
|
|
|
|
|
|
|
|
The profile is designed to be defined by a dictionary of fields, typically
|
|
|
|
loaded from a project YAML file (under the key 'render_profiles').
|
|
|
|
|
|
|
|
Attributes:
|
2021-06-29 18:53:48 +00:00
|
|
|
name (str):
|
|
|
|
Drop-down name for profile.
|
|
|
|
|
|
|
|
desc (str):
|
|
|
|
Longer descriptive name used for tooltips in the UI.
|
|
|
|
|
2021-05-30 06:48:38 +00:00
|
|
|
engine (str):
|
|
|
|
Mandatory choice of engine. Some aliases are supported, but the
|
|
|
|
standard values are: 'gl', meaning a setup for GL viewport
|
|
|
|
rendering, or one 'bi'/'BLENDER_INTERNAL', 'cycles'/'CYCLES',
|
|
|
|
or 'bge' / 'BLENDER_GAME' for rendering with the respective
|
|
|
|
engines. There is no support for Eevee, because this is a 2.7-only
|
|
|
|
Add-on. It should be included in the port. No third-party engines
|
|
|
|
are currently supported.
|
|
|
|
|
|
|
|
fps (float):
|
|
|
|
Frames-per-second.
|
|
|
|
|
|
|
|
fps_skip (int):
|
|
|
|
Frames to skip between rendered frames (effectively divides the
|
|
|
|
frame rate).
|
|
|
|
|
|
|
|
fps_divisor (float):
|
|
|
|
This is the weird hack for specifying NTSC-compliant fps of 29.97
|
|
|
|
by using 1.001 as a divisor, instead of 1.0. Avoid if you can!
|
|
|
|
|
|
|
|
rendersize (int):
|
|
|
|
Percentage size of defined pixel dimensions to render. Note that
|
|
|
|
we don't support setting the pixel size directly. You should
|
|
|
|
configure that in Blender, but you can use this feature to make
|
|
|
|
a lower-resolution render.
|
|
|
|
|
|
|
|
compress (int):
|
|
|
|
Compression ratio for image formats that support it.
|
|
|
|
|
|
|
|
format (str):
|
|
|
|
Image or video output format.
|
|
|
|
One of: 'PNG', 'JPG', 'EXR', 'AVI' or 'MKV'.
|
|
|
|
Note that we don't support the full range of options, just some
|
|
|
|
common ones for previz and final rendering.
|
|
|
|
|
|
|
|
freestyle (bool):
|
|
|
|
Whether to turn on Freestyle ink rendering.
|
|
|
|
|
|
|
|
antialiasing_samples (str):
|
|
|
|
Controlled by 'antialias' key, which can be a number: 5,8,11, or 16.
|
|
|
|
Note that this attribute, which is used to directly set the value
|
|
|
|
in Blender is a string, not an integer.
|
|
|
|
|
|
|
|
use_antialiasing (bool):
|
|
|
|
Controlled by 'antialias' key. Whether to turn on antialiasing.
|
|
|
|
Any value other than 'False' or 'None' will turn it on.
|
|
|
|
False turns it off. None leaves it as-is.
|
|
|
|
|
|
|
|
motion_blur_samples (int):
|
|
|
|
Controlled by 'motionblur' key, which can be a number determining
|
|
|
|
the number of samples.
|
|
|
|
|
|
|
|
use_motion_blur (bool):
|
|
|
|
Controlled by 'motionblur' key. Any value other than False or None
|
|
|
|
will turn on motion blur. A value of True turns it on without
|
|
|
|
changing the samples. A value of False turns it off. None causes
|
|
|
|
is to be left as-is.
|
|
|
|
|
|
|
|
framedigits (int):
|
|
|
|
The number of '#' characters to use in the render filename to
|
|
|
|
indicate frame number. Only used if the format is an image stream.
|
|
|
|
|
|
|
|
suffix (str):
|
|
|
|
A string suffix placed after the base name, but before the frame
|
|
|
|
number to indicate what profile was used for the render. This
|
|
|
|
avoids accidentally overwriting renders made with other profiles.
|
|
|
|
|
|
|
|
Note that these attributes are not intended to be manipulated directly
|
|
|
|
by the user. The production designer is expected to define these
|
|
|
|
profiles in the <project>.yaml file under the 'render_profiles' key,
|
|
|
|
like this:
|
|
|
|
|
|
|
|
render_profiles:
|
|
|
|
previz:
|
|
|
|
engine: gl
|
|
|
|
suffix: MP
|
|
|
|
fps: 30
|
|
|
|
fps_skip: 6
|
|
|
|
motionblur: False
|
|
|
|
antialias: False
|
|
|
|
freestyle: False
|
|
|
|
rendersize: 50
|
|
|
|
|
|
|
|
and so on. This is then loaded by ABX into a list of RenderProfile
|
|
|
|
objects. Calling the RenderProfile.apply() method actually causes the
|
|
|
|
settings to be made.
|
|
|
|
"""
|
2021-05-28 16:54:09 +00:00
|
|
|
render_formats = {
|
|
|
|
# VERY simplified and limited list of formats from Blender that we need:
|
|
|
|
# <API 'format'>: (<bpy file format>, <filename extension>),
|
|
|
|
'PNG': ('PNG', 'png'),
|
|
|
|
'JPG': ('JPEG', 'jpg'),
|
|
|
|
'EXR': ('OPEN_EXR_MULTILAYER', 'exr'),
|
|
|
|
'AVI': ('AVI_JPEG', 'avi'),
|
|
|
|
'MKV': ('FFMPEG', 'mkv')
|
|
|
|
}
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
engines = {
|
|
|
|
'bi': 'BLENDER_RENDER',
|
|
|
|
'BLENDER_RENDER': 'BLENDER_RENDER',
|
|
|
|
'BI': 'BLENDER_RENDER',
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
'cycles': 'CYCLES',
|
|
|
|
'CYCLES': 'CYCLES',
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
'bge': 'BLENDER_GAME',
|
|
|
|
'BLENDER_GAME': 'BLENDER_GAME',
|
|
|
|
'BGE': 'BLENDER_GAME',
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
'gl': None,
|
|
|
|
'GL': None
|
|
|
|
}
|
2021-05-17 19:24:14 +00:00
|
|
|
|
|
|
|
|
2021-06-29 18:53:48 +00:00
|
|
|
def __init__(self, code, fields):
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
# Note: Settings w/ value *None* are left unaltered
|
|
|
|
# That is, they remain whatever they were before
|
|
|
|
# If a setting isn't included in the fields, then
|
|
|
|
# the attribute will be *None*.
|
2021-05-17 19:24:14 +00:00
|
|
|
|
2021-06-29 18:53:48 +00:00
|
|
|
if 'name' in fields:
|
|
|
|
self.name = fields['name']
|
|
|
|
else:
|
|
|
|
self.name = code
|
|
|
|
|
|
|
|
if 'desc' in fields:
|
|
|
|
self.desc = fields['desc']
|
|
|
|
else:
|
|
|
|
self.desc = code
|
|
|
|
|
2021-05-28 16:54:09 +00:00
|
|
|
if 'engine' not in fields:
|
|
|
|
fields['engine'] = None
|
|
|
|
|
|
|
|
if fields['engine']=='gl':
|
|
|
|
self.viewport_render = True
|
|
|
|
self.engine = None
|
|
|
|
else:
|
|
|
|
self.viewport_render = False
|
|
|
|
|
|
|
|
if fields['engine'] in self.engines:
|
|
|
|
self.engine = self.engines[fields['engine']]
|
|
|
|
else:
|
|
|
|
self.engine = None
|
|
|
|
|
|
|
|
# Parameters which are stored as-is, without modification:
|
|
|
|
self.fps = 'fps' in fields and int(fields['fps']) or None
|
|
|
|
self.fps_skip = 'fps_skip' in fields and int(fields['fps_skip']) or None
|
|
|
|
self.fps_divisor = 'fps_divisor' in fields and float(fields['fps_divisor']) or None
|
|
|
|
self.rendersize = 'rendersize' in fields and int(fields['rendersize']) or None
|
|
|
|
self.compress = 'compress' in fields and int(fields['compress']) or None
|
|
|
|
|
|
|
|
self.format = 'format' in fields and str(fields['format']) or None
|
|
|
|
|
|
|
|
self.freestyle = 'freestyle' in fields and bool(fields['freestyle']) or None
|
|
|
|
|
|
|
|
self.antialiasing_samples = None
|
|
|
|
self.use_antialiasing = None
|
|
|
|
if 'antialias' in fields:
|
|
|
|
if fields['antialias']:
|
|
|
|
self.use_antialiasing = True
|
|
|
|
if fields['antialias'] in (5,8,11,16):
|
|
|
|
self.antialiasing_samples = str(fields['antialias'])
|
|
|
|
else:
|
|
|
|
self.use_antialiasing = False
|
|
|
|
|
|
|
|
self.use_motion_blur = None
|
|
|
|
self.motion_blur_samples = None
|
|
|
|
if 'motionblur' in fields:
|
|
|
|
if fields['motionblur']:
|
|
|
|
self.use_motion_blur = True
|
|
|
|
if type(fields['motionblur'])==int:
|
|
|
|
self.motion_blur_samples = int(fields['motionblur'])
|
|
|
|
else:
|
|
|
|
self.use_motion_blur = False
|
|
|
|
|
|
|
|
if 'framedigits' in fields:
|
|
|
|
self.framedigits = fields['framedigits']
|
|
|
|
else:
|
|
|
|
self.framedigits = 5
|
|
|
|
|
|
|
|
if 'suffix' in fields:
|
|
|
|
self.suffix = fields['suffix']
|
|
|
|
else:
|
|
|
|
self.suffix = ''
|
|
|
|
|
|
|
|
def apply(self, scene):
|
|
|
|
"""
|
|
|
|
Apply the profile settings to the given scene.
|
2021-05-30 06:48:38 +00:00
|
|
|
|
|
|
|
NOTE: in 0.2.6 this function isn't fully implemented, and the
|
|
|
|
render filepath will not include the proper unit name.
|
2021-05-28 16:54:09 +00:00
|
|
|
"""
|
|
|
|
if self.engine: scene.render.engine = self.engine
|
|
|
|
if self.fps: scene.render.fps = self.fps
|
|
|
|
if self.fps_skip: scene.frame_step = self.fps_skip
|
|
|
|
if self.fps_divisor: scene.render.fps_base = self.fps_divisor
|
|
|
|
if self.rendersize: scene.render.resolution_percentage = self.rendersize
|
|
|
|
if self.compress: scene.render.image_settings.compression = self.compress
|
|
|
|
|
|
|
|
if self.format:
|
|
|
|
scene.render.image_settings.file_format = self.render_formats[self.format][0]
|
|
|
|
|
|
|
|
if self.freestyle: scene.render.use_freestyle = self.freestyle
|
|
|
|
if self.use_antialiasing:
|
|
|
|
scene.render.use_antialiasing = self.use_antialiasing
|
|
|
|
|
|
|
|
if self.antialiasing_samples:
|
|
|
|
scene.render.antialiasing_samples = self.antialiasing_samples
|
|
|
|
if self.use_motion_blur:
|
|
|
|
scene.render.use_motion_blur = self.use_motion_blur
|
|
|
|
|
|
|
|
if self.motion_blur_samples:
|
|
|
|
scene.render.motion_blur_samples = self.motion_blur_samples
|
|
|
|
|
|
|
|
if self.format:
|
|
|
|
# prefix = scene.name_context.render_path
|
|
|
|
# prefix = BlendfileContext.name_contexts[scene.name_context].render_path
|
2021-06-29 18:53:48 +00:00
|
|
|
|
|
|
|
prefix = os.path.join(
|
|
|
|
scene.project_properties.render_folder,
|
|
|
|
scene.project_properties.render_prefix)
|
2021-05-28 16:54:09 +00:00
|
|
|
if self.suffix:
|
|
|
|
scene.render.filepath = (prefix + '-' + self.suffix + '-' +
|
|
|
|
'f'+('#'*self.framedigits) + '.' +
|
|
|
|
self.render_formats[self.format][1])
|
2021-06-29 18:53:48 +00:00
|
|
|
else:
|
|
|
|
scene.render.filepath = (prefix + '-f'+('#'*self.framedigits) + '.' +
|
|
|
|
self.render_formats[self.format][1])
|
2021-05-28 16:54:09 +00:00
|
|
|
|
|
|
|
|
2021-05-17 19:24:14 +00:00
|
|
|
|