feat(web): add built-in MuJoCo trace presets
This commit is contained in:
@@ -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`.
|
||||
|
||||
|
||||
1
sim/mujoco/model/mujoco_trace_10cm.json
Normal file
1
sim/mujoco/model/mujoco_trace_10cm.json
Normal file
File diff suppressed because one or more lines are too long
1
sim/mujoco/model/mujoco_trace_20cm.json
Normal file
1
sim/mujoco/model/mujoco_trace_20cm.json
Normal file
File diff suppressed because one or more lines are too long
1
sim/mujoco/model/mujoco_trace_30cm.json
Normal file
1
sim/mujoco/model/mujoco_trace_30cm.json
Normal file
File diff suppressed because one or more lines are too long
32
src/main.js
32
src/main.js
@@ -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];
|
||||
|
||||
42
src/ui.js
42
src/ui.js
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user