v1.1.0 — chemin Blender configurable depuis l'UI

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 <noreply@anthropic.com>
This commit is contained in:
sorlinv
2026-02-25 16:44:04 +01:00
parent f31f5aa605
commit d5cb63a27b
8 changed files with 393 additions and 38 deletions

View File

@@ -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
}
}