You have an arm description that parses, a gripper package with its own URDF, and a wrist camera to mount over the flange. The obvious move — including the gripper's URDF file into your top-level xacro — produces a robot where the tool floats beside the arm instead of hanging from it. The fix is to attach the tool by calling its xacro macro with a prefix and a parent link. The steps below attach any tool description that ships a macro, gripper or camera alike, and the worked tree check proves the merged description before MoveIt or Gazebo ever load it.
Why a plain include leaves the gripper floating
An include is a text paste. The <xacro:include> element inserts the other file's XML inside your <robot> element and nothing more. If that file is a plain URDF, its links arrive already parented: the gripper's first joint points at the gripper's own base link, and no element connects that base to your wrist flange. The merged file now holds two disconnected trees under one roof.
The failures follow from that. check_urdf prints only the tree it can reach from the root link, so the gripper's links may not appear at all. MoveIt cannot build a kinematic chain from the arm base to a fingertip, so no planning group can cover the tool. The Gazebo importer treats the extra root as a floating body. And an include has nowhere to take a prefix or a parent link, because parameters belong to macros.
The rule is short: include files that define macros, call macros to attach tools. The macro call is the only mechanism that lets you name the flange as the parent link and keep every tool link name unique.
What you need
- The arm's ROS description package, with the documented name of its flange link.
- The gripper package's macro file, a
.urdf.xacrowhose contents define a<xacro:macro>element. - The xacro command-line tool and check_urdf, both part of a standard ROS desktop install.
- MoveIt and Gazebo, if you plan to simulate.
The snippets below use placeholder names — your_arm_description, your_gripper_description, a macro called your_gripper_macro — so substitute the package and macro names your files actually declare, read from each macro's params line.
Attach the gripper in seven steps
-
Read the gripper macro's params line. It names every parameter the macro accepts; the common pattern is
params='prefix parent', where prefix is prepended to the tool's link and joint names and parent is the link the tool bolts to. -
Include the macro files at the top of your own xacro, one
<xacro:include>line per package, with$(find package_name)resolving each path at expansion time. -
Expand the arm from its own macro or keep your existing arm description, so the file holds exactly one top-level
<robot>element. -
Call the gripper macro once, passing a prefix and the flange link name as parent. Inside the macro, its own joint tag sets the parent link you passed — in the common pattern a fixed joint named
${prefix}gripper_base_joint— and that joint is what welds the tool to the wrist. You never write the attaching joint by hand. -
Pass the mount offset if the params line declares an origin block parameter; URDF origins are in metres, so a 12 mm spacer plate becomes an origin of
0 0 0.012. If the macro takes no origin, add the spacer as your own short fixed-joint chain between flange and tool. -
Add the wrist camera the same way: one fixed joint from the flange or the gripper base to
camera_link, carrying the CAD offsets in metres and the camera's optical rotation in the joint origin. - Expand and check. Run the xacro tool on the top-level file, then run check_urdf on the output. Expect a single tree rooted at the arm base.
A minimal top-level file then reads:
<?xml version='1.0'?>
<robot name='cell' xmlns:xacro='http://www.ros.org/wiki/xacro'>
<!-- macro definitions only; nothing is attached yet -->
<xacro:include filename='$(find your_arm_description)/urdf/arm.urdf.xacro'/>
<xacro:include filename='$(find your_gripper_description)/urdf/gripper_macro.urdf.xacro'/>
<!-- the arm itself -->
<xacro:your_arm_macro/>
<!-- one macro call does the attaching -->
<xacro:your_gripper_macro prefix='gripper_' parent='flange_link'/>
</robot>
Worked example: two-finger gripper and wrist camera on a six-axis arm
The case: a typical six-axis arm, a two-finger parallel gripper whose macro declares three links — a base and one link per finger — and two joints, and a wrist camera on a 12 mm spacer. The parent link for both tools is the arm's flange. Inputs:
- arm macro expands to 7 links and 6 joints (base link plus six arm links)
- gripper macro expands to 3 links and 2 joints (one joint per finger)
- camera chain adds 1 link and 1 fixed joint
The rule in words: a URDF is a tree. One root link, every other link with exactly one parent joint, no cycles, so a connected description always satisfies links = joints + 1, and every tool link must sit below the flange in the printed tree.
Arithmetic:
- links_total = 7 + 3 + 1 = 11
- joints_total = 6 + 2 + 1 = 9
- tree check: 11 = 9 + 1, consistent with one connected tree
The decision: expand and run check_urdf. Pass means one root — the arm base — with 9 joints printed in a single tree and both finger links plus camera_link listed below the flange. Fail means the arm's 7 links print while the gripper's 3 appear nowhere below it: the include merged the tool instead of attaching it, so replace the include with the macro call and re-check. Watch the trap: the broken include also totals 11 links and 9 joints. Counts prove size; the printed tree proves connectivity.
Check the result in MoveIt and Gazebo
In MoveIt, run the Setup Assistant on the expanded URDF and define a planning group whose chain runs from the arm base to a fingertip link; add the two finger joints to that group so the planner and the gripper controller can both command them. In Gazebo, every gripper link needs an inertial element and the finger joints need effort limits plus a transmission or a plugin entry before they move. If the macro omits these, add them in a wrapper file of your own instead of editing the shipped package.
Then spawn the robot, publish the joint states with robot_state_publisher, and drive the wrist through a slow trajectory. The fingers must follow the flange with no relative drift, and the camera frame must sit at the spacer offset you typed in. Who sends the grip commands, planner or cell controller, is an architecture decision of its own; the guide to vision system and robot gripper control architecture walks through it.
When the gripper ships only a plain URDF
Some packages ship a bare .urdf and no macro. Wrap it once, in your own workspace, and leave the original untouched: create a file whose contents are a <xacro:macro name='your_gripper_macro' params='prefix parent'> element, paste the plain file's links and joints inside, prepend ${prefix} to every link and joint name, and set the root joint's parent link to ${parent}. If a finger joint carries a mimic tag that follows the driving finger, prefix the joint name in the mimic attribute too. Your top-level file then treats the wrapper exactly like any shipped macro.
Mount the camera and pick the hardware
The offsets you typed into the joint origin come from the mount drawing in millimetres, divided by 1000 into URDF's metres. On the bench, hold the camera on hardware that keeps those offsets true between sessions: camera stands with a slide stage re-locate the sensor at the same height and angle, and the accessories finder filters stands and stages by type and travel. If the camera looks straight down from a vertical mount, put the rotation in the joint origin rather than in per-frame math, as the guide to vertically mounted depth cameras and the URDF joint explains.
The URDF pose stays nominal even with a perfect mount; production accuracy comes from a calibrated offset on top of it, which is the subject of URDF nominal extrinsics versus the calibrated camera offset. For the tool frame of the fingertips, a TCP calibration on the real arm closes the gap between URDF and hardware; see robot-held camera calibration with a TCP tool frame. Camera and gripper share the wrist power harness, and the fix for wrist camera and gripper red LED power faults covers that wiring.
For guidance you need a real sensor where camera_link sits: the 3D sensors category holds laser profilers and stereo cameras built for robot wrists and cells, and the camera finder filters them by type. The stand configurator pairs the sensor with brackets and a stand in one pass. More integration guides sit on the systems topic page.
Pre-run checklist
- One top-level
<robot>element; includes only for macro files. Why: a plain URDF included directly merges as a second, disconnected tree. - Every tool attached by a macro call with a prefix and the flange link as parent. Why: parameters exist only on macros.
- check_urdf prints one tree, links = joints + 1, every tool link below the flange. Why: counts alone cannot tell an attached tool from a floating one.
- Finger joints inside a MoveIt planning group. Why: the planner can only command joints a group exposes.
- An
inertialon every gripper link before Gazebo spawns it. Why: massless links make the physics engine reject or destabilise the model. - Camera joint origin in metres, optical rotation included. Why: millimetre drawings pasted as metres are a thousandfold error.
- Two tools, two prefixes. Why: the second expansion must not duplicate any link or joint name.
Questions
Can I attach two identical grippers to one arm?
Yes. Call the same macro twice with different prefixes, for example left_ and right_, and a different parent link for each. The prefix is prepended to every link and joint name the macro creates, so the second call cannot collide with the first. Leave the prefix empty and the second expansion produces duplicate link names, which the URDF parser rejects. Give each gripper its own planning group if MoveIt must command the fingers separately.
Does the same description run in both MoveIt and Gazebo?
The tree is shared, but each tool adds its own requirements. Gazebo needs an inertial element on every gripper link and, for actuated fingers, a transmission or plugin entry so the joints move; missing inertials make the model unstable or rejected. MoveIt needs the finger joints listed in a planning group so the planner and the gripper controller can command them. Keep one source xacro and expand it for both.
How do I add a wrist camera to the same description?
Add one fixed joint from the flange or the gripper base to a camera_link, with the offsets from the mount drawing in metres. Put the camera's optical rotation, which points the frame's z axis along the lens, in the joint origin rather than bending your calibration math later. The URDF pose is nominal; production accuracy comes from a calibrated offset on top of it.
What if the gripper package only ships a plain URDF file?
Wrap it. Create a macro file whose contents are a xacro:macro element taking prefix and parent parameters, paste the plain file's links and joints inside, prepend the prefix to every name, and set the root joint's parent link to the ${parent} parameter. Your top-level file then calls that wrapper macro exactly like any shipped one.
How do I check the result before starting Gazebo?
Expand the top-level file with the xacro tool and run check_urdf: a correct description prints one tree rooted at the arm base with every tool link below the flange. Then publish the joint states and open RViz; the fingers must render at the wrist and follow a test trajectory with no drift. Load the same expanded file in Gazebo afterwards so simulation and hardware descriptions cannot diverge.