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 00000000000..023499b4d3b --- /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 ecc8d29c844..73a1c58e8df 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 656314bba89..1746eb617ff 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 29af808690e..dc3ac068283 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 023499b4d3b..00000000000 --- 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 73a1c58e8df..ecc8d29c844 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 1746eb617ff..656314bba89 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 dc3ac068283..29af808690e 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 ecc8d29c844..567a71f6c86 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 4b7c1266ad0..e6fe91e63f6 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 656314bba89..e057278db97 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 3c3a1c1fe4e..793525ed5a4 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 e32b332dbc4..bdb6ba1d196 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 29af808690e..24142d5c070 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 567a71f6c86..03859bb9ac3 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 00000000000..26d33521af1 --- /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 00000000000..930c017364c --- /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 00000000000..105ce2da2d6 --- /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 00000000000..addea05e433 --- /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 00000000000..ec63674cd7f --- /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 00000000000..35eb1ddfbbc --- /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 e6fe91e63f6..9db28d478a5 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 e057278db97..114555ecaa5 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 793525ed5a4..971b9df20c3 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 24142d5c070..1f0ded9854f 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=[