#!/usr/bin/env python3 import re from collections import Counter from src.engines.blender.blender_engine import Blender from src.utilities.ffmpeg_helper import image_sequence_to_video from src.engines.core.base_worker import * class BlenderRenderWorker(BaseRenderWorker): engine = Blender def __init__(self, input_path, output_path, engine_path, args=None, parent=None, name=None): super(BlenderRenderWorker, self).__init__(input_path=input_path, output_path=output_path, engine_path=engine_path, args=args, parent=parent, name=name) # Stats self.__frame_percent_complete = 0.0 # Scene Info self.scene_info = Blender(engine_path).get_project_info(input_path) self.start_frame = int(self.scene_info.get('start_frame', 1)) self.end_frame = int(self.scene_info.get('end_frame', self.start_frame)) self.project_length = (self.end_frame - self.start_frame) + 1 self.current_frame = -1 def generate_worker_subprocess(self): cmd = [self.renderer_path] if self.args.get('background', True): # optionally run render not in background cmd.append('-b') cmd.append(self.input_path) # Start Python expressions - # todo: investigate splitting into separate 'setup' script cmd.append('--python-expr') python_exp = 'import bpy; bpy.context.scene.render.use_overwrite = False;' # Setup Custom Camera custom_camera = self.args.get('camera', None) if custom_camera: python_exp = python_exp + f"bpy.context.scene.camera = bpy.data.objects['{custom_camera}'];" # Set Render Device (gpu/cpu/any) blender_engine = self.args.get('engine', 'BLENDER_EEVEE').upper() if blender_engine == 'CYCLES': render_device = self.args.get('render_device', 'any').lower() if render_device not in {'any', 'gpu', 'cpu'}: raise AttributeError(f"Invalid Cycles render device: {render_device}") use_gpu = render_device in {'any', 'gpu'} use_cpu = render_device in {'any', 'cpu'} python_exp = python_exp + ("exec(\"for device in bpy.context.preferences.addons[" f"'cycles'].preferences.devices: device.use = {use_cpu} if device.type == 'CPU'" f" else {use_gpu}\")") # -- insert any other python exp checks / generators here -- # End Python expressions here cmd.append(python_exp) # Export format export_format = self.args.get('export_format', None) or 'JPEG' main_part, ext = os.path.splitext(self.output_path) # Remove the extension only if it is not composed entirely of digits path_without_ext = main_part if not ext[1:].isdigit() else self.output_path path_without_ext += "_" cmd.extend(['-E', blender_engine, '-o', path_without_ext, '-F', export_format]) # set frame range cmd.extend(['-s', self.start_frame, '-e', self.end_frame, '-a']) # Convert raw args from string if available raw_args = self.get_raw_args() if raw_args: cmd.extend(raw_args) return cmd def _parse_stdout(self, line): pattern = re.compile( r'Fra:(?P\d*).*Mem:(?P\S+).*Time:(?P