In lab automation, resources must be precisely located in space at well-defined positions. These positions can be defined on movable plate carriers like on the Hamilton STAR, or machined deck positions like on the OT-2. PyLabRobot makes it easy to assign labware to these defined positions as we will show below.
PLT_CAR_L5AC_A00 is a five-position Hamilton plate carrier. Carriers are the parents here: the plate’s parent is the carrier’s site 0, not the deck itself.
2
rails= is Hamilton-specific: the deck is numbered in rails, and the carrier is placed at the leftmost rail it occupies. On an OTDeck you hand a slot number instead — see the build-a-deck recipe.
Python you need here
carrier[0] = plate works because PlateCarrier implements __setitem__. It is the same assignment you would write for a list, and it does the assign_child_resource call for you.
Watch for mutable default arguments when you write deck-builder helpers: a list default is created once and shared by every call.
5.1 Build a deck from carriers
Put a plate on a Hamilton deck, and the same plate on an OT-2 deck, using each deck’s native positioning.
carrier[1] = cor_96_wellplate_360uL_Fb(name="second_plate")print("carrier children:", [c.name for c in carrier.children])
1
carrier[0] and carrier[1] are the same operation at different sites — the __setitem__ sugar for assigning a resource to a carrier position. You never hand the carrier a coordinate; it computes where site N is.
from pylabrobot.resources import OTDeck, cor_96_wellplate_360uL_Fbot_deck = OTDeck()ot_deck.assign_child_at_slot( cor_96_wellplate_360uL_Fb(name="ot_plate"), slot=1)
1
The OT-2 has no rails. OTDeck is a grid of twelve numbered slots, and assign_child_at_slot is its equivalent of rails=.
WarningGotcha: assign plates to the carrier, not the deck
lh.deck.assign_child_resource(plate, ...) technically works, but the plate ends up a direct child of the deck with no holder — no site tracking, and move_plate to a carrier position gets confused. Carriers are the interface for labware; the deck is the interface for carriers.
See also: carriers hold plates and tip racks; PlateHolder.pedestal_size_z controls how high a plate sits and is required in 0.2.2 — omit it and PLR raises.
5.2 Coordinates of a resource
Read where a resource is.
print("plate .location (relative to its holder):", plate.location)print("plate absolute: ", plate.get_absolute_location())
1
.location is parent-relative. The plate’s location of Coordinate(0, 0, -3.03) means “3 mm below the top of its holder” — it tells you nothing about where the holder is on the deck.
2
get_absolute_location() walks the tree and returns the deck coordinate: (306.5, 71.5, 183.1). This is the number that matters for collision checks and for reasoning about the physical deck.
plate .location (relative to its holder): Coordinate(000.000, 000.000, -03.030)
plate absolute: Coordinate(306.500, 071.500, 183.120)
WarningGotcha: .location and get_absolute_location() are different numbers
A plate on a deck rarely has a .location you would recognize as a deck position. If a coordinate ever looks wrong, ask which of the two you are reading. z="t" in get_absolute_location(z="t") gives the top surface, which matters when you are placing a second resource on top.
See also:center() and get_anchor() in chapter 4 resolve positions inside a well, the next level down.
5.3 Will it fit
Ask PLR whether a resource can be placed somewhere before committing to it.
new_plate = cor_96_wellplate_360uL_Fb(name="incoming")lh.deck.check_can_drop_resource_here(new_plate)print("empty space is fine")try: lh.deck.check_can_drop_resource_here(carrier, reassign=False)exceptValueErroras e:print("occupied space is not:", str(e)[:70])print("highest known point on deck:", lh.deck.get_highest_known_point())
1
A plate that is not yet assigned anywhere.
2
check_can_drop_resource_here is a non-mutating preflight — it validates the assignment without making it, so you can probe a position and decide.
3
A carrier is already there (via reassign=False this raises rather than replacing).
4
get_highest_known_point() returns the highest z over the whole deck — the number to check before a gripper traverses with something tall, or before stacking.
empty space is fine
occupied space is not: Will not reassign resource 'carrier' to the same parent: 'deck'. Will
highest known point on deck: 230.0
The two kinds of ‘no’.
check_can_drop_resource_here answers placement: does this fit, is this spot free.
Name conflicts answer identity: is a resource with this name already on the deck (chapter 2).
Different questions; both raise ValueError.
See also:ignore_collision=True on assign_child_resource skips the placement check.
rotated(z=90) returns a new resource rotated 90° about z — the original plate is untouched, so you can keep both a flat and a rotated view. Give the copy its own name: a rotation keeps the source name, and two resources cannot share one on the same deck (chapter 2).
2
The copy carries a Rotation(x, y, z) dataclass. Rotation alone does nothing; it is metadata that placement honors.
3
This is the rotation, visible: get_absolute_size_x/y() compute the bounding box from the rotated corners, so the 127.76 × 85.48 mm footprint comes back swapped. Portrait vs landscape is decided here, not by fiddling coordinates.
4
Assign the copy to a carrier site like any plate. An unassigned copy has no location — calling get_absolute_location() on it raises NoLocationError. Do not read the rotation out of this coordinate: it is site 2’s position, and it differs from plate’s mostly because it is a different site.
plate.rotated(z=90) does not change plate; if you forget to keep the return value, nothing has rotated. Prefer rotated = plate.rotated(z=90) and assign rotated to the carrier, not plate.
See also:Rotation composes for grippers and stacks; the well-level consequence of a rotated plate is that indexing (chapter 5) runs along the plate’s own axes.
5.5 What to remember
Carriers hold labware; the deck holds carriers. Plates go to carrier[i], never straight to the deck.
Hamilton decks use rails=; OT decks use assign_child_at_slot(slot=). Same idea, two words.
.location is parent-relative; get_absolute_location() is the deck coordinate. Read the absolute one.
check_can_drop_resource_here asks before committing; get_highest_known_point() tells you how tall the deck already is.
Rotation is a Rotation dataclass carried by a copy from rotated() — never a mutation.