// 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()}
${STATE.reliefScaleX.toFixed(3)}
${STATE.reliefScaleY.toFixed(1)}
${STATE.reliefScaleZ.toFixed(1)}
${STATE.reliefThickness.toFixed(1)}
${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(); } }