"""Daemon de rendu persistant pour Blender. Ce script est execute par Blender en mode background (-b) et reste actif. Il lit des commandes JSON depuis stdin et execute les rendus demandes. Le fichier .blend est charge une seule fois au demarrage. """ import bpy import json import sys import os import logging logging.basicConfig(level=logging.INFO, format="[render_daemon] %(message)s") obj_logger: logging.Logger = logging.getLogger(__name__) def apply_render_settings(obj_scene: object, obj_settings: dict) -> None: """Applique les parametres de rendu a la scene.""" if not obj_settings: return if "str_engine" in obj_settings: obj_scene.render.engine = obj_settings["str_engine"] if "nb_resolution_percentage" in obj_settings: obj_scene.render.resolution_percentage = obj_settings["nb_resolution_percentage"] if "is_film_transparent" in obj_settings: obj_scene.render.film_transparent = obj_settings["is_film_transparent"] str_engine: str = obj_scene.render.engine if str_engine == "CYCLES": if "nb_samples" in obj_settings: obj_scene.cycles.samples = obj_settings["nb_samples"] if "str_device" in obj_settings: obj_scene.cycles.device = obj_settings["str_device"] if "is_denoise" in obj_settings: obj_scene.cycles.use_denoising = obj_settings["is_denoise"] elif str_engine in ("BLENDER_EEVEE", "BLENDER_EEVEE_NEXT"): if "nb_samples" in obj_settings: try: obj_scene.eevee.taa_render_samples = obj_settings["nb_samples"] except AttributeError: try: obj_scene.eevee.samples = obj_settings["nb_samples"] except AttributeError: pass def apply_collections(list_collections: list) -> None: """Applique la visibilite des collections pour le rendu.""" if not list_collections: return def _set_layer_collection(obj_lc: object, str_name: str, is_val: bool) -> None: """Parcourt recursivement les layer collections pour exclure/inclure.""" for obj_child in obj_lc.children: if obj_child.name == str_name: obj_child.exclude = is_val return _set_layer_collection(obj_child, str_name, is_val) obj_view_layer_collection = bpy.context.view_layer.layer_collection for obj_col in list_collections: str_name: str = obj_col["str_name"] is_hide: bool = obj_col["is_hide_render"] bpy.data.collections[str_name].hide_render = is_hide _set_layer_collection(obj_view_layer_collection, str_name, is_hide) def process_render(obj_cmd: dict) -> None: """Execute le rendu d'une frame selon la commande recue.""" obj_scene = bpy.context.scene str_camera: str = obj_cmd["str_camera"] nb_frame: int = obj_cmd["nb_frame"] nb_resolution_x: int = obj_cmd["nb_resolution_x"] nb_resolution_y: int = obj_cmd["nb_resolution_y"] str_format: str = obj_cmd["str_format"] str_output_path: str = obj_cmd["str_output_path"] obj_scene.camera = bpy.data.objects[str_camera] obj_scene.render.resolution_x = nb_resolution_x obj_scene.render.resolution_y = nb_resolution_y obj_scene.render.image_settings.file_format = str_format obj_scene.frame_set(nb_frame) str_dir: str = os.path.dirname(str_output_path) if str_dir and not os.path.exists(str_dir): os.makedirs(str_dir, exist_ok=True) obj_scene.render.filepath = str_output_path apply_render_settings(obj_scene, obj_cmd.get("obj_render_settings")) apply_collections(obj_cmd.get("list_collections")) try: bpy.ops.render.render(write_still=True) str_result: str = json.dumps({"str_file": str_output_path}) sys.stdout.write("RENDER_DONE:" + str_result + "\n") sys.stdout.flush() except Exception as obj_err: str_error: str = json.dumps({"str_error": str(obj_err)}) sys.stdout.write("RENDER_ERROR:" + str_error + "\n") sys.stdout.flush() obj_logger.error("Erreur rendu : %s", str(obj_err)) def main() -> None: """Boucle principale du daemon de rendu.""" sys.stdout.write("DAEMON_READY\n") sys.stdout.flush() for str_line in sys.stdin: str_line = str_line.strip() if not str_line: continue try: obj_cmd: dict = json.loads(str_line) except json.JSONDecodeError as obj_err: str_error: str = json.dumps({"str_error": "JSON invalide : " + str(obj_err)}) sys.stdout.write("RENDER_ERROR:" + str_error + "\n") sys.stdout.flush() continue str_cmd: str = obj_cmd.get("str_cmd", "") if str_cmd == "quit": break elif str_cmd == "render": process_render(obj_cmd) else: str_error = json.dumps({"str_error": "Commande inconnue : " + str_cmd}) sys.stdout.write("RENDER_ERROR:" + str_error + "\n") sys.stdout.flush() main()