feat(gamut): integrate 3D gamut visualization and platform profile extension handling (v0.1.14) #74
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "iccery",
|
"name": "iccery",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iccery"
|
name = "iccery"
|
||||||
version = "0.1.13"
|
version = "0.1.14"
|
||||||
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
||||||
authors = ["Gordon"]
|
authors = ["Gordon"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
+74
-10
@@ -102,6 +102,32 @@ pub async fn detect_instruments(app: AppHandle, state: State<'_, ProcessManager>
|
|||||||
state.spawn(app, "instlist".to_string(), binary, vec![], None).await
|
state.spawn(app, "instlist".to_string(), binary, vec![], None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolve_profile_extension(cwd: &str, basename: &str) -> (String, std::path::PathBuf) {
|
||||||
|
let p_icc = std::path::Path::new(cwd).join(format!("{}.icc", basename));
|
||||||
|
let p_icm = std::path::Path::new(cwd).join(format!("{}.icm", basename));
|
||||||
|
|
||||||
|
if p_icm.exists() && !p_icc.exists() {
|
||||||
|
("icm".to_string(), p_icm)
|
||||||
|
} else if p_icc.exists() {
|
||||||
|
("icc".to_string(), p_icc)
|
||||||
|
} else {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
("icm".to_string(), p_icm)
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
("icc".to_string(), p_icc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_profile_path(cwd: String, basename: String) -> String {
|
||||||
|
let (_, path) = resolve_profile_extension(&cwd, &basename);
|
||||||
|
path.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn extract_gamut(
|
pub async fn extract_gamut(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
@@ -110,18 +136,41 @@ pub async fn extract_gamut(
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let binary = resolve_binary(app.clone(), "iccgamut".to_string()).await?;
|
let binary = resolve_binary(app.clone(), "iccgamut".to_string()).await?;
|
||||||
let path = std::path::Path::new(&icc_path);
|
let path = std::path::Path::new(&icc_path);
|
||||||
let basename = path.file_stem().unwrap().to_str().unwrap();
|
let basename = path.file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| "profile".to_string());
|
||||||
let parent_dir = path.parent().map(|p| p.to_str().unwrap()).unwrap_or("");
|
let parent_dir = path.parent().map(|p| p.to_string_lossy().to_string()).unwrap_or_default();
|
||||||
|
|
||||||
let mut args = vec!["-w".to_string()];
|
// Auto-detect .icc vs .icm if the specified path does not exist directly
|
||||||
if !parent_dir.is_empty() {
|
let resolved_path = if path.exists() {
|
||||||
args.push("-d".to_string());
|
icc_path.clone()
|
||||||
args.push(parent_dir.to_string());
|
} else {
|
||||||
}
|
let alt = if icc_path.ends_with(".icc") {
|
||||||
args.push(icc_path.clone());
|
icc_path.replace(".icc", ".icm")
|
||||||
|
} else if icc_path.ends_with(".icm") {
|
||||||
|
icc_path.replace(".icm", ".icc")
|
||||||
|
} else {
|
||||||
|
icc_path.clone()
|
||||||
|
};
|
||||||
|
if std::path::Path::new(&alt).exists() {
|
||||||
|
alt
|
||||||
|
} else {
|
||||||
|
icc_path.clone()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = vec![
|
||||||
|
"-v".to_string(),
|
||||||
|
"-d".to_string(),
|
||||||
|
"50.0".to_string(),
|
||||||
|
resolved_path,
|
||||||
|
];
|
||||||
|
let cwd = if parent_dir.is_empty() {
|
||||||
|
resolve_safe_cwd(&app, "").ok()
|
||||||
|
} else {
|
||||||
|
Some(parent_dir)
|
||||||
|
};
|
||||||
|
|
||||||
let id = format!("iccgamut_{}", basename);
|
let id = format!("iccgamut_{}", basename);
|
||||||
state.spawn(app, id, binary, args, None).await
|
state.spawn(app, id, binary, args, cwd).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
@@ -379,9 +428,24 @@ pub fn build_profcheck_args(config: &ProfcheckConfig) -> Vec<String> {
|
|||||||
pub async fn run_profcheck(
|
pub async fn run_profcheck(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
state: State<'_, ProcessManager>,
|
state: State<'_, ProcessManager>,
|
||||||
config: ProfcheckConfig,
|
mut config: ProfcheckConfig,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let binary = resolve_binary(app.clone(), "profcheck".to_string()).await?;
|
let binary = resolve_binary(app.clone(), "profcheck".to_string()).await?;
|
||||||
|
|
||||||
|
// Auto-detect .icc vs .icm if the specified icc_path does not exist directly
|
||||||
|
if !std::path::Path::new(&config.icc_path).exists() {
|
||||||
|
let alt = if config.icc_path.ends_with(".icc") {
|
||||||
|
config.icc_path.replace(".icc", ".icm")
|
||||||
|
} else if config.icc_path.ends_with(".icm") {
|
||||||
|
config.icc_path.replace(".icm", ".icc")
|
||||||
|
} else {
|
||||||
|
config.icc_path.clone()
|
||||||
|
};
|
||||||
|
if std::path::Path::new(&alt).exists() {
|
||||||
|
config.icc_path = alt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let args = build_profcheck_args(&config);
|
let args = build_profcheck_args(&config);
|
||||||
let id = format!("profcheck_{}", config.ti3_path);
|
let id = format!("profcheck_{}", config.ti3_path);
|
||||||
let cwd = Some(resolve_safe_cwd(&app, &config.cwd)?);
|
let cwd = Some(resolve_safe_cwd(&app, &config.cwd)?);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub fn run() {
|
|||||||
commands::kill_process,
|
commands::kill_process,
|
||||||
commands::resolve_binary,
|
commands::resolve_binary,
|
||||||
commands::detect_instruments,
|
commands::detect_instruments,
|
||||||
|
commands::get_profile_path,
|
||||||
commands::extract_gamut,
|
commands::extract_gamut,
|
||||||
commands::run_targen,
|
commands::run_targen,
|
||||||
commands::run_printtarg,
|
commands::run_printtarg,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "ICCery",
|
"productName": "ICCery",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"identifier": "com.gronod.iccery",
|
"identifier": "com.gronod.iccery",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
|
|||||||
+1379
File diff suppressed because it is too large
Load Diff
+36
-3
@@ -1,6 +1,7 @@
|
|||||||
const { invoke } = window.__TAURI__.core;
|
const { invoke } = window.__TAURI__.core;
|
||||||
const { listen } = window.__TAURI__.event;
|
const { listen } = window.__TAURI__.event;
|
||||||
import { setStage4Result } from './profcheck.js';
|
import { setStage4Result } from './profcheck.js';
|
||||||
|
import { loadGamutMesh } from './gamut_viewer.js';
|
||||||
|
|
||||||
let chartreadBasename = "";
|
let chartreadBasename = "";
|
||||||
let chartreadCwd = "";
|
let chartreadCwd = "";
|
||||||
@@ -80,7 +81,7 @@ export function initColprof() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const unlistenExit = await listen("process:exit", (event) => {
|
const unlistenExit = await listen("process:exit", async (event) => {
|
||||||
if (event.payload.id === processId) {
|
if (event.payload.id === processId) {
|
||||||
unlistenStdout();
|
unlistenStdout();
|
||||||
unlistenStderr();
|
unlistenStderr();
|
||||||
@@ -90,11 +91,23 @@ export function initColprof() {
|
|||||||
|
|
||||||
if (event.payload.code === 0) {
|
if (event.payload.code === 0) {
|
||||||
logPre.textContent += "\n[SUCCESS] colprof completed. Profile generated.\n";
|
logPre.textContent += "\n[SUCCESS] colprof completed. Profile generated.\n";
|
||||||
const iccFilename = `${basename}.icc`;
|
|
||||||
successInfo.textContent = `Profile: ${iccFilename} (${description})`;
|
// Resolve real profile path (.icm on Windows, .icc on Unix)
|
||||||
|
let profilePath = cwd ? `${cwd}/${basename}.icc` : `${basename}.icc`;
|
||||||
|
try {
|
||||||
|
profilePath = await invoke("get_profile_path", { cwd, basename });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not query platform profile path:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayFilename = profilePath.split(/[\\/]/).pop() || `${basename}.icc`;
|
||||||
|
successInfo.textContent = `Profile: ${displayFilename} (${description})`;
|
||||||
successCard.classList.remove("hidden");
|
successCard.classList.remove("hidden");
|
||||||
|
|
||||||
setStage4Result(basename, cwd);
|
setStage4Result(basename, cwd);
|
||||||
|
|
||||||
|
// Automatically extract gamut mesh for 3D visualization
|
||||||
|
triggerGamutExtraction(basename, cwd, profilePath);
|
||||||
} else {
|
} else {
|
||||||
logPre.textContent += `\n[ERROR] colprof exited with code ${event.payload.code}.\n`;
|
logPre.textContent += `\n[ERROR] colprof exited with code ${event.payload.code}.\n`;
|
||||||
}
|
}
|
||||||
@@ -116,6 +129,26 @@ export function initColprof() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function triggerGamutExtraction(basename, cwd, profilePath) {
|
||||||
|
try {
|
||||||
|
const processId = `iccgamut_${basename}`;
|
||||||
|
const unlistenExit = await listen("process:exit", (event) => {
|
||||||
|
if (event.payload.id === processId) {
|
||||||
|
unlistenExit();
|
||||||
|
if (event.payload.code === 0) {
|
||||||
|
const sep = cwd.includes('\\') ? '\\' : '/';
|
||||||
|
const gamFilePath = cwd ? `${cwd}${sep}${basename}.gam` : `${basename}.gam`;
|
||||||
|
loadGamutMesh(gamFilePath, 0x3b82f6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await invoke("extract_gamut", { iccPath: profilePath });
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Automated gamut extraction notice:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function advanceToStage5() {
|
function advanceToStage5() {
|
||||||
const steps = document.querySelectorAll('.step');
|
const steps = document.querySelectorAll('.step');
|
||||||
const stages = document.querySelectorAll('.stage');
|
const stages = document.querySelectorAll('.stage');
|
||||||
|
|||||||
+101
-42
@@ -2,40 +2,74 @@ const { invoke } = window.__TAURI__.core;
|
|||||||
const { listen } = window.__TAURI__.event;
|
const { listen } = window.__TAURI__.event;
|
||||||
|
|
||||||
let scene, camera, renderer, controls;
|
let scene, camera, renderer, controls;
|
||||||
let currentProfileMesh, sRgbMesh;
|
let currentProfileMesh = null;
|
||||||
|
let sRgbMesh = null;
|
||||||
|
|
||||||
export async function initGamutViewer() {
|
export async function initGamutViewer() {
|
||||||
try {
|
try {
|
||||||
const container = document.getElementById('gamutViewerContainer');
|
const container = document.getElementById('gamutViewerContainer');
|
||||||
if (!container || typeof THREE === 'undefined') return;
|
if (!container || typeof THREE === 'undefined') return;
|
||||||
|
|
||||||
|
// Clear any existing contents if re-initialized
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color(0x111116);
|
scene.background = new THREE.Color(0x111116);
|
||||||
|
|
||||||
camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 1, 1000);
|
const width = container.clientWidth > 0 ? container.clientWidth : 500;
|
||||||
camera.position.set(150, 100, 150);
|
const height = container.clientHeight > 0 ? container.clientHeight : 360;
|
||||||
|
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000);
|
||||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
camera.position.set(150, 110, 150);
|
||||||
|
|
||||||
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
renderer.setPixelRatio(window.devicePixelRatio || 1);
|
||||||
container.appendChild(renderer.domElement);
|
container.appendChild(renderer.domElement);
|
||||||
|
|
||||||
if (typeof THREE.OrbitControls !== 'undefined') {
|
if (typeof THREE.OrbitControls !== 'undefined') {
|
||||||
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||||
controls.enableDamping = true;
|
controls.enableDamping = true;
|
||||||
|
controls.dampingFactor = 0.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add CIELAB orientation helpers (Axes: X=a*, Y=L*, Z=b*)
|
// Add CIELAB orientation helpers (Axes: X=a*, Y=L*, Z=b*)
|
||||||
const axesHelper = new THREE.AxesHelper(80);
|
const axesHelper = new THREE.AxesHelper(100);
|
||||||
scene.add(axesHelper);
|
scene.add(axesHelper);
|
||||||
|
|
||||||
|
// Add grid helper at L*=0 plane
|
||||||
|
const gridHelper = new THREE.GridHelper(200, 20, 0x444455, 0x222233);
|
||||||
|
gridHelper.position.y = 0;
|
||||||
|
scene.add(gridHelper);
|
||||||
|
|
||||||
// Lights
|
// Lights
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
|
||||||
scene.add(ambientLight);
|
scene.add(ambientLight);
|
||||||
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||||
dirLight.position.set(100, 200, 100);
|
dirLight1.position.set(100, 200, 100);
|
||||||
scene.add(dirLight);
|
scene.add(dirLight1);
|
||||||
|
const dirLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
|
||||||
|
dirLight2.position.set(-100, -100, -100);
|
||||||
|
scene.add(dirLight2);
|
||||||
|
|
||||||
|
// Handle viewport resize dynamically (e.g., when switching to Stage 5 tab)
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (let entry of entries) {
|
||||||
|
const w = entry.contentRect.width;
|
||||||
|
const h = entry.contentRect.height;
|
||||||
|
if (w > 0 && h > 0 && renderer && camera) {
|
||||||
|
camera.aspect = w / h;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resizeObserver.observe(container);
|
||||||
|
|
||||||
animate();
|
animate();
|
||||||
|
|
||||||
|
// Load bundled sRGB reference gamut wireframe on startup
|
||||||
|
loadSrgbReferenceGamut();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("Gamut viewer initialization notice:", err);
|
console.warn("Gamut viewer initialization notice:", err);
|
||||||
}
|
}
|
||||||
@@ -74,42 +108,67 @@ function parseCGATS(text) {
|
|||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderGamutFromText(text, color, isWireframe, previousMesh) {
|
||||||
|
if (!scene) return null;
|
||||||
|
|
||||||
|
if (previousMesh) {
|
||||||
|
scene.remove(previousMesh);
|
||||||
|
if (previousMesh.geometry) previousMesh.geometry.dispose();
|
||||||
|
if (previousMesh.material) previousMesh.material.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const points = parseCGATS(text);
|
||||||
|
if (points.length < 3) return null;
|
||||||
|
|
||||||
|
const coords = [];
|
||||||
|
const vertices = new Float32Array(points.length * 3);
|
||||||
|
|
||||||
|
points.forEach((pt, i) => {
|
||||||
|
coords.push(pt.a, pt.b);
|
||||||
|
vertices[i * 3] = pt.a; // X = a*
|
||||||
|
vertices[i * 3 + 1] = pt.L; // Y = L*
|
||||||
|
vertices[i * 3 + 2] = pt.b; // Z = b*
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof Delaunator === 'undefined') return null;
|
||||||
|
const delaunay = new Delaunator(coords);
|
||||||
|
|
||||||
|
const geometry = new THREE.BufferGeometry();
|
||||||
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||||
|
geometry.setIndex(Array.from(delaunay.triangles));
|
||||||
|
geometry.computeVertexNormals();
|
||||||
|
|
||||||
|
const material = new THREE.MeshLambertMaterial({
|
||||||
|
color: color,
|
||||||
|
wireframe: isWireframe,
|
||||||
|
transparent: true,
|
||||||
|
opacity: isWireframe ? 0.35 : 0.75,
|
||||||
|
side: THREE.DoubleSide
|
||||||
|
});
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
scene.add(mesh);
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadSrgbReferenceGamut() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('assets/sRGB.gam');
|
||||||
|
if (response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
sRgbMesh = renderGamutFromText(text, 0x94a3b8, true, sRgbMesh);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not load sRGB reference gamut:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadGamutMesh(gamFilePath, color = 0x3b82f6, isWireframe = false) {
|
export async function loadGamutMesh(gamFilePath, color = 0x3b82f6, isWireframe = false) {
|
||||||
try {
|
try {
|
||||||
const base64Data = await invoke('read_file_base64', { path: gamFilePath });
|
const base64Data = await invoke('read_file_base64', { path: gamFilePath });
|
||||||
const text = atob(base64Data);
|
const text = atob(base64Data);
|
||||||
const points = parseCGATS(text);
|
currentProfileMesh = renderGamutFromText(text, color, isWireframe, currentProfileMesh);
|
||||||
if (points.length < 3) return null;
|
return currentProfileMesh;
|
||||||
|
|
||||||
const coords = [];
|
|
||||||
const vertices = new Float32Array(points.length * 3);
|
|
||||||
|
|
||||||
points.forEach((pt, i) => {
|
|
||||||
coords.push(pt.a, pt.b);
|
|
||||||
vertices[i * 3] = pt.a; // X = a*
|
|
||||||
vertices[i * 3 + 1] = pt.L; // Y = L*
|
|
||||||
vertices[i * 3 + 2] = pt.b; // Z = b*
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof Delaunator === 'undefined') return null;
|
|
||||||
const delaunay = new Delaunator(coords);
|
|
||||||
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
|
||||||
geometry.setIndex(Array.from(delaunay.triangles));
|
|
||||||
geometry.computeVertexNormals();
|
|
||||||
|
|
||||||
const material = new THREE.MeshLambertMaterial({
|
|
||||||
color: color,
|
|
||||||
wireframe: isWireframe,
|
|
||||||
transparent: true,
|
|
||||||
opacity: isWireframe ? 0.4 : 0.75,
|
|
||||||
side: THREE.DoubleSide
|
|
||||||
});
|
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
scene.add(mesh);
|
|
||||||
return mesh;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to load gamut mesh:", e);
|
console.error("Failed to load gamut mesh:", e);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+14
-2
@@ -1,5 +1,6 @@
|
|||||||
const { invoke } = window.__TAURI__.core;
|
const { invoke } = window.__TAURI__.core;
|
||||||
const { listen } = window.__TAURI__.event;
|
const { listen } = window.__TAURI__.event;
|
||||||
|
import { loadGamutMesh } from './gamut_viewer.js';
|
||||||
|
|
||||||
let profileBasename = "";
|
let profileBasename = "";
|
||||||
let profileCwd = "";
|
let profileCwd = "";
|
||||||
@@ -30,7 +31,14 @@ export function initProfcheck() {
|
|||||||
|
|
||||||
const sep = cwd.includes('\\') ? '\\' : '/';
|
const sep = cwd.includes('\\') ? '\\' : '/';
|
||||||
const ti3Path = cwd ? `${cwd}${sep}${basename}.ti3` : `${basename}.ti3`;
|
const ti3Path = cwd ? `${cwd}${sep}${basename}.ti3` : `${basename}.ti3`;
|
||||||
const iccPath = cwd ? `${cwd}${sep}${basename}.icc` : `${basename}.icc`;
|
|
||||||
|
// Query platform-aware profile path (.icm on Windows, .icc on Unix)
|
||||||
|
let iccPath = cwd ? `${cwd}${sep}${basename}.icc` : `${basename}.icc`;
|
||||||
|
try {
|
||||||
|
iccPath = await invoke("get_profile_path", { cwd, basename });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not query platform profile path:", e);
|
||||||
|
}
|
||||||
|
|
||||||
logPre.textContent = "";
|
logPre.textContent = "";
|
||||||
logContainer.open = false;
|
logContainer.open = false;
|
||||||
@@ -63,7 +71,7 @@ export function initProfcheck() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const unlistenExit = await listen("process:exit", (event) => {
|
const unlistenExit = await listen("process:exit", async (event) => {
|
||||||
if (event.payload.id === processId) {
|
if (event.payload.id === processId) {
|
||||||
unlistenStdout();
|
unlistenStdout();
|
||||||
unlistenStderr();
|
unlistenStderr();
|
||||||
@@ -73,6 +81,10 @@ export function initProfcheck() {
|
|||||||
if (event.payload.code === 0) {
|
if (event.payload.code === 0) {
|
||||||
logPre.textContent += "\n[SUCCESS] profcheck verification finished.\n";
|
logPre.textContent += "\n[SUCCESS] profcheck verification finished.\n";
|
||||||
parseAndRenderReport(stdoutAccumulator);
|
parseAndRenderReport(stdoutAccumulator);
|
||||||
|
|
||||||
|
// Ensure gamut mesh is loaded into 3D viewer
|
||||||
|
const gamFilePath = cwd ? `${cwd}${sep}${basename}.gam` : `${basename}.gam`;
|
||||||
|
loadGamutMesh(gamFilePath, 0x3b82f6);
|
||||||
} else {
|
} else {
|
||||||
logPre.textContent += `\n[ERROR] profcheck exited with code ${event.payload.code}.\n`;
|
logPre.textContent += `\n[ERROR] profcheck exited with code ${event.payload.code}.\n`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user