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// }