ROS 2 Learning Path  Β·  Practical Appendix

Appendix A: Connecting SolidWorks to URDF

From the ARCHO mechanical model in SolidWorks to a live display in RViz
Audience: anyone who has designed a robot body in SolidWorks
Prerequisites: Chapter 4 (URDF and Xacro) and Chapter 6 (RViz)
Running project: the ARCHO robot
Reading time: 60 to 90 minutes

Before anything else, build a mental picture. What you learn in this appendix follows exactly this path: a real mobile robot, designed on paper and in SolidWorks, gets translated step by step into a language ROS 2 can understand.

An industrial autonomous mobile robot (AMR) β€” the same kind of body we convert from SolidWorks to URDF in this appendix
An industrial autonomous mobile robot (AMR) β€” exactly the kind of body we go on to convert from SolidWorks to URDF: a chassis, two drive wheels, and several casters for balance
What's in this appendix A.1Overview of the pipeline β€” from assembly to RViz A.2What exactly is URDF? A.3The right way to think: from mechanism to Exporter tree A.4Preparing the SolidWorks assembly A.5The ROS coordinate system and Coordinate System A.6Material, mass, and inertia A.7Installing the SolidWorks URDF Exporter A.8Defining Links, Joints, and axes A.9Collision versus Visual A.10Exporting and transferring files to WSL A.11Building the ROS 2 package A.12Testing in RViz and checking TF A.13Common errors and how to fix them A.14Full sample URDF A.15Summary, checklist, and exercise

A.1Overview of the pipeline: from assembly to RViz

A common mistake is to assume SolidWorks connects directly and live to ROS 2. It doesn't work that way. The real path is a multi-stage pipeline: you prepare a mechanical assembly in SolidWorks, an add-in (the Exporter) converts it into URDF and Mesh files, and then you place those files inside a normal ROS 2 package β€” exactly what you learned in Chapter 4 about the structure of ARCHO's body URDF.

flowchart LR A["SolidWorks Assembly"] --> B["SolidWorks URDF Exporter"] B --> C["URDF + STL Meshes"] C --> D["ROS 2 Package"] D --> E["RViz"] E --> F["Gazebo"] F --> G["ros2_control"] style A fill:#2f7de8,stroke:#ffffff,color:#0b1220,font-weight:bold style C fill:#d98c19,stroke:#ffffff,color:#0b1220,font-weight:bold style E fill:#0f9d78,stroke:#ffffff,color:#0b1220,font-weight:bold style G fill:#8b5cf6,stroke:#ffffff,color:#ffffff,font-weight:bold

The goal of this appendix is simply to get ARCHO's mechanical model into a URDF that ROS 2 can understand and to see it displayed correctly in RViz. Once the model looks right in RViz, we move on to Gazebo and ros2_control (Chapters 7 and 8) β€” not before.

πŸ’‘ Simple Idea

SolidWorks is a mechanical translator, not a live driver. Its job is to translate the geometry, mass, and relative motion of the parts into URDF, once. After that, ROS 2 has nothing more to do with SolidWorks.

A.2What exactly is URDF?

URDF stands for Unified Robot Description Format: a text-based XML file that tells ROS what parts the robot is made of, which part is connected to which, where the joints are, what the rotation or motion axis of each joint is, what the mass and center of mass of each part is, and what the visual appearance and physical collision shape of the parts look like.

πŸ“– Two Core URDF Concepts

Link = a rigid body (an independent physical part)

Joint = the connection between two Links that defines their relative motion

For example, the Link tree of ARCHO β€” the same two-wheeled robot introduced in Chapter 1 β€” looks like this:

flowchart TD A["base_link"] --> B["left_wheel_link"] A --> C["right_wheel_link"] A --> D["caster_link"] A --> E["lidar_link"] A --> F["camera_link"] style A fill:#2f7de8,stroke:#ffffff,color:#0b1220,font-weight:bold

And every connection is defined by an independent Joint:

base_link
   β”œβ”€β”€ left_wheel_joint  β†’ left_wheel_link
   β”œβ”€β”€ right_wheel_joint β†’ right_wheel_link
   β”œβ”€β”€ lidar_joint       β†’ lidar_link
   └── camera_joint      β†’ camera_link

A.3The right way to think: from mechanism to Exporter tree

Before opening the Exporter, take a step back. SolidWorks tells you "this part is the chassis, this is the wheel, this is the bolt, this is the motor, this is the gearbox, this is the spring..." β€” that is, it sees the assembly in terms of part count. But ROS never asks "how many parts do you have?" ROS only has one question:

πŸ’‘ The Most Important Sentence in This Appendix

Which things move together like a single rigid body, and what motions exist between these bodies?
Link = rigid body β€” Joint = the motion relationship between two rigid bodies.

Step one: solve the mechanism on paper before touching the Exporter

Never go straight into the URDF Exporter. First ask yourself: what stays fixed? What rotates? What moves up and down? What moves as one unit with something else?

For example, consider a simple robot with a chassis, motor, gearbox, shaft, wheel, and camera. Suppose the motor and gearbox housing are bolted together, the gearbox output shaft rotates, the wheel is locked onto the shaft, and the camera is fixed to the body. From ROS's point of view, this is grouped as follows:

Chassis + Camera      β†’ one Link (or Fixed Links)
Motor + Gearbox housing β†’ one Link
Shaft + Wheel           β†’ one Link

Why do the shaft and wheel become one Link? Because the shaft doesn't move relative to the wheel β€” both rotate together.

The Golden Rule of Links

πŸ’‘ One Question, Always

Whenever you're unsure whether two parts should be one Link or not, just ask: do these two parts move relative to each other? If the answer is "no," they can probably be one Link.

For example, Wheel + Hub + Shaft, if all locked together, become one wheel_link. But Wheel and Gearbox Housing move relative to each other, so they cannot be one Link.

The Golden Rule of Joints

A Joint is not a physical part. wheel_joint, arm_joint, suspension_joint, and camera_joint are none of them parts in SolidWorks; a Joint only says "how does this Link move relative to that Link." For example:

gearbox_link
↓ wheel_joint
wheel_link

Meaning wheel_link rotates relative to gearbox_link.

Four joint types that cover 90% of projects

TypeMotionExample
fixedNo motion at allA camera bolted to the chassis: base_link ↓fixed camera_link
continuousUnlimited rotationWheels, motors, rotating shafts: gearbox_link ↓continuous wheel_link
revoluteLimited rotationAn arm joint between -90 and +90 degrees: arm_link_1 ↓revolute arm_link_2
prismaticLinear motionA linear rail, jack, ball screw, or lift: base_link ↓prismatic linear_stage_link
base_link camera_link fixed no relative motion gearbox_link wheel_link continuous unlimited rotation arm_link_1 arm_link_2 revolute limited rotation (-90Β° to +90Β°) base_link linear_stage_link prismatic linear motion
Four joint types that cover 90% of projects β€” each label's color matches the same Joint's color in the table above

Why springs are confusing

A spring is not a rigid body β€” it shortens, lengthens, and deforms; but a standard URDF is built around rigid bodies. So you need to separate two things:

⚠️ Spring mesh β‰  Suspension motion

If you want the spring itself to be visible in RViz, create a spring_link and put the spring's Mesh inside it. But the spring's behavior (the gearbox moving up and down relative to the chassis) is defined by a separate Joint, e.g. base_link ↓suspension_joint gearbox_link. The spring's visual model and the motion the spring produces are two separate things.

Conceptual tree versus the actual Exporter tree β€” a note that once caused a real error

Suppose you have a project with a body, a gearbox, and a wheel, and the actual motion is: body ↓suspension gearbox ↓rotation wheel. The conceptual URDF tree (just for understanding the system) looks like this:

base_link
└── suspension_joint
    └── gearbox_link
        └── wheel_joint
            └── wheel_link
⚠️ This is not the tree you build in the SolidWorks Exporter

Inside the SolidWorks URDF Exporter you only build Links; Joints are never separate Nodes in the Tree. The same system above becomes this in the Exporter:

❌ Wrong:
base_link
└── suspension_joint
    └── gearbox_link

βœ… Correct:
base_link
└── gearbox_link

And inside the settings of that same gearbox_link you write: Joint Name: suspension_joint.

How does the Exporter understand the Parent/Child relationship?

Suppose you build this Tree in the Exporter:

base_link
└── gearbox_link
    └── wheel_link

When you click on gearbox_link, you write Link Name: gearbox_link and Joint Name: suspension_joint. From these two lines alone, the Exporter understands Parent = base_link, Child = gearbox_link, Joint = suspension_joint. You repeat the same thing on wheel_link with Joint Name: wheel_joint and Joint Type: Continuous so the Exporter understands gearbox_link ↓wheel_joint wheel_link.

SolidWorks URDF Exporter base_link gearbox_link (selected) wheel_link Link Properties Link Name gearbox_link Joint Name suspension_joint Joint Type Fixed β–Ύ Parent Link base_link What does this mean? base_link β†’ suspension_joint β†’ gearbox_link
Clicking any Link in the Tree opens the Link Properties form for that Link β€” the Joint is defined within this form, not as a separate Node in the Tree

What does a Component mean?

Every Link needs to know which SolidWorks parts belong to it. For example, gearbox_link might contain Gearbox_Housing.SLDPRT, Motor_Housing.SLDPRT, and Motor_Bracket.SLDPRT β€” since these three parts don't move relative to each other, you assign them all to the same Link. Likewise, for wheel_link you might assign Wheel.SLDPRT, Hub.SLDPRT, and Output_Shaft.SLDPRT.

The root Link and naming rules

Every URDF must have exactly one root β€” usually base_link β€” meaning everything eventually connects to it:

base_link
β”œβ”€β”€ left_wheel_link
β”œβ”€β”€ right_wheel_link
β”œβ”€β”€ lidar_link
β”œβ”€β”€ camera_link
└── arm_link
⚠️ Two independent roots is wrong

If camera_link isn't connected to anything, the URDF will have two independent roots β€” the same Two root links found error we saw in the common errors table.

Establish a consistent naming convention from the start β€” it's not mandatory, but it's extremely useful:

Link  β†’ ends in _link   (base_link, left_wheel_link, gearbox_link, camera_link, lidar_link)
Joint β†’ ends in _joint  (left_wheel_joint, camera_joint, suspension_joint)

And don't reuse names β€” no two Links with the same name, no two Joints with the same name. The only exception is base_link itself, which, being the Root, has no Joint at all (its Joint Name stays empty); every other Link must have a Joint Name.

The Joint axis and Coordinate System

If a Joint is movable, ROS needs to know which axis it moves around: 1 0 0 for X, 0 1 0 for Y, 0 0 1 for Z. If a wheel shaft runs along Y, axis = 0 1 0 β€” but never assume out of habit that "a wheel is always the Y axis"; you must look at the actual model. As mentioned in the coordinate system section of this appendix, ROS prefers X = Forward, Y = Left, Z = Up, so before exporting, build a ROS_Coordinate_System in SolidWorks and put the origin somewhere logical, such as the center of the chassis or the center of the wheel axis.

Full example: from mechanism to the final Exporter tree

Suppose you have a new project with a Body, Motor, Gearbox, Output Shaft, Wheel, and Camera. The actual motion is: the Body is fixed relative to the Motor+Gearbox, the Motor/Gearbox rotates relative to the Output Shaft+Wheel, and the Body is fixed relative to the Camera. So the Links become:

base_link
gearbox_link
wheel_link
camera_link

And the actual tree inside the SolidWorks Exporter:

base_link
β”œβ”€β”€ gearbox_link
β”‚   └── wheel_link
└── camera_link

With the following settings on each Child Link:

gearbox_link β†’ Joint Name: gearbox_mount_joint  | Joint Type: Fixed
wheel_link   β†’ Joint Name: wheel_joint          | Joint Type: Continuous
camera_link  β†’ Joint Name: camera_joint         | Joint Type: Fixed

Checklist before clicking Preview and Export

βœ… Check these before exporting
  • The Tree contains only Links β€” I haven't built Joints as separate Nodes
  • base_link is exactly one and has no Joint
  • Every Child Link has a defined Joint Name
  • All Links have unique names
  • All Joints have unique names
  • Each Link's Components are correctly identified and assigned
  • Parts that move together are grouped into a single Link
  • Parts that move relative to each other are in separate Links
  • Each Joint's type (fixed/continuous/revolute/prismatic) is chosen correctly
  • Each Joint's axis matches the model's actual geometry
  • The Coordinate System is sensible (X-forward, Y-left, Z-up)
  • Mass Properties for every part make sense
πŸ“– Five Rules Worth Memorizing on Their Own

1. Link = rigid body.

2. Joint = motion between two Links.

3. Parts that don't move relative to each other β†’ one Link.

4. Inside the SolidWorks Exporter, the Tree is built only from Links.

5. Each Child Link's Joint is defined within that Child Link's settings β€” not as a separate Node.

And the overall thought process for every new project should follow this same path:

CAD parts
↓
Which parts move together?
↓
Rigid Body Groups
↓
Links
↓
Motion between Links
↓
Joints
↓
Conceptual URDF Tree
↓
SolidWorks Tree = Links Only
↓
Assign Components
↓
Configure Joint on Child Link
↓
Preview
↓
Export URDF
↓
ROS 2 validation

This is exactly the logic we apply, step by step, on the real ARCHO example, throughout the rest of this appendix.

A.4Preparing the SolidWorks assembly

This is the most important part of the whole job. Most URDF problems don't come from the Exporter itself; they come from a messy SolidWorks assembly.

πŸ’‘ The Core Rule

Any part that is supposed to move relative to another part must be an independent Link. Parts that never move relative to each other don't need separate Links.

Suppose ARCHO's chassis assembly has these parts: chassis, cover, battery, electronics board, brackets, left wheel, right wheel, caster wheel, LiDAR, and camera. Since the chassis, cover, battery, board, and brackets never move relative to each other, they all become part of a single Link:

base_link
β”œβ”€β”€ chassis
β”œβ”€β”€ cover
β”œβ”€β”€ battery
β”œβ”€β”€ electronics
└── brackets

But the wheels and sensors need independent Links:

left_wheel_link
right_wheel_link
caster_link
lidar_link
camera_link

The suggested assembly structure in SolidWorks looks roughly like this:

archo_assembly.SLDASM
β”‚
β”œβ”€β”€ base_subassembly.SLDASM
β”‚   β”œβ”€β”€ chassis.SLDPRT
β”‚   β”œβ”€β”€ top_cover.SLDPRT
β”‚   β”œβ”€β”€ battery.SLDPRT
β”‚   β”œβ”€β”€ motor_mount_left.SLDPRT
β”‚   └── motor_mount_right.SLDPRT
β”‚
β”œβ”€β”€ left_wheel.SLDPRT
β”œβ”€β”€ right_wheel.SLDPRT
β”œβ”€β”€ caster.SLDPRT
β”œβ”€β”€ lidar.SLDPRT
└── camera.SLDPRT

Fixing the chassis, floating the wheels

In SolidWorks, the main chassis must be Fixed. Right-click the chassis in the FeatureManager and select:

Right Click β†’ Fix

You should see an (f) marker next to the chassis, e.g. (f) base_chassis. The wheels must not be Fixed β€” if they are, right-click them and select Float. The correct structure looks like this:

(f) base_chassis
(-) left_wheel
(-) right_wheel
(-) caster

Proper Mates for each wheel

The Exporter uses the assembly structure, Mates, and your selections to build the Joints. Every wheel needs at least these two Mates:

⚠️ Common mistake

The Lock Rotation option must not be enabled, otherwise the wheel won't be able to rotate in the exported URDF. To test, rotate the wheel with the mouse β€” if it turns freely about its own axis, the Mate is defined correctly. Do this for both wheels (left and right).

A.5The ROS coordinate system and Coordinate System

ROS uses a standard coordinate convention: X forward, Y left, Z up.

Top view:
             +X
         front of robot
              ↑
              |
 +Y  ←  [ ARCHO ]  β†’  -Y
              |
              ↓
             -X

Side view:
       +Z
        ↑
        |
        |
        └────→ +X

If your SolidWorks model's coordinate system doesn't match this convention, ARCHO may appear lying down, upside down, or facing the wrong direction in RViz, or the wheels may rotate about the wrong axis.

To fix this at the root, build a dedicated coordinate system in SolidWorks:

Insert
β†’ Reference Geometry
β†’ Coordinate System

Name it, for example, ROS_Coordinate_System and set the axes like this:

X = Forward
Y = Left
Z = Up
The blue three-axis X-Y-Z triad that SolidWorks shows when creating a Coordinate System
You'll see this same blue Triad in SolidWorks β€” before continuing, always compare the X, Y, and Z directions against this shape

Preferably place the origin at one of these points: the geometric center of the chassis, the midpoint between the two wheel axes, on the ground plane below the robot's center, or the center of the base plate.

In a real assembly, you typically repeat this not just once, but for every Link and every Joint separately β€” a Coordinate System for the chassis, one for each wheel, one for each Caster. The result ends up looking like this: dozens of small Triads, each sitting exactly where a Joint will later be located:

A real SolidWorks assembly with several Coordinate Systems defined on the wheels and casters
Every blue Triad in this assembly is an independent Coordinate System β€” each one is later linked directly to a Joint in the URDF Exporter
πŸ”§ Engineering Note

For a mobile robot, a more professional structure uses two Links: base_footprint, which sits on the ground, and base_link, which sits at the physical center of the chassis. For the first export, it's enough to have just base_link; we'll add base_footprint to the URDF by hand later β€” just as we discussed in Chapter 5 (TF2) about the tree of coordinate frames.

A.6Material, mass, and inertia

A URDF isn't just a 3D picture; for physical simulation it also needs dynamic properties. SolidWorks can compute mass, center of mass, moments of inertia, and the inertia tensor from each part's Material and geometry.

For every part or Subassembly, define a real Material:

Right Click on Material
β†’ Edit Material

For example Aluminum 6061, ABS, Steel, or Rubber β€” whichever is closest to the real part. Then check:

Evaluate
β†’ Mass Properties

You should see sensible numbers for Mass, Center of Mass, Principal Axes, and Moments of Inertia. The Exporter can transfer this information directly into the <inertial> tag in URDF.

⚠️ Common mistake

If a Material isn't defined, or the mass is computed incorrectly, the robot may fly off in Gazebo, the wheels may jitter, the model may sink into the ground, the simulation may become unstable, or ARCHO may tip over from a small contact. This is exactly the class of errors we discussed in Chapter 7 (Gazebo) regarding the importance of Inertia.

The Configure Link Properties page β€” where mass becomes a number

After you've defined all the Joints, the Exporter shows you one more page: Configure Link Properties. This is no longer about motion; it's about how heavy each Link is, where its mass is concentrated, and what color we see it in.

The Configure Link Properties page in the SolidWorks URDF Exporter with fields for Mass, Moment of Inertia, Visual/Collision Origin, Mesh Detail, and Color
The Configure Link Properties page for left_wheel β€” the top section (Inertial) is computed directly from the CAD, the bottom section (Visual and Collision Meshes) determines the Link's appearance and physical behavior

This page has two main sections; let's go through each one separately.

Top section: Inertial β€” the same thing we discussed a few lines ago

If you've defined the Material correctly, the Exporter pulls these numbers straight from the CAD β€” you no longer need to guess:

FieldWhat it means
Mass (kg)The Link's actual mass, computed from the volume and Density of the chosen material β€” not an assumed number.
Inertial Origin (x, y, z)The location of the Center of Mass relative to the Link's frame. It doesn't need to sit exactly at zero β€” for example, x=-0.0001, y=-0.025, z=0 is perfectly normal.
Moment of Inertia β€” ixx, iyy, izzThe body's resistance to rotation about each of the three principal axes, in kgΒ·mΒ².
Moment of Inertia β€” ixy, ixz, iyzThe cross-coupling terms of the inertia tensor; for nearly symmetric bodies these are usually very small numbers.

These six numbers together form the Link's inertia matrix and go directly into the <inertial> tag in URDF β€” the same tag we'll see again a few lines later in the full sample URDF.

Bottom section: Visual and Collision Meshes

FieldWhat it means
Origin (m) / Roll-Pitch-YawIf the Mesh isn't exactly at the Link's origin, this is where you enter the needed offset and rotation.
Mesh Detail β€” CoarseFewer triangles β†’ smaller file, faster loading, less computational load for RViz and Gazebo.
Mesh Detail β€” FineMore triangles β†’ more accurate appearance, but a larger and heavier file. For most ROS projects, Fine doesn't offer a noticeable benefit.
Color β€” Red / Green / BlueThe Visual Mesh color, each a number between 0 and 1.
Color β€” AlphaTransparency level: 1 means fully opaque, 0 means fully transparent.
Material nameA name that can be shared across multiple Links, e.g. wheel_rubber or black_plastic, so the color is defined once and reused everywhere.
πŸ”§ Reminder of the Visual/Collision Golden Rule

This page builds both a Visual and a Collision entry for each Link β€” and the same rule we look at in more detail a few pages later applies here too: a precise Mesh is good for Visual, but for Collision it's better to use a simple Box or Cylinder so that Gazebo doesn't have to compute collisions against thousands of tiny triangles.

A.7Installing the SolidWorks URDF Exporter

The classic, well-known tool is the SolidWorks to URDF Exporter project from the ROS organization. This tool converts a SolidWorks assembly into URDF, Mesh files, and the initial structure of a ROS package.

After installing the Exporter:

  1. Open SolidWorks.
  2. Go to Tools β†’ Add-Ins.
  3. Find the SolidWorks to URDF Exporter option.
  4. Enable both the Active Add-ins and Start Up checkboxes.

After this step, the Export command should appear at the bottom of the Tools menu: Tools β†’ Export as URDF.

⚠️ If the Exporter doesn't appear in Add-ins

Check that SolidWorks was closed during installation, that you ran the Installer with Run as administrator, that the Exporter version is compatible with your SolidWorks version, that Windows hasn't blocked the DLL file, and that you restarted SolidWorks after installation.

πŸ”§ Engineering Note β€” a newer tool

For newer SolidWorks versions, a newer tool called sw2robot has also been introduced, which works as a Standalone tool using COM, analyzes Mates, and has a browser-based editor for adjusting axes, Joint Limits, and Collision. This tool can be a good replacement in versions where the classic Add-in has issues. That said, this appendix's teaching path uses the classic Exporter, because you should understand the resulting URDF structure by hand β€” exactly what you learned in raw form in Chapter 4.

A.9Collision versus Visual

Every Link usually has two separate geometry models: Visual, which the user sees, and Collision, which the physics engine uses for collisions. Visual can be detailed and beautiful; Collision is better kept simple.

For example, the chassis Visual can be a complex STL:

<visual>
  <geometry>
    <mesh filename="package://archo_description/meshes/base_link.stl"/>
  </geometry>
</visual>

But the Collision is better as a simple shape like a Box:

<collision>
  <geometry>
    <box size="0.60 0.45 0.20"/>
  </geometry>
</collision>
⚠️ Why a complex STL is bad for Collision

Because it makes the simulation heavy, causes unstable collisions, makes the robot jitter, forces Gazebo to do more processing, and lets the wheels catch on small body details. Simple rule: precise Visual Mesh, simple Collision Mesh.

The same logic applies to Mesh quality; very high quality increases file size and slows down RViz and Gazebo. For ARCHO, a medium- or good-quality Visual Mesh and a simple or simplified Collision shape is enough.

A.10Exporting and transferring files to WSL

Pick a simple path on Windows and avoid spaces, non-English characters, or special characters in the path:

Bad pathGood path
C:\Users\Pouya\Desktop\my new robot\C:\ros_exports\archo_description\

Then run Export. The output typically looks like this:

archo_description/
β”œβ”€β”€ CMakeLists.txt
β”œβ”€β”€ package.xml
β”œβ”€β”€ launch/
β”œβ”€β”€ meshes/
β”‚   β”œβ”€β”€ base_link.STL
β”‚   β”œβ”€β”€ left_wheel_link.STL
β”‚   β”œβ”€β”€ right_wheel_link.STL
β”‚   β”œβ”€β”€ lidar_link.STL
β”‚   └── camera_link.STL
β”œβ”€β”€ textures/
└── urdf/
    └── archo.urdf

Transferring from Windows to WSL

If your Workspace is ~/ros2_ws and the Windows output is at C:\ros_exports\archo_description, in WSL this path corresponds to /mnt/c/ros_exports/archo_description.

$ cd ~/ros2_ws/src
$ cp -r /mnt/c/ros_exports/archo_description .
$ ls
archo_bringup archo_description
$ sudo apt install tree
$ tree ~/ros2_ws/src/archo_description

Fixing the STL filenames

Linux is case-sensitive; base_link.STL and base_link.stl are two completely different files. It's best to lowercase all Meshes:

$ cd ~/ros2_ws/src/archo_description/meshes
$ mv base_link.STL base_link.stl
$ mv left_wheel_link.STL left_wheel_link.stl
$ mv right_wheel_link.STL right_wheel_link.stl

Then also fix the names inside the URDF from base_link.STL to base_link.stl.

⚠️ Check the Mesh path

The correct path structure is always package://package_name/folder/file, e.g. <mesh filename="package://archo_description/meshes/base_link.stl"/>. There should be no leftover Windows path (C:\Users\...) or absolute Linux path (/home/...) in the URDF.

A.11Building the ROS 2 package

CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(archo_description)

find_package(ament_cmake REQUIRED)

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

ament_package()

package.xml

<?xml version="1.0"?>
<package format="3">
  <name>archo_description</name>
  <version>0.0.1</version>

  <description>
    URDF description package for the ARCHO mobile robot.
  </description>

  <maintainer email="pouya@example.com">
    Pouya Mansournia
  </maintainer>

  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

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

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

Launch File for RViz

from pathlib import Path

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description() -> LaunchDescription:
    package_name = "archo_description"
    urdf_file_name = "archo.urdf"

    package_share = Path(get_package_share_directory(package_name))
    urdf_path = package_share / "urdf" / urdf_file_name

    if not urdf_path.exists():
        raise FileNotFoundError(f"URDF file not found: {urdf_path}")

    robot_description = urdf_path.read_text(encoding="utf-8")

    return LaunchDescription(
        [
            Node(
                package="robot_state_publisher",
                executable="robot_state_publisher",
                name="robot_state_publisher",
                output="screen",
                parameters=[{"robot_description": robot_description}],
            ),
            Node(
                package="joint_state_publisher_gui",
                executable="joint_state_publisher_gui",
                name="joint_state_publisher_gui",
                output="screen",
            ),
            Node(
                package="rviz2",
                executable="rviz2",
                name="rviz2",
                output="screen",
            ),
        ]
    )

The official ROS 2 documentation uses robot_state_publisher to display a URDF; this Node converts the URDF model and Joint states into TF so tools like RViz can show the robot's structure.

Build

$ cd ~/ros2_ws
$ source /opt/ros/jazzy/setup.bash
$ colcon build --packages-select archo_description --symlink-install
Summary: 1 package finished
$ source install/setup.bash
$ ros2 pkg prefix archo_description
/home/pouya_mn/ros2_ws/install/archo_description

A.12Testing in RViz and checking TF

πŸ’‘ A shortcut before ROS: Online URDF Viewer

Before you even go near a terminal and ROS, there's a faster way to see your result: an online URDF Viewer that opens URDF, XACRO, STL, and DAE files via Drag & Drop right in your browser, showing the 3D model and the initial motion of the Joints. This tool doesn't replace RViz or Gazebo, but for a quick check β€” is the robot's orientation correct? Are the wheels in the right place? Did the Meshes load? β€” it's great, and it surfaces obvious errors before you spend the time setting everything up in WSL.

After this quick check, it's time for a more serious review. First check the URDF file before anything else:

$ sudo apt install liburdfdom-tools ros-jazzy-urdf-tutorial ros-jazzy-joint-state-publisher-gui
$ check_urdf src/archo_description/urdf/archo.urdf
robot name is: archo
---------- Successfully Parsed XML ---------------
root Link: base_link has 5 child(ren)
child(1): left_wheel_link
child(2): right_wheel_link
child(3): caster_link
child(4): lidar_link
child(5): camera_link

Then run:

$ ros2 launch archo_description display.launch.py

In RViz: set Fixed Frame to base_link, click Add, and add both the RobotModel and TF displays. If the model is correct, the chassis and wheels appear, the TF tree is complete, and the Joints can be changed using the Joint State Publisher.

The RViz environment with the Displays panel on the left, the 3D scene in the center, and the Views panel on the right
The RViz environment β€” the Displays panel on the left (where you add RobotModel and TF), the 3D scene in the center, and the Views panel on the right for controlling the camera

If the model isn't visible but there's no error either, check these Topics:

$ ros2 topic list
/joint_states
/robot_description
/tf
/tf_static
$ ros2 run tf2_tools view_frames

The last command produces a frames.pdf file after a few seconds, which can be opened in Windows from WSL with explorer.exe frames.pdf, and it should show ARCHO's same five-branch tree.

A.13Common errors and how to fix them

Error / symptomLikely causeFix
Two root links foundA Link has no Parent Joint (e.g. camera_link)Define a <joint type="fixed"> for that Link
Could not load resourceWrong filename casing or wrong package:// pathCarefully check the package name, meshes folder, stl extension, and path
The model is far too largeSolidWorks exported in millimeters but URDF reads metersAdd scale="0.001 0.001 0.001" to the mesh tag β€” only if the Exporter isn't already handling scale itself
The model is far too smallScale applied twice (the STL was already in meters)Remove the extra scale from the mesh tag
The robot appears lying downThe SolidWorks coordinate system doesn't match ROS's X-forward/Y-left/Z-up conventionPreferably fix the Coordinate System in SolidWorks; temporary workaround: rpy="1.5708 0 0" (90 degrees in radians)
The wheel is in the wrong placeThe origin xyz value in the Joint is wrongRecheck the order xyz = X Y Z and rpy = Roll Pitch Yaw β€” all angles are in radians
The wheel rotates about the wrong axisThe <axis xyz=".."/> vector is wrongMatch the 1 0 0/0 1 0/0 0 1 axes with the wheel's actual geometry
The wheel doesn't rotate at allThe Joint was built as fixedChange the Joint type to continuous
Joint State Publisher shows no Slider for the wheelcontinuous Joints have no fixed numeric limitFor a temporary test make it revolute with limit lower="-3.14" upper="3.14", then switch back to continuous

A.14Full sample URDF

This sample is only for understanding the structure β€” the inertias and dimensions (chassis size, wheel radius) are illustrative, do not match the values from Chapter 4, and should not be used directly for the real ARCHO; before use, replace every number with your own robot's actual dimensions:

<?xml version="1.0"?>
<robot name="archo">

  <link name="base_link">
    <visual>
      <geometry>
        <mesh filename="package://archo_description/meshes/base_link.stl"/>
      </geometry>
    </visual>

    <collision>
      <geometry>
        <box size="0.60 0.45 0.20"/>
      </geometry>
    </collision>

    <inertial>
      <origin xyz="0 0 0"/>
      <mass value="20.0"/>
      <inertia
        ixx="0.40" ixy="0.0" ixz="0.0"
        iyy="0.65" iyz="0.0"
        izz="0.80"/>
    </inertial>
  </link>

  <link name="left_wheel_link">
    <visual>
      <geometry>
        <mesh filename="package://archo_description/meshes/left_wheel_link.stl"/>
      </geometry>
    </visual>

    <collision>
      <geometry>
        <cylinder radius="0.10" length="0.04"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.8"/>
      <inertia
        ixx="0.004" ixy="0.0" ixz="0.0"
        iyy="0.004" iyz="0.0"
        izz="0.007"/>
    </inertial>
  </link>

  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel_link"/>
    <origin xyz="0 0.245 -0.10" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>

  <link name="right_wheel_link">
    <visual>
      <geometry>
        <mesh filename="package://archo_description/meshes/right_wheel_link.stl"/>
      </geometry>
    </visual>

    <collision>
      <geometry>
        <cylinder radius="0.10" length="0.04"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.8"/>
      <inertia
        ixx="0.004" ixy="0.0" ixz="0.0"
        iyy="0.004" iyz="0.0"
        izz="0.007"/>
    </inertial>
  </link>

  <joint name="right_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="right_wheel_link"/>
    <origin xyz="0 -0.245 -0.10" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>

</robot>

A.15Summary, checklist, and exercise

Exporting from SolidWorks isn't the end of the job; it's a starting point. After exporting, these refinements are usually needed: fixing up Link and Joint names, converting URDF to Xacro (Chapter 4), simplifying Collisions, rechecking Inertia, adding base_footprint, adding Sensor Frames, adding ros2_control (Chapter 8), adding Gazebo plugins (Chapter 7), defining wheel radius and spacing, and a full test in RViz and then Gazebo before connecting to the real controller.

βœ… Checklist for this appendix
  • I opened and reviewed ARCHO's main assembly
  • I fixed the chassis and floated the wheels
  • I connected the wheels with proper Mates (Concentric + Coincident/Distance)
  • I checked the Material and Mass Properties of each part
  • I built a ROS Coordinate System (X-forward, Y-left, Z-up)
  • I activated the SolidWorks URDF Exporter
  • I defined base_link, left_wheel_link, and right_wheel_link
  • I exported the URDF and Meshes and transferred them to ~/ros2_ws/src
  • I checked the file with check_urdf
  • I saw the robot in RViz without errors and confirmed the TF tree
Hands-on Exercise

Prepare ARCHO's SolidWorks assembly (or any other robot you have on hand) following the steps in this appendix, export the URDF, and load it into the archo_description package. Then use check_urdf and RViz to confirm the model displays without errors and the wheels rotate about the correct axis. Finally, save the output of view_frames and compare the TF tree with the tree expected in this appendix.

🌍 Real-World Example: from this exact path to a real robot

All of these steps β€” from preparing the assembly in SolidWorks to exporting the URDF and loading it into a ROS 2 package β€” are exactly the path implemented in the warehouse-amr-ros2 repository: a warehouse AMR robot whose body and wheels were converted from SolidWorks to URDF using this exact method. If you want to see a complete, real example of this process β€” not just a teaching example β€” go check out that repository.

πŸ“– For Further Reading

This appendix can be read alongside these official resources:

Official ROS 2 documentation β€” Building a Movable Robot Model with URDF

ros2_control documentation β€” Mobile Robot Kinematics

Online URDF Viewer β€” the same fast Drag & Drop tool introduced in the RViz testing section.

Glossary

URDF
Unified Robot Description Format β€” the XML file that describes a robot's mechanical structure for ROS.
Link
An independent rigid body in the URDF tree.
Joint
The connection between two Links that defines their relative motion type and axis.
Continuous Joint
A joint that rotates with no angular limit β€” suited to drive wheels.
Revolute Joint
A joint that only moves within a specific angular range.
Prismatic Joint
A joint that moves linearly instead of rotating β€” like a rail, jack, or lift.
Fixed Joint
A connection with no relative motion at all β€” used for sensors like LiDAR and cameras.
Mimic Joint
A state where one Joint's motion is computed directly from another Joint β€” like two fingers of a Gripper.
Visual
The geometry the user sees in RViz/Gazebo; can be detailed and complex.
Collision
The geometry the physics engine uses to compute collisions; should be simple.
Inertial
The URDF tag that defines each Link's mass, center of mass, and inertia tensor.
base_footprint
An auxiliary Link that sits on the ground plane, separate from base_link (which sits at the physical center of the chassis).
package://
The standard ROS path prefix for referencing files inside a package, such as Meshes.