Spaces:
Running
Running
| // 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 ` | |
| <div id="relief-ingest-controls" class="section-card" style="display: flex; flex-direction: column; gap: var(--spacing-xs);"> | |
| ${modelPanelMarkup()} | |
| <div style="border-top: 1px solid oklch(from var(--color-neutral-50) l c h / 0.05); padding-top: var(--spacing-xs); margin-top: 4px;"> | |
| ${reliefFileSelectorMarkup()} | |
| ${reliefBootEmptyStateMarkup()} | |
| ${reliefIngestControlsMarkup()} | |
| </div> | |
| ${saeMetricsMarkup()} | |
| </div> | |
| `; | |
| } | |
| export function reliefVisualizerMarkup() { | |
| return ` | |
| <!-- Controles del Relieve Semántico (visibles en sub-modo RELIEF) --> | |
| <div id="relief-submode-controls" style="display:${STATE.blanketSubMode === 'RELIEF' ? 'flex' : 'none'}; flex-direction:column; gap: var(--spacing-xs); margin-top: 8px;"> | |
| ${reliefComparisonMarkup()} | |
| <!-- Escala X --> | |
| <div style="display:flex; flex-direction:column; gap:2px;"> | |
| <div style="display:flex; justify-content:space-between; align-items:center;"> | |
| <label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);">X Compression:</label> | |
| <span id="relief-scale-x-val" style="font-size: var(--font-size-caption); color: var(--color-brand-primary); font-family: var(--font-mono);">${STATE.reliefScaleX.toFixed(3)}</span> | |
| </div> | |
| <input type="range" id="relief-scale-x" min="0.005" max="0.2" step="0.005" value="${STATE.reliefScaleX}"> | |
| </div> | |
| <!-- Escala Y --> | |
| <div style="display:flex; flex-direction:column; gap:2px;"> | |
| <div style="display:flex; justify-content:space-between; align-items:center;"> | |
| <label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);">Y Elevation:</label> | |
| <span id="relief-scale-y-val" style="font-size: var(--font-size-caption); color: var(--color-brand-primary); font-family: var(--font-mono);">${STATE.reliefScaleY.toFixed(1)}</span> | |
| </div> | |
| <input type="range" id="relief-scale-y" min="1.0" max="50.0" step="1.0" value="${STATE.reliefScaleY}"> | |
| </div> | |
| <!-- Escala Z --> | |
| <div style="display:flex; flex-direction:column; gap:2px;"> | |
| <div style="display:flex; justify-content:space-between; align-items:center;"> | |
| <label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);">Z Depth:</label> | |
| <span id="relief-scale-z-val" style="font-size: var(--font-size-caption); color: var(--color-brand-primary); font-family: var(--font-mono);">${STATE.reliefScaleZ.toFixed(1)}</span> | |
| </div> | |
| <input type="range" id="relief-scale-z" min="0.1" max="10.0" step="0.1" value="${STATE.reliefScaleZ}"> | |
| </div> | |
| <!-- Modo de render --> | |
| <div style="display:flex; flex-direction:column; gap:2px; margin-top: 4px;"> | |
| <label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);">Render mode:</label> | |
| <select id="relief-render-mode" class="input-field" style="background: oklch(from var(--color-neutral-950) l c h / 0.5);"> | |
| <option value="MESH" ${STATE.reliefRenderMode === 'MESH' ? 'selected' : ''}>Continuous Mesh</option> | |
| <option value="POINTS" ${STATE.reliefRenderMode === 'POINTS' ? 'selected' : ''}>Point Cloud</option> | |
| <option value="RIBBONS" ${STATE.reliefRenderMode === 'RIBBONS' ? 'selected' : ''}>🧵 Ribbons by Chunk (Divergent)</option> | |
| </select> | |
| </div> | |
| <!-- Grosor de puntos (modo RIBBONS): chico = hilos separados, grueso = manta --> | |
| <div id="relief-thickness-row" style="display:${STATE.reliefRenderMode === 'RIBBONS' ? 'flex' : 'none'}; flex-direction:column; gap:2px;"> | |
| <div style="display:flex; justify-content:space-between; align-items:center;"> | |
| <label style="font-size: var(--font-size-caption); color: var(--color-neutral-400);">Point size:</label> | |
| <span id="relief-thickness-val" style="font-size: var(--font-size-caption); color: var(--color-brand-primary); font-family: var(--font-mono);">${STATE.reliefThickness.toFixed(1)}</span> | |
| </div> | |
| <input type="range" id="relief-thickness" min="1.0" max="10.0" step="0.5" value="${STATE.reliefThickness}"> | |
| </div> | |
| ${reliefSearchMarkup()} | |
| </div> | |
| `; | |
| } | |
| /** | |
| * 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(); | |
| } | |
| } | |