diff --git a/README.md b/README.md index 6d94199..f8cd0d9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ - tendon (`BE`) with automatic coupling to `AD` - defaults normalized to a `45 cm` leg baseline while preserving original mechanism ratios - mirrored automatically to far side (180° phase side) -- Presets save/load for named dimension setups (persisted in browser local storage). +- Presets tab for built-in MuJoCo playback traces (10cm/20cm/30cm). - Foot trajectory traces for both sides. - Optional linkage annotation overlay (joint and link labels). - Optional real-time 3D viewport with orbit camera and camera reset. diff --git a/src/main.js b/src/main.js index b9765eb..305fcce 100644 --- a/src/main.js +++ b/src/main.js @@ -6,8 +6,6 @@ import { clearTrace, createTraceBuffer, pushTrace } from './trace.js'; import { updateDiagnostics, wireUI } from './ui.js'; import { BASE_DIMENSIONS_CM } from './geometry.js'; -const PRESET_STORAGE_KEY = 'walkerSim.presets.v1'; - const DEFAULT_LENGTH_CM = { crank: BASE_DIMENSIONS_CM.crank, leg: BASE_DIMENSIONS_CM.leg, @@ -15,12 +13,12 @@ const DEFAULT_LENGTH_CM = { body: BASE_DIMENSIONS_CM.body, }; -const BUILTIN_PRESET_NAME = 'Default 45cm leg'; const MUJOCO_TRACE_PRESETS = { - 'MuJoCo 10cm trace': './sim/mujoco/model/mujoco_trace_10cm.json', - 'MuJoCo 20cm trace': './sim/mujoco/model/mujoco_trace_20cm.json', 'MuJoCo 30cm trace': './sim/mujoco/model/mujoco_trace_30cm.json', + 'MuJoCo 20cm trace': './sim/mujoco/model/mujoco_trace_20cm.json', + 'MuJoCo 10cm trace': './sim/mujoco/model/mujoco_trace_10cm.json', }; +const DEFAULT_MUJOCO_TRACE_PRESET = 'MuJoCo 30cm trace'; const canvas = document.getElementById('sim-canvas'); const view3dPanelEl = document.getElementById('view-3d-panel'); @@ -283,11 +281,6 @@ function currentLengthScale() { }; } -function sanitizePresetName(name) { - if (typeof name !== 'string') return ''; - return name.trim().slice(0, 40); -} - function resetSimulation({ clearLengths = false } = {}) { if (clearLengths) { state.lengthCm = { ...DEFAULT_LENGTH_CM }; @@ -307,52 +300,6 @@ function resetSimulation({ clearLengths = false } = {}) { state.pose = applyGrounding(raw); } -function loadPersistedPresets() { - const presets = { - [BUILTIN_PRESET_NAME]: { ...DEFAULT_LENGTH_CM }, - }; - - let parsed = null; - try { - parsed = JSON.parse(localStorage.getItem(PRESET_STORAGE_KEY) ?? 'null'); - } catch { - parsed = null; - } - - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { - for (const [rawName, rawPreset] of Object.entries(parsed)) { - const name = sanitizePresetName(rawName); - if (!name || !rawPreset || typeof rawPreset !== 'object') continue; - - const crank = Number(rawPreset.crank); - const leg = Number(rawPreset.leg); - const tendon = Number(rawPreset.tendon); - const body = Number(rawPreset.body); - - if ([crank, leg, tendon, body].every(Number.isFinite)) { - presets[name] = { crank, leg, tendon, body }; - } - } - } - - return presets; -} - -function persistPresets() { - const serializable = {}; - for (const [name, preset] of Object.entries(state.presets)) { - if (name === BUILTIN_PRESET_NAME) continue; - serializable[name] = { - crank: preset.crank, - leg: preset.leg, - tendon: preset.tendon, - body: preset.body, - }; - } - localStorage.setItem(PRESET_STORAGE_KEY, JSON.stringify(serializable)); -} - -state.presets = loadPersistedPresets(); refreshLayoutMode(); const ui = wireUI(state, { @@ -363,24 +310,6 @@ const ui = wireUI(state, { refresh() { resetSimulation({ clearLengths: false }); }, - savePreset(name) { - const presetName = sanitizePresetName(name); - if (!presetName) return null; - state.presets[presetName] = { ...state.lengthCm }; - persistPresets(); - return presetName; - }, - loadPreset(name) { - const presetName = sanitizePresetName(name); - const preset = state.presets[presetName]; - if (!preset) return false; - state.lengthCm = { ...preset }; - resetSimulation({ clearLengths: false }); - return true; - }, - listPresets() { - return Object.keys(state.presets); - }, listMujocoTracePresets() { return Object.keys(MUJOCO_TRACE_PRESETS); }, @@ -672,6 +601,10 @@ if (mujocoPlaybackTimeEl) { setMujocoPlaybackControlsVisible(false); syncMujocoPlaybackControls(); +void loadMujocoTraceFromUrl(MUJOCO_TRACE_PRESETS[DEFAULT_MUJOCO_TRACE_PRESET]).catch((err) => { + console.error('[MuJoCo overlay] Failed to load default preset', err); +}); + /* ── Mobile toggle button wiring ── */ if (mobileToggleEl) { mobileToggleEl.addEventListener('click', (e) => { diff --git a/src/ui.js b/src/ui.js index bf95ce7..3fe60c2 100644 --- a/src/ui.js +++ b/src/ui.js @@ -34,22 +34,9 @@ export function wireUI(state, hooks) { showAnnotations: state.showAnnotations, show3d: state.show3d, canShow3d: state.canShow3d, - presetName: '', - selectedPreset: '', selectedMujocoPreset: '', }; - function presetOptions() { - const names = hooks.listPresets(); - if (names.length === 0) return { none: '' }; - - const options = {}; - for (const name of names) { - options[name] = name; - } - return options; - } - function mujocoPresetOptions() { const names = hooks.listMujocoTracePresets?.() ?? []; if (names.length === 0) return { none: '' }; @@ -61,32 +48,8 @@ export function wireUI(state, hooks) { return options; } - let presetBinding = null; let mujocoPresetBinding = null; - function rebuildPresetList(preferredName = null) { - const options = presetOptions(); - const optionValues = Object.values(options); - if (optionValues.length === 0) { - params.selectedPreset = ''; - } else { - const next = preferredName && optionValues.includes(preferredName) - ? preferredName - : optionValues[0]; - params.selectedPreset = next; - } - - if (presetBinding) { - presetBinding.dispose(); - presetBinding = null; - } - - presetBinding = presetsFolder.addBinding(params, 'selectedPreset', { - label: 'Saved preset', - options, - }); - } - function rebuildMujocoPresetList(preferredName = null) { const options = mujocoPresetOptions(); const optionValues = Object.values(options); @@ -125,7 +88,6 @@ export function wireUI(state, hooks) { motionFolder.addButton({ title: 'Reset' }).on('click', () => { hooks.reset(); syncFromState(); - rebuildPresetList(params.selectedPreset); pane.refresh(); }); @@ -184,24 +146,6 @@ export function wireUI(state, hooks) { const presetsFolder = pane.addFolder({ title: 'Presets', expanded: !isCompactViewport }); presetsFolder.expanded = !isCompactViewport; - presetsFolder.addBinding(params, 'presetName', { label: 'Preset name' }); - rebuildPresetList(); - presetsFolder.addButton({ title: 'Save preset' }).on('click', () => { - const savedName = hooks.savePreset(params.presetName); - if (!savedName) return; - params.presetName = savedName; - rebuildPresetList(savedName); - pane.refresh(); - }); - presetsFolder.addButton({ title: 'Load selected preset' }).on('click', () => { - const target = params.selectedPreset; - if (!target) return; - const loaded = hooks.loadPreset(target); - if (!loaded) return; - syncFromState(); - rebuildPresetList(target); - pane.refresh(); - }); rebuildMujocoPresetList(); presetsFolder.addButton({ title: 'Load MuJoCo preset' }).on('click', async () => { const target = params.selectedMujocoPreset;