From 840c19877e6da3c4ccef1602fc1d623b4754541a Mon Sep 17 00:00:00 2001 From: Rick Wierenga Date: Mon, 9 Jun 2025 22:37:46 -0700 Subject: [PATCH] docs: add ResourceHolder tutorial --- .../resource-holder/resource-holder.ipynb | 167 ++++++++++++++++++ .../resource-holder/resource-holder.rst | 12 -- 2 files changed, 167 insertions(+), 12 deletions(-) create mode 100644 docs/resources/resource-holder/resource-holder.ipynb delete mode 100644 docs/resources/resource-holder/resource-holder.rst diff --git a/docs/resources/resource-holder/resource-holder.ipynb b/docs/resources/resource-holder/resource-holder.ipynb new file mode 100644 index 00000000000..f7b1e307926 --- /dev/null +++ b/docs/resources/resource-holder/resource-holder.ipynb @@ -0,0 +1,167 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1fabc0d7", + "metadata": {}, + "source": [ + "# ResourceHolder\n", + "\n", + "`ResourceHolder` is a mixin class for resources that can hold exactly one other\n", + "resource. Examples include the sites of plate carriers which hold a single\n", + "plate, or the docking position of a module that accepts one plate or trough.\n", + "\n", + "`ResourceHolder` ensures that the child resource is placed correctly inside the\n", + "holder. When the holder is rotated the child resource is automatically shifted\n", + "so that its local origin lines up with the correct corner of the holder. This\n", + "avoids having to manually adjust coordinates for every rotation." + ] + }, + { + "cell_type": "markdown", + "id": "c71e1e1b", + "metadata": {}, + "source": [ + "## Why use a ResourceHolder?\n", + "\n", + "Many pieces of labware are designed to accommodate another resource at a\n", + "fixed position. A `PlateHolder` inside a carrier for example always contains\n", + "exactly one plate. The `ResourceHolder` abstraction models this behaviour.\n", + "It guarantees that only one resource is assigned at a time and exposes\n", + "convenient methods to work with the contained resource.\n", + "\n", + "By subclassing from `ResourceHolder` your own holder classes immediately gain\n", + "these behaviours without duplicating logic." + ] + }, + { + "cell_type": "markdown", + "id": "9e89a6c0", + "metadata": {}, + "source": [ + "## Basic usage" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a758652", + "metadata": {}, + "outputs": [], + "source": [ + "from pylabrobot.resources import Resource, ResourceHolder\n", + "\n", + "holder = ResourceHolder(name=\"holder\", size_x=100, size_y=80, size_z=10)\n", + "plate = Resource(name=\"plate\", size_x=100, size_y=80, size_z=15)\n", + "\n", + "holder.assign_child_resource(plate)\n", + "holder.resource.name" + ] + }, + { + "cell_type": "markdown", + "id": "73233494", + "metadata": {}, + "source": [ + "After assignment the `resource` property returns the held item." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ae35102", + "metadata": {}, + "outputs": [], + "source": [ + "holder.resource is plate" + ] + }, + { + "cell_type": "markdown", + "id": "fbde07d7", + "metadata": {}, + "source": [ + "## Custom child location\n", + "\n", + "By default the child is positioned at the holder's origin. You can offset this\n", + "by passing ``child_location`` when constructing the holder." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34a1b2b4", + "metadata": {}, + "outputs": [], + "source": [ + "from pylabrobot.resources import Coordinate\n", + "\n", + "offset_holder = ResourceHolder(\n", + " name=\"offset_holder\",\n", + " size_x=100,\n", + " size_y=80,\n", + " size_z=10,\n", + " child_location=Coordinate(x=10, y=5, z=0),\n", + ")\n", + "lid = Resource(name=\"lid\", size_x=95, size_y=75, size_z=5)\n", + "offset_holder.assign_child_resource(lid)\n", + "offset_holder.resource.location" + ] + }, + { + "cell_type": "markdown", + "id": "6c54c505", + "metadata": {}, + "source": [ + "## Reassigning\n", + "\n", + "To replace the current child, call ``assign_child_resource`` again. The previous\n", + "child will be unassigned automatically." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db3b133e", + "metadata": {}, + "outputs": [], + "source": [ + "replacement = Resource(name=\"replacement\", size_x=100, size_y=80, size_z=15)\n", + "offset_holder.assign_child_resource(replacement)\n", + "offset_holder.resource.name" + ] + }, + { + "cell_type": "markdown", + "id": "eb813c6a", + "metadata": {}, + "source": [ + "Attempting to assign another resource without allowing reassignment raises an\n", + "error." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ece40649", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " holder.assign_child_resource(Resource(\"extra\", 10, 10, 10), reassign=False)\n", + "except ValueError as e:\n", + " error_message = str(e)\n", + "error_message" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "env", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/resources/resource-holder/resource-holder.rst b/docs/resources/resource-holder/resource-holder.rst deleted file mode 100644 index a890e5ee175..00000000000 --- a/docs/resources/resource-holder/resource-holder.rst +++ /dev/null @@ -1,12 +0,0 @@ -ResourceHolder -============== - -TODO: write a tutorial - -See :class:`~pylabrobot.resources.ResourceHolder` for the API reference. - -.. toctree:: - :maxdepth: 1 - :hidden: - - plate-holder/plate-holder