第 11.2 節

Ignition Gazebo(Gazebo Fortress)

0瀏覽次數0訪問次數--跳出率--平均停留

Ignition Gazebo (Gazebo Fortress, based on ROS2 Humble)

** It is recommended to use ROS2 Jazzy. This ROS2 Humble Gazebo version feels like a transitional release, and the code may conflict with future versions! ** (This version of the tutorial is no longer being updated. Future updates will focus on ROS2 Jazzy and later versions, as well as Gazebo Harmonic and subsequent releases.)

See the Gz Sim tutorial

Ign Gazebo Installation and Running

Gazebo changes significantly with each version.

In particular, the older version of Gazebo (black interface) used with ROS1 and the newer version of Gazebo (white interface) used with ROS2.

ROS2's different versions of Gazebo also vary significantly. For example, there are many notable differences in tags between Humble, Jazzy, and versions after Jazzy.

This tutorial uses the Humble version (i.e., Ignition Gazebo).

Of course, to ensure compatibility with future versions of Gazebo, there will also be a tutorial below on how to migrate from Ign Gazebo to Gazebo Sim (the very latest version of Gazebo).

Ignition Gazebo is a brand-new robot simulation tool used in ROS2, and it is an upgraded version of Gazebo. In Humble, it is still called Ignition Gazebo (also known as Gazebo Fortress), while in Jazzy it is called Gazebo Harmonic (with the "Ignition" name removed) (https://community.gazebosim.org/t/a-new-era-for-gazebo/1356). It offers better performance and usability, and provides a powerful simulation environment through tight integration with ROS2. Ignition Gazebo supports a variety of robot platforms and sensors, and offers flexible configuration options along with an easy-to-use interface. Its physics engine and sensor models help developers with the development, testing, and validation of robotic systems. Whether for research or education, Ignition Gazebo is a powerful tool.

If you want to migrate from Ignition Gazebo (ROS2 Humble) to Gazebo (ROS2 Jazzy), scroll down — there's a section below on how to migrate.

https://docs.ros.org/en/humble/Tutorials/Advanced/Simulators/Gazebo/Gazebo.html

The following website is the official tutorial (ROS2 Humble with Ignition Gazebo Fortress):

https://gazebosim.org/docs/fortress/getstarted/

https://gazebosim.org/docs/fortress/library_reference_nav/

Source code: https://github.com/gazebosim/docs/blob/master/fortress/tutorials

Installation

Ignition Gazebo is an independent project that does not depend on ROS2 and can be installed on its own. However, if ROS2 is already installed, the corresponding version of Ignition Gazebo is integrated into the ROS2 repositories, and it can be installed directly using the following command:


# 通用命令
sudo apt install ros-${ROS_DISTRO}-ros-gz

# Humble版本
sudo apt install ros-humble-ros-gz

# Jazzy版本
sudo apt install ros-jazzy-ros-gz

Running

After Ignition Gazebo is installed, it can be launched in two ways.

Method 1: Start with Ignition Gazebo, using the following command:


# Humble版本
ign gazebo

# Jazzy版本
gz sim

Method 2: Start using ROS2. The command is as follows:

ros2 launch ros_gz_sim gz_sim.launch.py

The execution results of both are the same, as shown in the figure below: In the pop-up window, select the simulation environment and then click the run button to run.

Interface Introduction

Next, using the Empty simulation environment as an example, let's introduce the interface components of Ignition Gazebo.

Note: If your Gazebo is not lagging, but Ignition Gazebo is extremely laggy, please confirm that Ignition Gazebo is running with the dedicated graphics card, not the integrated graphics.

If you don't know how to switch the application graphics card, you can simply disable the integrated graphics and switch from hybrid output to dedicated graphics output.

Toolbar

  • The toolbar at the top contains two buttons: the file menu button (horizontal stripes) on the left and the plugin button (vertical ellipsis) on the right.
  1. File menu button (horizontal lines)

  • The File menu button includes settings such as saving the simulation environment to a file, saving and loading interface configurations, and customizing the interface style.
  1. The plugin button on the right (vertical ellipsis)

  • The plugin button lists all available plugins. Clicking it will bring up the plugin list; scroll down this list to view all plugins. When one is selected, its interface will appear in the right panel.

3D viewport

  • The toolbar in the upper left contains buttons for various geometric shapes (sphere, box, cylinder) and transform controls. Using the shape buttons, you can directly insert box, sphere, or cylinder models into the simulation environment. Simply click the shape you want to insert, then place it into the environment. The shape will automatically snap to the ground plane.

  • The main view displays the simulation environment. We can navigate the scene using the mouse in different ways, with the relevant operations as follows:
左键单击:选择实体
右键单击:打开带有选项的菜单:
   Move to:移动到以实体为中心的场景
   Follow:选择一个实体让视图保持居中,无论是移动还是平移
   Remove:从模拟中删除实体
   Copy:复制实体
   Past: 粘贴实体
   View:显示实体的重心(Center of Mass)、碰撞边界(Collisions)、惯性(Inertia)、
         关节(Joints)、坐标系(Frames)、透明度(Transparent)、线框(Wireframe)等属性
左键单击并拖动:在场景中平移
右键单击并拖动:放大和缩小
滚轮向前/向后:放大和缩小
滚轮单击并拖动:旋转场景
  • To move this ball, click the Move mode in the top-left corner, then left-click to select the object.

  • At the bottom of the window, from left to right, are the Play button, Step button, and Real-Time Factor (RTF). Clicking the Play button starts the simulation environment; clicking it again pauses the simulation. The Step button sets the discrete unit of simulation time, and you can customize the step size by hovering your mouse over the button. The Real-Time Factor indicates the ratio of the simulation speed to real time.

Right panel

The right panel is used to display plugins. The current simulation environment includes two plugins by default: Model and Entity Tree.

  • The Entity Tree displays a list of entities in the simulation environment.
  • After clicking an entity in the Entity Tree, its related information can be displayed in the Model.
  • You can also hold Ctrl and click to select multiple entities.
  • You can also right-click any plugin to open its basic settings or disable it.

Ignition Gazebo comes with many built-in plugins. You can click the button on the right side of the toolbar to add them yourself. For example, you can select the Grid Config plugin to adjust the world grid's properties, including cell size, grid position, cell count, or color.

As the application deepens, other plugins will be introduced over time.

Integration with ROS2

This section will introduce how to integrate Ignition Gazebo with ROS2 to enable interaction between the two. For example, you can control the robot's movement using a ROS2 keyboard control node and display the robot's odometry (odom) data in rviz2. The general workflow is as follows:

  1. Start the Ignition Gazebo simulation environment.
  2. Establish the connection between ROS2 and Ignition Gazebo via ros_gz_bridge;
  3. Start the ROS2-related nodes to enable data transmission and reception with Ignition Gazebo.

All integration implementations between Ignition Gazebo and ROS2 basically follow the above workflow.

Launch the Simulation Environment

When installing Ignition Gazebo, some simulation environments are already built in and can be launched directly. Here, we can use the simulation file named visualize_lidar.sdf, which includes a differential drive robot and a LiDAR simulation. The launch command is as follows:

ign gazebo -v 4 -r visualize_lidar.sdf
#或者
gz sim -v 4 -r visualize_lidar.sdf

Alternatively, it can also be launched using ROS2 launch, with the following command:

ros2 launch ros_gz_sim gz_sim.launch.py gz_args:="-v 4 -r visualize_lidar.sdf" # 启动状态

Both methods are essentially the same: they start Ignition Gazebo and load the visualize_lidar.sdf file.

Establishing a Connection

Although the robot in the simulation environment has been configured with a motion control plugin and can subscribe to velocity commands via the /model/vehicle_blue/cmd_vel topic to move, the message formats in Ignition Gazebo and ROS 2 are not consistent. Therefore, the ros_gz_bridge bridging package is needed to convert messages between the two. The command to invoke it is as follows:

ros2 run ros_gz_bridge parameter_bridge /model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist

This instruction can convert ROS2 messages of type geometry_msgs/msg/Twist published on the /model/vehicle_blue/cmd_vel topic into messages of type gz.msgs.Twist that can be recognized by Ignition Gazebo.

Starting a ROS2 Node

Start the ROS2 keyboard control node and remap the topic to /model/vehicle_blue/cmd_vel, using the following command:

ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/model/vehicle_blue/cmd_vel

Now you can use the keyboard to control the robot's movement.

ros_gz_bridge

ros_gz_bridge is the bridge connecting ROS2 and Ignition Gazebo. The messages used by ROS2 and Ignition Gazebo are not compatible and must be converted through ros_gz_bridge.

ros_gz_bridge Usage Syntax

The bridging between ROS2 and Ignition Gazebo is implemented through the parameter_bridge node in the ros_gz_bridge package, with the following usage syntax:

parameter_bridge [<topic@ROS2_type@Gz_type> ..]  [<service@ROS2_srv_type[@Gz_req_type@Gz_rep_type]> ..]

In the topic, the first @ symbol serves as the separator between the topic name and the message type.

The first @ symbol is followed by a ROS message type.

ROS message types are followed by the @, , or symbols:

  • @ indicates a bidirectional bridge;
  • [ indicates the bridge from Ignition Gazebo to ROS;
  • ] indicates the bridge from ROS to Ignition Gazebo.

After the direction symbol is the Gazebo Transport message type.

(The two @ symbols do not have the same meaning.)

In a Service, the first @ symbol is the separator between the service name and type.

The first @ symbol is followed by the ROS service type. Optionally, Gazebo request and response types can be included, separated by @ symbols between them.

Only supports exposing Gazebo services as ROS services, meaning the ROS service forwards requests to the Gazebo service and then forwards the response back to the ROS client.

Bidirectional Bridging Example:

parameter_bridge /chatter@std_msgs/String@gz.msgs.StringMsg

Bridge example from Gazebo to ROS:

parameter_bridge /chatter@std_msgs/String[gz.msgs.StringMsg

Bridge example from ROS to Gazebo:

parameter_bridge /chatter@std_msgs/String]gz.msgs.StringMsg

Service Bridging Example:

parameter_bridge /world/default/control@ros_gz_interfaces/srv/ControlWorld
或者:
parameter_bridge /world/default/control@ros_gz_interfaces/srv/ControlWorld@gz.msgs.WorldControl@gz.msgs.Boolean

You can also run the ros2 run ros_gz_bridge parameter_bridge -h command to view the official documentation.

Message types supported by ros_gz_bridge

Here is the correspondence table of topic message types in ROS2 and Ignition Gazebo:

ROS2 message typesGazebo Transport types
builtin_interfaces/msg/Timegz.msgs.Time
geometry_msgs/msg/Pointgz.msgs.Vector3d
geometry_msgs/msg/Posegz.msgs.Pose
geometry_msgs/msg/PoseArraygz.msgs.Pose_V
geometry_msgs/msg/PoseStampedgz.msgs.Pose
geometry_msgs/msg/PoseWithCovariancegz.msgs.PoseWithCovariance
geometry_msgs/msg/Quaterniongz.msgs.Quaternion
geometry_msgs/msg/Transformgz.msgs.Pose
geometry_msgs/msg/TransformStampedgz.msgs.Pose
geometry_msgs/msg/Twistgz.msgs.Twist
geometry_msgs/msg/TwistWithCovariancegz.msgs.TwistWithCovariance
geometry_msgs/msg/TwistWithCovarianceStampedgz.msgs.TwistWithCovariance
geometry_msgs/msg/Vector3gz.msgs.Vector3d
geometry_msgs/msg/Wrenchgz.msgs.Wrench
geometry_msgs/msg/WrenchStampedgz.msgs.Wrench
nav_msgs/msg/Odometrygz.msgs.Odometry
nav_msgs/msg/Odometrygz.msgs.OdometryWithCovariance
rcl_interfaces/msg/ParameterValuegz.msgs.Any
ros_gz_interfaces/msg/Altimetergz.msgs.Altimeter
ros_gz_interfaces/msg/Contactgz.msgs.Contact
ros_gz_interfaces/msg/Contactsgz.msgs.Contacts
ros_gz_interfaces/msg/Dataframegz.msgs.Dataframe
ros_gz_interfaces/msg/Entitygz.msgs.Entity
ros_gz_interfaces/msg/Float32Arraygz.msgs.Float_V
ros_gz_interfaces/msg/GuiCameragz.msgs.GUICamera
ros_gz_interfaces/msg/JointWrenchgz.msgs.JointWrench
ros_gz_interfaces/msg/Lightgz.msgs.Light
ros_gz_interfaces/msg/SensorNoisegz.msgs.SensorNoise
ros_gz_interfaces/msg/StringVecgz.msgs.StringMsg_V
ros_gz_interfaces/msg/TrackVisualgz.msgs.TrackVisual
ros_gz_interfaces/msg/VideoRecordgz.msgs.VideoRecord
ros_gz_interfaces/msg/WorldControlgz.msgs.WorldControl
rosgraph_msgs/msg/Clock*gz.msgs.Clock*
sensor_msgs/msg/BatteryStategz.msgs.BatteryState
sensor_msgs/msg/CameraInfogz.msgs.CameraInfo
sensor_msgs/msg/FluidPressuregz.msgs.FluidPressure
sensor_msgs/msg/Imagegz.msgs.Image
sensor_msgs/msg/Imugz.msgs.IMU
sensor_msgs/msg/JointStategz.msgs.Model
sensor_msgs/msg/Joygz.msgs.Joy
sensor_msgs/msg/LaserScangz.msgs.LaserScan
sensor_msgs/msg/MagneticFieldgz.msgs.Magnetometer
sensor_msgs/msg/NavSatFixgz.msgs.NavSat
sensor_msgs/msg/PointCloud2gz.msgs.PointCloudPacked
std_msgs/msg/Boolgz.msgs.Boolean
std_msgs/msg/ColorRGBAgz.msgs.Color
std_msgs/msg/Emptygz.msgs.Empty
std_msgs/msg/Float32gz.msgs.Float
std_msgs/msg/Float64gz.msgs.Double
std_msgs/msg/Headergz.msgs.Header
std_msgs/msg/Int32gz.msgs.Int32
std_msgs/msg/Stringgz.msgs.StringMsg
std_msgs/msg/UInt32gz.msgs.UInt32
tf2_msgs/msg/TFMessagegz.msgs.Pose_V
trajectory_msgs/msg/JointTrajectorygz.msgs.JointTrajectory

And the service message type correspondence table:

ROS2 message typesGazebo requestGazebo response
ros_gz_interfaces/srv/ControlWorldgz.msgs.WorldControlgz.msgs.Boolean

Optimized integration with ROS2

In the implementation of Ignition Gazebo and ROS2 Integration, different commands need to be used in the terminal to start different modules. This process is somewhat complex. This section will introduce how to optimize it using launch files.

Create a New Package

First, call the following command to create a function package:

ros2 pkg create demo_gazebo_sim

Add Table of Contents

Add the following directories under the newly created package: launch, rviz, world. Then add the following code to CMakeLists.txt:

install(DIRECTORY rviz world launch DESTINATION share/${PROJECT_NAME})

The launch directory is used to store launch files, the rviz directory is used to store RViz2 configuration files, and the world directory is used to store files related to the Ignition Gazebo simulation environment.

Generate rviz2 configuration files in the rviz directory

Launch rviz2, save the default configuration directly to the rviz directory of the current package, and name the saved file sim.rviz.

Copy the world file

Copy the visualize_lidar.sdf file from the worlds directory in the ignition installation path (/usr/share/ignition/ignition-gazebo6/worlds) to the world directory.

If it is not found under that path, it may be located under the ROS installation path:

/opt/ros/jazzy/opt/gz_sim_vendor/share/gz/gz-sim8/worlds/

If you haven't already, manually search for it:

sudo find / -name "visualize_lidar.sdf"

Writing a launch file

Create a new launch file gazebo_sim_demo.launch.py in the launch directory, and enter the following content:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.conditions import IfCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration

from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory('demo_gazebo_sim')
    pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim')
    world_file = os.path.join(this_pkg,'world','visualize_lidar.sdf')

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')),
        launch_arguments={
            'gz_args': '-r ' + world_file
        }.items(),
    )

    # RViz
    rviz = Node(
       package='rviz2',
       executable='rviz2',
       arguments=['-d', os.path.join(this_pkg, 'rviz', 'sim.rviz')],
       condition=IfCondition(LaunchConfiguration('rviz'))
    )

    # Bridge
    bridge = Node(
        package='ros_gz_bridge',
        executable='parameter_bridge',
        arguments=['/model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist',
                   '/model/vehicle_blue/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry',
                   '/model/vehicle_blue/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V',
                   ],
        parameters=[{'qos_overrides./model/vehicle_blue.subscriber.reliability': 'reliable'}],
        remappings=[
                ('/model/vehicle_blue/tf', '/tf'),
                ('/model/vehicle_blue/cmd_vel','cmd_vel')
            ],
        output='screen'
    )

    return LaunchDescription([
        gz_sim,
        DeclareLaunchArgument('rviz', default_value='true',
                              description='Open RViz.'),
        bridge,
        rviz
    ])

In this launch file, the Ignition Gazebo simulation environment is started, a connection between the simulation and ROS2 is established via ros_gz_bridge, and the rviz2 node is launched. When establishing the connection, message conversion for velocity commands, odometry, and coordinate transforms is implemented.

Build

In the terminal, navigate to the current workspace and compile the package:

colcon build  --packages-select demo_gazebo_sim

Execute

In the terminal, navigate to the current workspace and run the following command to execute the launch file:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_demo.launch.py

Open a new terminal and start the keyboard control node:

ros2 run teleop_twist_keyboard teleop_twist_keyboard

Reconfigure rviz2, set Fixed Frame to vehicle_blue/odom, add the TF plugin, add the Odometry plugin and set the topic to /model/vehicle_blue/odometry. When sending velocity commands via keyboard control, the robot in the simulation environment starts moving, and coordinate transforms as well as odometry messages can be displayed in rviz2.

Creating a Simulation Environment SDF File

In the previous sections, we used the built-in simulation environment of Ignition Gazebo. Starting from this section, we will introduce how to build your own simulation environment. The example in this section will simulate a rectangular room that is 10 meters long and 5 meters wide. For this example, you can first launch Ignition Gazebo and build the simulation environment by dragging and dropping, then modify the corresponding simulation environment files to adjust the details.

Relationship between SDF, URDF, and Xacro:

  • Difference between URDF and SDF:
    • Complexity: SDF supports more powerful features and can describe a complete simulation environment; URDF is better suited for defining robot models.
    • Purpose: URDF is the standard for ROS; SDF is the standard for Gazebo.
    • Physics Engine Support: URDF supports Gazebo through plugins; SDF natively supports Gazebo.
    • Format Conversion: URDF can be converted to SDF (via the tool gz sdf -p provided by ROS).
  • The Role of Xacro:
    • Xacro is a URDF generation tool that helps users efficiently write URDF files, but it has no direct relationship with SDF.

Practical Advice

  • In the Gazebo simulation: If you are using ROS 2 and Gazebo, you can directly use SDF files, which offer more powerful functionality.
  • In ROS: If primarily used for robot control and planning, URDF or URDF generated by Xacro is recommended.
  • Combining the two: Use URDF for control and SDF for simulation. For example, after defining the robot structure with URDF, convert it to SDF using Gazebo plugins.

Example Comparison

URDF Example:

<robot name="example_robot">
  <link name="base_link">
    <inertial>
      <mass value="1.0" />
      <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
    </inertial>
  </link>
</robot>

Xacro example (generating URDF):

<xacro:robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="example_robot">
  <xacro:macro name="base_link" params="mass">
    <link name="base_link">
      <inertial>
        <mass value="${mass}" />
        <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
      </inertial>
    </link>
  </xacro:macro>

  <xacro:base_link mass="1.0" />
</xacro:robot>

SDF example:

<sdf version="1.6">
  <model name="example_robot">
    <link name="base_link">
      <inertial>
        <mass>1.0</mass>
        <inertia>
          <ixx>1.0</ixx>
          <iyy>1.0</iyy>
          <izz>1.0</izz>
        </inertia>
      </inertial>
    </link>
  </model>
</sdf>

1. Create an sdf file

First, call the command ign gazebo to launch Gazebo, select the Empty simulation environment, then add cubes, with each cube corresponding to a wall.

The coordinates corresponding to the up, down, left, and right cubes box, box_1, box_2, and box_3 are (5.0, 0.0, 0.5), (-5.0, 0.0, 0.5), (0.0, 2.5, 0.5), and (0.0, -2.5, 0.5), respectively.

(The coordinates above refer to X, Y, Z coordinates, with no rotation.)

Save the file to the world directory of the function package. The saved file must have the .sdf extension; here, the file name is house.sdf.

<sdf version='1.9'>
  <world name='empty'>
    <physics name='1ms' type='ignored'>
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1</real_time_factor>
      <real_time_update_rate>1000</real_time_update_rate>
    </physics>
    <plugin name='gz::sim::systems::Physics' filename='ignition-gazebo-physics-system'/>
    <plugin name='gz::sim::systems::UserCommands' filename='ignition-gazebo-user-commands-system'/>
    <plugin name='gz::sim::systems::SceneBroadcaster' filename='ignition-gazebo-scene-broadcaster-system'/>
    <plugin name='gz::sim::systems::Contact' filename='ignition-gazebo-contact-system'/>
    <gravity>0 0 -9.8</gravity>
    <magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
    <atmosphere type='adiabatic'/>
    <scene>
      <ambient>0.4 0.4 0.4 1</ambient>
      <background>0.7 0.7 0.7 1</background>
      <shadows>true</shadows>
    </scene>
    <model name='ground_plane'>
      <static>true</static>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='visual'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.8 0.8 0.8 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <inertial>
          <pose>0 0 0 0 -0 0</pose>
          <mass>100</mass>
          <inertia>
            <ixx>1</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>1</iyy>
            <iyz>0</iyz>
            <izz>1</izz>
          </inertia>
        </inertial>
        <enable_wind>false</enable_wind>
      </link>
      <pose>0 0 0 0 -0 0</pose>
      <self_collide>false</self_collide>
    </model>
    <model name='box'>
      <pose>5.0 0 0.5 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_0'>
      <pose>-5.0 -0 0.50000 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_1'>
      <pose>-0 -2.5 0.5 -0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_2'>
      <pose>-0 2.5 0.5 0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <light name='sun' type='directional'>
      <pose>0 0 10 0 -0 0</pose>
      <cast_shadows>true</cast_shadows>
      <intensity>1</intensity>
      <direction>-0.5 0.1 -0.9</direction>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <linear>0.01</linear>
        <constant>0.90000000000000002</constant>
        <quadratic>0.001</quadratic>
      </attenuation>
      <spot>
        <inner_angle>0</inner_angle>
        <outer_angle>0</outer_angle>
        <falloff>0</falloff>
      </spot>
    </light>
  </world>
</sdf>

2. Modify the sdf file

Modify the SDF file to adjust the cube dimensions and enclose the walls. In the SDF file, the four cubes correspond to four <model> tags, whose name attributes are box, box_1, box_2, and box_3. Change the <size>1 1 1</size> in box and box_1 to <size>0.1 5 1</size>, and change the <size>1 1 1</size> in box_2 and box_3 to <size>10 0.1 1</size> (Note: Each <model> tag contains two <size> tags, located under the <collision> tag and the <visual> tag respectively; both <size> tag contents need to be modified).

The modified content of the house.sdf file is as follows:

<sdf version='1.9'>
  <world name='empty'>
    <physics name='1ms' type='ignored'>
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1</real_time_factor>
      <real_time_update_rate>1000</real_time_update_rate>
    </physics>
    <plugin name='ign::gazebo::systems::Physics' filename='ignition-gazebo-physics-system'/>
    <plugin name='ign::gazebo::systems::UserCommands' filename='ignition-gazebo-user-commands-system'/>
    <plugin name='ign::gazebo::systems::SceneBroadcaster' filename='ignition-gazebo-scene-broadcaster-system'/>
    <plugin name='ign::gazebo::systems::Contact' filename='ignition-gazebo-contact-system'/>
    <gravity>0 0 -9.8</gravity>
    <magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
    <atmosphere type='adiabatic'/>
    <scene>
      <ambient>0.4 0.4 0.4 1</ambient>
      <background>0.7 0.7 0.7 1</background>
      <shadows>true</shadows>
    </scene>
    <model name='ground_plane'>
      <static>true</static>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='visual'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.8 0.8 0.8 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <inertial>
          <pose>0 0 0 0 -0 0</pose>
          <mass>100</mass>
          <inertia>
            <ixx>1</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>1</iyy>
            <iyz>0</iyz>
            <izz>1</izz>
          </inertia>
        </inertial>
        <enable_wind>false</enable_wind>
      </link>
      <pose>0 0 0 0 -0 0</pose>
      <self_collide>false</self_collide>
    </model>
    <model name='box'>
      <pose>5.0 0 0.5 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_0'>
      <pose>-5.0 -0 0.50000 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_1'>
      <pose>-0 -2.5 0.5 -0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_2'>
      <pose>-0 2.5 0.5 0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <light name='sun' type='directional'>
      <pose>0 0 10 0 -0 0</pose>
      <cast_shadows>true</cast_shadows>
      <intensity>1</intensity>
      <direction>-0.5 0.1 -0.9</direction>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <linear>0.01</linear>
        <constant>0.90000000000000002</constant>
        <quadratic>0.001</quadratic>
      </attenuation>
      <spot>
        <inner_angle>0</inner_angle>
        <outer_angle>0</outer_angle>
        <falloff>0</falloff>
      </spot>
    </light>
  </world>
</sdf>

3. Write the launch file

Create a new launch file gazebo_sim_world.launch.py in the launch directory, and enter the following content:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

def generate_launch_description():

    this_pkg = get_package_share_directory('demo_gazebo_sim')
    pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim')
    world_file = os.path.join(this_pkg,"world","house.sdf")

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')),
        launch_arguments={
            'gz_args': '-r ' + world_file
        }.items(),
    )
    return LaunchDescription([
        gz_sim
    ])

4. Build

In the terminal, navigate to the current workspace and compile the package:

colcon build  --packages-select demo_gazebo_sim

5. Execution

In the terminal, navigate to the current workspace and run the following command to execute the launch file:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py

The running result is shown in the figure below.

You can also continue designing the room model according to personal preference.

Adding a Model to IgnG

The Ignition Gazebo official website provides many simulation models that can be downloaded and used to optimize the simulation environment, making it more diverse, visually appealing, and realistic.

Resource Download

Official model links for Ignition Gazebo simulation:

http://app.ignitionrobotics.org/fuel/models

Select a simulation model, click to enter its details page, and then click the download button to save the model resource to your local device.

Create a new ign_models directory in the user directory, and extract the downloaded resources into this directory for later use.

Resource Allocation

To allow Ignition Gazebo to recognize model resources, the next step is to modify the .bashrc file in the user directory by adding the following code:


# Humble版本一般是下面的,但是有可能会更新,如果不生效,请尝试Jazzy的宏
export IGN_GAZEBO_RESOURCE_PATH=~/ign_models

# Jazzy版本的宏改了,如下:
export GZ_SIM_RESOURCE_PATH=~/ign_models

https://gazebosim.org/docs/latest/fuel_insert/

Model Addition

In the terminal, navigate to the world directory of the demo_gazebo_sim package. Use the command ign gazebo house.sdf or gz sim house.sdf to launch the simulation environment. Click the collapse button in the upper right corner of the window, search for Resource Spawner and open it. Click Local resources and select a model to drag into the simulation environment. Save the modified content to the house.sdf file.

After downloading the resources normally, they will appear here in the local resources section.

Here is an example of the contents of the house.sdf file:

<sdf version='1.9'>
  <world name='empty'>
    <physics name='1ms' type='ignored'>
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1</real_time_factor>
      <real_time_update_rate>1000</real_time_update_rate>
    </physics>
    <plugin name='gz::sim::systems::Physics' filename='ignition-gazebo-physics-system'/>
    <plugin name='gz::sim::systems::UserCommands' filename='ignition-gazebo-user-commands-system'/>
    <plugin name='gz::sim::systems::SceneBroadcaster' filename='ignition-gazebo-scene-broadcaster-system'/>
    <plugin name='gz::sim::systems::Contact' filename='ignition-gazebo-contact-system'/>
    <gravity>0 0 -9.8</gravity>
    <magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
    <atmosphere type='adiabatic'/>
    <scene>
      <ambient>0.4 0.4 0.4 1</ambient>
      <background>0.7 0.7 0.7 1</background>
      <shadows>true</shadows>
    </scene>
    <model name='ground_plane'>
      <static>true</static>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='visual'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.8 0.8 0.8 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <inertial>
          <pose>0 0 0 0 -0 0</pose>
          <mass>100</mass>
          <inertia>
            <ixx>1</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>1</iyy>
            <iyz>0</iyz>
            <izz>1</izz>
          </inertia>
        </inertial>
        <enable_wind>false</enable_wind>
      </link>
      <pose>0 0 0 0 -0 0</pose>
      <self_collide>false</self_collide>
    </model>
    <model name='box'>
      <pose>5.02632 -2e-06 0.500002 -0 4.4e-05 4.6e-05</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_0'>
      <pose>-5.01336 -0.00029 0.500002 0 -4.2e-05 -0.005335</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_1'>
      <pose>-0 -2.5 0.5 1e-06 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_2'>
      <pose>-0.000154 2.52488 0.500821 -0.018068 -0 -0.003156</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>false</static>
      <self_collide>false</self_collide>
    </model>
    <include>
      <uri>file://Bed</uri>
      <name>Bed</name>
      <pose>2.82155 1.18752 0 0 -0 0</pose>
    </include>
    <include>
      <uri>file://Office Desk</uri>
      <name>Desk</name>
      <pose>2.78306 -1.97796 0 0 -0 1.57</pose>
    </include>
    <include>
      <uri>file://Bathtub</uri>
      <name>Bathtub</name>
      <pose>-3.87509 1.82783 0 0 -0 0</pose>
    </include>
    <include>
      <uri>file://Vanity</uri>
      <name>Vanity</name>
      <pose>-2.5974 1.85613 -0.010992 0.021648 0 -1.57</pose>
    </include>
    <include>
      <uri>file://Vanity</uri>
      <name>Vanity_1</name>
      <pose>-2.5974 0.634325 -0.010992 0.021648 0 -1.57</pose>
    </include>
    <include>
      <uri>file://Dining Table</uri>
      <name>DiningTable</name>
      <pose>-0.374337 1.33602 0 0 0 -1.57</pose>
    </include>
    <include>
      <uri>file://Chair</uri>
      <name>Chair</name>
      <pose>2.79762 -1.26474 -0 -0 0 -2.3062</pose>
    </include>
    <include>
      <uri>file://Sofa</uri>
      <name>Sofa</name>
      <pose>-0.546136 -1.92328 0.000119 -0 0 1.57</pose>
    </include>
    <light name='sun' type='directional'>
      <pose>0 0 10 0 -0 0</pose>
      <cast_shadows>true</cast_shadows>
      <intensity>1</intensity>
      <direction>-0.5 0.1 -0.9</direction>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <linear>0.01</linear>
        <constant>0.90000000000000002</constant>
        <quadratic>0.001</quadratic>
      </attenuation>
      <spot>
        <inner_angle>0</inner_angle>
        <outer_angle>0</outer_angle>
        <falloff>0</falloff>
      </spot>
    </light>
  </world>
</sdf>

Build

In the terminal, navigate to the current workspace and compile the package:

colcon build  --packages-select demo_gazebo_sim

Execute

In the terminal, navigate to the current workspace and run the following command to execute the launch file:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py

The running result is shown in the figure below.

IgnG adds a robot

In Ignition Gazebo, you can directly create robot models, or load robot models in URDF format from ROS2. Here we use the latter approach (you can also choose to use your own URDF car, but be sure to modify the launch path).

I didn't use Teacher Zhao Xuzuo's mycar_description; instead, I used my own car model. All the subsequent source code is in the GitHub repository below. Feel free to clone it if needed.

https://github.com/tungchiahui/ROS2\_WS/tree/main/6.ws\_simulations

Prepare Robot Model Function Package

Copy the robot description package mycar_description to the src directory of the workspace, create a new mycar_description directory in ign_models, and copy the mesh directory from the mycar_description package into the mycar_description directory within ign_models.

Create a launch file under the robot model function package

Create a new launch file mycar_desc_sim.launch.py and enter the following content:

from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command,LaunchConfiguration
from launch.actions import DeclareLaunchArgument

#示例: ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo01_helloworld.urdf
def generate_launch_description():

    MYCAR_MODEL = os.environ['MYCAR_MODEL']

    mycar_description = get_package_share_directory("mycar_description")
    default_model_path = os.path.join(mycar_description,"urdf",MYCAR_MODEL + ".urdf")
    model = DeclareLaunchArgument(name="model", default_value=default_model_path)

    # 加载机器人模型

    # 启动 robot_state_publisher 节点并以参数方式加载 urdf 文件
    robot_description = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
    robot_state_publisher = Node(
        package="robot_state_publisher",
        executable="robot_state_publisher",
        parameters=[{"robot_description": robot_description}]
    )

    return LaunchDescription([
        model,
        robot_state_publisher,
    ])

Compared to previous versions, this file is missing the joint_state_publisher node, which is responsible for publishing active joint states. This functionality will be implemented later by Ignition.

Adding a Robot Model

Modify the gazebo_sim_world.launch.py file to include the robot model publishing file and spawn the robot model in Gazebo. The modified code is as follows:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory("demo_gazebo_sim")
    mycar_desc_pkg = get_package_share_directory("mycar_description")
    pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
    world_file = os.path.join(this_pkg,"world","house.sdf")

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
        launch_arguments={
            "gz_args": "-r " + world_file
        }.items(),
    )
    mycar_desc = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
        )
    )
    spawn = Node(package="ros_gz_sim", executable="create",
                arguments=[
                "-name", "mycar",
                "-x", "0",
                "-z", "0.01", #设置为0,可能会陷进地里
                "-y", "0",
                "-topic", "/robot_description"],
            output="screen")

    return LaunchDescription([
        gz_sim,
        spawn,
        mycar_desc,
    ])

Build

In the terminal, navigate to the current workspace and compile the package:

colcon build --packages-select mycar_description demo_gazebo_sim

Execute

In the terminal, navigate to the current workspace and run the following command to execute the launch file: (If there are issues during execution, as long as you've learned how to run URDF, you should have the ability to find and fix the errors yourself — go ahead and troubleshoot.)

. install/setup.bash

# MYCAR_MODEL值可以设置为arduino、stm32_2w 或stm32_4w(这个是具体的urdf文件名,在mycar_description包下的)
export MYCAR_MODEL=stm32_4w
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py

The running result is shown in the figure below.

   IgnG motion controller (essentially the <gazebo> tag)

This section will introduce how to make your robot move.

The principle is to add <gazebo> tags to URDF, Xacro, or similar files:

http://sdformat.org/tutorials?tut=sdformat_urdf_extensions&cat=specification&

https://gazebosim.org/api/plugin/2/index.html

Install the library:

https://gazebosim.org/api/plugin/2/installation.html

sudo apt-get update
sudo apt-get install lsb-release
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list'
wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
sudo apt-get update
sudo apt install libgz-plugin2-dev

Using plugins to make the car move, such as two-wheel differential plugins, four-wheel Mecanum wheel plugins, and so on.

Additionally, this plugin provides some options for controlling the output. Since it's a simulation, you also need to tell the plugin the corresponding joint names for the wheels and other information, resulting in the parameter table below:

Configuration itemMeaning
rosROS-related configuration, including namespaces and topic remapping, among others.
update_rateData update rate
left_jointLeft wheel joint name
right_jointRight wheel joint name
wheel_separationDistance between the left and right wheels
wheel_diameterThe diameter of the wheel
max_wheel_torqueThe wheel's maximum torque
max_wheel_accelerationThe maximum acceleration of the wheel
publish_odomPublish odometry
publish_odom_tfWhether to publish the odometry tf switch
publish_wheel_tfSwitch for publishing wheel tf data
odometry_frameThe framed ID of the odometer is ultimately reflected in the topic and TF.
robot_base_frameThe robot's base frame ID

Modifying the URDF File

In the arduino.urdf and stm32_2w.urdf files, add the following code under the <robot> root tag:


  <gazebo>
      <plugin filename="libignition-gazebo-diff-drive-system.so"
        name="ignition::gazebo::systems::DiffDrive">
        <left_joint>left_joint</left_joint>
        <right_joint>right_joint</right_joint>
        <wheel_separation>0.2097</wheel_separation>
        <wheel_radius>0.03415</wheel_radius>
        <odom_publish_frequency>10</odom_publish_frequency>
        <frame_id>odom</frame_id>
        <child_frame_id>base_footprint</child_frame_id>
        <topic>/cmd_vel</topic>
        <max_linear_acceleration>10</max_linear_acceleration>
        <min_linear_acceleration>-10</min_linear_acceleration>
        <max_angular_acceleration>10</max_angular_acceleration>
        <min_angular_acceleration>-10</min_angular_acceleration>
        <max_linear_velocity>0.5</max_linear_velocity>
        <min_linear_velocity>-0.5</min_linear_velocity>
        <max_angular_velocity>1</max_angular_velocity>
        <min_angular_velocity>-1</min_angular_velocity>
      </plugin>

  </gazebo>

  <gazebo>
    <plugin filename="ignition-gazebo-joint-state-publisher-system"
      name="ignition::gazebo::systems::JointStatePublisher">
    </plugin>
  </gazebo>

In the stm32_4w.urdf file, add the following code under the <robot> root tag:


<gazebo>
    <plugin
        filename="ignition-gazebo-diff-drive-system"
        name="ignition::gazebo::systems::DiffDrive">
        <left_joint>left_former_joint</left_joint>
        <left_joint>left_rear_joint</left_joint>
        <right_joint>right_former_joint</right_joint>
        <right_joint>right_rear_joint</right_joint>
        <wheel_separation>0.4</wheel_separation>
        <wheel_radius>0.0415</wheel_radius>
        <odom_publish_frequency>50</odom_publish_frequency>
        <frame_id>odom</frame_id>
        <child_frame_id>base_footprint</child_frame_id>
        <topic>/cmd_vel</topic>
        <max_linear_acceleration>10</max_linear_acceleration>
        <min_linear_acceleration>-10</min_linear_acceleration>
        <max_angular_acceleration>10</max_angular_acceleration>
        <min_angular_acceleration>-10</min_angular_acceleration>
        <max_linear_velocity>0.5</max_linear_velocity>
        <min_linear_velocity>-0.5</min_linear_velocity>
        <max_angular_velocity>1</max_angular_velocity>
        <min_angular_velocity>-1</min_angular_velocity>
      </plugin>
  </gazebo>

  <gazebo>
    <plugin filename="ignition-gazebo-joint-state-publisher-system"
      name="ignition::gazebo::systems::JointStatePublisher">
    </plugin>
  </gazebo>

If using Mecanum wheels (for ROS1; ROS2 version pending update — just set the wheel joint names to your own wheel joint names):

<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">

    <xacro:macro name="joint_trans" params="joint_name">

        <transmission name="${joint_name}_trans">
            <type>transmission_interface/SimpleTransmission</type>
            <joint name="${joint_name}">
                <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
            </joint>
            <actuator name="${joint_name}_motor">
                <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
                <mechanicalReduction>1</mechanicalReduction>
            </actuator>
        </transmission>
    </xacro:macro>

    <xacro:joint_trans joint_name="LeftFrontwheelToBase" />
    <xacro:joint_trans joint_name="LeftBackwheelToBase" />
    <xacro:joint_trans joint_name="RightFrontwheelToBase" />
    <xacro:joint_trans joint_name="RightBackwheelToBase" />

    <gazebo>
        <plugin name="mecanum_controller" filename="libgazebo_ros_planar_move.so">
            <commandTopic>cmd_vel</commandTopic>
            <odometryTopic>odom</odometryTopic>
            <odometryFrame>odom</odometryFrame>
            <leftFrontJoint>LeftFrontwheelToBase</leftFrontJoint>
            <rightFrontJoint>RightFrontwheelToBase</rightFrontJoint>
            <leftRearJoint>LeftBackwheelToBase</leftRearJoint>
            <rightRearJoint>RightBackwheelToBase</rightRearJoint>
            <odometryRate>100</odometryRate>
            <robotBaseFrame>base_footprint</robotBaseFrame>
            <broadcastTF>1</broadcastTF>
        </plugin>
    </gazebo>

</robot>

Modify the launch file

Modify the gazebo_sim_world.launch.py file. The modified code is as follows:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory("demo_gazebo_sim")
    mycar_desc_pkg = get_package_share_directory("mycar_description")
    pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
    world_file = os.path.join(this_pkg,"world","base.sdf")

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
        launch_arguments={
            "gz_args": "-r " + world_file
        }.items(),
    )
    mycar_desc = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
        )
    )
    spawn = Node(package="ros_gz_sim", executable="create",
                arguments=[
                "-name", "mycar",
                "-x", "-4",
                "-z", "0.01", #设置为0,可能会陷进地里
                "-y", "0",
                "-topic", "/robot_description"],
            output="screen")

    # Bridge
    bridge = Node(
        package="ros_gz_bridge",
        executable="parameter_bridge",
        arguments=["/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist",
                   "/model/mycar/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry",
                   "/model/mycar/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V",
                   "/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock",
                   "/world/empty/model/mycar/joint_state@sensor_msgs/msg/JointState[gz.msgs.Model",
                   ],
        parameters=[{"qos_overrides./model/mycar.subscriber.reliability": "reliable"}],
        remappings=[
                ("/model/mycar/tf", "/tf"),
                ("/world/empty/model/mycar/joint_state","joint_states"),
                ("/model/mycar/odometry","/odom")
            ],
        output="screen"
    )

    return LaunchDescription([
        gz_sim,
        spawn,
        mycar_desc,
        bridge
    ])

Build

In the terminal, navigate to the current workspace and compile the package:

colcon build --packages-select mycar_description demo_gazebo_sim

Execute

In the terminal, navigate to the current workspace and run the following command to execute the launch file:

. install/setup.bash
export MYCAR_MODEL=stm32_4w # MYCAR_MODEL值可以设置为arduino、stm32_2w 或stm32_4w(这个是具体的urdf文件名,在mycar_description包下的)
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py

Restart the keyboard control node, and you can control the robot's movement.

ros2 run teleop_twist_keyboard teleop_twist_keyboard

You can also launch rviz2 to view odometry messages and coordinate transforms. In the terminal, navigate to the current workspace and execute the following command to run the launch file:

Start rviz2

. install/setup.bash
rviz2

The RVIZ2 software configuration is shown in the following image:

Ignition Gazebo Simulation: Sensors

This section will introduce how to add sensors such as LiDAR and cameras to a simulated robot. The code in this section is fully universal for the robot models covered in our tutorial, including Arduino, STM32_2W, and STM32_4W.

Adding Sensor Plugins

Before simulating the sensor, you need to add a plugin named ignition-gazebo-sensors-system. Open the URDF file and add the following code inside the <robot> root tag:


<gazebo>
    <plugin
      filename="ignition-gazebo-sensors-system"
      name="ignition::gazebo::systems::Sensors">
      <render_engine>ogre2</render_engine>
    </plugin>
  </gazebo>

The ignition-gazebo-sensors-system is a plugin for the Ignition Gazebo simulation environment, providing sensor models and related functionality for creating, simulating, and testing various sensor devices. It includes common sensor models such as cameras, LiDAR, and others.

Adding Various Sensors

(Note: Your model must include models for the following sensors)

The radar model does not need collision. Please delete it, otherwise it will block the laser emission.


  <gazebo reference="laser">
    <sensor name='gpu_lidar' type='gpu_lidar'>
      <topic>scan</topic>
      <update_rate>30</update_rate>
      <lidar>
        <scan>
          <horizontal>
            <samples>640</samples>
            <resolution>1</resolution>
            <min_angle>-3.1415926</min_angle>
            <max_angle>3.1415926</max_angle>
          </horizontal>
          <vertical>
            <samples>16</samples>
            <resolution>1</resolution>
            <min_angle>-0.261799</min_angle>
            <max_angle>0.261799</max_angle>
          </vertical>
        </scan>
        <range>
          <min>0.08</min>
          <max>10.0</max>
          <resolution>0.01</resolution>
        </range>
      </lidar>
      <alwaysOn>1</alwaysOn>
      <visualize>true</visualize>
      <ignition_frame_id>laser</ignition_frame_id>
    </sensor>
  </gazebo>

  <gazebo reference="camera" >
    <sensor name="cam_link" type="camera">
      <update_rate>10.0</update_rate>
      <always_on>true</always_on>
      <ignition_frame_id>camera</ignition_frame_id>
      <pose>0 0 0 0 0 0</pose>
      <topic>/image_raw</topic>
      <camera name="my_camera">
        <horizontal_fov>1.3962634</horizontal_fov>
        <image>
           <width>600</width>
           <height>600</height>
           <format>R8G8B8</format>
        </image>
        <clip>
          <near>0.02</near>
          <far>300</far>
        </clip>
      </camera>
    </sensor>
  </gazebo>

  <gazebo reference="camera">
    <sensor name="depth_camera" type="depth_camera">
          <update_rate>10</update_rate>
          <topic>depth_camera</topic>
          <camera>
            <horizontal_fov>1.05</horizontal_fov>
            <image>
              <width>256</width>
              <height>256</height>
              <format>R_FLOAT32</format>
            </image>
            <clip>
              <near>0.1</near>
              <far>10.0</far>
            </clip>
          </camera>
          <alwaysOn>1</alwaysOn>
          <ignition_frame_id>camera</ignition_frame_id>
      </sensor>
  </gazebo>

IMU sensor found on the official website


    <gazebo>
        <plugin filename="libignition-gazebo-imu-system.so"
                name="ignition::gazebo::systems::Imu">
        </plugin>
    </gazebo>

    <gazebo reference="base_link">
        <sensor name="imu_sensor" type="imu">
            <always_on>1</always_on>
            <update_rate>30</update_rate>
            <visualize>true</visualize>
            <topic>imu</topic>
        </sensor>
    </gazebo>

You can use ign topic -e -t /imu to test whether Gazebo has published a topic, and then use gazebo_bridge to pass the topic to ROS2.

By default, rviz2 does not include a plugin for displaying IMU messages. You need to install the relevant plugin yourself. The specific installation command is as follows:

sudo apt install ros-${ROS_DISTRO}-imu-tools

The model automatically generated by SolidWorks may have flipped the laser_joint. Please correct it so that rivz2 can have the laser. Then, modify the visualization model to make it display properly, and do not assign collision properties to it, as that might block the laser.

Modify the gazebo_sim_world.launch.py file. The modified code is as follows:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory("demo_gazebo_sim")
    mycar_desc_pkg = get_package_share_directory("mycar_description")
    pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
    world_file = os.path.join(this_pkg,"world","house.sdf")

    gz_sim = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(
        os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
        launch_arguments={
        "gz_args": "-r " + world_file
        }.items(),
    )
    mycar_desc = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
        )
    )
    spawn = Node(package="ros_gz_sim", executable="create",
        arguments=[
            "-name", "mycar",
            "-x", "-4",
            "-z", "0.01", #设置为0,可能会陷进地里
            "-y", "0",
            "-topic", "/robot_description"],
        output="screen")

    # Bridge
    bridge = Node(
        package="ros_gz_bridge",
        executable="parameter_bridge",
        arguments=["/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist",
            "/model/mycar/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry",
            "/model/mycar/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V",
            "/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock",
            "/world/empty/model/mycar/joint_state@sensor_msgs/msg/JointState[gz.msgs.Model",
            "/scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan",
            "/scan/points@sensor_msgs/msg/PointCloud2@gz.msgs.PointCloudPacked",
            "/image_raw@sensor_msgs/msg/Image@gz.msgs.Image",
            "/camera_info@sensor_msgs/msg/CameraInfo@gz.msgs.CameraInfo",
            "/depth_camera@sensor_msgs/msg/Image@gz.msgs.Image",
            "/imu@sensor_msgs/msg/Imu[gz.msgs.IMU",
            "/imu/angular_velocity@geometry_msgs/msg/Vector3[gz.msgs.Vector3d"
        ],
        parameters=[{"qos_overrides./model/mycar.subscriber.reliability": "reliable"}],
        remappings=[
            ("/model/mycar/tf", "/tf"),
            ("/world/empty/model/mycar/joint_state","joint_states"),
            ("/model/mycar/odometry","/odom")
        ],
        output="screen"
    )

    return LaunchDescription([
        gz_sim,
        spawn,
        mycar_desc,
        bridge
    ])

Build

In the terminal, navigate to the current workspace and compile the package:

colcon build --packages-select mycar_description demo_gazebo_sim

Execute

In the terminal, navigate to the current workspace and run the following command to execute the launch file:

. install/setup.bash
export MYCAR_MODEL=stm32_4w # MYCAR_MODEL值可以设置为arduino、stm32_2w 或stm32_4w
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py

Restart the keyboard control node, and you can control the robot's movement.

You can also launch rviz2 to view the various data published by the robot. In the terminal, navigate to the current workspace and execute the following command to run the launch file:

. install/setup.bash
rviz2

I didn't use Teacher Zhao Xuzuo's mycar_description; instead, I used my own car model. All the subsequent source code is in the GitHub repository below. Feel free to clone it if needed.

https://github.com/tungchiahui/ROS2_WS/tree/main/6.ws_simulations

(Reproduce everything above to proceed to the next chapter navigation. The next chapter navigation is still based on simulation.)

音乐页