py-trees 2.3.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {py_trees-2.3.0 → py_trees-2.4.0}/PKG-INFO +20 -9
- {py_trees-2.3.0 → py_trees-2.4.0}/README.md +16 -6
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/behaviour.py +4 -4
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/behaviours.py +85 -10
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/blackboard.py +5 -5
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/common.py +6 -6
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/composites.py +30 -17
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/decorators.py +81 -8
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/action.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/blackboard.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/blackboard_namespaces.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/context_switching.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/dot_graphs.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/either_or.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/eternal_guard.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/lifecycle.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/logging.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/pick_up_where_you_left_off.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/selector.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/sequence.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/stewardship.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/tests.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/trees.py +5 -5
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/version.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/visitors.py +1 -1
- {py_trees-2.3.0 → py_trees-2.4.0}/pyproject.toml +1 -2
- {py_trees-2.3.0 → py_trees-2.4.0}/LICENSE +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/__init__.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/console.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/README.md +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/__init__.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/blackboard_remappings.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/demos/display_modes.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/display.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/idioms.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/logging.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/meta.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/programs/__init__.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/programs/render.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/py.typed +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/syntax_highlighting.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/timers.py +0 -0
- {py_trees-2.3.0 → py_trees-2.4.0}/py_trees/utilities.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: py_trees
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: pythonic implementation of behaviour trees
|
|
5
5
|
License: BSD
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Keywords: py_trees,py-trees,behaviour-trees
|
|
7
8
|
Author: Daniel Stonier
|
|
8
9
|
Maintainer: Daniel Stonier
|
|
@@ -19,9 +20,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
24
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
25
|
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
-
Requires-Dist: py (>=1.11.0,<2.0.0)
|
|
25
26
|
Requires-Dist: pydot (>=1.4)
|
|
26
27
|
Project-URL: Documentation, https://py-trees.readthedocs.io/en/devel/
|
|
27
28
|
Project-URL: Homepage, https://github.com/splintered-reality/py_trees
|
|
@@ -48,11 +49,13 @@ Brief feature list:
|
|
|
48
49
|
|
|
49
50
|
## What's New?
|
|
50
51
|
|
|
52
|
+
* [2025-11-13] New `ForEach` decorator.
|
|
53
|
+
* [2025-11-13] New `CompareBlackboardVariables` behaviour, with comparison expressions that allow callables.
|
|
51
54
|
* [2025-01-11] Support for Python 3.12 was added, and Python 3.8 was dropped.
|
|
52
55
|
|
|
53
56
|
## Documentation
|
|
54
57
|
|
|
55
|
-
[![devel][docs-devel-image]][docs-devel] [![2.3.x][docs-2.3.x-image]][docs-2.3.x] [![2.2.x][docs-2.2.x-image]][docs-2.2.x] [![2.1.x][docs-2.1.x-image]][docs-2.1.x]
|
|
58
|
+
[![devel][docs-devel-image]][docs-devel] [![2.4.x][docs-2.4.x-image]][docs-2.4.x] [![2.3.x][docs-2.3.x-image]][docs-2.3.x] [![2.2.x][docs-2.2.x-image]][docs-2.2.x] [![2.1.x][docs-2.1.x-image]][docs-2.1.x]
|
|
56
59
|
|
|
57
60
|
## Getting Started
|
|
58
61
|
|
|
@@ -115,6 +118,7 @@ Robotics:
|
|
|
115
118
|
|
|
116
119
|
## Releases
|
|
117
120
|
|
|
121
|
+
* `2.4.x` - New ForEach decorator, new CompareBlackboardVariables behaviour, and callables in comparison expressions.
|
|
118
122
|
* `2.3.x` - Support for Python 3.12 was added, and Python 3.8 was dropped.
|
|
119
123
|
* `2.2.x` - Selectors, Sequences with and without memory. Improved testing and style/type checking.
|
|
120
124
|
* `2.1.x` - Chooser deprecated. API housekeeping.
|
|
@@ -124,12 +128,12 @@ Robotics:
|
|
|
124
128
|
* `1.0.x` - Behaviours, Decorators, Composites, Blackboards, Tree Management and Viz tools.
|
|
125
129
|
* `0.y.x` - First open source pre-releases.
|
|
126
130
|
|
|
127
|
-
| | Devel | 2.
|
|
131
|
+
| | Devel | 2.4.x | 2.3.x | 2.2.x | 2.1.x | 2.0.x |
|
|
128
132
|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
129
|
-
| Sources | [![devel][sources-devel-image]][sources-devel] | [![2.
|
|
130
|
-
| Compatibility | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.
|
|
131
|
-
| CI | [![devel-Status][devel-build-status-image]][devel-build-status] | [![2.3.x-Status][2.3.x-build-status-image]][2.3.x-build-status] | [![2.2.x-Status][2.2.x-build-status-image]][2.2.x-build-status] | - | - | - | - |
|
|
132
|
-
| Documentation | [![devel-Docs][rtd-devel-image]][docs-devel] | [![2.
|
|
133
|
+
| Sources | [![devel][sources-devel-image]][sources-devel] | [![2.4.x][sources-2.4.x-image]][sources-2.4.x] | [![2.3.x][sources-2.3.x-image]][sources-2.3.x] | [![2.2.x][sources-2.2.x-image]][sources-2.2.x] | [![2.1.x][sources-2.1.x-image]][sources-2.1.x] | [![2.0.x][sources-2.0.x-image]][sources-2.0.x] |
|
|
134
|
+
| Compatibility | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.10][python310-image]][python310-docs]<br/>[![Python 3.8][python38-image]][python38-docs] | [![Python 3.6][python36-image]][python36-docs] | [![Python 3.6][python36-image]][python36-docs] | [![Python 3.6][python36-image]][python36-docs] |
|
|
135
|
+
| CI | [![devel-Status][devel-build-status-image]][devel-build-status] | [![2.4.x-Status][2.4.x-build-status-image]][2.4.x-build-status] | [![2.3.x-Status][2.3.x-build-status-image]][2.3.x-build-status] | [![2.2.x-Status][2.2.x-build-status-image]][2.2.x-build-status] | - | - | - | - |
|
|
136
|
+
| Documentation | [![devel-Docs][rtd-devel-image]][docs-devel] | [![2.4.x-Docs][rtd-2.4.x-image]][docs-2.4.x] | [![2.3.x-Docs][rtd-2.3.x-image]][docs-2.3.x] | [![2.2.x-Docs][rtd-2.2.x-image]][docs-2.2.x] | [![2.1.x-Docs][rtd-2.1.x-image]][docs-2.1.x] | [![2.0.x-Docs][rtd-2.0.x-image]][docs-2.0.x] |
|
|
133
137
|
|
|
134
138
|
|
|
135
139
|
[license-image]: https://img.shields.io/badge/License-BSD%203--Clause-orange.svg?style=plastic
|
|
@@ -146,12 +150,15 @@ Robotics:
|
|
|
146
150
|
|
|
147
151
|
[devel-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg
|
|
148
152
|
[devel-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
153
|
+
[2.4.x-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg??branch=release/2.4.x
|
|
154
|
+
[2.4.x-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
149
155
|
[2.3.x-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg??branch=release/2.3.x
|
|
150
156
|
[2.3.x-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
151
157
|
[2.2.x-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg??branch=release/2.2.x
|
|
152
158
|
[2.2.x-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
153
159
|
|
|
154
160
|
[docs-devel]: http://py-trees.readthedocs.io/
|
|
161
|
+
[docs-2.4.x]: http://py-trees.readthedocs.io/en/release-2.4.x/
|
|
155
162
|
[docs-2.3.x]: http://py-trees.readthedocs.io/en/release-2.3.x/
|
|
156
163
|
[docs-2.2.x]: http://py-trees.readthedocs.io/en/release-2.2.x/
|
|
157
164
|
[docs-2.1.x]: http://py-trees.readthedocs.io/en/release-2.1.x/
|
|
@@ -163,6 +170,7 @@ Robotics:
|
|
|
163
170
|
[docs-0.5.x]: http://docs.ros.org/kinetic/api/py_trees/html/
|
|
164
171
|
|
|
165
172
|
[docs-devel-image]: http://img.shields.io/badge/docs-devel-brightgreen.svg?style=plastic
|
|
173
|
+
[docs-2.4.x-image]: http://img.shields.io/badge/docs-2.4.x-brightgreen.svg?style=plastic
|
|
166
174
|
[docs-2.3.x-image]: http://img.shields.io/badge/docs-2.3.x-brightgreen.svg?style=plastic
|
|
167
175
|
[docs-2.2.x-image]: http://img.shields.io/badge/docs-2.2.x-brightgreen.svg?style=plastic
|
|
168
176
|
[docs-2.1.x-image]: http://img.shields.io/badge/docs-2.1.x-brightgreen.svg?style=plastic
|
|
@@ -174,6 +182,7 @@ Robotics:
|
|
|
174
182
|
[docs-0.5.x-image]: http://img.shields.io/badge/docs-0.5.x-brightgreen.svg?style=plastic
|
|
175
183
|
|
|
176
184
|
[rtd-devel-image]: https://readthedocs.org/projects/py-trees/badge/?version=devel&style=plastic
|
|
185
|
+
[rtd-2.4.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.4.x&style=plastic
|
|
177
186
|
[rtd-2.3.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.3.x&style=plastic
|
|
178
187
|
[rtd-2.2.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.2.x&style=plastic
|
|
179
188
|
[rtd-2.1.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.1.x&style=plastic
|
|
@@ -186,6 +195,7 @@ Robotics:
|
|
|
186
195
|
[not-available-docs-image]: http://img.shields.io/badge/docs-n/a-yellow.svg?style=plastic
|
|
187
196
|
|
|
188
197
|
[sources-devel]: https://github.com/splintered-reality/py_trees/tree/devel
|
|
198
|
+
[sources-2.4.x]: https://github.com/splintered-reality/py_trees/tree/release/2.4.x
|
|
189
199
|
[sources-2.3.x]: https://github.com/splintered-reality/py_trees/tree/release/2.3.x
|
|
190
200
|
[sources-2.2.x]: https://github.com/splintered-reality/py_trees/tree/release/2.2.x
|
|
191
201
|
[sources-2.1.x]: https://github.com/splintered-reality/py_trees/tree/release/2.1.x
|
|
@@ -197,6 +207,7 @@ Robotics:
|
|
|
197
207
|
[sources-0.5.x]: https://github.com/splintered-reality/py_trees/tree/release/0.5.x
|
|
198
208
|
|
|
199
209
|
[sources-devel-image]: http://img.shields.io/badge/sources-devel-blue.svg?style=plastic
|
|
210
|
+
[sources-2.4.x-image]: http://img.shields.io/badge/sources-2.4.x-blue.svg?style=plastic
|
|
200
211
|
[sources-2.3.x-image]: http://img.shields.io/badge/sources-2.3.x-blue.svg?style=plastic
|
|
201
212
|
[sources-2.2.x-image]: http://img.shields.io/badge/sources-2.2.x-blue.svg?style=plastic
|
|
202
213
|
[sources-2.1.x-image]: http://img.shields.io/badge/sources-2.1.x-blue.svg?style=plastic
|
|
@@ -18,11 +18,13 @@ Brief feature list:
|
|
|
18
18
|
|
|
19
19
|
## What's New?
|
|
20
20
|
|
|
21
|
+
* [2025-11-13] New `ForEach` decorator.
|
|
22
|
+
* [2025-11-13] New `CompareBlackboardVariables` behaviour, with comparison expressions that allow callables.
|
|
21
23
|
* [2025-01-11] Support for Python 3.12 was added, and Python 3.8 was dropped.
|
|
22
24
|
|
|
23
25
|
## Documentation
|
|
24
26
|
|
|
25
|
-
[![devel][docs-devel-image]][docs-devel] [![2.3.x][docs-2.3.x-image]][docs-2.3.x] [![2.2.x][docs-2.2.x-image]][docs-2.2.x] [![2.1.x][docs-2.1.x-image]][docs-2.1.x]
|
|
27
|
+
[![devel][docs-devel-image]][docs-devel] [![2.4.x][docs-2.4.x-image]][docs-2.4.x] [![2.3.x][docs-2.3.x-image]][docs-2.3.x] [![2.2.x][docs-2.2.x-image]][docs-2.2.x] [![2.1.x][docs-2.1.x-image]][docs-2.1.x]
|
|
26
28
|
|
|
27
29
|
## Getting Started
|
|
28
30
|
|
|
@@ -85,6 +87,7 @@ Robotics:
|
|
|
85
87
|
|
|
86
88
|
## Releases
|
|
87
89
|
|
|
90
|
+
* `2.4.x` - New ForEach decorator, new CompareBlackboardVariables behaviour, and callables in comparison expressions.
|
|
88
91
|
* `2.3.x` - Support for Python 3.12 was added, and Python 3.8 was dropped.
|
|
89
92
|
* `2.2.x` - Selectors, Sequences with and without memory. Improved testing and style/type checking.
|
|
90
93
|
* `2.1.x` - Chooser deprecated. API housekeeping.
|
|
@@ -94,12 +97,12 @@ Robotics:
|
|
|
94
97
|
* `1.0.x` - Behaviours, Decorators, Composites, Blackboards, Tree Management and Viz tools.
|
|
95
98
|
* `0.y.x` - First open source pre-releases.
|
|
96
99
|
|
|
97
|
-
| | Devel | 2.
|
|
100
|
+
| | Devel | 2.4.x | 2.3.x | 2.2.x | 2.1.x | 2.0.x |
|
|
98
101
|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
99
|
-
| Sources | [![devel][sources-devel-image]][sources-devel] | [![2.
|
|
100
|
-
| Compatibility | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.
|
|
101
|
-
| CI | [![devel-Status][devel-build-status-image]][devel-build-status] | [![2.3.x-Status][2.3.x-build-status-image]][2.3.x-build-status] | [![2.2.x-Status][2.2.x-build-status-image]][2.2.x-build-status] | - | - | - | - |
|
|
102
|
-
| Documentation | [![devel-Docs][rtd-devel-image]][docs-devel] | [![2.
|
|
102
|
+
| Sources | [![devel][sources-devel-image]][sources-devel] | [![2.4.x][sources-2.4.x-image]][sources-2.4.x] | [![2.3.x][sources-2.3.x-image]][sources-2.3.x] | [![2.2.x][sources-2.2.x-image]][sources-2.2.x] | [![2.1.x][sources-2.1.x-image]][sources-2.1.x] | [![2.0.x][sources-2.0.x-image]][sources-2.0.x] |
|
|
103
|
+
| Compatibility | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.12][python312-image]][python312-docs]<br/>[![Python 3.10][python310-image]][python310-docs] | [![Python 3.10][python310-image]][python310-docs]<br/>[![Python 3.8][python38-image]][python38-docs] | [![Python 3.6][python36-image]][python36-docs] | [![Python 3.6][python36-image]][python36-docs] | [![Python 3.6][python36-image]][python36-docs] |
|
|
104
|
+
| CI | [![devel-Status][devel-build-status-image]][devel-build-status] | [![2.4.x-Status][2.4.x-build-status-image]][2.4.x-build-status] | [![2.3.x-Status][2.3.x-build-status-image]][2.3.x-build-status] | [![2.2.x-Status][2.2.x-build-status-image]][2.2.x-build-status] | - | - | - | - |
|
|
105
|
+
| Documentation | [![devel-Docs][rtd-devel-image]][docs-devel] | [![2.4.x-Docs][rtd-2.4.x-image]][docs-2.4.x] | [![2.3.x-Docs][rtd-2.3.x-image]][docs-2.3.x] | [![2.2.x-Docs][rtd-2.2.x-image]][docs-2.2.x] | [![2.1.x-Docs][rtd-2.1.x-image]][docs-2.1.x] | [![2.0.x-Docs][rtd-2.0.x-image]][docs-2.0.x] |
|
|
103
106
|
|
|
104
107
|
|
|
105
108
|
[license-image]: https://img.shields.io/badge/License-BSD%203--Clause-orange.svg?style=plastic
|
|
@@ -116,12 +119,15 @@ Robotics:
|
|
|
116
119
|
|
|
117
120
|
[devel-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg
|
|
118
121
|
[devel-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
122
|
+
[2.4.x-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg??branch=release/2.4.x
|
|
123
|
+
[2.4.x-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
119
124
|
[2.3.x-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg??branch=release/2.3.x
|
|
120
125
|
[2.3.x-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
121
126
|
[2.2.x-build-status-image]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml/badge.svg??branch=release/2.2.x
|
|
122
127
|
[2.2.x-build-status]: https://github.com/splintered-reality/py_trees/actions/workflows/pre-merge.yaml
|
|
123
128
|
|
|
124
129
|
[docs-devel]: http://py-trees.readthedocs.io/
|
|
130
|
+
[docs-2.4.x]: http://py-trees.readthedocs.io/en/release-2.4.x/
|
|
125
131
|
[docs-2.3.x]: http://py-trees.readthedocs.io/en/release-2.3.x/
|
|
126
132
|
[docs-2.2.x]: http://py-trees.readthedocs.io/en/release-2.2.x/
|
|
127
133
|
[docs-2.1.x]: http://py-trees.readthedocs.io/en/release-2.1.x/
|
|
@@ -133,6 +139,7 @@ Robotics:
|
|
|
133
139
|
[docs-0.5.x]: http://docs.ros.org/kinetic/api/py_trees/html/
|
|
134
140
|
|
|
135
141
|
[docs-devel-image]: http://img.shields.io/badge/docs-devel-brightgreen.svg?style=plastic
|
|
142
|
+
[docs-2.4.x-image]: http://img.shields.io/badge/docs-2.4.x-brightgreen.svg?style=plastic
|
|
136
143
|
[docs-2.3.x-image]: http://img.shields.io/badge/docs-2.3.x-brightgreen.svg?style=plastic
|
|
137
144
|
[docs-2.2.x-image]: http://img.shields.io/badge/docs-2.2.x-brightgreen.svg?style=plastic
|
|
138
145
|
[docs-2.1.x-image]: http://img.shields.io/badge/docs-2.1.x-brightgreen.svg?style=plastic
|
|
@@ -144,6 +151,7 @@ Robotics:
|
|
|
144
151
|
[docs-0.5.x-image]: http://img.shields.io/badge/docs-0.5.x-brightgreen.svg?style=plastic
|
|
145
152
|
|
|
146
153
|
[rtd-devel-image]: https://readthedocs.org/projects/py-trees/badge/?version=devel&style=plastic
|
|
154
|
+
[rtd-2.4.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.4.x&style=plastic
|
|
147
155
|
[rtd-2.3.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.3.x&style=plastic
|
|
148
156
|
[rtd-2.2.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.2.x&style=plastic
|
|
149
157
|
[rtd-2.1.x-image]: https://readthedocs.org/projects/py-trees/badge/?version=release-2.1.x&style=plastic
|
|
@@ -156,6 +164,7 @@ Robotics:
|
|
|
156
164
|
[not-available-docs-image]: http://img.shields.io/badge/docs-n/a-yellow.svg?style=plastic
|
|
157
165
|
|
|
158
166
|
[sources-devel]: https://github.com/splintered-reality/py_trees/tree/devel
|
|
167
|
+
[sources-2.4.x]: https://github.com/splintered-reality/py_trees/tree/release/2.4.x
|
|
159
168
|
[sources-2.3.x]: https://github.com/splintered-reality/py_trees/tree/release/2.3.x
|
|
160
169
|
[sources-2.2.x]: https://github.com/splintered-reality/py_trees/tree/release/2.2.x
|
|
161
170
|
[sources-2.1.x]: https://github.com/splintered-reality/py_trees/tree/release/2.1.x
|
|
@@ -167,6 +176,7 @@ Robotics:
|
|
|
167
176
|
[sources-0.5.x]: https://github.com/splintered-reality/py_trees/tree/release/0.5.x
|
|
168
177
|
|
|
169
178
|
[sources-devel-image]: http://img.shields.io/badge/sources-devel-blue.svg?style=plastic
|
|
179
|
+
[sources-2.4.x-image]: http://img.shields.io/badge/sources-2.4.x-blue.svg?style=plastic
|
|
170
180
|
[sources-2.3.x-image]: http://img.shields.io/badge/sources-2.3.x-blue.svg?style=plastic
|
|
171
181
|
[sources-2.2.x-image]: http://img.shields.io/badge/sources-2.2.x-blue.svg?style=plastic
|
|
172
182
|
[sources-2.1.x-image]: http://img.shields.io/badge/sources-2.1.x-blue.svg?style=plastic
|
|
@@ -146,7 +146,7 @@ class Behaviour(abc.ABC):
|
|
|
146
146
|
|
|
147
147
|
Args:
|
|
148
148
|
**kwargs: distribute arguments to this
|
|
149
|
-
behaviour and in turn, all of
|
|
149
|
+
behaviour and in turn, all of its children
|
|
150
150
|
|
|
151
151
|
Raises:
|
|
152
152
|
Exception: if this behaviour has a fault in construction or configuration
|
|
@@ -261,7 +261,7 @@ class Behaviour(abc.ABC):
|
|
|
261
261
|
############################################
|
|
262
262
|
|
|
263
263
|
def setup_with_descendants(self) -> None:
|
|
264
|
-
"""Call setup on this child,
|
|
264
|
+
"""Call setup on this child, its children (its children's children, )."""
|
|
265
265
|
for child in self.children:
|
|
266
266
|
for node in child.iterate():
|
|
267
267
|
node.setup()
|
|
@@ -321,7 +321,7 @@ class Behaviour(abc.ABC):
|
|
|
321
321
|
|
|
322
322
|
def iterate(self, direct_descendants: bool = False) -> typing.Iterator[Behaviour]:
|
|
323
323
|
"""
|
|
324
|
-
Iterate over this child and
|
|
324
|
+
Iterate over this child and its children.
|
|
325
325
|
|
|
326
326
|
This utilises python generators for looping. To traverse the entire tree:
|
|
327
327
|
|
|
@@ -334,7 +334,7 @@ class Behaviour(abc.ABC):
|
|
|
334
334
|
direct_descendants (:obj:`bool`): only yield children one step away from this behaviour.
|
|
335
335
|
|
|
336
336
|
Yields:
|
|
337
|
-
:class:`~py_trees.behaviour.Behaviour`: one of
|
|
337
|
+
:class:`~py_trees.behaviour.Behaviour`: one of its children
|
|
338
338
|
"""
|
|
339
339
|
for child in self.children:
|
|
340
340
|
if not direct_descendants:
|
|
@@ -109,7 +109,7 @@ Crash test dummy used for anything dangerous.
|
|
|
109
109
|
|
|
110
110
|
class Periodic(behaviour.Behaviour):
|
|
111
111
|
"""
|
|
112
|
-
Simply periodically rotates
|
|
112
|
+
Simply periodically rotates its status over all each status.
|
|
113
113
|
|
|
114
114
|
That is, :data:`~py_trees.common.Status.RUNNING` for N ticks,
|
|
115
115
|
:data:`~py_trees.common.Status.SUCCESS` for N ticks,
|
|
@@ -210,7 +210,7 @@ class SuccessEveryN(behaviour.Behaviour):
|
|
|
210
210
|
"""
|
|
211
211
|
Non-blocking, periodic success.
|
|
212
212
|
|
|
213
|
-
This behaviour updates
|
|
213
|
+
This behaviour updates its status with :data:`~py_trees.common.Status.SUCCESS`
|
|
214
214
|
once every N ticks, :data:`~py_trees.common.Status.FAILURE` otherwise.
|
|
215
215
|
|
|
216
216
|
Args:
|
|
@@ -570,10 +570,10 @@ class CheckBlackboardVariableValue(behaviour.Behaviour):
|
|
|
570
570
|
"""
|
|
571
571
|
self.logger.debug("%s.update()" % self.__class__.__name__)
|
|
572
572
|
try:
|
|
573
|
-
|
|
573
|
+
lhs_value = self.blackboard.get(self.key)
|
|
574
574
|
if self.key_attributes:
|
|
575
575
|
try:
|
|
576
|
-
|
|
576
|
+
lhs_value = operator.attrgetter(self.key_attributes)(lhs_value)
|
|
577
577
|
except AttributeError:
|
|
578
578
|
self.feedback_message = (
|
|
579
579
|
"blackboard key-value pair exists, but the value does not "
|
|
@@ -588,20 +588,95 @@ class CheckBlackboardVariableValue(behaviour.Behaviour):
|
|
|
588
588
|
)
|
|
589
589
|
return common.Status.FAILURE
|
|
590
590
|
|
|
591
|
-
|
|
591
|
+
rhs_value = self.check.value_generator()
|
|
592
|
+
success = self.check.operator(lhs_value, rhs_value)
|
|
592
593
|
|
|
593
594
|
if success:
|
|
594
595
|
self.feedback_message = "'%s' comparison succeeded [v: %s][e: %s]" % (
|
|
595
596
|
self.check.variable,
|
|
596
|
-
|
|
597
|
-
|
|
597
|
+
lhs_value,
|
|
598
|
+
rhs_value,
|
|
598
599
|
)
|
|
599
600
|
return common.Status.SUCCESS
|
|
600
601
|
else:
|
|
601
602
|
self.feedback_message = "'%s' comparison failed [v: %s][e: %s]" % (
|
|
602
603
|
self.check.variable,
|
|
603
|
-
|
|
604
|
-
|
|
604
|
+
lhs_value,
|
|
605
|
+
rhs_value,
|
|
606
|
+
)
|
|
607
|
+
return common.Status.FAILURE
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
class CompareBlackboardVariables(behaviour.Behaviour):
|
|
611
|
+
"""
|
|
612
|
+
Non-blocking comparison between two blackboard variables.
|
|
613
|
+
|
|
614
|
+
Gets two blackboard variables and applies the given operator between them,
|
|
615
|
+
returning SUCCESS if the comparison holds.
|
|
616
|
+
This is non-blocking, so it will always tick with
|
|
617
|
+
:data:`~py_trees.common.Status.SUCCESS` or
|
|
618
|
+
:data:`~py_trees.common.Status.FAILURE`.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
name: name of the behaviour
|
|
622
|
+
var1_key: blackboard key for the first variable
|
|
623
|
+
var2_key: blackboard key for the second variable
|
|
624
|
+
operator: a callable comparison operator
|
|
625
|
+
|
|
626
|
+
.. note::
|
|
627
|
+
If the variables do not yet exist on the blackboard, the behaviour will
|
|
628
|
+
return with status :data:`~py_trees.common.Status.FAILURE`.
|
|
629
|
+
|
|
630
|
+
.. tip::
|
|
631
|
+
The python `operator module`_ includes many useful comparison operations.
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
def __init__(
|
|
635
|
+
self,
|
|
636
|
+
name: str,
|
|
637
|
+
var1_key: str,
|
|
638
|
+
var2_key: str,
|
|
639
|
+
operator: typing.Callable[[common.ComparisonV, common.ComparisonV], bool],
|
|
640
|
+
):
|
|
641
|
+
super().__init__(name=name)
|
|
642
|
+
|
|
643
|
+
self.var1_key = var1_key
|
|
644
|
+
self.var2_key = var2_key
|
|
645
|
+
self.operator = operator
|
|
646
|
+
self.blackboard = self.attach_blackboard_client()
|
|
647
|
+
self.blackboard.register_key(key=self.var1_key, access=common.Access.READ)
|
|
648
|
+
self.blackboard.register_key(key=self.var2_key, access=common.Access.READ)
|
|
649
|
+
|
|
650
|
+
def update(self) -> common.Status:
|
|
651
|
+
"""
|
|
652
|
+
Check for the two variables and applies the operator between them.
|
|
653
|
+
|
|
654
|
+
Returns:
|
|
655
|
+
:class:`~py_trees.common.Status`: :data:`~py_trees.common.Status.FAILURE`
|
|
656
|
+
if not matched, :data:`~py_trees.common.Status.SUCCESS` otherwise.
|
|
657
|
+
"""
|
|
658
|
+
self.logger.debug("%s.update()" % self.__class__.__name__)
|
|
659
|
+
try:
|
|
660
|
+
lhs_value = self.blackboard.get(self.var1_key)
|
|
661
|
+
rhs_value = self.blackboard.get(self.var2_key)
|
|
662
|
+
except KeyError as err:
|
|
663
|
+
self.logger.error(str(err))
|
|
664
|
+
return common.Status.FAILURE
|
|
665
|
+
|
|
666
|
+
if self.operator(lhs_value, rhs_value):
|
|
667
|
+
self.feedback_message = "'%s, %s' comparison succeeded [v1: %s][v2: %s]" % (
|
|
668
|
+
self.var1_key,
|
|
669
|
+
self.var2_key,
|
|
670
|
+
lhs_value,
|
|
671
|
+
rhs_value,
|
|
672
|
+
)
|
|
673
|
+
return common.Status.SUCCESS
|
|
674
|
+
else:
|
|
675
|
+
self.feedback_message = "'%s, %s' comparison failed [v1: %s][v2: %s]" % (
|
|
676
|
+
self.var1_key,
|
|
677
|
+
self.var2_key,
|
|
678
|
+
lhs_value,
|
|
679
|
+
rhs_value,
|
|
605
680
|
)
|
|
606
681
|
return common.Status.FAILURE
|
|
607
682
|
|
|
@@ -722,7 +797,7 @@ class CheckBlackboardVariableValues(behaviour.Behaviour):
|
|
|
722
797
|
)
|
|
723
798
|
)
|
|
724
799
|
return common.Status.FAILURE
|
|
725
|
-
results.append(check.operator(value, check.
|
|
800
|
+
results.append(check.operator(value, check.value_generator()))
|
|
726
801
|
if self.blackboard_results is not None:
|
|
727
802
|
for counter in range(1, len(results) + 1):
|
|
728
803
|
self.blackboard_results.set(str(counter), results[counter - 1])
|
|
@@ -188,7 +188,7 @@ class Blackboard(object):
|
|
|
188
188
|
Centralised key-value store for sharing data between behaviours.
|
|
189
189
|
|
|
190
190
|
This class is a coat-hanger for the centralised data store, metadata
|
|
191
|
-
for
|
|
191
|
+
for its administration and static methods for interacting with it.
|
|
192
192
|
|
|
193
193
|
This api is intended for authors of debugging and introspection
|
|
194
194
|
tools on the blackboard. Users should make use of the :class:`Client`.
|
|
@@ -234,7 +234,7 @@ class Blackboard(object):
|
|
|
234
234
|
variable_name: of the variable to get, can be nested, e.g. battery.percentage
|
|
235
235
|
|
|
236
236
|
Raises:
|
|
237
|
-
KeyError: if the variable or
|
|
237
|
+
KeyError: if the variable or its nested attributes do not yet exist on the blackboard
|
|
238
238
|
|
|
239
239
|
Return:
|
|
240
240
|
The stored value for the given variable
|
|
@@ -528,7 +528,7 @@ class Blackboard(object):
|
|
|
528
528
|
variable_name: blackboard variable name - can be nested, e.g. battery.percentage
|
|
529
529
|
|
|
530
530
|
Returns:
|
|
531
|
-
a tuple consisting of the key and
|
|
531
|
+
a tuple consisting of the key and its attributes (in string form)
|
|
532
532
|
"""
|
|
533
533
|
name_components = variable_name.split(".")
|
|
534
534
|
key = name_components[0]
|
|
@@ -986,7 +986,7 @@ class Client(object):
|
|
|
986
986
|
opportunity followed by higher priority behaviours in the tree and so forth.
|
|
987
987
|
Lower priority behaviours would use this to respect the pre-configured
|
|
988
988
|
setting and at most, just validate that it is acceptable to the functionality
|
|
989
|
-
of
|
|
989
|
+
of its own behaviour.
|
|
990
990
|
|
|
991
991
|
Args:
|
|
992
992
|
name: name of the variable to set
|
|
@@ -1092,7 +1092,7 @@ class Client(object):
|
|
|
1092
1092
|
|
|
1093
1093
|
Raises:
|
|
1094
1094
|
AttributeError: if the client does not have read access to the variable
|
|
1095
|
-
KeyError: if the variable or
|
|
1095
|
+
KeyError: if the variable or its nested attributes do not yet exist on the blackboard
|
|
1096
1096
|
"""
|
|
1097
1097
|
# key attributes is an empty string if not a nested variable name
|
|
1098
1098
|
name_components = name.split(".")
|
|
@@ -195,7 +195,7 @@ class ComparisonExpression(object):
|
|
|
195
195
|
|
|
196
196
|
Args:
|
|
197
197
|
variable: name of the variable to compare
|
|
198
|
-
value: value to compare against
|
|
198
|
+
value: value or callable to compare against
|
|
199
199
|
operator: a callable comparison operator
|
|
200
200
|
|
|
201
201
|
.. tip::
|
|
@@ -206,27 +206,27 @@ class ComparisonExpression(object):
|
|
|
206
206
|
|
|
207
207
|
check = ComparisonExpression(
|
|
208
208
|
variable="foo",
|
|
209
|
-
value=
|
|
209
|
+
value=5,
|
|
210
210
|
operator=operator.eq
|
|
211
211
|
)
|
|
212
|
-
success = check.operator(blackboard[check.variable], check.
|
|
212
|
+
success = check.operator(blackboard[check.variable], check.value_generator())
|
|
213
213
|
"""
|
|
214
214
|
|
|
215
215
|
def __init__(
|
|
216
216
|
self,
|
|
217
217
|
variable: str,
|
|
218
|
-
value: ComparisonV,
|
|
218
|
+
value: typing.Union[ComparisonV, typing.Callable[[], ComparisonV]],
|
|
219
219
|
operator: typing.Callable[[ComparisonV, ComparisonV], bool],
|
|
220
220
|
):
|
|
221
221
|
"""Initialise variables.
|
|
222
222
|
|
|
223
223
|
Args:
|
|
224
224
|
variable: the variable name to use
|
|
225
|
-
value: the value to use on the RHS of the expression
|
|
225
|
+
value: the value to use on the RHS of the expression (can also be a callable that returns the value)
|
|
226
226
|
operator: a logical operator to enable comparisons
|
|
227
227
|
"""
|
|
228
228
|
self.variable = variable
|
|
229
|
-
self.
|
|
229
|
+
self.value_generator = value if callable(value) else lambda: value
|
|
230
230
|
self.operator = operator
|
|
231
231
|
|
|
232
232
|
|
|
@@ -120,7 +120,7 @@ class Composite(behaviour.Behaviour, abc.ABC):
|
|
|
120
120
|
behaviours do the real work.
|
|
121
121
|
|
|
122
122
|
Such flows are a consequence of how the composite
|
|
123
|
-
interacts with
|
|
123
|
+
interacts with its children. The success of
|
|
124
124
|
behaviour trees depends on this logic being simple,
|
|
125
125
|
well defined and limited to a few well established
|
|
126
126
|
patterns - this is what ensures that visualising
|
|
@@ -391,7 +391,7 @@ class Selector(Composite):
|
|
|
391
391
|
Customise the tick behaviour for a selector.
|
|
392
392
|
|
|
393
393
|
This implements priority-interrupt style handling amongst the selector's children.
|
|
394
|
-
The selector's status is always a reflection of
|
|
394
|
+
The selector's status is always a reflection of its children's status.
|
|
395
395
|
|
|
396
396
|
Yields:
|
|
397
397
|
:class:`~py_trees.behaviour.Behaviour`: a reference to itself or one of its children
|
|
@@ -427,7 +427,8 @@ class Selector(Composite):
|
|
|
427
427
|
# clear out preceding status' - not actually necessary but helps
|
|
428
428
|
# visualise the case of memory vs no memory
|
|
429
429
|
for child in itertools.islice(self.children, None, index):
|
|
430
|
-
child.
|
|
430
|
+
if child.status != common.Status.INVALID:
|
|
431
|
+
child.stop(common.Status.INVALID)
|
|
431
432
|
else:
|
|
432
433
|
index = 0
|
|
433
434
|
|
|
@@ -442,7 +443,6 @@ class Selector(Composite):
|
|
|
442
443
|
or node.status == common.Status.SUCCESS
|
|
443
444
|
):
|
|
444
445
|
self.current_child = child
|
|
445
|
-
self.status = node.status
|
|
446
446
|
if previous is None or previous != self.current_child:
|
|
447
447
|
# we interrupted, invalidate everything at a lower priority
|
|
448
448
|
passed = False
|
|
@@ -451,10 +451,18 @@ class Selector(Composite):
|
|
|
451
451
|
if child.status != common.Status.INVALID:
|
|
452
452
|
child.stop(common.Status.INVALID)
|
|
453
453
|
passed = True if child == self.current_child else passed
|
|
454
|
+
|
|
455
|
+
# terminate the selector if a terminal state was reached
|
|
456
|
+
if node.status == common.Status.SUCCESS:
|
|
457
|
+
self.stop(node.status)
|
|
458
|
+
else:
|
|
459
|
+
self.status = node.status
|
|
460
|
+
|
|
454
461
|
yield self
|
|
455
462
|
return
|
|
463
|
+
|
|
456
464
|
# all children failed, set failure ourselves and current child to the last bugger who failed us
|
|
457
|
-
self.
|
|
465
|
+
self.stop(common.Status.FAILURE)
|
|
458
466
|
try:
|
|
459
467
|
self.current_child = self.children[-1]
|
|
460
468
|
except IndexError:
|
|
@@ -539,10 +547,9 @@ class Sequence(Composite):
|
|
|
539
547
|
if child.status != common.Status.INVALID:
|
|
540
548
|
child.stop(common.Status.INVALID)
|
|
541
549
|
self.initialise() # user specific initialisation
|
|
542
|
-
elif self.memory and
|
|
543
|
-
assert self.current_child is not None # should never be true, help mypy out
|
|
550
|
+
elif self.memory and self.current_child is not None:
|
|
544
551
|
index = self.children.index(self.current_child)
|
|
545
|
-
elif not self.memory
|
|
552
|
+
elif not self.memory:
|
|
546
553
|
self.current_child = self.children[0] if self.children else None
|
|
547
554
|
else:
|
|
548
555
|
# previous conditional checks should cover all variations
|
|
@@ -560,13 +567,19 @@ class Sequence(Composite):
|
|
|
560
567
|
for node in child.tick():
|
|
561
568
|
yield node
|
|
562
569
|
if node is child and node.status != common.Status.SUCCESS:
|
|
563
|
-
self.status = node.status
|
|
564
570
|
if not self.memory:
|
|
565
571
|
# invalidate the remainder of the sequence
|
|
566
572
|
# i.e. kill dangling runners
|
|
567
573
|
for child in itertools.islice(self.children, index + 1, None):
|
|
568
574
|
if child.status != common.Status.INVALID:
|
|
569
575
|
child.stop(common.Status.INVALID)
|
|
576
|
+
|
|
577
|
+
# stop the sequence if a terminal (non-success) state was reached
|
|
578
|
+
if node.status != common.Status.RUNNING:
|
|
579
|
+
self.stop(node.status)
|
|
580
|
+
else:
|
|
581
|
+
self.status = node.status
|
|
582
|
+
|
|
570
583
|
yield self
|
|
571
584
|
return
|
|
572
585
|
try:
|
|
@@ -660,13 +673,13 @@ class Parallel(Composite):
|
|
|
660
673
|
super(Parallel, self).__init__(name, children)
|
|
661
674
|
self.policy = policy
|
|
662
675
|
|
|
663
|
-
def setup(self, **kwargs:
|
|
676
|
+
def setup(self, **kwargs: typing.Any) -> None:
|
|
664
677
|
"""
|
|
665
678
|
Detect before ticking whether the policy configuration is invalid.
|
|
666
679
|
|
|
667
680
|
Args:
|
|
668
681
|
**kwargs (:obj:`dict`): distribute arguments to this
|
|
669
|
-
behaviour and in turn, all of
|
|
682
|
+
behaviour and in turn, all of its children
|
|
670
683
|
|
|
671
684
|
Raises:
|
|
672
685
|
RuntimeError: if the parallel's policy configuration is invalid
|
|
@@ -726,17 +739,17 @@ class Parallel(Composite):
|
|
|
726
739
|
self.current_child = failed_child
|
|
727
740
|
new_status = common.Status.FAILURE
|
|
728
741
|
except StopIteration:
|
|
729
|
-
if
|
|
730
|
-
if all(
|
|
742
|
+
if isinstance(self.policy, common.ParallelPolicy.SuccessOnAll):
|
|
743
|
+
if all(c.status == common.Status.SUCCESS for c in self.children):
|
|
731
744
|
new_status = common.Status.SUCCESS
|
|
732
745
|
self.current_child = self.children[-1]
|
|
733
|
-
elif
|
|
746
|
+
elif isinstance(self.policy, common.ParallelPolicy.SuccessOnOne):
|
|
734
747
|
for child in reversed(self.children):
|
|
735
748
|
if child.status == common.Status.SUCCESS:
|
|
736
749
|
new_status = common.Status.SUCCESS
|
|
737
750
|
self.current_child = child
|
|
738
751
|
break
|
|
739
|
-
elif
|
|
752
|
+
elif isinstance(self.policy, common.ParallelPolicy.SuccessOnSelected):
|
|
740
753
|
if all(
|
|
741
754
|
[c.status == common.Status.SUCCESS for c in self.policy.children]
|
|
742
755
|
):
|
|
@@ -770,7 +783,7 @@ class Parallel(Composite):
|
|
|
770
783
|
# clean up dangling (running) children
|
|
771
784
|
for child in self.children:
|
|
772
785
|
if child.status == common.Status.RUNNING:
|
|
773
|
-
# this unfortunately knocks out
|
|
786
|
+
# this unfortunately knocks out its running status for introspection
|
|
774
787
|
# but logically is the correct thing to do, see #132.
|
|
775
788
|
child.stop(common.Status.INVALID)
|
|
776
789
|
Composite.stop(self, new_status)
|
|
@@ -786,7 +799,7 @@ class Parallel(Composite):
|
|
|
786
799
|
Raises:
|
|
787
800
|
RuntimeError: if policy configuration was invalid
|
|
788
801
|
"""
|
|
789
|
-
if
|
|
802
|
+
if isinstance(self.policy, common.ParallelPolicy.SuccessOnSelected):
|
|
790
803
|
if not self.policy.children:
|
|
791
804
|
error_message = (
|
|
792
805
|
"policy SuccessOnSelected requires a non-empty "
|
|
@@ -36,6 +36,7 @@ Decorators with specific functionality:
|
|
|
36
36
|
* :class:`py_trees.decorators.Condition`
|
|
37
37
|
* :class:`py_trees.decorators.Count`
|
|
38
38
|
* :class:`py_trees.decorators.EternalGuard`
|
|
39
|
+
* :class:`py_trees.decorators.ForEach`
|
|
39
40
|
* :class:`py_trees.decorators.Inverter`
|
|
40
41
|
* :class:`py_trees.decorators.OneShot`
|
|
41
42
|
* :class:`py_trees.decorators.Repeat`
|
|
@@ -61,7 +62,7 @@ or :data:`~py_trees.common.Status.FAILURE`.
|
|
|
61
62
|
|
|
62
63
|
A decorator, such as :func:`py_trees.decorators.RunningIsSuccess` on
|
|
63
64
|
a blocking behaviour will immediately terminate the underlying child and
|
|
64
|
-
re-intialise on
|
|
65
|
+
re-intialise on its next tick. This is often surprising (to the user) but
|
|
65
66
|
is necessary to ensure the underlying child isn't left in a dangling state (i.e.
|
|
66
67
|
:data:`~py_trees.common.Status.RUNNING`) as subsequent ticks move on to other
|
|
67
68
|
parts of the tree.
|
|
@@ -80,6 +81,7 @@ import functools
|
|
|
80
81
|
import inspect
|
|
81
82
|
import time
|
|
82
83
|
import typing
|
|
84
|
+
from collections.abc import Iterable
|
|
83
85
|
|
|
84
86
|
from . import behaviour, blackboard, common
|
|
85
87
|
|
|
@@ -473,7 +475,7 @@ class Timeout(Decorator):
|
|
|
473
475
|
:meth:`~py_trees.behaviour.Behaviour.stop` method is called with
|
|
474
476
|
status :data:`~py_trees.common.Status.FAILURE` otherwise it will
|
|
475
477
|
simply directly tick and return with the same status
|
|
476
|
-
as that of
|
|
478
|
+
as that of its encapsulated behaviour.
|
|
477
479
|
"""
|
|
478
480
|
|
|
479
481
|
def __init__(self, name: str, child: behaviour.Behaviour, duration: float = 5.0):
|
|
@@ -528,10 +530,10 @@ class Timeout(Decorator):
|
|
|
528
530
|
|
|
529
531
|
class Count(Decorator):
|
|
530
532
|
"""
|
|
531
|
-
Count the number of times
|
|
533
|
+
Count the number of times its child has been ticked.
|
|
532
534
|
|
|
533
535
|
This increments counters tracking the total number of times
|
|
534
|
-
|
|
536
|
+
its child has been ticked as well as the number of times it
|
|
535
537
|
has landed in each respective state.
|
|
536
538
|
|
|
537
539
|
It will always re-zero counters on
|
|
@@ -560,7 +562,7 @@ class Count(Decorator):
|
|
|
560
562
|
self.running_count = 0
|
|
561
563
|
self.interrupt_count = 0
|
|
562
564
|
|
|
563
|
-
def setup(self, **kwargs:
|
|
565
|
+
def setup(self, **kwargs: typing.Any) -> None:
|
|
564
566
|
"""Reset the counters."""
|
|
565
567
|
self.total_tick_count = 0
|
|
566
568
|
self.failure_count = 0
|
|
@@ -619,7 +621,7 @@ class OneShot(Decorator):
|
|
|
619
621
|
|
|
620
622
|
This decorator ensures that the underlying child is ticked through
|
|
621
623
|
to completion just once and while doing so, will return
|
|
622
|
-
with the same status as
|
|
624
|
+
with the same status as its child. Thereafter it will return
|
|
623
625
|
with the final status of the underlying child.
|
|
624
626
|
|
|
625
627
|
Completion status is determined by the policy given on construction.
|
|
@@ -664,7 +666,7 @@ class OneShot(Decorator):
|
|
|
664
666
|
Tick the child or bounce back with the original status if already completed.
|
|
665
667
|
|
|
666
668
|
Yields:
|
|
667
|
-
a reference to itself or a behaviour in
|
|
669
|
+
a reference to itself or a behaviour in its child subtree
|
|
668
670
|
"""
|
|
669
671
|
if self.final_status:
|
|
670
672
|
# ignore the child
|
|
@@ -856,7 +858,7 @@ class Condition(Decorator):
|
|
|
856
858
|
"""
|
|
857
859
|
A blocking conditional decorator.
|
|
858
860
|
|
|
859
|
-
Encapsulates a behaviour and wait for
|
|
861
|
+
Encapsulates a behaviour and wait for its status to flip to the
|
|
860
862
|
desired state. This behaviour will tick with
|
|
861
863
|
:data:`~py_trees.common.Status.RUNNING` while waiting and
|
|
862
864
|
:data:`~py_trees.common.Status.SUCCESS` when the flip occurs.
|
|
@@ -920,3 +922,74 @@ class PassThrough(Decorator):
|
|
|
920
922
|
the behaviour's new status :class:`~py_trees.common.Status`
|
|
921
923
|
"""
|
|
922
924
|
return self.decorated.status
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
class ForEach(Decorator):
|
|
928
|
+
"""
|
|
929
|
+
Run the child behavior for each item in an iterable.
|
|
930
|
+
|
|
931
|
+
On initialization, the iterable is loaded from the blackboard and an
|
|
932
|
+
iterator is created. Every time the child succeeds, we advance to
|
|
933
|
+
the next item. We keep running until the iterable is exhausted.
|
|
934
|
+
"""
|
|
935
|
+
|
|
936
|
+
def __init__(
|
|
937
|
+
self, name: str, child: behaviour.Behaviour, source_key: str, target_key: str
|
|
938
|
+
):
|
|
939
|
+
"""
|
|
940
|
+
Initialise the ForEach decorator.
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
name (:obj:`str`): name of the behaviour
|
|
944
|
+
child (:obj:`Behaviour`): the child behaviour to decorate
|
|
945
|
+
source_key (:obj:`str`): blackboard key to read the input iterable
|
|
946
|
+
target_key (:obj:`str`): blackboard key to set for each iteration
|
|
947
|
+
"""
|
|
948
|
+
super().__init__(name=name, child=child)
|
|
949
|
+
self.source_key = source_key
|
|
950
|
+
self.target_key = target_key
|
|
951
|
+
self.blackboard = blackboard.Client(name=name)
|
|
952
|
+
self.blackboard.register_key(key=self.source_key, access=common.Access.READ)
|
|
953
|
+
self.blackboard.register_key(key=self.target_key, access=common.Access.WRITE)
|
|
954
|
+
self._iterator: typing.Iterator | None = None
|
|
955
|
+
self._current_item: typing.Any | None = None
|
|
956
|
+
|
|
957
|
+
def initialise(self) -> None:
|
|
958
|
+
"""Reset iteration on first tick."""
|
|
959
|
+
iterable = self.blackboard.get(self.source_key) or []
|
|
960
|
+
if not isinstance(iterable, Iterable):
|
|
961
|
+
raise TypeError(
|
|
962
|
+
f"[{self.name}] source_key '{self.source_key}' is not an iterable"
|
|
963
|
+
)
|
|
964
|
+
self._iterator = iter(iterable)
|
|
965
|
+
self._advance()
|
|
966
|
+
|
|
967
|
+
def _advance(self) -> None:
|
|
968
|
+
"""Advance to the next item in the iterator, or mark as finished."""
|
|
969
|
+
try:
|
|
970
|
+
if self._iterator is not None:
|
|
971
|
+
self._current_item = next(self._iterator)
|
|
972
|
+
self.blackboard.set(self.target_key, self._current_item)
|
|
973
|
+
except StopIteration:
|
|
974
|
+
self._current_item = None
|
|
975
|
+
|
|
976
|
+
def update(self) -> common.Status:
|
|
977
|
+
"""Execute the child for the current item and manage iteration state."""
|
|
978
|
+
if self._current_item is None:
|
|
979
|
+
# no more items left
|
|
980
|
+
return common.Status.SUCCESS
|
|
981
|
+
|
|
982
|
+
child_status = self.decorated.status
|
|
983
|
+
|
|
984
|
+
if child_status == common.Status.SUCCESS:
|
|
985
|
+
# move to next item
|
|
986
|
+
self._advance()
|
|
987
|
+
if self._current_item is not None:
|
|
988
|
+
return common.Status.RUNNING
|
|
989
|
+
|
|
990
|
+
return child_status
|
|
991
|
+
|
|
992
|
+
def terminate(self, new_status: common.Status) -> None:
|
|
993
|
+
"""Reset on termination."""
|
|
994
|
+
self._iterator = None
|
|
995
|
+
self._current_item = None
|
|
@@ -144,7 +144,7 @@ class Action(py_trees.behaviour.Behaviour):
|
|
|
144
144
|
super(Action, self).__init__(name)
|
|
145
145
|
self.logger.debug("%s.__init__()" % (self.__class__.__name__))
|
|
146
146
|
|
|
147
|
-
def setup(self, **kwargs:
|
|
147
|
+
def setup(self, **kwargs: typing.Any) -> None:
|
|
148
148
|
"""Kickstart the separate process this behaviour will work with.
|
|
149
149
|
|
|
150
150
|
Ordinarily this process will be already running. In this case,
|
|
@@ -218,7 +218,7 @@ class ParamsAndState(py_trees.behaviour.Behaviour):
|
|
|
218
218
|
|
|
219
219
|
def create_root() -> py_trees.behaviour.Behaviour:
|
|
220
220
|
"""
|
|
221
|
-
Create the root behaviour and
|
|
221
|
+
Create the root behaviour and its subtree.
|
|
222
222
|
|
|
223
223
|
Returns:
|
|
224
224
|
the root behaviour
|
|
@@ -165,7 +165,7 @@ def main() -> None:
|
|
|
165
165
|
"-------------------------------------------------------------------------------"
|
|
166
166
|
)
|
|
167
167
|
foo.awesome = True
|
|
168
|
-
# Only
|
|
168
|
+
# Only accessible via set since it's not in the namespace
|
|
169
169
|
foo.set("/brilliant", False)
|
|
170
170
|
# This will fail since it looks for the namespaced /foo/brilliant key
|
|
171
171
|
# foo.brilliant = False
|
|
@@ -163,7 +163,7 @@ class ContextSwitch(py_trees.behaviour.Behaviour):
|
|
|
163
163
|
|
|
164
164
|
def create_root() -> py_trees.behaviour.Behaviour:
|
|
165
165
|
"""
|
|
166
|
-
Create the root behaviour and
|
|
166
|
+
Create the root behaviour and its subtree.
|
|
167
167
|
|
|
168
168
|
Returns:
|
|
169
169
|
the root behaviour
|
|
@@ -140,7 +140,7 @@ def command_line_argument_parser() -> argparse.ArgumentParser:
|
|
|
140
140
|
|
|
141
141
|
def create_tree(level: str) -> py_trees.behaviour.Behaviour:
|
|
142
142
|
"""
|
|
143
|
-
Create the root behaviour and
|
|
143
|
+
Create the root behaviour and its subtree.
|
|
144
144
|
|
|
145
145
|
Returns:
|
|
146
146
|
the root behaviour
|
|
@@ -107,7 +107,7 @@ class Counter(py_trees.behaviour.Behaviour):
|
|
|
107
107
|
super(Counter, self).__init__(name)
|
|
108
108
|
self.logger.debug("%s.__init__()" % (self.__class__.__name__))
|
|
109
109
|
|
|
110
|
-
def setup(self, **kwargs:
|
|
110
|
+
def setup(self, **kwargs: typing.Any) -> None:
|
|
111
111
|
"""No delayed initialisation required for this example."""
|
|
112
112
|
self.logger.debug("%s.setup()" % (self.__class__.__name__))
|
|
113
113
|
|
|
@@ -104,7 +104,7 @@ def command_line_argument_parser() -> argparse.ArgumentParser:
|
|
|
104
104
|
|
|
105
105
|
def create_root() -> py_trees.behaviour.Behaviour:
|
|
106
106
|
"""
|
|
107
|
-
Create the root behaviour and
|
|
107
|
+
Create the root behaviour and its subtree.
|
|
108
108
|
|
|
109
109
|
Returns:
|
|
110
110
|
the root behaviour
|
|
@@ -101,7 +101,7 @@ def command_line_argument_parser() -> argparse.ArgumentParser:
|
|
|
101
101
|
|
|
102
102
|
def create_root() -> py_trees.behaviour.Behaviour:
|
|
103
103
|
"""
|
|
104
|
-
Create the root behaviour and
|
|
104
|
+
Create the root behaviour and its subtree.
|
|
105
105
|
|
|
106
106
|
Returns:
|
|
107
107
|
the root behaviour
|
|
@@ -86,7 +86,7 @@ def tick_tree(
|
|
|
86
86
|
to_tick: with from_tick, used to determine the # ticks required
|
|
87
87
|
visitors: a list of visitors to run on each tree tick
|
|
88
88
|
print_snapshot: print ascii/unicode snapshots after each tick
|
|
89
|
-
print_blackboard: display the blackboard and
|
|
89
|
+
print_blackboard: display the blackboard and its update after each tick
|
|
90
90
|
"""
|
|
91
91
|
if visitors is None:
|
|
92
92
|
visitors = []
|
|
@@ -51,7 +51,7 @@ def setup(
|
|
|
51
51
|
root: behaviour.Behaviour,
|
|
52
52
|
timeout: typing.Union[float, common.Duration] = common.Duration.INFINITE,
|
|
53
53
|
visitor: typing.Optional[visitors.VisitorBase] = None,
|
|
54
|
-
**kwargs:
|
|
54
|
+
**kwargs: typing.Any,
|
|
55
55
|
) -> None:
|
|
56
56
|
"""
|
|
57
57
|
Crawl across a (sub)tree of behaviours calling :meth:`~py_trees.behaviour.Behaviour.setup` on each behaviour.
|
|
@@ -65,7 +65,7 @@ def setup(
|
|
|
65
65
|
Args:
|
|
66
66
|
root: unmanaged (sub)tree root behaviour
|
|
67
67
|
timeout: time (s) to wait (use common.Duration.INFINITE to block indefinitely)
|
|
68
|
-
visitor: runnable entities on each node after
|
|
68
|
+
visitor: runnable entities on each node after its setup
|
|
69
69
|
**kwargs: dictionary of args to distribute to all behaviours in the (sub)tree
|
|
70
70
|
|
|
71
71
|
Raises:
|
|
@@ -355,7 +355,7 @@ class BehaviourTree(object):
|
|
|
355
355
|
self,
|
|
356
356
|
timeout: typing.Union[float, common.Duration] = common.Duration.INFINITE,
|
|
357
357
|
visitor: typing.Optional[visitors.VisitorBase] = None,
|
|
358
|
-
**kwargs:
|
|
358
|
+
**kwargs: typing.Any,
|
|
359
359
|
) -> None:
|
|
360
360
|
"""
|
|
361
361
|
Crawl across the tree calling :meth:`~py_trees.behaviour.Behaviour.setup` on each behaviour.
|
|
@@ -368,8 +368,8 @@ class BehaviourTree(object):
|
|
|
368
368
|
|
|
369
369
|
Args:
|
|
370
370
|
timeout: time (s) to wait (use common.Duration.INFINITE to block indefinitely)
|
|
371
|
-
visitor: runnable entities on each node after
|
|
372
|
-
**kwargs: distribute args to this behaviour and in turn, to
|
|
371
|
+
visitor: runnable entities on each node after its setup
|
|
372
|
+
**kwargs: distribute args to this behaviour and in turn, to its children
|
|
373
373
|
|
|
374
374
|
Raises:
|
|
375
375
|
Exception: be ready to catch if any of the behaviours raise an exception
|
|
@@ -174,7 +174,7 @@ class SnapshotVisitor(VisitorBase):
|
|
|
174
174
|
|
|
175
175
|
class DisplaySnapshotVisitor(SnapshotVisitor):
|
|
176
176
|
"""
|
|
177
|
-
Visit the tree, capturing the visited path
|
|
177
|
+
Visit the tree, capturing the visited path and its changes since the last tick.
|
|
178
178
|
|
|
179
179
|
Additionally print the snapshot to console.
|
|
180
180
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "py_trees"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.0"
|
|
4
4
|
description = "pythonic implementation of behaviour trees"
|
|
5
5
|
authors = ["Daniel Stonier", "Naveed Usmani", "Michal Staniaszek"]
|
|
6
6
|
maintainers = ["Daniel Stonier <d.stonier@gmail.com>", "Sebastian Castro <sebas.a.castro@gmail.com>"]
|
|
@@ -25,7 +25,6 @@ keywords=["py_trees", "py-trees", "behaviour-trees"]
|
|
|
25
25
|
[tool.poetry.dependencies]
|
|
26
26
|
python = ">=3.9,<4.0"
|
|
27
27
|
pydot = ">=1.4"
|
|
28
|
-
py = "^1.11.0"
|
|
29
28
|
|
|
30
29
|
[tool.poetry.group.dev.dependencies]
|
|
31
30
|
tox = ">=3.26"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|