AutoAPMS
Streamlining behaviors in ROS 2
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;
71 NodeModelMap model_map;
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 }
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, manifest));
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 NodeModelMap::const_iterator it = model_map.find(node_name);
112 if (it == model_map.end()) continue;
113 const 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"(
120class )" << node_name << R"( : public auto_apms_behavior_tree::core::)" << base_class_name << R"(
121{
122friend class auto_apms_behavior_tree::core::TreeDocument::NodeElement;
123
124using )" << base_class_name << "::" << base_class_name << R"(;
125
126public:
128static PortInfos ports()
129{
130PortInfos port_infos;
131)";
132 for (const NodePortInfo & info : model.port_infos) {
133 content << "port_infos.insert(BT::CreatePort<"<< info.port_type <<">(BT::convertFromString<BT::PortDirection>(\"" << info.port_direction << "\"), \"" << info.port_name << "\", \"" << info.port_description << "\"));\n";
134 if (info.port_has_default && !BT::TreeNode::isBlackboardPointer(info.port_default)) {
135 content << "port_infos[\"" << info.port_name << "\"].setDefaultValue(BT::convertFromString<" << info.port_type << ">(\"" << info.port_default << "\"));\n";
136 }
137 }
138 content << R"(return port_infos;
139}
140
142static BT::NodeType type()
143{
144return BT::convertFromString<BT::NodeType>(")" << model.type << R"(");
145}
146
148static std::string name()
149{
150return ")" << node_name << R"(";
151}
152
154std::string getRegistrationName() const override final
155{
156return name();
157}
158
159)";
160 if (!is_native_node) {
161 content << R"(/// @brief Registration options for this node.
162static RegistrationOptions registrationOptions()
163{
164return RegistrationOptions::decode(R"()" << options.encode() << ")\");" << R"(
165}
166
167)";
168 }
169 if (!is_leaf) {
170 content << "AUTO_APMS_BEHAVIOR_TREE_CORE_DEFINE_NON_LEAF_THISREF_METHODS(" << node_name << ")\n";
171 }
172 content << "AUTO_APMS_BEHAVIOR_TREE_CORE_DEFINE_LEAF_THISREF_METHODS(" << node_name << ")\n";
173 for (const NodePortInfo & info : model.port_infos) {
174 content << R"(
178)" << node_name << " & set_" << info.port_name << "(const std::string & str";
179 if (info.port_has_default) {
180 content << " = \"" << info.port_default << "\")";
181 } else {
182 content << ")";
183 }
184 content << R"(
185{
186return setPorts({{")" << info.port_name << R"(", str}});
187}
188
192const std::string & get_)" << info.port_name << R"(_str() const
193{
194return getPorts().at(")" << info.port_name << R"(");
195}
196)";
197 if (std::regex_search(info.port_type, no_typed_setter_getter_types)) continue;
198 content << R"(
202)" << node_name << " & set_" << info.port_name << "(const " << info.port_type << " & val)" << R"(
203{
204return setPorts({{")" << info.port_name << R"(", BT::toStr(val)}});
205}
206
210)" << info.port_type << " get_" << info.port_name << R"(() const
211{
212return BT::convertFromString<)" << info.port_type << ">(get_" << info.port_name << R"(_str());
213}
214)";
215 // clang-format on
216 }
217 content << "};\n";
218 }
219 content << "\n}";
220
221 std::ofstream out_stream(header_path);
222 if (out_stream.is_open()) {
223 out_stream << content.str();
224 out_stream.close();
225 } else {
226 throw std::runtime_error("Error opening node model header file '" + header_path.string() + "'");
227 }
228 } catch (const std::exception & e) {
229 std::cerr << "ERROR (create_node_model_header): " << e.what() << "\n";
230 return EXIT_FAILURE;
231 }
232
233 return EXIT_SUCCESS;
234}
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.
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc, const NodeManifest &manifest)
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.
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32
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....
std::string class_name
Fully qualified name of the behavior tree node plugin class.