diff --git a/CHANGELOG.md b/CHANGELOG.md index 4be416878c2..5ca13bbe9f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `HamiltonTCPClient.setup()` now resets all per-session state (client id, sequence numbers, instrument addresses, object registry) rather than carrying it into the new session, and refuses to run on an already-connected client instead of leaking the socket (#1195). - `HamiltonTCPClient` no longer recurses without bound when a device fails the introspection queries that error enrichment itself issues. Enrichment is now non-re-entrant and falls back to the static HC_RESULT tables, so a degraded instrument yields a terse error instead of a `RecursionError` (#1195). - `HamiltonTCPClient` no longer fails every command after the device sends a HARP control frame. MLPrep firmware sends one (options, no HOI body) shortly after registration; it was parsed as a command response and killed the reader. Frames with no routable message are skipped, as are unparsable frames, which is safe because frames are length-prefixed and consumed whole (#1195). +- `PlateAdapter.compute_plate_location` reads the grid from the plate's wells alone, so a plate carrying a lid can be assigned or moved onto a plate adapter instead of raising `ValueError: well spacing has to be uniform`. ### Changed diff --git a/pylabrobot/resources/plate_adapter.py b/pylabrobot/resources/plate_adapter.py index e3935653277..8f8c20e4fe4 100644 --- a/pylabrobot/resources/plate_adapter.py +++ b/pylabrobot/resources/plate_adapter.py @@ -124,9 +124,11 @@ def compute_plate_location(self, resource: Plate) -> Coordinate: align the `Plate` well-grid with the adapter's hole grid.""" # Calculate Plate information (which is not directly accessible from the Plate class) + # Wells only: a plate's lid is among its children too. # Child locations are never None, so we can safely ignore the type error - x_locations = sorted(set(well_n.location.x for well_n in resource.children)) # type: ignore - y_locations = sorted(set(well_n.location.y for well_n in resource.children)) # type: ignore + wells = resource.get_wells() + x_locations = sorted(set(well_n.location.x for well_n in wells)) # type: ignore + y_locations = sorted(set(well_n.location.y for well_n in wells)) # type: ignore def calculate_well_spacing(float_list: List[float]): """Calculate the difference between every x and x+1 element in the list of floats.""" @@ -142,8 +144,8 @@ def calculate_well_spacing(float_list: List[float]): plate_dx, plate_dy = float(x_locations[0]), float(y_locations[0]) plate_item_dx = abs(calculate_well_spacing(x_locations)) plate_item_dy = abs(calculate_well_spacing(y_locations)) - well_size_x = resource.children[0].get_absolute_size_x() - well_size_y = resource.children[0].get_absolute_size_y() + well_size_x = wells[0].get_absolute_size_x() + well_size_y = wells[0].get_absolute_size_y() # true_dz = resource.get_size_z() - resource.children[0].get_size_z() # Well-grid to hole-grid compatibility check diff --git a/pylabrobot/resources/plate_adapter_tests.py b/pylabrobot/resources/plate_adapter_tests.py index d25adc6ff32..b019e7ba51f 100644 --- a/pylabrobot/resources/plate_adapter_tests.py +++ b/pylabrobot/resources/plate_adapter_tests.py @@ -2,6 +2,7 @@ from pylabrobot.resources import ( Coordinate, + Lid, Plate, PlateAdapter, Well, @@ -94,3 +95,33 @@ def test_rectangular_hole_centers_each_axis_on_its_own_size(self): well_center = plate.get_well("H1").get_location_wrt(adapter, x="c", y="c", z="b") self.assertAlmostEqual(well_center.x, adapter.dx + adapter.adapter_hole_size_x / 2) self.assertAlmostEqual(well_center.y, adapter.dy + adapter.adapter_hole_size_y / 2) + + def test_lid_is_not_taken_for_a_well(self): + """A plate is placed on the adapter where it goes without its lid.""" + + plate = Plate( + name="plate", + size_x=127.76, + size_y=85.48, + size_z=14.0, + ordered_items=create_ordered_items_2d( + Well, + num_items_x=12, + num_items_y=8, + dx=9.5, + dy=6.5, + dz=1.0, + item_dx=9.0, + item_dy=9.0, + size_x=6.0, + size_y=6.0, + size_z=10.0, + ), + ) + without_lid = _adapter.compute_plate_location(plate) + + plate.assign_child_resource( + Lid(name="lid", size_x=127.76, size_y=85.48, size_z=8.0, nesting_z_height=6.0) + ) + + self.assertEqual(_adapter.compute_plate_location(plate), without_lid)