const { spawn } = require("child_process"); const PathResolver = require("./PathResolver.js"); const BlenderProcess = { render_frame: (obj_params) => { let str_blend_path = obj_params.str_blend_path; let str_camera_name = obj_params.str_camera_name; let nb_frame = obj_params.nb_frame; let nb_resolution_x = obj_params.nb_resolution_x; let nb_resolution_y = obj_params.nb_resolution_y; let str_format = obj_params.str_format; let str_output_path = obj_params.str_output_path; let str_safe_name = str_camera_name.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); let list_python_lines = [ "import bpy", "scene=bpy.context.scene", "scene.camera=bpy.data.objects['" + str_safe_name + "']", "scene.render.resolution_x=" + nb_resolution_x, "scene.render.resolution_y=" + nb_resolution_y, ]; if (obj_params.obj_render_settings) { let obj_rs = obj_params.obj_render_settings; if (obj_rs.str_engine) { list_python_lines.push("scene.render.engine='" + obj_rs.str_engine + "'"); } if (obj_rs.nb_resolution_percentage !== undefined) { list_python_lines.push("scene.render.resolution_percentage=" + obj_rs.nb_resolution_percentage); } if (obj_rs.is_film_transparent !== undefined) { list_python_lines.push("scene.render.film_transparent=" + (obj_rs.is_film_transparent ? "True" : "False")); } if (obj_rs.str_engine === "CYCLES") { if (obj_rs.nb_samples !== undefined) { list_python_lines.push("scene.cycles.samples=" + obj_rs.nb_samples); } if (obj_rs.str_device) { list_python_lines.push("scene.cycles.device='" + obj_rs.str_device + "'"); } if (obj_rs.is_denoise !== undefined) { list_python_lines.push("scene.cycles.use_denoising=" + (obj_rs.is_denoise ? "True" : "False")); } } else if (obj_rs.str_engine === "BLENDER_EEVEE" || obj_rs.str_engine === "BLENDER_EEVEE_NEXT") { if (obj_rs.nb_samples !== undefined) { list_python_lines.push("try:\n scene.eevee.taa_render_samples=" + obj_rs.nb_samples + "\nexcept:\n try:\n scene.eevee.samples=" + obj_rs.nb_samples + "\n except:pass"); } } } let str_python_expr = list_python_lines.join(";"); if (obj_params.list_collections && obj_params.list_collections.length > 0) { str_python_expr += "\ndef _slc(lc,n,v):" + "\n for c in lc.children:" + "\n if c.name==n:c.exclude=v;return" + "\n _slc(c,n,v)\n"; for (let obj_col of obj_params.list_collections) { let str_col_safe = obj_col.str_name.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); let str_val = obj_col.is_hide_render ? "True" : "False"; str_python_expr += "bpy.data.collections['" + str_col_safe + "'].hide_render=" + str_val + ";_slc(bpy.context.view_layer.layer_collection,'" + str_col_safe + "'," + str_val + ")\n"; } } let list_args = [ "-b", str_blend_path, "--python-expr", str_python_expr, "-o", str_output_path, "-F", str_format, "-f", String(nb_frame), ]; return new Promise((resolve, reject) => { let str_stdout = ""; let str_stderr = ""; let obj_process = spawn(PathResolver.get_blender_path(), list_args); obj_process.stdout.on("data", (obj_data) => { str_stdout += obj_data.toString(); if (obj_params.fn_on_stdout) { obj_params.fn_on_stdout(obj_data.toString()); } }); obj_process.stderr.on("data", (obj_data) => { str_stderr += obj_data.toString(); }); obj_process.on("close", (nb_code) => { if (nb_code !== 0) { reject({ str_message: "Blender a quitte avec le code " + nb_code, str_stderr: str_stderr, str_stdout: str_stdout, }); return; } let str_rendered_file = BlenderProcess._find_rendered_file(str_stdout); resolve({ str_rendered_file: str_rendered_file, str_stdout: str_stdout, }); }); obj_process.on("error", (obj_err) => { reject({ str_message: "Impossible de lancer Blender : " + obj_err.message, str_stderr: "", str_stdout: "", }); }); // Store process reference for kill support if (obj_params.fn_on_process) { obj_params.fn_on_process(obj_process); } }); }, _find_rendered_file: (str_stdout) => { let list_lines = str_stdout.split("\n"); for (let str_line of list_lines) { let nb_index = str_line.indexOf("Saved: "); if (nb_index !== -1) { let str_path = str_line.substring(nb_index + 7).trim(); // Remove trailing info like " Time: 00:01.23" let nb_time_index = str_path.indexOf(" Time:"); if (nb_time_index !== -1) { str_path = str_path.substring(0, nb_time_index).trim(); } // Blender 5.x wraps path in single quotes: Saved: '/path/file.png' if (str_path.startsWith("'") && str_path.endsWith("'")) { str_path = str_path.substring(1, str_path.length - 1); } return str_path; } } return null; }, }; module.exports = BlenderProcess;