feat(web): add built-in MuJoCo trace presets

This commit is contained in:
5shekel
2026-02-19 10:35:04 +02:00
parent 5a5f6274de
commit fd10d98160
6 changed files with 83 additions and 0 deletions

View File

@@ -106,6 +106,12 @@ If you export with `--frame body`, the torso is re-centered to body-local coordi
In the web app, use "MuJoCo Import" to load that JSON.
You can also load built-in MuJoCo playback presets from the web app "Presets" tab:
- `MuJoCo 10cm trace`
- `MuJoCo 20cm trace`
- `MuJoCo 30cm trace`
- Default format is `v2` (`walkersim-mujoco-playback-v2`) and is used for true playback in the 3D pane.
- To export the old 2D-only overlay schema, add `--format v1`.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -16,6 +16,11 @@ const DEFAULT_LENGTH_CM = {
};
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',
};
const canvas = document.getElementById('sim-canvas');
const view3dPanelEl = document.getElementById('view-3d-panel');
@@ -376,6 +381,22 @@ const ui = wireUI(state, {
listPresets() {
return Object.keys(state.presets);
},
listMujocoTracePresets() {
return Object.keys(MUJOCO_TRACE_PRESETS);
},
async loadMujocoTracePreset(name) {
const presetName = typeof name === 'string' ? name : '';
const url = MUJOCO_TRACE_PRESETS[presetName];
if (!url) return false;
try {
await loadMujocoTraceFromUrl(url);
return true;
} catch (err) {
console.error('[MuJoCo overlay] Failed to load preset', err);
alert(`Failed to load MuJoCo preset: ${err.message}`);
return false;
}
},
set3DVisible(visible) {
state.show3d = state.canShow3d && Boolean(visible);
apply3DVisibility();
@@ -580,6 +601,17 @@ function loadMujocoTraceObject(obj) {
throw new Error('Unsupported trace format.');
}
async function loadMujocoTraceFromUrl(url) {
const res = await fetch(url, { cache: 'no-store' });
if (!res.ok) {
throw new Error(`Failed to fetch preset (${res.status})`);
}
const payload = await res.json();
loadMujocoTraceObject(payload);
if (mujocoTraceInputEl) mujocoTraceInputEl.value = '';
updateDiagnostics(diagEl, state);
}
if (mujocoTraceInputEl) {
mujocoTraceInputEl.addEventListener('change', async () => {
const file = mujocoTraceInputEl.files?.[0];

View File

@@ -36,6 +36,7 @@ export function wireUI(state, hooks) {
canShow3d: state.canShow3d,
presetName: '',
selectedPreset: '',
selectedMujocoPreset: '',
};
function presetOptions() {
@@ -49,7 +50,19 @@ export function wireUI(state, hooks) {
return options;
}
function mujocoPresetOptions() {
const names = hooks.listMujocoTracePresets?.() ?? [];
if (names.length === 0) return { none: '' };
const options = {};
for (const name of names) {
options[name] = name;
}
return options;
}
let presetBinding = null;
let mujocoPresetBinding = null;
function rebuildPresetList(preferredName = null) {
const options = presetOptions();
@@ -74,6 +87,29 @@ export function wireUI(state, hooks) {
});
}
function rebuildMujocoPresetList(preferredName = null) {
const options = mujocoPresetOptions();
const optionValues = Object.values(options);
if (optionValues.length === 0) {
params.selectedMujocoPreset = '';
} else {
const next = preferredName && optionValues.includes(preferredName)
? preferredName
: optionValues[0];
params.selectedMujocoPreset = next;
}
if (mujocoPresetBinding) {
mujocoPresetBinding.dispose();
mujocoPresetBinding = null;
}
mujocoPresetBinding = presetsFolder.addBinding(params, 'selectedMujocoPreset', {
label: 'MuJoCo trace',
options,
});
}
const motionFolder = pane.addFolder({ title: 'Motion', expanded: !isCompactViewport });
motionFolder.expanded = !isCompactViewport;
motionFolder.addBinding(params, 'speedRps', { label: 'Speed (rev/s)', min: 0, max: 2, step: 0.01 }).on('change', (ev) => {
@@ -166,6 +202,12 @@ export function wireUI(state, hooks) {
rebuildPresetList(target);
pane.refresh();
});
rebuildMujocoPresetList();
presetsFolder.addButton({ title: 'Load MuJoCo preset' }).on('click', async () => {
const target = params.selectedMujocoPreset;
if (!target) return;
await hooks.loadMujocoTracePreset?.(target);
});
function syncFromState() {
params.speedRps = state.speedRps;