@@ -574,6 +574,7 @@ def build_port_remappings(
574574
575575def instantiate_ports_node (
576576 elem : ET .Element ,
577+ cls : type [PortsMixin ],
577578 node_registry : dict ,
578579 remapping_table : dict [str , str ],
579580 subtree_namespace : str ,
@@ -591,6 +592,7 @@ def instantiate_ports_node(
591592
592593 Args:
593594 elem: The XML element to parse.
595+ cls (type[py_trees.behavious.Behaviour]): A behaviour class type.
594596 node_registry (dict): Mapping from class names (str) to callables (constructors or partials)
595597 that return ``PortsMixin``-derived instances.
596598 remapping_table (dict): Mapping from keys (str) to absolute keys (str).
@@ -602,15 +604,6 @@ def instantiate_ports_node(
602604 Returns:
603605 PortsMixin: the fully initialised node.
604606 """
605- portsmixin_name = elem .tag
606- # Get the class definition from node_registry - needed to check the ports.
607- try :
608- cls = get_class_from_registry (portsmixin_name , node_registry )
609- except KeyError as e :
610- raise NotImplementedError (
611- f"Class name '{ portsmixin_name } ' not found in node_registry. Supporting other types is still TODO."
612- ) from e
613-
614607 port_remappings = build_port_remappings (
615608 elem = elem ,
616609 class_ = cls ,
@@ -621,22 +614,19 @@ def instantiate_ports_node(
621614
622615 instance_name = generate_node_name (
623616 explicit_name = elem .attrib .get ("name" , None ),
624- general_name = portsmixin_name ,
617+ general_name = elem . tag ,
625618 prefix = parent_names_str ,
626619 )
627620 logger .debug (f"PortsMixin node '{ instance_name } ' in namespace { subtree_namespace } remappings: { port_remappings } " )
628621
629- if portsmixin_name not in node_registry :
630- raise ValueError (f"PortsMixin class '{ portsmixin_name } ' not found in node_registry table" )
631-
632622 constructor_kwargs = constructor_kwargs or {} # create constructor_kwargs
633623 for attrib_key , attrib_value in elem .attrib .items ():
634624 if attrib_key == "name" :
635625 continue
636626 if attrib_key not in cls .input_ports () and attrib_key not in cls .output_ports ():
637627 if is_key (attrib_value ):
638628 raise ValueError (
639- f"'{ portsmixin_name } '(name='{ instance_name } '): Port remappings are "
629+ f"'{ elem . tag } '(name='{ instance_name } '): Port remappings are "
640630 f"not supported for non-port attributes ('{ attrib_key } '='{ attrib_value } '). "
641631 )
642632 # Consistency check: if constructor_kwargs already has this key, and it's conflicting,
@@ -654,7 +644,7 @@ def instantiate_ports_node(
654644 )
655645 constructor_kwargs [attrib_key ] = attrib_value
656646
657- ctor_callable = node_registry [portsmixin_name ]
647+ ctor_callable = node_registry [elem . tag ]
658648 # Try to convert the constructor arguments to the correct type.
659649 ignore_keys = {"child" , "children" , "behaviour_class_name" }
660650 constructor_kwargs , success = apply_type_hints (ctor_callable , constructor_kwargs , logger = logger , ignore = ignore_keys )
@@ -665,14 +655,14 @@ def instantiate_ports_node(
665655 )
666656 try :
667657 # Pass the behaviour_class_name (the tag/registry name) to the constructor
668- node : PortsMixin = node_registry [ portsmixin_name ] (
658+ node : PortsMixin = ctor_callable (
669659 name = instance_name ,
670- behaviour_class_name = portsmixin_name ,
660+ behaviour_class_name = elem . tag ,
671661 ** constructor_kwargs ,
672662 )
673663 except Exception as e : # Catch everything that may go wrong in the constructor
674664 raise XMLParserError (
675- f"Failed to instantiate '{ portsmixin_name } ' with args { constructor_kwargs } "
665+ f"Failed to instantiate '{ elem . tag } ' with args { constructor_kwargs } "
676666 f"(check: does the instantiated class have kwargs in __init__?): { e } "
677667 ) from e
678668
@@ -718,24 +708,73 @@ def build_tree_from_xml(
718708 logger .debug (f"Processing tag: '{ elem .tag } ' with attributes { elem .attrib } . Remapping table: { remapping_table } " )
719709 if elem .tag in node_registry :
720710 # Prioritize nodes that are in the registry, which could even be user overrides for built-ins.
721- # This is any leaf PortsMixin node (any PortsMixin + Behaviour combination).
722- logger .debug (f"Creating PortsMixin leaf node for tag { elem .tag } ." )
711+ # This is any PortsMixin node (any PortsMixin + Behaviour combination).
712+ try :
713+ cls = get_class_from_registry (elem .tag , node_registry )
714+ except KeyError as e :
715+ raise NotImplementedError (
716+ f"Class name '{ tag } ' not found in node_registry. Supporting other types is still TODO."
717+ ) from e
718+
719+ if not (issubclass (cls , PortsMixin ) and issubclass (cls , py_trees .behaviour .Behaviour )):
720+ raise TypeError (
721+ f"XML tag '{ elem .tag } ' did not instantiate a PortsMixin + "
722+ f"py_trees.behaviour.Behaviour; got { cls .__name__ } "
723+ )
724+
725+ children = []
726+ constructor_kwargs : dict | None = None
727+ is_decorator = issubclass (cls , py_trees .decorators .Decorator )
728+ is_composite = issubclass (cls , py_trees .composites .Composite )
729+ if is_decorator and len (elem ) != 1 :
730+ raise XMLParserError (f"Decorator node '{ tag } ' must have exactly 1 child, found { len (elem )} ." )
731+ if is_decorator or is_composite :
732+ for child in elem :
733+ # Use no general name fallback for the parent_names_str to pass to the children.
734+ # Otherwise, the generated node names will get too long and not very readable.
735+ # It also means that we lose the guarantee of unique names, but that can be avoided by
736+ # assigning an explicit name to the parent tags.
737+ concise_parent_names_str = generate_node_name (
738+ explicit_name = elem .attrib .get ("name" , None ),
739+ general_name = "" ,
740+ prefix = parent_names_str ,
741+ no_uuid = True ,
742+ )
743+ child_node = build_tree_from_xml (
744+ child ,
745+ remapping_table ,
746+ node_registry ,
747+ bt_index ,
748+ logger = logger ,
749+ subtree_namespace = subtree_namespace ,
750+ parent_names_str = concise_parent_names_str ,
751+ )
752+ if not isinstance (child_node , py_trees .behaviour .Behaviour ):
753+ raise TypeError (f"Child node of type { type (child_node ).__name__ } is not a valid py_trees Behavior." )
754+ children .append (child_node )
755+
756+ if is_decorator :
757+ constructor_kwargs = {"child" : children [0 ]} # already checked its length
758+ elif is_composite :
759+ constructor_kwargs = {"children" : children }
760+
761+ logger .debug (f"Creating PortsMixin node for tag { elem .tag } ." )
723762 node = instantiate_ports_node (
724763 elem = elem ,
764+ cls = cls ,
725765 node_registry = node_registry ,
726766 remapping_table = remapping_table ,
727767 subtree_namespace = subtree_namespace ,
728768 logger = logger ,
769+ constructor_kwargs = constructor_kwargs ,
729770 parent_names_str = parent_names_str ,
730771 )
731- if not (isinstance (node , PortsMixin ) and isinstance (node , py_trees .behaviour .Behaviour )):
732- raise TypeError (
733- f"XML tag '{ elem .tag } ' did not instantiate a PortsMixin + "
734- f"py_trees.behaviour.Behaviour; got { type (node ).__name__ } "
735- )
736772 return node
773+
737774 elif tag in PARENT_NODES_TAGS :
738- # Composite/Decorator nodes which can have children:
775+ # Composite/Decorator nodes which can have children.
776+ # TODO: Everything in this section should be eventually replaced with actual
777+ # built-in behaviours with ports that are part of the node registry.
739778 node_name = generate_node_name (
740779 explicit_name = elem .attrib .get ("name" , None ),
741780 general_name = elem .tag ,
0 commit comments