convert_v3_to_v4.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 """Converts BehaviorTree.CPP V3 compatible tree xml files to V4 format.
3 """
4 
5 import argparse
6 import copy
7 import logging
8 import sys
9 import typing
10 import xml.etree.ElementTree as ET
11 
12 logger = logging.getLogger(__name__)
13 
14 
15 def strtobool(val: typing.Union[str, int, bool]) -> bool:
16  """``distutils.util.strtobool`` equivalent, since it will be deprecated.
17  origin: https://stackoverflow.com/a/715468/17094594
18  """
19  return str(val).lower() in ("yes", "true", "t", "1")
20 
21 
22 # see ``XMLParser::Pimpl::createNodeFromXML`` for all underscores
23 SCRIPT_DIRECTIVES = [
24  "_successIf",
25  "_failureIf",
26  "_skipIf",
27  "_while",
28  "_onSuccess",
29  "_onFailure",
30  "_onHalted",
31  "_post",
32 ]
33 
34 
35 def convert_single_node(node: ET.Element) -> None:
36  """converts a leaf node from V3 to V4.
37  Args:
38  node (ET.Element): the node to convert.
39  """
40  if node.tag == "root":
41  node.attrib["BTCPP_format"] = "4"
42 
43  def convert_no_warn(node_type: str, v3_name: str, v4_name: str):
44  if node.tag == v3_name:
45  node.tag = v4_name
46  elif (
47  (node.tag == node_type)
48  and ("ID" in node.attrib)
49  and (node.attrib["ID"] == v3_name)
50  ):
51  node.attrib["ID"] = v3_name
52 
53  original_attrib = copy.copy(node.attrib)
54  convert_no_warn("Control", "SequenceStar", "SequenceWithMemory")
55 
56  if node.tag == "SubTree":
57  logger.info(
58  "SubTree is now deprecated, auto converting to V4 SubTree"
59  " (formerly known as SubTreePlus)"
60  )
61  for key, val in original_attrib.items():
62  if key == "__shared_blackboard" and strtobool(val):
63  logger.warning(
64  "__shared_blackboard for subtree is deprecated"
65  ", using _autoremap instead."
66  " Some behavior may change!"
67  )
68  node.attrib.pop(key)
69  node.attrib["_autoremap"] = "1"
70  elif key == "ID":
71  pass
72  else:
73  node.attrib[key] = f"{{{val}}}"
74 
75  elif node.tag == "SubTreePlus":
76  node.tag = "SubTree"
77  for key, val in original_attrib.items():
78  if key == "__autoremap":
79  node.attrib.pop(key)
80  node.attrib["_autoremap"] = val
81 
82  for key in node.attrib:
83  if key in SCRIPT_DIRECTIVES:
84  logging.error(
85  "node %s%s has port %s, this is reserved for scripts in V4."
86  " Please edit the node before converting to V4.",
87  node.tag,
88  f" with ID {node.attrib['ID']}" if "ID" in node.attrib else "",
89  key,
90  )
91 
92 
93 def convert_all_nodes(root_node: ET.Element) -> None:
94  """recursively converts all nodes inside a root node.
95  Args:
96  root_node (ET.Element): the root node to start the conversion.
97  """
98 
99  def recurse(base_node: ET.Element) -> None:
100  convert_single_node(base_node)
101  for node in base_node:
102  recurse(node)
103 
104  recurse(root_node)
105 
106 
107 def convert_stream(in_stream: typing.TextIO, out_stream: typing.TextIO):
108  """Converts the behavior tree V3 xml from in_file to V4, and writes to out_file.
109  Args:
110  in_stream (typing.TextIO): The input file stream.
111  out_stream (typing.TextIO): The output file stream.
112  """
113 
114  class CommentedTreeBuilder(ET.TreeBuilder):
115  """Class for preserving comments in xml
116  see: https://stackoverflow.com/a/34324359/17094594
117  """
118 
119  def comment(self, text):
120  self.start(ET.Comment, {})
121  self.data(text)
122  self.end(ET.Comment)
123 
124  element_tree = ET.parse(in_stream, ET.XMLParser(target=CommentedTreeBuilder()))
125  convert_all_nodes(element_tree.getroot())
126  element_tree.write(out_stream, encoding="unicode", xml_declaration=True)
127 
128 
129 def main():
130  """the main function when used in cli mode"""
131 
132  logger.addHandler(logging.StreamHandler())
133  logger.setLevel(logging.DEBUG)
134 
135  parser = argparse.ArgumentParser(description=__doc__)
136  parser.add_argument(
137  "-i",
138  "--in_file",
139  type=argparse.FileType("r"),
140  help="The file to convert from (v3). If absent, reads xml string from stdin.",
141  )
142  parser.add_argument(
143  "-o",
144  "--out_file",
145  nargs="?",
146  type=argparse.FileType("w"),
147  default=sys.stdout,
148  help="The file to write the converted xml (V4)."
149  " Prints to stdout if not specified.",
150  )
151 
152  class ArgsType(typing.NamedTuple):
153  """Dummy class to provide type hinting to arguments parsed with argparse"""
154 
155  in_file: typing.Optional[typing.TextIO]
156  out_file: typing.TextIO
157 
158  args: ArgsType = parser.parse_args()
159 
160  if args.in_file is None:
161  if not sys.stdin.isatty():
162  args.in_file = sys.stdin
163  else:
164  logging.error(
165  "The input file was not specified, nor a stdin stream was detected."
166  )
167  sys.exit(1)
168 
169  convert_stream(args.in_file, args.out_file)
170 
171 
172 if __name__ == "__main__":
173  main()
convert_v3_to_v4.strtobool
bool strtobool(typing.Union[str, int, bool] val)
Definition: convert_v3_to_v4.py:15
convert_v3_to_v4.convert_single_node
None convert_single_node(ET.Element node)
Definition: convert_v3_to_v4.py:35
lexyd::recurse
constexpr auto recurse
Definition: production.hpp:242
convert_v3_to_v4.convert_all_nodes
None convert_all_nodes(ET.Element root_node)
Definition: convert_v3_to_v4.py:93
convert_v3_to_v4.convert_stream
def convert_stream(typing.TextIO in_stream, typing.TextIO out_stream)
Definition: convert_v3_to_v4.py:107
lexyd::ascii::lower
constexpr auto lower
Definition: ascii.hpp:145
convert_v3_to_v4.main
def main()
Definition: convert_v3_to_v4.py:129


behaviortree_cpp_v4
Author(s): Davide Faconti
autogenerated on Sun Sep 22 2024 02:19:15