Skip to content

feat(manipulation): add live voxel collision planning - #3163

Draft
TomCC7 wants to merge 16 commits into
mainfrom
cc/feat/manip-voxel-grid
Draft

feat(manipulation): add live voxel collision planning#3163
TomCC7 wants to merge 16 commits into
mainfrom
cc/feat/manip-voxel-grid

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jul 24, 2026

Copy link
Copy Markdown
Member

Contribution path

Problem

The xArm voxel-planning demo did not have a responsive, atomic path from live point-cloud occupancy to collision checking. Scene mutation could block IK and collision queries, camera transforms could lag or be unavailable, and the same planning occupancy appeared twice in Viser.

Solution

  • Add a planning collision snapshot pipeline with latest-wins staging and a stable obstacle identity.
  • Align obstacle replacement with WorldSpec.update_obstacle and use copy-on-write RoboPlan scene swaps so readers see the old or new scene atomically.
  • Decouple IK, collision checks, and scene ingestion into independent workers.
  • Use Pink for interactive IK while retaining RoboPlan for planning and collision checking.
  • Add point-cloud self-filtering, a fixed-rate TF pose source, octree support, and the xArm Viser demo blueprint.
  • Show one live blue planning-collision visualization and suppress the duplicate reserved red octree visualization.
  • Document the launch/configuration path and the snapshot lifecycle decision.

How to Test

uv run dimos run xarm-voxel-planning-viser-demo

Focused automated verification:

PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest -p pytest_mock -o addopts='' -q \
  dimos/manipulation/planning/monitor/test_planning_collision_snapshot.py \
  dimos/manipulation/planning/monitor/test_world_monitor.py \
  dimos/manipulation/planning/world/test_drake_world_planning_groups.py \
  dimos/manipulation/test_manipulation_unit.py \
  dimos/manipulation/test_roboplan_world.py \
  dimos/manipulation/visualization \
  dimos/perception/test_point_cloud_self_filter.py \
  dimos/robot/manipulators/xarm/blueprints/test_xarm_voxel_planning_viser_demo.py

Result: 214 passed.

The three direct TfPoseSource lifecycle tests require LCM multicast sockets, which are unavailable in the coding sandbox.

AI assistance

Codex with GPT-5 implemented, debugged, tested, and prepared this change under developer direction.

Checklist

  • I have read and approved the CLA.

@mintlify

mintlify Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Jul 24, 2026, 5:44 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

❌ 13 Tests Failed:

Tests completed Failed Passed Skipped
3829 13 3816 175
View the top 3 failed test(s) by shortest run time
::dimos.robot.manipulators.xarm.blueprints.test_xarm_voxel_planning_viser_demo
Stack Traces | 0s run time
ImportError while importing test module '.../xarm/blueprints/test_xarm_voxel_planning_viser_demo.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
.../planning/world/roboplan_world.py:34: in <module>
    import roboplan.core as roboplan_core
E   ModuleNotFoundError: No module named 'roboplan'

The above exception was the direct cause of the following exception:
.../xarm/blueprints/test_xarm_voxel_planning_viser_demo.py:29: in <module>
    from dimos.manipulation.planning.world.roboplan_world import RoboPlanWorld
.../planning/world/roboplan_world.py:36: in <module>
    raise ImportError(
E   ImportError: RoboPlanWorld requires the optional roboplan dependency. Install the manipulation extra before selecting the roboplan backend.
dimos.manipulation.planning.world.test_drake_world_planning_groups::test_meshcat_obstacle_path_encodes_native_id_as_one_injective_segment
Stack Traces | 0.001s run time
def test_meshcat_obstacle_path_encodes_native_id_as_one_injective_segment() -> None:
>       paths = {DrakeWorld._meshcat_obstacle_path(value) for value in ("a", "a/b", "id-61", "")}
E       AttributeError: type object 'DrakeWorld' has no attribute '_meshcat_obstacle_path'


.../planning/world/test_drake_world_planning_groups.py:41: AttributeError
dimos.manipulation.planning.examples.test_manipulation_client::test_update_obstacle_exposes_generic_mesh_replacement
Stack Traces | 0.002s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff23126283e0>

    def test_update_obstacle_exposes_generic_mesh_replacement(mocker: MockerFixture) -> None:
        client = MagicMock()
        client.update_obstacle.return_value = True
        mocker.patch.object(manipulation_client, "_client", client)
        pose = Pose(
            position=Vector3(0.1, 0.2, 0.3),
            orientation=Quaternion(0.0, 0.0, 0.0, 1.0),
        )
    
>       result = manipulation_client.update_obstacle(
            "mesh",
            pose,
            "mesh",
            mesh_path="replacement.obj",
        )
E       TypeError: update_obstacle() got an unexpected keyword argument 'mesh_path'

client     = <MagicMock id='280526063121472'>
mocker     = <pytest_mock.plugin.MockerFixture object at 0xff23126283e0>
pose       = Pose(position=Vector([        0.1         0.2         0.3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000))

.../planning/examples/test_manipulation_client.py:95: TypeError
dimos.manipulation.planning.world.test_drake_world_planning_groups::test_drake_pre_finalize_remove_and_clear_reject_without_dropping_tracking
Stack Traces | 0.002s run time
def test_drake_pre_finalize_remove_and_clear_reject_without_dropping_tracking() -> None:
        world = object.__new__(DrakeWorld)
        world._lock = RLock()
        world._finalized = False
        world._obstacles = {"a": object()}
    
        with pytest.raises(NotImplementedError, match="before finalization"):
>           world.remove_obstacle("a")

world      = <dimos.manipulation.planning.world.drake_world.DrakeWorld object at 0xff4392798dd0>

.../planning/world/test_drake_world_planning_groups.py:54: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../planning/world/drake_world.py:661: in remove_obstacle
    self._require_finalized()
        obstacle_id = 'a'
        self       = <dimos.manipulation.planning.world.drake_world.DrakeWorld object at 0xff4392798dd0>
.../planning/world/drake_world.py:838: in _require_finalized
    self._require_usable()
        self       = <dimos.manipulation.planning.world.drake_world.DrakeWorld object at 0xff4392798dd0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.manipulation.planning.world.drake_world.DrakeWorld object at 0xff4392798dd0>

    def _require_usable(self) -> None:
>       if not self._usable:
E       AttributeError: 'DrakeWorld' object has no attribute '_usable'

self       = <dimos.manipulation.planning.world.drake_world.DrakeWorld object at 0xff4392798dd0>

.../planning/world/drake_world.py:834: AttributeError
dimos.manipulation.planning.examples.test_manipulation_client::test_update_box_calls_complete_obstacle_rpc
Stack Traces | 0.003s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff2310397500>

    def test_update_box_calls_complete_obstacle_rpc(mocker: MockerFixture) -> None:
        client = MagicMock()
        client.update_obstacle.return_value = True
        mocker.patch.object(manipulation_client, "_client", client)
    
        result = manipulation_client.update_box(
            "box",
            1.0,
            2.0,
            3.0,
            w=0.4,
            h=0.5,
            d=0.6,
            color=[0.1, 0.2, 0.3, 0.9],
        )
    
        assert result is True
>       name, pose, shape, dimensions, mesh_path, color = client.update_obstacle.call_args.args
E       ValueError: not enough values to unpack (expected 6, got 1)

client     = <MagicMock id='280526099515488'>
mocker     = <pytest_mock.plugin.MockerFixture object at 0xff2310397500>
result     = True

.../planning/examples/test_manipulation_client.py:45: ValueError
dimos.perception.test_point_cloud_self_filter::test_mesh_filter_uses_exact_mesh_surface_with_padding
Stack Traces | 0.003s run time
def test_mesh_filter_uses_exact_mesh_surface_with_padding() -> None:
        mesh = trimesh.creation.icosphere(radius=1.0)
        geometry = _CollisionGeometry(
            link="arm",
            link_from_geometry=np.eye(4),
            mesh=mesh,
            shape="mesh",
            dimensions=(),
            clear_samples=np.empty((0, 3)),
        )
        points = np.asarray(
            [
                [0.9, 0.9, 0.0],  # Outside the sphere, inside its bounding box.
                [1.02, 0.0, 0.0],  # Included by padding.
                [1.2, 0.0, 0.0],
            ]
        )
    
>       removed = PointCloudSelfFilter._points_inside_geometry(points, geometry, padding=0.05)

geometry   = _CollisionGeometry(link='arm', link_from_geometry=array([[          1,           0,           0,           0],
       ...e=(642, 3), faces.shape=(1280, 3))>, shape='mesh', dimensions=(), clear_samples=array([], shape=(0, 3), dtype=float64))
mesh       = <trimesh.Trimesh(vertices.shape=(642, 3), faces.shape=(1280, 3))>
points     = array([[        0.9,         0.9,           0],
       [       1.02,           0,           0],
       [        1.2,           0,           0]])

dimos/perception/test_point_cloud_self_filter.py:193: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/perception/point_cloud_self_filter.py:255: in _points_inside_geometry
    signed_distance = trimesh.proximity.signed_distance(  # type: ignore[no-untyped-call]
        candidates = array([ True,  True, False])
        geometry   = _CollisionGeometry(link='arm', link_from_geometry=array([[          1,           0,           0,           0],
       ...e=(642, 3), faces.shape=(1280, 3))>, shape='mesh', dimensions=(), clear_samples=array([], shape=(0, 3), dtype=float64))
        inside     = array([False, False, False])
        padded_lower = array([      -1.05,       -1.05,       -1.05])
        padded_upper = array([       1.05,        1.05,        1.05])
        padding    = 0.05
        points     = array([[        0.9,         0.9,           0],
       [       1.02,           0,           0],
       [        1.2,           0,           0]])
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:242: in signed_distance
    closest, distance, triangle_id = closest_point(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(642, 3), faces.shape=(1280, 3))>
        points     = array([[        0.9,         0.9,           0],
       [       1.02,           0,           0]])
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:147: in closest_point
    candidates = nearby_faces(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(642, 3), faces.shape=(1280, 3))>
        points     = array([[        0.9,         0.9,           0],
       [       1.02,           0,           0]])
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:53: in nearby_faces
    rtree = mesh.triangles_tree
        mesh       = <trimesh.Trimesh(vertices.shape=(642, 3), faces.shape=(1280, 3))>
        points     = array([[        0.9,         0.9,           0],
       [       1.02,           0,           0]])
.venv/lib/python3.12.../site-packages/trimesh/caching.py:139: in get_cached
    value = function(*args, **kwargs)
        args       = (<trimesh.Trimesh(vertices.shape=(642, 3), faces.shape=(1280, 3))>,)
        function   = <function Trimesh.triangles_tree at 0xffd2cc2eb1a0>
        kwargs     = {}
        name       = 'triangles_tree'
        self       = <trimesh.Trimesh(vertices.shape=(642, 3), faces.shape=(1280, 3))>
.venv/lib/python3.12.../site-packages/trimesh/base.py:911: in triangles_tree
    return triangles.bounds_tree(self.triangles)
        self       = <trimesh.Trimesh(vertices.shape=(642, 3), faces.shape=(1280, 3))>
.venv/lib/python3.12.../site-packages/trimesh/triangles.py:392: in bounds_tree
    tree = util.bounds_tree(triangle_bounds)
        triangle_bounds = array([[   -0.61564,     0.78384,           0,    -0.48444,     0.86493,      0.1312],
       [   -0.69378,     0.7020...     0.16246],
       [    0.91298,      0.2664,   -0.082324,     0.96386,     0.39961,    0.082324]], shape=(1280, 6))
        triangles  = array([[[   -0.52573,     0.85065,           0],
        [   -0.61564,     0.78384,    0.081086],
        [   -0.48444...        [    0.91298,     0.39961,    0.082324],
        [    0.96386,      0.2664,           0]]], shape=(1280, 3, 3))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

bounds = array([[   -0.61564,     0.78384,           0,    -0.48444,     0.86493,      0.1312],
       [   -0.69378,     0.7020...     0.16246],
       [    0.91298,      0.2664,   -0.082324,     0.96386,     0.39961,    0.082324]], shape=(1280, 6))

    def bounds_tree(bounds):
        """
        Given a set of axis aligned bounds create an r-tree for
        broad-phase collision detection.
    
        Parameters
        ------------
        bounds : (n, 2D) or (n, 2, D) float
          Non-interleaved bounds where D=dimension
          E.G a 2D bounds tree:
          [(minx, miny, maxx, maxy), ...]
    
        Returns
        ---------
        tree : Rtree
          Tree containing bounds by index
        """
>       import rtree
E       ModuleNotFoundError: No module named 'rtree'

bounds     = array([[   -0.61564,     0.78384,           0,    -0.48444,     0.86493,      0.1312],
       [   -0.69378,     0.7020...     0.16246],
       [    0.91298,      0.2664,   -0.082324,     0.96386,     0.39961,    0.082324]], shape=(1280, 6))

.venv/lib/python3.12.../site-packages/trimesh/util.py:1821: ModuleNotFoundError
dimos.manipulation.planning.examples.test_manipulation_client::test_update_round_shape_helpers_call_complete_obstacle_rpc[update_cylinder-dimensions1-cylinder]
Stack Traces | 0.007s run time
self = <MagicMock name='mock.update_obstacle' id='280526060774912'>
args = ('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None), kwargs = {}
expected = call('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None)
actual = call(Obstacle(name='shape', obstacle_type=<ObstacleType.CYLINDER: 3>, pose=Pose(position=Vector([          1          ...0, 1.000000)), dimensions=(0.4, 0.8), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0xff23122ecf40>
cause = None

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: update_obstacle('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None)
E             Actual: update_obstacle(Obstacle(name='shape', obstacle_type=<ObstacleType.CYLINDER: 3>, pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), dimensions=(0.4, 0.8), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))

_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0xff23122ecf40>
actual     = call(Obstacle(name='shape', obstacle_type=<ObstacleType.CYLINDER: 3>, pose=Pose(position=Vector([          1          ...0, 1.000000)), dimensions=(0.4, 0.8), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
args       = ('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None)
cause      = None
expected   = call('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None)
kwargs     = {}
self       = <MagicMock name='mock.update_obstacle' id='280526060774912'>

....../usr/lib/python3.12/unittest/mock.py:944: AssertionError

During handling of the above exception, another exception occurred:

self = <MagicMock name='mock.update_obstacle' id='280526060774912'>
args = ('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
            raise AssertionError(msg)
>       return self.assert_called_with(*args, **kwargs)
E       AssertionError: expected call not found.
E       Expected: update_obstacle('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None)
E         Actual: update_obstacle(Obstacle(name='shape', obstacle_type=<ObstacleType.CYLINDER: 3>, pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), dimensions=(0.4, 0.8), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
E       
E       pytest introspection follows:
E       
E       Args:
E       assert (Obstacle(nam...lution=None),) == ('shape', <AN...], None, None)
E         
E         At index 0 diff: #x1B[0mObstacle(name=#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, obstacle_type=<ObstacleType.CYLINDER: #x1B[94m3#x1B[39;49;00m>, pose=Pose(position=Vector([          #x1B[94m1#x1B[39;49;00m           #x1B[94m2#x1B[39;49;00m           #x1B[94m3#x1B[39;49;00m]), orientation=Quaternion(#x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m1.000000#x1B[39;49;00m)), dimensions=(#x1B[94m0.4#x1B[39;49;00m, #x1B[94m0.8#x1B[39;49;00m), color=(#x1B[94m0.8#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.8#x1B[39;49;00m), mesh_path=#x1B[94mNone#x1B[39;49;00m, points=#x1B[94mNone#x1B[39;49;00m, octree_resolution=#x1B[94mNone#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m != #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         Right contains 5 more items, first extra item: #x1B[0m<ANY>#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[91m-     'shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     <ANY>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     'cylinder',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     [#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-         0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-         0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     Obstacle(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         name='shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         obstacle_type=<ObstacleType.CYLINDER: 3>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         dimensions=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                        0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                        0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                    ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         color=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+               ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         mesh_path=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         points=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         octree_resolution=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     ],#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[90m #x1B[39;49;00m )#x1B[90m#x1B[39;49;00m

args       = ('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None)
kwargs     = {}
self       = <MagicMock name='mock.update_obstacle' id='280526060774912'>

....../usr/lib/python3.12/unittest/mock.py:956: AssertionError

During handling of the above exception, another exception occurred:

mocker = <pytest_mock.plugin.MockerFixture object at 0xff23125141a0>
helper_name = 'update_cylinder', dimensions = [0.4, 0.8], shape = 'cylinder'

    @pytest.mark.parametrize(
        ("helper_name", "dimensions", "shape"),
        [
            ("update_sphere", [0.4], "sphere"),
            ("update_cylinder", [0.4, 0.8], "cylinder"),
        ],
    )
    def test_update_round_shape_helpers_call_complete_obstacle_rpc(
        mocker: MockerFixture,
        helper_name: str,
        dimensions: list[float],
        shape: str,
    ) -> None:
        client = MagicMock()
        client.update_obstacle.return_value = True
        mocker.patch.object(manipulation_client, "_client", client)
        helper = getattr(manipulation_client, helper_name)
    
        result = helper("shape", 1.0, 2.0, 3.0, *dimensions)
    
        assert result is True
>       client.update_obstacle.assert_called_once_with(
            "shape",
            mocker.ANY,
            shape,
            dimensions,
            None,
            None,
        )
E       AssertionError: expected call not found.
E       Expected: update_obstacle('shape', <ANY>, 'cylinder', [0.4, 0.8], None, None)
E         Actual: update_obstacle(Obstacle(name='shape', obstacle_type=<ObstacleType.CYLINDER: 3>, pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), dimensions=(0.4, 0.8), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
E       
E       pytest introspection follows:
E       
E       Args:
E       assert (Obstacle(nam...lution=None),) == ('shape', <AN...], None, None)
E         
E         At index 0 diff: #x1B[0mObstacle(name=#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, obstacle_type=<ObstacleType.CYLINDER: #x1B[94m3#x1B[39;49;00m>, pose=Pose(position=Vector([          #x1B[94m1#x1B[39;49;00m           #x1B[94m2#x1B[39;49;00m           #x1B[94m3#x1B[39;49;00m]), orientation=Quaternion(#x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m1.000000#x1B[39;49;00m)), dimensions=(#x1B[94m0.4#x1B[39;49;00m, #x1B[94m0.8#x1B[39;49;00m), color=(#x1B[94m0.8#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.8#x1B[39;49;00m), mesh_path=#x1B[94mNone#x1B[39;49;00m, points=#x1B[94mNone#x1B[39;49;00m, octree_resolution=#x1B[94mNone#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m != #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         Right contains 5 more items, first extra item: #x1B[0m<ANY>#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[91m-     'shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     <ANY>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     'cylinder',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     [#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-         0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-         0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     Obstacle(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         name='shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         obstacle_type=<ObstacleType.CYLINDER: 3>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         dimensions=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                        0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                        0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                    ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         color=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+               ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         mesh_path=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         points=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         octree_resolution=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     ],#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[90m #x1B[39;49;00m )#x1B[90m#x1B[39;49;00m

client     = <MagicMock id='280530253805904'>
dimensions = [0.4, 0.8]
helper     = <function update_cylinder at 0xff237cdca160>
helper_name = 'update_cylinder'
mocker     = <pytest_mock.plugin.MockerFixture object at 0xff23125141a0>
result     = True
shape      = 'cylinder'

.../planning/examples/test_manipulation_client.py:75: AssertionError
dimos.perception.test_point_cloud_self_filter::test_model_filter_removes_robot_surface_and_preserves_external_point
Stack Traces | 0.063s run time
make_filter = <function make_filter.<locals>.make at 0xffd23930fe20>

    def test_model_filter_removes_robot_surface_and_preserves_external_point(
        make_filter: Callable[[], PointCloudSelfFilter],
    ) -> None:
>       module = make_filter()

make_filter = <function make_filter.<locals>.make at 0xffd23930fe20>

dimos/perception/test_point_cloud_self_filter.py:108: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/perception/test_point_cloud_self_filter.py:61: in make
    module = PointCloudSelfFilter(
        modules    = []
        urdf       = PosixPath('.../popen-gw0/test_model_filter_removes_robo0/robot.urdf')
dimos/perception/point_cloud_self_filter.py:71: in __init__
    self._collision_geometry = self._load_collision_geometry()
        __class__  = <class 'dimos.perception.point_cloud_self_filter.PointCloudSelfFilter'>
        kwargs     = {'padding_m': 0.01, 'robot_model': RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, defau...hardware_id=None, home_joints=None, pre_grasp_offset=0.1), 'tf_forward_tolerance_s': 0.0, 'tf_tolerance_s': 0.001, ...}
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd239259d00>
dimos/perception/point_cloud_self_filter.py:191: in _load_collision_geometry
    clear_samples=self._clear_samples(mesh),
        collision  = Collision(name=None, origin=None, geometry=Geometry(box=Box(size=array([          1,           1,           1])), cylinder=None, sphere=None, mesh=None))
        config     = RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, default_rpc_timeout=120.0, rpc_timeouts=...ity=1.0, max_acceleration=2.0, joint_name_mapping={}, gripper_hardware_id=None, home_joints=None, pre_grasp_offset=0.1)
        dimensions = (1.0, 1.0, 1.0)
        link       = Link(name='base', inertial=None, visuals=[], collisions=[Collision(name=None, origin=None, geometry=Geometry(box=Box(size=array([          1,           1,           1])), cylinder=None, sphere=None, mesh=None))])
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        result     = []
        robot      = <yourdfpy.urdf.URDF object at 0xffd2392461b0>
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd239259d00>
        shape      = (<trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>, 'box', (1.0, 1.0, 1.0))
        shape_name = 'box'
        urdf_path  = PosixPath('.../home/runner/.cache.../df9638f9c18878a2/robot/robot.urdf')
dimos/perception/point_cloud_self_filter.py:271: in _clear_samples
    signed_distance = trimesh.proximity.signed_distance(  # type: ignore[no-untyped-call]
        axes       = [array([       -0.6,       -0.55,        -0.5,       -0.45,        -0.4,       -0.35,        -0.3,       -0.25,       ...  0.2,        0.25,         0.3,        0.35,         0.4,        0.45,         0.5,        0.55,
               0.6])]
        grid       = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
        lower      = array([-12, -12, -12])
        margin     = np.float64(0.053301270189221935)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        pitch      = 0.05
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd239259d00>
        upper      = array([12, 12, 12])
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:242: in signed_distance
    closest, distance, triangle_id = closest_point(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:147: in closest_point
    candidates = nearby_faces(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:53: in nearby_faces
    rtree = mesh.triangles_tree
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12.../site-packages/trimesh/caching.py:139: in get_cached
    value = function(*args, **kwargs)
        args       = (<trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>,)
        function   = <function Trimesh.triangles_tree at 0xffd2cc2eb1a0>
        kwargs     = {}
        name       = 'triangles_tree'
        self       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
.venv/lib/python3.12.../site-packages/trimesh/base.py:911: in triangles_tree
    return triangles.bounds_tree(self.triangles)
        self       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
.venv/lib/python3.12.../site-packages/trimesh/triangles.py:392: in bounds_tree
    tree = util.bounds_tree(triangle_bounds)
        triangle_bounds = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])
        triangles  = array([[[       -0.5,        -0.5,         0.5],
        [       -0.5,         0.5,         0.5],
        [       -0.5... 0.5,         0.5],
        [        0.5,        -0.5,         0.5],
        [        0.5,         0.5,        -0.5]]])
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

bounds = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])

    def bounds_tree(bounds):
        """
        Given a set of axis aligned bounds create an r-tree for
        broad-phase collision detection.
    
        Parameters
        ------------
        bounds : (n, 2D) or (n, 2, D) float
          Non-interleaved bounds where D=dimension
          E.G a 2D bounds tree:
          [(minx, miny, maxx, maxy), ...]
    
        Returns
        ---------
        tree : Rtree
          Tree containing bounds by index
        """
>       import rtree
E       ModuleNotFoundError: No module named 'rtree'

bounds     = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])

.venv/lib/python3.12.../site-packages/trimesh/util.py:1821: ModuleNotFoundError
dimos.perception.test_point_cloud_self_filter::test_missing_required_link_tf_drops_whole_capture
Stack Traces | 0.067s run time
make_filter = <function make_filter.<locals>.make at 0xffd2392e2de0>

    def test_missing_required_link_tf_drops_whole_capture(
        make_filter: Callable[[], PointCloudSelfFilter],
    ) -> None:
>       module = make_filter()

make_filter = <function make_filter.<locals>.make at 0xffd2392e2de0>

dimos/perception/test_point_cloud_self_filter.py:149: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/perception/test_point_cloud_self_filter.py:61: in make
    module = PointCloudSelfFilter(
        modules    = []
        urdf       = PosixPath('.../popen-gw0/test_missing_required_link_tf_0/robot.urdf')
dimos/perception/point_cloud_self_filter.py:71: in __init__
    self._collision_geometry = self._load_collision_geometry()
        __class__  = <class 'dimos.perception.point_cloud_self_filter.PointCloudSelfFilter'>
        kwargs     = {'padding_m': 0.01, 'robot_model': RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, defau...hardware_id=None, home_joints=None, pre_grasp_offset=0.1), 'tf_forward_tolerance_s': 0.0, 'tf_tolerance_s': 0.001, ...}
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd2393b6bd0>
dimos/perception/point_cloud_self_filter.py:191: in _load_collision_geometry
    clear_samples=self._clear_samples(mesh),
        collision  = Collision(name=None, origin=None, geometry=Geometry(box=Box(size=array([          1,           1,           1])), cylinder=None, sphere=None, mesh=None))
        config     = RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, default_rpc_timeout=120.0, rpc_timeouts=...ity=1.0, max_acceleration=2.0, joint_name_mapping={}, gripper_hardware_id=None, home_joints=None, pre_grasp_offset=0.1)
        dimensions = (1.0, 1.0, 1.0)
        link       = Link(name='base', inertial=None, visuals=[], collisions=[Collision(name=None, origin=None, geometry=Geometry(box=Box(size=array([          1,           1,           1])), cylinder=None, sphere=None, mesh=None))])
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        result     = []
        robot      = <yourdfpy.urdf.URDF object at 0xffd2393b67e0>
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd2393b6bd0>
        shape      = (<trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>, 'box', (1.0, 1.0, 1.0))
        shape_name = 'box'
        urdf_path  = PosixPath('.../home/runner/.cache.../57fcdd1fa13c695f/robot/robot.urdf')
dimos/perception/point_cloud_self_filter.py:271: in _clear_samples
    signed_distance = trimesh.proximity.signed_distance(  # type: ignore[no-untyped-call]
        axes       = [array([       -0.6,       -0.55,        -0.5,       -0.45,        -0.4,       -0.35,        -0.3,       -0.25,       ...  0.2,        0.25,         0.3,        0.35,         0.4,        0.45,         0.5,        0.55,
               0.6])]
        grid       = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
        lower      = array([-12, -12, -12])
        margin     = np.float64(0.053301270189221935)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        pitch      = 0.05
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd2393b6bd0>
        upper      = array([12, 12, 12])
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:242: in signed_distance
    closest, distance, triangle_id = closest_point(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:147: in closest_point
    candidates = nearby_faces(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:53: in nearby_faces
    rtree = mesh.triangles_tree
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12.../site-packages/trimesh/caching.py:139: in get_cached
    value = function(*args, **kwargs)
        args       = (<trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>,)
        function   = <function Trimesh.triangles_tree at 0xffd2cc2eb1a0>
        kwargs     = {}
        name       = 'triangles_tree'
        self       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
.venv/lib/python3.12.../site-packages/trimesh/base.py:911: in triangles_tree
    return triangles.bounds_tree(self.triangles)
        self       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
.venv/lib/python3.12.../site-packages/trimesh/triangles.py:392: in bounds_tree
    tree = util.bounds_tree(triangle_bounds)
        triangle_bounds = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])
        triangles  = array([[[       -0.5,        -0.5,         0.5],
        [       -0.5,         0.5,         0.5],
        [       -0.5... 0.5,         0.5],
        [        0.5,        -0.5,         0.5],
        [        0.5,         0.5,        -0.5]]])
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

bounds = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])

    def bounds_tree(bounds):
        """
        Given a set of axis aligned bounds create an r-tree for
        broad-phase collision detection.
    
        Parameters
        ------------
        bounds : (n, 2D) or (n, 2, D) float
          Non-interleaved bounds where D=dimension
          E.G a 2D bounds tree:
          [(minx, miny, maxx, maxy), ...]
    
        Returns
        ---------
        tree : Rtree
          Tree containing bounds by index
        """
>       import rtree
E       ModuleNotFoundError: No module named 'rtree'

bounds     = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])

.venv/lib/python3.12.../site-packages/trimesh/util.py:1821: ModuleNotFoundError
dimos.perception.test_point_cloud_self_filter::test_clear_mask_contains_previous_and_current_robot_volumes
Stack Traces | 0.068s run time
make_filter = <function make_filter.<locals>.make at 0xffd23935a840>

    def test_clear_mask_contains_previous_and_current_robot_volumes(
        make_filter: Callable[[], PointCloudSelfFilter],
    ) -> None:
>       module = make_filter()

make_filter = <function make_filter.<locals>.make at 0xffd23935a840>

dimos/perception/test_point_cloud_self_filter.py:131: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/perception/test_point_cloud_self_filter.py:61: in make
    module = PointCloudSelfFilter(
        modules    = []
        urdf       = PosixPath('.../popen-gw0/test_clear_mask_contains_previ0/robot.urdf')
dimos/perception/point_cloud_self_filter.py:71: in __init__
    self._collision_geometry = self._load_collision_geometry()
        __class__  = <class 'dimos.perception.point_cloud_self_filter.PointCloudSelfFilter'>
        kwargs     = {'padding_m': 0.01, 'robot_model': RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, defau...hardware_id=None, home_joints=None, pre_grasp_offset=0.1), 'tf_forward_tolerance_s': 0.0, 'tf_tolerance_s': 0.001, ...}
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd239282bd0>
dimos/perception/point_cloud_self_filter.py:191: in _load_collision_geometry
    clear_samples=self._clear_samples(mesh),
        collision  = Collision(name=None, origin=None, geometry=Geometry(box=Box(size=array([          1,           1,           1])), cylinder=None, sphere=None, mesh=None))
        config     = RobotModelConfig(rpc_transport=<class 'dimos.protocol.rpc.pubsubrpc.LCMRPC'>, default_rpc_timeout=120.0, rpc_timeouts=...ity=1.0, max_acceleration=2.0, joint_name_mapping={}, gripper_hardware_id=None, home_joints=None, pre_grasp_offset=0.1)
        dimensions = (1.0, 1.0, 1.0)
        link       = Link(name='base', inertial=None, visuals=[], collisions=[Collision(name=None, origin=None, geometry=Geometry(box=Box(size=array([          1,           1,           1])), cylinder=None, sphere=None, mesh=None))])
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        result     = []
        robot      = <yourdfpy.urdf.URDF object at 0xffd239234320>
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd239282bd0>
        shape      = (<trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>, 'box', (1.0, 1.0, 1.0))
        shape_name = 'box'
        urdf_path  = PosixPath('.../home/runner/.cache.../76c16b469d71e9ca/robot/robot.urdf')
dimos/perception/point_cloud_self_filter.py:271: in _clear_samples
    signed_distance = trimesh.proximity.signed_distance(  # type: ignore[no-untyped-call]
        axes       = [array([       -0.6,       -0.55,        -0.5,       -0.45,        -0.4,       -0.35,        -0.3,       -0.25,       ...  0.2,        0.25,         0.3,        0.35,         0.4,        0.45,         0.5,        0.55,
               0.6])]
        grid       = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
        lower      = array([-12, -12, -12])
        margin     = np.float64(0.053301270189221935)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        pitch      = 0.05
        self       = <dimos.perception.point_cloud_self_filter.PointCloudSelfFilter object at 0xffd239282bd0>
        upper      = array([12, 12, 12])
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:242: in signed_distance
    closest, distance, triangle_id = closest_point(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:147: in closest_point
    candidates = nearby_faces(mesh, points)
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12........./site-packages/trimesh/proximity.py:53: in nearby_faces
    rtree = mesh.triangles_tree
        mesh       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
        points     = array([[       -0.6,        -0.6,        -0.6],
       [       -0.6,        -0.6,       -0.55],
       [       -0.6,  ....5],
       [        0.6,         0.6,        0.55],
       [        0.6,         0.6,         0.6]], shape=(15625, 3))
.venv/lib/python3.12.../site-packages/trimesh/caching.py:139: in get_cached
    value = function(*args, **kwargs)
        args       = (<trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>,)
        function   = <function Trimesh.triangles_tree at 0xffd2cc2eb1a0>
        kwargs     = {}
        name       = 'triangles_tree'
        self       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
.venv/lib/python3.12.../site-packages/trimesh/base.py:911: in triangles_tree
    return triangles.bounds_tree(self.triangles)
        self       = <trimesh.Trimesh(vertices.shape=(8, 3), faces.shape=(12, 3))>
.venv/lib/python3.12.../site-packages/trimesh/triangles.py:392: in bounds_tree
    tree = util.bounds_tree(triangle_bounds)
        triangle_bounds = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])
        triangles  = array([[[       -0.5,        -0.5,         0.5],
        [       -0.5,         0.5,         0.5],
        [       -0.5... 0.5,         0.5],
        [        0.5,        -0.5,         0.5],
        [        0.5,         0.5,        -0.5]]])
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

bounds = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])

    def bounds_tree(bounds):
        """
        Given a set of axis aligned bounds create an r-tree for
        broad-phase collision detection.
    
        Parameters
        ------------
        bounds : (n, 2D) or (n, 2, D) float
          Non-interleaved bounds where D=dimension
          E.G a 2D bounds tree:
          [(minx, miny, maxx, maxy), ...]
    
        Returns
        ---------
        tree : Rtree
          Tree containing bounds by index
        """
>       import rtree
E       ModuleNotFoundError: No module named 'rtree'

bounds     = array([[       -0.5,        -0.5,        -0.5,        -0.5,         0.5,         0.5],
       [       -0.5,        -0....0.5,         0.5,         0.5],
       [        0.5,        -0.5,        -0.5,         0.5,         0.5,         0.5]])

.venv/lib/python3.12.../site-packages/trimesh/util.py:1821: ModuleNotFoundError
dimos.manipulation.planning.examples.test_manipulation_client::test_update_round_shape_helpers_call_complete_obstacle_rpc[update_sphere-dimensions0-sphere]
Stack Traces | 0.197s run time
self = <MagicMock name='mock.update_obstacle' id='280526096259536'>
args = ('shape', <ANY>, 'sphere', [0.4], None, None), kwargs = {}
expected = call('shape', <ANY>, 'sphere', [0.4], None, None)
actual = call(Obstacle(name='shape', obstacle_type=<ObstacleType.SPHERE: 2>, pose=Pose(position=Vector([          1           2...00000, 1.000000)), dimensions=(0.4,), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0xff23122cf240>
cause = None

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: update_obstacle('shape', <ANY>, 'sphere', [0.4], None, None)
E             Actual: update_obstacle(Obstacle(name='shape', obstacle_type=<ObstacleType.SPHERE: 2>, pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), dimensions=(0.4,), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))

_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0xff23122cf240>
actual     = call(Obstacle(name='shape', obstacle_type=<ObstacleType.SPHERE: 2>, pose=Pose(position=Vector([          1           2...00000, 1.000000)), dimensions=(0.4,), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
args       = ('shape', <ANY>, 'sphere', [0.4], None, None)
cause      = None
expected   = call('shape', <ANY>, 'sphere', [0.4], None, None)
kwargs     = {}
self       = <MagicMock name='mock.update_obstacle' id='280526096259536'>

....../usr/lib/python3.12/unittest/mock.py:944: AssertionError

During handling of the above exception, another exception occurred:

self = <MagicMock name='mock.update_obstacle' id='280526096259536'>
args = ('shape', <ANY>, 'sphere', [0.4], None, None), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
            raise AssertionError(msg)
>       return self.assert_called_with(*args, **kwargs)
E       AssertionError: expected call not found.
E       Expected: update_obstacle('shape', <ANY>, 'sphere', [0.4], None, None)
E         Actual: update_obstacle(Obstacle(name='shape', obstacle_type=<ObstacleType.SPHERE: 2>, pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), dimensions=(0.4,), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
E       
E       pytest introspection follows:
E       
E       Args:
E       assert (Obstacle(nam...lution=None),) == ('shape', <AN...], None, None)
E         
E         At index 0 diff: #x1B[0mObstacle(name=#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, obstacle_type=<ObstacleType.SPHERE: #x1B[94m2#x1B[39;49;00m>, pose=Pose(position=Vector([          #x1B[94m1#x1B[39;49;00m           #x1B[94m2#x1B[39;49;00m           #x1B[94m3#x1B[39;49;00m]), orientation=Quaternion(#x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m1.000000#x1B[39;49;00m)), dimensions=(#x1B[94m0.4#x1B[39;49;00m,), color=(#x1B[94m0.8#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.8#x1B[39;49;00m), mesh_path=#x1B[94mNone#x1B[39;49;00m, points=#x1B[94mNone#x1B[39;49;00m, octree_resolution=#x1B[94mNone#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m != #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         Right contains 5 more items, first extra item: #x1B[0m<ANY>#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[91m-     'shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     <ANY>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     'sphere',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     [#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-         0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     Obstacle(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         name='shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         obstacle_type=<ObstacleType.SPHERE: 2>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         dimensions=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                        0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                    ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         color=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+               ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         mesh_path=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         points=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         octree_resolution=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     ],#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[90m #x1B[39;49;00m )#x1B[90m#x1B[39;49;00m

args       = ('shape', <ANY>, 'sphere', [0.4], None, None)
kwargs     = {}
self       = <MagicMock name='mock.update_obstacle' id='280526096259536'>

....../usr/lib/python3.12/unittest/mock.py:956: AssertionError

During handling of the above exception, another exception occurred:

mocker = <pytest_mock.plugin.MockerFixture object at 0xff2312672780>
helper_name = 'update_sphere', dimensions = [0.4], shape = 'sphere'

    @pytest.mark.parametrize(
        ("helper_name", "dimensions", "shape"),
        [
            ("update_sphere", [0.4], "sphere"),
            ("update_cylinder", [0.4, 0.8], "cylinder"),
        ],
    )
    def test_update_round_shape_helpers_call_complete_obstacle_rpc(
        mocker: MockerFixture,
        helper_name: str,
        dimensions: list[float],
        shape: str,
    ) -> None:
        client = MagicMock()
        client.update_obstacle.return_value = True
        mocker.patch.object(manipulation_client, "_client", client)
        helper = getattr(manipulation_client, helper_name)
    
        result = helper("shape", 1.0, 2.0, 3.0, *dimensions)
    
        assert result is True
>       client.update_obstacle.assert_called_once_with(
            "shape",
            mocker.ANY,
            shape,
            dimensions,
            None,
            None,
        )
E       AssertionError: expected call not found.
E       Expected: update_obstacle('shape', <ANY>, 'sphere', [0.4], None, None)
E         Actual: update_obstacle(Obstacle(name='shape', obstacle_type=<ObstacleType.SPHERE: 2>, pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)), dimensions=(0.4,), color=(0.8, 0.2, 0.2, 0.8), mesh_path=None, points=None, octree_resolution=None))
E       
E       pytest introspection follows:
E       
E       Args:
E       assert (Obstacle(nam...lution=None),) == ('shape', <AN...], None, None)
E         
E         At index 0 diff: #x1B[0mObstacle(name=#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m, obstacle_type=<ObstacleType.SPHERE: #x1B[94m2#x1B[39;49;00m>, pose=Pose(position=Vector([          #x1B[94m1#x1B[39;49;00m           #x1B[94m2#x1B[39;49;00m           #x1B[94m3#x1B[39;49;00m]), orientation=Quaternion(#x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m0.000000#x1B[39;49;00m, #x1B[94m1.000000#x1B[39;49;00m)), dimensions=(#x1B[94m0.4#x1B[39;49;00m,), color=(#x1B[94m0.8#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.2#x1B[39;49;00m, #x1B[94m0.8#x1B[39;49;00m), mesh_path=#x1B[94mNone#x1B[39;49;00m, points=#x1B[94mNone#x1B[39;49;00m, octree_resolution=#x1B[94mNone#x1B[39;49;00m)#x1B[90m#x1B[39;49;00m != #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mshape#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         Right contains 5 more items, first extra item: #x1B[0m<ANY>#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[91m-     'shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     <ANY>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     'sphere',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     [#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-         0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     Obstacle(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         name='shape',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         obstacle_type=<ObstacleType.SPHERE: 2>,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         pose=Pose(position=Vector([          1           2           3]), orientation=Quaternion(0.000000, 0.000000, 0.000000, 1.000000)),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         dimensions=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                        0.4,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                    ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         color=(#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.2,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+                   0.8,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+               ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         mesh_path=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         points=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+         octree_resolution=None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     ],#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     ),#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         ?     ^#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[91m-     None,#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[90m #x1B[39;49;00m )#x1B[90m#x1B[39;49;00m

client     = <MagicMock id='280526097687344'>
dimensions = [0.4]
helper     = <function update_sphere at 0xff237cdca0c0>
helper_name = 'update_sphere'
mocker     = <pytest_mock.plugin.MockerFixture object at 0xff2312672780>
result     = True
shape      = 'sphere'

.../planning/examples/test_manipulation_client.py:75: AssertionError
dimos.codebase_checks.test_no_dunder_new::test_no_dunder_new
Stack Traces | 1.92s run time
def test_no_dunder_new() -> None:
        """Fail if any test file calls `__new__` to bypass `__init__`."""
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        hits = find_dunder_new_calls()
        if hits:
            listing = "\n".join(
                f"  - {p.relative_to(dimos_dir)}:{lineno}: {line.strip()}" for p, lineno, line in hits
            )
>           raise AssertionError(
                f"Found __new__ call(s) in test files:\n{listing}\n\n"
                "Tests must construct objects with the real constructor: __init__ is "
                "code under test too, and an object assembled by hand silently rots "
                "when the constructor changes. If __init__ does heavy work, mock the "
                "collaborators it needs instead of skipping it. Only if that is truly "
                "impossible, add the call to the WHITELIST in "
                "dimos/codebase_checks/test_no_dunder_new.py."
            )
E           AssertionError: Found __new__ call(s) in test files:
E             - .../planning/world/test_drake_world_planning_groups.py:48: world = object.__new__(DrakeWorld)
E             - .../planning/world/test_drake_world_planning_groups.py:192: DrakeWorld._create_shape(object.__new__(DrakeWorld), obstacle)
E           
E           Tests must construct objects with the real constructor: __init__ is code under test too, and an object assembled by hand silently rots when the constructor changes. If __init__ does heavy work, mock the collaborators it needs instead of skipping it. Only if that is truly impossible, add the call to the WHITELIST in dimos/codebase_checks/test_no_dunder_new.py.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
hits       = [(PosixPath('.../dimos/dimos/dimos/.../planning/world/test_drake_world_planning_groups.py'), 48.../test_drake_world_planning_groups.py'), 192, '        DrakeWorld._create_shape(object.__new__(DrakeWorld), obstacle)')]
listing    = '  - .../planning/world/test_drake_world_planning_groups.py:48: world = object.__new__(DrakeWorld)\n  - manip...planning/world/test_drake_world_planning_groups.py:192: DrakeWorld._create_shape(object.__new__(DrakeWorld), obstacle)'

dimos/codebase_checks/test_no_dunder_new.py:67: AssertionError
dimos.codebase_checks.test_no_underscore_assign::test_no_underscore_assignment
Stack Traces | 6.83s run time
def test_no_underscore_assignment():
        """Fail if any file assigns to a bare `_`."""
        dimos_dir = DIMOS_PROJECT_ROOT / "dimos"
        hits = find_underscore_assignments()
        if hits:
            listing = "\n".join(f"  - {p.relative_to(dimos_dir)}:{lineno}" for p, lineno in hits)
>           raise AssertionError(
                f"Found assignment(s) to `_` in dimos/:\n{listing}\n\n"
                "Assigning to `_` is not allowed: it hides an unused variable instead "
                "of removing it. Delete the variable. If you only need the "
                "expression's side effect, evaluate it directly with a call "
                "(`obj.method()`, `getattr(obj, 'attr')`) or log it; a bare attribute "
                "access needs `# noqa: B018`. Tuple unpacking (`a, _ = f()`) is fine "
                "and not flagged by this rule."
            )
E           AssertionError: Found assignment(s) to `_` in dimos/:
E             - perception/point_cloud_self_filter.py:80
E             - protocol/tf/point_cloud_tf_pose_source.py:53
E           
E           Assigning to `_` is not allowed: it hides an unused variable instead of removing it. Delete the variable. If you only need the expression's side effect, evaluate it directly with a call (`obj.method()`, `getattr(obj, 'attr')`) or log it; a bare attribute access needs `# noqa: B018`. Tuple unpacking (`a, _ = f()`) is fine and not flagged by this rule.

dimos_dir  = PosixPath('.../dimos/dimos/dimos')
hits       = [(PosixPath('.../dimos/dimos/dimos/perception/point_cloud_self_filter.py'), 80), (PosixPath('.../dimos/dimos/dimos/protocol/tf/point_cloud_tf_pose_source.py'), 53)]
listing    = '  - perception/point_cloud_self_filter.py:80\n  - protocol/tf/point_cloud_tf_pose_source.py:53'

dimos/codebase_checks/test_no_underscore_assign.py:55: 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.

@mintlify

mintlify Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟡 Building Jul 24, 2026, 5:43 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

can this file be unified with obstacle monitor

Comment thread dimos/protocol/tf/tf_pose_source.py Outdated

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

need to clarify the changes here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I prefer not to change these logics as I'm not the owner. any alternative?


# Input: Joint state from coordinator (for world sync)
coordinator_joint_state: In[JointState]
planning_voxel_map: In[PointCloud2]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

can we add through rpc api instead of continuous?

@mustafab0

mustafab0 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Heads up — I'm ingesting this branch's work onto current main, split across three PRs:

What survived is the idea, a wrist camera occludes the volume behind its own arm, so ray tracing can never fire a miss there and the arm walls itself in. The clear mask is the only way out, and nothing on main had it.

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