Conversation
Introduce a py_trees Behavior Tree module (PickPlaceModule) that wraps PickAndPlaceModule RPCs with retry, recovery, grasp verification, and interruptible execution. Includes gripper adaptation, workspace filtering, and DL-based grasp generation via GraspGen Docker. - New dimos/manipulation/bt/ package: actions, conditions, trees, pick_place_module - New gripper_adapter for cross-gripper grasp pose adaptation - Add visualize_grasps RPC to PickAndPlaceModule (MeshCat wireframes) - Add rpc_timeout passthrough in RpcCall for long Docker RPCs - Fix GraspGen Dockerfile: --no-deps, spconv-cu120, full runtime deps - Add angular_distance to Quaternion for pose verification - Add xarm7 joint limits, bt-pick-place-agent blueprint - Add py-trees>=2.2,<3 dependency
- Remove @DataClass from PickPlaceModuleConfig (ModuleConfig is now Pydantic) - Replace dataclasses.field with pydantic.Field for home_joints default - Update __init__ signature: drop *args (new module system is kwargs-only) - Fix all_blueprints path: manipulation_blueprints to blueprints (file renamed on dev)
…issues - Add BTManipulationModule: slim ManipulationModule subclass for BT workflow with perception integration, GraspGen Docker, and MeshCat visualization - Fix Pydantic pickle errors: change Iterable/Sequence to list in config - Fix OSR __init__ to accept **kwargs for GlobalConfig passthrough - Fix PointCloud2 pickle: strip cached Open3D bounding boxes in __getstate__ - Fix detect skill: accept prompts as keyword list instead of varargs - Fix xArm adapter: add clean_error/motion_enable on connect, error logging - Add langchain-core to Docker extras for module.py import - Update all BT actions/conditions to use BTManipulationModule RPC names
5dde367 to
8dac89a
Compare
Greptile SummaryThis PR introduces a full BT-driven pick-and-place architecture ( Key changes:
Issues found:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Agent
participant PickPlaceModule
participant BT as BehaviorTree
participant BTManipModule as BTManipulationModule
participant OSR as ObjectSceneRegistrationModule
participant GraspGen as GraspGenModule (Docker)
participant Coordinator
Agent->>PickPlaceModule: pick("red cup")
PickPlaceModule->>PickPlaceModule: _start_async(_exec_pick)
PickPlaceModule-->>Agent: None (async started)
PickPlaceModule->>BT: build_pick_tree() + _tick_tree()
loop BT Tick Loop (20Hz)
BT->>BTManipModule: reset() [RPC]
BT->>OSR: set_prompts(["red cup"]) [RPC]
Note over BT,OSR: prompt_settle_time (~3s sleep)
BT->>BTManipModule: refresh_obstacles(duration) [RPC]
BT->>BTManipModule: list_cached_detections() [RPC]
BTManipModule-->>BT: detections list
BT->>OSR: get_object_pointcloud_by_name("red cup") [RPC]
OSR-->>BT: PointCloud2
BT->>BTManipModule: generate_grasps(pc, scene_pc) [RPC, background thread]
Note over BT,GraspGen: GenerateGrasps returns RUNNING while Docker starts
BTManipModule->>GraspGen: generate_grasps(pointcloud) [Docker RPC]
GraspGen-->>BTManipModule: PoseArray (grasp candidates)
BTManipModule-->>BT: PoseArray
BT->>BTManipModule: visualize_grasps(candidates) [RPC, MeshCat]
loop GraspWithRetry
BT->>BTManipModule: plan_to_pose(pre_grasp_pose) [RPC]
BTManipModule->>Coordinator: plan trajectory
Coordinator-->>BTManipModule: trajectory
BT->>BTManipModule: execute() [RPC]
Coordinator->>Coordinator: execute trajectory
BT->>BTManipModule: get_trajectory_status() [RPC poll]
BTManipModule-->>BT: COMPLETED
BT->>BTManipModule: set_gripper(0.0) [RPC close]
BT->>BTManipModule: get_gripper() [RPC verify]
BTManipModule-->>BT: position > threshold → SUCCESS
end
BT-->>PickPlaceModule: SUCCESS
end
PickPlaceModule->>PickPlaceModule: _notify_agent("Pick complete")
PickPlaceModule-)Agent: /human_input publish
|
|
|
||
| def update(self) -> Status: | ||
| if not self._execute_ok: | ||
| self.bb.error_message = "Error: Trajectory execution send failed" | ||
| return Status.FAILURE | ||
|
|
||
| try: | ||
| status = self.module.get_rpc_calls("BTManipulationModule.get_trajectory_status")() |
There was a problem hiding this comment.
Race condition: old RPC thread can overwrite next attempt's results
When the BT is interrupted (terminate(INVALID)) while GenerateGrasps is running, self._thread is set to None but the background thread is still alive and will eventually write to self._result or self._error. If the BT retries immediately:
terminate()sets_thread = None,_result = None,_error = Noneinitialise()for the new attempt sets_result = Noneand starts a new thread T2- The old thread T1 (still running) writes
self._result = <stale result> - T2 also writes
self._result = <correct result>, but T1 may finish last
If T1 finishes after T2, update() will read T1's stale grasp candidates, causing the robot to attempt grasps that are no longer valid.
The fix is to use a per-run generation counter so the thread can detect it has been superseded before writing:
def initialise(self) -> None:
self._run_id = object() # fresh token each run
current_id = self._run_id
self._result = None
self._error = None
...
def _run() -> None:
try:
result = ...
if current_id is self._run_id: # still current?
self._result = result
except Exception as e:
if current_id is self._run_id:
self._error = e
...| @@ -25,7 +25,7 @@ RUN conda init bash && \ | |||
| echo 'yes' | conda tos accept --override-channels --channel defaults 2>/dev/null || true && \ | |||
| conda create -n app python=3.10 -y && conda clean -afy | |||
There was a problem hiding this comment.
Comment promises reproducibility but still uses a floating branch
The comment was updated to say "pin to a known-good commit for reproducibility", but the command still checks out main — a floating branch that will silently drift with every upstream push. This means two Docker image builds on different days may use different GraspGen code and produce different results, which is the exact problem the comment claims to have fixed.
Pin to the specific commit SHA that was tested:
| conda create -n app python=3.10 -y && conda clean -afy | |
| RUN git clone https://github.com/NVlabs/GraspGen.git && cd GraspGen && git checkout <tested-commit-sha> |
# Conflicts: # dimos/robot/all_blueprints.py
e91e97b to
9be3755
Compare
9be3755 to
9a98340
Compare
|
Runnnig blueprint |
Problem
The existing
PickAndPlaceModuletightly couples perception, grasp generation, planning, and execution into a single monolithic skill-based module. This makes it difficult to compose behavior-tree (BT) driven pick-and-place workflows where the BT orchestrates each phase independently.Solution
New
BTManipulationModule— a slimManipulationModulesubclass indimos/manipulation/bt/that provides:@skillmethods — only the BTPickPlaceModuleregisters skills with the agent, avoiding skill registration conflictsBT
PickPlaceModuleupdated to route all RPC calls throughBTManipulationModuleinstead of the monolithicPickAndPlaceModule, and adds adetectskill that chainsset_prompts->refresh_obstacles->list_cached_detections.Bug fixes:
Iterable[T]/Sequence[T]type hints tolist[T]inRobotModelConfig— Pydantic v2 wraps these in unpicklableValidatorIteratorobjects, breaking worker process deployment**kwargspassthrough soObjectSceneRegistrationModuleaccepts theg=GlobalConfig(...)kwarg from the module system__getstate__to strip cached Open3DOrientedBoundingBoxobjects that can't be pickled for LCM transportclean_error()+motion_enable()on connect and error-state logging (error_code,warn_code,state,mode) on command failurelangchain-coreto[docker]extras sincedimos.core.moduleimports it at the top levelKey design decision: Full architectural separation between BT and non-BT workflows. The BT blueprint uses
BTManipulationModule(no ties toPickAndPlaceModule), keeping both paths independent and composable.Breaking Changes
None — the existing
PickAndPlaceModule,xarm_perception, andxarm_perception_agentblueprints are unchanged.How to Test