import type { ReactNode } from 'react'; import { Box, Database, MapPinned, PanelRight, SlidersHorizontal } from 'lucide-react'; import type { MapEntry, ModelEntry } from '../../project/types'; import type { ActuatorParameters, SimulationSnapshot } from '../../simulation/SimulationSession'; import type { UrdfBaseMode, UrdfLoadMode } from '../../simulation/PhysicsAdapter'; import type { ViewerSelection } from '../../viewer/MuJoCoViewer'; import type { MapSelection, PlacedMapSelection } from '../../map/types'; import type { EditableMapDocument, MapEditorInteractionCallbacks, MapEditorSessionState, } from '../../map/editor/types'; import { PhysicalMapPanel } from '../../map/PhysicalMapPanel'; import { BodyInspector, InspectorIdentity, JointInspector, ModelSummaryInspector, } from '../../simulation/RobotInspector'; import type { EditorSelection } from '../editorSelection'; import { SidebarPanel } from './SidebarPanel'; import type { WorkspaceTool } from './WorkspaceToolsPanel'; import { RightSidebarTabs } from './RightSidebarTabs'; interface ModelControlsProps { snapshot?: SimulationSnapshot; selection: EditorSelection | null; viewerSelection: ViewerSelection | null; selectedFormat?: ModelEntry['format']; loading: boolean; visible?: boolean; urdfMode: UrdfLoadMode; baseMode: UrdfBaseMode; showCollision: boolean; ignoreJointLimits: boolean; jointAdvanced: boolean; angleUnit: 'rad' | 'deg'; mapSelection: MapSelection; activeMapAssetId?: string; activeMapAssetName?: string; mapSceneDirty: boolean; maps: MapEntry[]; showVisualMap: boolean; showMapCollision: boolean; editorDocument: EditableMapDocument | null; editorDraftDocument?: EditableMapDocument; workspaceTool: WorkspaceTool | null; workspaceTools?: ReactNode; onWorkspaceToolChange: (tool: WorkspaceTool | null) => void; onSelectJoint: (jointId: number, bodyId: number) => void; onUrdfMode: (value: UrdfLoadMode) => void; onBaseMode: (value: UrdfBaseMode) => void; onShowCollision: (value: boolean) => void; onResetJoints: () => void; onToggleJointLimits: () => void; onToggleAdvanced: () => void; onToggleAngleUnit: () => void; onActuator: (id: number, value: number) => void; onActuatorParameters: (id: number, parameters: ActuatorParameters) => void; onJoint: (id: number, value: number) => void; onApplyMap: (value: MapSelection) => void; onMapDraft: (value: PlacedMapSelection) => void; onEditorPreview: (document: EditableMapDocument | null) => void; onEditorDraftChange: ( descriptorPath: string, document: EditableMapDocument, dirty: boolean, ) => void; onEditorApply: (document: EditableMapDocument) => Promise; onEditorExport: () => void; onEditorConvert: () => Promise; onEditorBindInteraction: (callbacks: MapEditorInteractionCallbacks | null) => void; onEditorSelect: (id: string | null) => void; onEditorSessionStateChange?: ( descriptorPath: string, state: MapEditorSessionState | null, ) => void; onEditorSurfaceHeight?: (position: readonly [number, number, number]) => number | null; onMapDisplay: (visual: boolean, collision: boolean) => void; } export function ModelControlsSidebar(props: ModelControlsProps) { const snapshot = props.snapshot; const activeView = props.workspaceTool ?? 'inspector'; const title = activeView === 'controls' ? '控制台' : activeView === 'data' ? '数据录制' : '检查器'; const icon = activeView === 'controls' ? ( ) : activeView === 'data' ? ( ) : ( ); const tabs = ( props.onWorkspaceToolChange(value === 'inspector' ? null : value)} /> ); if (!snapshot) return ( {tabs} {props.workspaceTool ? ( props.workspaceTools ) : (
导入模型后显示检查器
)}
); const selection = props.selection; const selectedBody = selection?.kind === 'body' ? snapshot.bodies.find((body) => body.id === selection.bodyId) : undefined; const selectedJoint = selection?.kind === 'joint' ? snapshot.joints.find((joint) => joint.id === selection.jointId) : undefined; const mapSelected = (selection?.kind === 'map' || selection?.kind === 'map-object') && selection.mapAssetId === props.activeMapAssetId; const selectedMapObject = selection?.kind === 'map-object' && mapSelected ? (props.editorDraftDocument ?? props.editorDocument)?.objects.find( (object) => object.id === selection.objectId, ) : undefined; const mapPanel = ( body.id !== 0 && body.parentId === 0 && !body.name.startsWith('__platform_map_'), ) .map((body) => body.name)} loading={props.loading} nativeUrdf={props.selectedFormat === 'urdf' && props.urdfMode === 'native'} showVisualMap={props.showVisualMap} showMapCollision={props.showMapCollision} editorDocument={props.editorDocument} editorDraftDocument={props.editorDraftDocument} sceneDirty={props.mapSceneDirty} onEditorPreview={props.onEditorPreview} onEditorDraftChange={props.onEditorDraftChange} onEditorApply={props.onEditorApply} onEditorExport={props.onEditorExport} onEditorConvert={props.onEditorConvert} selectedEditorObjectId={ selection?.kind === 'map-object' && mapSelected ? selection.objectId : undefined } onEditorBindInteraction={props.onEditorBindInteraction} onEditorSelect={props.onEditorSelect} onEditorSessionStateChange={props.onEditorSessionStateChange} onEditorSurfaceHeight={props.onEditorSurfaceHeight} onMapDisplay={props.onMapDisplay} onDraft={props.onMapDraft} onApply={props.onApplyMap} /> ); let inspector: ReactNode; if (selectedBody) { inspector = ( ); } else if (selectedJoint) { inspector = ( ); } else if (mapSelected) { const mapName = selectedMapObject?.name ?? props.activeMapAssetName ?? '地图实例'; inspector = ( <> : } eyebrow={selectedMapObject ? 'Map / Object' : 'Map / Instance'} name={mapName} meta={ selectedMapObject ? `${selectedMapObject.type} · ${props.activeMapAssetName ?? '地图'}` : props.mapSelection.kind === 'builtin' ? '参数化地形' : '工程地图包实例' } /> {mapPanel} ); } else { inspector = ( <> {props.mapSelection.kind === 'none' && mapPanel} ); } return ( {tabs} {props.workspaceTool && props.workspaceTools} ); }