Metadata-Version: 2.4
Name: bteng-moveit
Version: 0.1.0
Summary: MoveIt2 behavior tree nodes for the BTEng engine
Author-email: BTEng contributors <mdirzpr@gmail.com>
License-Expression: Apache-2.0
Keywords: behavior-tree,robotics,ros2,moveit2,manipulation
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: bteng>=0.2.0
Requires-Dist: bteng-ros2>=0.1.0
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# bteng-moveit

![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
![License Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-green)

MoveIt2 behavior tree nodes for the BTEng engine.

`bteng-moveit` provides a collection of plug-and-play behavior tree nodes that wrap the MoveIt2 ROS 2 interfaces — action servers, services, and topics — so that robot manipulation pipelines can be composed entirely from a behavior tree without writing any ROS 2 boilerplate.

## Install

```bash
pip install bteng-moveit
```

The package depends on [`bteng`](https://github.com/BTEng/bteng) and [`bteng-ros2`](https://github.com/BTEng/bteng-ros2). MoveIt2 message packages (`moveit_msgs`, `shape_msgs`, etc.) must be available in the active ROS 2 environment.

## Design

### Lazy ROS 2 imports

Every node defers all ROS 2 and MoveIt2 message imports to the first tick or service call (`on_start`, `make_goal`, `make_request`, `tick`). This means the package can be imported in any Python process — even one that has not sourced a ROS 2 workspace — without raising `ImportError`. The ROS 2 environment is only required at runtime when a node actually executes.

### MoveItErrorCodes check

Action nodes inherit from `MoveItActionNode`, a thin subclass of `RosActionNode`. When a MoveIt2 action completes, `on_running` inspects `action_result.error_code.val`. If the value is not `1` (`MoveItErrorCodes.SUCCESS`), the node transitions to `FAILURE` immediately, even though the ROS 2 action itself succeeded. This maps MoveIt2-level planning or execution failures onto the behavior tree status without any extra error-handling code in each node.

### Composable node architecture

Each node exposes its interface purely through `provided_ports()`. Input and output values are passed through the blackboard, so any node in the tree can feed data to any other. The three plan-only nodes (`PlanToPoseNode`, `PlanToJointValuesNode`, `ComputeCartesianPathNode`) write their trajectory to an output port so that `ExecuteTrajectoryNode` can consume it separately, enabling plan-then-inspect-then-execute workflows. `MoveGroupNode` combines planning and execution in a single step when that split is not needed.

## Nodes

| Class | Type | Description |
|---|---|---|
| `PlanToPoseNode` | Action | Plan to Cartesian pose (plan only) |
| `PlanToJointValuesNode` | Action | Plan to joint target (plan only) |
| `ExecuteTrajectoryNode` | Action | Execute a pre-planned trajectory |
| `MoveGroupNode` | Action | Plan and execute in one step |
| `ComputeCartesianPathNode` | Service | Compute Cartesian path through waypoints |
| `ComputeIKNode` | Service | Compute inverse kinematics |
| `ComputeFKNode` | Service | Compute forward kinematics |
| `GetPlanningSceneNode` | Service | Query the MoveIt planning scene |
| `ApplyPlanningSceneNode` | Service | Apply a planning scene update |
| `AddCollisionObjectNode` | Publisher | Add collision object to scene |
| `RemoveCollisionObjectNode` | Publisher | Remove collision object by ID |
| `IsMoveGroupReadyCondition` | Condition | Check move_group action server is ready |
| `IsAtJointTargetCondition` | Condition | Check joints are at target positions |

## Usage

### Plan to a Cartesian pose, then execute

`PlanToPoseNode` sets `plan_only = True` and writes the resulting trajectory to the `trajectory` output port. Pass that port directly into `ExecuteTrajectoryNode`.

```python
import bteng as bt
from bteng_moveit import PlanToPoseNode, ExecuteTrajectoryNode

tree = bt.Sequence(
    children=[
        PlanToPoseNode(
            name="Plan",
            ports={
                "pose": bt.Blackboard.entry("target_pose"),
                "planning_group": "arm",
                "link_name": "tool0",
                "trajectory": bt.Blackboard.entry("planned_traj"),
            },
        ),
        ExecuteTrajectoryNode(
            name="Execute",
            ports={
                "trajectory": bt.Blackboard.entry("planned_traj"),
            },
        ),
    ]
)
```

### Plan to joint values, then execute

```python
from bteng_moveit import PlanToJointValuesNode, ExecuteTrajectoryNode

tree = bt.Sequence(
    children=[
        PlanToJointValuesNode(
            name="PlanJoints",
            ports={
                "planning_group": "arm",
                "joint_names": ["joint_1", "joint_2", "joint_3"],
                "joint_values": [0.0, -1.57, 1.57],
                "trajectory": bt.Blackboard.entry("planned_traj"),
            },
        ),
        ExecuteTrajectoryNode(
            name="Execute",
            ports={
                "trajectory": bt.Blackboard.entry("planned_traj"),
            },
        ),
    ]
)
```

### Compute IK before planning

```python
from bteng_moveit import ComputeIKNode

tree = bt.Sequence(
    children=[
        ComputeIKNode(
            name="IK",
            ports={
                "planning_group": "arm",
                "link_name": "tool0",
                "pose": bt.Blackboard.entry("target_pose"),
                "joint_state": bt.Blackboard.entry("ik_solution"),
            },
        ),
        # use ik_solution downstream ...
    ]
)
```

### Guard with readiness and position checks

Wrap motion subtrees with `IsMoveGroupReadyCondition` to avoid sending goals before the action server is up, and use `IsAtJointTargetCondition` to skip redundant moves.

```python
from bteng_moveit import IsMoveGroupReadyCondition, IsAtJointTargetCondition, PlanToJointValuesNode

tree = bt.Sequence(
    children=[
        IsMoveGroupReadyCondition(name="ServerReady"),
        bt.Fallback(
            children=[
                IsAtJointTargetCondition(
                    name="AlreadyAtHome",
                    ports={
                        "joint_names": ["joint_1", "joint_2"],
                        "joint_values": [0.0, 0.0],
                        "tolerance": 0.01,
                    },
                ),
                # motion subtree executed only when not already at target
                PlanToJointValuesNode(...),
            ]
        ),
    ]
)
```

## License

Apache-2.0. See [LICENSE](LICENSE).
