import os.path
from shutil import copyfile
from pathlib import Path
[docs]
class RenderResult:
"""Result of any form of render.
Treated as a series of paths to the output files. Handles different output types, cameras, and frame counts.
"""
def __init__(self, render_paths: dict[tuple, Path]):
"""Expects dictionary of:
(output_key, camera_name, frame_number) -> Path.
"""
self.render_paths = render_paths
[docs]
def add(
self,
output_key: str,
camera_name: str,
frame_number: int,
path: Path,
overwrite=False,
):
"""Add a new RenderResult."""
key = (output_key, camera_name, frame_number)
if key in self.render_paths and not overwrite:
raise ValueError(
f"Render path already exists for {output_key}, {camera_name}, {frame_number}."
)
self.render_paths[key] = path
@property
def output_types(self) -> list[str]:
"""List of output types."""
return list(set([k[0] for k in self.render_paths.keys()]))
@property
def camera_names(self) -> list[str]:
"""List of camera names."""
return list(set([k[1] for k in self.render_paths.keys()]))
@property
def frames(self) -> list[int]:
"""List of frame numbers."""
return list(set([k[2] for k in self.render_paths.keys()]))
@property
def num_cameras(self) -> int:
"""Number of cameras in the render."""
return len(self.camera_names)
@property
def num_frames(self) -> int:
"""Number of frames in the render."""
return len(self.frames)
@property
def num_output_types(self) -> int:
"""Number of output types in the render."""
return len(self.output_types)
[docs]
def get_render_path(
self, output_type: str, camera_name: str = None, frame_number: int = None
) -> Path:
"""Get the path to a specific render output.
:param output_type: Type of output - `name` returned from :meth:`~blendersynth.blender.compositor.compositor.Compositor.define_output`.
:param camera_name: Name of camera to get render for. Only required if multiple cameras used.
:param frame_number: Frame number to get render for. Only required if multiple frames used.
:return: Path to the render output.
"""
if camera_name is None:
if self.num_cameras != 1:
raise ValueError(
"Must specify camera name if more than one camera. Cameras: ",
self.camera_names,
)
camera_name = self.camera_names[0]
if frame_number is None:
if self.num_frames != 1:
raise ValueError(
"Must specify frame number if more than one frame. Frames: ",
self.frames,
)
frame_number = self.frames[0]
return self.render_paths[(output_type, camera_name, frame_number)]
[docs]
def save_all(
self,
output_directory: str,
suppress_camera_name: bool = True,
suppress_frame_number: bool = True,
):
"""Save all the files to a directory.
By default, saves files in format {data_type}_{camera_name}_{frame_number}.{ext}.
:param output_directory: Directory to save files to.
:param suppress_camera_name: If True, suppress camera name in filename (only works if single camera).
:param suppress_frame_number: If True, suppress frame number in filename (only works if single frame).
"""
os.makedirs(output_directory, exist_ok=True)
suppress_camera_name = suppress_camera_name and self.num_cameras == 1
suppress_frame_number = suppress_frame_number and self.num_frames == 1
for (data_type, camera, frame), path in self.render_paths.items():
fname = f"{data_type}"
if not suppress_camera_name:
fname += f"_{camera}"
if not suppress_frame_number:
fname += f"_{frame:06d}"
fname += os.path.splitext(path)[1]
copyfile(path, os.path.join(output_directory, fname))
[docs]
def save_file(
self,
output_path: str,
output_type: str,
camera_name: str = None,
frame_number: int = None,
):
"""Save a single file to a path.
:param output_path: Path to save file to.
:param output_type: Type of output - `name` returned from :meth:`~blendersynth.blender.compositor.compositor.Compositor.define_output`.
:param camera_name: Name of camera to get render for. Only required if multiple cameras used.
:param frame_number: Frame number to get render for. Only required if multiple frames used.
"""
if os.path.dirname(output_path):
os.makedirs(os.path.dirname(output_path), exist_ok=True)
path = self.get_render_path(output_type, camera_name, frame_number)
target_ext = os.path.splitext(output_path)[1]
rendered_ext = os.path.splitext(path)[1]
if target_ext != rendered_ext:
raise ValueError(
f"Output path must have same extension as rendered path. Got {target_ext}, expected {rendered_ext}."
)
copyfile(path, output_path)