Visualization Platform RVIZ2 and URDF Modeling Language
Visual Introduction



Rviz2 plugins related to coordinates, LiDAR, and cameras.





Basic usage of rviz2

When installing ROS2 in the sudo apt install ros-[ROS_DISTRO]-desktop format, RViz is already installed by default.
sudo apt install ros-[ROS_DISTRO]-rviz2
Note: The [ROS_DISTRO] in the command refers to the ROS2 version.

Method 1: rviz2;
Method 2: ros2 run rviz2 rviz2.

After rviz2 starts, the default interface is as follows:
- The upper part is the toolbar, which includes view control, estimated pose setting, target setting, and more. Custom plugins can also be added.
- The left side is the plugin display area: it includes functions such as adding, deleting, copying, and renaming plugins, displaying plugins, and setting plugin properties.
- In the middle is the 3D view display area: it visually shows the added plugin information.
- On the right is the observation angle settings area: you can set different observation angles.
- Below is the time display area: including system time and ROS time.
The left plugin display area has two plugins by default:
- Global Options: This plugin is used to set global display-related parameters. Generally, the option that needs to be configured manually is the Fixed Frame option, which defines the global coordinate system that all other data reference during visualization.
- Global Status: This plugin is used to display whether all coordinate transformations are functioning properly after the Fixed Frame is set in the Global Options.

At the very top is the menu area, on the left is the plugin display area, in the center is the 3D debugging area, on the right is the view switching area, and at the very bottom is the time area. ROS Time refers to the ROS2 time, Wall Time refers to the system time, and Elapsed is the time Rviz2 has been running.

You can save the rviz2 configuration, and also open a configuration.

Set up the display panel

You can set the number of grid cells on the plane.

You can set the number of vertical grid cells.

You can set the grid side length to any size.

Change the 4 channels of sRGB

The perspective can be changed.

Set the offset. If Z is -1, the grid will sink 1 unit relative to the coordinate system.

Fixed name is typically the name of the root coordinate system.
background color is the background color
frame rate is the publishing frequency of the coordinate system

When the fixed name is set correctly, there will be no warning for the global state.

Perspective switching (generally default)

Can flip the Z-axis.
Common Plugins:
| Serial number | Name | Function | Message type |
|---|---|---|---|
| 1 | Axes | Display the default coordinate system of rviz2. | |
| 2 | Camera | To display the camera image, you must use the message: CameraInfo. | sensor_msgs/msg/Image,sensor_msgs/msg/CameraInfo |
| 3 | Grid | Display a grid centered on the origin of the reference coordinate system. | |
| 4 | Grid Cells | Drawing cells from a grid, typically representing obstacles in the costmap of a navigation stack. | nav_msgs/msg/GridCells |
| 5 | Image | Displays the camera image, but unlike the Camera plugin, it does not require using the CameraInfo message. | sensor_msgs/msg/Image |
| 6 | InteractiveMarker | Displays 3D objects from one or more interactive marker servers and allows mouse interaction with them. | visualization_msgs/msg/InteractiveMarker |
| 7 | Laser Scan | Display LiDAR data. | sensor_msgs/msg/LaserScan |
| 8 | Map | Display map data. | nav_msgs/msg/OccupancyGrid |
| 9 | Markers | Allows developers to display geometry of any original shape through theming. | visualization_msgs/msg/Marker,visualization_msgs/msg/MarkerArray |
| 10 | Path | Display path-related data in robot navigation. | nav_msgs/msg/Path |
| 11 | PointStamped | Draw a point in the form of a small ball. | geometry_msgs/msg/PointStamped |
| 12 | Pose | Draw the pose using arrows or coordinate axes. | geometry_msgs/msg/PoseStamped |
| 13 | Pose Array | Draw a set of poses. | geometry_msgs/msg/PoseArray |
| 14 | Point Cloud2 | Plot point cloud data. | sensor_msgs/msg/PointCloud,sensor_msgs/msg/PointCloud2 |
| 15 | Polygon | Draw the outline of the polygon as a line. | geometry_msgs/msg/Polygon |
| 16 | Odometry | Displays the accumulated odometry messages over time. | nav_msgs/msg/Odometry |
| 17 | Range | Displays a cone representing distance measurements from a sonar or infrared distance sensor. | sensor_msgs/msg/Range |
| 18 | RobotModel | Display the robot model. | |
| 19 | TF | Display the tf transform hierarchy. | |
| 20 | Wrench | Display the geometry_msgs/WrenchStamped message as an arrow representing force and an arrow with a circle representing torque. | geometry_msgs/msg/WrenchStamped |
| 21 | Oculus | Render the RViz scene onto an Oculus headset. |
Each of the above plugins also contains many properties, which can be configured to control the final display effect of the plugin.

Image is a camera data plugin.
LaserScan is a LiDAR data plugin.
TF is a coordinate transformation plugin.
RobotModel is a robot model plugin.
ros2 run rviz2 rviz2 -d xxx.rviz
#可以读取自己保存的rviz配置
Basic workflow for integrating URDF with rviz2
Case Analysis





Please run the following command to install the two required function packages (which can control the robot's joint movements):
sudo apt install ros-humble-joint-state-publisher
sudo apt install ros-humble-joint-state-publisher-gui
In the terminal, navigate to the src directory of your workspace and run the following command to create a C++ package.
ros2 pkg create cpp06_urdf --build-type ament_cmake
Create the urdf, rviz, launch, and meshes directories under the function package for later use. Within the urdf directory, create subdirectories urdf and xacro to store URDF files and Xacro files respectively.

launch stores launch files
The urdf file stores the 3D model file in URDF format.
meshes stores STL models
xacro can simplify URDF files and enhance their flexibility.
rviz stores the configuration for rviz2
Framework setup

<robot> name="hello_world"
<link> name="base_link"
<visual>
<geometry>
<box size="0.5 0.2 0.1"/>
</geometry>
</visual>
</link>
</robot>
Standard XML file


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():
cpp06_urdf_dir = get_package_share_directory("cpp06_urdf")
default_model_path = os.path.join(cpp06_urdf_dir,"urdf/urdf","demo01_helloworld.urdf")
default_rviz_path = os.path.join(cpp06_urdf_dir,"rviz","display.rviz")
model = DeclareLaunchArgument(name="model", default_value=default_model_path)
# 加载机器人模型
# 1.启动 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}]
)
# 2.启动 joint_state_publisher 节点发布非固定关节状态
joint_state_publisher = Node(
package="joint_state_publisher",
executable="joint_state_publisher"
)
# rviz2 节点
rviz2 = Node(
package="rviz2",
executable="rviz2"
# arguments=["-d", default_rviz_path]
)
return LaunchDescription([
model,
robot_state_publisher,
joint_state_publisher,
rviz2
])

<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>

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

colcon build --packages-select cpp06_urdf

source install/setup.bash
ros2 launch cpp06_urdf display.launch.py

Tip:
In the subsequent cases in this chapter, all implementations follow the steps above. In the following cases, we only need to focus on the URDF implementation; the launch files and configuration files do not need to be modified.




URDF file
Press Ctrl+\ to generate comments.



Because the URDF plugin is already installed, there is a prompt indicating that a robot root tag needs to be created.

The first attribute is the robot's name.

The second one has an XML namespace pointing to xacro.


The third one has an XML namespace pointing to xacro, and also a robot name.
(For now, use the simplest one, which is the first.)

The link tag is called a link, and it also needs a name. A link generally refers to the rigid body part.
The link has a subset tag called visual.
Under the visual tag, write about the robot's shape.
Then under that tag, there is a subset tag called geometry.
Then there is a subset tag called box (rectangular shape), followed by size which corresponds to length, width, and height.

<robot name="boxrobot">
<link name="base_link">
<visual>
<geometry>
<box size="1.0 0.5 0.1"/>
</geometry>
</visual>
</link>
</robot>
xacro tool (a tool for loading disk files into ROS2)
Search whether xacro has been installed.
ros2 pkg list | grep -i xacro

If xacro is printed, it means it is installed. If nothing is printed, it needs to be installed manually.
sudo apt-get update
sudo apt-get install ros-humble-xacro
Use xacro to read a file

The content from the file was output to the terminal; we typically integrate it into a launch file. In the terminal, we can only view the content, but using a launch file allows us to bring the file into a node, meaning it gets integrated into ROS2.
Core implementation of launch
The core implementation involves just three steps: loading the robot model, publishing the state of non-fixed joints from a node, and launching the rviz2 node.

Creating an rviz2 node is very simple — just declare the package name and declare the executable.
Loading a robot model is relatively complex. To load a robot model, you also need to create a node.
Then there are parameters, and within the parameters there is a key called robot_description, which corresponds to a value.
The value is a ParameterValue object. This object executes a command called xacro, followed by a Launch configuration, which is essentially the path to the URDF file.




This value is essentially the content from the URDF file, but since the content is too long, we encapsulate it into an object.


The command line cannot be directly used as an object parameter value, so it still needs to be encapsulated.

Comand is specifically designed to encapsulate terminal command execution.


Remember to have a space after xacro, and fill in the path here.


We have now located the cpp06_urdf path under the share directory of cpp06_urdf, and the returned value is the string of that path.



from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
p_value = ParameterValue(Command(["xacro ",get_package_share_directory("cpp06_urdf") + "/urdf/urdf/demo01_boxrobot.urdf"]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
rviz2 = Node(
package="rviz2",
executable="rviz2"
)
def generate_launch_description():
return LaunchDescription([robot_state_pub,rviz2])




Click Add in the lower left corner to add the RobotModel plugin.







Create a new coordinate system plugin, with length along X, width along Y, and height along Z.
Launch Optimization Explanation and Implementation
We still need to optimize three points: first, opening the joint node; second, setting the default configuration file for rviz2; and third, the URDF file path is hardcoded in the Launch file, so the structure needs to be improved.

You also need to start this node to control joint motion. You can change it to joint_state_publisher_gui, which will bring up a graphical interface.





Save the rviz2 configuration.

Normal command.
ros2 run rviz2 rviz2 -d rviz2配置的路径



Create a parameter called model with the value being that long string.

LaunchConfiguration parses parameters.

Remember to put the model at the very front; placing it at the end is not allowed. The path has now been fully encapsulated.
Now it starts normally using the default URDF path.

Parse a non-default URDF. In the terminal, there is also something similar to get_package_share_directory. The following is an example (here the parameter model is missing an 'L'): you need to enclose the parameter value in backticks (the key between ESC and TAB).

ros2 pkg prefix --share cpp06_urdf
ros2 run launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/hahah.urdf



from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
model = DeclareLaunchArgument(name="model",default_value=get_package_share_directory("cpp06_urdf") + "/urdf/urdf/demo01_boxrobot.urdf")
p_value = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
joint_state_pub = Node(
package="joint_state_publisher",
executable="joint_state_publisher"
)
rviz2 = Node(
package="rviz2",
executable="rviz2",
arguments=["-d",get_package_share_directory("cpp06_urdf") + "/rviz/urdf.rviz"]
)
def generate_launch_description():
return LaunchDescription([model,robot_state_pub,joint_state_pub,rviz2])
Continue optimizing the final code to:
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
import os
cpp06_urdf_dir = get_package_share_directory("cpp06_urdf")
default_model_path = os.path.join(cpp06_urdf_dir,"urdf/urdf","demo01_boxrobot.urdf")
default_rviz_path = os.path.join(cpp06_urdf_dir,"rviz","urdf.rviz")
model = DeclareLaunchArgument(name="model",default_value=default_model_path)
p_value = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
# 关节信息节点
# joint_state_pub = Node(
# package="joint_state_publisher",
# executable="joint_state_publisher"
# )
# 关节信息节点图形界面(建议)
joint_state_pub = Node(
package="joint_state_publisher_gui",
executable="joint_state_publisher_gui"
)
rviz2 = Node(
package="rviz2",
executable="rviz2",
# arguments=["-d",get_package_share_directory("cpp06_urdf") + "/rviz/urdf.rviz"]
arguments=["-d",default_rviz_path]
)
def generate_launch_description():
return LaunchDescription([model,robot_state_pub,joint_state_pub,rviz2])
URDF syntax
Introduction

robot root tag


Segment the robot into several subsets. For example, one subset describes the head, another describes the body, and finally combine them into a complete set to form the robot.
Although my sub-files and main file are logically related through inclusion, they are actually separate URDF files. In the main file, the name attribute of the <robot> tag must be specified. The sub-files do not need to include it, but if they do, the value of name in the sub-file must match that of the main file!


link tag
Introduction

Each link is a rigid body and an independent component.
Link is assembled through joints.
The Link mainly consists of three parts: Visual, Collision, and Inertial.

Items without angle brackets are attributes; those with angle brackets are tags.
- name (optional): Specifies the name of the link. This name maps to a coordinate frame of the same name, and the link can also be located by referencing this value.
(required): Used to set the shape of the link, such as a cube, sphere, or cylinder. : A cube tag, where the side length of the cube is set via the sizeattribute, and the origin is its geometric center.: Cylinder tag. Set the cylinder radius using the radiusattribute, and set the cylinder height using thelengthattribute. The origin is at its geometric center.: A sphere tag that sets the sphere's radius via the radiusattribute, with its origin at the geometric center.: Uses the filenameattribute to reference a "skin" file, setting the appearance for the link. The file must be a local file. Prefix the filename withpackage://<packagename>/<path>.- (Optional): Used to set the relative offset and rotation angle of the link. If not specified, default values are used (no offset and no rotation).
- xyz: Indicates the offset in the x, y, and z dimensions (in meters). Separate different values with spaces. If not specified, the default value is used (no offset in any of the three dimensions).
- rpy: Represents the roll, pitch, and yaw angles (in radians). Separate different values with spaces. If not specified, the default values are used (no rotation in any of the three dimensions).
(optional): The material of the visual element. A <material>tag can also be defined in the root<robot>tag, and then referenced by name in a<link>.- name (optional): Specifies a name for the material, which can be used for referencing it.
(optional): The color of an rgba material, composed of four numbers representing red/green/blue/alpha, each ranging from 0, 1. (optional): The texture of the material, which can be set via the filenameattribute.
When there are multiple Visuals, you need to set a name for each Visual, so it is optional.
Collision is related to simulation. We can set a collision range for our robot's rigid bodies. As long as an obstacle enters that range, a collision occurs. Typically, the collision range is larger than the actual size.
- name (optional): Set a name for the collision.
(required): Please refer to the geometry usage rules in the visual tag. (optional): Please refer to the usage rules for the origin attribute in the visual tag.
Inertial is used to set the inertia matrix, which is also related to simulation. For example, when a robot brakes, it may tilt forward. If the center of mass in the inertia matrix is higher, then sudden braking could cause the robot to flip over.
- (Optional): This pose (translation, rotation) describes the position and orientation of the link's center of mass frame C relative to the link frame L.
- xyz : indicates that the position vector from Lo (link frame origin) to Co (link's center of mass) is x L̂x + y L̂y + z L̂z, where L̂x, L̂y, L̂z are the orthonormal unit vectors of the link frame L.
- rpy: Represents the orientation of the C-frame unit vectors Ĉx, Ĉy, Ĉz relative to the link frame L as a sequence of Euler rotations (r p y) in radians. Note: Ĉx, Ĉy, Ĉz do not need to be aligned with the link's principal axes of inertia.
- ** (Required)**: Set the quality of the link via its
valueattribute. (required): The moments of inertia ixx, iyy, izz and the products of inertia ixy, ixz, iyz about Co (the link's center of mass) for the unit vectors Ĉx, Ĉy, Ĉz fixed in the centroidal coordinate system C.
Note: <collision> and <inertial> are only needed in simulation environments. If you are simply integrating URDF in rviz2, it is not necessary to define these two tags for the link.
Use

<robot name="link_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
</robot>

ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo02_link.urdf


Rectangular solid, sphere, cylinder






Translation amount: X translation 1, Y and Z translation 0, rpy rotation set to 0 (the rotation angles are respectively Roll (around X), Pitch (around Y), and Yaw (around Z) in Euler angles).


Found that the cuboid is offset by 1 on the X axis.


Set the yaw angle (motion around Z) to 0.5 rad.


Set the Roll angle (motion around X) to 0.5 rad.


Set the pitch angle (rotation around Y) to 0.5 rad.

sRGB is R, G, B, Alpha (a floating-point model, so the range is 0–1.0).




If a color needs to be used multiple times, it can be encapsulated into something like a global variable, and then when referencing it in other links, simply use

<robot name="link_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
</robot>
Using supplements

The mesh tag references a skin file, typically an STL file, which can be exported from SolidWorks. Refer to the SW2URDF documentation later in this guide.
If you don’t know how to use SolidWorks, you can learn it — it only takes about two and a half days to pick up. It’s just a tool; simply being able to draw doesn’t mean much. The most important and difficult part is mechanical design. If you want to learn, you can watch the videos from the Mechanical Society’s WeChat public account of the brother club.
Of course, if you don't have time to learn, you can directly use a robot that someone else has already designed. Search for "turtlebot3" on GitHub.



Here are the exported models: the bases folder contains the appearance models, sensors contains the sensor models, and wheels contains the wheel models.
We clone the repository, making sure to clone the branch that is the ros2 branch.

We'll just reference one to see how it works; for real applications, you'll need to export using SolidWorks through the SW2URDF plugin.
Copy the burger_base.stl file from the meshes/bases directory in the project into the meshes directory in our workspace.


The filename is the skin file just mentioned. package:// is the protocol name, followed by the package name, which is cpp06_urdf. Then comes the file path under the functional package, which is meshes/burger_base.stl (which is actually the path under share).

Since it is a 3D model, when filling in the scale values, you need to enter three ratios. Just fill in 1.0 for all of them.

Why is the displayed model so large? Because rviz2 uses meters as its unit, while STL uses millimeters — keep that in mind. In mechanical design, if no unit is specified, it's assumed to be millimeters by default. Don't arbitrarily change the units; generally, millimeters should be used. We're building robots here, so let's be professional.


joint tag
Introduction
The joint tag in URDF is used to describe the kinematic and dynamic properties of a robot joint, and can also specify safety limits for joint motion. Two parts of the robot (referred to as the parent link and child link) are connected in the form of a "joint." Different joints have different types of motion: rotation, sliding, fixed, rotational speed, rotational angle limits, etc. For example, a wheel mounted on a base can rotate 360 degrees, while a camera might be completely fixed to the base.

- name (required): Name the joint; the name must be unique.
- type (required): Sets the joint type. Available types are as follows:
- continuous: A rotary joint that can rotate infinitely around a single axis.
- revolute: a rotational joint, similar to continuous, but with a limited range of rotation.
- prismatic: a sliding joint that moves along a single axis, with position limits.
- planer: A planar joint that allows translation or rotation in orthogonal directions within a plane.
- floating: floating joint, allowing translational and rotational movement.
- fixed: A special joint that is fixed and does not allow movement.
Here are the child-level tags.
(required): Specifies the parent link. - link (required): The name of the parent link, which is the name of this link in the robot's structure tree.
(required): Specifies the child link. - link (required): The name of the child link, which is the name of this link in the robot's structure tree.
- (optional): This is the transformation from the parent link to the child link, with the joint located at the origin of the child link.
- xyz : The offsets on each axis.
- rpy: The offset radians on each axis.
- <axis> (optional): If not set, the default value is (1, 0, 0).
- xyz: Used to set which joint axis the motion revolves around.
(optional): The reference position of the joint, used to calibrate the absolute position of the joint. - rising (optional): When the joint moves in the positive direction, this reference position triggers a rising edge.
- falling (optional): When the joint moves in the positive direction, this reference position triggers a falling edge.
(optional): An element that specifies the physical characteristics of a joint. These values are used to define the joint's modeling properties and are useful for simulation. - damping (optional): The physical damping value of the joint, defaults to 0.
- friction (optional): The physical static friction value of the joint, defaults to 0.
(required when the joint type is revoluteorprismatic):- lower (optional): Specifies the lower limit of the joint (in radians for revolute joints, in meters for prismatic joints). Omitted if the joint is continuous.
- upper (optional): Attribute specifying the upper limit of the joint (in radians for revolute joints, in meters for prismatic joints). Omitted if the joint is continuous.
- effort (required): Specifies the maximum force a joint can withstand.
- velocity (required): Used to set the maximum joint velocity (for revolute joints in radians per second rad/s, for prismatic joints in meters per second m/s).
(optional): This tag is used to specify that the defined joint mimics another existing joint. The value of this joint can be calculated as value = multiplier * other_joint_value + offset. - joint (required): Specifies the name of the joint to simulate.
- multiplier (optional): Specifies the multiplication factor in the formula above.
- offset (optional): Specifies the offset to be added to the above formula, defaulting to 0 (in radians for revolute joints and meters for prismatic joints).
- <safety_controller> (optional): Safety controller.
- soft_lower_limit (optional): Specifies the lower joint boundary at which the safety controller begins to limit the joint position. This limit must be greater than the joint's lower limit.
- soft_upper_limit (optional): Specifies the attribute of the joint's upper boundary where the safety controller begins to limit the joint position. This limit must be less than the joint's upper limit.
- k_position (optional): Specifies the relationship between position and velocity limits.
- k_velocity (required): Specifies the relationship between force and velocity limits.

The joint name is required and must be unique.

The most commonly used joints are the revolute type with limited range, the continuous type that can rotate indefinitely, and the fixed type. Fill this in based on the specific joint type.
A revolute joint is typically used for the joints of an industrial robotic arm, a continuous joint is used for structures like a steering wheel joint, and a fixed joint is used for structural joints that cannot move.

There are many subset tags, but we only use a few of the most common ones. Just remember the commonly used ones.

The parent tag, with the link attribute specifying the name of the parent link.

The child tag is similar.


This axis defaults to 1, 0, 0, rotating around the X-axis, but it usually needs to be configured. You can set a reference axis through SolidWorks.
The remaining tags are all related to joint types, such as limit. If the joint type is revolute and no limit is set, then the joint angle cannot be adjusted in joint_state_publisher_gui.

Other tags will be introduced when they are used.
Practice

First implement the two joints individually, then connect them through the joint.







If it's yellow, there should be more red and green.

This is red.

The chassis Link has been created.

The camera link has also been created.

Set the joint name to camera2base_link, which is the joint connecting the camera to the base. Then set the type to continuous, allowing 360-degree rotation.

Fill in the child link and parent link.

This way, the two Links are connected together through this Joint.

We still need to configure these two options, but let's not set them for now. First, let's see what the default state is.
colcon build --packages-select cpp06_urdf
source install/setup.bash
ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo03_joint.urdf


You can open TF to check the coordinate frames, check Show Names, and you'll find they overlap. So the display doesn't match our logical business logic (in the default state). Therefore, you need to set an offset.

If we want to move the camera to the front of the car, as shown in the diagram, there is an offset in X, none in Y, but there is in Z. There is no offset in roll, pitch, or yaw.



The height of Z is 1/2 of the chassis height plus 1/2 of the camera height.



We're going to rotate around Yaw, which means rotating around the Z-axis, i.e., (0, 0, 1). Note that it must be an integer, not a floating-point number.

To check if rotation is possible, open joint_state_publisher_gui. You can open it from the launch file or directly from the terminal.


ros2 run joint_state_publisher_gui joint_state_publisher_gui


Drag the scrollbar to change the camera's Yaw.

Randomize means random number, Center means return to center (reset).
<robot name="joint_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<link name="camera">
<visual>
<geometry>
<box size="0.02 0.05 0.05" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="red" />
</visual>
</link>
<joint name="camera2baselink" type="continuous">
<parent link="base_link"/>
<child link="camera" />
<origin xyz="0.2 0 0.075" rpy="0 0 0" />
<axis xyz="0 0 1" />
</joint>
</robot>
joint_state_publisher
bug: Yaw is unstable and keeps returning to center.



Actually, it's because the one launched in the launch file conflicts with the one started in the terminal.
Solution: Start only one of them.

Solution: Directly launch the GUI version in Launch to resolve this. (But not recommended.)
Suggested approach: Use the non-GUI version, because in the future, we will control the joints programmatically, not via GUI.

If you just want to display the model, use the GUI.
If you want to control it with a program, use the standard version.
base_footprint
bug: robot chassis partially submerged underground




<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 0.05"/>
</joint>
The Z offset should be filled with the distance the chassis sinks, which is half of the entire vehicle chassis.


Modify the reference coordinate system
Actually, this optimization is optional and won't make much difference. However, it is recommended to use the version with basefootprint.
<robot name="base_footprint_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<link name="base_link">
<visual>
<geometry>
<box size="0.5 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 0.05"/>
</joint>
<link name="camera">
<visual>
<geometry>
<box size="0.02 0.05 0.05" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="red" />
</visual>
</link>
<joint name="camera2baselink" type="fixed">
<parent link="base_link"/>
<child link="camera" />
<origin xyz="0.2 0 0.075" rpy="0 0 0" />
<axis xyz="0 0 1" />
</joint>
</robot>
Practice

No need to practice too deeply; we can directly use SolidWorks for modeling, which is more user-friendly.

<robot name="exercise_demo">
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<material name="gray">
<color rgba="0.2 0.2 0.2 0.8" />
</material>
<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<link name="base_link">
<visual>
<geometry>
<box size="0.2 0.12 0.07" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 0.05"/>
</joint>
<link name="front_left_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="frontleftwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="front_left_wheel" />
<origin xyz="0.075 0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
<link name="front_right_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="frontrightwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="front_right_wheel" />
<origin xyz="0.075 -0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
<link name="back_left_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="backleftwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="back_left_wheel" />
<origin xyz="-0.075 0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
<link name="back_right_wheel">
<visual>
<geometry>
<cylinder radius="0.025" length="0.02"/>
</geometry>
<origin xyz="0 0 0" rpy="1.57 0 0" />
<material name="gray" />
</visual>
</link>
<joint name="backrightwheel2baselink" type="continuous">
<parent link="base_link"/>
<child link="back_right_wheel" />
<origin xyz="-0.075 -0.06 -0.025" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
</robot>
Run in terminal
ros2 launch cpp06_urdf display.launch.py model:=`ros2 pkg prefix --share cpp06_urdf`/urdf/urdf/demo05_exercise.urdf

URDF tools
In ROS2, several tools related to URDF files are provided, such as:
- The
check_urdfcommand can check complex URDF files for syntax issues. - The
urdf_to_graphvizcommand can be used to view the structure of a URDF model, displaying the hierarchical relationships between different links.
Of course, before using the tool, please install it first. Installation command: sudo apt install liburdfdom-tools.
check_urdf Syntax Check
Enter the directory containing the URDF file and call: check_urdf urdf文件. If no exception is thrown, the file is valid; otherwise, it is invalid.
Example: In the terminal, navigate to the urdf/urdf directory of the cpp06_urdf package and run the following command:
check_urdf demo05_exercise.urdf
If there are no anomalies in the URDF file, the hierarchical relationship of the links in the URDF will be displayed, as shown in the following figure:

Otherwise, an error message will be displayed.

Demonstration error.

urdf_to_graphviz Structure Viewer
Navigate to the directory containing the URDF file, and run: urdf_to_graphviz urdf文件. A PDF file will be generated in the current directory.
Example: In the terminal, navigate to the urdf/urdf directory of the cpp06_urdf package and run the following command:
urdf_to_graphviz demo05_exercise.urdf
In the current directory, a .pdf and a .gv file will be generated, named after the robot name in the URDF. Opening the PDF file will display content as shown in the figure below:

In the figure above, the relationship between links and joints is displayed in a tree structure.
Note: This tool was previously named urdf_to_graphiz and it is now recommended to use urdf_to_graphviz instead.

urdf_to_graphiz is a historical version and has been deprecated. It is recommended to use urdf_to_graphviz.




Black boxes represent Links, blue represents Joints, and information such as translation amounts and rotation degrees is also displayed.
SW2URDF
Introduction to SolidWorks
SolidWorks is a computer-aided design (CAD) and computer-aided manufacturing (CAM) software developed by Dassault Systèmes SolidWorks Corp. It is primarily used for engineering design and manufacturing, enabling users to create 3D models, perform assembly design, conduct engineering analysis, and generate drawings. SolidWorks features an intuitive user interface and powerful capabilities, allowing engineers and designers to quickly and accurately design complex parts and assemblies. This software is widely used in industries such as mechanical engineering, aerospace, automotive, and medical devices, making it a key tool in the field of engineering design.

Introduction to the SolidWorks Plugin SW2URDF
sw2urdf plugin Wikipedia:
https://wiki.ros.org/sw\_urdf\_exporter
GitHub download link:
https://github.com/ros/solidworks\_urdf\_exporter/releases
Although the GitHub link states it only supports up to SW2021, it has been found that the latest versions of SW (2022 and 2024 tested) also work fine.

Install sw2urdf
- Go to GitHub to download the sw2urdf plugin (ignore the SW version; it has been tested and still works with later SW versions).

- Check the installation path of SW.
For example, my path "C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\"

- Open the plugin installer. By default, it will automatically locate your SW installation path for installation. If it does not automatically find the path, you will need to manually select the SW installation path.

- Check if the plugin was installed successfully.


As shown in the picture, the installation was successful.
Export URDF and Meshes
- The image shows a standard SolidWorks assembly drawing (you can also try to create one manually; it doesn't need to include a transmission mechanism).
If this process is hard to follow, you can refer to Guyueju's video and study it alongside this tutorial. Exporting SolidWorks Models to URDF (Guyueju) - Bilibili https://www.bilibili.com/video/BV1Tx411o7rH

- Create a reference axis for the joint.

Select the cylindrical surface or other surface of the joint to calibrate the rotation axis.

If you have created a datum axis and find that it is not displaying properly, then turn on this option.


After building all the systems
- Open the "export as URDF" option.

- You need to first select the base_link base rigid body.

Here, you may need to select the Global Origin yourself — just choose Origin_Global. However, it's usually fine to select Automatically Generate.
- Because his base_link connects to a child_link through a joint, this option should be set to 1.


Let's call the joint connecting base_link joint1, and the rigid body above joint1 link1.
Then, refer to the reference axis and select the reference axis 1 we just created at this joint. Then, for the joint type, choose Revolute (a joint with limited range of motion; only this way can the robot joint move normally).

The reference coordinate system here can be set to automatically generate, or you can choose Origin_Joint1.
Then add one to link1 to create link2.
The steps for adding link3 and link4 are the same, so they will not be shown in detail.



- Click preview and export

- Set joint limit positions.
If the Axis coordinate is displayed incorrectly here, it means the Coordinates or Axis selection is wrong. Please manually select the correct coordinate system or reference axis.

If the axis generation is incorrect, for example all zeros, you can fill it in manually.

For example, here is the X-axis. You can set the left wheel to -100 and the right wheel to 100 (this is for reference only, a solution provided by ChatGPT).

- View the matrix, then click Export URDF and meshes.




This successfully generates the ROS1 workspace. Move the workspace to the Linux system.
Convert a ROS1 workspace to a ROS2 workspace
- First, move the ROS1 workspace to the Linux system.

- Create a new ROS2 workspace

mkdir -p ros2_4axisrobot_ws/src
cd ros2_4axisrobot_ws/
colcon build
cd src
ros2 pkg create cpp01_urdf --build-type ament_cmake
cd ..
code .
- Complete the table of contents for WS.


Create folders such as launch, meshes, rviz, urdf, etc., under the ./src/cpp01_urdf path. Then, inside the urdf folder, create two more folders named urdf and xacro.
- Copy the URDF and Meshes from the ROS1 workspace into the ROS2 workspace.



- Configure CMake and the package.
In CMake, please add
install(
DIRECTORY launch urdf rviz meshes
DESTINATION share/${PROJECT_NAME}
)
In Package.xml, please add
<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>
CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(cpp01_urdf)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
install(
DIRECTORY launch urdf rviz meshes
DESTINATION share/${PROJECT_NAME}
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp01_urdf</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="tungchiahui@gmail.com">tungchiahui</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
- Write the launch file.
For detailed steps, please refer to the section on core optimizations related to URDF Launch.

from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
# from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
import os
cpp01_urdf_dir = get_package_share_directory("cpp01_urdf")
default_model_path = os.path.join(cpp01_urdf_dir,"urdf/urdf","4axisrobot.urdf")
default_rviz_path = os.path.join(cpp01_urdf_dir,"rviz","urdf.rviz")
model = DeclareLaunchArgument(name="model",default_value=default_model_path)
p_value = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
robot_state_pub = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{"robot_description":p_value}]
)
# 关节信息节点(建议)
# joint_state_pub = Node(
# package="joint_state_publisher",
# executable="joint_state_publisher"
# )
# 关节信息节点图形界面
joint_state_pub = Node(
package="joint_state_publisher_gui",
executable="joint_state_publisher_gui"
)
rviz2 = Node(
package="rviz2",
executable="rviz2",
# arguments=["-d",get_package_share_directory("cpp06_urdf") + "/rviz/urdf.rviz"]
arguments=["-d",default_rviz_path]
)
def generate_launch_description():
return LaunchDescription([model,robot_state_pub,joint_state_pub,rviz2])
- Modify the URDF file.
The main changes are two: one is the path for meshes, and the other is removing comments that could easily cause errors.
Mainly because the workspace paths for ROS1 and ROS2 are different, we only need to modify the path-related content, such as the Meshes path.


The package:// typically refers to the share directory, so we need to start writing from the cpp01_urdf level (the share directory will only appear after compilation).

Then press Ctrl+H


Then you also need to delete the comments at the beginning of the URDF file, otherwise it will also cause errors.

Below is the replaced URDF file:
<?xml version="1.0" encoding="utf-8"?>
<robot
name="4axisrobot">
<link
name="base_link">
<inertial>
<origin
xyz="-0.0819771145953119 -0.0394328123681598 -0.0605612328464761"
rpy="0 0 0" />
<mass
value="7.43807257011133" />
<inertia
ixx="0.33173827700719"
ixy="-1.91350798633049E-05"
ixz="-2.38586363043925E-05"
iyy="0.510937907009661"
iyz="1.14706407748075E-05"
izz="0.66107272632796" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/base_link.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/base_link.STL" />
</geometry>
</collision>
</link>
<link
name="link1">
<inertial>
<origin
xyz="0.19141 -0.050676 -0.030057"
rpy="0 0 0" />
<mass
value="21.822" />
<inertia
ixx="0.50464"
ixy="0.13295"
ixz="0.087353"
iyy="0.59136"
iyz="-0.066053"
izz="0.64081" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link1.STL" />
</geometry>
<material
name="">
<color
rgba="0.79216 0.81961 0.93333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link1.STL" />
</geometry>
</collision>
</link>
<joint
name="joint1"
type="revolute">
<origin
xyz="-0.081947 -0.039447 0.11191"
rpy="0.40394 -1.5708 0" />
<parent
link="base_link" />
<child
link="link1" />
<axis
xyz="1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
<link
name="link2">
<inertial>
<origin
xyz="0.00711598521454873 0.430460941882332 -0.181074256126024"
rpy="0 0 0" />
<mass
value="30.6115349647897" />
<inertia
ixx="2.84056101478768"
ixy="0.00176700425270318"
ixz="-0.000605507690039326"
iyy="0.803420593303961"
iyz="0.881921487314161"
izz="2.25142021571439" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link2.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link2.STL" />
</geometry>
</collision>
</link>
<joint
name="joint2"
type="revolute">
<origin
xyz="0.35141 -0.28119 0.13918"
rpy="-2.0566 1.1982 1.5708" />
<parent
link="link1" />
<child
link="link2" />
<axis
xyz="-1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
<link
name="link3">
<inertial>
<origin
xyz="0.00112887115421612 0.0366346915274407 0.31720330934553"
rpy="0 0 0" />
<mass
value="32.192726667578" />
<inertia
ixx="4.01515983126431"
ixy="0.0013193752174692"
ixz="0.0113529938102155"
iyy="3.86196311673823"
iyz="-0.336440022491883"
izz="0.399977905540088" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link3.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link3.STL" />
</geometry>
</collision>
</link>
<joint
name="joint3"
type="revolute">
<origin
xyz="0.3765 0.82481 -0.49591"
rpy="2.5802 0 0" />
<parent
link="link2" />
<child
link="link3" />
<axis
xyz="1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
<link
name="link4">
<inertial>
<origin
xyz="0.162516620557178 -0.213844847423599 3.38133504529381E-07"
rpy="0 0 0" />
<mass
value="12.1646007734167" />
<inertia
ixx="0.288208127188405"
ixy="0.00080326659836716"
ixz="8.72489392803044E-07"
iyy="0.107000706575729"
iyz="-1.48935044230573E-07"
izz="0.295147863994059" />
</inertial>
<visual>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link4.STL" />
</geometry>
<material
name="">
<color
rgba="0.792156862745098 0.819607843137255 0.933333333333333 1" />
</material>
</visual>
<collision>
<origin
xyz="0 0 0"
rpy="0 0 0" />
<geometry>
<mesh
filename="package://cpp01_urdf/meshes/link4.STL" />
</geometry>
</collision>
</link>
<joint
name="joint4"
type="revolute">
<origin
xyz="0 0.070837 0.99728"
rpy="0.82315 -1.5708 0" />
<parent
link="link3" />
<child
link="link4" />
<axis
xyz="1 0 0" />
<limit
lower="-3.14"
upper="3.14"
effort="100"
velocity="1" />
</joint>
</robot>
- Compile
colcon build --packages-select cpp01_urdf


- Update the terminal environment
source install/setup.bash

- Run Launch
ros2 launch cpp01_urdf display.launch.py


- Adding plugins and basic configuration in Rviz2

- You can adjust joint angles using joint_state_publisher_gui.


- Save the rviz2 configuration to the rviz folder.
First, click on File in the rviz2 menu bar, then select Save Config As.

You can watch the demo video in the introduction of this section.
Precautions (for reference only, ChatGPT's solution)
Coordinate system 1
The wheels must use continuous joints, and try to choose the coordinate system yourself. The requirement is that when looking at the car from behind, left is Y, forward is X, and up is Z. All coordinate systems are required.
You can first have it automatically generate one, then modify the coordinate system — that would be simpler.



The coordinate system makes the car lie flat.
If your car is lying on its side in rviz2, then:
Ha, exactly — this kind of "vehicle lying on its side" situation is 90% due to incorrect STL coordinate axis orientation. Many CAD tools export STL models with the default:
- Z-axis forward (e.g., SolidWorks: Z forward, Y up, use the right-hand coordinate system to determine the X-axis position; viewed from behind the vehicle, it points left.)
- or Y-axis up (e.g., Blender)
The ROS/URDF coordinate system is:
- X forward (direction of travel)
- Y to the left (translation direction)
- Z upward (direction of gravity)
The car goes straight and then turns; it turns and then goes straight.
That means there's a problem with your rotating shaft.
If moving forward results in a left turn, it means the left wheel's axle is wrong—missing a symbol. Just add it in.
For example, this 010 should be changed to 0-10.

xacro
Scenarios, Functions, and Concepts
Scene
In the previous process of building the robot model using the URDF file, there were several issues.
Problem 1: When designing joint positions, calculations must follow fixed rules. However, in URDF, this relies on manual computation, which is inconvenient and prone to errors. Additionally, when certain parameters change, recalculation is required.
Problem 2: Some content in URDF is highly repetitive, such as the design and implementation of wheels. Different wheels differ only in certain parameters, while their shape, color, and rotation values are identical. In practical applications, building complex robot models often leads to highly repetitive designs. Following general programming principles, repetitive code should be encapsulated and reused. However, the previous URDF file did not include such operations.
......
If you encounter a similar problem in a general programming language, we can solve it by combining variables with functions. Correspondingly, ROS also provides a programming-like optimization solution called: Xacro (which can be understood as urdf 2.0).
Concept
Xacro is short for XML Macros. Xacro is an XML macro language and a programmable form of XML.
Xacro can declare variables and solve them through mathematical operations; it can use flow control to manage execution order; and it can encapsulate functionality through macros (which can be thought of as functions) and reuse them, thereby improving code reuse rates and program security.
Purpose
Compared to a pure URDF implementation, it allows for writing safer, more concise, and more readable robot model files, while also improving writing efficiency.
Quick Start
First, install xacro.
#humble版本
sudo apt install ros-humble-xacro
#jazzy版本
sudo apt install ros-jazzy-xacro
- Requirements
Optimize the car chassis implementation from 6.4.4 URDF Exercise using xacro. Use variables to encapsulate vehicle parameters, and use xacro macros to encapsulate the repetitive wheel code, then call the macro to create four wheels (Note: This demonstrates basic xacro usage; generating valid URDF is not required).
- Implementation
In the urdf/xacro directory of the package cpp06_urdf, create a new xacro file named demo01_helloworld.urdf.xacro, and edit the file by entering the following content:
<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="wheel_radius" value="0.025" />
<xacro:property name="wheel_length" value="0.02" />
<xacro:property name="PI" value="3.1415927" />
<xacro:macro name="wheel_func" params="wheel_name" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="wheel_color">
<color rgba="0 0 0 0.3" />
</material>
</visual>
</link>
</xacro:macro>
<xacro:wheel_func wheel_name="left_front"/>
<xacro:wheel_func wheel_name="left_back"/>
<xacro:wheel_func wheel_name="right_front"/>
<xacro:wheel_func wheel_name="right_back"/>
</robot>

Macros are similar to functions.
params are similar to entry parameters
Sub-tags are similar to function bodies.
In the terminal, navigate to the directory mentioned in the current file and enter the following command:
cd src/cpp06_urdf/urdf/xacro/
xacro demo01_helloworld.urdf.xacro
#或者
ros2 run xacro xacro demo01_helloworld.urdf.xacro
The terminal will output the following content (the content below is pure URDF):
<?xml version="1.0" ?>
<robot name="mycar">
<link name="left_front_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
<link name="left_back_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
<link name="right_front_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
<link name="right_back_wheel">
<visual>
<geometry>
<cylinder length="0.02" radius="0.025"/>
</geometry>
<origin rpy="1.57079635 0 0" xyz="0 0 0"/>
<material name="wheel_color">
<color rgba="0 0 0 0.3"/>
</material>
</visual>
</link>
</robot>
Obviously, through xacro we have conveniently achieved code reuse.

grammar
1. Introduction

xacro provides a programmable interface, similar to a computer language, including syntax for variable declaration and invocation, function declaration and invocation, and more. When using xacro to generate URDF, the root tag robot must include a namespace declaration: xmlns:xacro="``http://wiki.ros.org/xacro``".
Variables
Variables are used to encapsulate certain fields in URDF, such as: the value of PI, the dimensions of the car, the wheel radius, etc. The basic syntax for using variables includes variable definition, variable invocation, variable operations, and more.
1.1 Variable Definition
Syntax format:
<xacro:property name="变量名" value="变量值" />
```
Example:
```xml
<xacro:property name="PI" value="3.1416"/>
<xacro:property name="wheel_radius" value="0.025"/>
<xacro:property name="wheel_length" value="0.02"/>
```
1.2 Variable Invocation
Syntax format:
```xml
${变量名}
```
Example:
```xml
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
```
1.3 Variable Operations
Syntax format:
```xml
${数学表达式}
```
Example:
```xml
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
```
```xml
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="demo2_pro">
<xacro:property name="num1" value="10"/>
<xacro:property name="num2" value="20"/>
<car length="${num1}" width="${num2}"/>
<sum value="${num1 + num2}"/>
</robot>
```

**Macros**
Similar to function implementation, it improves code reusability, optimizes code structure, and enhances security. The basic usage syntax of macros includes their definition and invocation.
2.1 Macro Definitions
Syntax format:
```xml
<xacro:macro name="宏名称" params="参数列表(多参数之间使用空格分隔)">
.....
参数调用格式: ${参数名}
</xacro:macro>
```
Example:
```xml
<xacro:macro name="wheel_func" params="wheel_name" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="wheel_color">
<color rgba="0 0 0 0.3" />
</material>
</visual>
</link>
</xacro:macro>
```
2.2 Macro Invocation
Syntax format:
```xml
<xacro:宏名称 参数1=xxx 参数2=xxx/>
```
Example:
```xml
<xacro:wheel_func wheel_name="left_front"/>
<xacro:wheel_func wheel_name="left_back"/>
<xacro:wheel_func wheel_name="right_front"/>
<xacro:wheel_func wheel_name="right_back"/>
```
```xml
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="demo3_func">
<xacro:macro name="get_sum" params="num1 num2">
<sum value="${num1 + num2}"/>
</xacro:macro>
<xacro:get_sum num1="20" num2="30"/>
<xacro:get_sum num1="70" num2="30"/>
</robot>
```

**Files**
A robot is composed of multiple components, and different components may be encapsulated as separate xacro files. These files are then integrated and combined to form the complete robot, which can be achieved through file inclusion.
Syntax format:
```xml
<xacro:include filename="其他xacro文件" />
Example:
<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="car_base.xacro" />
<xacro:include filename="car_camera.xacro" />
<xacro:include filename="car_laser.xacro" />
</robot>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="demo4_include">
<xacro:include filename="demo02_base_pro.urdf.xacro"/>
<xacro:include filename="demo03_base_func.urdf.xacro"/>
</robot>

But this is not recommended; it is advised that the parent xacro and child xacro use the same name.
Practice
framework
- Requirements
Create a four-wheeled robot model using xacro. The chassis of this model can refer to the implementation in 6.4.4 URDF Exercise, with a camera and a LiDAR added on top of the chassis. The size parameters and installation positions of the camera and LiDAR can be customized.

- Implementation Analysis
The robot model in the requirements consists of three parts: the chassis, the camera, and the radar. Each part can be encapsulated into a separate xacro file, and finally, these xacro files can be included and organized to form a complete robot model.
- Implementation
In the urdf/xacro directory of the cpp06_urdf package, create several new xacro files:
- car.urdf.xacro: used to include the xacro files corresponding to different robot components.
- car_base.urdf.xacro: The xacro file describing the robot chassis.
- car_camera.urdf.xacro: Xacro file describing the camera.
- car_laser.urdf.xacro: The xacro file describing the lidar.
Edit the car.urdf.xacro file and enter the following content:
<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:include filename="car_base.urdf.xacro"/>
<xacro:include filename="car_camera.urdf.xacro"/>
<xacro:include filename="car_laser.urdf.xacro"/>
</robot>
The vehicle body
Edit the car_base.urdf.xacro file and enter the following content:
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="PI" value="3.1416"/>
<xacro:property name="base_link_x" value="0.2"/>
<xacro:property name="base_link_y" value="0.12"/>
<xacro:property name="base_link_z" value="0.07"/>
<xacro:property name="distance" value="0.015"/>
<xacro:property name="wheel_radius" value="0.025"/>
<xacro:property name="wheel_length" value="0.02"/>
<material name="yellow">
<color rgba="0.7 0.7 0 0.8" />
</material>
<material name="red">
<color rgba="0.8 0.1 0.1 0.8" />
</material>
<material name="gray">
<color rgba="0.2 0.2 0.2 0.95" />
</material>
<link name="base_footprint">
<visual>
<geometry>
<sphere radius="0.001"/>
</geometry>
</visual>
</link>
<link name="base_link">
<visual>
<geometry>
<box size="${base_link_x} ${base_link_y} ${base_link_z}" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="yellow"/>
</visual>
</link>
<joint name="baselink2basefootprint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 ${distance + base_link_z / 2}"/>
</joint>
<xacro:macro name="wheel_func" params="wheel_name is_front is_left" >
<link name="${wheel_name}_wheel">
<visual>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_length}" />
</geometry>
<origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
<material name="gray"/>
</visual>
</link>
<joint name="${wheel_name}2baselink" type="continuous">
<parent link="base_link" />
<child link="${wheel_name}_wheel" />
<origin xyz="${(base_link_x / 2 - wheel_radius) * is_front} ${base_link_y / 2 * is_left} ${(base_link_z / 2 + distance - wheel_radius) * -1}" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
</xacro:macro>
<xacro:wheel_func wheel_name="left_front" is_front="1" is_left="1" />
<xacro:wheel_func wheel_name="left_back" is_front="-1" is_left="1" />
<xacro:wheel_func wheel_name="right_front" is_front="1" is_left="-1" />
<xacro:wheel_func wheel_name="right_back" is_front="-1" is_left="-1" />
</robot>
Add a camera
Edit the car_camera.urdf.xacro file and enter the following content:
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<xacro:property name="camera_x" value="0.012" />
<xacro:property name="camera_y" value="0.05" />
<xacro:property name="camera_z" value="0.01" />
<xacro:property name="camera_joint_x" value="${base_link_x / 2 - camera_x / 2}" />
<xacro:property name="camera_joint_y" value="0.0" />
<xacro:property name="camera_joint_z" value="${base_link_z / 2 + camera_z / 2}" />
<link name="camera">
<visual>
<geometry>
<box size="${camera_x} ${camera_y} ${camera_z}" />
</geometry>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
<material name="red" />
</visual>
</link>
<joint name="camera2baselink" type="fixed">
<parent link="base_link" />
<child link="camera" />
<origin xyz="${camera_joint_x} ${camera_joint_y} ${camera_joint_z}" />
</joint>
</robot>
Add radar
Edit the car_laser.urdf.xacro file and enter the following content:
<robot xmlns:xacro="http://wiki.ros.org/xacro">
<material name="blue">
<color rgba="0.0 0.0 0.4 0.95" />
</material>
<xacro:property name="laser_length" value="0.03" />
<xacro:property name="laser_radius" value="0.03" />
<xacro:property name="laser_joint_x" value="0.0" />
<xacro:property name="laser_joint_y" value="0.0" />
<xacro:property name="laser_joint_z" value="${base_link_z / 2 + laser_length / 2}" />
<link name="laser">
<visual>
<geometry>
<cylinder radius="${laser_radius}" length="${laser_length}" />
</geometry>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
<material name="blue" />
</visual>
</link>
<joint name="laser2baselink" type="fixed">
<parent link="base_link" />
<child link="laser" />
<origin xyz="${laser_joint_x} ${laser_joint_y} ${laser_joint_z}" />
</joint>
</robot>
Execute
After compilation, execute the following command in the workspace terminal:
# ROS Humble
ros2 launch cpp06_urdf display.launch.py model:=ros2 pkg prefix --share cpp06_urdf/urdf/xacro/car.urdf.xacro
#ROS Jazzy
colcon build
source install/setup.bash
ros2 run xacro xacro $(ros2 pkg prefix cpp06_urdf)/share/cpp06_urdf/urdf/xacro/car.urdf.xacro -o ./src/cpp06_urdf/urdf/urdf/car.urdf
ros2 launch cpp06_urdf display.launch.py model:=./src/cpp06_urdf/urdf/urdf/car.urdf
After executing the command, rviz2 displays a robot model similar to the requirements.

Summary

Currently, it's just an empty shell. The LiDAR, camera, and wheels are all placeholders; their functionality will only be realized in advanced practice.