AutoAPMS
Resilient Robot Mission Management
Loading...
Searching...
No Matches
create_node_model_header.cpp
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
15#include <tinyxml2.h>
16
17#include <filesystem>
18#include <fstream>
19#include <iostream>
20#include <regex>
21#include <sstream>
22
23#include "auto_apms_behavior_tree_core/builder.hpp"
24#include "auto_apms_behavior_tree_core/node/node_manifest.hpp"
25#include "auto_apms_util/container.hpp"
26#include "auto_apms_util/string.hpp"
27#include "behaviortree_cpp/xml_parsing.h"
28
29using namespace auto_apms_behavior_tree;
30
31const std::regex no_typed_setter_getter_types(
32 "std::shared_ptr|std::unique_ptr|std::weak_ptr|BT::Any|BT::AnyTypeAllowed|std::string");
33const std::vector<BT::NodeType> leaf_node_types{BT::NodeType::ACTION, BT::NodeType::SUBTREE};
34
35int main(int argc, char ** argv)
36{
37 bool include_native_nodes = false;
38 if (argc < 5) {
39 std::cerr << "create_node_model_header: Missing inputs! The program requires: \n\t1.) The path to the node plugin "
40 "manifest YAML file.\n\t2.) The path to the nodes model XML file.\n\t3.) The name of the package "
41 "building the node model header.\n\t4.) The path of the output .hpp file.\n";
42 std::cerr << "Usage: create_node_model_header <manifest_file> <model_file> <build_package_name> <header_path>.\n";
43 return EXIT_FAILURE;
44 }
45
46 try {
47 const std::filesystem::path manifest_file = std::filesystem::absolute(argv[1]);
48 const std::filesystem::path model_file = std::filesystem::absolute(argv[2]);
49 const std::string build_package_name = auto_apms_util::trimWhitespaces(argv[3]);
50 const std::filesystem::path header_path = std::filesystem::absolute(auto_apms_util::trimWhitespaces(argv[4]));
51 if (build_package_name == "auto_apms_behavior_tree") include_native_nodes = true;
52
53 // Ensure that arguments are not empty
54 if (manifest_file.empty()) {
55 throw std::runtime_error("Argument manifest_file must not be empty.");
56 }
57 if (model_file.empty()) {
58 throw std::runtime_error("Argument model_file must not be empty.");
59 }
60 if (header_path.empty()) {
61 throw std::runtime_error("Argument header_path must not be empty.");
62 }
63
64 // Ensure correct extensions
65 if (header_path.extension() != ".hpp") {
66 throw std::runtime_error("Output file '" + header_path.string() + "' has wrong extension. Must be '.hpp'.");
67 }
68
69 core::NodeManifest manifest;
70 tinyxml2::XMLDocument model_doc;
72
73 const std::set<std::string> native_node_names = BT::BehaviorTreeFactory().builtinNodes();
74 if (include_native_nodes) {
75 if (
76 model_doc.Parse(BT::writeTreeNodesModelXML(BT::BehaviorTreeFactory(), true).c_str()) !=
77 tinyxml2::XMLError::XML_SUCCESS) {
78 throw std::runtime_error(model_doc.ErrorStr());
79 }
80 model_map = core::TreeDocument::getNodeModel(model_doc);
81 for (const auto & [name, _] : model_map) {
83 opt.class_name = "empty";
84 manifest.add(name, opt);
85 }
86 manifest.remove(core::TreeDocument::SUBTREE_ELEMENT_NAME);
87 model_doc.Clear();
88 }
89
90 manifest.merge(core::NodeManifest::fromFile(manifest_file));
91 if (model_doc.LoadFile(model_file.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
92 throw std::runtime_error(model_doc.ErrorStr());
93 }
94 model_map.merge(core::TreeDocument::getNodeModel(model_doc));
95
96 // clang-format off
97 std::ostringstream content;
98 content << R"(// This header has been generated automatically. DO NOT CHANGE!
99
100#pragma once
101
102#include "behaviortree_cpp/basic_types.h"
103#include "auto_apms_behavior_tree_core/convert.hpp"
104#include "auto_apms_behavior_tree_core/node/node_model_type.hpp"
105
106namespace )" << build_package_name << R"(::model
107{
108)";
109 // clang-format on
110 for (const auto & [node_name, options] : manifest.map()) {
111 core::TreeBuilder::NodeModelMap::const_iterator it = model_map.find(node_name);
112 if (it == model_map.end()) continue;
113 const core::TreeBuilder::NodeModel & model = it->second;
114 const bool is_native_node = native_node_names.find(node_name) != native_node_names.end();
115 const bool is_leaf = auto_apms_util::contains(leaf_node_types, model.type);
116 const std::string base_class_name = is_leaf ? "LeafNodeModelType" : "NodeModelType";
117 // clang-format off
118 content << R"(
119class )" << node_name << R"( : public auto_apms_behavior_tree::core::)" << base_class_name << R"(
120{
121friend class auto_apms_behavior_tree::core::TreeDocument::NodeElement;
122
123using )" << base_class_name << "::" << base_class_name << R"(;
124
125public:
127static PortInfos ports()
128{
129PortInfos port_infos;
130)";
131 for (const core::TreeBuilder::NodePortInfo & info : model.port_infos) {
132 content << "port_infos.insert(BT::CreatePort<"<< info.port_type <<">(BT::convertFromString<BT::PortDirection>(\"" << info.port_direction << "\"), \"" << info.port_name << "\", \"" << info.port_description << "\"));\n";
133 if (!info.port_default.empty() && !BT::TreeNode::isBlackboardPointer(info.port_default)) {
134 content << "port_infos[\"" << info.port_name << "\"].setDefaultValue(BT::convertFromString<" << info.port_type << ">(\"" << info.port_default << "\"));\n";
135 }
136 }
137 content << R"(return port_infos;
138}
139
141static BT::NodeType type()
142{
143return BT::convertFromString<BT::NodeType>(")" << model.type << R"(");
144}
145
147static std::string name()
148{
149return ")" << node_name << R"(";
150}
151
153std::string getRegistrationName() const override final
154{
155return name();
156}
157
158)";
159 if (!is_native_node) {
160 content << R"(/// @brief Registration options for this node.
161static RegistrationOptions registrationOptions()
162{
163return RegistrationOptions::decode(R"()" << options.encode() << ")\");" << R"(
164}
165
166)";
167 }
168 if (!is_leaf) {
169 content << "AUTO_APMS_BEHAVIOR_TREE_CORE_DEFINE_NON_LEAF_THISREF_METHODS(" << node_name << ")\n";
170 }
171 content << "AUTO_APMS_BEHAVIOR_TREE_CORE_DEFINE_LEAF_THISREF_METHODS(" << node_name << ")\n";
172 for (const core::TreeBuilder::NodePortInfo & info : model.port_infos) {
173 content << R"(
177)" << node_name << " & set_" << info.port_name << "(const std::string & str";
178 if (info.port_default.empty()) {
179 content << ")";
180 } else {
181 content << " = \"" << info.port_default << "\")";
182 }
183 content << R"(
184{
185return setPorts({{")" << info.port_name << R"(", str}});
186}
187
191const std::string & get_)" << info.port_name << R"(_str() const
192{
193return getPorts().at(")" << info.port_name << R"(");
194}
195)";
196 if (std::regex_search(info.port_type, no_typed_setter_getter_types)) continue;
197 content << R"(
201)" << node_name << " & set_" << info.port_name << "(const " << info.port_type << " & val)" << R"(
202{
203return setPorts({{")" << info.port_name << R"(", BT::toStr(val)}});
204}
205
209)" << info.port_type << " get_" << info.port_name << R"(() const
210{
211return BT::convertFromString<)" << info.port_type << ">(get_" << info.port_name << R"(_str());
212}
213)";
214 // clang-format on
215 }
216 content << "};\n";
217 }
218 content << "\n}";
219
220 std::ofstream out_stream(header_path);
221 if (out_stream.is_open()) {
222 out_stream << content.str();
223 out_stream.close();
224 } else {
225 throw std::runtime_error("Error opening node model header file '" + header_path.string() + "'.");
226 }
227 } catch (const std::exception & e) {
228 std::cerr << "ERROR (create_node_model_header): " << e.what() << "\n";
229 return EXIT_FAILURE;
230 }
231
232 return EXIT_SUCCESS;
233}
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.
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.
NodeManifest & remove(const std::string &node_name)
Remove registration options for a behavior tree node.
std::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc)
Convert a behavior tree node model document to the corresponding data structure.
std::string trimWhitespaces(const std::string &str)
Trim whitespaces from both ends of a string.
Definition string.cpp:84
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
Definition container.hpp:36
Models for all available behavior tree nodes.
Useful tooling for incorporating behavior trees for task development.
Definition builder.hpp:30
Parameters for loading and registering a behavior tree node class from a shared library using e....
std::string class_name
Fully qualified name of the behavior tree node plugin class.
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.
BT::PortDirection port_direction
Direction of the port.
std::string port_type
String representation of the C++ type given to the port.