diff --git a/pylabrobot/visualizer3D/grids.py b/pylabrobot/visualizer3D/grids.py index 46b84d5fd1b..b1d629cc36a 100644 --- a/pylabrobot/visualizer3D/grids.py +++ b/pylabrobot/visualizer3D/grids.py @@ -48,6 +48,17 @@ def describe_bands(resource: Resource) -> Optional[List[Dict[str, Any]]]: return described or None +def _depth_in(child: Resource, frame: Resource) -> float: + """How far `child` reaches along `frame`'s y axis, in mm: its box, turned as it stands there.""" + ys = [ + child.get_location_wrt(frame, x, y, z).y + for x in ("l", "r") + for y in ("f", "b") + for z in ("b", "t") + ] + return max(ys) - min(ys) + + def describe_grid(resource: Resource) -> Optional[Dict[str, Any]]: """The repeated grid this resource lays positions on, or None if it lays none. @@ -102,10 +113,9 @@ def describe_grid(resource: Resource) -> Optional[Dict[str, Any]]: # A mark runs to the back of what the positions carry, not of the resource: the deepest child # seated on the line (sharing its y), or the resource's own depth when nothing stands on it. - try: - depth = resource.get_absolute_size_y() - except Exception: - return None + # Both along the resource's own y, the frame the marks are drawn in, so turning the resource + # turns its marks without changing them. + depth = resource.get_size_y() seated: List[float] = [] for child in getattr(resource, "children", []): @@ -113,7 +123,7 @@ def describe_grid(resource: Resource) -> Optional[Dict[str, Any]]: if location is None or abs(location.y - first.y) > SEATED_TOLERANCE: continue try: - seated.append(child.get_absolute_size_y()) + seated.append(_depth_in(child, resource)) except Exception: continue diff --git a/pylabrobot/visualizer3D/grids_tests.py b/pylabrobot/visualizer3D/grids_tests.py new file mode 100644 index 00000000000..dcac972e2b5 --- /dev/null +++ b/pylabrobot/visualizer3D/grids_tests.py @@ -0,0 +1,30 @@ +"""The grids a resource lays its positions on, as the page is given them to draw.""" + +import unittest + +from pylabrobot.resources.hamilton import STARLetDeck, hamilton_plate_carrier_L5_ac +from pylabrobot.visualizer3D.grids import describe_grid + + +class DescribeGridTests(unittest.TestCase): + """A deck's grid is measured in the deck's own frame, the frame its marks are drawn in.""" + + def test_a_turned_deck_has_the_grid_it_has_upright(self): + """The page draws a deck's marks in the deck's own frame and turns them with the deck, so the + grid is in that frame too. From a STARlet's front edge, its marks end at its back, 653.5 mm, + or at the back of a carrier on track 1, 560 mm: an `extent` of 590.5 or 497 mm from the first + track's line at 63 mm. A STARlet turned 90 degrees and measured along the world's y would end + its marks 1005 mm from its front edge, or 198 mm beside the carrier.""" + for turn in (90, 37.5): + for with_carrier, extent in ((False, 590.5), (True, 497.0)): + upright, turned = STARLetDeck(), STARLetDeck() + if with_carrier: + for deck in (upright, turned): + deck.assign_child_resource(hamilton_plate_carrier_L5_ac("carrier"), track=1) + turned.rotate(z=turn) + with self.subTest(turn=turn, with_carrier=with_carrier): + upright_grid, turned_grid = describe_grid(upright), describe_grid(turned) + assert upright_grid is not None and turned_grid is not None + self.assertAlmostEqual(upright_grid.pop("extent"), extent, places=3) + self.assertAlmostEqual(turned_grid.pop("extent"), extent, places=3) + self.assertEqual(turned_grid, upright_grid)