From d5cb63a27b56d8157460274cb1041c9776daa40e Mon Sep 17 00:00:00 2001 From: sorlinv Date: Wed, 25 Feb 2026 16:44:04 +0100 Subject: [PATCH] =?UTF-8?q?v1.1.0=20=E2=80=94=20chemin=20Blender=20configu?= =?UTF-8?q?rable=20depuis=20l'UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refonte de PathResolver : auto-detection systeme (which/where + chemins courants), persistance du chemin dans userData, validation du fichier. Ajout d'un modal Bootstrap pour configurer le chemin manuellement. Badge vert/rouge dans la navbar indiquant le statut Blender. Le modal s'ouvre automatiquement si Blender n'est pas trouve au lancement. Suppression de extraResources (plus de Blender embarque dans le build). Co-Authored-By: Claude Opus 4.6 --- main.js | 35 +++++ package.json | 7 - preload.js | 18 +++ src/main/PathResolver.js | 165 +++++++++++++++++++---- src/renderer/index.html | 6 +- src/renderer/scripts/App.js | 1 + src/renderer/scripts/BlenderPath.js | 197 ++++++++++++++++++++++++++++ version.json | 2 +- 8 files changed, 393 insertions(+), 38 deletions(-) create mode 100644 src/renderer/scripts/BlenderPath.js diff --git a/main.js b/main.js index 160bc85..2f55529 100644 --- a/main.js +++ b/main.js @@ -5,6 +5,7 @@ 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; @@ -27,9 +28,12 @@ const create_window = () => { obj_queue_manager = new QueueManager(obj_main_window); + PathResolver.load_saved_path(); + 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()); }); }; @@ -159,6 +163,37 @@ ipcMain.handle("read-image", (event, str_image_path) => { 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(); }); diff --git a/package.json b/package.json index 70ff36a..3f4d62e 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,6 @@ "src/**/*", "version.json" ], - "extraResources": [ - { - "from": "blender", - "to": "blender", - "filter": ["**/*"] - } - ], "linux": { "target": "dir" }, diff --git a/preload.js b/preload.js index 346f5e6..bfc7284 100644 --- a/preload.js +++ b/preload.js @@ -67,6 +67,24 @@ contextBridge.exposeInMainWorld("api", { }); }, + get_blender_path: () => { + return ipcRenderer.invoke("get-blender-path"); + }, + + set_blender_path: (str_path) => { + return ipcRenderer.invoke("set-blender-path", str_path); + }, + + select_blender_exe: () => { + return ipcRenderer.invoke("select-blender-exe"); + }, + + on_blender_path_status: (fn_callback) => { + ipcRenderer.on("blender-path-status", (event, obj_data) => { + fn_callback(obj_data); + }); + }, + check_for_updates: () => { return ipcRenderer.invoke("check-for-updates"); }, diff --git a/src/main/PathResolver.js b/src/main/PathResolver.js index f663141..e2ccf2f 100644 --- a/src/main/PathResolver.js +++ b/src/main/PathResolver.js @@ -1,47 +1,154 @@ const path = require("path"); const fs = require("fs"); +const { app } = require("electron"); +const { execFileSync } = require("child_process"); const STR_EXE_NAME = process.platform === "win32" ? "blender.exe" : "blender"; +const STR_CONFIG_FILE = "blender_path.json"; const PathResolver = { _str_blender_path: null, + _is_found: false, - get_blender_path: () => { - if (PathResolver._str_blender_path) { - return PathResolver._str_blender_path; + load_saved_path: () => { + let str_config_path = PathResolver._get_config_path(); + + try { + if (fs.existsSync(str_config_path)) { + let str_content = fs.readFileSync(str_config_path, "utf8"); + let obj_data = JSON.parse(str_content); + + if (obj_data.str_path && fs.existsSync(obj_data.str_path)) { + PathResolver._str_blender_path = obj_data.str_path; + PathResolver._is_found = true; + return; + } + } + } catch (obj_err) { + console.error("PathResolver: impossible de lire la config :", obj_err.message); } - // 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; + let str_detected = PathResolver.auto_detect(); + if (str_detected) { + PathResolver._str_blender_path = str_detected; + PathResolver._is_found = true; + } else { + PathResolver._str_blender_path = "blender"; + PathResolver._is_found = false; } - - // 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; + get_blender_path: () => { + if (!PathResolver._str_blender_path) { + PathResolver.load_saved_path(); + } + return PathResolver._str_blender_path; + }, + + is_found: () => { + return PathResolver._is_found; + }, + + set_blender_path: (str_path) => { + if (!str_path || !fs.existsSync(str_path)) { + return { is_success: false, str_error: "Fichier introuvable : " + str_path }; } - 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; + PathResolver._str_blender_path = str_path; + PathResolver._is_found = true; + + let str_config_path = PathResolver._get_config_path(); + try { + let str_dir = path.dirname(str_config_path); + if (!fs.existsSync(str_dir)) { + fs.mkdirSync(str_dir, { recursive: true }); + } + fs.writeFileSync(str_config_path, JSON.stringify({ str_path: str_path }, null, 4), "utf8"); + } catch (obj_err) { + console.error("PathResolver: impossible de sauvegarder :", obj_err.message); + } + + return { is_success: true, str_path: str_path }; + }, + + auto_detect: () => { + if (process.platform === "win32") { + return PathResolver._auto_detect_windows(); + } + return PathResolver._auto_detect_linux(); + }, + + get_status: () => { + return { + str_path: PathResolver._str_blender_path || "blender", + is_found: PathResolver._is_found, + }; + }, + + // ── Private ────────────────────────────────────────────── + + _get_config_path: () => { + return path.join(app.getPath("userData"), STR_CONFIG_FILE); + }, + + _auto_detect_linux: () => { + let LIST_PATHS = [ + "/usr/bin/blender", + "/snap/bin/blender", + "/usr/local/bin/blender", + "/opt/blender/blender", + ]; + + try { + let str_result = execFileSync("which", ["blender"], { encoding: "utf8", timeout: 5000 }).trim(); + if (str_result && fs.existsSync(str_result)) { + return str_result; + } + } catch (obj_err) { + // which not found or blender not in PATH + } + + for (let str_path of LIST_PATHS) { + if (fs.existsSync(str_path)) { + return str_path; + } + } + + return null; + }, + + _auto_detect_windows: () => { + try { + let str_result = execFileSync("where", ["blender.exe"], { encoding: "utf8", timeout: 5000 }).trim(); + let str_first = str_result.split("\n")[0].trim(); + if (str_first && fs.existsSync(str_first)) { + return str_first; + } + } catch (obj_err) { + // where not found or blender not in PATH + } + + let LIST_BASES = [ + process.env.PROGRAMFILES || "C:\\Program Files", + process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", + ]; + + for (let str_base of LIST_BASES) { + let str_foundation = path.join(str_base, "Blender Foundation"); + if (!fs.existsSync(str_foundation)) { + continue; + } + + try { + let list_dirs = fs.readdirSync(str_foundation); + for (let str_dir of list_dirs) { + let str_exe = path.join(str_foundation, str_dir, STR_EXE_NAME); + if (fs.existsSync(str_exe)) { + return str_exe; + } + } + } catch (obj_err) { + // permission denied or similar } } diff --git a/src/renderer/index.html b/src/renderer/index.html index ee6b093..aa317d8 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -3,7 +3,7 @@ - + Multi Render Blender @@ -253,6 +253,9 @@ + + + @@ -261,6 +264,7 @@ + diff --git a/src/renderer/scripts/App.js b/src/renderer/scripts/App.js index e48f9aa..701a1b5 100644 --- a/src/renderer/scripts/App.js +++ b/src/renderer/scripts/App.js @@ -10,6 +10,7 @@ const App = { PreviewPanel.init(); ProgressBar.init(); UpdateBanner.init(); + BlenderPath.init(); App._bind_events(); App._bind_render_events(); diff --git a/src/renderer/scripts/BlenderPath.js b/src/renderer/scripts/BlenderPath.js new file mode 100644 index 0000000..dd362d2 --- /dev/null +++ b/src/renderer/scripts/BlenderPath.js @@ -0,0 +1,197 @@ +const BlenderPath = { + str_current_path: null, + is_found: false, + obj_modal: null, + + init: () => { + BlenderPath._create_badge(); + BlenderPath._create_modal(); + BlenderPath._bind_events(); + }, + + _create_badge: () => { + let obj_nav_right = document.querySelector("nav .d-flex.gap-2"); + let obj_badge = document.createElement("button"); + obj_badge.id = "btn_blender_status"; + obj_badge.className = "btn btn-sm btn-outline-secondary"; + obj_badge.title = "Chemin Blender"; + obj_badge.innerHTML = ''; + obj_nav_right.insertBefore(obj_badge, obj_nav_right.firstChild); + + obj_badge.addEventListener("click", () => { + BlenderPath._open_modal(); + }); + }, + + _create_modal: () => { + let obj_modal_el = document.createElement("div"); + obj_modal_el.id = "modal_blender_path"; + obj_modal_el.className = "modal fade"; + obj_modal_el.tabIndex = -1; + obj_modal_el.innerHTML = + ''; + + document.body.appendChild(obj_modal_el); + BlenderPath.obj_modal = new bootstrap.Modal(obj_modal_el); + }, + + _bind_events: () => { + let obj_btn_browse = document.getElementById("btn_browse_blender"); + obj_btn_browse.addEventListener("click", () => { + BlenderPath._browse(); + }); + + let obj_btn_detect = document.getElementById("btn_detect_blender"); + obj_btn_detect.addEventListener("click", () => { + BlenderPath._detect(); + }); + + let obj_btn_validate = document.getElementById("btn_validate_blender"); + obj_btn_validate.addEventListener("click", () => { + BlenderPath._validate(); + }); + + window.api.on_blender_path_status((obj_data) => { + BlenderPath.str_current_path = obj_data.str_path; + BlenderPath.is_found = obj_data.is_found; + BlenderPath._update_badge(); + + if (!obj_data.is_found) { + BlenderPath._open_modal(); + } + }); + }, + + _open_modal: () => { + let obj_input = document.getElementById("input_blender_path"); + obj_input.value = BlenderPath.is_found ? BlenderPath.str_current_path : ""; + BlenderPath._update_modal_status(BlenderPath.is_found, BlenderPath.str_current_path); + BlenderPath.obj_modal.show(); + + if (!BlenderPath.is_found) { + BlenderPath._detect(); + } + }, + + _browse: () => { + window.api.select_blender_exe() + .then((str_path) => { + if (!str_path) { + return; + } + let obj_input = document.getElementById("input_blender_path"); + obj_input.value = str_path; + BlenderPath._update_modal_status(true, str_path); + }) + .catch((obj_err) => { + ConsoleLog.add("Erreur selection Blender : " + obj_err.message); + }); + }, + + _detect: () => { + let obj_btn = document.getElementById("btn_detect_blender"); + obj_btn.disabled = true; + obj_btn.innerHTML = 'Detection...'; + + window.api.get_blender_path() + .then((obj_data) => { + obj_btn.disabled = false; + obj_btn.innerHTML = 'Detecter automatiquement'; + + if (obj_data.is_found) { + let obj_input = document.getElementById("input_blender_path"); + obj_input.value = obj_data.str_path; + BlenderPath._update_modal_status(true, obj_data.str_path); + } else { + BlenderPath._update_modal_status(false, null); + } + }) + .catch(() => { + obj_btn.disabled = false; + obj_btn.innerHTML = 'Detecter automatiquement'; + BlenderPath._update_modal_status(false, null); + }); + }, + + _validate: () => { + let str_path = document.getElementById("input_blender_path").value; + if (!str_path) { + return; + } + + window.api.set_blender_path(str_path) + .then((obj_result) => { + if (obj_result.is_success) { + BlenderPath.str_current_path = obj_result.str_path; + BlenderPath.is_found = true; + BlenderPath._update_badge(); + BlenderPath.obj_modal.hide(); + ConsoleLog.add("Chemin Blender configure : " + obj_result.str_path); + } else { + BlenderPath._update_modal_status(false, null); + ConsoleLog.add("Chemin Blender invalide : " + (obj_result.str_error || "")); + } + }) + .catch((obj_err) => { + ConsoleLog.add("Erreur configuration Blender : " + obj_err.message); + }); + }, + + _update_badge: () => { + let obj_badge = document.getElementById("btn_blender_status"); + if (BlenderPath.is_found) { + obj_badge.className = "btn btn-sm btn-outline-success"; + obj_badge.title = "Blender : " + BlenderPath.str_current_path; + } else { + obj_badge.className = "btn btn-sm btn-outline-danger"; + obj_badge.title = "Blender non trouve"; + } + }, + + _update_modal_status: (is_valid, str_path) => { + let obj_label = document.getElementById("label_blender_status"); + let obj_btn_validate = document.getElementById("btn_validate_blender"); + + if (is_valid && str_path) { + obj_label.className = "badge bg-success"; + obj_label.innerHTML = 'Trouve'; + obj_btn_validate.disabled = false; + } else { + obj_label.className = "badge bg-danger"; + obj_label.innerHTML = 'Non trouve'; + obj_btn_validate.disabled = true; + } + }, +}; diff --git a/version.json b/version.json index 8aadd7b..7af38cf 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "str_version": "1.0.0" + "str_version": "1.1.0" }