From 48f113444c920d258e94d6fb4cf87b3880425919 Mon Sep 17 00:00:00 2001 From: caila-marashaj Date: Thu, 7 Aug 2025 14:18:31 -0400 Subject: [PATCH 1/4] add xy offset to geometry --- .../protocol_engine/state/geometry.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 3993333c7f0..52515bbcd36 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -588,6 +588,40 @@ def validate_probed_height( f"Liquid Height of {probed_height} mm is greater than maximum well height {well_depth} mm." ) + def get_xy_offset_if_needed(self, labware_id: str, well_name: str) -> WellOffset: + """Add an x,y offset to position the tip into the center of the well if needed.""" + well_def = self._labware.get_well_definition(labware_id, well_name) + well_geometry = self._labware.get_well_geometry( + labware_id=labware_id, well_name=well_name + ) + x_offset = 0.0 + y_offset = 0.0 + if isinstance(well_geometry, InnerWellGeometry): + sorted_well = sorted( + well_geometry.sections, key=lambda section: section.topHeight + ) + bottom_xcount = sorted_well[0].xCount + bottom_ycount = sorted_well[0].yCount + + if well_def.shape == "circular": + well_x_dimension = well_y_dimension = well_def.diameter + else: + assert well_def.shape == "rectangular", "invalid well shape" + well_x_dimension = well_def.xDimension + well_y_dimension = well_def.yDimension + + # if there are an even number of subsections, we'll need to reposition into + # the middle of one of the subsections, rather than the middle of the whole well. + if bottom_xcount % 2 == 0: + subsection_x_dimension = well_x_dimension / bottom_xcount + # move over into the middle of the nearest subsection + x_offset = subsection_x_dimension / 2 + if bottom_ycount % 2 == 0: + subsection_y_dimension = well_y_dimension / bottom_ycount + # move over into the middle of the nearest subection + y_offset = subsection_y_dimension / 2 + return WellOffset(x=x_offset, y=y_offset, z=0) + def get_well_position( self, labware_id: str, @@ -600,8 +634,10 @@ def get_well_position( labware_pos = self.get_labware_position(labware_id) well_def = self._labware.get_well_definition(labware_id, well_name) well_depth = well_def.depth - - offset = WellOffset(x=0, y=0, z=well_depth) + xy_offset = self.get_xy_offset_if_needed( + labware_id=labware_id, well_name=well_name + ) + offset = WellOffset(x=xy_offset.x, y=xy_offset.y, z=well_depth) if well_location is not None: offset = well_location.offset # location of the bottom of the well offset_adjustment = self.get_well_offset_adjustment( From 7c6a2eaf8ee2b89105ea06486dfad77ca463f5fd Mon Sep 17 00:00:00 2001 From: caila-marashaj Date: Thu, 7 Aug 2025 14:47:17 -0400 Subject: [PATCH 2/4] docstring fix --- api/src/opentrons/protocol_engine/state/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 52515bbcd36..af737cc1e24 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -589,7 +589,7 @@ def validate_probed_height( ) def get_xy_offset_if_needed(self, labware_id: str, well_name: str) -> WellOffset: - """Add an x,y offset to position the tip into the center of the well if needed.""" + """Add an x,y offset to position the tip into the center of a sub-well if needed.""" well_def = self._labware.get_well_definition(labware_id, well_name) well_geometry = self._labware.get_well_geometry( labware_id=labware_id, well_name=well_name From 58bf071b3e07d8eee4e80ee3da7014589c586b88 Mon Sep 17 00:00:00 2001 From: caila-marashaj Date: Fri, 8 Aug 2025 13:03:50 -0400 Subject: [PATCH 3/4] return immediately for stuff w no labwaregeometry --- api/src/opentrons/protocol_engine/state/geometry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index af737cc1e24..1847d911f69 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -590,6 +590,9 @@ def validate_probed_height( def get_xy_offset_if_needed(self, labware_id: str, well_name: str) -> WellOffset: """Add an x,y offset to position the tip into the center of a sub-well if needed.""" + labware_definition = self._labware.get_definition(labware_id) + if labware_definition.innerLabwareGeometry is None: + return WellOffset(x=0, y=0, z=0) well_def = self._labware.get_well_definition(labware_id, well_name) well_geometry = self._labware.get_well_geometry( labware_id=labware_id, well_name=well_name From 933ad22e18c209f9aa5d7176e44eb76f8ec929b7 Mon Sep 17 00:00:00 2001 From: caila-marashaj Date: Fri, 8 Aug 2025 15:25:55 -0400 Subject: [PATCH 4/4] change reqs for easy stuff --- api/src/opentrons/protocol_engine/state/geometry.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 1847d911f69..64e0ba9b2ed 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -588,11 +588,11 @@ def validate_probed_height( f"Liquid Height of {probed_height} mm is greater than maximum well height {well_depth} mm." ) - def get_xy_offset_if_needed(self, labware_id: str, well_name: str) -> WellOffset: + def _get_xy_offset_if_needed(self, labware_id: str, well_name: str) -> Point: """Add an x,y offset to position the tip into the center of a sub-well if needed.""" labware_definition = self._labware.get_definition(labware_id) if labware_definition.innerLabwareGeometry is None: - return WellOffset(x=0, y=0, z=0) + return Point(x=0, y=0, z=0) well_def = self._labware.get_well_definition(labware_id, well_name) well_geometry = self._labware.get_well_geometry( labware_id=labware_id, well_name=well_name @@ -609,7 +609,6 @@ def get_xy_offset_if_needed(self, labware_id: str, well_name: str) -> WellOffset if well_def.shape == "circular": well_x_dimension = well_y_dimension = well_def.diameter else: - assert well_def.shape == "rectangular", "invalid well shape" well_x_dimension = well_def.xDimension well_y_dimension = well_def.yDimension @@ -623,7 +622,7 @@ def get_xy_offset_if_needed(self, labware_id: str, well_name: str) -> WellOffset subsection_y_dimension = well_y_dimension / bottom_ycount # move over into the middle of the nearest subection y_offset = subsection_y_dimension / 2 - return WellOffset(x=x_offset, y=y_offset, z=0) + return Point(x=x_offset, y=y_offset, z=0) def get_well_position( self, @@ -637,7 +636,7 @@ def get_well_position( labware_pos = self.get_labware_position(labware_id) well_def = self._labware.get_well_definition(labware_id, well_name) well_depth = well_def.depth - xy_offset = self.get_xy_offset_if_needed( + xy_offset = self._get_xy_offset_if_needed( labware_id=labware_id, well_name=well_name ) offset = WellOffset(x=xy_offset.x, y=xy_offset.y, z=well_depth)