143 lines
5.0 KiB
Python
143 lines
5.0 KiB
Python
"""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()
|