v1
This commit is contained in:
300
src/main/QueueManager.js
Normal file
300
src/main/QueueManager.js
Normal file
@@ -0,0 +1,300 @@
|
||||
const BlenderProcess = require("./BlenderProcess.js");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const STR_STATUS_IDLE = "idle";
|
||||
const STR_STATUS_RUNNING = "running";
|
||||
const STR_STATUS_PAUSED = "paused";
|
||||
|
||||
class QueueManager {
|
||||
constructor(obj_window) {
|
||||
this.obj_window = obj_window;
|
||||
this.list_queue = [];
|
||||
this.nb_current_index = 0;
|
||||
this.str_status = STR_STATUS_IDLE;
|
||||
this.obj_current_process = null;
|
||||
this.nb_last_render_ms = 0;
|
||||
this.str_last_image_path = null;
|
||||
}
|
||||
|
||||
start(obj_config) {
|
||||
if (this.str_status === STR_STATUS_PAUSED) {
|
||||
this.str_status = STR_STATUS_RUNNING;
|
||||
this._send_log("Reprise du rendu...");
|
||||
this._process_next();
|
||||
return Promise.resolve({ is_success: true });
|
||||
}
|
||||
|
||||
this.list_queue = this._build_queue(obj_config);
|
||||
this.nb_current_index = 0;
|
||||
this.str_status = STR_STATUS_RUNNING;
|
||||
this.nb_last_render_ms = 0;
|
||||
this.str_last_image_path = null;
|
||||
this.str_overwrite_mode = obj_config.str_overwrite_mode || "overwrite";
|
||||
|
||||
this._send_log("File de rendu construite : " + this.list_queue.length + " elements.");
|
||||
this._send_progress();
|
||||
this._process_next();
|
||||
|
||||
return Promise.resolve({ is_success: true, nb_total: this.list_queue.length });
|
||||
}
|
||||
|
||||
pause() {
|
||||
if (this.str_status !== STR_STATUS_RUNNING) {
|
||||
return Promise.resolve({ is_success: false });
|
||||
}
|
||||
this.str_status = STR_STATUS_PAUSED;
|
||||
this._send_log("Rendu en pause.");
|
||||
return Promise.resolve({ is_success: true });
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.str_status = STR_STATUS_IDLE;
|
||||
|
||||
if (this.obj_current_process) {
|
||||
this.obj_current_process.kill("SIGTERM");
|
||||
this.obj_current_process = null;
|
||||
}
|
||||
|
||||
this._send_log("Rendu arrete.");
|
||||
return Promise.resolve({ is_success: true });
|
||||
}
|
||||
|
||||
_build_queue(obj_config) {
|
||||
let list_queue = [];
|
||||
let str_blend_path = obj_config.str_blend_file;
|
||||
let str_mode = obj_config.str_render_mode;
|
||||
let str_base_output = obj_config.str_output_path;
|
||||
let str_output_mode = obj_config.str_output_mode || "subfolder";
|
||||
let list_cameras = obj_config.list_cameras;
|
||||
|
||||
let list_enabled = [];
|
||||
for (let obj_cam of list_cameras) {
|
||||
if (obj_cam.is_enabled) {
|
||||
list_enabled.push(obj_cam);
|
||||
}
|
||||
}
|
||||
|
||||
if (str_mode === "camera_by_camera") {
|
||||
for (let obj_cam of list_enabled) {
|
||||
for (let nb_frame = obj_cam.nb_frame_start; nb_frame <= obj_cam.nb_frame_end; nb_frame++) {
|
||||
list_queue.push(this._create_queue_item(str_blend_path, str_base_output, str_output_mode, obj_cam, nb_frame));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let nb_min_frame = Infinity;
|
||||
let nb_max_frame = -Infinity;
|
||||
for (let obj_cam of list_enabled) {
|
||||
if (obj_cam.nb_frame_start < nb_min_frame) {
|
||||
nb_min_frame = obj_cam.nb_frame_start;
|
||||
}
|
||||
if (obj_cam.nb_frame_end > nb_max_frame) {
|
||||
nb_max_frame = obj_cam.nb_frame_end;
|
||||
}
|
||||
}
|
||||
|
||||
for (let nb_frame = nb_min_frame; nb_frame <= nb_max_frame; nb_frame++) {
|
||||
for (let obj_cam of list_enabled) {
|
||||
if (nb_frame >= obj_cam.nb_frame_start && nb_frame <= obj_cam.nb_frame_end) {
|
||||
list_queue.push(this._create_queue_item(str_blend_path, str_base_output, str_output_mode, obj_cam, nb_frame));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list_queue;
|
||||
}
|
||||
|
||||
_create_queue_item(str_blend_path, str_base_output, str_output_mode, obj_cam, nb_frame) {
|
||||
let str_padded_frame = String(nb_frame).padStart(5, "0");
|
||||
let str_ext = obj_cam.str_format.toLowerCase();
|
||||
if (str_ext === "open_exr") {
|
||||
str_ext = "exr";
|
||||
} else if (str_ext === "jpeg") {
|
||||
str_ext = "jpg";
|
||||
} else if (str_ext === "tiff") {
|
||||
str_ext = "tif";
|
||||
}
|
||||
|
||||
let str_output_path = "";
|
||||
let str_expected_file = "";
|
||||
|
||||
if (str_output_mode === "prefix") {
|
||||
// Flat: /sortie/Camera.001_frame_#####
|
||||
str_output_path = path.join(str_base_output, obj_cam.str_name + "_frame_#####");
|
||||
str_expected_file = path.join(str_base_output, obj_cam.str_name + "_frame_" + str_padded_frame + "." + str_ext);
|
||||
} else {
|
||||
// Subfolder: /sortie/Camera.001/frame_#####
|
||||
let str_output_dir = path.join(str_base_output, obj_cam.str_name);
|
||||
str_output_path = path.join(str_output_dir, "frame_#####");
|
||||
str_expected_file = path.join(str_output_dir, "frame_" + str_padded_frame + "." + str_ext);
|
||||
}
|
||||
|
||||
return {
|
||||
str_blend_path: str_blend_path,
|
||||
str_camera_name: obj_cam.str_name,
|
||||
nb_frame: nb_frame,
|
||||
nb_resolution_x: obj_cam.nb_resolution_x,
|
||||
nb_resolution_y: obj_cam.nb_resolution_y,
|
||||
str_format: obj_cam.str_format,
|
||||
str_output_path: str_output_path,
|
||||
str_expected_file: str_expected_file,
|
||||
str_status: "pending",
|
||||
};
|
||||
}
|
||||
|
||||
_process_next() {
|
||||
if (this.str_status !== STR_STATUS_RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Batch skip : boucle iterative pour eviter un stack overflow recursif
|
||||
let nb_skip_count = 0;
|
||||
while (this.nb_current_index < this.list_queue.length && this.str_overwrite_mode === "skip") {
|
||||
let obj_check = this.list_queue[this.nb_current_index];
|
||||
if (!fs.existsSync(obj_check.str_expected_file)) {
|
||||
break;
|
||||
}
|
||||
let obj_stats = fs.statSync(obj_check.str_expected_file);
|
||||
if (obj_stats.size === 0) {
|
||||
this._send_log("Placeholder vide detecte, re-rendu : " + obj_check.str_camera_name + " F" + obj_check.nb_frame);
|
||||
break;
|
||||
}
|
||||
obj_check.str_status = "skipped";
|
||||
this.nb_current_index++;
|
||||
nb_skip_count++;
|
||||
}
|
||||
|
||||
if (nb_skip_count > 0) {
|
||||
this._send_log("Skip : " + nb_skip_count + " fichier(s) existant(s)");
|
||||
this._send_progress();
|
||||
}
|
||||
|
||||
if (this.nb_current_index >= this.list_queue.length) {
|
||||
this.str_status = STR_STATUS_IDLE;
|
||||
this._send_log("Tous les rendus sont termines !");
|
||||
this._send_event("render-complete", { is_all_done: true });
|
||||
return;
|
||||
}
|
||||
|
||||
let obj_item = this.list_queue[this.nb_current_index];
|
||||
obj_item.str_status = "rendering";
|
||||
|
||||
if (this.str_overwrite_mode === "skip") {
|
||||
try {
|
||||
let str_dir = path.dirname(obj_item.str_expected_file);
|
||||
if (!fs.existsSync(str_dir)) {
|
||||
fs.mkdirSync(str_dir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(obj_item.str_expected_file, "");
|
||||
} catch (obj_file_err) {
|
||||
this._send_log("ERREUR creation placeholder : " + obj_file_err.message);
|
||||
}
|
||||
}
|
||||
|
||||
this._send_log("Rendu : " + obj_item.str_camera_name + " - Frame " + obj_item.nb_frame);
|
||||
this._send_progress();
|
||||
|
||||
let nb_start = Date.now();
|
||||
|
||||
BlenderProcess.render_frame({
|
||||
str_blend_path: obj_item.str_blend_path,
|
||||
str_camera_name: obj_item.str_camera_name,
|
||||
nb_frame: obj_item.nb_frame,
|
||||
nb_resolution_x: obj_item.nb_resolution_x,
|
||||
nb_resolution_y: obj_item.nb_resolution_y,
|
||||
str_format: obj_item.str_format,
|
||||
str_output_path: obj_item.str_output_path,
|
||||
fn_on_stdout: (str_data) => {
|
||||
this._send_log(str_data.trim());
|
||||
},
|
||||
fn_on_process: (obj_process) => {
|
||||
this.obj_current_process = obj_process;
|
||||
},
|
||||
})
|
||||
.then((obj_result) => {
|
||||
this.obj_current_process = null;
|
||||
|
||||
if (this.str_status !== STR_STATUS_RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
obj_item.str_status = "done";
|
||||
this.nb_last_render_ms = Date.now() - nb_start;
|
||||
|
||||
let str_image = obj_result.str_rendered_file || obj_item.str_expected_file;
|
||||
this.str_last_image_path = str_image;
|
||||
this._send_event("preview-update", str_image);
|
||||
this._send_log("Termine : " + str_image);
|
||||
|
||||
this.nb_current_index++;
|
||||
this._send_progress();
|
||||
this._process_next();
|
||||
})
|
||||
.catch((obj_err) => {
|
||||
this.obj_current_process = null;
|
||||
|
||||
if (this.str_overwrite_mode === "skip" && fs.existsSync(obj_item.str_expected_file)) {
|
||||
try {
|
||||
let obj_stats = fs.statSync(obj_item.str_expected_file);
|
||||
if (obj_stats.size === 0) {
|
||||
fs.unlinkSync(obj_item.str_expected_file);
|
||||
}
|
||||
} catch (obj_cleanup_err) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
if (this.str_status !== STR_STATUS_RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
obj_item.str_status = "error";
|
||||
this.nb_last_render_ms = Date.now() - nb_start;
|
||||
this.str_last_image_path = null;
|
||||
|
||||
this._send_log("ERREUR : " + obj_err.str_message);
|
||||
this._send_event("render-error", {
|
||||
str_camera: obj_item.str_camera_name,
|
||||
nb_frame: obj_item.nb_frame,
|
||||
str_error: obj_err.str_message,
|
||||
});
|
||||
|
||||
this.nb_current_index++;
|
||||
this._send_progress();
|
||||
this._process_next();
|
||||
});
|
||||
}
|
||||
|
||||
_send_progress() {
|
||||
let obj_item = this.list_queue[this.nb_current_index] || {};
|
||||
let list_skipped = [];
|
||||
for (let nb_i = 0; nb_i < this.list_queue.length; nb_i++) {
|
||||
if (this.list_queue[nb_i].str_status === "skipped") {
|
||||
list_skipped.push(nb_i);
|
||||
}
|
||||
}
|
||||
this._send_event("render-progress", {
|
||||
nb_current: this.nb_current_index,
|
||||
nb_total: this.list_queue.length,
|
||||
str_camera: obj_item.str_camera_name || "-",
|
||||
nb_frame: obj_item.nb_frame || 0,
|
||||
str_status: this.str_status,
|
||||
nb_last_render_ms: this.nb_last_render_ms,
|
||||
str_last_image_path: this.str_last_image_path,
|
||||
list_skipped: list_skipped,
|
||||
});
|
||||
}
|
||||
|
||||
_send_log(str_message) {
|
||||
this._send_event("log", str_message);
|
||||
}
|
||||
|
||||
_send_event(str_channel, obj_data) {
|
||||
if (this.obj_window && !this.obj_window.isDestroyed()) {
|
||||
this.obj_window.webContents.send(str_channel, obj_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = QueueManager;
|
||||
Reference in New Issue
Block a user