Skip to content

Commit

Permalink
fix(api, shared-date): Deck extents fix to utilize padding for confli…
Browse files Browse the repository at this point in the history
…ct checking in place of mount offsets (#16119)

Cover RQA-3067
Extent padding numbers to limit deck extent movement across deck were measured from the extent limits to ensure the following truths:
- The front most nozzle must not cross the rear padding "line"
- The rear most nozzle must not cross the front padding "line"
- The left most nozzle must not cross the right padding "line"
- the right most nozzle must not cross the left padding "line"
These lines were identified on Flex as: 
- Rear line is row G of labware in deck row A
- Front line is row F of labware in deck row D
- Left line is column 2 of labware in Thermocycler
- Right line is column 11 of labware in deck column 2
  • Loading branch information
CaseyBatten authored Aug 26, 2024
1 parent dd7173d commit 21c7adb
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 27 deletions.
57 changes: 41 additions & 16 deletions api/src/opentrons/protocol_api/core/engine/deck_conflict.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,23 +416,48 @@ def _is_within_pipette_extents(
pipette_bounding_box_at_loc: Tuple[Point, Point, Point, Point],
) -> bool:
"""Whether a given point is within the extents of a configured pipette on the specified robot."""
mount = engine_state.pipettes.get_mount(pipette_id)
robot_extent_per_mount = engine_state.geometry.absolute_deck_extents
pip_back_left_bound, pip_front_right_bound, _, _ = pipette_bounding_box_at_loc
pipette_bounds_offsets = engine_state.pipettes.get_pipette_bounding_box(pipette_id)
from_back_right = (
robot_extent_per_mount.back_right[mount]
+ pipette_bounds_offsets.back_right_corner
)
from_front_left = (
robot_extent_per_mount.front_left[mount]
+ pipette_bounds_offsets.front_left_corner
)
channels = engine_state.pipettes.get_channels(pipette_id)
robot_extents = engine_state.geometry.absolute_deck_extents
(
pip_back_left_bound,
pip_front_right_bound,
pip_back_right_bound,
pip_front_left_bound,
) = pipette_bounding_box_at_loc

# Given the padding values accounted for against the deck extents,
# a pipette is within extents when all of the following are true:

# Each corner slot full pickup case:
# A1: Front right nozzle is within the rear and left-side padding limits
# D1: Back right nozzle is within the front and left-side padding limits
# A3 Front left nozzle is within the rear and right-side padding limits
# D3: Back left nozzle is within the front and right-side padding limits
# Thermocycler Column A2: Front right nozzle is within padding limits

if channels == 96:
return (
pip_front_right_bound.y
<= robot_extents.deck_extents.y + robot_extents.padding_rear
and pip_front_right_bound.x >= robot_extents.padding_left_side
and pip_back_right_bound.y >= robot_extents.padding_front
and pip_back_right_bound.x >= robot_extents.padding_left_side
and pip_front_left_bound.y
<= robot_extents.deck_extents.y + robot_extents.padding_rear
and pip_front_left_bound.x
<= robot_extents.deck_extents.x + robot_extents.padding_right_side
and pip_back_left_bound.y >= robot_extents.padding_front
and pip_back_left_bound.x
<= robot_extents.deck_extents.x + robot_extents.padding_right_side
)
# For 8ch pipettes we only check the rear and front extents
return (
from_back_right.x >= pip_back_left_bound.x >= from_front_left.x
and from_back_right.y >= pip_back_left_bound.y >= from_front_left.y
and from_back_right.x >= pip_front_right_bound.x >= from_front_left.x
and from_back_right.y >= pip_front_right_bound.y >= from_front_left.y
pip_front_right_bound.y
<= robot_extents.deck_extents.y + robot_extents.padding_rear
and pip_back_right_bound.y >= robot_extents.padding_front
and pip_front_left_bound.y
<= robot_extents.deck_extents.y + robot_extents.padding_rear
and pip_back_left_bound.y >= robot_extents.padding_front
)


Expand Down
14 changes: 14 additions & 0 deletions api/src/opentrons/protocol_engine/state/addressable_areas.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,20 @@ def mount_offsets(self) -> Dict[str, Point]:
"right": Point(x=right_offset[0], y=right_offset[1], z=right_offset[2]),
}

@cached_property
def padding_offsets(self) -> Dict[str, float]:
"""The padding offsets to be applied to the deck extents of the robot."""
rear_offset = self.state.robot_definition["paddingOffsets"]["rear"]
front_offset = self.state.robot_definition["paddingOffsets"]["front"]
left_side_offset = self.state.robot_definition["paddingOffsets"]["leftSide"]
right_side_offset = self.state.robot_definition["paddingOffsets"]["rightSide"]
return {
"rear": rear_offset,
"front": front_offset,
"left_side": left_side_offset,
"right_side": right_side_offset,
}

def get_addressable_area(self, addressable_area_name: str) -> AddressableArea:
"""Get addressable area."""
if not self._state.use_simulated_deck_config:
Expand Down
13 changes: 12 additions & 1 deletion api/src/opentrons/protocol_engine/state/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class _GripperMoveType(enum.Enum):
class _AbsoluteRobotExtents:
front_left: Dict[MountType, Point]
back_right: Dict[MountType, Point]
deck_extents: Point
padding_rear: float
padding_front: float
padding_left_side: float
padding_right_side: float


_LabwareLocation = TypeVar("_LabwareLocation", bound=LabwareLocation)
Expand Down Expand Up @@ -118,7 +123,13 @@ def absolute_deck_extents(self) -> _AbsoluteRobotExtents:
MountType.RIGHT: self._addressable_areas.deck_extents + right_offset,
}
return _AbsoluteRobotExtents(
front_left=front_left_abs, back_right=back_right_abs
front_left=front_left_abs,
back_right=back_right_abs,
deck_extents=self._addressable_areas.deck_extents,
padding_rear=self._addressable_areas.padding_offsets["rear"],
padding_front=self._addressable_areas.padding_offsets["front"],
padding_left_side=self._addressable_areas.padding_offsets["left_side"],
padding_right_side=self._addressable_areas.padding_offsets["right_side"],
)

def get_labware_highest_z(self, labware_id: str) -> float:
Expand Down
2 changes: 0 additions & 2 deletions api/src/opentrons/protocol_engine/state/pipettes.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,8 +845,6 @@ def get_pipette_bounds_at_specified_move_to_position(
- primary_nozzle_offset
+ pipette_bounds_offsets.front_right_corner
)
# TODO (spp, 2024-02-27): remove back right & front left;
# return only back left and front right points.
pip_back_right_bound = Point(
pip_front_right_bound.x, pip_back_left_bound.y, pip_front_right_bound.z
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,11 @@ def test_deck_conflict_raises_for_bad_pipette_move(
MountType.LEFT: Point(463.7, 433.3, 0.0),
MountType.RIGHT: Point(517.7, 433.3),
},
deck_extents=Point(477.2, 493.8, 0.0),
padding_rear=-181.21,
padding_front=55.8,
padding_left_side=31.88,
padding_right_side=-80.32,
)
)
decoy.when(
Expand Down Expand Up @@ -677,6 +682,11 @@ def test_deck_conflict_raises_for_collision_with_tc_lid(
MountType.LEFT: Point(463.7, 433.3, 0.0),
MountType.RIGHT: Point(517.7, 433.3),
},
deck_extents=Point(477.2, 493.8, 0.0),
padding_rear=-181.21,
padding_front=55.8,
padding_left_side=31.88,
padding_right_side=-80.32,
)
)

Expand All @@ -696,7 +706,7 @@ def test_deck_conflict_raises_for_collision_with_tc_lid(
)
with pytest.raises(
deck_conflict.PartialTipMovementNotAllowedError,
match="collision with thermocycler lid in deck slot A1.",
match="Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.",
):
deck_conflict.check_safe_for_pipette_movement(
engine_state=mock_state_view,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ def test_deck_conflicts_for_96_ch_a12_column_configuration() -> None:
):
instrument.pick_up_tip(badly_placed_tiprack.wells_by_name()["A1"])

with pytest.raises(
PartialTipMovementNotAllowedError, match="outside of robot bounds"
):
# Picking up from A1 in an east-most slot using a configuration with column 12 would
# result in a collision with the side of the robot.
instrument.pick_up_tip(well_placed_tiprack.wells_by_name()["A1"])

instrument.pick_up_tip(well_placed_tiprack.wells_by_name()["A12"])
instrument.aspirate(50, well_placed_labware.wells_by_name()["A4"])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ def test_deck_configuration_setting(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ def simulated_subject(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down Expand Up @@ -101,6 +107,12 @@ def subject(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand All @@ -127,6 +139,12 @@ def test_initial_state_simulated(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ def get_addressable_area_view(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ def addressable_area_store(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ def get_addressable_area_view(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down
6 changes: 6 additions & 0 deletions api/tests/opentrons/protocol_engine/state/test_module_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ def get_addressable_area_view(
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32,
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down
6 changes: 6 additions & 0 deletions api/tests/opentrons/protocol_engine/state/test_state_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ def placeholder_error_recovery_policy(*args: object, **kwargs: object) -> Any:
"robotType": "OT-2 Standard",
"models": ["OT-2 Standard", "OT-2 Refresh"],
"extents": [446.75, 347.5, 0.0],
"paddingOffsets": {
"rear": -35.91,
"front": 31.89,
"leftSide": 0,
"rightSide": 0,
},
"mountOffsets": {"left": [-34.0, 0.0, 0.0], "right": [0.0, 0.0, 0.0]},
},
deck_fixed_labware=[],
Expand Down
10 changes: 10 additions & 0 deletions shared-data/python/opentrons_shared_data/robot/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ class mountOffset(TypedDict):
gripper: NotRequired[List[float]]


class paddingOffset(TypedDict):
"""The padding offsets for a given robot type based off how far the pipettes can travel beyond the deck extents."""

rear: float
front: float
leftSide: float
rightSide: float


class RobotDefinition(TypedDict):
"""A python version of the robot definition type."""

displayName: str
robotType: RobotType
models: List[str]
extents: List[float]
paddingOffsets: paddingOffset
mountOffsets: mountOffset
6 changes: 6 additions & 0 deletions shared-data/robot/definitions/1/ot2.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
"robotType": "OT-2 Standard",
"models": ["OT-2 Standard", "OT-2 Refresh"],
"extents": [446.75, 347.5, 0.0],
"paddingOffsets": {
"rear": -35.91,
"front": 31.89,
"leftSide": 0,
"rightSide": 0
},
"mountOffsets": {
"left": [-34.0, 0.0, 0.0],
"right": [0.0, 0.0, 0.0]
Expand Down
6 changes: 6 additions & 0 deletions shared-data/robot/definitions/1/ot3.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
"robotType": "OT-3 Standard",
"models": ["OT-3 Standard"],
"extents": [477.2, 493.8, 0.0],
"paddingOffsets": {
"rear": -177.42,
"front": 51.8,
"leftSide": 31.88,
"rightSide": -80.32
},
"mountOffsets": {
"left": [-13.5, -60.5, 255.675],
"right": [40.5, -60.5, 255.675],
Expand Down
23 changes: 23 additions & 0 deletions shared-data/robot/schemas/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@
"description": "The maximum addressable coordinates of the deck without instruments.",
"$ref": "#/definitions/xyzArray"
},
"paddingOffsets": {
"description": "The distance from a given edge of a deck extent by which the maximum amount of travel is limited.",
"type": "object",
"required": ["rear", "front", "leftSide", "rightSide"],
"properties": {
"rear": {
"description": "The padding distance from the rear edge of the deck extents which the front nozzles of a pipette must not exceed.",
"type": "number"
},
"front": {
"description": "The padding distance from the front edge of the deck extents which the rear nozzles of a pipette must not exceed.",
"type": "number"
},
"leftSide": {
"description": "The padding distance from the left edge of the deck extents which the right-most nozzles of a pipette must not exceed.",
"type": "number"
},
"rightSide": {
"description": "The padding distance from the right edge of the deck extents which the left-most nozzles of a pipette must not exceed.",
"type": "number"
}
}
},
"mountOffsets": {
"description": "The physical mount offsets from the center of the instrument carriage.",
"type": "object",
Expand Down

0 comments on commit 21c7adb

Please sign in to comment.