Metadata-Version: 2.4
Name: bteng
Version: 0.2.4
Summary: Modular Behavior Tree execution engine for Python
Author: BTEng contributors
License-Expression: LicenseRef-BTEng-Proprietary
Keywords: behavior-tree,robotics,automation,planning,control
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: viz
Requires-Dist: graphviz>=0.20; extra == "viz"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: graphviz>=0.20; extra == "dev"
Dynamic: license-file

# BTEng

BTEng is a lightweight Behavior Tree execution engine for Python, built for
robotics, automation, simulation, and other tick-driven control systems.

It gives you a small core runtime, reusable control/decorator nodes, a
thread-safe blackboard, XML tree loading, plugin-based custom nodes, execution
tracing, and Graphviz visualization without forcing a large framework around
your application.

> Status: Python implementation is available now. C++ support is planned as a
> future direction.

## Why BTEng?

Behavior Trees are useful when a system needs to make repeated decisions while
reacting to changing world state. In robotics and automation, that usually means
checking preconditions, running long-lived actions, interrupting work when safety
or environment conditions change, and falling back to recovery behavior.

BTEng is designed around those needs:

- Tick-based execution with `SUCCESS`, `FAILURE`, `RUNNING`, and `IDLE` states
- Standard control nodes: `Sequence`, `Fallback` / `Selector`, `Parallel`
- Reactive control nodes for interruptible behavior
- Decorators for retry, timeout, rate control, inversion, and forced result
- Synchronous, stateful, and background-thread action nodes
- Thread-safe blackboard with scoped subtree remapping
- XML tree definitions inspired by BehaviorTree.CPP style workflows
- Runtime node registration and plugin loading for application-specific actions
- Execution tracing and DOT / image visualization support

## Installation

For local development:

```bash
git clone <repo-url>
cd BTEng
pip install -e .
```

Optional visualization dependencies:

```bash
pip install -e ".[viz]"
```

The Python package requires Python 3.9 or newer.

## Quick Start

Build a tree directly in Python:

```python
from bteng import (
    ActionNode,
    BehaviorTreeEngine,
    Blackboard,
    ConditionNode,
    NodeConfig,
    NodeStatus,
    SequenceNode,
)


class BatteryOK(ConditionNode):
    def tick(self) -> NodeStatus:
        level = self.blackboard.get("battery_level", 0)
        return NodeStatus.SUCCESS if level > 20 else NodeStatus.FAILURE


class Navigate(ActionNode):
    def tick(self) -> NodeStatus:
        goal = self.blackboard.get("goal", "home")
        print(f"Navigating to {goal}")
        return NodeStatus.SUCCESS


bb = Blackboard()
bb.set("battery_level", 87)
bb.set("goal", "charging_station")

cfg = NodeConfig(blackboard=bb)
root = SequenceNode(
    "mission",
    children=[
        BatteryOK("battery_ok", cfg),
        Navigate("navigate", cfg),
    ],
    config=cfg,
)

engine = BehaviorTreeEngine(root, blackboard=bb)
status = engine.run_until_complete()
print(status)  # NodeStatus.SUCCESS
```

Run an included example:

```bash
python examples/01_simple_sequence.py
python examples/02_reactive_behavior.py
python examples/03_async_action.py
python examples/04_subtree_usage.py
```

## Core Concepts

### Status Model

Every node returns a `NodeStatus`:

- `SUCCESS`: the node completed successfully
- `FAILURE`: the node failed
- `RUNNING`: the node is still active and should be ticked again
- `IDLE`: the node is inactive or has been halted

The engine calls `tick()` repeatedly through `BehaviorTreeEngine.tick_once()` or
`BehaviorTreeEngine.run_until_complete()`.

### Control Nodes

Built-in control nodes compose child behavior:

| Node | Behavior |
| --- | --- |
| `Sequence` | Runs children left to right until one fails or runs |
| `Fallback` / `Selector` | Runs children left to right until one succeeds or runs |
| `Parallel` | Ticks all children and applies success/failure thresholds |
| `ReactiveSequence` | Rechecks earlier children every tick and can interrupt later work |
| `ReactiveFallback` | Rechecks higher-priority children every tick |

Reactive nodes are especially useful for robotics behavior such as "navigate
while path is clear" or "continue task unless emergency stop becomes active".

### Decorators

Decorators wrap a single child:

| Decorator | Purpose |
| --- | --- |
| `Inverter` | Swaps success and failure |
| `Retry` | Retries a failing child up to `max_attempts` |
| `Timeout` | Fails if the child runs longer than a duration |
| `RateController` | Limits how often the child is ticked |
| `ForceSuccess` | Converts the child result to success |
| `ForceFailure` | Converts the child result to failure |

### Blackboard

The blackboard is a shared key-value store for world state, task state, and node
ports:

```python
from bteng import Blackboard

bb = Blackboard()
bb.set("robot_pose", (1.0, 2.0, 0.0))
pose = bb.get("robot_pose")
```

Subtrees can use scoped child blackboards with remapped keys:

```python
child = Blackboard.create_child(
    bb,
    remapping={"target": "current_target"},
)

child.get("target")  # reads bb["current_target"]
```

## XML Trees

BTEng can load trees from XML:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<BTEng format_version="1.0" main_tree_to_execute="main">
  <Tree ID="main">
    <ReactiveFallback name="root">
      <ReactiveSequence name="navigate_if_safe">
        <Condition ID="PathClear"/>
        <Timeout duration="10.0">
          <Action ID="NavigateTo" goal="{target_goal}"/>
        </Timeout>
      </ReactiveSequence>
      <Action ID="StopRobot"/>
    </ReactiveFallback>
  </Tree>

  <TreeNodesModel>
    <Condition ID="PathClear"/>
    <Action ID="NavigateTo">
      <input_port name="goal"/>
    </Action>
    <Action ID="StopRobot"/>
  </TreeNodesModel>
</BTEng>
```

Register application nodes and run the XML:

```python
from bteng import ActionNode, BehaviorTreeEngine, ConditionNode, NodeStatus, register_node


@register_node()
class PathClear(ConditionNode):
    def tick(self) -> NodeStatus:
        return NodeStatus.SUCCESS if self.blackboard.get("path_clear") else NodeStatus.FAILURE


@register_node()
class NavigateTo(ActionNode):
    def tick(self) -> NodeStatus:
        goal = self.get_input("goal")
        print(f"Navigating to {goal}")
        return NodeStatus.SUCCESS


@register_node()
class StopRobot(ActionNode):
    def tick(self) -> NodeStatus:
        print("Stopping robot")
        return NodeStatus.SUCCESS


engine = BehaviorTreeEngine.from_xml("mission.xml", hz=10.0)
engine.blackboard.set("path_clear", True)
engine.blackboard.set("target_goal", "dock")
engine.run_until_complete(max_ticks=100)
```

XML attributes wrapped in braces are blackboard references:

```xml
<Action ID="NavigateTo" goal="{target_goal}"/>
```

Static values are passed as node parameters:

```xml
<Action ID="SetMode" mode="inspection"/>
```

See [docs/xml_spec.md](docs/xml_spec.md) for the full XML format.

## Custom Nodes

Use `ConditionNode` for checks and `ActionNode` for work:

```python
from bteng import ActionNode, InputPort, NodeStatus, OutputPort


class DetectObject(ActionNode):
    @classmethod
    def define_ports(cls):
        return [
            InputPort("camera"),
            OutputPort("object_pose"),
        ]

    def tick(self) -> NodeStatus:
        camera = self.get_input("camera")
        pose = run_detector(camera)
        if pose is None:
            return NodeStatus.FAILURE

        self.set_output("object_pose", pose)
        return NodeStatus.SUCCESS
```

For long-running work, use `StatefulActionNode` or `AsyncActionNode`.

```python
from bteng import NodeStatus, StatefulActionNode


class MoveArm(StatefulActionNode):
    def on_start(self) -> NodeStatus:
        self._controller.send_goal(self.get_input("pose"))
        return NodeStatus.RUNNING

    def on_running(self) -> NodeStatus:
        if self._controller.done():
            return NodeStatus.SUCCESS
        if self._controller.failed():
            return NodeStatus.FAILURE
        return NodeStatus.RUNNING

    def on_halted(self) -> None:
        self._controller.cancel()
```

## CLI

The package installs a `bteng` command:

```bash
bteng run mission.xml --plugin my_robot_nodes.py --tree main --hz 10 --log run.json -v
bteng viz mission.xml --output tree.dot
bteng viz mission.xml --output tree.png --format png
```

XML trees that reference application-specific actions or conditions must load
those node classes first, either from Python before calling
`BehaviorTreeEngine.from_xml()` or through `--plugin` in the CLI. Plugin files
can either export `BTENG_NODES` or rely on automatic discovery of `TreeNode`
subclasses.

## Visualization and Tracing

Execution tracing records node status transitions:

```python
from bteng import BehaviorTreeEngine, ExecutionTracer

tracer = ExecutionTracer()
engine = BehaviorTreeEngine(root, tracer=tracer)
engine.run_until_complete()

tracer.print_summary()
tracer.save("run.json")
```

Graphviz export is available through `bteng.visualization`:

```python
from bteng.visualization import export_graphviz, render_png

export_graphviz(engine.root, output_path="tree.dot", include_status=True)
render_png(engine.root, "tree.png")
```

PNG rendering requires the optional Python `graphviz` package and the Graphviz
system tools installed on your machine.

## Project Layout

```text
bteng/
  core/           TreeNode, NodeStatus, BehaviorTreeEngine
  nodes/          Built-in control, decorator, action, and condition nodes
  blackboard/     Thread-safe shared state and scoped remapping
  factory/        Node registry and registration decorators
  xml_parser/     XML-to-tree parser
  plugins/        Dynamic plugin loading
  logging/        Execution tracing
  visualization/  Graphviz export and rendering helpers
docs/             Architecture, XML, API, and node-system notes
examples/         Programmatic and XML examples
```

## Documentation

- [API usage](docs/api_usage.md)
- [Architecture](docs/architecture.md)
- [XML specification](docs/xml_spec.md)
- [Node system](docs/node_system.md)

## Development

Install in editable mode with development dependencies:

```bash
pip install -e ".[dev]"
```

Run the examples as smoke tests:

```bash
python examples/01_simple_sequence.py
python examples/02_reactive_behavior.py
python examples/03_async_action.py
python examples/04_subtree_usage.py
```

## License

BTEng is distributed under a proprietary license. See [LICENSE](LICENSE).
