Currently, discard_tips() in liquid_handler.py works like this:
trash = self.deck.get_trash_area() # Single Trash resource
trash_offsets = get_tight_single_resource_liquid_op_offsets(trash, num_channels=n)
return await self.drop_tips(tip_spots=[trash] * n, offsets=offsets, ...)
This computes Y offsets at runtime to spread tips across a single trash resource using MIN_SPACING_BETWEEN_CHANNELS = 9mm. The deck only knows about one trash location; the individual per-channel positions are calculated on the fly.
The Nimbus approach
@Cody made a great PR adding the Nimbus (Nimbus Integration: TCP Backend and Interface Introspection by cmoscy · Pull Request #740 · PyLabRobot/pylabrobot · GitHub) where he took a different approach to this:
The Nimbus deck defines individual waste positions for each channel as first-class resources:
# nimbus_decks.py
waste_positions_hamilton = [
Coordinate(x=553.746, y=19.863, z=131.389), # default_long_1
Coordinate(x=553.746, y=1.880, z=131.389), # default_long_2
Coordinate(x=553.746, y=-76.149, z=131.389), # default_long_3
...
Coordinate(x=553.746, y=-237.532, z=131.389), # default_long_8
]
for i, locations in enumerate(locations, start=1):
waste_position = Trash(name=f"default_long_{i}", ...)
waste_block.assign_child_resource(waste_position, location=location)
The backend then looks up the correct resource per channel:
# nimbus_backend.py line 1263
waste_pos_name = f"{self.deck.waste_type}_{channel_idx + 1}"
waste_pos = self.deck.get_resource(waste_pos_name)
No offset computation needed. Channel 0 → default_long_1, channel 1 → default_long_2, etc.
Proposal
The current approach in PLR bakes assumptions into the offset calculation that may not match the actual hardware waste geometry. The Nimbus approach makes the waste positions explicit and queryable, just like tip rack spots.
I want to use Cody’s Nimbus approach everywhere in PLR. Specifically:
- Add per-channel waste resources to all decks. Like Nimbus, STARLetDeck and others should define Trash resources for each channel position (e.g. trash_1, trash_2, … trash_16).
- Update discard_tips() to use per-channel resources. Instead of:
tip_spots=[trash] * n, offsets=computed_offsetsit becomes:tip_spots=[deck.get_resource(f"trash_{ch+1}") for ch in use_channels](something like that) - Remove
get_tight_single_resource_liquid_op_offsetsfrom discard path. This function would only be needed for edge cases where users truly want to drop to an arbitrary single resource.