Skip to content
Michael Behrens edited this page Jan 24, 2024 · 8 revisions

Introduction

xacro is an XML macro language and preprocessor that is typically used to simplify the generation of URDF files. However, it can be applied to any XML. Using parameterizable macros, re-occurring XML blocks can be created with a few commands only.

Here is a simple example:

<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
	<!-- Define a macro with parameters prefix, parent, and reflect - partially with default values -->
	<xacro:macro name="arm" params="prefix:='' parent reflect:=1">
		<xacro:property name="prefix_" value='${prefix + "_" if prefix else ""}' />
		<upperarm prefix="${prefix}" reflect="${reflect}" parent="${parent}" />
		<forearm prefix="${prefix}" reflect="${reflect}" parent="${prefix_}elbow" />
	</xacro:macro>
	<!-- Instantiate the macro with different parameters -->
	<xacro:arm prefix="left" reflect="1" parent="torso" />
	<xacro:arm prefix="right" reflect="-1" parent="torso" />
</robot>

which expands to:

<robot>
	<upperarm parent="torso" prefix="left" reflect="1"/>
	<forearm parent="left_elbow" prefix="left" reflect="1"/>
	<upperarm parent="torso" prefix="right" reflect="-1"/>
	<forearm parent="right_elbow" prefix="right" reflect="-1"/>
</robot>

Variables and Expressions

Arguments vs. properties

xacro distinguishes two different types of parameters:

  • substitution arguments, known from roslaunch files, are accessed via $(arg param_name).
    Arguments need to be specified on the command line using the param_name:=value syntax.
    Default argument values can be define like so in the XML:
    <xacro:arg name="param_name" default=""/>
    In contrast to roslaunch files, it is not possible to define (non-default) values programmatically.
  • xacro properties allow to store arbitrary text expressions or XML blocks.
    This is the recommended method to specify parameters within xacro itself:
    <xacro:property name="param_name" value="value" />

Both types of parameters have their own namespace. Thus, you can use the same parameter name for both, an argument and a property.

Expression evaluation: ${•}

Dollar-braces ${•} allow evaluation of arbitrary python expressions. This allows for complex math expressions and application of several functions exposed from python. Here are a few examples:

  • simple access to property values: ${param_name}
  • complex math expression: ${(angle/pi * radius)**2 + 1e-8}
  • python list comprehension: ${[x**2 for x in range(10)]}
  • function application: ${radians(90)}, ${degrees(pi)}, ${sin(angle)**2 + cos(angle)**2}

Note that due to limitations of the xacro parser, you cannot nest ${•} expressions. More specifically, dollar-braces expressions must not contain braces themselves. For this reason, it is not possible to use the python notation {"a": 1, "b": 2} to declare a dictionary. Use ${dict(a=1, b=2)} instead.

Available functions and symbols

  • Symbols and functions from python's math module are directly accessible, e.g. pi, sin, exp, fabs
  • Most builtin python functions and types are available within the python namespace, i.e. via python.round(•):
    list, dict, map, len, str, float, int, True, False, min, max, None, round, all, any, complex, divmod, enumerate, filter, frozenset, hash, isinstance, issubclass, ord, repr, reversed, slice, set, sum, tuple, type, zip
  • xacro-specific functions are available within the xacro namespace:
    • load_yaml(file): load a .yaml file and expose its content as a dotified dictonary (or list)
      This is useful for larger sets of parameters, e.g. calibration settings.
    • dotify(dict): Facilitate dictionary access by providing dotified member access: d.member ≡ d['member']
    • [message,warning,error](*args, print_location=[True|False]): print(*args) a message to stderr.
      warnings and errors are colorized yellow and red respectively.
    • print_location(): Print location information for the currently processed macro / file.
    • abs_filename(file): Prepend the directory name of the currently processed .xacro file (if file isn't yet absolute)
  • Some commonly used functions and types are directly accessible as well, i.e. without the python namespace:
    list, dict, map, len, str, float, int, bool, True, False, min, max, round
    However, to reduce namespace pollution, most symbols are available via a namespace only.

Evaluation order

Historically, xacro uses lazy evaluation of property expressions. That is, for properties defined via
<xacro:property name="param_name" value="${expression}" />
the corresponding value expression is not immediately evaluated, but only the first time the property is actually used.
This allows to define properties in arbitrary order:

<!-- Definition of var2 uses var1, which is only defined later -->
<xacro:property name="var2" value="${2*var1}" />
<xacro:property name="var1" value="42" />
<!-- As long as we use var2 only if everything required is actually defined, that's fine: -->
${var2}

However, lazy evaluation prohibits recursive evaluation and property reassignment.
To allow that, you can disable lazy evaluation like this:
<xacro:property name="text" value="${text.lower()}" lazy_eval="false"/>

rospack commands: $(•)

xacro supports all rospack commands that are supported by roslaunch as well (with the exception of eval). They are accessed via dollared parentheses $(•):

  • <foo value="$(find xacro)" />: retrieve ROS package folder
  • <foo value="$(arg my_argument)" />: retrieve (cmdline) argument

Note, that you cannot nest $(•) expressions.

Escaping $

If you want to have a literal ${ or $( in the final document, you should escape the dollar sign as $${ or $$(.

Property blocks

It's also possible to define (several) XML blocks as a variable, like this:

<xacro:property name="zero">
  <origin xyz="0 0 0" rpy="0 0 0" />
</xacro:property>

To insert the block(s) somewhere, use the xacro:insert_block tag:

<link name="link">
  <xacro:insert_block name="zero" />
</link>

Macros

The main feature of xacro is its support for macros. Define macros with the xacro:macro tag, and specify the macro name and the list of parameters, a list of white-space separated names. These parameters become macro-local properties.

The following example declares a macro called my_macro with parameters name, block1, block2, and block3. While name is a textual property to be used via ${name}, the starred parameters are XML block properties, inserted via <xacro:insert_block name="param"/>. These block parameters come in two flavors:

  • single-starred, e.g. *block1: the block will be inserted as-is.
  • double-starred, e.g. **block2: only the block's content is inserted. Its root tag (and all its attributes) will be discarded.

Block parameters will be associated to provided blocks in positional order. Users can use blocks multiple times, of course, and re-order them arbitrarily.

<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
	<xacro:property name="prop" value="outer value" />
	<xacro:macro name="my_macro" params="name *block1 **block2 **block3">
		<xacro:property name="prop" value="inner value" />
		<wrap name="${name}" prop="${prop}">
			<!-- This block is inserted as is (with its original root tag) -->
			<xacro:insert_block name="block1" />
			<!-- From the other blocks (marked ** in the parameter list) only the content is inserted -->
			<!-- We can re-order and re-use blocks multiple times -->
			<xacro:insert_block name="block3" />
			<xacro:insert_block name="block2" />
			<xacro:insert_block name="block3" />
		</wrap>
	</xacro:macro>

	<xacro:my_macro name="some name">
		<!-- Blocks are associated to block parameters in positional order -->
		<first>content</first>
		<second><actual content="2"/></second>
		<third><actual content="2" /></third>
	</xacro:my_macro>
	<out prop="${prop}" />
</robot>

This example will expand as follows:

<robot>
	<wrap name="some name" prop="inner value">
		<first>content</first>
		<actual content="2"/>
		<actual content="2"/>
		<actual content="2"/>
	</wrap>
	<out prop="outer value"/>
</robot>

Notice, that the property prop, declared both inside and outside the macro, will show different values, depending on the scope it is used in.

Default parameter values

It is possible to declare default values for textual macro parameters:
<xacro:macro name="foo" params="x:=${x} y:=${2*y} z:=0 text:='some text' N:=${None}"/>

Note, that properties from outer scope are automatically available within macros.
Sometimes you want to inherit an outer-scope property's value, if it exists, and fall back to some default otherwise. To facilitate this common use case, there is the ^| syntax: <xacro:macro name="foo" params="x:=^ y:=^|${2*x}"> The caret ^ indicates to use the outer-scope property (with the same name). The pipe | indicates to use the given fallback if the property is not defined in the outer scope.

Scopes and namespaces

Macro names, property names, and substitution args all have their own namespace. Thus, you can use the same name for a property, a macro, and an argument without problems. While there is a unique global namespace for substitution args, properties and macros have scoped namespaces.

This means that a macro opens a new scope for macro and property names. Defining new or redefining existing properties and macros within a macro is perfectly possible and won't affect the outer scope as shown in the example above for the property prop.

Much like in python property and macro names from an outer scope are still accessible within a macro (which could be considered the equivalent of a python function). However, if an existing name is redefined within the macro, this new definition will override the outer scope's definition within the local scope of the macro. Obviously, scopes are organized hierarchically (as you can declare macros within macros).

Sometimes, it is handy to return the result of some computation within a macro to the outer scope. To this end, one can use the optional attribute scope="parent | global" in the property definition to perform the property definition within the outer (or global) scope:
<xacro:property name="result" value='42' scope="parent" />

Including files

<xacro:include>

You can include other xacro files using the xacro:include tag:

<xacro:include filename="$(find package)/other.xacro" />
<xacro:include filename="other.xacro" />
<xacro:include filename="$(cwd)/other.xacro" />

The file other.xacro, will be included and expanded by xacro.
Relative filenames are interpreted relative to the currently processed file. Note: When including files within a macro, not the macro-defining but the macro-calling file is the one that processes the include!
$(cwd) explicitly allows to access files in the current working directory of the calling process.

Macros and properties included from files are imported into the current scope by default. To avoid name conflicts when importing multiple files, you can explicitly declare a namespace to import into via the ns attribute:
<xacro:include filename="other.xacro" ns="other"/>

Only one file can be included into one namespace. Including a second file into a namespace will overwrite the first.

Subsequently, you can access the namespaced macros and properties via: other.macro and other.property.

load_yaml()

Instead of defining property dictionaries manually like ${dict(a=1, sub=dict(b=2))}, you can load them from .yaml files as well:

<xacro:property name="yaml_file" value="$(find package)/config/props.yaml" />
<xacro:property name="props" value="${load_yaml(yaml_file)}"/>

This is particularly useful for large sets of configuration parameters or calibration data, as editing .yaml is much cleaner than writing python code. The content of the .yaml file is provided as a scalar, list, or dictionary as usual. For convenience, access to dictionary members is dotified, i.e. members can be accessed via props.member instead of props['member'].

Control Structures

Conditional blocks: xacro:if, xacro:unless

xacro provides conditional blocks to enable things like configurable robots or loading different Gazebo plugins. It follows this syntax:

<xacro:if value="<expression>">
	... some xml code here ...
</xacro:if>
<xacro:unless value="<expression>">
	... some xml code here ...
</xacro:unless>

Using expression evaluation via ${•}, you can use virtually any python expression evaluating to a Boolean:

<xacro:property name="var" value="useit"/>
<xacro:if value="${var == 'useit'}"/>
<xacro:if value="${var.startswith('use') and var.endswith('it')}"/>

<xacro:property name="allowed" value="${[1,2,3]}"/>
<xacro:if value="${1 in allowed}"/>

Loops

Loops are currently not directly supported. However, by recursively calling a macro, it is possible to unroll a loop and generate multiple similar XML snippets for a list of items:

<robot name="loop example" xmlns:xacro="http://www.ros.org/wiki/xacro">
	<xacro:macro name="loop" params="items:=^">
		<xacro:if value="${items}">
			<!-- pop first item from list -->
			<xacro:property name="item" value="${items.pop(0)}"/>

			<item>${item}</item>

			<!-- recursively call myself -->
			<xacro:loop/>
		</xacro:if>
	</xacro:macro>

	<!-- define the list of items to iterate -->
	<xacro:property name="items" value="${[1,2,3,4,5]}" />

	<xacro:loop items="${list(items)}"/>
	Passing a list copy, the original list is untouched: ${items}

	<xacro:loop items="${items}" />
	Passing the list directly, it is emptied: ${items}
</robot>

Comment handling

Removal of comments

All comments in front of xacro-specific tags are automatically removed during processing as they are considered to refer to the corresponding xacro command (e.g. explaining a macro call). If you want to keep a comment in the final document, separate it with an empty line from the macro:

<!-- Comments separated by a newline from a xacro-specific tag will be kept -->

<!-- Comments preceeding a xacro-specific tag will be removed -->
<!-- This includes multi-line comments -->
<xacro:macro name="my_macro" params="x">

Processing expressions in comments

By default, XML comments are not processed by xacro. However, comment evaluation can be enabled with a special comment:

<!-- xacro:eval-comments --> or <!-- xacro:eval-comments:on -->

Comment evaluation will stay active for all subsequent comments until:

  • the current XML tag's scope is left (or a new tag entered)
  • another tag or non-whitespace text is processed
  • it becomes explicitly disabled via: <!-- xacro:eval-comments:off -->

Example

A real-world example illustrating the use of extended xacro features can be found in https://github.com/ubi-agni/human_hand.