Files
Mujoco_WASM/web_platform/src/app/hooks/useSimulationActions.ts
T
chenlin 26bb5634bd
web-platform-ci / Standalone decision service (no cloud credentials) (push) Waiting to run
web-platform-ci / TypeScript, lint, unit, build (push) Waiting to run
web-platform-ci / Playwright E2E (push) Waiting to run
lekiwi-compatibility / cpu-compatibility (push) Waiting to run
refactor: release v1.0.5 安全精简与网站发布
2026-09-29 09:55:08 +08:00

165 lines
5.6 KiB
TypeScript

import type { RefObject } from 'react';
import type { MainThreadPhysicsAdapter } from '../../simulation/PhysicsAdapter';
import type { ActuatorParameters, SimulationSession } from '../../simulation/SimulationSession';
import type { ProjectManifest } from '../../project/types';
import type { PickPlaceTaskConfig } from '../../mobile/agent/PickPlaceScene';
import type { DataRecorderConfig } from '../../telemetry/DataRecorder';
import type { InteractionMode } from '../../viewer/MuJoCoViewer';
import type { useAppStore } from '../../stores/useAppStore';
import { downloadBytes } from '../../project/cachedFiles';
import { diagnostic, type useWorkbenchFeedback } from './useWorkbenchFeedback';
/** UI 意图委托,不拥有物理时钟或控制权算法。 */
export function useSimulationActions({
state,
adapterRef,
manifestRef,
agentSceneSessionRef,
lastAgentConfigRef,
prepareAgentScene,
ignoreJointLimits,
setIgnoreJointLimits,
setGeneratedMjcf,
notify,
}: {
state: Pick<
ReturnType<typeof useAppStore.getState>,
| 'paused'
| 'snapshot'
| 'selectedEntry'
| 'setPaused'
| 'setSnapshot'
| 'setSpeed'
| 'setMode'
| 'setDiagnostic'
>;
adapterRef: RefObject<MainThreadPhysicsAdapter>;
manifestRef: RefObject<ProjectManifest | null>;
agentSceneSessionRef: RefObject<SimulationSession | undefined>;
lastAgentConfigRef: RefObject<PickPlaceTaskConfig>;
prepareAgentScene(config: PickPlaceTaskConfig): Promise<SimulationSession>;
ignoreJointLimits: boolean;
setIgnoreJointLimits(value: boolean): void;
setGeneratedMjcf(value: string): void;
notify: ReturnType<typeof useWorkbenchFeedback>['notify'];
}) {
const togglePause = () => {
const value = !state.paused;
state.setPaused(value);
adapterRef.current.setPaused(value);
};
const reset = () => {
if (
agentSceneSessionRef.current &&
agentSceneSessionRef.current === adapterRef.current.session
) {
void prepareAgentScene(lastAgentConfigRef.current).catch(() => undefined);
return;
}
adapterRef.current.setPaused(true);
adapterRef.current.reset();
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
state.setPaused(true);
};
const singleStep = () => {
adapterRef.current.singleStep();
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const changeSpeed = (value: number) => {
state.setSpeed(value);
adapterRef.current.setSpeed(value);
};
const jointPoseEditingDisabled = Boolean(
state.snapshot?.robot ||
state.snapshot?.mobileRobotId ||
(state.snapshot?.controlOwner && state.snapshot.controlOwner !== 'manual'),
);
const mode = (value: InteractionMode) => {
if (value === 'joint' && jointPoseEditingDisabled) return;
state.setMode(value);
};
const resetJoints = () => {
adapterRef.current.resetJoints();
state.setPaused(true);
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const toggleJointLimits = () => {
const next = !ignoreJointLimits;
adapterRef.current.setIgnoreJointLimits(next);
setIgnoreJointLimits(next);
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const setActuator = (id: number, value: number) => {
adapterRef.current.setActuator(id, value);
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const setActuatorParameters = (id: number, parameters: ActuatorParameters) => {
if (!adapterRef.current.setActuatorParameters(id, parameters)) return;
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
try {
setGeneratedMjcf(new TextDecoder().decode(adapterRef.current.exportMjcf()));
} catch (error) {
console.warn('[MuJoCo] 无法刷新驱动器参数源码', error);
}
};
const setJoint = (id: number, value: number) => {
adapterRef.current.setJointPosition(id, value);
state.setPaused(true);
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const configureDataRecorder = (patch: Partial<DataRecorderConfig>) => {
try {
adapterRef.current.configureDataRecorder(patch);
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
} catch (error) {
state.setDiagnostic(diagnostic('仿真', error, state.selectedEntry));
}
};
const startDataRecording = () => {
adapterRef.current.startDataRecording();
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const stopDataRecording = () => {
adapterRef.current.stopDataRecording();
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const clearDataRecording = () => {
adapterRef.current.clearDataRecording();
state.setSnapshot(adapterRef.current.snapshot() ?? undefined);
};
const exportDataRecording = (format: 'csv' | 'json') => {
try {
const stem =
(manifestRef.current?.name ?? 'simulation')
.replace(/\.(?:zip|xml|urdf)$/i, '')
.replace(/[^\p{L}\p{N}._-]+/gu, '_') || 'simulation';
downloadBytes(
adapterRef.current.exportDataRecording(format),
`${stem}-telemetry.${format}`,
format === 'csv' ? 'text/csv' : 'application/json',
);
notify(`遥测 ${format.toUpperCase()} 已导出`, `${stem}-telemetry.${format}`);
} catch (error) {
state.setDiagnostic(diagnostic('仿真', error, state.selectedEntry));
}
};
return {
togglePause,
reset,
singleStep,
changeSpeed,
jointPoseEditingDisabled,
mode,
resetJoints,
toggleJointLimits,
setActuator,
setActuatorParameters,
setJoint,
configureDataRecorder,
startDataRecording,
stopDataRecording,
clearDataRecording,
exportDataRecording,
};
}