Skip to content

OT2RobotGeometry: add static OT-2 robot geometry with per-mount reach checks#1130

Merged
BioCam merged 4 commits into
PyLabRobot:mainfrom
BioCam:ot2-robot-geometry
Jul 7, 2026
Merged

OT2RobotGeometry: add static OT-2 robot geometry with per-mount reach checks#1130
BioCam merged 4 commits into
PyLabRobot:mainfrom
BioCam:ot2-robot-geometry

Conversation

@BioCam

@BioCam BioCam commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Adds OT2RobotGeometry, a frozen dataclass capturing the OT-2's fixed geometry in the robot frame: the gantry working extents, per-mount nozzle offsets, the head8 per-channel y-offsets, and the partial-tip body paddings. Because every OT-2 Standard shares this geometry it is a constant rather than something probed from the device - the key difference from Hamilton STAR, whose installed configuration must be read from firmware.

It also provides a centre-based can_reach_position(mount, position, channel_offset) predicate (mirroring STARBackend.can_reach_position) that works for single and multi-channel alike: a single pipette uses channel_offset=0, a head8 passes each nozzle's offset from channel_y_offsets(). Near the front/back limits only a subset of a multi's nozzles reach, which is why an edge column is picked up partially rather than all 8.

Pure additive: a new module, its tests, and one export. No behaviour change. This is the foundation for wiring reach checks into the OT-2 backend operations, which lands in a follow-up.

🤖 Generated with Claude Code

The OT-2's deck/gantry geometry is factory-identical across machines, so it lives as
a constant rather than something probed from the device (unlike STAR hardware). This
dataclass holds the gantry extents, partial-tip padding offsets, and left/right mount
offsets, sourced from the Opentrons shared-data definitions.

`single_channel_reach` / `can_reach_position` / `channel_y_offsets` mirror
`STARBackend.can_reach_position`; the head8 channel offsets are validated against the
Opentrons eight_channel nozzle map (A1 back-most at +31.5, 9 mm pitch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread pylabrobot/resources/opentrons/ot2_geometry.py
Comment on lines +61 to +70
@staticmethod
def channel_y_offsets(num_channels: int = 8, channel_pitch: float = 9.0) -> List[float]:
"""Y offset of each channel from the head center, in mm, for a linear multi-channel head.

Indexed back-to-front to match the Opentrons nozzle map: index 0 is the back-most nozzle (row
A, the primary/critical point) at the largest +y, descending to the front-most nozzle at -y.
Defaults describe the head8: 8 channels at 9 mm pitch spanning the head center by +-31.5 mm.
"""
half = (num_channels - 1) / 2
return [(half - i) * channel_pitch for i in range(num_channels)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

given that the 8 channels are fixed, how to interpret num_channels?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this for partial tip pickup scenarios?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

as a simple start, I recommend here to index based on coordinates and usual channel indexing:
(1) left to right in x
(2) back to front in y

Each channel is still an independent unit that can act with others but doesn't have to.
That way a tip in tip_spot "C1" e.g. can still be picked up by an 8-channel head's channel_2 and act like a standalone channel, giving maximum power and flexibility.

In v1, with a fully developed head8 capability I would like to build the a "per-head channel indexing" system.

But this needs to be tested in steps and in v0 we can use the Opentrons OT-2 as a testing bed for it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That way a tip in tip_spot "C1" e.g. can still be picked up by an 8-channel head's channel_2 and act like a standalone channel, giving maximum power and flexibility.

so the answer to the question is yes?

But this needs to be tested in steps and in v0 we can use the Opentrons OT-2 as a testing bed for it.

this is better done in v1

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes this is for partial tip pickup

I agree getting this straight into v1 would be best but since v1 is not ready yet for production and we need this urgently im happy to develop in v0 and v1 in parallel, I.e. merge this and learn from it how to submit updates to v1 in parallel

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In regards to 8 channels: yes i agree that doesn't make sense to make mutable, will update

BioCam and others added 2 commits July 1, 2026 18:30
Co-authored-by: Rick Wierenga <rick_wierenga@icloud.com>
The OT-2 head8 has a fixed 8 channels at 9 mm pitch, so these are not
per-call parameters. Inline them and drop the arguments.
Comment on lines +77 to +83
Mirrors ``STARBackend.can_reach_position``: a pure predicate over the reachable region, here the
single-channel deck extents for the mount. For a single channel use the default
``channel_offset=0``. For a head8, pass each channel's offset from :meth:`channel_y_offsets`; the
center must land within the extents, so near the front/back limits only a subset of channels can
reach, which is why an edge column is picked up partially rather than all 8. Z is not bounded by
deck extents and is checked separately by the motion layer.
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

still has star comparison

see #1130 (comment)

Describe the predicate in its own terms rather than by analogy to
another backend.
@BioCam BioCam merged commit a10a7fb into PyLabRobot:main Jul 7, 2026
21 checks passed
@BioCam BioCam deleted the ot2-robot-geometry branch July 7, 2026 09:02
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