const { app, BrowserWindow, ipcMain, dialog } = require("electron"); const path = require("path"); const fs = require("fs"); const CameraParser = require("./src/main/CameraParser.js"); const QueueManager = require("./src/main/QueueManager.js"); const ConfigManager = require("./src/main/ConfigManager.js"); const UpdateManager = require("./src/main/UpdateManager.js"); const PathResolver = require("./src/main/PathResolver.js"); let obj_main_window = null; let obj_queue_manager = null; let obj_notification_config = { is_notify_each_image: false, is_notify_all_done: true }; const STR_NOTIFICATION_CONFIG_FILE = "notification_config.json"; const _load_notification_config = () => { let str_config_path = path.join(app.getPath("userData"), STR_NOTIFICATION_CONFIG_FILE); try { if (fs.existsSync(str_config_path)) { let str_content = fs.readFileSync(str_config_path, "utf8"); obj_notification_config = JSON.parse(str_content); } } catch (obj_err) { console.error("Erreur lecture config notifications :", obj_err.message); } }; const _save_notification_config = () => { let str_config_path = path.join(app.getPath("userData"), STR_NOTIFICATION_CONFIG_FILE); try { fs.writeFileSync(str_config_path, JSON.stringify(obj_notification_config, null, 4), "utf8"); } catch (obj_err) { console.error("Erreur sauvegarde config notifications :", obj_err.message); } }; const create_window = () => { obj_main_window = new BrowserWindow({ width: 1400, height: 900, minWidth: 1000, minHeight: 700, webPreferences: { preload: path.join(__dirname, "preload.js"), contextIsolation: true, nodeIntegration: false, }, title: "Multi Render Blender", }); obj_main_window.loadFile(path.join(__dirname, "src", "renderer", "index.html")); obj_queue_manager = new QueueManager(obj_main_window); PathResolver.load_saved_path(); _load_notification_config(); obj_queue_manager.set_notification_config(obj_notification_config); UpdateManager.init(obj_main_window); obj_main_window.webContents.on("did-finish-load", () => { UpdateManager.check_for_updates(); obj_main_window.webContents.send("blender-path-status", PathResolver.get_status()); }); }; // ── App lifecycle ────────────────────────────────────────────── app.whenReady().then(() => { create_window(); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { create_window(); } }); }); app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); } }); // ── IPC Handlers ─────────────────────────────────────────────── ipcMain.handle("select-blend-file", () => { return dialog.showOpenDialog(obj_main_window, { title: "Selectionner un fichier Blender", filters: [ { name: "Blender Files", extensions: ["blend"] }, ], properties: ["openFile"], }) .then((obj_result) => { if (obj_result.canceled || obj_result.filePaths.length === 0) { return null; } return obj_result.filePaths[0]; }); }); ipcMain.handle("get-cameras", (event, str_blend_path) => { return CameraParser.list_cameras(str_blend_path); }); ipcMain.handle("start-render", (event, obj_config) => { return obj_queue_manager.start(obj_config); }); ipcMain.handle("check-queue", (event, obj_config) => { return obj_queue_manager.check_queue(obj_config); }); ipcMain.handle("pause-render", () => { return obj_queue_manager.pause(); }); ipcMain.handle("stop-render", () => { return obj_queue_manager.stop(); }); ipcMain.handle("save-config", (event, obj_config) => { return ConfigManager.save(obj_main_window, obj_config); }); ipcMain.handle("load-config", () => { return ConfigManager.load(obj_main_window); }); ipcMain.handle("read-image", (event, str_image_path) => { let NB_MAX_RETRIES = 10; let NB_RETRY_DELAY = 500; let fn_try_read = (nb_attempt) => { return new Promise((resolve, reject) => { if (!fs.existsSync(str_image_path)) { if (nb_attempt < NB_MAX_RETRIES) { setTimeout(() => { fn_try_read(nb_attempt + 1).then(resolve).catch(reject); }, NB_RETRY_DELAY); return; } reject(new Error("Fichier introuvable apres " + NB_MAX_RETRIES + " tentatives : " + str_image_path)); return; } // Wait for file size to stabilize (file fully written) let nb_size_prev = -1; let fn_check_stable = () => { let obj_stats = fs.statSync(str_image_path); if (obj_stats.size === 0 || obj_stats.size !== nb_size_prev) { nb_size_prev = obj_stats.size; if (nb_attempt < NB_MAX_RETRIES) { setTimeout(() => { nb_attempt++; fn_check_stable(); }, NB_RETRY_DELAY); return; } if (obj_stats.size === 0) { reject(new Error("Le fichier est vide apres " + NB_MAX_RETRIES + " tentatives : " + str_image_path)); return; } } fs.readFile(str_image_path, (obj_err, obj_buffer) => { if (obj_err) { reject(new Error("Impossible de lire l'image : " + obj_err.message)); return; } let str_ext = path.extname(str_image_path).toLowerCase().replace(".", ""); let str_mime = "image/png"; if (str_ext === "jpg" || str_ext === "jpeg") { str_mime = "image/jpeg"; } else if (str_ext === "bmp") { str_mime = "image/bmp"; } else if (str_ext === "tiff" || str_ext === "tif") { str_mime = "image/tiff"; } else if (str_ext === "exr") { str_mime = "image/x-exr"; } let str_base64 = obj_buffer.toString("base64"); resolve("data:" + str_mime + ";base64," + str_base64); }); }; fn_check_stable(); }); }; return fn_try_read(0); }); ipcMain.handle("get-blender-path", () => { return PathResolver.get_status(); }); ipcMain.handle("set-blender-path", (event, str_path) => { let obj_result = PathResolver.set_blender_path(str_path); if (obj_result.is_success) { obj_main_window.webContents.send("blender-path-status", PathResolver.get_status()); } return obj_result; }); ipcMain.handle("select-blender-exe", () => { let str_exe_name = process.platform === "win32" ? "blender.exe" : "blender"; let list_filters = process.platform === "win32" ? [{ name: "Blender", extensions: ["exe"] }] : [{ name: "Blender", extensions: ["*"] }]; return dialog.showOpenDialog(obj_main_window, { title: "Selectionner l'executable Blender", filters: list_filters, properties: ["openFile"], }) .then((obj_result) => { if (obj_result.canceled || obj_result.filePaths.length === 0) { return null; } return obj_result.filePaths[0]; }); }); ipcMain.handle("check-for-updates", () => { return UpdateManager.check_for_updates(); }); ipcMain.handle("apply-update", (event, str_tag_name) => { return UpdateManager.download_and_apply(str_tag_name); }); ipcMain.handle("select-output-folder", () => { return dialog.showOpenDialog(obj_main_window, { title: "Selectionner le dossier de sortie", properties: ["openDirectory"], }) .then((obj_result) => { if (obj_result.canceled || obj_result.filePaths.length === 0) { return null; } return obj_result.filePaths[0]; }); }); // ── Notification Config ─────────────────────────────────────── ipcMain.handle("get-notification-config", () => { return obj_notification_config; }); ipcMain.handle("set-notification-config", (event, obj_config) => { obj_notification_config = obj_config; _save_notification_config(); obj_queue_manager.set_notification_config(obj_notification_config); return { is_success: true }; });