feat(nav2): load custom editor layouts in gazebo

This commit is contained in:
2026-07-31 13:04:14 +09:00
parent 41683be18b
commit e65929bcbc
@@ -1,3 +1,4 @@
import json
import os
import tempfile
import xml.etree.ElementTree as ET
@@ -76,6 +77,59 @@ def create_flat_house_world(gazebo_dir):
return flat_world_path
def create_custom_layout_world():
"""Build a Gazebo world from the editor's persisted 2D layout."""
layout_path = os.environ.get('WORLD_LAYOUT_PATH', '/modules/world-layout.json')
try:
with open(layout_path, 'r', encoding='utf-8') as source:
objects = json.load(source).get('objects', [])
except (OSError, ValueError, AttributeError):
objects = []
models = []
for index, item in enumerate(objects):
if not isinstance(item, dict):
continue
position = item.get('position') or [0, 0, 0]
rotation = item.get('rotation') or [0, 0, 0]
size = item.get('size') or [1, 1, 1]
try:
x, y, z = (float(position[0]), float(position[1]), float(position[2]))
yaw = float(rotation[2])
sx, sy, sz = (max(0.05, float(size[0])), max(0.05, float(size[1])), max(0.05, float(size[2])))
except (IndexError, TypeError, ValueError):
continue
name = f"custom_{item.get('type', 'box')}_{index}"
if item.get('type') == 'pillar':
geometry = f'<cylinder><radius>{sx / 2.0}</radius><length>{sz}</length></cylinder>'
else:
geometry = f'<box><size>{sx} {sy} {sz}</size></box>'
models.append(
f'''<model name="{name}">
<static>true</static>
<pose>{x} {y} {z} 0 0 {yaw}</pose>
<link name="link">
<collision name="collision"><geometry>{geometry}</geometry></collision>
<visual name="visual"><geometry>{geometry}</geometry><material><ambient>0.34 0.58 0.82 1</ambient><diffuse>0.34 0.58 0.82 1</diffuse></material></visual>
</link>
</model>'''
)
world_path = os.path.join(tempfile.gettempdir(), 'nav2_custom_layout.world')
with open(world_path, 'w', encoding='utf-8') as destination:
destination.write('''<?xml version="1.0"?>
<sdf version="1.6">
<world name="nav2_custom_layout">
<include><uri>model://sun</uri></include>
<include><uri>model://ground_plane</uri></include>
<physics type="ode"><real_time_update_rate>1000</real_time_update_rate></physics>
''')
destination.write('\n'.join(models))
destination.write('\n </world>\n</sdf>\n')
return world_path
def launch_gazebo(context):
gazebo_dir = get_package_share_directory('turtlebot3_gazebo')
simulator_dir = get_package_share_directory('nav2_turtlebot_simulator')
@@ -86,7 +140,9 @@ def launch_gazebo(context):
if render_mode not in {'flat', 'full'}:
raise RuntimeError('render_mode must be either flat or full')
if world == 'turtlebot3_house.world' and render_mode == 'flat':
if world == 'empty_world.world' and os.path.isfile(os.environ.get('WORLD_LAYOUT_PATH', '/modules/world-layout.json')):
world_path = create_custom_layout_world()
elif world == 'turtlebot3_house.world' and render_mode == 'flat':
world_path = create_flat_house_world(gazebo_dir)
else:
world_path = os.path.join(