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, | 'paused' | 'snapshot' | 'selectedEntry' | 'setPaused' | 'setSnapshot' | 'setSpeed' | 'setMode' | 'setDiagnostic' >; adapterRef: RefObject; manifestRef: RefObject; agentSceneSessionRef: RefObject; lastAgentConfigRef: RefObject; prepareAgentScene(config: PickPlaceTaskConfig): Promise; ignoreJointLimits: boolean; setIgnoreJointLimits(value: boolean): void; setGeneratedMjcf(value: string): void; notify: ReturnType['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) => { 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, }; }