This commit is contained in:
2026-08-03 14:06:06 +09:00
parent 107ccd151e
commit 99cfef1004
37 changed files with 3273 additions and 356 deletions
@@ -3,12 +3,40 @@ import type { EnvironmentProfile } from '../platforms/types'
import type { RuntimeImageArtifact } from './types'
import type { VisualizationStack } from './WorkspaceVisualizationPanel'
export type Nav2VisualizationMode = 'slam' | 'navigation'
function resolveVisualizationStack(profileType: string): VisualizationStack | null {
const type = profileType.toLowerCase()
if (type === 'real_slam') return 'slam'
if (type === 'real_navigation') return 'nav2'
return type === 'autoware' || type === 'nav2' || type === 'slam' ? type : null
}
function resolveNav2Mode(profile: EnvironmentProfile | null): Nav2VisualizationMode {
return profile?.profileType.toLowerCase() === 'real_navigation' ? 'navigation' : 'slam'
}
function isRealPlatformProfile(profile: EnvironmentProfile | null): boolean {
const type = profile?.profileType.toLowerCase()
return type === 'real_slam' || type === 'real_navigation'
}
function selectRuntimeProfiles(profiles: EnvironmentProfile[], remoteWorkspace: boolean): EnvironmentProfile[] {
const matchingProfiles = profiles.filter((profile) => (
remoteWorkspace ? isRealPlatformProfile(profile) : !isRealPlatformProfile(profile)
))
return matchingProfiles.length ? matchingProfiles : profiles
}
type NoticeTone = 'info' | 'warning' | 'error'
type UseWorkspaceVisualizationOptions = {
profiles: EnvironmentProfile[]
clonePathPreview: string
rosDomainId: string
remoteWorkspace: boolean
mapKey?: string | null
worldLayoutVersion?: number
onTitlebarNotice: (title: string, tone?: NoticeTone, addToList?: boolean) => void
}
@@ -19,6 +47,8 @@ type UseWorkspaceVisualizationResult = {
visualizationRunning: boolean
visualizationBusy: boolean
visualizationViewerUrl: string
nav2VisualizationMode: Nav2VisualizationMode
setNav2VisualizationMode: (mode: Nav2VisualizationMode) => void
activeEnvironmentProfile: EnvironmentProfile | null
activeRuntimeImages: RuntimeImageArtifact[]
refreshVisualizationStatus: () => Promise<void>
@@ -36,20 +66,25 @@ export function useWorkspaceVisualization({
profiles,
clonePathPreview,
rosDomainId,
remoteWorkspace,
mapKey,
worldLayoutVersion,
onTitlebarNotice
}: UseWorkspaceVisualizationOptions): UseWorkspaceVisualizationResult {
const [visualizationStack, setVisualizationStack] = useState<VisualizationStack>('autoware')
const [nav2VisualizationMode, setNav2VisualizationMode] = useState<Nav2VisualizationMode>('slam')
const [visualizationRunning, setVisualizationRunning] = useState(false)
const [, setVisualizationStatusMessage] = useState('대기')
const [visualizationBusy, setVisualizationBusy] = useState(false)
const runtimeProfiles = useMemo(() => selectRuntimeProfiles(profiles, remoteWorkspace), [profiles, remoteWorkspace])
const visualizationStacks = useMemo(() => (
profiles
.map((profile) => profile.profileType.toLowerCase())
.filter((stack): stack is VisualizationStack => stack === 'autoware' || stack === 'nav2' || stack === 'slam')
), [profiles])
runtimeProfiles
.map((profile) => resolveVisualizationStack(profile.profileType))
.filter((stack): stack is VisualizationStack => stack !== null)
), [runtimeProfiles])
const activeEnvironmentProfile = profiles.find((profile) => profile.profileType.toLowerCase() === visualizationStack) ?? profiles[0] ?? null
const activeEnvironmentProfile = runtimeProfiles.find((profile) => resolveVisualizationStack(profile.profileType) === visualizationStack) ?? runtimeProfiles[0] ?? null
const activeEnvironmentConfig = activeEnvironmentProfile?.config ?? {}
const activeRuntimeImages = Array.isArray(activeEnvironmentConfig.runtimeImages)
? activeEnvironmentConfig.runtimeImages.filter((image): image is RuntimeImageArtifact => (
@@ -78,16 +113,15 @@ export function useWorkspaceVisualization({
? visualizationBridgeHttpUrl.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:')
: 'ws://localhost:8765'
const visualizationBackendUrl = visualizationBridgeHttpUrl
const visualizationViewerUrl = visualizationStack === 'slam'
? `/slam-viewer/index.html?directBridge=1&bridgeWs=${encodeURIComponent(`${visualizationBridgeUrl}/ws/state`)}&bridgeHttp=${encodeURIComponent(visualizationBridgeHttpUrl)}`
const visualizationViewerUrl = visualizationStack === 'slam' || visualizationStack === 'nav2'
? `/slam-viewer/index.html?directBridge=1&mode=${nav2VisualizationMode}&runtime=${isRealPlatformProfile(activeEnvironmentProfile) ? 'real' : 'simulation'}&bridgeWs=${encodeURIComponent(`${visualizationBridgeUrl}/ws/state`)}&bridgeHttp=${encodeURIComponent(visualizationBridgeHttpUrl)}&mapKey=${encodeURIComponent(mapKey ?? 'nav2-turtlebot3-world')}&layoutVersion=${worldLayoutVersion ?? 0}`
: `/autoware-viewer/index.html?source=ros&stack=${visualizationStack}&bridge=${encodeURIComponent(visualizationBridgeUrl)}&backend=${encodeURIComponent(visualizationBackendUrl)}`
useEffect(() => {
const profileType = profiles[0]?.profileType?.toLowerCase()
if (profileType === 'slam' || profileType === 'nav2' || profileType === 'autoware') {
setVisualizationStack(profileType)
}
}, [profiles.map((profile) => profile.id).join('|')])
const stack = runtimeProfiles[0] ? resolveVisualizationStack(runtimeProfiles[0].profileType) : null
if (stack) setVisualizationStack(stack)
setNav2VisualizationMode(resolveNav2Mode(runtimeProfiles[0] ?? null))
}, [runtimeProfiles])
const refreshVisualizationStatus = async (): Promise<void> => {
const status = await window.localWorkspace.visualization.status().catch((error) => ({
@@ -155,6 +189,8 @@ export function useWorkspaceVisualization({
visualizationRunning,
visualizationBusy,
visualizationViewerUrl,
nav2VisualizationMode,
setNav2VisualizationMode,
activeEnvironmentProfile,
activeRuntimeImages,
refreshVisualizationStatus,