Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions pylabrobot/liquid_handling/backends/hamilton/STAR_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
from pylabrobot.resources.hamilton.hamilton_decks import (
STAR_SIZE_X,
STARLET_SIZE_X,
HamiltonCoreGrippers,
)
from pylabrobot.resources.liquid import Liquid
from pylabrobot.resources.rotation import Rotation
Expand Down Expand Up @@ -5161,6 +5162,27 @@ async def dispense_pip(

# -------------- 3.5.5 CoRe gripper commands --------------

def _get_core_front_back(self):
core_grippers = self.deck.get_resource("core_grippers")
assert isinstance(core_grippers, HamiltonCoreGrippers), "core_grippers must be CoReGrippers"
back_channel_y_center = round(
(
core_grippers.get_location_wrt(self.deck).y
+ core_grippers.back_channel_y_center
+ self.core_adjustment.y
)
* 10
)
front_channel_y_center = round(
(
core_grippers.get_location_wrt(self.deck).y
+ core_grippers.front_channel_y_center
+ self.core_adjustment.y
)
* 10
)
return back_channel_y_center, front_channel_y_center

@need_iswap_parked
async def get_core(self, p1: int, p2: int):
"""Get CoRe gripper tool from wasteblock mount."""
Expand All @@ -5182,8 +5204,7 @@ async def get_core(self, p1: int, p2: int):
raise ValueError(f"Deck size {deck_size} not supported")

channel_x_coord = round(xs + self.core_adjustment.x * 10)
back_channel_y_center = round(1250 + self.core_adjustment.y * 10)
front_channel_y_center = round(1070 + self.core_adjustment.y * 10)
back_channel_y_center, front_channel_y_center = self._get_core_front_back()
begin_z_coord = round(2350 + self.core_adjustment.z * 10)
end_z_coord = round(2250 + self.core_adjustment.z * 10)

Expand Down Expand Up @@ -5219,8 +5240,7 @@ async def put_core(self):
raise ValueError(f"Deck size {deck_size} not supported")

channel_x_coord = round(xs + self.core_adjustment.x * 10)
back_channel_y_center = round(1240 + self.core_adjustment.y * 10)
front_channel_y_center = round(1065 + self.core_adjustment.y * 10)
back_channel_y_center, front_channel_y_center = self._get_core_front_back()
begin_z_coord = round(2150 + self.core_adjustment.z * 10)
end_z_coord = round(2050 + self.core_adjustment.z * 10)

Expand Down
2 changes: 1 addition & 1 deletion pylabrobot/liquid_handling/backends/hamilton/STAR_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,7 @@ async def test_move_core(self):
"C0ZRid0003xs03479xd0yj2102zj1876zi000zy0500yo0885th2800te2800"
),
_any_write_and_read_command_call(
"C0ZSid0004xs07975xd0ya1240yb1065tp2150tz2050th2800te2800"
"C0ZSid0004xs07975xd0ya1250yb1070tp2150tz2050th2800te2800"
),
]
)
Expand Down
27 changes: 14 additions & 13 deletions pylabrobot/resources/hamilton/hamilton_deck_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,29 @@ def test_summary(self):
deck.summary(),
textwrap.dedent(
"""
Rail Resource Type Coordinates (mm)
=================================================================================
(-6) ├── trash_core96 Trash (-58.200, 106.000, 216.400)
Rail Resource Type Coordinates (mm)
=======================================================================================
(-6) ├── trash_core96 Trash (-58.200, 106.000, 216.400)
(1) ├── tip_carrier TipCarrier (100.000, 063.000, 100.000)
│ ├── tip_rack_01 TipRack (106.200, 073.000, 214.950)
│ ├── tip_rack_02 TipRack (106.200, 169.000, 214.950)
(1) ├── tip_carrier TipCarrier (100.000, 063.000, 100.000)
│ ├── tip_rack_01 TipRack (106.200, 073.000, 214.950)
│ ├── tip_rack_02 TipRack (106.200, 169.000, 214.950)
│ ├── <empty>
│ ├── tip_rack_04 TipRack (106.200, 361.000, 214.950)
│ ├── tip_rack_04 TipRack (106.200, 361.000, 214.950)
│ ├── <empty>
(21) ├── plate carrier PlateCarrier (550.000, 063.000, 100.000)
│ ├── aspiration plate Plate (554.000, 071.500, 183.120)
(21) ├── plate carrier PlateCarrier (550.000, 063.000, 100.000)
│ ├── aspiration plate Plate (554.000, 071.500, 183.120)
│ ├── <empty>
│ ├── dispense plate Plate (554.000, 263.500, 183.120)
│ ├── dispense plate Plate (554.000, 263.500, 183.120)
│ ├── <empty>
│ ├── <empty>
(31) ├── waste_block Resource (775.000, 115.000, 100.000)
│ ├── teaching_tip_rack TipRack (780.900, 461.100, 100.000)
(31) ├── waste_block Resource (775.000, 115.000, 100.000)
│ ├── teaching_tip_rack TipRack (780.900, 461.100, 100.000)
│ ├── core_grippers HamiltonCoreGrippers (797.500, 125.000, 205.000)
(32) ├── trash Trash (800.000, 190.600, 137.100)
(32) ├── trash Trash (800.000, 190.600, 137.100)
"""[1:]
),
)
Expand Down
117 changes: 105 additions & 12 deletions pylabrobot/resources/hamilton/hamilton_decks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
from abc import ABCMeta, abstractmethod
from typing import Optional, cast
from typing import Literal, Optional, cast

from pylabrobot.resources.carrier import ResourceHolder
from pylabrobot.resources.coordinate import Coordinate
Expand Down Expand Up @@ -167,7 +167,13 @@ def assign_child_resource(
else:
raise ValueError("Either rails or location must be provided.")

if not ignore_collision:
def should_check_collision(res: Resource) -> bool:
"""Determine if collision detection should be performed for this resource."""
if isinstance(res, HamiltonCoreGrippers):
return False
return True

if not ignore_collision and should_check_collision(resource):
if resource_location is not None: # collision detection
if (
resource_location.x + resource.get_absolute_size_x()
Expand Down Expand Up @@ -262,7 +268,7 @@ def find_longest_type_name(resource: Resource):
name_column_length = max(
max_name_length + 4, 30
) # 4 per depth (by find_longest_child), 4 extra
type_column_length = max_type_length + 3 - 4
type_column_length = max_type_length + 1
location_column_length = 30

# Print header
Expand Down Expand Up @@ -349,6 +355,75 @@ def print_tree(resource: Resource, depth=0):
return summary_


class HamiltonCoreGrippers(Resource):
def __init__(
self,
name: str,
back_channel_y_center: float,
front_channel_y_center: float,
size_x: float,
size_y: float,
size_z: float,
model,
rotation=None,
category="core_grippers",
barcode=None,
):
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
rotation=rotation,
category=category,
model=model,
barcode=barcode,
)
self.back_channel_y_center = back_channel_y_center
self.front_channel_y_center = front_channel_y_center

def serialize(self):
return {
**super().serialize(),
"back_channel_y_center": self.back_channel_y_center,
"front_channel_y_center": self.front_channel_y_center,
}


def hamilton_core_gripper_1000ul_at_waste() -> HamiltonCoreGrippers:
# inner hole diameter is 8.6mm
# distance from base of rack to outer base of containers: -7mm
# left outer edge of rack is 22.5mm
# front outer edge of rack is 9.5mm

return HamiltonCoreGrippers(
name="core_grippers",
size_x=45, # from venus
size_y=45, # from venus
size_z=24, # from venus
back_channel_y_center=0,
front_channel_y_center=-26,
model=hamilton_core_gripper_1000ul_at_waste.__name__,
)


def hamilton_core_gripper_1000ul_5ml_on_waste() -> HamiltonCoreGrippers:
# distance from base of rack to outer base of containers: 0mm
# inner hole diameter is 8.6mm
# left outer edge of rack is 19.5mm
# front outer edge of rack is 39.5mm

return HamiltonCoreGrippers(
name="core_grippers",
size_x=39, # from venus
size_y=61, # from venus
size_z=24, # from venus
back_channel_y_center=0,
front_channel_y_center=-18,
model=hamilton_core_gripper_1000ul_5ml_on_waste.__name__,
)


class HamiltonSTARDeck(HamiltonDeck):
"""Base class for a Hamilton STAR(let) deck."""

Expand All @@ -364,8 +439,9 @@ def __init__(
with_trash: bool = True,
with_trash96: bool = True,
with_teaching_rack: bool = True,
no_trash: Optional[bool] = None,
no_teaching_rack: Optional[bool] = None,
core_grippers: Optional[
Literal["1000uL-at-waste", "1000uL-5mL-on-waste"]
] = "1000uL-5mL-on-waste",
) -> None:
"""Create a new STAR(let) deck of the given size."""

Expand All @@ -379,13 +455,6 @@ def __init__(
origin=origin,
)

if no_trash is not None:
raise NotImplementedError("no_trash is deprecated. Use with_trash=False instead.")
if no_teaching_rack is not None:
raise NotImplementedError(
"no_teaching_rack is deprecated. Use with_teaching_rack=False instead."
)

# assign trash area
if with_trash:
trash_x = (
Expand Down Expand Up @@ -436,10 +505,26 @@ def __init__(
location=Coordinate(x=self.rails_to_location(self.num_rails - 1).x, y=115.0, z=100),
)

if core_grippers == "1000uL-at-waste": # "at waste"
x: float = 1338 if num_rails == STAR_NUM_RAILS else 798
waste_block.assign_child_resource(
hamilton_core_gripper_1000ul_at_waste(),
location=Coordinate(x=x, y=105.550, z=205) - waste_block.location,
# ignore_collision=True,
)
elif core_grippers == "1000uL-5mL-on-waste": # "on waste"
x = 1337.5 if num_rails == STAR_NUM_RAILS else 797.5
waste_block.assign_child_resource(
hamilton_core_gripper_1000ul_5ml_on_waste(),
location=Coordinate(x=x, y=125, z=205) - waste_block.location,
# ignore_collision=True,
)

def serialize(self) -> dict:
return {
**super().serialize(),
"with_teaching_rack": False, # data encoded as child. (not very pretty to have this key though...)
"core_grippers": None, # data encoded as child. (not very pretty to have this key though...)
}

def rails_to_location(self, rails: int) -> Coordinate:
Expand Down Expand Up @@ -470,6 +555,9 @@ def STARLetDeck(
with_trash: bool = True,
with_trash96: bool = True,
with_teaching_rack: bool = True,
core_grippers: Optional[
Literal["1000uL-at-waste", "1000uL-5mL-on-waste"]
] = "1000uL-5mL-on-waste",
) -> HamiltonSTARDeck:
"""Create a new STARLet deck.

Expand All @@ -485,6 +573,7 @@ def STARLetDeck(
with_trash=with_trash,
with_trash96=with_trash96,
with_teaching_rack=with_teaching_rack,
core_grippers=core_grippers,
)


Expand All @@ -493,6 +582,9 @@ def STARDeck(
with_trash: bool = True,
with_trash96: bool = True,
with_teaching_rack: bool = True,
core_grippers: Optional[
Literal["1000uL-at-waste", "1000uL-5mL-on-waste"]
] = "1000uL-5mL-on-waste",
) -> HamiltonSTARDeck:
"""Create a new STAR deck.

Expand All @@ -508,4 +600,5 @@ def STARDeck(
with_trash=with_trash,
with_trash96=with_trash96,
with_teaching_rack=with_teaching_rack,
core_grippers=core_grippers,
)