Skip to content

Commit

Permalink
Merge branch 'add_modules_to_deck_config' of github.com:Opentrons/ope…
Browse files Browse the repository at this point in the history
…ntrons into add_modules_to_deck_config
  • Loading branch information
b-cooper committed Apr 2, 2024
2 parents 746600e + b2783ca commit 0fc0f57
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 220 deletions.
16 changes: 3 additions & 13 deletions api/src/opentrons/protocol_api/core/engine/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,21 +412,11 @@ def load_module(
normalized_deck_slot = deck_slot.to_equivalent_for_robot_type(robot_type)
self._ensure_module_location(normalized_deck_slot, module_type)

addressable_area = validation.ensure_and_convert_module_fixture_location(
deck_slot, self._api_version, robot_type, model
result = self._engine_client.load_module(
model=EngineModuleModel(model),
location=DeckSlotLocation(slotName=normalized_deck_slot),
)

if robot_type == "OT-3 Standard" and isinstance(addressable_area, str):
result = self._engine_client.load_module(
model=EngineModuleModel(model),
location=AddressableAreaLocation(addressableAreaName=addressable_area),
)
else:
result = self._engine_client.load_module(
model=EngineModuleModel(model),
location=DeckSlotLocation(slotName=normalized_deck_slot),
)

module_core = self._get_module_core(load_module_result=result, model=model)

# FIXME(mm, 2023-02-21):
Expand Down
89 changes: 1 addition & 88 deletions api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from opentrons.protocols.api_support.types import APIVersion
from opentrons.protocols.api_support.util import APIVersionError
from opentrons.protocols.models import LabwareDefinition
from opentrons.protocol_engine.types import DeckType
from opentrons.types import Mount, DeckSlotName, StagingSlotName, Location
from opentrons.hardware_control.modules.types import (
ModuleModel,
Expand Down Expand Up @@ -336,94 +337,6 @@ def ensure_and_convert_trash_bin_location(
return map_trash_bin_addressable_area[slot_name_ot3]


def ensure_and_convert_module_fixture_location(
deck_slot: DeckSlotName,
api_version: APIVersion,
robot_type: RobotType,
model: ModuleModel,
) -> Optional[str]:
"""Ensure module fixture load location is valid.
Also, convert the deck slot to a valid module fixture addressable area.
"""

if robot_type == "OT-2 Standard":
# OT-2 Utilizes the existing compatibleModulelTypes list of traditional addressable areas
return None

if isinstance(model, MagneticBlockModel):
valid_slots = [
slot
for slot in [
"A1",
"B1",
"C1",
"D1",
"A2",
"B2",
"C2",
"D2",
"A3",
"B3",
"C3",
"D3",
]
]
addressable_areas = [
"magneticBlockV1A1",
"magneticBlockV1B1",
"magneticBlockV1C1",
"magneticBlockV1D1",
"magneticBlockV1A2",
"magneticBlockV1B2",
"magneticBlockV1C2",
"magneticBlockV1D2",
"magneticBlockV1A3",
"magneticBlockV1B3",
"magneticBlockV1C3",
"magneticBlockV1D3",
]

elif isinstance(model, HeaterShakerModuleModel):
valid_slots = [
slot for slot in ["A1", "B1", "C1", "D1", "A3", "B3", "C3", "D3"]
]
addressable_areas = [
"heaterShakerV1A1",
"heaterShakerV1B1",
"heaterShakerV1C1",
"heaterShakerV1D1",
"heaterShakerV1A3",
"heaterShakerV1B3",
"heaterShakerV1C3",
"heaterShakerV1D3",
]
elif isinstance(model, TemperatureModuleModel):
valid_slots = [
slot for slot in ["A1", "B1", "C1", "D1", "A3", "B3", "C3", "D3"]
]
addressable_areas = [
"temperatureModuleV2A1",
"temperatureModuleV2B1",
"temperatureModuleV2C1",
"temperatureModuleV2D1",
"temperatureModuleV2A3",
"temperatureModuleV2B3",
"temperatureModuleV2C3",
"temperatureModuleV2D3",
]
elif isinstance(model, ThermocyclerModuleModel):
return "thermocyclerModuleV2"
else:
return None

map_addressable_area = {
slot: addressable_area
for slot, addressable_area in zip(valid_slots, addressable_areas)
}
return map_addressable_area[deck_slot.value]


def ensure_hold_time_seconds(
seconds: Optional[float], minutes: Optional[float]
) -> float:
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_engine/clients/sync_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def move_to_coordinates(
def load_module(
self,
model: ModuleModel,
location: Union[DeckSlotLocation, AddressableAreaLocation],
location: DeckSlotLocation,
) -> commands.LoadModuleResult:
"""Execute a LoadModule command and return the result."""
request = commands.LoadModuleCreate(
Expand Down
16 changes: 10 additions & 6 deletions api/src/opentrons/protocol_engine/commands/load_module.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Implementation, request models, and response models for the load module command."""
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Type, Union
from typing import TYPE_CHECKING, Optional, Type
from typing_extensions import Literal
from pydantic import BaseModel, Field

Expand All @@ -9,7 +9,6 @@
DeckSlotLocation,
ModuleModel,
ModuleDefinition,
AddressableAreaLocation,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -42,7 +41,7 @@ class LoadModuleParams(BaseModel):
# single deck slot precludes loading a Thermocycler in its special "shifted slightly
# to the left" position. This is okay for now because neither the Python Protocol
# API nor Protocol Designer attempt to support it, either.
location: Union[DeckSlotLocation, AddressableAreaLocation] = Field(
location: DeckSlotLocation = Field(
...,
description=(
"The location into which this module should be loaded."
Expand Down Expand Up @@ -109,13 +108,18 @@ def __init__(

async def execute(self, params: LoadModuleParams) -> LoadModuleResult:
"""Check that the requested module is attached and assign its identifier."""
if isinstance(params.location, DeckSlotLocation):
if self._state_view.config.robot_type == "OT-2 Standard":
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
params.location.slotName.id
)
elif isinstance(params.location, AddressableAreaLocation):
else:
addressable_area = self._state_view.geometry._modules.ensure_and_convert_module_fixture_location(
deck_slot=params.location.slotName,
deck_type=self._state_view.config.deck_type,
model=params.model,
)
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
params.location.addressableAreaName
addressable_area
)

verified_location = self._state_view.geometry.ensure_location_not_occupied(
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_engine/execution/equipment.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ async def load_magnetic_block(
async def load_module(
self,
model: ModuleModel,
location: Union[DeckSlotLocation, AddressableAreaLocation],
location: DeckSlotLocation,
module_id: Optional[str],
) -> LoadedModuleData:
"""Ensure the required module is attached.
Expand Down
18 changes: 7 additions & 11 deletions api/src/opentrons/protocol_engine/slot_standardization.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,13 @@ def _standardize_load_labware(
def _standardize_load_module(
original: commands.LoadModuleCreate, robot_type: RobotType
) -> commands.LoadModuleCreate:
if isinstance(original.params.location, DeckSlotLocation):
params = original.params.copy(
update={
"location": _standardize_deck_slot_location(
original.params.location, robot_type
)
}
)
else:
params = original.params

params = original.params.copy(
update={
"location": _standardize_deck_slot_location(
original.params.location, robot_type
)
}
)
return original.copy(update={"params": params})


Expand Down
21 changes: 21 additions & 0 deletions api/src/opentrons/protocol_engine/state/addressable_areas.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,27 @@ def get_addressable_area_position(
position = addressable_area.position
return Point(x=position.x, y=position.y, z=position.z)

def get_addressable_area_offsets_from_cutout(
self,
addressable_area_name: str,
) -> Point:
"""Get the offset form cutout fixture of an addressable area."""

for addressable_area in self.state.deck_definition["locations"][
"addressableAreas"
]:
if addressable_area["id"] == addressable_area_name:
area_offset = addressable_area["offsetFromCutoutFixture"]
position = Point(
x=area_offset[0],
y=area_offset[1],
z=area_offset[2],
)
return Point(x=position.x, y=position.y, z=position.z)
raise ValueError(
f"No matching addressable area named {addressable_area_name} identified."
)

def get_addressable_area_bounding_box(
self,
addressable_area_name: str,
Expand Down
5 changes: 4 additions & 1 deletion api/src/opentrons/protocol_engine/state/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def get_highest_z_in_slot(
except LabwareNotLoadedOnModuleError:
return self._modules.get_module_highest_z(
module_id=module_id,
addressable_areas=self._addressable_areas,
)
else:
return self.get_highest_z_of_labware_stack(labware_id)
Expand Down Expand Up @@ -246,7 +247,9 @@ def _get_labware_position_offset(
return LabwareOffsetVector(x=0, y=0, z=0)
elif isinstance(labware_location, ModuleLocation):
module_id = labware_location.moduleId
module_offset = self._modules.get_nominal_module_offset(module_id=module_id)
module_offset = self._modules.get_nominal_module_offset(
module_id=module_id, addressable_areas=self._addressable_areas
)
module_model = self._modules.get_connected_model(module_id)
stacking_overlap = self._labware.get_module_overlap_offsets(
labware_id, module_model
Expand Down
Loading

0 comments on commit 0fc0f57

Please sign in to comment.