Repository navigation
feat(worlds): add configurable simulation worlds - #63
Merged
Merged
Conversation
The Gazebo worlds get a package of their own, one directory per world: empty, room and warehouse move there from kpi_rover_bringup unchanged, as worlds/<name>/<name>.world. sim.launch.py takes room.world from there. The package is for the simulation only: the robot image does not build it.
A Python module kpi_rover_worlds (common/python/) with world_info, for the
launch files and for tools such as the GUI. It finds the worlds,
worlds/<name>/<name>.world (SDF) or <name>.world.xacro (a configurable
world, its arguments are xacro args), reads their manifests <name>.yaml
(title, description, arguments, start points, preview cameras), builds a
configurable world with its arguments into a world file, gives the start
points of a world and checks the manifests against the worlds and their
previews:
python3 -m kpi_rover_worlds.world_info [--json | --check | --build <world> name:=value ...]
Manifests of empty, room and warehouse.
Media for the shared parts of the configurable worlds: - photo textures of floors and walls, models of objects (boxes, crates, cans, a tyre ...) from Poly Haven, CC0; credits in CREDITS.md - stripe textures of markings, a traffic cone texture, meshes of traffic cones, a flat board and mitered marking ends
Parts used by several worlds; they never refer to a world: - common/xacro: base.xacro (world setup, materials, visibility flags), floor.xacro, marking.xacro (lines, arcs, lanes), walls.xacro (walls and the solid boxes they are made of), objects.xacro (the object library), start_point.xacro (a robot start point, seen only in the Gazebo GUI) - common/config: floors, markings, walls and objects, the catalogs the choices of world arguments come from - common/python: scatter.py (objects scattered over the free area of a world), textured_box.py (box meshes with textures in real size)
- test_square: a 1x1 m square marked on the floor to check motion and odometry; arguments floor and marking, start point a - maze: a warehouse with a maze of walls, doorways and arches, some of them a tight fit or impassable; fixed levels (config/levels) or a random one, objects scattered around it, start points at the entrance and the exit. generator.py turns the arguments into the layout of the walls and arches before the world is built.
The body of the overpass with smooth ramps, in the two sizes of the overpass world: wide (0.7 m) and narrow (0.4 m).
A warehouse with a figure eight marking and an overpass at the crossing, so two robots can drive the figure eight at the same time without meeting. Arguments: the overpass (wide with rails, or narrow), the road surface and the lane marking, floor, walls and objects. overpass.xacro is the overpass with its ramps, rails and marking; generator.py makes the road band and the wall meshes and scatters the objects.
- photo textures of grounds (meadow, loose soil, mud, gravel, sand, rubble), a road and tree bark, grass models from Poly Haven, CC0; credits in CREDITS.md - apple tree models made with Blender's Sapling Tree Gen - worlds/garden/config/relief: heights of a real place for the relief
An apple orchard on uneven terrain: rows of trees on grass with patches of loose soil and mud, pits and puddles, a field road around, objects scattered over it. The garden is modeled in real sizes and scaled down to the robot. generator.py builds the terrain for the arguments (relief, grounds, grass, trees) with terrain.py, trees.py and textures.py into a cache, reused on the next launches. common/python gets gltf.py.
Pictures for tools such as the GUI, rendered by render_previews.py: each world, the robot camera view from its start points, its presets and examples of its argument values.
world.launch.py starts Gazebo with the world. sim_robot.launch.py is the simulated robot, the counterpart of robot.launch.py: the robot spawned into a running Gazebo at a pose (new yaw_pose), the bridge of its sensors, its controllers, localization, SLAM and navigation. sim.launch.py includes both, with RViz and joystick teleop as before; tools such as the GUI can start the world and the robot one by one. The parts of sim.launch.py move over unchanged.
world.launch.py: world:= takes a world of kpi_rover_worlds by name (room by default; a configurable one with its default arguments) or a world file, e.g. one built with other arguments by world_info --build. sim.launch.py: start:= puts the robot at a start point of the world instead of x_pose/y_pose.
Visibility masks: the camera and the lidar do not see the visuals the worlds show only in the Gazebo GUI (start points, the maze route), and the lidar does not see grass.
ros2 run kpi_rover_worlds render_previews.py renders the previews of a world (or, with --sdf, the map of a world file, for the GUI); spawn_object.py puts an object of the library into a running world.
README: the kpi_rover_worlds package, selecting and building a world. CONTRIBUTING: scope worlds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Each configurable world is worlds/<name>/<name>.py with build(args), which returns the SDF made with xml.etree (common/python/sdf.py); the argument defaults are in the world manifests. xacro and its templates are gone. - World geometry configs are constants in the world code; unused ground textures and the grass tuft code are removed. - Faster generation: overpass 9.6 s -> 0.6 s, garden from cache 7.9 s -> 1 s, a new garden terrain 16 s -> 8 s (scipy in scatter, Perlin gradient lookup). - Simpler garden terrain and render_previews.py code. - world_info --build prints only the world file to stdout. - The robot image skips kpi_rover_worlds and its dependencies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AksonovSergei
approved these changes
Nov 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #52
What and why
A new package
kpi_rover_worldswith the simulation worlds, so the simulation is not limited to a few fixed world files:empty,room,warehouse(world files) andtest_square,maze,overpass,garden(configurable, built in Python from their arguments);
worlds/<name>/with a manifest<name>.yaml: title, arguments with defaults,presets, start points, previews;
python3 -m kpi_rover_worlds.world_infolists the worlds and their arguments,--build <world> name:=valuebuilds one,
--checkchecks the manifests against the worlds;ros2 run kpi_rover_worlds render_previews.py(previews and the map of a built world),spawn_object.py(puts objects into a running world).Changes outside the package:
kpi_rover_bringup:sim.launch.pyis nowworld.launch.py(Gazebo with the world) andsim_robot.launch.py(the simulated robot) together;world:=takes a world name or a path,start:=puts the robot at a start point of the world. The old world files moved intokpi_rover_worlds.kpi_rover_description:visibility_maskincamera.xacroandlidar.xacro, so the sensors don't seethe helper visuals shown only in the Gazebo window (start point arrows) and the lidar doesn't see grass.
The URDF of the real robot changes only by this tag.
docker/Dockerfile: the robot image skipskpi_rover_worldsand its dependencies.worlds).How it was tested
colcon buildof the workspace,world_info --checkpasses.sim.launch.pywith every world and every start point (12 runs): the controllers come up, the robot drivesfrom
/cmd_vel, real time factor 1.0.spawn_object.py --list,render_previews.py(map of a built world).Notes
global_costmaplogs "Received map message is malformed" in open worlds (empty,test_square), the sameon
mainwithempty.world: not from this PR.(it builds on
sim_robot.launch.pyfrom this one).Checklist