// Relief Semántico panel (extracted from Sidebar.js in H4 4.2).
// Owns the entire `#relief-submode-controls` block: comparison modes A/B/C,
// active-document list, scale/render/thickness controls, PDF selector + ingestion
// + search, and the Mode-C dimension inspector. The SAE and Model sub-panels are
// nested here and composed from their own modules to keep the DOM identical.
//
// Cross-panel: publishes ctx.refresh.{reliefFiles,docList,dimList,comparisonModeUI,
// subModeUI}; reads ctx.refresh.modelPanel. Also mirrors renderDocList/renderDimList
// onto window.* for visualizer/DimensionInspector.js.
import { STATE } from '../../core/State.js';
import { updateThreadVisibility } from '../../visualizer/ThreadManager.js';
import { updateAllThreadPositions } from '../../visualizer/LayoutEngine.js';
import { clearRelief, updateReliefScale, updateReliefThickness } from '../../visualizer/ReliefRenderer.js';
import { hideDimensionInspector } from '../../visualizer/DimensionInspector.js';
import { saeMetricsMarkup } from './SaeMetricsPanel.js';
import { modelPanelMarkup } from './ModelPanel.js';
import { reliefIngestControlsMarkup, wireReliefIngest } from './relief/ReliefIngest.js';
import { reliefBootEmptyStateMarkup, wireReliefBootEmptyState } from './relief/ReliefAutoLoad.js';
import {
reliefFileSelectorMarkup,
wireReliefFileList,
} from './relief/ReliefFileList.js';
import { reliefSearchMarkup, wireReliefSearch } from './relief/ReliefSearch.js';
import { reliefComparisonMarkup, wireReliefComparison } from './relief/ReliefComparison.js';
/** @typedef {import('../types.js').PanelContext} PanelContext */
export function reliefIngestMarkup() {
return `
${modelPanelMarkup()}
${reliefFileSelectorMarkup()}
${reliefBootEmptyStateMarkup()}
${reliefIngestControlsMarkup()}
${saeMetricsMarkup()}
`;
}
export function reliefVisualizerMarkup() {
return `
${reliefComparisonMarkup()}
Render mode:
Continuous Mesh
Point Cloud
🧵 Ribbons by Chunk (Divergent)
${reliefSearchMarkup()}
`;
}
/**
* Wire the relief controls. Publishes cross-panel refreshers and mirrors
* renderDocList/renderDimList onto window.* for DimensionInspector.
* @param {PanelContext} ctx
* @returns {void}
*/
export function wireReliefPanel(ctx) {
const { applyComparison } = ctx;
// --- Relieve Semántico Events ---
const btnSubThreads = document.getElementById('blanket-sub-threads');
const btnSubRelief = document.getElementById('blanket-sub-relief');
const threadsControls = document.getElementById('threads-submode-controls');
const reliefControls = document.getElementById('relief-submode-controls');
const reliefScaleX = document.getElementById('relief-scale-x');
const reliefScaleY = document.getElementById('relief-scale-y');
const reliefScaleZ = document.getElementById('relief-scale-z');
const valScaleX = document.getElementById('relief-scale-x-val');
const valScaleY = document.getElementById('relief-scale-y-val');
const valScaleZ = document.getElementById('relief-scale-z-val');
const reliefRenderMode = document.getElementById('relief-render-mode');
const fileList = wireReliefFileList(ctx);
wireReliefBootEmptyState();
wireReliefIngest(ctx);
wireReliefSearch(ctx, fileList);
const comparison = wireReliefComparison(ctx);
const updateSubModeUI = () => {
const subMode = STATE.blanketSubMode;
const layoutMode = STATE.layoutMode;
if (btnSubThreads) {
if (subMode === 'THREADS') {
btnSubThreads.style.background = 'var(--color-brand-primary)';
btnSubThreads.style.color = 'var(--color-neutral-950)';
} else {
btnSubThreads.style.background = '';
btnSubThreads.style.color = '';
}
}
if (btnSubRelief) {
if (subMode === 'RELIEF') {
btnSubRelief.style.background = 'var(--color-brand-secondary)';
btnSubRelief.style.color = 'var(--color-neutral-950)';
} else {
btnSubRelief.style.background = '';
btnSubRelief.style.color = '';
}
}
if (threadsControls) {
threadsControls.style.display = (subMode === 'THREADS' && layoutMode === 'BLANKET') ? 'block' : 'none';
}
const compareControls = document.getElementById('compare-controls');
if (compareControls) {
compareControls.style.display = (subMode === 'THREADS' && layoutMode === 'COMPARE') ? 'block' : 'none';
}
if (reliefControls) {
reliefControls.style.display = (subMode === 'RELIEF') ? 'flex' : 'none';
}
const compareModeContent = document.getElementById('compare-mode-content');
if (compareModeContent) {
compareModeContent.style.display = (subMode === 'THREADS' && layoutMode === 'COMPARE') ? 'block' : 'none';
}
if (subMode === 'THREADS') {
clearRelief(STATE.scene || window.scene);
hideDimensionInspector();
ctx.refresh.clearReliefSearch?.();
}
if (subMode === 'RELIEF') {
ctx.refresh.reliefFiles?.();
ctx.refresh.docList?.();
if (STATE.reliefDocs.length > 0) applyComparison();
} else {
const hud = document.getElementById('relief-hud');
if (hud) hud.style.display = 'none';
hideDimensionInspector();
}
};
ctx.refresh.subModeUI = updateSubModeUI;
if (btnSubThreads) {
btnSubThreads.onclick = () => {
STATE.blanketSubMode = 'THREADS';
updateSubModeUI();
updateThreadVisibility();
updateAllThreadPositions();
};
}
if (btnSubRelief) {
btnSubRelief.onclick = () => {
STATE.blanketSubMode = 'RELIEF';
updateSubModeUI();
updateThreadVisibility();
updateAllThreadPositions();
};
}
// Fast GPU scaling updates (User request 1: direct scale.set() calls, no geometry rebuild)
const updateScaleValues = () => {
const sx = STATE.reliefScaleX;
const sy = STATE.reliefScaleY;
const sz = STATE.reliefScaleZ;
updateReliefScale(sx, sy, sz);
};
if (reliefScaleX) {
reliefScaleX.oninput = function() {
STATE.reliefScaleX = parseFloat(this.value);
if (valScaleX) valScaleX.textContent = STATE.reliefScaleX.toFixed(3);
updateScaleValues();
};
}
if (reliefScaleY) {
reliefScaleY.oninput = function() {
STATE.reliefScaleY = parseFloat(this.value);
if (valScaleY) valScaleY.textContent = STATE.reliefScaleY.toFixed(1);
updateScaleValues();
};
}
if (reliefScaleZ) {
reliefScaleZ.oninput = function() {
STATE.reliefScaleZ = parseFloat(this.value);
if (valScaleZ) valScaleZ.textContent = STATE.reliefScaleZ.toFixed(1);
updateScaleValues();
};
}
const reliefThicknessRow = document.getElementById('relief-thickness-row');
const reliefThickness = document.getElementById('relief-thickness');
const valThickness = document.getElementById('relief-thickness-val');
if (reliefRenderMode) {
reliefRenderMode.onchange = function() {
STATE.reliefRenderMode = this.value;
// El slider de grosor solo aplica al modo RIBBONS
if (reliefThicknessRow) {
reliefThicknessRow.style.display = (this.value === 'RIBBONS') ? 'flex' : 'none';
}
// Cambiar el tipo de geometría requiere reconstruir; re-aplica la comparación.
if (STATE.reliefDocs.length > 0) applyComparison();
};
}
if (reliefThickness) {
reliefThickness.oninput = function() {
STATE.reliefThickness = parseFloat(this.value);
if (valThickness) valThickness.textContent = STATE.reliefThickness.toFixed(1);
// Cambio de tamaño en caliente (sin reconstruir geometría)
updateReliefThickness(STATE.reliefThickness);
};
}
fileList.setOnDocListRendered(comparison.updateSimilarityMetrics);
// Initialize comparison UI state (mode toggles + active doc list).
comparison.updateComparisonModeUI();
comparison.updateBSubUI();
ctx.refresh.docList?.();
// Initialize Relief UI state on load if already in relief mode
if (STATE.blanketSubMode === 'RELIEF') {
updateSubModeUI();
}
}