v1
This commit is contained in:
103
src/main/BlenderProcess.js
Normal file
103
src/main/BlenderProcess.js
Normal file
@@ -0,0 +1,103 @@
|
||||
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 str_python_expr = [
|
||||
"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,
|
||||
].join(";");
|
||||
|
||||
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;
|
||||
72
src/main/CameraParser.js
Normal file
72
src/main/CameraParser.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const { spawn } = require("child_process");
|
||||
const PathResolver = require("./PathResolver.js");
|
||||
|
||||
const STR_CAMERA_MARKER = "CAMERAS_JSON:";
|
||||
|
||||
const STR_PYTHON_EXPR = [
|
||||
"import bpy, json, sys;",
|
||||
"cams=[o.name for o in bpy.data.objects if o.type=='CAMERA'];",
|
||||
"sys.stdout.write('CAMERAS_JSON:' + json.dumps(cams) + '\\n');",
|
||||
"sys.stdout.flush()",
|
||||
].join("");
|
||||
|
||||
const CameraParser = {
|
||||
list_cameras: (str_blend_path) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let str_stdout = "";
|
||||
let str_stderr = "";
|
||||
|
||||
let obj_process = spawn(PathResolver.get_blender_path(), [
|
||||
"-b", str_blend_path,
|
||||
"--python-expr", STR_PYTHON_EXPR,
|
||||
]);
|
||||
|
||||
obj_process.stdout.on("data", (obj_data) => {
|
||||
str_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(new Error("Blender a quitte avec le code " + nb_code + " : " + str_stderr));
|
||||
return;
|
||||
}
|
||||
|
||||
let list_cameras = CameraParser._parse_camera_output(str_stdout);
|
||||
if (list_cameras === null) {
|
||||
console.error("[CameraParser] Stdout Blender:\n" + str_stdout.substring(str_stdout.length - 2000));
|
||||
reject(new Error("Impossible de parser les cameras. Verifiez que le fichier .blend contient des cameras."));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(list_cameras);
|
||||
});
|
||||
|
||||
obj_process.on("error", (obj_err) => {
|
||||
reject(new Error("Impossible de lancer Blender. Verifiez qu'il est installe et accessible dans le PATH. " + obj_err.message));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_parse_camera_output: (str_stdout) => {
|
||||
let list_lines = str_stdout.split("\n");
|
||||
for (let str_line of list_lines) {
|
||||
let nb_index = str_line.indexOf(STR_CAMERA_MARKER);
|
||||
if (nb_index !== -1) {
|
||||
let str_json = str_line.substring(nb_index + STR_CAMERA_MARKER.length).trim();
|
||||
try {
|
||||
return JSON.parse(str_json);
|
||||
} catch (obj_err) {
|
||||
console.error("[CameraParser] JSON parse error:", obj_err.message, "raw:", str_json);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = CameraParser;
|
||||
66
src/main/ConfigManager.js
Normal file
66
src/main/ConfigManager.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const fs = require("fs");
|
||||
const { dialog } = require("electron");
|
||||
|
||||
const ConfigManager = {
|
||||
save: (obj_window, obj_config) => {
|
||||
return dialog.showSaveDialog(obj_window, {
|
||||
title: "Sauvegarder la configuration",
|
||||
defaultPath: "render_config.json",
|
||||
filters: [
|
||||
{ name: "Configuration JSON", extensions: ["json"] },
|
||||
],
|
||||
})
|
||||
.then((obj_result) => {
|
||||
if (obj_result.canceled || !obj_result.filePath) {
|
||||
return { is_success: false };
|
||||
}
|
||||
|
||||
let str_json = JSON.stringify(obj_config, null, 4);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(obj_result.filePath, str_json, "utf8", (obj_err) => {
|
||||
if (obj_err) {
|
||||
reject(new Error("Impossible de sauvegarder : " + obj_err.message));
|
||||
return;
|
||||
}
|
||||
resolve({ is_success: true, str_path: obj_result.filePath });
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
load: (obj_window) => {
|
||||
return dialog.showOpenDialog(obj_window, {
|
||||
title: "Charger une configuration",
|
||||
filters: [
|
||||
{ name: "Configuration JSON", extensions: ["json"] },
|
||||
],
|
||||
properties: ["openFile"],
|
||||
})
|
||||
.then((obj_result) => {
|
||||
if (obj_result.canceled || obj_result.filePaths.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let str_file_path = obj_result.filePaths[0];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(str_file_path, "utf8", (obj_err, str_data) => {
|
||||
if (obj_err) {
|
||||
reject(new Error("Impossible de lire : " + obj_err.message));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let obj_config = JSON.parse(str_data);
|
||||
resolve(obj_config);
|
||||
} catch (obj_parse_err) {
|
||||
reject(new Error("Fichier corrompu : " + obj_parse_err.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ConfigManager;
|
||||
52
src/main/PathResolver.js
Normal file
52
src/main/PathResolver.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const STR_EXE_NAME = process.platform === "win32" ? "blender.exe" : "blender";
|
||||
|
||||
const PathResolver = {
|
||||
_str_blender_path: null,
|
||||
|
||||
get_blender_path: () => {
|
||||
if (PathResolver._str_blender_path) {
|
||||
return PathResolver._str_blender_path;
|
||||
}
|
||||
|
||||
// Mode package : resources/blender/
|
||||
let str_resources_dir = path.join(process.resourcesPath, "blender");
|
||||
let str_found = PathResolver._find_in_dir(str_resources_dir);
|
||||
if (str_found) {
|
||||
PathResolver._str_blender_path = str_found;
|
||||
return str_found;
|
||||
}
|
||||
|
||||
// Mode dev : racine projet/blender/
|
||||
let str_dev_dir = path.join(__dirname, "..", "..", "blender");
|
||||
str_found = PathResolver._find_in_dir(str_dev_dir);
|
||||
if (str_found) {
|
||||
PathResolver._str_blender_path = str_found;
|
||||
return str_found;
|
||||
}
|
||||
|
||||
// Fallback : PATH systeme
|
||||
PathResolver._str_blender_path = "blender";
|
||||
return "blender";
|
||||
},
|
||||
|
||||
_find_in_dir: (str_dir) => {
|
||||
if (!fs.existsSync(str_dir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let list_entries = fs.readdirSync(str_dir);
|
||||
for (let str_entry of list_entries) {
|
||||
let str_exe = path.join(str_dir, str_entry, STR_EXE_NAME);
|
||||
if (fs.existsSync(str_exe)) {
|
||||
return str_exe;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = PathResolver;
|
||||
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