temps restant + video + download link + left click + show next frame done
This commit is contained in:
142
src/python/render_daemon.py
Normal file
142
src/python/render_daemon.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user