def _set_parameters(self): """ Parse the complete scenario definition file, and replace all parameter references with the actual values Set _global_parameters. """ _global_params_overwrite = dict() if self._global_params_overwrite is not None: if isinstance(self._global_params_overwrite, type(dict)): _global_params_overwrite = dict([ tuple(m.strip() for m in mn.split(':')) for mn in self._global_params_overwrite.split(',') ]) else: _global_params_overwrite = self._global_params_overwrite print("Global parameters: ", _global_params_overwrite) self.xml_tree, self._global_parameters = OpenScenarioParser.set_parameters( self.xml_tree, _global_params_overwrite) for elem in self.xml_tree.iter(): if elem.find('ParameterDeclarations') is not None: elem, _ = OpenScenarioParser.set_parameters(elem) OpenScenarioParser.set_global_parameters(self._global_parameters)
def _set_scenario_name(self): """ Extract the scenario name from the OpenSCENARIO header information """ header = self.xml_tree.find("FileHeader") self.name = header.attrib.get('description', 'Unknown') if self.name.startswith("CARLA:"): OpenScenarioParser.set_use_carla_coordinate_system()
def _set_parameters(self): """ Parse the complete scenario definition file, and replace all parameter references with the actual values """ self.xml_tree = OpenScenarioParser.set_parameters(self.xml_tree) for elem in self.xml_tree.iter(): if elem.find('ParameterDeclarations') is not None: elem = OpenScenarioParser.set_parameters(elem)
def _parse_openscenario_configuration(self): """ Parse the given OpenSCENARIO config file, set and validate parameters """ OpenScenarioParser.set_osc_filepath(os.path.dirname(self.filename)) self._check_version() self._load_catalogs() self._set_scenario_name() self._set_carla_town() self._set_actor_information() self._validate_result()
def _set_parameters(self): """ Parse the complete scenario definition file, and replace all parameter references with the actual values Set _global_parameters. """ self.xml_tree, self._global_parameters = OpenScenarioParser.set_parameters(self.xml_tree, self._custom_params) for elem in self.xml_tree.iter(): if elem.find('ParameterDeclarations') is not None: elem, _ = OpenScenarioParser.set_parameters(elem) OpenScenarioParser.set_global_parameters(self._global_parameters)
def _create_environment_behavior(self): # Set the appropriate weather conditions env_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="EnvironmentBehavior") weather_update = ChangeWeather( OpenScenarioParser.get_weather_from_env_action(self.config.init, self.config.catalogs)) road_friction = ChangeRoadFriction( OpenScenarioParser.get_friction_from_env_action(self.config.init, self.config.catalogs)) env_behavior.add_child(oneshot_with_check(variable_name="InitialWeather", behaviour=weather_update)) env_behavior.add_child(oneshot_with_check(variable_name="InitRoadFriction", behaviour=road_friction)) return env_behavior
def _create_condition_container(self, node, name='Conditions Group', oneshot=False): """ This is a generic function to handle conditions utilising ConditionGroups Each ConditionGroup is represented as a Sequence of Conditions The ConditionGroups are grouped under a SUCCESS_ON_ONE Parallel If oneshot is set to True, oneshot_behaviour will be applied to conditions """ parallel_condition_groups = py_trees.composites.Parallel(name, policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) for condition_group in node.iter("ConditionGroup"): condition_group_sequence = py_trees.composites.Sequence( name="Condition Group") for condition in condition_group.iter("Condition"): criterion = OpenScenarioParser.convert_condition_to_atomic( condition, self.other_actors + self.ego_vehicles) if oneshot: criterion = oneshot_behavior(criterion) condition_group_sequence.add_child(criterion) if condition_group_sequence.children: parallel_condition_groups.add_child(condition_group_sequence) return parallel_condition_groups
def _create_init_behavior(self): init_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="InitBehaviour") for actor in self.config.other_actors + self.config.ego_vehicles: for carla_actor in self.other_actors + self.ego_vehicles: if (carla_actor is not None and 'role_name' in carla_actor.attributes and carla_actor.attributes['role_name'] == actor.rolename): actor_init_behavior = py_trees.composites.Sequence(name="InitActor{}".format(actor.rolename)) controller_atomic = None for private in self.config.init.iter("Private"): if private.attrib.get('entityRef', None) == actor.rolename: for private_action in private.iter("PrivateAction"): for controller_action in private_action.iter('ControllerAction'): module, args = OpenScenarioParser.get_controller( controller_action, self.config.catalogs) controller_atomic = ChangeActorControl( carla_actor, control_py_module=module, args=args, scenario_file_path=os.path.dirname(self.config.filename)) if controller_atomic is None: controller_atomic = ChangeActorControl(carla_actor, control_py_module=None, args={}) actor_init_behavior.add_child(controller_atomic) if actor.speed > 0: actor_init_behavior.add_child(ChangeActorTargetSpeed(carla_actor, actor.speed, init_speed=True)) init_behavior.add_child(actor_init_behavior) break return init_behavior
def _get_actor_transform(self, actor_name): """ Get the initial actor transform provided by the Init section Note: - The OpenScenario specification allows multiple definitions. We use the _first_ one - The OpenScenario specification allows different ways of specifying a position. We currently only support a specification with absolute world coordinates """ actor_transform = carla.Transform() actor_found = False for private_action in self.init.iter("Private"): if private_action.attrib.get('object', None) == actor_name: if actor_found: # pylint: disable=line-too-long print( "Warning: The actor '{}' was already assigned an initial position. Overwriting pose!" .format(actor_name)) # pylint: enable=line-too-long actor_found = True for position in private_action.iter('Position'): transform = OpenScenarioParser.convert_position_to_transform( position) if transform: actor_transform = transform if not actor_found: print( "Warning: The actor '{}' was not assigned an initial position. Using (0,0,0)" .format(actor_name)) return actor_transform
def _create_condition_container(self, node, name='Conditions Group', sequence=None, maneuver=None, success_on_all=True): """ This is a generic function to handle conditions utilising ConditionGroups Each ConditionGroup is represented as a Sequence of Conditions The ConditionGroups are grouped under a SUCCESS_ON_ONE Parallel """ parallel_condition_groups = py_trees.composites.Parallel(name, policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) for condition_group in node.iter("ConditionGroup"): if success_on_all: condition_group_sequence = py_trees.composites.Parallel( name="Condition Group", policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL) else: condition_group_sequence = py_trees.composites.Parallel( name="Condition Group", policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) for condition in condition_group.iter("Condition"): criterion = OpenScenarioParser.convert_condition_to_atomic( condition, self.other_actors + self.ego_vehicles) if sequence is not None and maneuver is not None: xml_path = get_xml_path(self.config.story, sequence) + '>' + \ get_xml_path(maneuver, condition) # See note in get_xml_path else: xml_path = get_xml_path(self.config.story, condition) criterion = oneshot_behavior(variable_name=xml_path, behaviour=criterion) condition_group_sequence.add_child(criterion) if condition_group_sequence.children: parallel_condition_groups.add_child(condition_group_sequence) return parallel_condition_groups
def _set_actor_information(self): """ Extract all actors and their corresponding specification NOTE: The rolename property has to be unique! """ for entity in self.xml_tree.iter("Entities"): for obj in entity.iter("ScenarioObject"): rolename = obj.attrib.get('name', 'simulation') args = {} for prop in obj.iter("Property"): key = prop.get('name') value = prop.get('value') args[key] = value for catalog_reference in obj.iter("CatalogReference"): entry = OpenScenarioParser.get_catalog_entry(self.catalogs, catalog_reference) if entry.tag == "Vehicle": self._extract_vehicle_information(entry, rolename, entry, args) elif entry.tag == "Pedestrian": self._extract_pedestrian_information(entry, rolename, entry, args) elif entry.tag == "MiscObject": self._extract_misc_information(entry, rolename, entry, args) else: self.logger.debug( " A CatalogReference specifies a reference that is not an Entity. Skipping...") for vehicle in obj.iter("Vehicle"): self._extract_vehicle_information(obj, rolename, vehicle, args) for pedestrian in obj.iter("Pedestrian"): self._extract_pedestrian_information(obj, rolename, pedestrian, args) for misc in obj.iter("MiscObject"): self._extract_misc_information(obj, rolename, misc, args) # Set transform for all actors # This has to be done in a multi-stage loop to resolve relative position settings all_actor_transforms_set = False while not all_actor_transforms_set: all_actor_transforms_set = True for actor in self.other_actors + self.ego_vehicles: if actor.transform is None: try: actor.transform = self._get_actor_transform(actor.rolename) except AttributeError as e: if "Object '" in str(e): ref_actor_rolename = str(e).split('\'')[1] for ref_actor in self.other_actors + self.ego_vehicles: if ref_actor.rolename == ref_actor_rolename: if ref_actor.transform is not None: raise e break else: raise e if actor.transform is None: all_actor_transforms_set = False
def _create_test_criteria(self): """ A list of all test criteria will be created that is later used in parallel behavior tree. """ parallel_criteria = py_trees.composites.Parallel("EndConditions (Criteria Group)", policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) for condition in self.config.criteria.iter("Condition"): criterion = OpenScenarioParser.convert_condition_to_atomic(condition, self.ego_vehicles) parallel_criteria.add_child(criterion) return parallel_criteria
def _create_test_criteria(self): """ A list of all test criteria will be created that is later used in parallel behavior tree. """ parallel_criteria = py_trees.composites.Parallel("EndConditions (Criteria Group)", policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE) criteria = [] for endcondition in self.config.storyboard.iter("StopTrigger"): for condition in endcondition.iter("Condition"): if condition.attrib.get('name').startswith('criteria_'): criteria.append(condition) for condition in criteria: criterion = OpenScenarioParser.convert_condition_to_atomic(condition, self.ego_vehicles) parallel_criteria.add_child(criterion) return parallel_criteria
def _get_actor_transform(self, actor_name): """ Get the initial actor transform provided by the Init section Note: - The OpenScenario specification allows multiple definitions. We use the _first_ one - The OpenScenario specification allows different ways of specifying a position. We currently support the specification with absolute world coordinates and the relative positions RelativeWorld, RelativeObject and RelativeLane - When using relative positions the relevant reference point (e.g. transform of another actor) should be defined before! """ actor_transform = carla.Transform() actor_found = False for private_action in self.init.iter("Private"): if private_action.attrib.get('entityRef', None) == actor_name: if actor_found: # pylint: disable=line-too-long self.logger.warning( " Warning: The actor '%s' was already assigned an initial position. Overwriting pose!", actor_name) # pylint: enable=line-too-long actor_found = True for position in private_action.iter('Position'): transform = OpenScenarioParser.convert_position_to_transform( position, actor_list=self.other_actors + self.ego_vehicles) if transform: actor_transform = transform if not actor_found: # pylint: disable=line-too-long self.logger.warning( " Warning: The actor '%s' was not assigned an initial position. Using (0,0,0)", actor_name) # pylint: enable=line-too-long return actor_transform
def _create_behavior(self): """ Basic behavior do nothing, i.e. Idle """ story_behavior = py_trees.composites.Sequence("Story") for act in self.config.story.iter("Act"): if act.attrib.get('name') != 'Behavior': continue parallel_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="Maneuver + EndConditions Group") for sequence in act.iter("Sequence"): sequence_behavior = py_trees.composites.Sequence() repetitions = sequence.attrib.get('numberOfExecutions', 1) actor_ids = [] for actor in sequence.iter("Actors"): for entity in actor.iter("Entity"): for k, _ in enumerate(self.other_actors): if entity.attrib.get( 'name', None ) == self.config.other_actors[k].rolename: actor_ids.append(k) break tmp_sequence_behavior = py_trees.composites.Sequence( name=sequence.attrib.get('name')) for maneuver in sequence.iter("Maneuver"): maneuver_sequence = py_trees.composites.Sequence( name="Maneuver " + maneuver.attrib.get('name')) for event in maneuver.iter("Event"): event_sequence = py_trees.composites.Sequence( name="Event " + event.attrib.get('name')) parallel_actions = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy. SUCCESS_ON_ALL, name="Actions") for child in event.iter(): if child.tag == "Action": for actor_id in actor_ids: maneuver_behavior = OpenScenarioParser.convert_maneuver_to_atomic( child, self.other_actors[actor_id]) parallel_actions.add_child( maneuver_behavior) if child.tag == "StartConditions": # There is always on StartConditions block per Event for condition in child.iter('Condition'): condition_behavior = OpenScenarioParser.convert_condition_to_atomic( condition, self.other_actors + self.ego_vehicles) condition_behavior.name += " for {}".format( parallel_actions.name) if condition_behavior: event_sequence.add_child( condition_behavior) event_sequence.add_child(parallel_actions) maneuver_sequence.add_child(event_sequence) tmp_sequence_behavior.add_child(maneuver_sequence) for _ in range(int(repetitions)): sequence_behavior.add_child(tmp_sequence_behavior) if sequence_behavior.children: parallel_behavior.add_child(sequence_behavior) for conditions in act.iter("Conditions"): start_condition_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="StartConditions Group") for start_condition in conditions.iter("Start"): for condition in start_condition.iter('Condition'): condition_behavior = OpenScenarioParser.convert_condition_to_atomic( condition, self.other_actors + self.ego_vehicles) oneshot_idiom = oneshot_behavior( name=condition_behavior.name, variable_name=condition_behavior.name, behaviour=condition_behavior) start_condition_behavior.add_child(oneshot_idiom) for end_condition in conditions.iter("End"): for condition in end_condition.iter('Condition'): condition_behavior = OpenScenarioParser.convert_condition_to_atomic( condition, self.other_actors + self.ego_vehicles) parallel_behavior.add_child(condition_behavior) for end_condition in conditions.iter("Cancel"): for condition in end_condition.iter('Condition'): condition_behavior = OpenScenarioParser.convert_condition_to_atomic( condition, self.other_actors + self.ego_vehicles) parallel_behavior.add_child(condition_behavior) if start_condition_behavior.children: story_behavior.add_child(start_condition_behavior) if parallel_behavior.children: story_behavior.add_child(parallel_behavior) # Build behavior tree # sequence.add_child(maneuver_behavior) return story_behavior
def _create_behavior(self): """ Basic behavior do nothing, i.e. Idle """ story_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Story") joint_actor_list = self.other_actors + self.ego_vehicles for act in self.config.story.iter("Act"): act_sequence = py_trees.composites.Sequence( name="Act StartConditions and behaviours") start_conditions = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="StartConditions Group") parallel_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="Maneuver + EndConditions Group") parallel_sequences = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Maneuvers") for sequence in act.iter("ManeuverGroup"): sequence_behavior = py_trees.composites.Sequence(name=sequence.attrib.get('name')) repetitions = sequence.attrib.get('maximumExecutionCount', 1) for _ in range(int(repetitions)): actor_ids = [] for actor in sequence.iter("Actors"): for entity in actor.iter("EntityRef"): for k, _ in enumerate(joint_actor_list): if entity.attrib.get('entityRef', None) == joint_actor_list[k].attributes['role_name']: actor_ids.append(k) break if not actor_ids: print("Warning: Maneuvergroup does not use reference actors!") # Collect catalog reference maneuvers in order to process them at the same time as normal maneuvers catalog_maneuver_list = [] for catalog_reference in sequence.iter("CatalogReference"): catalog_maneuver = self.config.catalogs[catalog_reference.attrib.get( "catalogName")][catalog_reference.attrib.get("entryName")] catalog_maneuver_list.append(catalog_maneuver) all_maneuvers = itertools.chain(iter(catalog_maneuver_list), sequence.iter("Maneuver")) single_sequence_iteration = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name=sequence_behavior.name) for maneuver in all_maneuvers: # Iterates through both CatalogReferences and Maneuvers maneuver_parallel = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Maneuver " + maneuver.attrib.get('name')) for event in maneuver.iter("Event"): event_sequence = py_trees.composites.Sequence( name="Event " + event.attrib.get('name')) parallel_actions = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Actions") for child in event.iter(): if child.tag == "Action": for actor_id in actor_ids: maneuver_behavior = OpenScenarioParser.convert_maneuver_to_atomic( child, joint_actor_list[actor_id]) maneuver_behavior = StoryElementStatusToBlackboard( maneuver_behavior, "ACTION", child.attrib.get('name')) parallel_actions.add_child( oneshot_behavior(variable_name= # See note in get_xml_path get_xml_path(self.config.story, sequence) + '>' + \ get_xml_path(maneuver, child), behaviour=maneuver_behavior)) if child.tag == "StartTrigger": # There is always one StartConditions block per Event parallel_condition_groups = self._create_condition_container( child, "Parallel Condition Groups", sequence, maneuver) event_sequence.add_child( parallel_condition_groups) parallel_actions = StoryElementStatusToBlackboard( parallel_actions, "EVENT", event.attrib.get('name')) event_sequence.add_child(parallel_actions) maneuver_parallel.add_child( oneshot_behavior(variable_name=get_xml_path(self.config.story, sequence) + '>' + get_xml_path(maneuver, event), # See get_xml_path behaviour=event_sequence)) maneuver_parallel = StoryElementStatusToBlackboard( maneuver_parallel, "MANEUVER", maneuver.attrib.get('name')) single_sequence_iteration.add_child( oneshot_behavior(variable_name=get_xml_path(self.config.story, sequence) + '>' + get_xml_path(maneuver, maneuver), # See get_xml_path behaviour=maneuver_parallel)) # OpenSCENARIO refers to Sequences as Scenes in this instance single_sequence_iteration = StoryElementStatusToBlackboard( single_sequence_iteration, "SCENE", sequence.attrib.get('name')) single_sequence_iteration = repeatable_behavior( single_sequence_iteration, get_xml_path(self.config.story, sequence)) sequence_behavior.add_child(single_sequence_iteration) if sequence_behavior.children: parallel_sequences.add_child( oneshot_behavior(variable_name=get_xml_path(self.config.story, sequence), behaviour=sequence_behavior)) if parallel_sequences.children: parallel_sequences = StoryElementStatusToBlackboard( parallel_sequences, "ACT", act.attrib.get('name')) parallel_behavior.add_child(parallel_sequences) start_triggers = act.find("StartTrigger") if list(start_triggers) is not None: for start_condition in start_triggers: parallel_start_criteria = self._create_condition_container(start_condition, "StartConditions") if parallel_start_criteria.children: start_conditions.add_child(parallel_start_criteria) end_triggers = act.find("StopTrigger") if end_triggers is not None and list(end_triggers) is not None: for end_condition in end_triggers: parallel_end_criteria = self._create_condition_container( end_condition, "EndConditions", success_on_all=False) if parallel_end_criteria.children: parallel_behavior.add_child(parallel_end_criteria) if start_conditions.children: act_sequence.add_child(start_conditions) if parallel_behavior.children: act_sequence.add_child(parallel_behavior) if act_sequence.children: story_behavior.add_child(act_sequence) # Build behavior tree behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="behavior") init_behavior = self._create_init_behavior() if init_behavior is not None: behavior.add_child(oneshot_behavior(variable_name=get_xml_path( self.config.story, self.config.story), behaviour=init_behavior)) behavior.add_child(story_behavior) return behavior
def _create_behavior(self): """ Basic behavior do nothing, i.e. Idle """ story_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Story") joint_actor_list = self.other_actors + self.ego_vehicles for act in self.config.story.iter("Act"): if act.attrib.get('name') != 'Behavior': continue act_sequence = py_trees.composites.Sequence( name="Act StartConditions and behaviours") start_conditions = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="StartConditions Group") parallel_behavior = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE, name="Maneuver + EndConditions Group") parallel_sequences = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Maneuvers") for sequence in act.iter("Sequence"): sequence_behavior = py_trees.composites.Sequence() repetitions = sequence.attrib.get('numberOfExecutions', 1) actor_ids = [] for actor in sequence.iter("Actors"): for entity in actor.iter("Entity"): for k, _ in enumerate(joint_actor_list): if entity.attrib.get('name', None) == joint_actor_list[k].attributes['role_name']: actor_ids.append(k) break single_sequence_iteration = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name=sequence.attrib.get('name')) for maneuver in sequence.iter("Maneuver"): maneuver_parallel = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Maneuver " + maneuver.attrib.get('name')) for event in maneuver.iter("Event"): event_sequence = py_trees.composites.Sequence( name="Event " + event.attrib.get('name')) parallel_actions = py_trees.composites.Parallel( policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ALL, name="Actions") for child in event.iter(): if child.tag == "Action": for actor_id in actor_ids: maneuver_behavior = OpenScenarioParser.convert_maneuver_to_atomic( child, joint_actor_list[actor_id]) maneuver_behavior = StoryElementStatusToBlackboard( maneuver_behavior, "ACTION", child.attrib.get('name')) parallel_actions.add_child( oneshot_behavior(maneuver_behavior)) if child.tag == "StartConditions": # There is always one StartConditions block per Event parallel_condition_groups = self._create_condition_container( child, "Parallel Condition Groups") event_sequence.add_child( parallel_condition_groups) parallel_actions = StoryElementStatusToBlackboard( parallel_actions, "EVENT", event.attrib.get('name')) event_sequence.add_child(parallel_actions) maneuver_parallel.add_child( oneshot_behavior(event_sequence)) maneuver_parallel = StoryElementStatusToBlackboard( maneuver_parallel, "MANEUVER", maneuver.attrib.get('name')) single_sequence_iteration.add_child( oneshot_behavior(maneuver_parallel)) # OpenSCENARIO refers to Sequences as Scenes in this instance single_sequence_iteration = StoryElementStatusToBlackboard( single_sequence_iteration, "SCENE", sequence.attrib.get('name')) single_sequence_iteration = repeatable_behavior( single_sequence_iteration) for _ in range(int(repetitions)): sequence_behavior.add_child(single_sequence_iteration) if sequence_behavior.children: parallel_sequences.add_child( oneshot_behavior(sequence_behavior)) if parallel_sequences.children: parallel_sequences = StoryElementStatusToBlackboard( parallel_sequences, "ACT", act.attrib.get('name')) parallel_behavior.add_child(parallel_sequences) for conditions in act.iter("Conditions"): for start_condition in conditions.iter("Start"): parallel_start_criteria = self._create_condition_container( start_condition, "StartConditions", oneshot=True) if parallel_start_criteria.children: start_conditions.add_child(parallel_start_criteria) for end_condition in conditions.iter("End"): parallel_end_criteria = self._create_condition_container( end_condition, "EndConditions") if parallel_end_criteria.children: parallel_behavior.add_child(parallel_end_criteria) for cancel_condition in conditions.iter("Cancel"): parallel_cancel_criteria = self._create_condition_container( cancel_condition, "CancelConditions") if parallel_cancel_criteria.children: parallel_behavior.add_child(parallel_cancel_criteria) if start_conditions.children: act_sequence.add_child(start_conditions) if parallel_behavior.children: act_sequence.add_child(parallel_behavior) if act_sequence.children: story_behavior.add_child(act_sequence) # Build behavior tree # sequence.add_child(maneuver_behavior) return story_behavior