From a6fe029933e078904274483e5dbddc42893102e3 Mon Sep 17 00:00:00 2001 From: nedru004 Date: Fri, 13 Jun 2025 12:43:21 -0500 Subject: [PATCH 1/5] Remove redundancy and edit span When placing resources in the deck, 100 is added to the x location when placing on the deck and then removed when determining location. Also a similar algorithm was in place for y location. ys (span of pipettes) was set to the resource size, which works for plates, but not for troughs. If the pipettes are targeting the same resource, then keep the span at the minimum (90). Finally, find wash station coordinates instead of hardcoding. --- .idea/workspace.xml | 51 +++++++++++++++++++ .../backends/tecan/EVO_backend.py | 26 +++++++--- pylabrobot/resources/tecan/tecan_decks.py | 6 +-- pylabrobot/resources/tecan/wash.py | 6 +-- 4 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000000..023499b4d3 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py index ecc8d29c84..73a1c58e8d 100644 --- a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py +++ b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py @@ -11,6 +11,7 @@ ) from pylabrobot.io.usb import USB +from pylabrobot.liquid_handling.utils import get_tight_single_resource_liquid_op_offsets from pylabrobot.liquid_handling.backends.backend import ( LiquidHandlerBackend, ) @@ -288,6 +289,13 @@ async def setup(self): # Initialize plungers. Assumes wash station assigned at rail 1. await self.liha.set_z_travel_height([self._z_range] * self.num_channels) + wash = self.deck.get_resource("wash_waste") + wash_offsets = get_tight_single_resource_liquid_op_offsets(wash, self.num_channels) + location = wash.get_absolute_location() + wash_offsets + x = int(location.x * 10) + y = int(location.y * 10) + z = int(wash.get_size_z() * 10) # place pipettes above waste + await self.liha.position_absolute_all_axis(x,y,90,[z]*self.num_channels) await self.liha.position_absolute_all_axis(45, 1031, 90, [1200] * self.num_channels) await self.liha.initialize_plunger(self._bin_use_channels(list(range(self.num_channels)))) await self.liha.position_valve_logical([1] * self.num_channels) @@ -295,7 +303,7 @@ async def setup(self): await self.liha.position_valve_logical([0] * self.num_channels) await self.liha.set_end_speed_plunger([1800] * self.num_channels) await self.liha.move_plunger_relative([-100] * self.num_channels) - await self.liha.position_absolute_all_axis(45, 1031, 90, [self._z_range] * self.num_channels) + await self.liha.position_absolute_all_axis(x, y, 90, [self._z_range] * self.num_channels) async def setup_arm(self, module): try: @@ -367,7 +375,10 @@ async def aspirate( for op in ops ] - ys = int(ops[0].resource.get_absolute_size_y() * 10) + if len(set([op.resource for op in ops]))==1: # if the resource is the same, the span will stay at min + ys=90 + else: + ys = int(ops[0].resource.get_absolute_size_y() * 10) # for plate this number should be 90 zadd: List[Optional[int]] = [0] * self.num_channels for i, channel in enumerate(use_channels): par = ops[i].resource.parent @@ -443,7 +454,10 @@ async def dispense(self, ops: List[SingleChannelDispense], use_channels: List[in """ x_positions, y_positions, z_positions = self._liha_positions(ops, use_channels) - ys = int(ops[0].resource.get_absolute_size_y() * 10) + if len(set([op.resource for op in ops]))==1: # if the resource is the same, the span will stay at min + ys=90 + else: + ys = int(ops[0].resource.get_absolute_size_y() * 10) # for plate this number should be 90 tecan_liquid_classes = [ get_liquid_class( @@ -514,7 +528,7 @@ async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int]): first_z_start, _ = self._first_valid(z_positions["start"]) assert first_z_start is not None, "Could not find a valid z_start position" await self.liha.get_disposable_tip( - self._bin_use_channels(use_channels), first_z_start - 227, 210 + self._bin_use_channels(use_channels), first_z_start, 300 ) async def drop_tips(self, ops: List[Drop], use_channels: List[int]): @@ -678,8 +692,8 @@ def get_z_position(z, z_off, tip_length): for i, (op, channel) in enumerate(zip(ops, use_channels)): location = ops[i].resource.get_absolute_location() + op.resource.center() - x_positions[channel] = int((location.x - 100 + op.offset.x) * 10) - y_positions[channel] = int((346.5 - location.y + op.offset.y) * 10) # TODO: verify + x_positions[channel] = int((location.x + op.offset.x) * 10) + y_positions[channel] = int((location.y + op.offset.y) * 10) # TODO: verify par = ops[i].resource.parent if not isinstance(par, (TecanPlate, TecanTipRack)): diff --git a/pylabrobot/resources/tecan/tecan_decks.py b/pylabrobot/resources/tecan/tecan_decks.py index 656314bba8..1746eb617f 100644 --- a/pylabrobot/resources/tecan/tecan_decks.py +++ b/pylabrobot/resources/tecan/tecan_decks.py @@ -135,15 +135,15 @@ def _coordinate_for_rails(self, rails: int, resource: Resource): raise ValueError(f"Resource {resource} is not a Tecan resource.") return Coordinate( - (rails - 1) * _RAILS_WIDTH - resource.off_x + 100, - resource.off_y + 345 - resource.get_absolute_size_y(), + (rails - 1) * _RAILS_WIDTH - resource.off_x + 130, + resource.off_y + resource.get_absolute_size_y(), 0, ) # TODO: verify def _rails_for_x_coordinate(self, x: float): """Convert an x coordinate to a rail identifier.""" - return round((x + _RAILS_WIDTH - 101) / _RAILS_WIDTH) + 1 + return round((x + _RAILS_WIDTH - 131) / _RAILS_WIDTH) + 1 def summary(self) -> str: """Return a summary of the deck.""" diff --git a/pylabrobot/resources/tecan/wash.py b/pylabrobot/resources/tecan/wash.py index 29af808690..dc3ac06828 100644 --- a/pylabrobot/resources/tecan/wash.py +++ b/pylabrobot/resources/tecan/wash.py @@ -71,12 +71,12 @@ def Wash_Station(name: str) -> TecanWashStation: def Wash_Station_Waste(name: str) -> Trash: - return Trash(name=name, size_x=12.0, size_y=100.0, size_z=140.0) + return Trash(name=name, size_x=12.0, size_y=100.0, size_z=100.0) def Wash_Station_Cleaner_shallow(name: str) -> Trash: - return Trash(name=name, size_x=12.0, size_y=73.0, size_z=140.0) + return Trash(name=name, size_x=12.0, size_y=73.0, size_z=100.0) def Wash_Station_Cleaner_deep(name: str) -> Trash: - return Trash(name=name, size_x=12.0, size_y=73.0, size_z=140.0) + return Trash(name=name, size_x=12.0, size_y=73.0, size_z=100.0) From 7af93d9503904192786fc09edeb81ae7e096a090 Mon Sep 17 00:00:00 2001 From: nedru004 Date: Tue, 17 Jun 2025 09:36:28 -0500 Subject: [PATCH 2/5] Revert "Remove redundancy and edit span" This reverts commit a6fe029933e078904274483e5dbddc42893102e3. --- .idea/workspace.xml | 51 ------------------- .../backends/tecan/EVO_backend.py | 26 +++------- pylabrobot/resources/tecan/tecan_decks.py | 6 +-- pylabrobot/resources/tecan/wash.py | 6 +-- 4 files changed, 12 insertions(+), 77 deletions(-) delete mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 023499b4d3..0000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py index 73a1c58e8d..ecc8d29c84 100644 --- a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py +++ b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py @@ -11,7 +11,6 @@ ) from pylabrobot.io.usb import USB -from pylabrobot.liquid_handling.utils import get_tight_single_resource_liquid_op_offsets from pylabrobot.liquid_handling.backends.backend import ( LiquidHandlerBackend, ) @@ -289,13 +288,6 @@ async def setup(self): # Initialize plungers. Assumes wash station assigned at rail 1. await self.liha.set_z_travel_height([self._z_range] * self.num_channels) - wash = self.deck.get_resource("wash_waste") - wash_offsets = get_tight_single_resource_liquid_op_offsets(wash, self.num_channels) - location = wash.get_absolute_location() + wash_offsets - x = int(location.x * 10) - y = int(location.y * 10) - z = int(wash.get_size_z() * 10) # place pipettes above waste - await self.liha.position_absolute_all_axis(x,y,90,[z]*self.num_channels) await self.liha.position_absolute_all_axis(45, 1031, 90, [1200] * self.num_channels) await self.liha.initialize_plunger(self._bin_use_channels(list(range(self.num_channels)))) await self.liha.position_valve_logical([1] * self.num_channels) @@ -303,7 +295,7 @@ async def setup(self): await self.liha.position_valve_logical([0] * self.num_channels) await self.liha.set_end_speed_plunger([1800] * self.num_channels) await self.liha.move_plunger_relative([-100] * self.num_channels) - await self.liha.position_absolute_all_axis(x, y, 90, [self._z_range] * self.num_channels) + await self.liha.position_absolute_all_axis(45, 1031, 90, [self._z_range] * self.num_channels) async def setup_arm(self, module): try: @@ -375,10 +367,7 @@ async def aspirate( for op in ops ] - if len(set([op.resource for op in ops]))==1: # if the resource is the same, the span will stay at min - ys=90 - else: - ys = int(ops[0].resource.get_absolute_size_y() * 10) # for plate this number should be 90 + ys = int(ops[0].resource.get_absolute_size_y() * 10) zadd: List[Optional[int]] = [0] * self.num_channels for i, channel in enumerate(use_channels): par = ops[i].resource.parent @@ -454,10 +443,7 @@ async def dispense(self, ops: List[SingleChannelDispense], use_channels: List[in """ x_positions, y_positions, z_positions = self._liha_positions(ops, use_channels) - if len(set([op.resource for op in ops]))==1: # if the resource is the same, the span will stay at min - ys=90 - else: - ys = int(ops[0].resource.get_absolute_size_y() * 10) # for plate this number should be 90 + ys = int(ops[0].resource.get_absolute_size_y() * 10) tecan_liquid_classes = [ get_liquid_class( @@ -528,7 +514,7 @@ async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int]): first_z_start, _ = self._first_valid(z_positions["start"]) assert first_z_start is not None, "Could not find a valid z_start position" await self.liha.get_disposable_tip( - self._bin_use_channels(use_channels), first_z_start, 300 + self._bin_use_channels(use_channels), first_z_start - 227, 210 ) async def drop_tips(self, ops: List[Drop], use_channels: List[int]): @@ -692,8 +678,8 @@ def get_z_position(z, z_off, tip_length): for i, (op, channel) in enumerate(zip(ops, use_channels)): location = ops[i].resource.get_absolute_location() + op.resource.center() - x_positions[channel] = int((location.x + op.offset.x) * 10) - y_positions[channel] = int((location.y + op.offset.y) * 10) # TODO: verify + x_positions[channel] = int((location.x - 100 + op.offset.x) * 10) + y_positions[channel] = int((346.5 - location.y + op.offset.y) * 10) # TODO: verify par = ops[i].resource.parent if not isinstance(par, (TecanPlate, TecanTipRack)): diff --git a/pylabrobot/resources/tecan/tecan_decks.py b/pylabrobot/resources/tecan/tecan_decks.py index 1746eb617f..656314bba8 100644 --- a/pylabrobot/resources/tecan/tecan_decks.py +++ b/pylabrobot/resources/tecan/tecan_decks.py @@ -135,15 +135,15 @@ def _coordinate_for_rails(self, rails: int, resource: Resource): raise ValueError(f"Resource {resource} is not a Tecan resource.") return Coordinate( - (rails - 1) * _RAILS_WIDTH - resource.off_x + 130, - resource.off_y + resource.get_absolute_size_y(), + (rails - 1) * _RAILS_WIDTH - resource.off_x + 100, + resource.off_y + 345 - resource.get_absolute_size_y(), 0, ) # TODO: verify def _rails_for_x_coordinate(self, x: float): """Convert an x coordinate to a rail identifier.""" - return round((x + _RAILS_WIDTH - 131) / _RAILS_WIDTH) + 1 + return round((x + _RAILS_WIDTH - 101) / _RAILS_WIDTH) + 1 def summary(self) -> str: """Return a summary of the deck.""" diff --git a/pylabrobot/resources/tecan/wash.py b/pylabrobot/resources/tecan/wash.py index dc3ac06828..29af808690 100644 --- a/pylabrobot/resources/tecan/wash.py +++ b/pylabrobot/resources/tecan/wash.py @@ -71,12 +71,12 @@ def Wash_Station(name: str) -> TecanWashStation: def Wash_Station_Waste(name: str) -> Trash: - return Trash(name=name, size_x=12.0, size_y=100.0, size_z=100.0) + return Trash(name=name, size_x=12.0, size_y=100.0, size_z=140.0) def Wash_Station_Cleaner_shallow(name: str) -> Trash: - return Trash(name=name, size_x=12.0, size_y=73.0, size_z=100.0) + return Trash(name=name, size_x=12.0, size_y=73.0, size_z=140.0) def Wash_Station_Cleaner_deep(name: str) -> Trash: - return Trash(name=name, size_x=12.0, size_y=73.0, size_z=100.0) + return Trash(name=name, size_x=12.0, size_y=73.0, size_z=140.0) From a9fab4b4c97c9eab0eba59dd66b6b163bcd4976a Mon Sep 17 00:00:00 2001 From: nedru004 Date: Wed, 18 Jun 2025 09:39:32 -0500 Subject: [PATCH 3/5] Simplify x,y positioning Change x,y positioning to only use off_y for the carrier and get rid of off_x. This uses the edge of the metal deck as the reference for the y axis. Only updated tip carrier 10613022, tip rack 30000630, and plate carrier 30013061 --- .../backends/tecan/EVO_backend.py | 28 ++++++++++++++----- pylabrobot/resources/tecan/plate_carriers.py | 12 ++++---- pylabrobot/resources/tecan/tecan_decks.py | 6 ++-- pylabrobot/resources/tecan/tip_carriers.py | 12 ++++---- pylabrobot/resources/tecan/tip_racks.py | 2 +- pylabrobot/resources/tecan/wash.py | 10 +++---- 6 files changed, 42 insertions(+), 28 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py index ecc8d29c84..567a71f6c8 100644 --- a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py +++ b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py @@ -10,6 +10,7 @@ Union, ) +from pylabrobot.liquid_handling.utils import get_tight_single_resource_liquid_op_offsets from pylabrobot.io.usb import USB from pylabrobot.liquid_handling.backends.backend import ( LiquidHandlerBackend, @@ -288,14 +289,20 @@ async def setup(self): # Initialize plungers. Assumes wash station assigned at rail 1. await self.liha.set_z_travel_height([self._z_range] * self.num_channels) - await self.liha.position_absolute_all_axis(45, 1031, 90, [1200] * self.num_channels) + wash = self.deck.get_resource("wash_waste") + wash_offsets = get_tight_single_resource_liquid_op_offsets(wash, self.num_channels) + location = wash.get_absolute_location() + wash.center() + wash_offsets[0] + location.x = int(location.x * 10) + location.y = int((352 - location.y) * 10) + location.z = int(wash.get_size_z() * 10) + await self.liha.position_absolute_all_axis(location.x, location.y,90, [location.z] * self.num_channels) await self.liha.initialize_plunger(self._bin_use_channels(list(range(self.num_channels)))) await self.liha.position_valve_logical([1] * self.num_channels) await self.liha.move_plunger_relative([100] * self.num_channels) await self.liha.position_valve_logical([0] * self.num_channels) await self.liha.set_end_speed_plunger([1800] * self.num_channels) await self.liha.move_plunger_relative([-100] * self.num_channels) - await self.liha.position_absolute_all_axis(45, 1031, 90, [self._z_range] * self.num_channels) + await self.liha.position_absolute_all_axis(location.x, location.y, 90, [self._z_range] * self.num_channels) async def setup_arm(self, module): try: @@ -375,8 +382,11 @@ async def aspirate( continue if not isinstance(par, TecanPlate): raise ValueError(f"Operation is not supported by resource {par}.") - # TODO: calculate defaults when area is not specified - zadd[channel] = round(ops[i].volume / par.area * 10) + # Calculate distance to travel during aspiration + try: + zadd[channel] = int(ops[i].resource.compute_height_from_volume(ops[i].volume) * 10) + except NotImplementedError: + zadd[channel] = int(32) # moves such that first channel is over first location x, _ = self._first_valid(x_positions) @@ -459,7 +469,7 @@ async def dispense(self, ops: List[SingleChannelDispense], use_channels: List[in x, _ = self._first_valid(x_positions) y, yi = self._first_valid(y_positions) assert x is not None and y is not None - await self.liha.set_z_travel_height(z if z else self._z_range for z in z_positions["travel"]) + await self.liha.set_z_travel_height([z if z else self._z_range for z in z_positions["travel"]]) await self.liha.position_absolute_all_axis( x, y - yi * ys, @@ -487,6 +497,7 @@ async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int]): # Get positions including offsets x_positions, y_positions, z_positions = self._liha_positions(ops, use_channels) + z_positions["start"] = [int(op.resource.parent.z_start) for op in ops if op.resource.parent.z_start] # directly get z_position from z_start to avoid tip length issue? # move channels ys = int(ops[0].resource.get_absolute_size_y() * 10) @@ -678,8 +689,8 @@ def get_z_position(z, z_off, tip_length): for i, (op, channel) in enumerate(zip(ops, use_channels)): location = ops[i].resource.get_absolute_location() + op.resource.center() - x_positions[channel] = int((location.x - 100 + op.offset.x) * 10) - y_positions[channel] = int((346.5 - location.y + op.offset.y) * 10) # TODO: verify + x_positions[channel] = int((location.x + op.offset.x) * 10) + y_positions[channel] = int((352 - (location.y + op.offset.y)) * 10) # TODO: verify par = ops[i].resource.parent if not isinstance(par, (TecanPlate, TecanTipRack)): @@ -692,12 +703,15 @@ def get_z_position(z, z_off, tip_length): z_positions["start"][channel] = get_z_position( par.z_start, par.get_absolute_location().z + op.offset.z, tip_length ) + # container.get_absolute_position(z="cavity_bottom") + lld_search_height z_positions["dispense"][channel] = get_z_position( par.z_dispense, par.get_absolute_location().z + op.offset.z, tip_length ) + # container.get_absolute_position(z="cavity_bottom") + liquid_height z_positions["max"][channel] = get_z_position( par.z_max, par.get_absolute_location().z + op.offset.z, tip_length ) + # container.get_absolute_position(z="cavity_bottom") return x_positions, y_positions, z_positions diff --git a/pylabrobot/resources/tecan/plate_carriers.py b/pylabrobot/resources/tecan/plate_carriers.py index 4b7c1266ad..e6fe91e63f 100644 --- a/pylabrobot/resources/tecan/plate_carriers.py +++ b/pylabrobot/resources/tecan/plate_carriers.py @@ -553,8 +553,8 @@ def MP_4Pos_flat(name: str) -> TecanPlateCarrier: size_x=149.0, size_y=380.0, size_z=6.9, - off_x=11.0, - off_y=51.0, + off_x=0, + off_y=23.0, roma_x=1835, roma_y=388, roma_z_safe=946, @@ -563,10 +563,10 @@ def MP_4Pos_flat(name: str) -> TecanPlateCarrier: sites=create_homogeneous_resources( klass=PlateHolder, locations=[ - Coordinate(10.0, 3.5, 6.9), - Coordinate(10.0, 99.5, 6.9), - Coordinate(10.0, 195.5, 6.9), - Coordinate(10.0, 291.5, 6.9), + Coordinate(11.5, 3.5, 6.9), + Coordinate(11.5, 99.5, 6.9), + Coordinate(11.5, 195.5, 6.9), + Coordinate(11.5, 291.5, 6.9), ], resource_size_x=127.0, resource_size_y=85.5, diff --git a/pylabrobot/resources/tecan/tecan_decks.py b/pylabrobot/resources/tecan/tecan_decks.py index 656314bba8..e057278db9 100644 --- a/pylabrobot/resources/tecan/tecan_decks.py +++ b/pylabrobot/resources/tecan/tecan_decks.py @@ -135,15 +135,15 @@ def _coordinate_for_rails(self, rails: int, resource: Resource): raise ValueError(f"Resource {resource} is not a Tecan resource.") return Coordinate( - (rails - 1) * _RAILS_WIDTH - resource.off_x + 100, - resource.off_y + 345 - resource.get_absolute_size_y(), + 117 + (rails - 1) * _RAILS_WIDTH , + resource.off_y, 0, ) # TODO: verify def _rails_for_x_coordinate(self, x: float): """Convert an x coordinate to a rail identifier.""" - return round((x + _RAILS_WIDTH - 101) / _RAILS_WIDTH) + 1 + return round((x + _RAILS_WIDTH - 117) / _RAILS_WIDTH) + 1 def summary(self) -> str: """Return a summary of the deck.""" diff --git a/pylabrobot/resources/tecan/tip_carriers.py b/pylabrobot/resources/tecan/tip_carriers.py index 3c3a1c1fe4..793525ed5a 100644 --- a/pylabrobot/resources/tecan/tip_carriers.py +++ b/pylabrobot/resources/tecan/tip_carriers.py @@ -146,15 +146,15 @@ def DiTi_3Pos(name: str) -> TecanTipCarrier: name=name, size_x=149.0, size_y=374.0, - size_z=4.5, - off_x=12.0, - off_y=24.7, + size_z=123.2, + off_x=0, + off_y=60, sites=create_homogeneous_resources( klass=ResourceHolder, locations=[ - Coordinate(13.3, 70.0, 4.5), - Coordinate(13.3, 170.0, 4.5), - Coordinate(13.3, 270.0, 4.5), + Coordinate(14.7, 13.4, 4.5), + Coordinate(14.7, 113.4, 4.5), + Coordinate(14.7, 213.4, 4.5), ], resource_size_x=127.0, resource_size_y=88.5, diff --git a/pylabrobot/resources/tecan/tip_racks.py b/pylabrobot/resources/tecan/tip_racks.py index e32b332dbc..bdb6ba1d19 100644 --- a/pylabrobot/resources/tecan/tip_racks.py +++ b/pylabrobot/resources/tecan/tip_racks.py @@ -1235,7 +1235,7 @@ def DiTi_1000ul_LiHa(name: str) -> TecanTipRack: num_items_x=12, num_items_y=8, dx=7.7, - dy=8.7, + dy=7.7, dz=32.6, item_dx=9.0, item_dy=9.0, diff --git a/pylabrobot/resources/tecan/wash.py b/pylabrobot/resources/tecan/wash.py index 29af808690..24142d5c07 100644 --- a/pylabrobot/resources/tecan/wash.py +++ b/pylabrobot/resources/tecan/wash.py @@ -46,14 +46,14 @@ def Wash_Station(name: str) -> TecanWashStation: size_x=25.0, size_y=390.0, size_z=0.0, - off_x=12.5, - off_y=24.7, + off_y= -13, + off_x=0, sites=create_resources( klass=ResourceHolder, locations=[ - Coordinate(12.2, 106.7, 0.0), - Coordinate(11.0, 180.7, 0.0), - Coordinate(12.2, 281.7, 0.0), + Coordinate(12.2, 107, 0.0), + Coordinate(11.0, 181, 0.0), + Coordinate(12.2, 282, 0.0), ], resource_size_x=[ 12.0, From 72c487c215d1aac7c407c4044da66b593fecb8c4 Mon Sep 17 00:00:00 2001 From: David Nedrud Date: Wed, 18 Jun 2025 09:51:55 -0500 Subject: [PATCH 4/5] Update EVO_backend.py revert zadd. change this in future version --- pylabrobot/liquid_handling/backends/tecan/EVO_backend.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py index 567a71f6c8..03859bb9ac 100644 --- a/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py +++ b/pylabrobot/liquid_handling/backends/tecan/EVO_backend.py @@ -382,11 +382,8 @@ async def aspirate( continue if not isinstance(par, TecanPlate): raise ValueError(f"Operation is not supported by resource {par}.") - # Calculate distance to travel during aspiration - try: - zadd[channel] = int(ops[i].resource.compute_height_from_volume(ops[i].volume) * 10) - except NotImplementedError: - zadd[channel] = int(32) + # TODO: calculate defaults when area is not specified + zadd[channel] = round(ops[i].volume / par.area * 10) # moves such that first channel is over first location x, _ = self._first_valid(x_positions) From 59ea24f98cad3e17e816a369806e2c17ecd798cd Mon Sep 17 00:00:00 2001 From: nedru004 Date: Thu, 19 Jun 2025 13:56:34 -0500 Subject: [PATCH 5/5] Revert off_x Restore the off_x value --- .idea/.gitignore | 3 +++ .idea/inspectionProfiles/Project_Default.xml | 14 ++++++++++++++ .idea/inspectionProfiles/profiles_settings.xml | 6 ++++++ .idea/misc.xml | 7 +++++++ .idea/pylabrobot.iml | 7 +++++++ .idea/vcs.xml | 6 ++++++ pylabrobot/resources/tecan/plate_carriers.py | 10 +++++----- pylabrobot/resources/tecan/tecan_decks.py | 4 ++-- pylabrobot/resources/tecan/tip_carriers.py | 8 ++++---- pylabrobot/resources/tecan/wash.py | 2 +- 10 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/pylabrobot.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..26d33521af --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000..930c017364 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..105ce2da2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..addea05e43 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/pylabrobot.iml b/.idea/pylabrobot.iml new file mode 100644 index 0000000000..ec63674cd7 --- /dev/null +++ b/.idea/pylabrobot.iml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..35eb1ddfbb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pylabrobot/resources/tecan/plate_carriers.py b/pylabrobot/resources/tecan/plate_carriers.py index e6fe91e63f..9db28d478a 100644 --- a/pylabrobot/resources/tecan/plate_carriers.py +++ b/pylabrobot/resources/tecan/plate_carriers.py @@ -553,7 +553,7 @@ def MP_4Pos_flat(name: str) -> TecanPlateCarrier: size_x=149.0, size_y=380.0, size_z=6.9, - off_x=0, + off_x=11.0, off_y=23.0, roma_x=1835, roma_y=388, @@ -563,10 +563,10 @@ def MP_4Pos_flat(name: str) -> TecanPlateCarrier: sites=create_homogeneous_resources( klass=PlateHolder, locations=[ - Coordinate(11.5, 3.5, 6.9), - Coordinate(11.5, 99.5, 6.9), - Coordinate(11.5, 195.5, 6.9), - Coordinate(11.5, 291.5, 6.9), + Coordinate(10.0, 3.5, 6.9), + Coordinate(10.0, 99.5, 6.9), + Coordinate(10.0, 195.5, 6.9), + Coordinate(10.0, 291.5, 6.9), ], resource_size_x=127.0, resource_size_y=85.5, diff --git a/pylabrobot/resources/tecan/tecan_decks.py b/pylabrobot/resources/tecan/tecan_decks.py index e057278db9..114555ecaa 100644 --- a/pylabrobot/resources/tecan/tecan_decks.py +++ b/pylabrobot/resources/tecan/tecan_decks.py @@ -135,7 +135,7 @@ def _coordinate_for_rails(self, rails: int, resource: Resource): raise ValueError(f"Resource {resource} is not a Tecan resource.") return Coordinate( - 117 + (rails - 1) * _RAILS_WIDTH , + 130 + (rails - 1) * _RAILS_WIDTH - resource.off_x, resource.off_y, 0, ) # TODO: verify @@ -143,7 +143,7 @@ def _coordinate_for_rails(self, rails: int, resource: Resource): def _rails_for_x_coordinate(self, x: float): """Convert an x coordinate to a rail identifier.""" - return round((x + _RAILS_WIDTH - 117) / _RAILS_WIDTH) + 1 + return round((x + _RAILS_WIDTH - 130) / _RAILS_WIDTH) + 1 def summary(self) -> str: """Return a summary of the deck.""" diff --git a/pylabrobot/resources/tecan/tip_carriers.py b/pylabrobot/resources/tecan/tip_carriers.py index 793525ed5a..971b9df20c 100644 --- a/pylabrobot/resources/tecan/tip_carriers.py +++ b/pylabrobot/resources/tecan/tip_carriers.py @@ -147,14 +147,14 @@ def DiTi_3Pos(name: str) -> TecanTipCarrier: size_x=149.0, size_y=374.0, size_z=123.2, - off_x=0, + off_x=12, off_y=60, sites=create_homogeneous_resources( klass=ResourceHolder, locations=[ - Coordinate(14.7, 13.4, 4.5), - Coordinate(14.7, 113.4, 4.5), - Coordinate(14.7, 213.4, 4.5), + Coordinate(13.3, 13.4, 4.5), + Coordinate(13.3, 113.4, 4.5), + Coordinate(13.3, 213.4, 4.5), ], resource_size_x=127.0, resource_size_y=88.5, diff --git a/pylabrobot/resources/tecan/wash.py b/pylabrobot/resources/tecan/wash.py index 24142d5c07..1f0ded9854 100644 --- a/pylabrobot/resources/tecan/wash.py +++ b/pylabrobot/resources/tecan/wash.py @@ -47,7 +47,7 @@ def Wash_Station(name: str) -> TecanWashStation: size_y=390.0, size_z=0.0, off_y= -13, - off_x=0, + off_x=12.5, sites=create_resources( klass=ResourceHolder, locations=[