15#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
17#include "auto_apms_behavior_tree_core/builder.hpp"
18#include "auto_apms_behavior_tree_core/exceptions.hpp"
19#include "auto_apms_behavior_tree_core/node/node_model_type.hpp"
20#include "auto_apms_behavior_tree_core/tree/tree_resource.hpp"
21#include "auto_apms_util/container.hpp"
22#include "auto_apms_util/logging.hpp"
23#include "auto_apms_util/string.hpp"
24#include "behaviortree_cpp/xml_parsing.h"
29std::vector<std::string> getAllTreeNamesImpl(
const tinyxml2::XMLDocument & doc)
31 std::vector<std::string> names;
32 if (
const tinyxml2::XMLElement * root = doc.RootElement()) {
33 if (strcmp(root->Name(), TreeDocument::ROOT_ELEMENT_NAME) == 0) {
35 for (
const tinyxml2::XMLElement * child = root->FirstChildElement(); child !=
nullptr;
36 child = child->NextSiblingElement()) {
37 if (strcmp(TreeDocument::TREE_ELEMENT_NAME, child->Name()) == 0) {
38 if (
const char * name = child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) {
39 names.push_back(name);
41 throw exceptions::TreeDocumentError(
42 "Cannot get tree name, because required attribute '" +
43 std::string(TreeDocument::TREE_NAME_ATTRIBUTE_NAME) +
"' is missing.");
47 }
else if (strcmp(root->Name(), TreeDocument::TREE_ELEMENT_NAME) == 0) {
49 if (
const char * name = root->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) names.push_back(name);
59 throw exceptions::TreeDocumentError(
"Cannot create an instance of NodeElement with doc_ptr=nullptr.");
62 throw exceptions::TreeDocumentError(
"Cannot create an instance of NodeElement with ele_ptr=nullptr.");
65 NodeModelMap::const_iterator it =
model.find(
ele_ptr_->Name());
66 std::vector<NodePortInfo> port_infos_str_vec;
67 if (it !=
model.end()) port_infos_str_vec = it->second.port_infos;
74 if (!port_infos_str_vec.empty()) {
75 for (const NodePortInfo & port_info : port_infos_str_vec) {
76 port_names_.push_back(port_info.port_name);
77 if (!port_info.port_default.empty()) port_default_values_[port_info.port_name] = port_info.port_default;
83 for (
const auto & [name, val] : port_default_values_) {
84 if (existing_port_values.find(name) == existing_port_values.end()) {
85 ele_ptr_->SetAttribute(name.c_str(), val.c_str());
93 XMLElement * other_ele = other.
ele_ptr_;
96 other_ele = other_ele->DeepClone(
doc_ptr_)->ToElement();
98 XMLElement * prev =
ele_ptr_->PreviousSiblingElement();
100 ele_ptr_->Parent()->InsertAfterChild(
ele_ptr_->PreviousSibling(), other_ele);
102 ele_ptr_->Parent()->InsertFirstChild(other_ele);
109 port_names_ = other.port_names_;
110 port_default_values_ = other.port_default_values_;
116bool TreeDocument::NodeElement::operator!=(
const NodeElement & other)
const {
return !this->operator==(other); }
119 const std::string & name,
const NodeElement * before_this)
121 if (
const std::set<std::string> names =
doc_ptr_->getRegisteredNodeNames(
true); names.find(name) == names.end()) {
122 throw exceptions::TreeDocumentError(
123 "Cannot insert unkown node <" + name +
124 ">. Before inserting a new node, the associated document must register the corresponding behavior tree "
125 "node. Consider using a signature of insertNode() that does this automatically.");
127 XMLElement * ele =
doc_ptr_->NewElement(name.c_str());
128 return insertBeforeImpl(before_this, ele);
136 if (
const std::set<std::string> names =
doc_ptr_->getRegisteredNodeNames(
false); names.find(name) == names.end()) {
143 const std::string & tree_name,
const NodeElement * before_this)
145 if (!
doc_ptr_->hasTreeName(tree_name)) {
146 throw exceptions::TreeDocumentError(
147 "Cannot insert subtree node for tree '" + tree_name +
"' because no tree with that name exists.");
150 ele.
ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
156 const std::string tree_name = tree.
getName();
157 if (
doc_ptr_->hasTreeName(tree_name)) {
159 if (
doc_ptr_->getTree(tree_name) != tree) {
160 throw exceptions::TreeDocumentError(
161 "Cannot insert subtree node using the provided tree element, because another tree with name '" + tree_name +
162 "' already exists.");
174 const XMLElement * root_child = tree.
ele_ptr_->FirstChildElement();
176 throw exceptions::TreeDocumentError(
179 if (root_child->NextSibling()) {
180 throw exceptions::TreeDocumentError(
181 "Cannot insert tree element '" + tree.
getFullyQualifiedName() +
"' because it has more than one child node.");
190 const std::vector<std::string> other_tree_names = doc.
getAllTreeNames();
191 if (other_tree_names.empty()) {
192 throw exceptions::TreeDocumentError(
193 "Cannot insert tree '" + tree_name +
"' because document has no <" + TREE_ELEMENT_NAME +
"> elements.");
196 throw exceptions::TreeDocumentError(
197 "Cannot insert tree '" + tree_name +
"' because document doesn't specify a tree with that name.");
201 std::set<std::string> required_tree_names = {tree_name};
207 doc.DeepCopy(&temp_doc);
208 const TreeElement other_tree(&temp_doc, temp_doc.getXMLElementForTreeWithName(tree_name));
210 collect_dependency_tree_names = [&required_tree_names, &collect_dependency_tree_names](
const NodeElement & ele) {
211 if (ele.getRegistrationName() == SUBTREE_ELEMENT_NAME) {
212 if (
const char * name = ele.ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
214 required_tree_names.insert(name);
217 ele.doc_ptr_->getTree(name).deepApplyConst(collect_dependency_tree_names);
219 throw exceptions::TreeDocumentError(
"Subtree element has no name attribute.");
229 return available_names.find(ele.getRegistrationName()) == available_names.end();
231 for (
const std::string & name : required_tree_names) {
232 const NodeElement ele(&temp_doc, temp_doc.getXMLElementForTreeWithName(name)->FirstChildElement());
233 if (
const std::vector<NodeElement> found = ele.
deepApplyConst(apply); !found.empty()) {
234 std::vector<std::string> names;
236 throw exceptions::TreeDocumentError(
237 "Cannot insert tree '" + tree_name +
"' because the following nodes found in tree '" + name +
238 "' are unkown to the builder:\n\t- " + auto_apms_util::join(names,
"\n\t- "));
244 required_tree_names.erase(tree_name);
245 for (
const std::string & name : other_tree_names) {
246 if (required_tree_names.find(name) == required_tree_names.end()) {
251 doc_ptr_->mergeTreeDocument(
static_cast<const XMLDocument &
>(temp_doc),
false);
254 return insertBeforeImpl(
255 before_this, doc.getXMLElementForTreeWithName(tree_name)->FirstChildElement()->DeepClone(
doc_ptr_)->ToElement());
265 const std::string & tree_str,
const std::string & tree_name,
const NodeElement * before_this)
273 const std::string & tree_str,
const NodeElement * before_this)
281 const std::string & path,
const std::string & tree_name,
const NodeElement * before_this)
289 const std::string & path,
const NodeElement * before_this)
300 insert_doc.
mergeFile(resource.tree_file_path_);
319 const std::string & registration_name,
const std::string & instance_name)
const
325 if (registration_name.empty())
return ele.getName() == instance_name;
326 if (instance_name.empty())
return ele.getRegistrationName() == registration_name;
327 return ele.getRegistrationName() == registration_name && ele.getName() == instance_name;
329 if (
const std::vector<NodeElement> found =
deepApplyConst(apply); !found.empty())
return found[0];
332 throw exceptions::TreeDocumentError(
333 "Cannot find a child node that matches the search arguments (registration_name: '" + registration_name +
338 const std::string & registration_name,
const std::string & instance_name)
355 for (
const tinyxml2::XMLAttribute * attr =
ele_ptr_->FirstAttribute(); attr !=
nullptr; attr = attr->Next()) {
357 values[attr_name] = attr->Value();
366 std::vector<std::string> unkown_keys;
367 for (
const auto & [key, _] : port_values) {
370 if (!unkown_keys.empty()) {
371 throw exceptions::TreeDocumentError(
372 "Cannot set ports. According to the node model, the following ports are not implemented by '" +
373 std::string(
ele_ptr_->Name()) +
"': [ " + auto_apms_util::join(unkown_keys,
", ") +
" ].");
377 for (
const auto & [key, val] : port_values) {
378 ele_ptr_->SetAttribute(key.c_str(), val.c_str());
385 for (
const std::string & name : port_names_) {
386 PortValues::const_iterator it = port_default_values_.find(name);
387 if (it != port_default_values_.end()) {
388 ele_ptr_->SetAttribute(it->first.c_str(), it->second.c_str());
390 ele_ptr_->DeleteAttribute(name.c_str());
398 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.
str().c_str());
404 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.
str().c_str());
410 ele_ptr_->SetAttribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME, instance_name.c_str());
418 if (
const char * name =
ele_ptr_->Attribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME))
return name;
425 std::string instance_name =
getName();
426 if (registration_name == instance_name)
return registration_name;
427 return instance_name +
" (" + registration_name +
")";
435 std::vector<NodeElement> found;
436 deepApplyImpl(*
this, apply_callback, found);
442 std::vector<NodeElement> found;
443 deepApplyImpl(*
this, apply_callback, found);
448 const NodeElement * before_this, XMLElement * add_this)
451 XMLElement * prev =
nullptr;
452 XMLElement * curr = ele_ptr_->FirstChildElement();
458 if (curr == before_this->
ele_ptr_) {
463 curr = curr->NextSiblingElement();
467 ele_ptr_->InsertAfterChild(prev, add_this);
469 throw exceptions::TreeDocumentError(
471 getFullyQualifiedName() +
".");
474 ele_ptr_->InsertFirstChild(add_this);
477 ele_ptr_->InsertEndChild(add_this);
482void TreeDocument::NodeElement::deepApplyImpl(
483 const NodeElement & parent, ConstDeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
485 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child !=
nullptr;
486 child = child->NextSiblingElement()) {
487 const NodeElement child_ele(parent.doc_ptr_, child);
490 if (apply_callback(child_ele)) vec.push_back(child_ele);
493 deepApplyImpl(child_ele, apply_callback, vec);
497void TreeDocument::NodeElement::deepApplyImpl(
498 NodeElement & parent, DeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
500 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child !=
nullptr;
501 child = child->NextSiblingElement()) {
505 if (apply_callback(child_ele)) vec.push_back(child_ele);
508 deepApplyImpl(child_ele, apply_callback, vec);
514 if (!ele_ptr->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
515 throw exceptions::TreeDocumentError(
516 "Cannot create tree element without a '" + std::string(TREE_NAME_ATTRIBUTE_NAME) +
"' attribute.");
522 const std::string other_tree_name = other.
getName();
526 throw exceptions::TreeDocumentError(
527 "Cannot copy tree '" + other.
getName() +
"' because another tree with this name already exists.");
535 ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
541 if (
const char * name =
ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME))
return name;
556 const bool is_native_node =
doc_ptr_->native_node_names_.find(name) !=
doc_ptr_->native_node_names_.end();
557 if (!is_native_node && !m.
contains(name)) {
558 if (!
doc_ptr_->registered_nodes_manifest_.contains(name)) {
559 throw exceptions::NodeManifestError(
560 "Cannot assemble the required node manifest for tree '" +
getName() +
561 "' since there are no registration options for node '" + name +
"'.");
563 m.
add(name,
doc_ptr_->registered_nodes_manifest_[name]);
572 TreeDocument doc(
doc_ptr_->format_version_,
doc_ptr_->tree_node_loader_ptr_);
578 XMLDocument tree_doc;
579 tree_doc.InsertEndChild(
ele_ptr_->DeepClone(&tree_doc));
580 tinyxml2::XMLPrinter printer;
581 tree_doc.Print(&printer);
582 return printer.CStr();
586 const std::string & registration_name,
const std::string & instance_name)
601: XMLDocument(true, tinyxml2::PRESERVE_WHITESPACE),
603 native_node_names_(BT::BehaviorTreeFactory().builtinNodes()),
604 format_version_(format_version),
605 tree_node_loader_ptr_(tree_node_loader),
606 registered_nodes_manifest_(),
608 logger_(rclcpp::get_logger(LOGGER_NAME))
616 const XMLElement * other_root = other.RootElement();
618 throw exceptions::TreeDocumentError(
"Cannot merge tree documents: other_root is nullptr.");
621 auto verify_tree_structure = [](
const XMLElement * tree_ele) {
622 const char * tree_id = tree_ele->Attribute(TREE_NAME_ATTRIBUTE_NAME);
624 throw exceptions::TreeDocumentError(
625 "Cannot merge tree document: Found a <" + std::string(TREE_ELEMENT_NAME) +
626 "> element that doesn't specify the required attribute '" + TREE_NAME_ATTRIBUTE_NAME +
"'.");
628 const XMLElement * tree_root_child = tree_ele->FirstChildElement();
629 if (!tree_root_child) {
630 throw exceptions::TreeDocumentError(
631 "Cannot merge tree document: Tree '" + std::string(tree_id) +
"' has no child nodes.");
633 if (tree_root_child->NextSibling()) {
634 throw exceptions::TreeDocumentError(
635 "Cannot merge tree document: Tree '" + std::string(tree_id) +
"' has more than one child node.");
639 const std::vector<std::string> other_tree_names = getAllTreeNamesImpl(other);
643 if (!common_tree_names.empty()) {
644 throw exceptions::TreeDocumentError(
645 "Cannot merge tree document: The following trees are already defined: [ " +
646 auto_apms_util::join(common_tree_names,
", ") +
" ].");
649 if (strcmp(other_root->Name(), ROOT_ELEMENT_NAME) == 0) {
651 if (
const char * ver = other_root->Attribute(BTCPP_FORMAT_ATTRIBUTE_NAME)) {
652 if (std::string(ver) != format_version_) {
653 throw exceptions::TreeDocumentError(
654 "Cannot merge tree document: Format of other document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
655 ver +
") is not compatible with this document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
656 format_version_ +
").");
659 throw exceptions::TreeDocumentError(
660 "Cannot merge tree document: Root element of other document doesn't have required attribute '" +
661 std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
"'.");
665 for (
const XMLElement * child = other_root->FirstChildElement(); child !=
nullptr;
666 child = child->NextSiblingElement()) {
668 if (strcmp(child->Name(), TREE_NODE_MODEL_ELEMENT_NAME) == 0)
continue;
671 if (strcmp(child->Name(), TREE_ELEMENT_NAME) == 0) {
672 verify_tree_structure(child);
676 RootElement()->InsertEndChild(child->DeepClone(
this));
679 if (adopt_root_tree) {
682 if (
const char * name = other_root->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
setRootTreeName(name);
684 }
else if (strcmp(other_root->Name(), TREE_ELEMENT_NAME) == 0) {
687 verify_tree_structure(other_root);
690 RootElement()->InsertEndChild(other_root->DeepClone(
this));
692 throw exceptions::TreeDocumentError(
693 "Cannot merge tree document: Root element of other document must either be <" + std::string(ROOT_ELEMENT_NAME) +
694 "> or <" + TREE_ELEMENT_NAME +
">.");
697 if (adopt_root_tree && other_tree_names.size() == 1) {
708 return mergeTreeDocument(
static_cast<const XMLDocument &
>(other), adopt_root_tree);
713 XMLDocument other_doc;
714 if (other_doc.Parse(tree_str.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
715 throw exceptions::TreeDocumentError(
"Cannot merge tree document from string: " + std::string(other_doc.ErrorStr()));
722 XMLDocument other_doc;
723 if (other_doc.LoadFile(path.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
724 throw exceptions::TreeDocumentError(
"Cannot create tree document from file " + path +
": " + other_doc.ErrorStr());
732 return mergeFile(resource.tree_file_path_, adopt_root_tree);
737 XMLDocument tree_doc;
738 tree_doc.InsertEndChild(tree.
ele_ptr_->DeepClone(&tree_doc));
751 if (tree_name.empty()) {
752 throw exceptions::TreeDocumentError(
"Cannot create a new tree with an empty name");
755 throw exceptions::TreeDocumentError(
756 "Cannot create a new tree with name '" + tree_name +
"' because it already exists.");
758 TreeDocument::XMLElement * new_ele = RootElement()->InsertNewChildElement(TreeDocument::TREE_ELEMENT_NAME);
759 new_ele->SetAttribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
770 std::string name(tree_name);
774 }
else if (
const std::vector<std::string> names = other.
getAllTreeNames(); names.size() == 1) {
777 throw exceptions::TreeDocumentError(
778 "Failed to create new tree element from another document because argument tree_name was omitted and it was not "
779 "possible to determine the root tree automatically.");
789 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
796 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
802 const TreeResource & resource,
const std::string & tree_name)
804 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
805 new_doc.
mergeFile(resource.tree_file_path_);
819 return TreeElement(
this, getXMLElementForTreeWithName(tree_name));
824 if (tree_name.empty()) {
825 throw exceptions::TreeDocumentError(
"Cannot set root tree name with empty string.");
828 throw exceptions::TreeDocumentError(
829 "Cannot make tree with name '" + tree_name +
"' the root tree because it doesn't exist.");
831 RootElement()->SetAttribute(ROOT_TREE_ATTRIBUTE_NAME, tree_name.c_str());
837 if (RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
return true;
843 if (
const auto tree_name = RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
return tree_name;
844 throw exceptions::TreeDocumentError(
845 "Cannot get root tree name because the document's root element has no attribute '" +
846 std::string(ROOT_TREE_ATTRIBUTE_NAME) +
"'.");
853 RootElement()->DeleteChild(getXMLElementForTreeWithName(tree_name));
864 std::set<std::string> all_registration_names;
865 for (
const auto & [name, _] : tree_node_manifest.
map()) all_registration_names.insert(name);
866 if (
const std::set<std::string> common =
869 throw exceptions::TreeDocumentError(
870 "Found reserved node registration names in the node manifest. The following names are not allowed, because "
871 "they refer to native behavior tree nodes: [ " +
872 auto_apms_util::join(std::vector<std::string>(common.begin(), common.end()),
", ") +
" ].");
875 for (
const auto & [node_name, params] : tree_node_manifest.
map()) {
877 if (registered_nodes_manifest_.contains(node_name)) {
880 if (registered_nodes_manifest_[node_name].class_name == params.class_name) {
885 }
else if (
override) {
888 factory_.unregisterBuilder(node_name);
891 throw exceptions::TreeDocumentError(
892 "Tried to register node '" + node_name +
"' (Class : " + params.class_name +
893 ") which is already known to the builder, but under a different class name (" +
894 registered_nodes_manifest_[node_name].class_name +
895 "). You must explicitly set override=true to allow for overriding previously registered nodes.");
900 if (!tree_node_loader_ptr_->isClassAvailable(params.class_name)) {
901 if (all_node_classes_package_map_.find(params.class_name) == all_node_classes_package_map_.end()) {
902 throw exceptions::TreeDocumentError(
903 "Node '" + node_name +
" (" + params.class_name +
904 ")' cannot be registered, because the class name is not known to the class loader. "
905 "Make sure that it's spelled correctly and registered by calling "
906 "auto_apms_behavior_tree_declare_nodes() in the CMakeLists.txt of the "
907 "corresponding package.");
909 throw exceptions::TreeDocumentError(
910 "Node '" + node_name +
" (" + params.class_name +
911 ")' cannot be registered, because the corresponding resource belongs to excluded package '" +
912 all_node_classes_package_map_.at(params.class_name) +
"'.");
915 pluginlib::UniquePtr<NodeRegistrationInterface> plugin_instance;
917 plugin_instance = tree_node_loader_ptr_->createUniqueInstance(params.class_name);
918 }
catch (
const pluginlib::CreateClassException & e) {
919 throw pluginlib::CreateClassException(
920 "Failed to create an instance of node '" + node_name +
" (" + params.class_name +
921 ")'. Remember that the AUTO_APMS_BEHAVIOR_TREE_DECLARE_NODE "
922 "macro must be called in the source file for the node class to be discoverable. "
928 if (plugin_instance->requiresRosNodeContext()) {
929 if (only_non_ros_nodes_) {
930 throw exceptions::NodeRegistrationError(
931 "Node '" + node_name +
932 "' relies on ROS 2 functionality but this instance only allows to use non-ROS nodes.");
935 ros_node_wptr_.lock(), tree_node_waitables_callback_group_wptr_.lock(),
936 tree_node_waitables_executor_wptr_.lock(), params);
937 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
939 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name);
941 }
catch (
const std::exception & e) {
942 throw exceptions::NodeRegistrationError(
943 "Cannot register node '" + node_name +
" (" + params.class_name +
")': " + e.what() +
".");
945 registered_nodes_manifest_.add(node_name, params);
952 std::set<std::string> names;
953 if (include_native) names = native_node_names_;
954 for (
const auto & [name, _] : registered_nodes_manifest_.map()) names.insert(name);
963 XMLElement * ptr =
const_cast<XMLElement *
>(getXMLElementForTreeWithName(tree_name));
972 tinyxml2::XMLDocument model_doc;
974 model_doc.Parse(BT::writeTreeNodesModelXML(factory_, include_native).c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
975 throw exceptions::TreeDocumentError(
976 "Error parsing the model of the currently registered nodes: " + std::string(model_doc.ErrorStr()));
982 const tinyxml2::XMLElement * model_child =
983 model_doc.RootElement()->FirstChildElement(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME);
986 tinyxml2::XMLNode * copied_child = model_child->DeepClone(
this);
989 RootElement()->InsertEndChild(copied_child);
995 const tinyxml2::XMLElement * root = doc.RootElement();
997 throw exceptions::TreeDocumentError(
"Node model document has no root element.");
999 if (
const char * ver = root->Attribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME)) {
1000 const std::string expected_format = TreeDocument::BTCPP_FORMAT_DEFAULT_VERSION;
1001 if (std::string(ver) != expected_format) {
1002 throw exceptions::TreeDocumentError(
1003 "Cannot parse node model document: Format of model document (" +
1004 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " + ver +
1005 ") doesn't comply with the expected format (" + std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
1006 expected_format +
").");
1009 throw exceptions::TreeDocumentError(
1010 "Cannot parse node model document: Root element of model document doesn't have required attribute '" +
1011 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
"'.");
1013 tinyxml2::XMLElement * model_ele = doc.RootElement()->FirstChildElement(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME);
1015 throw exceptions::TreeDocumentError(
1016 "Element <" + std::string(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME) +
1017 "> doesn't exist in node model document.");
1021 for (tinyxml2::XMLElement * ele = model_ele->FirstChildElement(); ele !=
nullptr; ele = ele->NextSiblingElement()) {
1022 const char * node_name = ele->Attribute(
"ID");
1024 throw exceptions::TreeDocumentError(
1025 "Element '" + std::string(ele->Name()) +
"' in node model document is missing the required attribute 'ID'");
1028 model.type = BT::convertFromString<BT::NodeType>(ele->Name());
1029 for (
const tinyxml2::XMLElement * port_ele = ele->FirstChildElement(); port_ele !=
nullptr;
1030 port_ele = port_ele->NextSiblingElement()) {
1031 const std::string direction = port_ele->Name();
1033 if (direction ==
"input_port") {
1035 }
else if (direction ==
"output_port") {
1037 }
else if (direction ==
"inout_port") {
1040 throw exceptions::TreeDocumentError(
1041 "Unkown port direction in node model for '" + std::string(node_name) +
"': " + direction);
1043 if (
const char * c = port_ele->Attribute(
"name")) {
1046 if (
const char * c = port_ele->Attribute(
"type")) {
1049 if (
const char * c = port_ele->Attribute(
"default")) {
1052 if (
const char * c = port_ele->GetText()) {
1055 model.port_infos.push_back(std::move(port_info));
1064 tinyxml2::XMLDocument model_doc;
1066 model_doc.Parse(BT::writeTreeNodesModelXML(factory_, include_native).c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1067 throw exceptions::TreeDocumentError(
1068 "Error parsing the model of the currently registered nodes: " + std::string(model_doc.ErrorStr()));
1076 std::unordered_map<std::string, BT::NodeType> registered_nodes;
1077 for (
const auto & [node_name,
model] : model_map) {
1078 registered_nodes[node_name] =
model.type;
1082 }
catch (
const BT::RuntimeError & e) {
1083 return nonstd::make_unexpected(e.what());
1090 tinyxml2::XMLPrinter printer;
1092 return printer.CStr();
1099 tinyxml2::XMLError result = doc.SaveFile(path.c_str());
1100 if (result != tinyxml2::XML_SUCCESS) {
1101 throw exceptions::TreeDocumentError(
1102 "Failed to write tree document to file. Error ID: " + std::string(doc.ErrorIDToName(result)));
1109 tinyxml2::XMLElement * root_ele = NewElement(TreeDocument::ROOT_ELEMENT_NAME);
1110 root_ele->SetAttribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME, format_version_.c_str());
1111 InsertFirstChild(root_ele);
1115const TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(
const std::string & tree_name)
const
1117 return getXMLElementForTreeWithNameImpl<const XMLElement *>(*
this, tree_name);
1120TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(
const std::string & tree_name)
1122 return getXMLElementForTreeWithNameImpl<XMLElement *>(*
this, tree_name);
1125template <
typename ReturnT,
typename DocumentT>
1126ReturnT TreeDocument::getXMLElementForTreeWithNameImpl(DocumentT & doc,
const std::string & tree_name)
1128 if (tree_name.empty()) {
1129 throw exceptions::TreeDocumentError(
"Cannot get tree with an empty name.");
1131 if (!doc.hasTreeName(tree_name)) {
1132 throw exceptions::TreeDocumentError(
"Cannot get tree with name '" + tree_name +
"' because it doesn't exist.");
1134 auto child = doc.RootElement()->FirstChildElement(TreeDocument::TREE_ELEMENT_NAME);
1135 while (child && !child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str())) {
1136 child = child->NextSiblingElement();
1139 throw std::logic_error(
1140 "Unexpected error trying to get tree element with name '" + tree_name +
1141 "'. Since hasTreeName() returned true, there MUST be a corresponding element.");
Data structure for information about which behavior tree node plugin to load and how to configure the...
NodeManifest & add(const std::string &node_name, const RegistrationOptions &opt)
Add registration options for a behavior tree node to the manifest.
bool contains(const std::string &node_name) const
Determine if a behavior tree node has been added to the manifest.
NodeManifest & merge(const NodeManifest &other, bool replace=false)
Merges another NodeManifest with this one.
const Map & map() const
Get a view of the internal map.
A pluginlib::ClassLoader specifically for loading installed behavior tree node plugins.
Additional parameters specific to ROS 2 determined at runtime by TreeBuilder.
Class that encapsulates behavior tree script expressions.
std::string str() const
Concatenate all expressions of this instance to a single string.
Handle for a single node of a TreeDocument.
const TreeDocument & getParentDocument() const
Get a const view of this node's parent tree document.
NodeElement insertTreeFromResource(const TreeResource &resource, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from one of the installed package's behavior tree resources and add its first chil...
const std::vector< std::string > & getPortNames() const
Get the names of the data ports implemented by the node represented by this element.
virtual std::string getRegistrationName() const
Get the name of this node given during registration representing its dynamic type.
std::function< bool(const NodeElement &)> ConstDeepApplyCallback
Callback invoked for every node found under another node. It cannot modify the current node.
NodeElement & resetPorts()
Delete all currently specified port values and reset with the defaults.
std::function< bool(NodeElement &)> DeepApplyCallback
Callback invoked for every node found under another node. It may modify the current node.
NodeElement & removeFirstChild(const std::string ®istration_name="", const std::string &instance_name="")
Recursively visit this node's children in execution order and remove the first node with a particular...
NodeElement & setPorts(const PortValues &port_values)
Populate the the node's data ports.
virtual NodeElement & setName(const std::string &instance_name)
Assign a name for this specific node instance.
NodeElement getFirstNode(const std::string ®istration_name="", const std::string &instance_name="") const
Recursively visit this node's children in execution order and get the first node with a particular re...
NodeElement(TreeDocument *doc_ptr, XMLElement *ele_ptr)
Protected constructor intended for internal use only.
std::map< std::string, std::string > PortValues
Mapping of port names and its respective value encoded as string.
const std::vector< NodeElement > deepApplyConst(ConstDeepApplyCallback apply_callback) const
Recursively apply a callback to this node's children.
TreeDocument * doc_ptr_
Pointer to the tree document that created the tree this node belongs to.
virtual std::string getName() const
Get the name of this node given to this specific instance by the developer.
PortValues getPorts() const
Assemble the values given to each data port implemented by this node.
NodeElement & setConditionalScript(BT::PreCond type, const Script &script)
Specify a script that is evaluated before this node is ticked.
NodeElement insertTreeFromString(const std::string &tree_str, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document created from a string and add its first child node to the children...
tinyxml2::XMLElement * ele_ptr_
Pointer to the corresponding XMLElement of the base document.
NodeElement insertTree(const TreeElement &tree, const NodeElement *before_this=nullptr)
Concatenate a tree and add its first child node to the children of this node.
std::string getFullyQualifiedName() const
Create a string that uniquely identifies this node considering its registration and its instance name...
NodeElement insertTreeFromDocument(const TreeDocument &doc, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document and add its first child node to the children of this node.
model::SubTree insertSubTreeNode(const std::string &tree_name, const NodeElement *before_this=nullptr)
Add a subtree node for a specific tree element to the children of this node.
bool hasChildren() const
Determine whether any children have been given to this node.
bool operator==(const NodeElement &other) const
Determine if two node elements refer to the same node.
NodeElement insertNode(const std::string &name, const NodeElement *before_this=nullptr)
Add a new node to the children of this node.
NodeElement insertTreeFromFile(const std::string &path, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document created from a file and add its first child node to the children o...
NodeElement & removeChildren()
Recursively remove all children of this node element.
NodeElement & operator=(const NodeElement &other)
Replace this node with another.
std::vector< NodeElement > deepApply(DeepApplyCallback apply_callback)
Recursively apply a callback to this node's children.
Handle for a single behavior tree of a TreeDocument.
TreeElement & removeChildren()
TreeElement(TreeDocument *doc_ptr, XMLElement *ele_ptr)
Protected constructor intended to be used only by certain factory methods of TreeDocument.
std::string getName() const override
Get the name of the behavior tree.
BT::Result verify() const
Verify that this behavior tree is structured correctly and can be created successfully.
NodeManifest getRequiredNodeManifest() const
Assemble the node manifest that is required for successfully creating an instance of this tree.
TreeElement & makeRoot()
Set this behavior tree as the root tree of the parent document.
TreeElement & removeFirstChild(const std::string ®istration_name="", const std::string &instance_name="")
TreeElement & operator=(const TreeElement &other)
Replace the behavior tree represented by this element with another.
TreeElement & setName(const std::string &tree_name) override
Set the name of the behavior tree.
std::string writeToString() const
Write this behavior tree to an XML encoded in a string.
TreeElement getRootTree()
Get the corresponding behavior tree element for the root tree of this document.
TreeDocument & mergeResource(const TreeResource &resource, bool adopt_root_tree=false)
Merge the behavior trees from one of the installed package's behavior tree resources.
std::string getRootTreeName() const
Get the name of this document's root tree.
TreeDocument & mergeTreeDocument(const XMLDocument &other, bool adopt_root_tree=false)
Merge another tree document with this one.
TreeDocument & mergeString(const std::string &tree_str, bool adopt_root_tree=false)
Create a tree document from a string and merge it with this one.
TreeElement newTreeFromDocument(const TreeDocument &other, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside another tree doc...
void writeToFile(const std::string &path) const
Write the XML of this tree document to a file.
TreeElement newTreeFromFile(const std::string &path, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside the XML file.
BT::Result verify() const
Verify that all behavior trees of this document are structured correctly and can be created successfu...
NodeManifest getRequiredNodeManifest() const
Assemble the node manifest that is required for successfully creating an instance of any of the docum...
std::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
std::set< std::string > getRegisteredNodeNames(bool include_native=true) const
Get the names of all nodes that are known to this document.
TreeElement newTreeFromString(const std::string &tree_str, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside the XML string.
TreeElement newTreeFromResource(const TreeResource &resource, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one the trees found inside a part...
TreeDocument & reset()
Clear this document and reset it to its initial state.
TreeElement getTree(const std::string &tree_name)
Get the corresponding behavior tree element for a tree inside this document.
TreeDocument & addNodeModel(bool include_native=false)
Add an behavior tree node model element to the document.
virtual TreeDocument & registerNodes(const NodeManifest &tree_node_manifest, bool override=false)
Load behavior tree node plugins and register them with the internal behavior tree factory.
TreeDocument & setRootTreeName(const std::string &tree_name)
Define the root tree of this document.
bool hasTreeName(const std::string &tree_name) const
Determine if this document specifies a behavior tree with a particular name.
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc)
Convert a behavior tree node model document to the corresponding data structure.
TreeDocument & mergeFile(const std::string &path, bool adopt_root_tree=false)
Create a tree document from a file and merge it with this one.
std::vector< std::string > getAllTreeNames() const
Get the names of all behavior trees inside this document.
TreeDocument & mergeTree(const TreeElement &tree, bool make_root_tree=false)
Merge an existing behavior tree with this tree document.
std::string writeToString() const
Write the XML of this tree document to a string.
bool hasRootTreeName() const
Determine if this document specifies which of its trees is the root tree.
TreeDocument & removeTree(const std::string &tree_name)
Remove a particular behavior tree from this document.
TreeDocument(const std::string &format_version=BTCPP_FORMAT_DEFAULT_VERSION, NodeRegistrationLoader::SharedPtr tree_node_loader=NodeRegistrationLoader::make_shared())
Create a an empty tree document.
TreeElement newTree(const std::string &tree_name)
Create a new behavior tree inside this document.
Class containing behavior tree resource data.
std::string getRootTreeName() const
Get the name of the root tree of this behavior tree resource.
NodeManifest getNodeManifest() const
Get the node manifest associated with this resource.
bool hasRootTreeName() const
Determine if this behavior tree resource specifies a root tree.
Subtree behavior tree node model.
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
std::set< KeyT, CompareT, AllocatorT > getCommonElements(std::set< KeyT, CompareT, AllocatorT > c1, std::set< KeyT, CompareT, AllocatorT > c2)
Assemble common elements of two sets.
void exposeToGlobalDebugLogging(const rclcpp::Logger &logger)
Enable ROS 2 debug logging, if the C preprocessor flag _AUTO_APMS_DEBUG_LOGGING is set.
Core API for AutoAPMS's behavior tree implementation.
Models for all available behavior tree nodes.
Useful tooling for incorporating behavior trees for task development.
Parameters for loading and registering a behavior tree node class from a shared library using e....
Data structure encapsulating the information of all ports implemented by a behavior tree node.
Implementation details of a single data port.
std::string port_default
Default value of the port encoded as string.
std::string port_description
Description of the port.
BT::PortDirection port_direction
Direction of the port.
std::string port_type
String representation of the C++ type given to the port.
std::string port_name
Name of the port.