15#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
20#include "ament_index_cpp/get_package_share_directory.hpp"
21#include "auto_apms_behavior_tree_core/builder.hpp"
22#include "auto_apms_behavior_tree_core/exceptions.hpp"
23#include "auto_apms_behavior_tree_core/node/node_model_type.hpp"
24#include "auto_apms_behavior_tree_core/tree/tree_resource.hpp"
25#include "auto_apms_util/container.hpp"
26#include "auto_apms_util/logging.hpp"
27#include "auto_apms_util/string.hpp"
28#include "behaviortree_cpp/xml_parsing.h"
33std::vector<std::string> getAllTreeNamesImpl(
const tinyxml2::XMLDocument & doc)
35 std::vector<std::string> names;
36 if (
const tinyxml2::XMLElement * root = doc.RootElement()) {
37 if (strcmp(root->Name(), TreeDocument::ROOT_ELEMENT_NAME) == 0) {
39 for (
const tinyxml2::XMLElement * child = root->FirstChildElement(); child !=
nullptr;
40 child = child->NextSiblingElement()) {
41 if (strcmp(TreeDocument::TREE_ELEMENT_NAME, child->Name()) == 0) {
42 if (
const char * name = child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) {
43 names.push_back(name);
45 throw exceptions::TreeDocumentError(
46 "Cannot get tree name, because required attribute '" +
47 std::string(TreeDocument::TREE_NAME_ATTRIBUTE_NAME) +
"' is missing.");
51 }
else if (strcmp(root->Name(), TreeDocument::TREE_ELEMENT_NAME) == 0) {
53 if (
const char * name = root->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) names.push_back(name);
63 throw exceptions::TreeDocumentError(
"Cannot create an instance of NodeElement with doc_ptr=nullptr.");
66 throw exceptions::TreeDocumentError(
"Cannot create an instance of NodeElement with ele_ptr=nullptr.");
69 NodeModelMap::const_iterator it =
model.find(
ele_ptr_->Name());
70 std::vector<NodePortInfo> port_infos_str_vec;
71 if (it !=
model.end()) port_infos_str_vec = it->second.port_infos;
78 if (!port_infos_str_vec.empty()) {
79 for (const NodePortInfo & port_info : port_infos_str_vec) {
80 port_names_.push_back(port_info.port_name);
81 if (!port_info.port_default.empty()) port_default_values_[port_info.port_name] = port_info.port_default;
87 for (
const auto & [name, val] : port_default_values_) {
88 if (existing_port_values.find(name) == existing_port_values.end()) {
89 ele_ptr_->SetAttribute(name.c_str(), val.c_str());
97 XMLElement * other_ele = other.
ele_ptr_;
100 other_ele = other_ele->DeepClone(
doc_ptr_)->ToElement();
102 XMLElement * prev =
ele_ptr_->PreviousSiblingElement();
104 ele_ptr_->Parent()->InsertAfterChild(
ele_ptr_->PreviousSibling(), other_ele);
106 ele_ptr_->Parent()->InsertFirstChild(other_ele);
113 port_names_ = other.port_names_;
114 port_default_values_ = other.port_default_values_;
120bool TreeDocument::NodeElement::operator!=(
const NodeElement & other)
const {
return !this->operator==(other); }
123 const std::string & name,
const NodeElement * before_this)
125 if (
const std::set<std::string> names =
doc_ptr_->getRegisteredNodeNames(
true); names.find(name) == names.end()) {
126 throw exceptions::TreeDocumentError(
127 "Cannot insert unkown node <" + name +
128 ">. Before inserting a new node, the associated document must register the corresponding behavior tree "
129 "node. Consider using a signature of insertNode() that does this automatically.");
131 XMLElement * ele =
doc_ptr_->NewElement(name.c_str());
132 return insertBeforeImpl(before_this, ele);
140 if (
const std::set<std::string> names =
doc_ptr_->getRegisteredNodeNames(
false); names.find(name) == names.end()) {
147 const std::string & tree_name,
const NodeElement * before_this)
149 if (!
doc_ptr_->hasTreeName(tree_name)) {
150 throw exceptions::TreeDocumentError(
151 "Cannot insert subtree node for tree '" + tree_name +
"' because no tree with that name exists.");
154 ele.
ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
160 const std::string tree_name = tree.
getName();
161 if (
doc_ptr_->hasTreeName(tree_name)) {
163 if (
doc_ptr_->getTree(tree_name) != tree) {
164 throw exceptions::TreeDocumentError(
165 "Cannot insert subtree node using the provided tree element, because another tree with name '" + tree_name +
166 "' already exists.");
178 const XMLElement * root_child = tree.
ele_ptr_->FirstChildElement();
180 throw exceptions::TreeDocumentError(
183 if (root_child->NextSibling()) {
184 throw exceptions::TreeDocumentError(
185 "Cannot insert tree element '" + tree.
getFullyQualifiedName() +
"' because it has more than one child node.");
194 const std::vector<std::string> other_tree_names = doc.
getAllTreeNames();
195 if (other_tree_names.empty()) {
196 throw exceptions::TreeDocumentError(
197 "Cannot insert tree '" + tree_name +
"' because document has no <" + TREE_ELEMENT_NAME +
"> elements.");
200 throw exceptions::TreeDocumentError(
201 "Cannot insert tree '" + tree_name +
"' because document doesn't specify a tree with that name.");
205 std::set<std::string> required_tree_names = {tree_name};
211 doc.DeepCopy(&temp_doc);
212 const TreeElement other_tree(&temp_doc, temp_doc.getXMLElementForTreeWithName(tree_name));
214 collect_dependency_tree_names = [&required_tree_names, &collect_dependency_tree_names](
const NodeElement & ele) {
215 if (ele.getRegistrationName() == SUBTREE_ELEMENT_NAME) {
216 if (
const char * name = ele.ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
218 required_tree_names.insert(name);
221 ele.doc_ptr_->getTree(name).deepApplyConst(collect_dependency_tree_names);
223 throw exceptions::TreeDocumentError(
"Subtree element has no name attribute.");
233 return available_names.find(ele.getRegistrationName()) == available_names.end();
235 for (
const std::string & name : required_tree_names) {
236 const NodeElement ele(&temp_doc, temp_doc.getXMLElementForTreeWithName(name)->FirstChildElement());
237 if (
const std::vector<NodeElement> found = ele.
deepApplyConst(apply); !found.empty()) {
238 std::vector<std::string> names;
240 throw exceptions::TreeDocumentError(
241 "Cannot insert tree '" + tree_name +
"' because the following nodes found in tree '" + name +
242 "' are unkown to the builder:\n\t- " + auto_apms_util::join(names,
"\n\t- "));
248 required_tree_names.erase(tree_name);
249 for (
const std::string & name : other_tree_names) {
250 if (required_tree_names.find(name) == required_tree_names.end()) {
255 doc_ptr_->mergeTreeDocument(
static_cast<const XMLDocument &
>(temp_doc),
false);
258 return insertBeforeImpl(
259 before_this, doc.getXMLElementForTreeWithName(tree_name)->FirstChildElement()->DeepClone(
doc_ptr_)->ToElement());
269 const std::string & tree_str,
const std::string & tree_name,
const NodeElement * before_this)
277 const std::string & tree_str,
const NodeElement * before_this)
285 const std::string & path,
const std::string & tree_name,
const NodeElement * before_this)
293 const std::string & path,
const NodeElement * before_this)
304 insert_doc.
mergeFile(resource.build_request_file_path_);
323 const std::string & registration_name,
const std::string & instance_name)
const
329 if (registration_name.empty())
return ele.getName() == instance_name;
330 if (instance_name.empty())
return ele.getRegistrationName() == registration_name;
331 return ele.getRegistrationName() == registration_name && ele.getName() == instance_name;
333 if (
const std::vector<NodeElement> found =
deepApplyConst(apply); !found.empty())
return found[0];
336 throw exceptions::TreeDocumentError(
337 "Cannot find a child node that matches the search arguments (registration_name: '" + registration_name +
342 const std::string & registration_name,
const std::string & instance_name)
359 for (
const tinyxml2::XMLAttribute * attr =
ele_ptr_->FirstAttribute(); attr !=
nullptr; attr = attr->Next()) {
361 values[attr_name] = attr->Value();
370 std::vector<std::string> unkown_keys;
371 for (
const auto & [key, _] : port_values) {
374 if (!unkown_keys.empty()) {
375 throw exceptions::TreeDocumentError(
376 "Cannot set ports. According to the node model, the following ports are not implemented by '" +
377 std::string(
ele_ptr_->Name()) +
"': [ " + auto_apms_util::join(unkown_keys,
", ") +
" ].");
381 for (
const auto & [key, val] : port_values) {
382 ele_ptr_->SetAttribute(key.c_str(), val.c_str());
389 for (
const std::string & name : port_names_) {
390 PortValues::const_iterator it = port_default_values_.find(name);
391 if (it != port_default_values_.end()) {
392 ele_ptr_->SetAttribute(it->first.c_str(), it->second.c_str());
394 ele_ptr_->DeleteAttribute(name.c_str());
402 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.
str().c_str());
408 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.
str().c_str());
414 ele_ptr_->SetAttribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME, instance_name.c_str());
422 if (
const char * name =
ele_ptr_->Attribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME))
return name;
429 std::string instance_name =
getName();
430 if (registration_name == instance_name)
return registration_name;
431 return instance_name +
" (" + registration_name +
")";
439 std::vector<NodeElement> found;
440 deepApplyImpl(*
this, apply_callback, found);
446 std::vector<NodeElement> found;
447 deepApplyImpl(*
this, apply_callback, found);
452 const NodeElement * before_this, XMLElement * add_this)
455 XMLElement * prev =
nullptr;
456 XMLElement * curr = ele_ptr_->FirstChildElement();
462 if (curr == before_this->
ele_ptr_) {
467 curr = curr->NextSiblingElement();
471 ele_ptr_->InsertAfterChild(prev, add_this);
473 throw exceptions::TreeDocumentError(
475 getFullyQualifiedName() +
".");
478 ele_ptr_->InsertFirstChild(add_this);
481 ele_ptr_->InsertEndChild(add_this);
486void TreeDocument::NodeElement::deepApplyImpl(
487 const NodeElement & parent, ConstDeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
489 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child !=
nullptr;
490 child = child->NextSiblingElement()) {
491 const NodeElement child_ele(parent.doc_ptr_, child);
494 if (apply_callback(child_ele)) vec.push_back(child_ele);
497 deepApplyImpl(child_ele, apply_callback, vec);
501void TreeDocument::NodeElement::deepApplyImpl(
502 NodeElement & parent, DeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
504 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child !=
nullptr;
505 child = child->NextSiblingElement()) {
509 if (apply_callback(child_ele)) vec.push_back(child_ele);
512 deepApplyImpl(child_ele, apply_callback, vec);
518 if (!ele_ptr->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
519 throw exceptions::TreeDocumentError(
520 "Cannot create tree element without a '" + std::string(TREE_NAME_ATTRIBUTE_NAME) +
"' attribute.");
526 const std::string other_tree_name = other.
getName();
530 throw exceptions::TreeDocumentError(
531 "Cannot copy tree '" + other.
getName() +
"' because another tree with this name already exists.");
539 ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
545 if (
const char * name =
ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME))
return name;
560 const bool is_native_node =
doc_ptr_->native_node_names_.find(name) !=
doc_ptr_->native_node_names_.end();
561 if (!is_native_node && !m.
contains(name)) {
562 if (!
doc_ptr_->registered_nodes_manifest_.contains(name)) {
563 throw exceptions::NodeManifestError(
564 "Cannot assemble the required node manifest for tree '" +
getName() +
565 "' since there are no registration options for node '" + name +
"'.");
567 m.
add(name,
doc_ptr_->registered_nodes_manifest_[name]);
576 TreeDocument doc(
doc_ptr_->format_version_,
doc_ptr_->tree_node_loader_ptr_);
582 XMLDocument tree_doc;
583 tree_doc.InsertEndChild(
ele_ptr_->DeepClone(&tree_doc));
584 tinyxml2::XMLPrinter printer;
585 tree_doc.Print(&printer);
586 return printer.CStr();
590 const std::string & registration_name,
const std::string & instance_name)
605: XMLDocument(true, tinyxml2::PRESERVE_WHITESPACE),
607 native_node_names_(BT::BehaviorTreeFactory().builtinNodes()),
608 format_version_(format_version),
609 tree_node_loader_ptr_(tree_node_loader),
610 registered_nodes_manifest_(),
612 logger_(rclcpp::get_logger(LOGGER_NAME))
619 std::set<std::string> include_stack;
620 return mergeTreeDocumentImpl(other, adopt_root_tree, include_stack);
624 const XMLDocument & other,
bool adopt_root_tree, std::set<std::string> & include_stack)
626 const XMLElement * other_root = other.RootElement();
628 throw exceptions::TreeDocumentError(
"Cannot merge tree documents: other_root is nullptr.");
631 auto verify_tree_structure = [](
const XMLElement * tree_ele) {
632 const char * tree_id = tree_ele->Attribute(TREE_NAME_ATTRIBUTE_NAME);
634 throw exceptions::TreeDocumentError(
635 "Cannot merge tree document: Found a <" + std::string(TREE_ELEMENT_NAME) +
636 "> element that doesn't specify the required attribute '" + TREE_NAME_ATTRIBUTE_NAME +
"'.");
638 const XMLElement * tree_root_child = tree_ele->FirstChildElement();
639 if (!tree_root_child) {
640 throw exceptions::TreeDocumentError(
641 "Cannot merge tree document: Tree '" + std::string(tree_id) +
"' has no child nodes.");
643 if (tree_root_child->NextSibling()) {
644 throw exceptions::TreeDocumentError(
645 "Cannot merge tree document: Tree '" + std::string(tree_id) +
"' has more than one child node.");
650 auto check_duplicates = [
this](
const std::vector<std::string> & new_tree_names,
const std::string & source) {
652 if (!common.empty()) {
653 throw exceptions::TreeDocumentError(
654 "Cannot merge tree document: The following trees from " + source +
" are already defined: [ " +
655 auto_apms_util::join(common,
", ") +
" ].");
660 const std::vector<std::string> other_tree_names = getAllTreeNamesImpl(other);
662 if (strcmp(other_root->Name(), ROOT_ELEMENT_NAME) == 0) {
664 if (
const char * ver = other_root->Attribute(BTCPP_FORMAT_ATTRIBUTE_NAME)) {
665 if (std::string(ver) != format_version_) {
666 throw exceptions::TreeDocumentError(
667 "Cannot merge tree document: Format of other document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
668 ver +
") is not compatible with this document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
669 format_version_ +
").");
672 throw exceptions::TreeDocumentError(
673 "Cannot merge tree document: Root element of other document doesn't have required attribute '" +
674 std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
"'.");
678 TreeDocument include_doc(format_version_, tree_node_loader_ptr_);
679 for (
const XMLElement * include_ele = other_root->FirstChildElement(INCLUDE_ELEMENT_NAME); include_ele !=
nullptr;
680 include_ele = include_ele->NextSiblingElement(INCLUDE_ELEMENT_NAME)) {
681 if (
const char * path = include_ele->Attribute(INCLUDE_PATH_ATTRIBUTE_NAME)) {
682 if (std::string(path).empty()) {
683 throw exceptions::TreeDocumentError(
684 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
685 "> element that specifies an empty path in attribute '" + INCLUDE_PATH_ATTRIBUTE_NAME +
"'.");
687 std::string absolute_path = path;
688 if (
const char * ros_pkg = include_ele->Attribute(INCLUDE_ROS_PKG_ATTRIBUTE_NAME)) {
689 if (std::string(ros_pkg).empty()) {
690 throw exceptions::TreeDocumentError(
691 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
692 "> element that specifies an empty ROS package name in attribute '" + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
695 if (std::filesystem::path(path).is_absolute()) {
696 throw exceptions::TreeDocumentError(
697 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
698 "> element that specifies an absolute path '" + std::string(path) +
"' in attribute '" +
699 INCLUDE_PATH_ATTRIBUTE_NAME +
"' together with a ROS package name in attribute '" +
700 INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
"'. Please remove the attribute " + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
701 " or use a relative path if you want to refer to a location "
702 "relative to the package's share directory.");
706 (std::filesystem::path(ament_index_cpp::get_package_share_directory(ros_pkg)) / path).
string();
707 }
catch (
const ament_index_cpp::PackageNotFoundError & e) {
708 throw exceptions::TreeDocumentError(
709 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
710 "> element that specifies a non-existing ROS package '" + std::string(ros_pkg) +
"' in attribute '" +
711 INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
"'.");
717 include_doc.mergeFileImpl(absolute_path,
false, include_stack);
718 }
catch (
const std::exception & e) {
719 throw exceptions::TreeDocumentError(
720 "Cannot merge tree document: Failed to include file '" + absolute_path +
"': " + e.what());
723 throw exceptions::TreeDocumentError(
724 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
725 "> element that doesn't specify the required attribute '" + INCLUDE_PATH_ATTRIBUTE_NAME +
"'.");
730 check_duplicates(include_doc.getAllTreeNames(),
"included files");
733 for (
const XMLElement * child = include_doc.RootElement()->FirstChildElement(TREE_ELEMENT_NAME); child !=
nullptr;
734 child = child->NextSiblingElement(TREE_ELEMENT_NAME)) {
739 RootElement()->InsertEndChild(child->DeepClone(
this));
743 check_duplicates(other_tree_names,
"the merged document");
746 for (
const XMLElement * child = other_root->FirstChildElement(TREE_ELEMENT_NAME); child !=
nullptr;
747 child = child->NextSiblingElement(TREE_ELEMENT_NAME)) {
748 verify_tree_structure(child);
751 RootElement()->InsertEndChild(child->DeepClone(
this));
754 if (adopt_root_tree) {
757 if (
const char * name = other_root->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
setRootTreeName(name);
759 }
else if (strcmp(other_root->Name(), TREE_ELEMENT_NAME) == 0) {
762 verify_tree_structure(other_root);
765 check_duplicates(other_tree_names,
"the merged document");
768 RootElement()->InsertEndChild(other_root->DeepClone(
this));
770 throw exceptions::TreeDocumentError(
771 "Cannot merge tree document: Root element of other document must either be <" + std::string(ROOT_ELEMENT_NAME) +
772 "> or <" + TREE_ELEMENT_NAME +
">.");
775 if (adopt_root_tree && other_tree_names.size() == 1) {
787 std::set<std::string> include_stack;
788 return mergeTreeDocumentImpl(
static_cast<const XMLDocument &
>(other), adopt_root_tree, include_stack);
793 XMLDocument other_doc;
794 if (other_doc.Parse(tree_str.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
795 throw exceptions::TreeDocumentError(
"Cannot merge tree document from string: " + std::string(other_doc.ErrorStr()));
797 std::set<std::string> include_stack;
798 return mergeTreeDocumentImpl(other_doc, adopt_root_tree, include_stack);
803 std::set<std::string> include_stack;
804 return mergeFileImpl(path, adopt_root_tree, include_stack);
808 const std::string & path,
bool adopt_root_tree, std::set<std::string> & include_stack)
811 std::string canonical_path;
813 canonical_path = std::filesystem::canonical(path).string();
814 }
catch (
const std::filesystem::filesystem_error &) {
817 canonical_path = path;
821 if (include_stack.count(canonical_path) > 0) {
822 throw exceptions::TreeDocumentError(
823 "Cannot merge tree document: Circular include detected for file '" + path +
"'.");
827 include_stack.insert(canonical_path);
829 XMLDocument other_doc;
830 if (other_doc.LoadFile(path.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
831 throw exceptions::TreeDocumentError(
"Cannot create tree document from file " + path +
": " + other_doc.ErrorStr());
834 mergeTreeDocumentImpl(other_doc, adopt_root_tree, include_stack);
837 include_stack.erase(canonical_path);
844 return mergeFile(resource.build_request_file_path_, adopt_root_tree);
849 XMLDocument tree_doc;
850 tree_doc.InsertEndChild(tree.
ele_ptr_->DeepClone(&tree_doc));
858 if (tree_name.empty()) {
859 throw exceptions::TreeDocumentError(
"Cannot create a new tree with an empty name");
862 throw exceptions::TreeDocumentError(
863 "Cannot create a new tree with name '" + tree_name +
"' because it already exists.");
865 TreeDocument::XMLElement * new_ele = RootElement()->InsertNewChildElement(TreeDocument::TREE_ELEMENT_NAME);
866 new_ele->SetAttribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
877 std::string name(tree_name);
881 }
else if (
const std::vector<std::string> names = other.
getAllTreeNames(); names.size() == 1) {
884 throw exceptions::TreeDocumentError(
885 "Failed to create new tree element from another document because argument tree_name was omitted and it was not "
886 "possible to determine the root tree automatically.");
896 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
903 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
909 const TreeResource & resource,
const std::string & tree_name)
911 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
912 new_doc.
mergeFile(resource.build_request_file_path_);
926 return TreeElement(
this, getXMLElementForTreeWithName(tree_name));
931 if (tree_name.empty()) {
932 throw exceptions::TreeDocumentError(
"Cannot set root tree name with empty string.");
935 throw exceptions::TreeDocumentError(
936 "Cannot make tree with name '" + tree_name +
"' the root tree because it doesn't exist.");
938 RootElement()->SetAttribute(ROOT_TREE_ATTRIBUTE_NAME, tree_name.c_str());
944 if (RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
return true;
950 if (
const auto tree_name = RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
return tree_name;
951 throw exceptions::TreeDocumentError(
952 "Cannot get root tree name because the document's root element has no attribute '" +
953 std::string(ROOT_TREE_ATTRIBUTE_NAME) +
"'.");
960 RootElement()->DeleteChild(getXMLElementForTreeWithName(tree_name));
971 std::set<std::string> all_registration_names;
972 for (
const auto & [name, _] : tree_node_manifest.
map()) all_registration_names.insert(name);
973 if (
const std::set<std::string> common =
976 throw exceptions::TreeDocumentError(
977 "Found reserved node registration names in the node manifest. The following names are not allowed, because "
978 "they refer to native behavior tree nodes: [ " +
979 auto_apms_util::join(std::vector<std::string>(common.begin(), common.end()),
", ") +
" ].");
982 for (
const auto & [node_name, params] : tree_node_manifest.
map()) {
984 if (registered_nodes_manifest_.contains(node_name)) {
987 if (registered_nodes_manifest_[node_name].class_name == params.class_name) {
992 }
else if (
override) {
995 factory_.unregisterBuilder(node_name);
998 throw exceptions::TreeDocumentError(
999 "Tried to register node '" + node_name +
"' (Class : " + params.class_name +
1000 ") which is already known to the builder, but under a different class name (" +
1001 registered_nodes_manifest_[node_name].class_name +
1002 "). You must explicitly set override=true to allow for overriding previously registered nodes.");
1007 if (!tree_node_loader_ptr_->isClassAvailable(params.class_name)) {
1008 if (all_node_classes_package_map_.find(params.class_name) == all_node_classes_package_map_.end()) {
1009 throw exceptions::TreeDocumentError(
1010 "Node '" + node_name +
" (" + params.class_name +
1011 ")' cannot be registered, because the class name is not known to the class loader. "
1012 "Make sure that it's spelled correctly and registered by calling "
1013 "auto_apms_behavior_tree_register_nodes() in the CMakeLists.txt of the "
1014 "corresponding package.");
1016 throw exceptions::TreeDocumentError(
1017 "Node '" + node_name +
" (" + params.class_name +
1018 ")' cannot be registered, because the corresponding resource belongs to excluded package '" +
1019 all_node_classes_package_map_.at(params.class_name) +
"'.");
1022 pluginlib::UniquePtr<NodeRegistrationInterface> plugin_instance;
1024 plugin_instance = tree_node_loader_ptr_->createUniqueInstance(params.class_name);
1025 }
catch (
const pluginlib::CreateClassException & e) {
1026 throw pluginlib::CreateClassException(
1027 "Failed to create an instance of node '" + node_name +
" (" + params.class_name +
1028 ")'. Remember that the AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE "
1029 "macro must be called in the source file for the node class to be discoverable. "
1035 if (plugin_instance->requiresRosNodeContext()) {
1036 if (only_non_ros_nodes_) {
1037 throw exceptions::NodeRegistrationError(
1038 "Node '" + node_name +
1039 "' relies on ROS 2 functionality but this instance only allows to use non-ROS nodes.");
1042 ros_node_wptr_.lock(), tree_node_waitables_callback_group_wptr_.lock(),
1043 tree_node_waitables_executor_wptr_.lock(), params);
1044 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
1047 RosNodeContext ros_node_context(
nullptr,
nullptr,
nullptr, params);
1048 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
1050 }
catch (
const std::exception & e) {
1051 throw exceptions::NodeRegistrationError(
1052 "Cannot register node '" + node_name +
" (" + params.class_name +
")': " + e.what() +
".");
1054 registered_nodes_manifest_.add(node_name, params);
1061 std::set<std::string> names;
1062 if (include_native) names = native_node_names_;
1063 for (
const auto & [name, _] : registered_nodes_manifest_.map()) names.insert(name);
1072 XMLElement * ptr =
const_cast<XMLElement *
>(getXMLElementForTreeWithName(tree_name));
1082 tinyxml2::XMLElement * model_root = RootElement()->FirstChildElement(TREE_NODE_MODEL_ELEMENT_NAME);
1086 model_root = NewElement(TREE_NODE_MODEL_ELEMENT_NAME);
1087 RootElement()->InsertEndChild(model_root);
1091 for (
const auto & [node_name,
model] : model_map) {
1093 tinyxml2::XMLElement * node_element = NewElement(BT::toStr(
model.type).c_str());
1094 node_element->SetAttribute(
"ID", node_name.c_str());
1097 for (
const auto & port_info :
model.port_infos) {
1098 tinyxml2::XMLElement * port_element =
nullptr;
1101 switch (port_info.port_direction) {
1102 case BT::PortDirection::INPUT:
1103 port_element = NewElement(
"input_port");
1105 case BT::PortDirection::OUTPUT:
1106 port_element = NewElement(
"output_port");
1108 case BT::PortDirection::INOUT:
1109 port_element = NewElement(
"inout_port");
1114 port_element->SetAttribute(
"name", port_info.port_name.c_str());
1116 if (!port_info.port_type.empty()) {
1117 port_element->SetAttribute(
"type", port_info.port_type.c_str());
1120 if (port_info.port_has_default) {
1121 port_element->SetAttribute(
"default", port_info.port_default.c_str());
1125 if (!port_info.port_description.empty()) {
1126 port_element->SetText(port_info.port_description.c_str());
1129 node_element->InsertEndChild(port_element);
1132 model_root->InsertEndChild(node_element);
1140 const tinyxml2::XMLElement * root = doc.RootElement();
1142 throw exceptions::TreeDocumentError(
"Node model document has no root element.");
1144 if (
const char * ver = root->Attribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME)) {
1145 const std::string expected_format = TreeDocument::BTCPP_FORMAT_DEFAULT_VERSION;
1146 if (std::string(ver) != expected_format) {
1147 throw exceptions::TreeDocumentError(
1148 "Cannot parse node model document: Format of model document (" +
1149 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " + ver +
1150 ") doesn't comply with the expected format (" + std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
1151 expected_format +
").");
1154 throw exceptions::TreeDocumentError(
1155 "Cannot parse node model document: Root element of model document doesn't have required attribute '" +
1156 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
"'.");
1158 tinyxml2::XMLElement * model_ele = doc.RootElement()->FirstChildElement(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME);
1160 throw exceptions::TreeDocumentError(
1161 "Element <" + std::string(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME) +
1162 "> doesn't exist in node model document.");
1166 for (tinyxml2::XMLElement * ele = model_ele->FirstChildElement(); ele !=
nullptr; ele = ele->NextSiblingElement()) {
1167 const char * node_name = ele->Attribute(
"ID");
1169 throw exceptions::TreeDocumentError(
1170 "Element '" + std::string(ele->Name()) +
"' in node model document is missing the required attribute 'ID'");
1173 model.type = BT::convertFromString<BT::NodeType>(ele->Name());
1174 for (
const tinyxml2::XMLElement * port_ele = ele->FirstChildElement(); port_ele !=
nullptr;
1175 port_ele = port_ele->NextSiblingElement()) {
1176 const std::string direction = port_ele->Name();
1178 if (direction ==
"input_port") {
1180 }
else if (direction ==
"output_port") {
1182 }
else if (direction ==
"inout_port") {
1185 throw exceptions::TreeDocumentError(
1186 "Unkown port direction in node model for '" + std::string(node_name) +
"': " + direction);
1188 if (
const char * c = port_ele->Attribute(
"name")) {
1191 if (
const char * c = port_ele->Attribute(
"type")) {
1194 if (
const char * c = port_ele->Attribute(
"default")) {
1200 if (
const char * c = port_ele->GetText()) {
1203 model.port_infos.push_back(std::move(port_info));
1209 std::map<std::string, std::vector<std::string>> hidden_ports;
1210 for (
const auto & [node_name, registration_options] : manifest.
map()) {
1212 for (
const std::string & port_name : registration_options.hidden_ports) {
1213 hidden_ports[node_name].push_back(port_name);
1217 for (
const auto & [port_name, _] : registration_options.port_alias) {
1218 hidden_ports[node_name].push_back(port_name);
1223 for (
const auto & [node_name, ports_to_hide] : hidden_ports) {
1224 auto it = model_map.find(node_name);
1225 if (it == model_map.end()) {
1229 model.port_infos.erase(
1231 model.port_infos.begin(),
model.port_infos.end(),
1233 return std::find(ports_to_hide.begin(), ports_to_hide.end(), port_info.port_name) != ports_to_hide.end();
1235 model.port_infos.end());
1244 std::string model_xml;
1246 model_xml = BT::writeTreeNodesModelXML(factory_, include_native);
1247 }
catch (
const std::exception & e) {
1248 throw exceptions::TreeDocumentError(
"Error generating node model XML from factory: " + std::string(e.what()));
1252 tinyxml2::XMLDocument model_doc;
1253 if (model_doc.Parse(model_xml.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1254 throw exceptions::TreeDocumentError(
1255 "Error parsing the model of the currently registered nodes: " + std::string(model_doc.ErrorStr()));
1259 return getNodeModel(model_doc, registered_nodes_manifest_);
1265 std::unordered_map<std::string, BT::NodeType> registered_nodes;
1266 for (
const auto & [node_name,
model] : model_map) {
1267 registered_nodes[node_name] =
model.type;
1271 }
catch (
const BT::RuntimeError & e) {
1272 return nonstd::make_unexpected(e.what());
1279 tinyxml2::XMLPrinter printer;
1281 return printer.CStr();
1288 tinyxml2::XMLError result = doc.SaveFile(path.c_str());
1289 if (result != tinyxml2::XML_SUCCESS) {
1290 throw exceptions::TreeDocumentError(
1291 "Failed to write tree document to file. Error ID: " + std::string(doc.ErrorIDToName(result)));
1298 tinyxml2::XMLElement * root_ele = NewElement(TreeDocument::ROOT_ELEMENT_NAME);
1299 root_ele->SetAttribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME, format_version_.c_str());
1300 InsertFirstChild(root_ele);
1304const TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(
const std::string & tree_name)
const
1306 return getXMLElementForTreeWithNameImpl<const XMLElement *>(*
this, tree_name);
1309TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(
const std::string & tree_name)
1311 return getXMLElementForTreeWithNameImpl<XMLElement *>(*
this, tree_name);
1314template <
typename ReturnT,
typename DocumentT>
1315ReturnT TreeDocument::getXMLElementForTreeWithNameImpl(DocumentT & doc,
const std::string & tree_name)
1317 if (tree_name.empty()) {
1318 throw exceptions::TreeDocumentError(
"Cannot get tree with an empty name.");
1320 if (!doc.hasTreeName(tree_name)) {
1321 throw exceptions::TreeDocumentError(
"Cannot get tree with name '" + tree_name +
"' because it doesn't exist.");
1323 auto child = doc.RootElement()->FirstChildElement(TreeDocument::TREE_ELEMENT_NAME);
1324 while (child && !child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str())) {
1325 child = child->NextSiblingElement();
1328 throw std::logic_error(
1329 "Unexpected error trying to get tree element with name '" + tree_name +
1330 "'. Since hasTreeName() returned true, there MUST be a corresponding element.");
const NodeManifest & getNodeManifest() const
Get the node manifest associated with this resource.
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.
Document Object Model (DOM) for the behavior tree XML schema. This class offers a programmatic approa...
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::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.
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 contains a behavior tree with a particular name.
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 & addNodeModel(NodeModelMap model_map)
Add a behavior tree node model element to the document by parsing the contents of model_map.
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.
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc, const NodeManifest &manifest)
Convert a behavior tree node model document to the corresponding data structure.
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.
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.
Core API for AutoAPMS's behavior tree implementation.
Models for all available behavior tree nodes.
Powerful tooling for incorporating behavior trees for task development.
std::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
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.
bool port_has_default
Flag whether the port implements a default value or not.
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.
Parameters for loading and registering a behavior tree node class from a shared library using e....