Rendering jobs¶
To obtain renders of any type, PCBooth
executes rendering jobs.
These jobs are defined in the blendcfg.yaml
configuration file.
The following rendering jobs are queued by default:
STAGES:
- STATIC:
- FLIPTRANSITIONS:
Each stage in the pipeline is provided from the jobs
package in the Blender Python environment.
For example, STATIC
is defined in the jobs/static.py file:
class Static(pcbooth.core.job.Job):
"""
Static image rendering job.
This module handles rendering static images of a model (usually PCBs)
using various camera angles and on selected backgrounds.
Yields renders named <camera_angle><position initial>_<background name>
e.g. rightT_paper_black.png, for each combination.
"""
def iterate(self) -> None:
"""
Main loop of the module to be run within execute() method.
"""
renderer = RendererWrapper()
total_renders = len(self.studio.positions) * len(self.studio.cameras) * len(self.studio.backgrounds)
self.update_status(total_renders)
for position in self.studio.positions:
self.studio.change_position(position)
Background.update_position(self.studio.top_parent)
for background in self.studio.backgrounds:
Background.use(background)
for camera in self.studio.cameras:
camera.change_position(position)
filename = f"{camera.name.lower()}{position[0]}_{background.name}"
renderer.render(camera.object, filename)
renderer.thumbnail(camera.object, filename)
renderer.clear_cache()
self.update_status()
A rendering job can iterate over elements of the scene (backgrounds, cameras, rendered model) through the Studio class object that gets passed to Job class instances on initialization or alter model objects.
A single Python file can contain multiple stage definitions.
A stage must inherit from the core.job.Job
base class to be discoverable by PCBooth
.
To call a stage from blendcfg.yaml
, refer to the stage’s class name in uppercase (STATIC
, in this case) for PCBooth
to dynamically load and run the code defined in the iterate
method for the class.
The execute
method for a stage is called when all previous stages are completed successfully.
Writing rendering jobs¶
Tracking progress¶
To properly display progress of the rendering job, the update_status
method can be used.
When calling it with integer as an argument, you can set the count of all renders within this job.
You can update this total count value as many time as needed.
Here’s an example of setting total number of renders based on cameras to use:
def iterate(self) -> None:
total_renders = len(self.studio.cameras)
self.update_status(total_renders)
...
When called without an argument, it will increment the progress count iterator and print a status update in the console:
for camera in self.studio.cameras:
renderer.render(camera.object, file_name="render")
renderer.clear_cache()
self.update_status()
[15:19:30] [pcbooth.modules.renderer] (INFO) Rendering photo1T_paper_black...
[15:19:40] [pcbooth.modules.renderer] (INFO) Saved render as: //blender_renders/photo1T_paper_black.png
[15:19:40] [pcbooth.modules.renderer] (INFO) Saved render as: //blender_renders/photo1T_paper_black.jpg
[15:19:41] [pcbooth.core.job] (INFO) ### Progress: 1/6 (16%)
Overriding config¶
For some rendering jobs, you might want to force rendering with specific cameras or on specific backgrounds.
You can do it by overriding _override_studio
method within your job class and defining studio’s lists there:
from pcbooth.modules.background import Background
from pcbooth.modules.camera import Camera
class MyRenderingJob(pcbooth.core.job.Job):
""" Custom example rendering job """
def _override_studio(self):
self.studio.backgrounds = [Background.get("transparent")]
self.studio.positions = ["TOP", "BOTTOM"]
self.studio.cameras = [Camera.get("PHOTO1"), Camera.get("FRONT")]
This method will now override values read from config for your specific job only. Other jobs will run with the lists parsed from blendcfg.yaml unless they have their own overrides added.
Passing job-specific parameters in the config¶
You can run rendering jobs with additional parameters defined in the config file.
To enable them, override the ParameterSchema(BaseModel)
class in your rendering job and define your parameter types and default values as follows:
from pydantic import BaseModel
...
class ParameterSchema(BaseModel):
"""
Pydantic schema class for optional job parameters.
Overwrite this in deriving classes to add their own parameters.
"""
PARAMETER1: bool = True
PARAMETER2: int = 0
PARAMETER3: List[str] = ["FOO", "BAR"]
Parameters are parsed and converted with use of this schema, so that only the valid ones get passed to the rendering job. Enabled parameters will be printed to console in the rendering job report:
[14:24:20] [pcbooth.core.job] (INFO) ### MASKS rendering job
[14:24:20] [pcbooth.core.job] (INFO) * enabled rendered object positions: TOP, BOTTOM
[14:24:20] [pcbooth.core.job] (INFO) * enabled cameras: FRONT, PHOTO1
[14:24:20] [pcbooth.core.job] (INFO) * enabled backgrounds: paper_black
[14:24:20] [pcbooth.core.job] (INFO) * enabled parameters: FULL=False, COVERED=True, HIGHLIGHTED=['A', 'J', 'PS', 'T', 'U', 'IC', 'POT']
[14:24:20] [pcbooth.core.job] (INFO) Total renders: 62
In the rendering job module code, you can you can access parameters using self.params.get(<parameter name>)
.
Using the RendererWrapper
and FFmpegWrapper
¶
RendererWrapper
and FFmpegWrapper
are classes handling saving render results and sequencing them into animations.
PCBooth
relies on image data buffers stored within them to optimize saving each output in different file formats.
In order to use them, import them from the renderer
module and create their instances within the iterate
method:
from pcbooth.modules.renderer import FFmpegWrapper, RendererWrapper
def iterate(self) -> None
"""
Main loop of the module to be run within execute() method.
"""
ffmpeg = FFmpegWrapper()
renderer = RendererWrapper()
...
RendererWrapper
class¶
- class renderer.RendererWrapper¶
Class responsible for rendering images and frames within Blender.
- clear_cache() None ¶
Remove render cache file. Looks for CACHE_NAME files. Sets cache attribute to None.
-
render(camera: bpy.types.Object, file_name: str, format_override: str | None =
None
) None ¶ Render an image using specified camera and save under provided file name. Optional format_override can be used to ignore format list from config file. Uses cached render from _init_render method.
- render_animation(camera: bpy.types.Object, file_name: str) None ¶
Render sequence of images iterating over frame count range from bpy.context.scene. Clears cache after each frame as they are not supposed to be rendered as mutliple format.
-
thumbnail(camera: bpy.types.Object, file_name: str, format_override: str | None =
None
) None ¶ Make thumbnail copy of a rendered image. Uses previously saved render image data. Uses cached render from _init_render method.
FFmpegWrapper
class¶
- class renderer.FFmpegWrapper¶
Class responsible for running FFMPEG commands to sequence series of images to common video formats.
- clear_frames() None ¶
Remove frame files after sequencing. Recognizes <filename>_<frame_number>.<ext> filenames using regex. Expects 4-digit frame number.
- reverse(input_file: str, output_file: str) None ¶
Reverse existing video file.
- run(input_file: str, output_file: str) None ¶
Run FFPMEG and sequence images into full-scale animation.
-
thumbnail(input_file: str, output_file: str | None =
None
) None ¶ Scale existing video file down into thumbnail.
Override context managers¶
Some rendering jobs might require rendering settings, model position or other properties to be temporarily altered from their default state.
To not interfere with subsequent rendering jobs in the queue, those alterations need to be reversed at the end of the job.
To help with this, context managers from the job_utilities
module can be used, for example:
from pcbooth.modules.renderer import (
RendererWrapper,
setup_ultralow_cycles,
)
import pcbooth.modules.job_utilities as ju
class MyRenderingJob(pcbooth.core.job.Job):
""" Custom example rendering job """
def iterate(self) -> None:
...
with (ju.cycles_override(setup_ultralow_cycles), ju.global_material_override()):
for camera in self.studio.cameras:
...
This rendering job will render images using ultralow quality Cycles settings preset and apply global material override for all objects in a scene, then it will restore the settings to the previous state when it’s finished.
job_utilities
module¶
Module containing utility functions for rendering jobs.
- job_utilities.compositing_override(compositing_func: Callable[[...], None]) Generator[None, Any, None] ¶
Apply compositor override.
- job_utilities.cycles_override(settings_func: Callable[[...], None]) Generator[None, Any, None] ¶
Apply Cycles settings override.
-
job_utilities.global_material_override(base_material: bpy.types.Material | None =
None
) Generator[None, Any, None] ¶ Apply global material override for all objects in a scene.
-
job_utilities.hide_override(components: list[bpy.types.Object] | set[bpy.types.Object], hide_viewport: bool =
False
) Generator[None, Any, None] ¶ Apply hide from render override for components from provided list.
-
job_utilities.holdout_override(components: list[bpy.types.Object] | set[bpy.types.Object], full: bool =
False
) Generator[None, Any, None] ¶ Apply holdout override for components from provided list. If non_obstructed
- job_utilities.material_override(base_material: bpy.types.Material, components: list[bpy.types.Object] | set[bpy.types.Object]) Generator[None, Any, None] ¶
Override all materials with the specified one for all components in the list. Skips linked objects. Generates backup dictionary with the material slots for revert.
-
job_utilities.position_override(components: list[bpy.types.Object] | set[bpy.types.Object], position_func: Callable[[list[bpy.types.Object]], None], rendered_obj: bpy.types.Object | None =
None
) Generator[None, Any, None] ¶ Temporarily change location or rotation of components from provided list by updating their delta location and Euler rotation. Uses function passed as argument. Restore original location and rotation in the end. By passing rendered object to the context manager, light will be adjusted to the updated model position as well.
- job_utilities.shadow_override(components: list[bpy.types.Object] | set[bpy.types.Object]) Generator[None, Any, None] ¶
Override object visibility to shadow rays.