diff --git a/api/tests/opentrons/protocol_runner/test_json_translator.py b/api/tests/opentrons/protocol_runner/test_json_translator.py index 0c65274ad9a..6dbba6a006c 100644 --- a/api/tests/opentrons/protocol_runner/test_json_translator.py +++ b/api/tests/opentrons/protocol_runner/test_json_translator.py @@ -13,6 +13,10 @@ Group, Metadata1, WellDefinition, + BoundedSection, + RectangularCrossSection, + InnerLabwareGeometry, + SphericalSegment, ) from opentrons_shared_data.protocol.models import ( protocol_schema_v6, @@ -685,6 +689,31 @@ def _load_labware_definition_data() -> LabwareDefinition: }, dimensions=Dimensions(yDimension=85.5, zDimension=100, xDimension=127.75), cornerOffsetFromSlot=CornerOffsetFromSlot(x=0, y=0, z=0), + innerWellGeometry=InnerLabwareGeometry( + frusta=[ + BoundedSection( + geometry=RectangularCrossSection( + shape="rectangular", + xDimension=7.6, + yDimension=8.5, + ), + topHeight=45, + ), + BoundedSection( + geometry=RectangularCrossSection( + shape="rectangular", + xDimension=5.6, + yDimension=6.5, + ), + topHeight=20, + ), + ], + bottomShape=SphericalSegment( + shape="spherical", + radius_of_curvature=6, + depth=10, + ), + ), brand=BrandData(brand="foo"), metadata=Metadata( displayName="Foo 8 Well Plate 33uL", diff --git a/shared-data/python/opentrons_shared_data/labware/labware_definition.py b/shared-data/python/opentrons_shared_data/labware/labware_definition.py index 1b2e68040de..537ea89ee27 100644 --- a/shared-data/python/opentrons_shared_data/labware/labware_definition.py +++ b/shared-data/python/opentrons_shared_data/labware/labware_definition.py @@ -224,6 +224,55 @@ class Config: ) +class CircularCrossSection(BaseModel): + shape: Literal["circular"] = Field(..., description="Denote shape as circular") + diameter: _NonNegativeNumber = Field( + ..., description="The diameter of a circular cross section of a well" + ) + + +class RectangularCrossSection(BaseModel): + shape: Literal["rectangular"] = Field( + ..., description="Denote shape as rectangular" + ) + xDimension: Optional[_NonNegativeNumber] = Field( + None, + description="x dimension of a subsection of wells", + ) + yDimension: Optional[_NonNegativeNumber] = Field( + None, + description="y dimension of a subsection of wells", + ) + + +class SphericalSegment(BaseModel): + shape: Literal["spherical"] = Field(..., description="Denote shape as spherical") + radius_of_curvature: _NonNegativeNumber = Field( + ..., + description="radius of curvature of bottom subsection of wells", + ) + depth: _NonNegativeNumber = Field( + ..., description="The depth of a spherical bottom of a well" + ) + + +TopCrossSection = Union[CircularCrossSection, RectangularCrossSection] +BottomShape = Union[CircularCrossSection, RectangularCrossSection, SphericalSegment] + + +class BoundedSection(BaseModel): + geometry: TopCrossSection = Field( + ..., + description="Geometrical information needed to calculate the volume of a subsection of a well", + discriminator="shape", + ) + topHeight: _NonNegativeNumber = Field( + ..., + description="The height at the top of a bounded subsection of a well, relative to the bottom" + "of the well", + ) + + class Metadata1(BaseModel): """ Metadata specific to a grid of wells in a labware @@ -252,6 +301,18 @@ class Group(BaseModel): ) +class InnerLabwareGeometry(BaseModel): + frusta: List[BoundedSection] = Field( + ..., + description="A list of all of the sections of the well that have a contiguous shape", + ) + bottomShape: BottomShape = Field( + ..., + description="The shape at the bottom of the well: either a spherical segment or a cross-section", + discriminator="shape", + ) + + class LabwareDefinition(BaseModel): schemaVersion: Literal[1, 2] = Field( ..., description="Which schema version a labware is using" @@ -326,3 +387,7 @@ class LabwareDefinition(BaseModel): default_factory=None, description="Force, in Newtons, with which the gripper should grip the labware.", ) + innerWellGeometry: Optional[InnerLabwareGeometry] = Field( + None, + description="A list of bounded sections describing the geometry of the inside of the wells.", + ) diff --git a/shared-data/python/opentrons_shared_data/labware/types.py b/shared-data/python/opentrons_shared_data/labware/types.py index a938f337c0f..8a6135de8f9 100644 --- a/shared-data/python/opentrons_shared_data/labware/types.py +++ b/shared-data/python/opentrons_shared_data/labware/types.py @@ -3,7 +3,7 @@ types in this file by and large require the use of typing_extensions. this module shouldn't be imported unless typing.TYPE_CHECKING is true. """ -from typing import Dict, List, NewType, Union +from typing import Dict, List, NewType, Union, Optional from typing_extensions import Literal, TypedDict @@ -37,6 +37,7 @@ Circular = Literal["circular"] Rectangular = Literal["rectangular"] +Spherical = Literal["spherical"] WellShape = Union[Circular, Rectangular] @@ -117,6 +118,37 @@ class WellGroup(TypedDict, total=False): brand: LabwareBrandData +class CircularCrossSection(TypedDict): + shape: Circular + diameter: float + + +class RectangularCrossSection(TypedDict): + shape: Rectangular + xDimension: float + yDimension: float + + +class SphericalSegment(TypedDict): + shape: Spherical + radius_of_curvature: float + depth: float + + +TopCrossSection = Union[CircularCrossSection, RectangularCrossSection] +BottomShape = Union[CircularCrossSection, RectangularCrossSection, SphericalSegment] + + +class BoundedSection(TypedDict): + geometry: TopCrossSection + topHeight: float + + +class InnerLabwareGeometry(TypedDict): + frusta: List[BoundedSection] + bottomShape: BottomShape + + class _RequiredLabwareDefinition(TypedDict): schemaVersion: Literal[2] version: int @@ -138,3 +170,4 @@ class LabwareDefinition(_RequiredLabwareDefinition, total=False): gripperOffsets: Dict[str, GripperOffsets] gripForce: float gripHeightFromLabwareBottom: float + innerWellGeometry: Optional[InnerLabwareGeometry]