Collision checking¶
Collision is checked between the robot and itself, the robot and every enabled obstacle, and — with several robots — robot against robot. The same geometry serves the live queries here, the planner, and the sequence rollout's tick checks.
The queries¶
scene.in_collision() # bool, the fast yes/no
scene.check_collisions() # who: [(('link', 'forearm_link'), ('obstacle', 'wall')), ...]
scene.min_obstacle_distance() # tightest robot-obstacle distance in meters
# 0 when colliding, None with no obstacles
scene.min_obstacle_distance(to=["guard"]) # ... to the obstacles under guard/ only
check_collisions names pairs as (kind, name) tuples with kind "link" or
"obstacle" — the same names the studio uses to highlight the offending
geometry in red.
Over a whole baked cycle, the equivalent of min_obstacle_distance is
SequenceTimeline.min_clearance —
see Timeline assertions.
What the shapes are¶
Primitives collide as themselves. Meshes collide as VHACD convex decompositions: triangle-mesh-vs-triangle-mesh testing misses containment and mis-reports distance, so every mesh is decomposed into convex pieces (about a second per mesh on first load, cached on disk afterwards). The studio still renders the original mesh — only the collision layer sees the decomposition. The pieces hug the mesh surface rather than the corners of the voxels they were grown from, so a small gap keeps its clearance instead of gaining a resolution-dependent skin — and two faces in exact zero-gap contact count as touching. Concavities are still approximated, and a mesh with holes in it can lose its solid interior.
Anything that could not be prepared for collision is listed, not silently dropped:
Self-collision, and the inter-robot ACM¶
A robot's own allowed-contact matrix (adjacent links, permanently-touching pairs) is generated automatically by sampling. Inter-robot pairs are never inferred: whether two arms may touch depends on where their bases stand, which no sampler can know — so it is the author's call:
the escape hatch for arms that share a mount plate or are meant to touch.
The sampler only finds pairs that touch in nearly every pose (95 % of 256 samples). A whole-body machine has pairs that touch by design and fall short of that — torso segments a vendor checks with overlapping capsules (one RB-Y1 pair touches in 94 % of poses), a drive wheel steering inside its housing — and a plan that includes the torso then starts "in self-collision". Those are declared, the way a MoveIt SRDF does:
robot = robot.allow_collisions([("link_torso_2", "link_torso_4"),
("base_link", "left_wheel_drive_link")])
It returns a new robot; a name that is no link, or a pair of a link with
itself, is refused by name. A catalog package
declares its own (self_collision.allowed_pairs, with the basis it was taken
from) and Robot.from_catalog applies them, so a cell rarely writes this. A
declaration rides a welded tool or mount and is saved with the project. The
generated script re-authors the pairs a cell declared by hand; a package's own
come back with from_catalog and are not written out again.
Switching obstacles out¶
Disabled obstacles are excluded from every query above but keep rendering — moving scenery, dress geometry, or a fixture you want visible but not planned around.
Grasped objects¶
An attached obstacle collides as part of the
robot: it is checked against the environment, excluded against its
touch_links (the gripper pads that are supposed to touch it), and carried
through planning. Detach it and it goes back to being an obstacle where it
stands.