Skip to content

refactor(manipulation): compose canonical pick and place - #3715

Merged
mustafab0 merged 7 commits into
mainfrom
ruthwik/refactor/canonical-pick-place
Aug 28, 2026
Merged

refactor(manipulation): compose canonical pick and place#3715
mustafab0 merged 7 commits into
mainfrom
ruthwik/refactor/canonical-pick-place

Conversation

@ruthwikdasyam

Copy link
Copy Markdown
Contributor

Summary

  • Replace the legacy pick-and-place subclass with a capability-composed module that coordinates OSR, grasp generation, and manipulation execution.
  • Add stable-ID scan, explicit grasp selection, verified pick, and explicit place workflow skills.
  • Compose the canonical workflow into real and simulated xArm perception blueprints.

Stack

Validation

  • TMPDIR=/dev/shm uv run --no-sync pytest dimos/manipulation/test_pick_and_place_unit.py dimos/perception/experimental/test_object_scene_registration_temporal.py dimos/robot/test_all_blueprints_generation.py -q
  • Ruff format/check and mypy on all changed files.
  • Imported real and simulated xArm perception blueprints.

Deliberately Deferred

  • Public method-description polish, candidate filtering/reranking, visualization, obstacle suppression, and table/box features.

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
4897 2 4895 82
View the full list of 2 ❄️ flaky test(s)
dimos.e2e_tests.test_manipulation_planning_groups::test_dual_arm_plans_and_dispatches_both_arms_through_control_coordinator

Flake rate in main: 100.00% (Passed 0 times, Failed 3 times)

Stack Traces | 120s run time
lcm_spy = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4cb93b7260>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7e4c9a690860>

    def test_dual_arm_plans_and_dispatches_both_arms_through_control_coordinator(
        lcm_spy: LcmSpy,
        start_blueprint: Callable[..., DimosCliCall],
    ) -> None:
        """Plan one generated plan over both arms and dispatch through one trajectory task."""
>       _start_openarm_mock_planner(start_blueprint, lcm_spy)

lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4cb93b7260>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7e4c9a690860>

dimos/e2e_tests/test_manipulation_planning_groups.py:190: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/e2e_tests/test_manipulation_planning_groups.py:151: in _start_openarm_mock_planner
    lcm_spy.wait_for_saved_topic(JOINT_STATE_TOPIC, timeout=120.0)
        lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4cb93b7260>
        start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7e4c9a690860>
dimos/e2e_tests/lcm_spy.py:97: in wait_for_saved_topic
    wait_until(
        condition  = <function LcmSpy.wait_for_saved_topic.<locals>.condition at 0x7e4c9a690e00>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4cb93b7260>
        timeout    = 120.0
        topic      = '/coordinator_joint_state#sensor_msgs.JointState'
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

predicate = <function LcmSpy.wait_for_saved_topic.<locals>.condition at 0x7e4c9a690e00>

    def wait_until(
        predicate: Callable[[], bool],
        *,
        timeout: float,
        interval: float = 0.1,
        message: str | None = None,
    ) -> None:
        """Poll ``predicate`` until it returns truthy or ``timeout`` elapses."""
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            if predicate():
                return
            time.sleep(interval)
>       raise TimeoutError(message or f"Timed out after {timeout}s waiting for condition")
E       TimeoutError: Timeout waiting for topic /coordinator_joint_state#sensor_msgs.JointState

deadline   = 4424188.152184639
interval   = 0.1
message    = 'Timeout waiting for topic /coordinator_joint_state#sensor_msgs.JointState'
predicate  = <function LcmSpy.wait_for_saved_topic.<locals>.condition at 0x7e4c9a690e00>
timeout    = 120.0

.../utils/testing/waiting.py:35: TimeoutError
dimos.e2e_tests.test_manipulation_planning_groups::test_single_arm_plans_and_executes_through_control_coordinator

Flake rate in main: 25.00% (Passed 12 times, Failed 4 times)

Stack Traces | 120s run time
lcm_spy = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4dc0060d40>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7e4c9a690400>

    def test_single_arm_plans_and_executes_through_control_coordinator(
        lcm_spy: LcmSpy,
        start_blueprint: Callable[..., DimosCliCall],
    ) -> None:
        """Plan with one arm and execute through its trajectory task."""
>       _start_openarm_mock_planner(start_blueprint, lcm_spy)

lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4dc0060d40>
start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7e4c9a690400>

dimos/e2e_tests/test_manipulation_planning_groups.py:159: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/e2e_tests/test_manipulation_planning_groups.py:151: in _start_openarm_mock_planner
    lcm_spy.wait_for_saved_topic(JOINT_STATE_TOPIC, timeout=120.0)
        lcm_spy    = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4dc0060d40>
        start_blueprint = <function start_blueprint.<locals>.set_name_and_start at 0x7e4c9a690400>
dimos/e2e_tests/lcm_spy.py:97: in wait_for_saved_topic
    wait_until(
        condition  = <function LcmSpy.wait_for_saved_topic.<locals>.condition at 0x7e4c9a60bb00>
        self       = <dimos.e2e_tests.lcm_spy.LcmSpy object at 0x7e4dc0060d40>
        timeout    = 120.0
        topic      = '/coordinator_joint_state#sensor_msgs.JointState'
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

predicate = <function LcmSpy.wait_for_saved_topic.<locals>.condition at 0x7e4c9a60bb00>

    def wait_until(
        predicate: Callable[[], bool],
        *,
        timeout: float,
        interval: float = 0.1,
        message: str | None = None,
    ) -> None:
        """Poll ``predicate`` until it returns truthy or ``timeout`` elapses."""
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            if predicate():
                return
            time.sleep(interval)
>       raise TimeoutError(message or f"Timed out after {timeout}s waiting for condition")
E       TimeoutError: Timeout waiting for topic /coordinator_joint_state#sensor_msgs.JointState

deadline   = 4424067.980965734
interval   = 0.1
message    = 'Timeout waiting for topic /coordinator_joint_state#sensor_msgs.JointState'
predicate  = <function LcmSpy.wait_for_saved_topic.<locals>.condition at 0x7e4c9a60bb00>
timeout    = 120.0

.../utils/testing/waiting.py:35: TimeoutError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@ruthwikdasyam
ruthwikdasyam force-pushed the ruthwik/refactor/canonical-pick-place branch 3 times, most recently from c313d61 to 6e9d305 Compare August 28, 2026 00:36
@ruthwikdasyam
ruthwikdasyam force-pushed the ruthwik/refactor/canonical-pick-place branch from 8efb261 to 28e0f4a Compare August 28, 2026 02:23
@ruthwikdasyam
ruthwikdasyam marked this pull request as ready for review August 28, 2026 02:27
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change replaces the inherited pick-and-place implementation with composed scene registration, grasp generation, and manipulation services, and wires the new skills into real and simulated xArm blueprints. The held-object scan-and-place flow, blueprint imports, movement capability declarations, and targeted pick-and-place tests completed successfully. However, a failed plan for the first grasp proposal stops the operation instead of trying later ranked proposals that may be reachable.

Confidence Score: 4/5

Not ready to merge because a reachable object can be left unpicked when the highest-ranked grasp is not plannable.

The failure was reproduced with a realistic-fake workflow harness that exercised an unplannable first grasp and a plan-valid second grasp. The targeted unit suite passed, but it does not cover retrying ranked grasp proposals after a planning failure.

Files Needing Attention: dimos/manipulation/pick_and_place_module.py needs a bounded ranked-grasp retry path, with corresponding coverage in dimos/manipulation/test_pick_and_place_unit.py.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the focused PR 3715 capability workflow harness to validate the posted P1 finding and collected its outputs across the base retry, composed harness, targeted unit tests, and xArm blueprint imports.
  • Validated the contract by running the capability workflow harness and the unit-test suite, observing a planning failure with planned_candidate_x_values [0.1] and no selected rank, running pytest with 17 tests passing, and completing the real/sim xArm blueprint import successfully.
  • Posted a second P1 finding-comment-proof to document the additional review finding.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 pick_object abandons ranked grasp fallback after the first planning failure

    • Bug
      • pick_object selects only candidates.candidates[0] at dimos/manipulation/pick_and_place_module.py:144 and returns immediately from the pregrasp motion failure at lines 155-158. The executed harness provided two ranked proposals: rank 0 was deliberately unplannable while rank 1 was plan-valid. The observed result was PLANNING_FAILED with planned candidate positions [0.1]; candidate rank 1 (x=0.2) was never planned. This regresses the base workflow’s bounded candidate loop and contradicts the new API’s “Generate ranked grasps” contract.
    • Cause
      • The PR replaced the previous bounded loop over candidate grasps with a single hard-coded candidates.candidates[0] selection, while retaining ranked-candidate terminology and GRASP_ATTEMPTS_EXHAUSTED in the manipulation error contract.
    • Fix
      • Iterate over the ranked candidates (with an explicit bounded attempt limit), attempt each candidate’s pregrasp/grasp plan until one completes, return the winning rank and score, and return GRASP_ATTEMPTS_EXHAUSTED only after all eligible candidates fail. Preserve safe gripper/state handling between attempts.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "chore(manipulation): use default grasp R..." | Re-trigger Greptile

Comment thread dimos/manipulation/pick_and_place_module.py
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Aug 28, 2026
@mustafab0
mustafab0 force-pushed the ruthwik/refactor/canonical-pick-place branch from 28e0f4a to 7cf1338 Compare August 28, 2026 06:31
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Aug 28, 2026
Comment thread dimos/manipulation/pick_and_place_module.py Outdated
Comment thread dimos/manipulation/pick_and_place_module.py
@ruthwikdasyam
ruthwikdasyam force-pushed the ruthwik/refactor/canonical-pick-place branch from 7cf1338 to 78d2732 Compare August 28, 2026 21:26
@ruthwikdasyam
ruthwikdasyam dismissed mustafab0’s stale review August 28, 2026 21:28

addresssed comments

mustafab0
mustafab0 previously approved these changes Aug 28, 2026
Base automatically changed from ruthwik/feat/osr-owlvit-edgetam to main August 28, 2026 21:39
@mustafab0
mustafab0 force-pushed the ruthwik/refactor/canonical-pick-place branch from 78d2732 to 7b00f04 Compare August 28, 2026 21:40
@mustafab0
mustafab0 added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 3b905d9 Aug 28, 2026
31 of 32 checks passed
@mustafab0
mustafab0 deleted the ruthwik/refactor/canonical-pick-place branch August 28, 2026 22:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants