Skip to content

feat(manip): let the voxel map define the planner's world - #3875

Open
mustafab0 wants to merge 1 commit into
mb/grasp-07-graspgenx-providerfrom
mb/grasp-08-voxel-map-obstacles
Open

feat(manip): let the voxel map define the planner's world#3875
mustafab0 wants to merge 1 commit into
mb/grasp-07-graspgenx-providerfrom
mb/grasp-08-voxel-map-obstacles

Conversation

@mustafab0

Copy link
Copy Markdown
Contributor

The planner only avoided registered objects, so anything the detector did not name was invisible to it, and the one thing standing in for the table was floor_z: a 0.6 x 1.2 x 0.2 m box centred at (0.7, 0, -0.12). Not a floor, one bench, pinned to one spot, leaving everything nearer than x=0.4 unguarded. It is gone. The wrist camera now feeds PointCloudSelfFilter and RayTracingVoxelMap into ManipulationModule.voxel_map on both the arm and the sim, and what the camera sees defines the obstacles. The removal and the replacement ship together on purpose — dropping the box first would leave the hardware planner with no ground at all.

Four things were needed to make the chain carry data, all of them silent when wrong. Both cameras default enable_pointcloud to False. The self filter needs a capture-time transform for every collision link and drops a whole cloud when one is missing, so the model publishes all fifteen. Its stock 20 ms TF tolerance cannot bracket the 10 Hz period ManipulationModule publishes robot TF at, so most clouds found no transform at all; the override is one full publish period, derived from that rate rather than picked — the module default is arguably wrong for any ManipulationModule-fed filter, which is worth a follow-up. And the two edges whose stream names differ are remapped.

Two consequences. The target object is itself mapped geometry once the map feeds the planner, so a collision-checked plan into it can only ever be rejected; the pregrasp-to-grasp leg and the retreat are now straight-line move_linear servos with checking off, which is what that primitive is for, and only the approach to the pregrasp stays a checked plan. And #3714 added the OCTREE obstacle type and its planner support but no rendering anywhere, so the viser scene's dispatch raised unsupported obstacle type and drew a grey MESH RENDER FAILED proxy box per cell over the map. Octrees now render as a new case in that same dispatch — occupied cells as one point cloud sized to the cell edge, because a box per cell is tens of thousands of scene nodes.

Verified in the grasp sim: mapping/voxel-map installs as an octree obstacle and the self filter stops dropping clouds once TF is up.

Eighth of nine in the xArm grasping re-landing stack.

The planner only avoided registered objects, so anything the detector did not
name was invisible to it and the one thing standing in for the table was
floor_z: a 0.6 x 1.2 x 0.2 m box centred at (0.7, 0, -0.12). Not a floor, one
bench, pinned to one spot, leaving everything nearer than x=0.4 unguarded. It is
gone. The wrist camera now feeds PointCloudSelfFilter and RayTracingVoxelMap
into ManipulationModule.voxel_map, and what the camera sees defines the
obstacles.

Four things were needed to make the chain carry data: both cameras default
enable_pointcloud to False; the self filter needs a capture-time transform for
every collision link and drops a whole cloud when one is missing; its stock 20 ms
TF tolerance cannot bracket the 10 Hz period ManipulationModule publishes robot
TF at, so most clouds found no transform at all; and the two edges whose stream
names differ are remapped.

Two consequences. The target object is itself mapped geometry once the map
feeds the planner, so a collision-checked plan into it can only be rejected; the
pregrasp-to-grasp leg and the retreat are now straight-line move_linear servos
with checking off, which is what that primitive is for. And #3714 added the
OCTREE obstacle type and its planner support but no rendering, so the viser
scene's dispatch raised "unsupported obstacle type" and drew a grey proxy box
per cell instead of the map. Occupied cells draw as one point cloud sized to the
cell edge: a box per cell would be tens of thousands of scene nodes.
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
4574 1 4573 179
View the top 1 failed test(s) by shortest run time
dimos.utils.test_shm::test_create_or_attach_splits_owner_and_reader
Stack Traces | 0.582s run time
name = 'dimos_test_a3086ac35763'
slow_ftruncate = <function slow_ftruncate.<locals>.delayed at 0xff7294988040>

    def test_create_or_attach_splits_owner_and_reader(name, slow_ftruncate):
        """Concurrent symmetric callers: exactly one owns, the other waits and attaches."""
        out: list[tuple[bool, int]] = []
        lock = threading.Lock()
        barrier = threading.Barrier(2)
    
        def racer() -> None:
            barrier.wait()
            shm, owner = create_or_attach_shm(name, SIZE, timeout=5.0)
            with lock:
                out.append((owner, shm.size))
            shm.close()
    
        threads = [threading.Thread(target=racer, daemon=True) for _ in range(2)]
        for t in threads:
            t.start()
        for t in threads:
            t.join(timeout=8)
    
        assert len(out) == 2, f"a racer did not finish: {out}"
        assert sorted(owner for owner, _ in out) == [False, True]
>       assert {size for _, size in out} == {SIZE}
E       assert {0, 65536} == {65536}
E         
E         Extra items in the left set:
E         #x1B[0m#x1B[94m0#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         
E         Full diff:
E         #x1B[0m#x1B[90m #x1B[39;49;00m {#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     0,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[90m #x1B[39;49;00m     65536,#x1B[90m#x1B[39;49;00m
E         #x1B[90m #x1B[39;49;00m }#x1B[90m#x1B[39;49;00m

barrier    = <threading.Barrier at 0xff7295f19760: waiters=0/2>
lock       = <unlocked _thread.lock object at 0xff722576efc0>
name       = 'dimos_test_a3086ac35763'
out        = [(False, 0), (True, 65536)]
racer      = <function test_create_or_attach_splits_owner_and_reader.<locals>.racer at 0xff729716b600>
slow_ftruncate = <function slow_ftruncate.<locals>.delayed at 0xff7294988040>
t          = <Thread(Thread-1900 (racer), stopped daemon 280864319467904)>
threads    = [<Thread(Thread-1899 (racer), stopped daemon 280864051032448)>, <Thread(Thread-1900 (racer), stopped daemon 280864319467904)>]

dimos/utils/test_shm.py:148: AssertionError

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

@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change replaces the fixed xArm floor obstacle with wrist-camera voxel mapping, connects the voxel map to the manipulation planner, adds octree visualization, and uses linear Cartesian motion for final grasp, placement, and retreat legs.

The final Cartesian legs disable collision checking for the entire planner scene. That bypass applies to unrelated mapped obstacles as well as the intended grasp or placement contact, so the arm, gripper, or carried object can be commanded through nearby geometry. The xArm configuration also removes the fixed floor without preventing planning until a non-empty voxel map is installed; because an empty map removes the octree, planning can proceed with neither an octree nor table obstacle.

T-Rex validation blocked

  • The focused final-servo collision harness could not start because the required turbojpeg Python package is missing. This is a missing package blocker.
  • The focused voxel-map readiness harness could not start because the prepared Python interpreter is unavailable; fallback attempts also lacked required project packages including numpy and dotenv. This is a missing package environment blocker.

Confidence Score: 3/5

Not safe to merge until final contact motions retain protection from unrelated obstacles and planning is prevented when table-protection geometry is unavailable.

The implementation directly passes disabled collision checking from the final motion helper through Cartesian planning and execution. The xArm setup also depends on an octree that can be absent or removed without preventing planning. The focused runtime harnesses could not execute because required Python runtime components were unavailable.

Files Needing Attention: dimos/manipulation/pick_and_place_module.py needs a target-specific contact exception instead of globally disabling collision checking. dimos/robot/manipulators/xarm/blueprints/grasp.py and the planner integration need a readiness policy that preserves table protection until a valid voxel map is available.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex attempted to run the final-servo harness validation, but the run was blocked by ModuleNotFoundError: No module named 'turbojpeg' and by unavailable runtime dependencies.
  • T-Rex attempted the voxel-map readiness harness, but the Python process exited with code 127 before execution, with fallback attempts failing on missing numpy and dotenv during pytest loading.
  • T-Rex produced a proof for a posted P1 finding.
  • T-Rex prepared the pick-servo-unrelated-obstacle-harness.py path and attempted a focused collision-path test, but the last command ended with ModuleNotFoundError: No module named 'turbojpeg' during import, and the focused pytest run could not complete due to missing runtime dependencies.
  • T-Rex documented blocked execution for the xarm-octree-readiness harness, where the Python executable could not start and earlier attempts failed due to numpy and dotenv being missing, leaving no octree planning paths exercised.

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. dimos/robot/manipulators/xarm/blueprints/grasp.py, line 245-253 (link)

    P1 Motion precedes map readiness

    This composition has no fixed floor_z obstacle and relies on the remapped voxel map for planner collision geometry, but it does not block manipulation until a non-empty map is present. Since an empty map update removes the octree, a pick or placement that starts before the first map arrives—or after the map is cleared—can plan without either the octree or table obstacle. Keep a conservative table obstacle or reject planning until a valid map is installed.

Reviews (1): Last reviewed commit: "feat(manip): let the voxel map define th..." | Re-trigger Greptile

Comment on lines +280 to +286
result = self._manipulation.move_linear(
end.position.x - start.position.x,
end.position.y - start.position.y,
end.position.z - start.position.z,
planning_group,
check_collision=False,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Servo bypasses obstacle checks

_servo passes check_collision=False for every final grasp, placement, and retreat leg. move_linear propagates that setting to Cartesian planning and executes accepted plans without a later collision recheck, so this is not limited to the contacted target: a route through a table edge, adjacent object, or other mapped obstacle is accepted as well. Represent the intended target contact as a narrow allowed-contact exception while keeping collision checks enabled for the rest of the planning scene.

T-Rex Ran code and verified through T-Rex

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.

1 participant