-
-
Notifications
You must be signed in to change notification settings - Fork 98
Home
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>
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 theparam_name:=value
syntax.
Default argument values can be define like so in the XML:
<xacro:arg name="param_name" default=""/>
In contrast toroslaunch
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.
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}
Available functions and symbols:- Symbols and functions from python's
math
module are directly accessible, e.g.pi
,sin
,exp
- Most builtin python functions and types are available within the
python
namespace, i.e. viapython.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 tostderr
.
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.
- Symbols and functions from python's
Note that due to limitations of the xacro parser, you cannot nest ${•}
expressions.
More specifically, dollar-braces expressions must not contain braces itself.
For this reason, it is not possible to use the python notation {"a": 1, "b": 2}
to declare a dictionary.
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}
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>
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 flavours:
- single-starred, e.g.
*block1
: the block is inserted as is - double-starred, e.g.
**block2
: only the block's content is inserted. It's root tag (and all its attributes) will be discarded.
Block parameters will be associated to provided blocks in positional order. Use 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.
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 fallback 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 same name). The pipe |
indicates to use the given fallback if the property is not defined in the outer scope.
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 definiton within the outer (or global) scope:
<xacro:property name="result" value='42' scope="parent" />
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"/>
Subsequently, you can access the namespaced macros and properties via:
other.macro
and other.property
.
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.