robot_description_builder/to_rdf/to_urdf.rs
1//! The infrastructure to describe a `Robot` in the Universal Robot Description Format (URDF).
2// TODO: EXPAND Module doc?, Matbe not
3
4use std::io::Cursor;
5
6use quick_xml::{
7 events::{BytesDecl, Event},
8 Writer,
9};
10
11use super::{make_xml_writer, XMLMode};
12use crate::cluster_objects::KinematicInterface;
13
14// FIXME: FIX CONFIG, MAYBE MAKE AN INTERNAL CONFIG TYPE
15/// A Configuration for the exporting of the description in the [URDF](http://wiki.ros.org/urdf) format.
16#[derive(Debug, PartialEq, Eq, Clone, Default)]
17pub struct URDFConfig {
18 /// Determines the way all `Material`s are displayed.
19 ///
20 /// For example either referenced or inline under specific conditions.
21 pub material_references: URDFMaterialReferences,
22 /// Determines how the current `Material` is displayed, internal use only.
23 ///
24 /// This field is overwritten by the logic of [`material_references`](URDFConfig).
25 pub direct_material_ref: URDFMaterialMode,
26 /// Determines the Target URDF-parser specification variant.
27 pub urdf_target: URDFTarget,
28 /// Determines the XML style.
29 pub xml_mode: XMLMode,
30}
31
32/// Determines how Referencable/Named `Material`s should be written.
33#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
34pub enum URDFMaterialReferences {
35 /// Write all named [`Material`s](crate::material::Material) at the top of the document and refer to them when used even if they are only used once.
36 /// This is the default.
37 ///
38 /// This mode is recommened to be used, since it makes it easy to change materials slightly by hand after the fact.
39 #[default]
40 AllNamedMaterialOnTop,
41 /// Only [`Material`s](crate::material::Material), that are used more then once, will be displayed at the top of the description.
42 /// These `Material`s are written as references where used.
43 ///
44 /// Other [`Material`s](crate::material::Material) (Unnamed and Named, but used once) will be written fully inside of the [`Link`](crate::link::Link).
45 OnlyMultiUseMaterials,
46 /// Always writes the full [`Material`](crate::material::Material) in the [`Link`](crate::link::Link). No matter if it is referenceable/named or not.
47 ///
48 /// Therefor no materials will be written at the top.
49 AlwaysInline,
50}
51
52/// Determines how the current `Material` is displayed. Can only be used internally.
53///
54/// This field is overwritten by logic controlled by [`URDFMaterialReferences`].
55#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
56pub enum URDFMaterialMode {
57 /// Display the `Material` fully, with content. This is the default.
58 #[default]
59 FullMaterial,
60 /// Display the `Material` as a reference.
61 Referenced,
62}
63
64/// A way to specify a target URDF reader.
65///
66/// This is needed, since not all URDF-Parser are created equally.
67/// They can have minor structuring preferences, which can be respected by the use of this Enum.
68///
69/// Currently, this only changes Transmission styles.
70#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
71#[non_exhaustive]
72pub enum URDFTarget {
73 /// The standard URDF configuration as specified by the [URDF specification](http://wiki.ros.org/urdf/XML). This is the default.
74 ///
75 /// This is compatible with [ROS Control](http://wiki.ros.org/ros_control)'s RobotHW way of [Hardware interfaces](crate::transmission::TransmissionHardwareInterface) for a [`Transmission`](crate::transmission::Transmission).
76 #[default]
77 Standard,
78 // Not fully implemented yet
79 /// A URDF configuration for [Gazebo Simulator](https://gazebosim.org/home).
80 ///
81 /// This specifies [Hardware interfaces](crate::transmission::TransmissionHardwareInterface) for a [`Transmission`](crate::transmission::Transmission) in the Gazebo style.
82 Gazebo,
83}
84
85/// A trait to allow parts of a `Robot` to be described in the URDF format.
86pub trait ToURDF {
87 /// Represents the element as in URDF format.
88 fn to_urdf(
89 &self,
90 writer: &mut Writer<Cursor<Vec<u8>>>,
91 urdf_config: &URDFConfig,
92 ) -> Result<(), quick_xml::Error>;
93}
94
95/// A function to represent a `KinematicInterface` implementor in the URDF format.
96///
97/// This function should be used to generate the descriptions.
98///
99/// # Example
100/// Reads and writes are hidden for brevity.
101/// ```
102/// # use robot_description_builder::{
103/// # link_data::{geometry::*, Visual},
104/// # material::MaterialDescriptor,
105/// # prelude::*,
106/// # to_rdf::{
107/// # to_urdf::{to_urdf, URDFConfig},
108/// # xml_writer_to_string, XMLMode
109/// # },
110/// # Link, Robot, SmartJointBuilder, Transform,
111/// # };
112/// #
113/// let white_material = MaterialDescriptor::new_rgb(1., 1., 1.).named("white");
114///
115/// let right_leg_link = Link::builder("[\\[right]\\]_leg").add_visual(
116/// Visual::builder(BoxGeometry::new(0.6, 0.1, 0.2))
117/// .materialized(white_material.clone())
118/// .transformed(Transform::new_translation(0., 0., -0.3)),
119/// );
120///
121/// let right_leg: Robot = right_leg_link.build_tree().to_robot("Right_Leg_bot");
122///
123/// let right_base_link = Link::builder("[\\[right]\\]_base")
124/// .add_visual(Visual::builder(BoxGeometry::new(0.4, 0.1, 0.1)).materialized(white_material));
125///
126/// let right_base_joint = SmartJointBuilder::new_fixed("[\\[right]\\]_base_joint")
127/// .add_transform(Transform::new_translation(0., 0., -0.6));
128///
129/// right_leg
130/// .get_root_link()
131/// .write()
132/// .unwrap()
133/// .try_attach_child(right_base_joint, right_base_link)
134/// .unwrap();
135///
136/// assert_eq!(
137/// xml_writer_to_string(
138/// to_urdf(
139/// &right_leg,
140/// URDFConfig{
141/// xml_mode: XMLMode::Indent(' ', 2),
142/// ..Default::default()
143/// }).unwrap()),
144/// r#"<?xml version="1.0"?>
145/// <robot name="Right_Leg_bot">
146/// <material name="white">
147/// <color rgba="1 1 1 1"/>
148/// </material>
149/// <link name="[[right]]_leg">
150/// <visual>
151/// <origin xyz="0 0 -0.3"/>
152/// <geometry>
153/// <box size="0.6 0.1 0.2"/>
154/// </geometry>
155/// <material name="white"/>
156/// </visual>
157/// </link>
158/// <joint name="[[right]]_base_joint" type="fixed">
159/// <origin xyz="0 0 -0.6"/>
160/// <parent link="[[right]]_leg"/>
161/// <child link="[[right]]_base"/>
162/// </joint>
163/// <link name="[[right]]_base">
164/// <visual>
165/// <geometry>
166/// <box size="0.4 0.1 0.1"/>
167/// </geometry>
168/// <material name="white"/>
169/// </visual>
170/// </link>
171/// </robot>"#
172/// )
173/// ```
174pub fn to_urdf(
175 tree: &(impl KinematicInterface + ToURDF),
176 urdf_config: URDFConfig,
177) -> Result<Writer<Cursor<Vec<u8>>>, quick_xml::Error> {
178 let mut writer = make_xml_writer(urdf_config.xml_mode);
179
180 writer.write_bom()?;
181 writer.write_event(&Event::Decl(BytesDecl::new("1.0", None, None)))?;
182 tree.to_urdf(&mut writer, &urdf_config)?;
183 Ok(writer)
184}
185
186// This does not work due to ElementWriter.write_inner() expecting a closure that returns `quick_xml::Error`
187// /// TODO DOCS
188// /// TODO DOES THIS COMPLY WITH THE NAMING CONVENTION
189// /// THIS DOES NOT WORK DO TO CLOSURES
190// #[derive(Debug, Error)]
191// pub enum ToURDFError {
192// #[error(transparent)]
193// XML(#[from] quick_xml::Error),
194// }