feat(lms): add ROS connection status workspace view

This commit is contained in:
2026-08-13 17:17:47 +09:00
parent 56db72a054
commit 99f0451412
12 changed files with 836 additions and 41 deletions
+28 -15
View File
@@ -480,7 +480,7 @@ const SENSOR_POINT_CLOUD_MAX_AGE_MS = 3_000;
const ENABLE_CAMERA_PREVIEW = false;
const CUSTOM_WORLD = !REAL_RUNTIME && viewerMapKey === 'nav2-custom';
const activeWorldModel = viewerMapKey === 'nav2-turtlebot3-house' ? 'turtlebot3_house' : 'turtlebot3_world';
const MKMINI_WORLD_SPACING_SCALE = USE_MKMINI_MODEL && activeWorldModel === 'turtlebot3_world' ? 1.6 : 1;
const MKMINI_WORLD_SINGLE_PILLAR = USE_MKMINI_MODEL && activeWorldModel === 'turtlebot3_world';
const MKMINI_RENDER_LENGTH = 0.84;
const MKMINI_RENDER_WIDTH = 0.61;
const MKMINI_RENDER_HEIGHT = 0.6575;
@@ -1066,14 +1066,10 @@ function applySdfPose(obj, pose) {
obj.rotation.set(pose.roll, pose.pitch, pose.yaw, 'XYZ');
}
function applyWorldSdfPose(obj, pose) {
applySdfPose(obj, {
...pose,
x: pose.x * MKMINI_WORLD_SPACING_SCALE,
y: pose.y * MKMINI_WORLD_SPACING_SCALE,
});
applySdfPose(obj, pose);
}
function isMkMiniWorldPillar(visual) {
return MKMINI_WORLD_SPACING_SCALE > 1
return MKMINI_WORLD_SINGLE_PILLAR
&& /^(one|two|three)_/.test(visual.getAttribute('name') || '');
}
function shouldRenderWorldVisual(visual) {
@@ -1141,9 +1137,6 @@ async function addMesh(parent, visual, geometry) {
if (!uri) return;
const url = gazeboUriToPath(uri);
const scale = parseScale(getDirectText(meshEl, 'scale', '1 1 1'));
if (MKMINI_WORLD_SPACING_SCALE > 1 && uri.endsWith('/wall.dae')) {
scale.multiplyScalar(MKMINI_WORLD_SPACING_SCALE);
}
const material = materialFromVisual(visual);
if (/\.stl$/i.test(url)) {
@@ -1942,7 +1935,19 @@ function updatePointCloud(payload) {
// loaded navigation map after localization starts.
if (frameId !== 'map' && hasGlobalMapPointCloud) return;
updatePointGeometry(pointCloudGeometry, payload?.points);
if (frameId === 'map') hasGlobalMapPointCloud = true;
if (frameId === 'map') {
hasGlobalMapPointCloud = true;
// The saved 3D cloud is now available. The occupancy grid was only a
// startup fallback while the exported cloud was loading.
if (navigationMapMesh && SHOW_POINT_CLOUD) {
navigationMapRoot.remove(navigationMapMesh);
navigationMapMesh.geometry.dispose();
navigationMapMesh.material.dispose();
navigationMapMesh = null;
navigationMapTexture?.dispose();
navigationMapTexture = null;
}
}
const transform = payload?.transform;
if (!transform || !Number.isFinite(Number(transform.x)) || !Number.isFinite(Number(transform.y))) {
// A cloud already authored in `map` does not need a transform. Hide
@@ -2216,7 +2221,10 @@ function drawMiniMap(state) {
function updateMiniMapMetadata(map) {
if (!map?.width || !map?.height) return;
if (navigationMapLocked) return;
// MAP1 and its metadata are sent as separate WebSocket frames. Accept the
// first metadata frame even when its binary payload arrived first; only a
// fully rendered saved navigation map is locked against later local maps.
if (navigationMapLocked && navigationMapMesh) return;
latestMiniMapGrid = {
...(latestMiniMapGrid || {}),
...map,
@@ -2314,9 +2322,10 @@ function updateRemoteNavigationRoomTransform() {
function renderNavigationOccupancyMap() {
const grid = latestMiniMapGrid;
// A 3D SLAM session uses RTAB-Map's accumulated point cloud as the scene
// itself. Keep the occupancy grid for the minimap only, not as a floor plane.
if (SHOW_POINT_CLOUD || NAV2_VIEW_MODE !== 'navigation' || !grid?.data?.length || !grid.width || !grid.height || !grid.resolution) return;
if (NAV2_VIEW_MODE !== 'navigation' || !grid?.data?.length || !grid.width || !grid.height || !grid.resolution || !grid.origin) return false;
// The occupancy grid is a short-lived fallback for a saved 3D map. It
// keeps the scene usable if the point-cloud export is still starting.
if (SHOW_POINT_CLOUD && hasGlobalMapPointCloud) return false;
const canvas = document.createElement('canvas');
canvas.width = grid.width;
@@ -2379,6 +2388,7 @@ function renderNavigationOccupancyMap() {
camera.lookAt(center);
navigationMapFocused = true;
}
return true;
}
function applyMapBinary(buffer) {
@@ -2410,6 +2420,9 @@ function applyMapBinary(buffer) {
data,
};
miniMapBitmap = null;
// Do not lock a selected navigation map before its separate metadata frame
// supplies resolution and origin. Otherwise Three.js receives valid /map
// data but cannot construct a plane from it.
renderNavigationOccupancyMap();
drawMiniMap(latestSlamState);
downloadMapButton.disabled = false;