import os
from datetime import datetime
from enum import Enum
import bpy
[docs]class ImageFormatExtension(Enum):
BMP = ".bmp"
PNG = ".png"
IRIS = ".sgi"
JPEG = ".jpg"
JPEG2000 = ".jp2"
TARGA = ".tga"
TARGA_RAW = ".tga"
DPX = ".dpx"
CINEON = ".cin"
OPEN_EXR = ".exr"
OPEN_EXR_MULTILAYER = ".exr"
HDR = ".hdr"
TIFF = ".tiff"
WEBP = ".webp"
[docs]def set_world_background(color=(1, 1, 1, 1), strength=0.2, world=None):
"""Set the world background color and emission.
Parameters
----------
color : Sequence of 4 floats between 0 and 1 (default=(1, 1, 1, 1))
Color the background will have.
strength : float (default=0.2)
Emission strength of the background.
world : bpy.types.World (default=None)
World to modify. If None, use world of the current scene.
"""
if world is None:
world = bpy.context.scene.world
world_background_shader_inputs = world.node_tree.nodes["Background"].inputs
background_color = world_background_shader_inputs[0]
background_strength = world_background_shader_inputs[1]
background_color.default_value = color
background_strength.default_value = strength
[docs]def set_film_transparency(scene=None, transparent=True):
"""Set the transparency of the render's film.
Parameters
----------
scene : bpy.types.Scene (default=None)
Scene to apply the render setup to. If None, use the current scene.
transparent : bool (default=True)
If True make the film transparent, else use opaque world background.
"""
if scene is None:
scene = bpy.context.scene
scene.render.film_transparent = transparent
[docs]def setup_eevee_renderer(scene=None, samples=64, shadows_px="128"):
"""Set render settings for the Eevee engine.
Parameters
----------
scene : bpy.types.Scene (default=None)
Scene to which to apply the render settings. If None use the current scene.
samples : int (default=64)
Number of samples.
shadows_px : enum in ["64", "128", "256", "512", "1024", "2048", "4096"],
(default="128")
Number of pixels to use for rendering of shadows.
"""
if scene is None:
scene = bpy.context.scene
render = scene.render
render.engine = "BLENDER_EEVEE"
scene.eevee.taa_render_samples = samples
scene.eevee.shadow_cube_size = shadows_px
scene.eevee.shadow_cascade_size = shadows_px
[docs]def setup_cycles_renderer(
scene=None,
device="CPU",
noise_threshold=0.01,
min_samples=0,
max_samples=512,
time_limit=5,
denoise=False,
persistent_data=True,
):
"""Set render settings for the Cycles engine with adaptive sampling.
Parameters
----------
scene : bpy.types.Scene (default=None)
Scene to which to apply the render settings. If None, use the current scene.
device : str (default="CPU")
Cycles render device, either "CPU" or "GPU".
noise_threshold : float (default=0.01)
The error threshold used in adaptive sampling.
min_samples : int (default=0)
Minimum number of samples.
max_samples : int (default=512)
Minimum number of samples.
time_limit : float (default=5)
Maximum time of a render. Zero means no time limit.
denoise : bool (default=False)
Whether or not to denoise the output after render.
persistent_data : bool (default=True)
Whether or not to keep render data for faster re-renders and animation renders.
"""
if scene is None:
scene = bpy.context.scene
scene.render.engine = "CYCLES"
scene.render.use_persistent_data = persistent_data
cycles = scene.cycles
cycles.device = device
cycles.use_adaptive_sampling = True
cycles.samples = max_samples
cycles.time_limit = time_limit
cycles.adaptive_min_samples = min_samples
cycles.adaptive_threshold = noise_threshold
cycles.use_denoising = denoise
def _timestamp_path(path):
"""Get path to a timestamp subdirectory under path.
Parameters
----------
path : str
Prefix path to which to append a timestamp subdir.
Returns
-------
str
"""
timestamp = format(datetime.now(), "%y%m%d-%H%M")
return os.path.join(path, timestamp, "")
[docs]def set_render_stamp_note(
note,
scene=None,
font_size=26,
font_color=(0, 0, 0, 1),
bg_color=(0, 0, 0, 0),
):
"""Set a custom string to display in the render stamp.
Parameters
----------
note : str
The string to display in render stamp.
scene : bpy.types.Scenes (default=None)
The scene to setup render stamp. If None use the current scene.
font_size : int (default=26)
The font size of displayed string.
font_color : Iterable of four floats (default=(0.,0.,0.,1.))
RGBA values of displayed string.
bg_color : Iterable of four floats (default=(0.,0.,0.,0.))
RGBA values of background of displayed string.
Notes
-----
In order to display a stamp note `scene.render.use_stamp` must be
set to `True`. Blender then automatically adds other render stamps,
such as render time, date and file name. You can disable those manually,
for example by setting `scene.render.use_stamp_filename = False`.
For more settings, see `bpy.types.RenderSettings`.
"""
if scene is None:
scene = bpy.context.scene
render = scene.render
render.use_stamp = True
render.use_stamp_note = True
render.stamp_note_text = note
render.stamp_font_size = font_size
render.stamp_background = bg_color
render.stamp_foreground = font_color
[docs]def set_render_output_images(
output_path,
time=True,
scene=None,
file_format="PNG",
alpha=True,
quality=100,
):
"""Set the rendering output for an image sequence.
Unless time=False, each time this function is called a new timestamp is set,
and the output images will be saved under {output_path}/{timestamp}/
in order to isolate renders between sessions.
Parameters
----------
output_path : str
The directory path to which to save to ouput.
time : bool (default=True)
Whether or not to create a timestamp subdirectory and save the rendered images \
under it.
scene : bpy.types.Scenes (default=None)
The scene to which to apply the settings. If None use the current scene.
file_format : str (default="PNG")
The file format of the images. In upper letters. For example 'PNG' or 'JPEG2000'
alpha : bool (default=True)
Whether to use alpha in the output image.
quality : int (default=100)
Quality of the output image in percent.
See Also
--------
render_frame, render_animation
"""
if scene is None:
scene = bpy.context.scene
render = scene.render
render.image_settings.file_format = file_format
if alpha:
render.image_settings.color_mode = "RGBA"
else:
render.image_settings.color_mode = "RGB"
render.image_settings.quality = quality
render.filepath = (
_timestamp_path(output_path) if time else os.path.join(output_path, "")
)
def _zfill(frame, end_frame):
"""Format frame with enough prefix zeros to match end_frame.
Parameters
----------
frame : int
Frame number to format.
end_frame : int
Reference frame.
"""
return str(frame).zfill(len(str(end_frame)))
[docs]def render_frame(frame, full_path=None, camera=None):
"""Render a frame to full_path.
Parameters
----------
frame : int
Frame to use for the render.
full_path : str (default=None)
Full output path with file name and extension. If None use the current scene
`scene.render.filepath` and `scene.render.image_settings.file_format`.
camera : bpy.types.Camera (default=None)
Camera to use for the render. If None use the current scene camera.
See Also
--------
set_render_output_images
"""
scene = bpy.context.scene
if camera:
scene.camera = camera
print(f"Rendering frame {frame}")
scene.frame_current = frame
bpy.ops.render.render()
image = bpy.data.images["Render Result"]
if full_path is None:
output_directory = scene.render.filepath
image_format = scene.render.image_settings.file_format
full_path = os.path.join(
output_directory, str(frame) + ImageFormatExtension[image_format].value
)
print(f"Saving {full_path}")
image.save_render(full_path)
[docs]def render_animation(output_directory=None, start=None, end=None, camera=None):
"""Render all frames between start and end to path.
Parameters
----------
output_directory : str (default=None)
Directory of the render output. If None use current scene
`scene.render.filepath` and `scene.render.image_settings.file_format`.
start : int (default=None)
Start frame. If None use current scene start frame.
end : int (default=None)
End frame. If None use current scene end frame.
camera : bpy.types.Camera (default=None)
Camera to use for the render. If None, use the current scene camera.
See Also
--------
set_render_output_images
"""
scene = bpy.context.scene
if output_directory is None:
output_directory = scene.render.filepath
if start is None:
start = scene.frame_start
if end is None:
end = scene.frame_end
if camera:
scene.camera = camera
print(f"Rendering frames {start} to {end}...")
image_format = scene.render.image_settings.file_format
for frame in range(start, end + 1):
path = os.path.join(
output_directory,
_zfill(frame, end) + ImageFormatExtension[image_format].value,
)
render_frame(frame, full_path=path)
print("Rendering done.")