AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
tree_builder.cpp
Go to the documentation of this file.
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
16
17#include "behaviortree_cpp/xml_parsing.h"
20
22{
23
24TreeBuilder::TreeBuilder(std::shared_ptr<BT::BehaviorTreeFactory> factory_ptr) : factory_ptr_(factory_ptr)
25{
26 doc_.Parse("<root BTCPP_format=\"4\"></root>");
27}
28
29TreeBuilder& TreeBuilder::registerNodePlugins(rclcpp::Node::SharedPtr node_ptr, const NodeManifest& node_manifest,
30 NodePluginClassLoader& tree_node_loader, bool override)
31{
32 const auto registered_nodes = getRegisteredNodes();
33 for (const auto& [node_name, params] : node_manifest.getInternalMap())
34 {
35 // If the node is already registered
36 if (registered_nodes.find(node_name) != registered_nodes.end())
37 {
38 if (override)
39 {
40 factory_ptr_->unregisterBuilder(node_name);
41 }
42 else
43 {
44 throw exceptions::TreeBuildError("Node '" + node_name +
45 "' is already registered. Set override = true to allow for "
46 "overriding previously registered nodes.");
47 }
48 }
49
50 // Check if the class we search for is actually available with the loader.
51 if (!tree_node_loader.isClassAvailable(params.class_name))
52 {
53 throw exceptions::TreeBuildError("Node '" + node_name + " (Class: " + params.class_name +
54 ")' cannot be loaded, because the class name is not known to the class loader. "
55 "Make sure that it's spelled correctly and registered by calling "
56 "auto_apms_behavior_tree_register_nodes() in the CMakeLists.txt of the "
57 "corresponding package.");
58 }
59
60 RCLCPP_DEBUG(node_ptr->get_logger(), "Loading behavior tree node '%s' (Class: %s) from library %s.",
61 node_name.c_str(), params.class_name.c_str(),
62 tree_node_loader.getClassLibraryPath(params.class_name).c_str());
63
64 pluginlib::UniquePtr<NodeRegistrationInterface> plugin_instance;
65 try
66 {
67 plugin_instance = tree_node_loader.createUniqueInstance(params.class_name);
68 }
69 catch (const std::exception& e)
70 {
71 throw exceptions::TreeBuildError("Failed to create an instance of node '" + node_name +
72 " (Class: " + params.class_name +
73 ")'. Remember that the AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE "
74 "macro must be called in the source file for the node class to be discoverable. "
75 "Error message: " +
76 e.what() + ".");
77 }
78
79 try
80 {
81 if (plugin_instance->requiresROSNodeParams())
82 {
83 RosNodeContext ros_node_context(node_ptr, params);
84 plugin_instance->registerWithBehaviorTreeFactory(*factory_ptr_, node_name, &ros_node_context);
85 }
86 else
87 {
88 plugin_instance->registerWithBehaviorTreeFactory(*factory_ptr_, node_name);
89 }
90 }
91 catch (const std::exception& e)
92 {
93 throw exceptions::TreeBuildError("Failed to register node '" + node_name + " (Class: " + params.class_name +
94 ")': " + e.what() + ".");
95 }
96 }
97 return *this;
98}
99
100TreeBuilder& TreeBuilder::registerNodePlugins(rclcpp::Node::SharedPtr node_ptr, const NodeManifest& node_manifest,
101 bool override)
102{
104 return registerNodePlugins(node_ptr, node_manifest, loader, override);
105}
106
107TreeBuilder& TreeBuilder::addTreeFromXMLDocument(const tinyxml2::XMLDocument& doc)
108{
109 // Overwrite main tree in current document
110 auto other_root = doc.RootElement();
111 if (!other_root)
112 throw exceptions::TreeBuildError("Cannot merge tree document: other_root is nullptr.");
113 if (const auto name = other_root->Attribute(MAIN_TREE_ATTRIBUTE_NAME.c_str()))
114 setMainTreeName(name);
115
116 const auto this_tree_names = getTreeNames(doc_);
117 const auto other_tree_names = getTreeNames(doc);
118
119 // Verify that there are no duplicate tree names
120 std::vector<std::string> same_names;
121 std::set_intersection(this_tree_names.begin(), this_tree_names.end(), other_tree_names.begin(),
122 other_tree_names.end(), std::inserter(same_names, same_names.begin()));
123 if (!same_names.empty())
124 {
125 std::ostringstream oss;
126 std::copy(same_names.begin(), same_names.end() - 1, std::ostream_iterator<std::string>(oss, ", "));
127 oss << same_names.back();
129 "Cannot merge tree document: The following trees are already defined: " + oss.str() + ".");
130 }
131
132 // Iterate over all the children of the new document's root element
133 auto this_root = doc_.RootElement();
134 for (const tinyxml2::XMLElement* child = other_root->FirstChildElement(); child != nullptr;
135 child = child->NextSiblingElement())
136 {
137 // Clone the child element to the original document
138 auto copied_child = child->DeepClone(&doc_);
139 // Append the copied child to the original document's root
140 this_root->InsertEndChild(copied_child);
141 }
142
143 try
144 {
145 // Verify the structure of the new tree document and that all mentioned nodes are registered with the factory
146 BT::VerifyXML(writeTreeXMLToString(), getRegisteredNodes());
147 }
148 catch (const BT::RuntimeError& e)
149 {
150 throw exceptions::TreeBuildError(e.what());
151 }
152 return *this;
153}
154
155TreeBuilder& TreeBuilder::addTreeFromString(const std::string& tree_str)
156{
157 tinyxml2::XMLDocument new_doc;
158 if (new_doc.Parse(tree_str.c_str()) != tinyxml2::XMLError::XML_SUCCESS)
159 {
160 throw exceptions::TreeBuildError("Cannot add tree: " + std::string(new_doc.ErrorStr()));
161 }
162 return addTreeFromXMLDocument(new_doc);
163}
164
165TreeBuilder& TreeBuilder::addTreeFromFile(const std::string& tree_file_path)
166{
167 tinyxml2::XMLDocument new_doc;
168 if (new_doc.LoadFile(tree_file_path.c_str()) != tinyxml2::XMLError::XML_SUCCESS)
169 {
170 throw exceptions::TreeBuildError("Cannot add tree: " + std::string(new_doc.ErrorStr()));
171 }
172 return addTreeFromXMLDocument(new_doc);
173}
174
175TreeBuilder& TreeBuilder::addTreeFromResource(const TreeResource& resource, rclcpp::Node::SharedPtr node_ptr)
176{
178 return addTreeFromFile(resource.tree_file_path);
179}
180
182{
183 if (const auto main_tree_name = doc_.RootElement()->Attribute(MAIN_TREE_ATTRIBUTE_NAME.c_str()))
184 return main_tree_name;
185 return "";
186}
187
188TreeBuilder& TreeBuilder::setMainTreeName(const std::string& main_tree_name)
189{
190 if (!main_tree_name.empty())
191 doc_.RootElement()->SetAttribute(MAIN_TREE_ATTRIBUTE_NAME.c_str(), main_tree_name.c_str());
192 return *this;
193}
194
196{
197 return writeXMLDocumentToString(doc_);
198}
199
200std::unordered_map<std::string, BT::NodeType> TreeBuilder::getRegisteredNodes()
201{
202 std::unordered_map<std::string, BT::NodeType> registered_nodes;
203 for (const auto& it : factory_ptr_->manifests())
204 registered_nodes.insert({ it.first, it.second.type });
205 return registered_nodes;
206}
207
208Tree TreeBuilder::buildTree(const std::string main_tree_name, TreeBlackboardSharedPtr root_bb_ptr)
209{
210 setMainTreeName(main_tree_name);
211 Tree tree;
212 try
213 {
214 factory_ptr_->registerBehaviorTreeFromText(writeTreeXMLToString());
215 tree = factory_ptr_->createTree(getMainTreeName(), root_bb_ptr);
216 factory_ptr_->clearRegisteredBehaviorTrees();
217 }
218 catch (const std::exception& e)
219 {
220 throw exceptions::TreeBuildError(e.what());
221 }
222 return tree;
223}
224
226{
227 return buildTree("", root_bb_ptr);
228}
229
230std::set<std::string> TreeBuilder::getTreeNames(const tinyxml2::XMLDocument& doc)
231{
232 std::set<std::string> names;
233 if (const auto root = doc.RootElement())
234 {
235 for (const tinyxml2::XMLElement* child = root->FirstChildElement(); child != nullptr;
236 child = child->NextSiblingElement())
237 {
238 if (TREE_ELEMENT_NAME.compare(child->Name()) == 0)
239 {
240 if (const auto name = child->Attribute(TREE_ID_ATTRIBUTE_NAME.c_str()))
241 {
242 names.insert(name);
243 }
244 else
245 {
246 throw exceptions::TreeBuildError("Cannot get tree name, because required attribute '" +
247 TREE_ID_ATTRIBUTE_NAME + "' is missing.");
248 }
249 }
250 }
251 }
252 return names;
253}
254
255std::string TreeBuilder::writeXMLDocumentToString(const tinyxml2::XMLDocument& doc)
256{
257 tinyxml2::XMLPrinter printer;
258 doc.Print(&printer);
259 return printer.CStr();
260}
261
262} // namespace auto_apms_behavior_tree
Data structure for resource lookup data and configuration parameters required for loading and registe...
static NodeManifest fromFile(const std::string &file_path)
Create a node plugin manifest from a file.
const ParamMap & getInternalMap() const
Version of pluginlib::ClassLoader specifically for loading installed behavior tree node plugins.
Class for creating behavior trees according to the builder design pattern.
TreeBuilder & addTreeFromString(const std::string &tree_str)
TreeBuilder(std::shared_ptr< BT::BehaviorTreeFactory > factory_ptr=std::make_shared< BT::BehaviorTreeFactory >())
TreeBuilder & addTreeFromFile(const std::string &tree_file_path)
static std::set< std::string > getTreeNames(const tinyxml2::XMLDocument &doc)
std::unordered_map< std::string, BT::NodeType > getRegisteredNodes()
TreeBuilder & addTreeFromResource(const TreeResource &resource, rclcpp::Node::SharedPtr node_ptr)
static std::string writeXMLDocumentToString(const tinyxml2::XMLDocument &doc)
TreeBuilder & addTreeFromXMLDocument(const tinyxml2::XMLDocument &doc)
TreeBuilder & setMainTreeName(const std::string &main_tree_name)
TreeBuilder & registerNodePlugins(rclcpp::Node::SharedPtr node_ptr, const NodeManifest &node_manifest, NodePluginClassLoader &tree_node_loader, bool override=false)
Load behavior tree node plugins and register with behavior tree factory.
Tree buildTree(const std::string main_tree_name, TreeBlackboardSharedPtr root_bb_ptr=TreeBlackboard::create())
std::shared_ptr< TreeBlackboard > TreeBlackboardSharedPtr
Struct containing behavior tree resource data.