PyLabRobot Integration
Orca uses PyLabRobot for labware definitions and device backends.
Labware Definitions
PyLabRobot provides a comprehensive catalog of labware definitions that Orca uses through template classes.
Plate Definitions
from orca.sdk.labware import PlateTemplate
from pylabrobot.resources.thermo_fisher.plates import Thermo_Nunc_96_well_plate_1300uL_Rb
from pylabrobot.resources.corning.falcon.plates import Cor_Falcon_96_wellplate_340ul_Fb_Black
# Create templates with PLR plate factories
sample_plate = PlateTemplate(
"sample_plate",
Thermo_Nunc_96_well_plate_1300uL_Rb,
None # No lid
)
The second argument is a PyLabRobot plate factory function. These are imported from pylabrobot.resources submodules.
Common Plate Sources
| Manufacturer | Import Path |
|---|---|
| Thermo Fisher | pylabrobot.resources.thermo_fisher.plates |
| Corning | pylabrobot.resources.corning |
| Generic | pylabrobot.resources |
Browse the PyLabRobot resources directory for available definitions.
Tip Racks
from orca.sdk.labware import TipRackTemplate
from pylabrobot.resources.opentrons import opentrons_96_tiprack_300ul
tips = TipRackTemplate(
"tips_300",
opentrons_96_tiprack_300ul,
with_tips=True
)
Tube Racks and Troughs
from orca.resource_models.labware import TubeRackTemplate, TroughTemplate
tube_rack = TubeRackTemplate("tubes", tube_rack_factory)
trough = TroughTemplate("reagent_trough", trough_factory)
Device Backends
Orca wraps PyLabRobot backends so they work with Orca's driver interface.
Automatic Wrapping
Orca device constructors automatically wrap PLR backends:
from orca.devices.sealer import Sealer
from pylabrobot.sealing.a4s_backend import A4SBackend
# Pass PLR backend directly - Orca wraps it automatically
a4s_backend = A4SBackend(port="/dev/tty.usbserial-0001", timeout=10)
sealer = Sealer("sealer", a4s_backend)
The Sealer constructor detects the PLR backend type and wraps it with PLRSealerBackendWrapper.
Supported PLR Backends
| Device Type | PLR Backend Class | Wrapper |
|---|---|---|
| Sealer | SealerBackend | PLRSealerBackendWrapper |
| Shaker | ShakerBackend | PLRShakerBackendWrapper |
| Centrifuge | CentrifugeBackend | PLRCentrifugeBackendWrapper |
| Transporter | SCARABackend | PLRTransporterBackendWrapper |
How Wrapping Works
PLR backends use different method signatures than Orca's driver interfaces. Wrappers adapt them:
# PLR backend interface
class SealerBackend:
async def setup(self) -> None: ...
async def seal(self, temperature: int, duration: float) -> None: ...
# Orca driver interface
class ISealerDriver:
async def initialize(self) -> None: ...
async def seal(self, temperature: int, duration: float) -> None: ...
# Wrapper adapts PLR to Orca interface
class PLRSealerBackendWrapper(ISealerDriver):
def __init__(self, backend: SealerBackend):
self._backend = backend
async def initialize(self) -> None:
await self._backend.setup() # Maps initialize → setup
async def seal(self, temperature: int, duration: float) -> None:
await self._backend.seal(temperature=temperature, duration=duration)
Using PLR Backends Directly
You can also create the wrapper explicitly:
from cheshire_drivers import PLRSealerBackendWrapper
from pylabrobot.sealing.a4s_backend import A4SBackend
backend = A4SBackend(port="/dev/tty.usbserial-0001", timeout=10)
wrapper = PLRSealerBackendWrapper(backend)
sealer = Sealer("sealer", wrapper)
Labware Instances and PLR Objects
At runtime, Orca creates labware instances that wrap PLR resource objects:
# Template (design time)
sample_plate = PlateTemplate("sample", Thermo_Nunc_96_well_plate_1300uL_Rb, None)
# Instance created by Orca (runtime)
instance = sample_plate.create_instance()
# Access the underlying PLR Plate object
plr_plate = instance.PLR()
Each instance type has a PLR() method:
| Instance Type | Returns |
|---|---|
PlateInstance | pylabrobot.resources.Plate |
TipRackInstance | pylabrobot.resources.TipRack |
TubeRackInstance | pylabrobot.resources.TubeRack |
TroughInstance | pylabrobot.resources.Trough |
Transporter Integration
Orca's transporter integration with PLR is more sophisticated, handling:
- Coordinate conversion (Cartesian and Joint)
- Access patterns (vertical/horizontal)
- Crossover maneuvers (changing elbow orientation)
- Gateway waypoint traversal
Coordinate Conversion
from cheshire_drivers import convert_cartesian_to_plr_coord, convert_joint_to_plr_list
# Cartesian coordinates
from cheshire_drivers.teachpoints import CartesianCoordinates
coords = CartesianCoordinates(x=100, y=200, z=50, roll=0, pitch=90, yaw=0)
plr_coords = convert_cartesian_to_plr_coord(coords, orientation="right")
# Joint coordinates
from cheshire_drivers.teachpoints import JointCoordinates
joints = JointCoordinates(rail=0, base=170, shoulder=0, elbow=180, wrist=0, gripper=0)
plr_joints = convert_joint_to_plr_list(joints) # Returns [0, 170, 0, 180, 0, 0]
Access Patterns
PLR defines access patterns for pick/place operations. Orca teachpoints configure these:
{
"name": "shaker",
"access_type": "horizontal",
"horizontal_clearance": 30,
"vertical_clearance": 20
}
Maps to PLR HorizontalAccess or VerticalAccess patterns.
Example: Full PLR Integration
import asyncio
from orca.devices.sealer import Sealer
from orca.devices.shaker import Shaker
from orca.resource_models.transporter import Transporter
from orca.sdk.labware import PlateTemplate
from orca.sdk.workflow import MethodTemplate, ThreadTemplate, WorkflowTemplate
from orca.sdk.system import SdkToSystemBuilder, WorkflowExecutor, ResourceRegistry, SystemMap
from orca.sdk.events import EventBus
from orca.sdk.actions import Seal, Shake
# PLR imports
from pylabrobot.sealing.a4s_backend import A4SBackend
from pylabrobot.shaking.inheco_backend import InhecoThermoShakeBackend
from pylabrobot.arms.precise_flex.backend import PreciseFlexBackend
from pylabrobot.resources.thermo_fisher.plates import Thermo_Nunc_96_well_plate_1300uL_Rb
# Labware with PLR definition
plate = PlateTemplate("plate", Thermo_Nunc_96_well_plate_1300uL_Rb, None)
# Devices with PLR backends (auto-wrapped)
sealer = Sealer("sealer", A4SBackend(port="/dev/ttyUSB0", timeout=10))
shaker = Shaker("shaker", InhecoThermoShakeBackend(port="/dev/ttyUSB1"))
arm = Transporter("arm", PreciseFlexBackend(ip="192.168.1.100"), "teachpoints.json")
# Build workflow
resources = ResourceRegistry()
resources.add_resources([sealer, shaker, arm])
system_map = SystemMap(resources)
system_map.assign_resources({"sealer": sealer, "shaker": shaker})
seal_method = MethodTemplate(
name="seal",
actions=[Seal(resource=sealer, temperature=165, duration=5, inputs=[plate], outputs=[plate])]
)
shake_method = MethodTemplate(
name="shake",
actions=[Shake(resource=shaker, duration=30, speed=800, inputs=[plate], outputs=[plate])]
)
thread = ThreadTemplate(
plate,
system_map.get_location("input"),
system_map.get_location("output"),
[shake_method, seal_method]
)
workflow = WorkflowTemplate("plr_workflow")
workflow.add_thread(thread, is_start=True)
# Build and run
builder = SdkToSystemBuilder(
"plr_example", "PLR integration example",
[plate], resources, system_map,
[seal_method, shake_method], [workflow], EventBus()
)
system = builder.get_system()
async def run():
await system.initialize_all()
executor = WorkflowExecutor(workflow, system)
await executor.start(sim=False)
asyncio.run(run())
Next Steps
- Devices - All device types
- Transporters - Teachpoint configuration
- Labware - Template types