2 """Converts BehaviorTree.CPP V3 compatible tree xml files to V4 format.
10 import xml.etree.ElementTree
as ET
12 logger = logging.getLogger(__name__)
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
19 return str(val).
lower()
in (
"yes",
"true",
"t",
"1")
36 """converts a leaf node from V3 to V4.
38 node (ET.Element): the node to convert.
40 if node.tag ==
"root":
41 node.attrib[
"BTCPP_format"] =
"4"
43 def convert_no_warn(node_type: str, v3_name: str, v4_name: str):
44 if node.tag == v3_name:
47 (node.tag == node_type)
48 and (
"ID" in node.attrib)
49 and (node.attrib[
"ID"] == v3_name)
51 node.attrib[
"ID"] = v3_name
53 original_attrib = copy.copy(node.attrib)
54 convert_no_warn(
"Control",
"SequenceStar",
"SequenceWithMemory")
56 if node.tag ==
"SubTree":
58 "SubTree is now deprecated, auto converting to V4 SubTree"
59 " (formerly known as SubTreePlus)"
61 for key, val
in original_attrib.items():
62 if key ==
"__shared_blackboard" and strtobool(val):
64 "__shared_blackboard for subtree is deprecated"
65 ", using _autoremap instead."
66 " Some behavior may change!"
69 node.attrib[
"_autoremap"] =
"1"
73 node.attrib[key] = f
"{{{val}}}"
75 elif node.tag ==
"SubTreePlus":
77 for key, val
in original_attrib.items():
78 if key ==
"__autoremap":
80 node.attrib[
"_autoremap"] = val
82 for key
in node.attrib:
83 if key
in SCRIPT_DIRECTIVES:
85 "node %s%s has port %s, this is reserved for scripts in V4."
86 " Please edit the node before converting to V4.",
88 f
" with ID {node.attrib['ID']}" if "ID" in node.attrib
else "",
94 """recursively converts all nodes inside a root node.
96 root_node (ET.Element): the root node to start the conversion.
99 def recurse(base_node: ET.Element) ->
None:
101 for node
in base_node:
108 """Converts the behavior tree V3 xml from in_file to V4, and writes to out_file.
110 in_stream (typing.TextIO): The input file stream.
111 out_stream (typing.TextIO): The output file stream.
114 class CommentedTreeBuilder(ET.TreeBuilder):
115 """Class for preserving comments in xml
116 see: https://stackoverflow.com/a/34324359/17094594
119 def comment(self, text):
120 self.start(ET.Comment, {})
124 element_tree = ET.parse(in_stream, ET.XMLParser(target=CommentedTreeBuilder()))
126 element_tree.write(out_stream, encoding=
"unicode", xml_declaration=
True)
130 """the main function when used in cli mode"""
132 logger.addHandler(logging.StreamHandler())
133 logger.setLevel(logging.DEBUG)
135 parser = argparse.ArgumentParser(description=__doc__)
139 type=argparse.FileType(
"r"),
140 help=
"The file to convert from (v3). If absent, reads xml string from stdin.",
146 type=argparse.FileType(
"w"),
148 help=
"The file to write the converted xml (V4)."
149 " Prints to stdout if not specified.",
152 class ArgsType(typing.NamedTuple):
153 """Dummy class to provide type hinting to arguments parsed with argparse"""
155 in_file: typing.Optional[typing.TextIO]
156 out_file: typing.TextIO
158 args: ArgsType = parser.parse_args()
160 if args.in_file
is None:
161 if not sys.stdin.isatty():
162 args.in_file = sys.stdin
165 "The input file was not specified, nor a stdin stream was detected."
172 if __name__ ==
"__main__":