Grabber¶
Breaking change: grab is ray-based
grab no longer takes a world-coordinate point. It always grabs a
grabbable within radius metres of the desktop cursor ray's hit
point — aim first with
CursorClient.set_position. A ray miss is reported as
GrabResult.grabbed == False (not an error); VR mode fails with
FAILED_PRECONDITION.
Runnable example
python/examples/grabber_grab.py — a full positive pick-up: spawn a Mirror from the inventory, hold the cursor on it, grab at the ray hit point, then release. The grabbed object stays at the cursor position where it was grabbed and follows the hand from there.
Operating the held / equipped item
After grabbing, drive the interactions an avatar can perform on the held object:
usepresses a button (primary= left-click,secondary= right-click) and holds it down untilunuse. While grabbing, a primary press aligns the object; while a tool is equipped it activates the tool. The hold persists across RPCs, so a Pen can be pressed, dragged viaCursorClient.set_position, then released to draw a stroke. The optionalstrength(0..1, default1.0) is the analog pressure of the primary press — aBrushTool/Pen reads it as pen pressure — and is ignored for the secondary button.clickis a convenience for a single press+release (e.g. one-shot align).equip/dequipequip a grabbed tool into the hand / remove it.equipis a no-op when noIToolis grabbed.
GrabState.held_buttons, is_tool_equipped, and
equipped_tool_name report the resulting state.
resoio.grabber.GrabberClient ¶
Bases: _BaseClient[GrabberStub]
Async client for the Resonite IO Grabber service over a UDS.
Use as an async context manager so the gRPC channel is closed
deterministically. Socket resolution mirrors
:class:resoio.ConnectionClient.
Source code in src/resoio/_client.py
grab
async
¶
Grab a grabbable near the cursor ray hit point.
Grabs a grabbable within radius metres (<= 0 lets the
server apply its default, 0.1m) of the current desktop cursor
ray's hit point. Aim beforehand with
:meth:resoio.CursorClient.set_position. A ray miss or nothing
grabbable in range is reported as
GrabResult.grabbed == False, not an error. In VR mode the
call fails with :class:grpclib.exceptions.GRPCError
(FAILED_PRECONDITION).
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
release
async
¶
Release everything the hand is holding and return the new state.
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
get_state
async
¶
Return the hand's current hold state without modifying it.
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
use
async
¶
use(
*,
hand: GrabberHandArg = "primary",
button: GrabberButtonArg = "primary",
strength: float = 1.0,
) -> GrabState
Press button and hold it down until :meth:unuse.
button="primary" is a left-click, "secondary" a
right-click. While grabbing, a primary press aligns the held
object; while a tool is equipped it activates the tool. The
button stays held (re-injected every engine frame), so a Pen can
be pressed, dragged via
:meth:resoio.CursorClient.set_position, then released with
:meth:unuse to draw a stroke.
strength is the analog press strength (0..1) of the primary
button, e.g. the pen pressure a BrushTool reads (default
1.0). It is held at the same value for the duration of the
hold and is ignored for button="secondary" (which is digital
only). Out-of-range values are clamped by the server.
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
unuse
async
¶
Release button previously held via :meth:use (no-op if not
held).
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
click
async
¶
click(
*,
hand: GrabberHandArg = "primary",
button: GrabberButtonArg = "primary",
strength: float = 1.0,
) -> GrabState
Press and release button once (a :meth:use then
:meth:unuse).
A convenience for single-shot interactions such as aligning a
grabbed object, where the hold of :meth:use is not needed. The
two RPCs run on separate engine ticks so the press registers
before the release. Returns the state after :meth:unuse.
strength is forwarded to the :meth:use press (analog
primary press strength 0..1, default 1.0, ignored for
secondary); :meth:unuse carries no strength.
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
equip
async
¶
Equip the grabbed tool into the hand (no-op if no tool is grabbed).
Searches the hand's grabbed objects for an ITool component
and equips the first one found. After equipping, :meth:use
activates the tool's function.
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
dequip
async
¶
Remove the tool currently equipped on the hand (no-op if none).
gRPC failures surface as :class:grpclib.exceptions.GRPCError.
Source code in src/resoio/grabber.py
resoio.grabber.GrabResult
dataclass
¶
Result of a :meth:GrabberClient.grab call.
grabbed is True only when this call newly grabbed something;
a ray miss or nothing grabbable in range is reported as
grabbed=False rather than an error. state is the hold state
after the call.
resoio.grabber.GrabState
dataclass
¶
GrabState(
hand: GrabberHandArg,
is_holding: bool,
object_names: tuple[str, ...],
unix_nanos: int,
is_tool_equipped: bool,
equipped_tool_name: str,
held_buttons: tuple[GrabberButtonArg, ...],
)
Snapshot of what a hand is currently holding / operating.
hand echoes back which hand the server actually acted on, so a
caller that passed "primary" learns whether it resolved to left
or right (it is never "unspecified" — UNSPECIFIED decodes as
"primary"). object_names is a best-effort list of held
grabbable slot names and may be empty even when is_holding is
True. is_tool_equipped / equipped_tool_name describe the
tool currently equipped on the hand (equipped_tool_name is empty
when nothing is equipped). held_buttons lists the buttons
currently held down via :meth:GrabberClient.use (cleared by
:meth:GrabberClient.unuse).