API Reference

Deck

class biohit_pipettor_plus.deck_structure.Deck(range_x: tuple[int, int], range_y: tuple[int, int], deck_id: str, range_z: float = 500)[source]

Bases: Serializable

Represents the Deck of a Pipettor. The Deck can contain multiple slots, each of which can hold multiple Labware objects stacked vertically.

deck_id

Unique identifier for the deck instance.

Type:

str

range_x

Minimum and maximum x coordinates of the deck.

Type:

tuple[int, int]

range_y

Minimum and maximum y coordinates of the deck.

Type:

tuple[int, int]

slots

Dictionary mapping slot IDs to Slot objects.

Type:

dict[str, Slot]

labware

Dictionary mapping labware IDs to Labware objects.

Type:

dict[str, Labware]

range_z

Maximum vertical range of the deck (mm). This is the total Z-axis travel from top (pipettor home) to bottom (deck surface).

Type:

float, optional

add_labware(labware: Labware, slot_id: str, min_z: float)[source]

Add a Labware to a specific Slot at a specific Z position.

Parameters:
  • labware (Labware) – Labware object to place.

  • slot_id (str) – ID of the slot where the labware will be placed.

  • min_z (float) – Starting Z coordinate within the slot.

Raises:
  • TypeError – If the object is not a Labware instance.

  • ValueError – If the slot does not exist, labware ID already exists, or labware does not fit in the slot.

add_slots(slots: list[Slot])[source]

Add a Slot to the Deck after validating range and overlap.

Parameters:

slots (list of slots) – The slot to add to the deck.

Raises:

ValueError – If slot ID already exists, is out of deck range, or overlaps an existing slot.

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_slot_for_labware(labware_id: str) str | None[source]

Find the slot ID containing the given labware_id. :param labware_id: The ID of the labware to locate. :type labware_id: str

Returns:

The slot ID if found, else None.

Return type:

Optional[str]

remove_labware(labware_id: str) Labware[source]

Remove a Labware from its Slot and the Deck. Only the topmost labware in a slot’s stack can be removed. Returns the Labware object for reuse or deletion.

remove_slot(slot_id: str, unplace_labware: bool = False)[source]

Remove a Slot from the Deck.

Parameters:
  • slot_id (str) – The ID of the slot to remove.

  • unplace_labware (bool) – If True, unplace (remove) all labware contained within the slot first. If False (default), raise an error if the slot contains labware.

Returns:

A list of Labware objects that were unplaced (if unplace_labware is True).

Return type:

list[Labware]

to_dict() dict[source]

Serialize the Deck to a dictionary for JSON export, including slots with stacked labware.

Returns:

Dictionary containing deck attributes, slots, and labware.

Return type:

dict

Slot

class biohit_pipettor_plus.deck_structure.Slot(range_x: tuple[float, float], range_y: tuple[float, float], range_z: float, slot_id: str)[source]

Bases: Serializable

Represents a single slot on a Deck. A slot can hold multiple Labware objects stacked vertically with specified Z-ranges

range_x

Minimum and maximum x coordinates of the slot.

Type:

tuple[float, float]

range_y

Minimum and maximum y coordinates of the slot.

Type:

tuple[float, float]

range_z

Maximum height of the slot.

Type:

float

slot_id

Unique identifier for the slot.

Type:

str

labware_stack

Dictionary mapping Labware IDs to a list containing the Labware object and its Z-range (min_z, max_z) within the slot.

Type:

dict[str, list[Labware, tuple[float, float]]]

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_highest_z() float[source]

Returns the maximum Z coordinate currently occupied in this slot.

is_compatible_labware(lw: Labware, min_z: float) None[source]

Checks if a Labware object can be placed at the given Z position, raising a ValueError if any constraint (XY fit, Z range, stacking, or overlap) is violated.

to_dict() dict[source]

Serialize the Slot instance to a dictionary, including stacked labware and their Z-ranges.

Returns:

Dictionary representation of the slot.

Return type:

dict

Labware

class biohit_pipettor_plus.deck_structure.Labware(size_x: float, size_y: float, size_z: float, offset: tuple[float, float] = (0.0, 0.0), labware_id: str | None = None, position: tuple[float, float] | None = None, can_be_stacked_upon: bool = False)[source]

Bases: Serializable

Base class for all labware objects with automatic subclass registry.

size_x

Width of the labware in millimeters.

Type:

float

size_y

Depth of the labware in millimeters.

Type:

float

size_z

Height of the labware in millimeters.

Type:

float

position

x,y coordinate of the labware.

Type:

tuple[float, float]

labware_id

Unique identifier of the labware instance.

Type:

str

Initialize a Labware instance.

Parameters:
  • size_x (float) – Width of the labware in millimeters.

  • size_y (float) – Depth of the labware in millimeters.

  • size_z (float) – Height of the labware in millimeters.

  • labware_id (str, optional) – Unique ID for the labware. If None, a UUID will be generated.

  • position (tuple[float, float], optional) – (x, y) position coordinates of the labware in millimeters. If None, position is not set.

each_tip_needs_separate_item() bool[source]

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

to_dict() dict[source]

Serialize the Labware instance to a dictionary.

Returns:

dictionary representation of the labware.

Return type:

dict

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str][source]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None[source]

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str][source]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')[source]

Validate that dimensions are positive

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

Plate

class biohit_pipettor_plus.deck_structure.Plate(size_x: float, size_y: float, size_z: float, wells_x: int, wells_y: int, well: Well, add_height: float = -3, remove_height: float = -10, offset: tuple[float, float] = (0, 0), x_spacing: float | None = None, y_spacing: float | None = None, labware_id: str | None = None, position: tuple[float, float] | None = None, can_be_stacked_upon: bool = False)[source]

Bases: Labware

Initialize a Plate instance.

Parameters:
  • size_x (float) – Width of the plate.

  • size_y (float) – Depth of the plate.

  • size_z (float) – Height of the plate.

  • wells_x (int) – Number of wells in X direction.

  • wells_y (int) – Number of wells in Y direction.

  • add_height (float) – Height above the well bottom used when adding liquid (in mm).

  • remove_height (float) – Height above the well bottom used when removing liquid (in mm).

  • well (Well) – Template well to use for all wells in the plate.

  • offset (tuple[float, float], optional) – Offset of the plate.

  • x_spacing (float, optional) – Distance along x-axis between hooks in millimeters.

  • y_spacing (float, optional) – Distance along y-axis between hooks in millimeters.

  • labware_id (str, optional) – Unique ID for the plate.

  • position (tuple[float, float], optional) – (x, y) position coordinates of the plate in millimeters.

each_tip_needs_separate_item() bool

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_all_children() list[Well][source]
get_child_at(column: int, row: int) Well | None[source]
get_state_snapshot() dict[source]

Return deep copy of all wells’ state

get_well_at(column: int, row: int) Well | None[source]

Get the well at a specific position.

Parameters:
  • column (int) – Column index

  • row (int) – Row index

Returns:

The well at the position, or None if not found

Return type:

Optional[Well]

get_wells() dict[tuple[int, int], Well][source]

Get all wells in the plate.

get_wells_in_column(column: int) list[Well][source]

Get all wells in a specific column.

get_wells_in_row(row: int) list[Well][source]

Get all wells in a specific row.

place_wells()[source]

Create wells across the grid using the template well.

restore_state_snapshot(snapshot: dict) None[source]

Restore all wells’ state from snapshot

to_dict()[source]

Serialize the Plate instance to a dictionary.

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

property grid_x: int

Standard grid dimension (columns)

property grid_y: int

Standard grid dimension (rows)

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}
property wells_x: int

Number of wells in X direction (columns)

property wells_y: int

Number of wells in Y direction (rows)

Well

class biohit_pipettor_plus.deck_structure.Well(size_x: float, size_y: float, size_z: float, offset: tuple[float, float] = (0, 0), position: tuple[float, float] | None = None, can_be_stacked_upon: bool = False, labware_id: str | None = None, row: int | None = None, column: int | None = None, content: dict | None = None, capacity: float = 1000, shape: Literal['rectangular', 'circular', 'conical', 'u_bottom'] | None = None)[source]

Bases: Labware

Represents a single Well, extending Labware with additional parameters for liquid handling.

content

Dictionary mapping content types to volumes (µL).

Type:

dict

capacity

Maximum volume the well can hold (µL).

Type:

float

Initialize a Well instance.

Parameters:
  • size_x (float) – Width of the well in millimeters.

  • size_y (float) – Depth of the well in millimeters.

  • size_z (float) – Height of the well in millimeters.

  • position (tuple[float, float], optional) – (x, y) position coordinates of the well in millimeters.

  • labware_id (str, optional) – Unique identifier for this well. If None, a UUID will be generated.

  • row (int, optional.) – row inside of plate

  • column (int, optional) – column inside of plate

  • content (dict, optional) – Dictionary mapping content types to volumes (µL). Example: {“PBS”: 150, “water”: 100}

  • capacity (float, optional) – Maximum volume the well can hold (µL). Default is Default_well_capacity

add_content(content_type: str, volume: float) None[source]

Add content to the well with intelligent mixing logic.

When adding content to a well: - Same content type: volumes are combined - Different content type: tracked separately (but physically mixed)

Note: Once liquids are mixed in a well, they cannot be separated. Removal is always proportional from all content types.

Parameters:
  • content_type (str) – Content to add (e.g., “PBS”, “water”, “sample”)

  • volume (float) – Volume to add (µL)

Raises:

ValueError – If adding volume would exceed capacity or volume is negative

clear_content() None[source]

Clear all content from the well.

each_tip_needs_separate_item() bool

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_available_volume() float[source]

Get the remaining capacity available in the well.

Returns:

Available volume in µL

Return type:

float

get_content_by_type(content_type: str) float[source]

Get volume of specific content type.

Parameters:

content_type (str) – Type of content to query

Returns:

Volume of specified content type (0 if not present)

Return type:

float

get_content_info() dict[source]

Get current content information.

Returns:

Dictionary with detailed content information

Return type:

dict

get_content_summary() str[source]

Get a human-readable summary of well content.

Returns:

Summary string like “PBS: 150.0µL, water: 100.0µL” or “empty”

Return type:

str

get_state_snapshot() dict[source]
get_total_volume() float[source]

Get total volume of all content in the well.

Returns:

Total volume in µL

Return type:

float

has_content_type(content_type: str) bool[source]

Check if well contains specific content type.

Parameters:

content_type (str) – Type of content to check

Returns:

True if content type is present with volume > 0

Return type:

bool

remove_content(volume: float, return_dict: bool = False) dict[str, float] | None[source]

Remove content from the well proportionally.

When content is removed from a well, it’s removed proportionally from all content types since they are mixed together.

Parameters:
  • volume (float) – Volume to remove (µL)

  • return_dict (bool, optional) – If True, return a dictionary of removed content types and volumes (default: False)

Returns:

If return_dict is True, returns dictionary mapping content types to removed volumes. Otherwise, returns None.

Return type:

Optional[dict[str, float]]

Raises:

ValueError – If trying to remove more volume than available or volume is negative

restore_state_snapshot(snapshot: dict) None[source]

Restore state from snapshot

to_dict() dict[source]

Serialize the Well to a dictionary for JSON export.

Returns:

dictionary containing all well attributes.

Return type:

dict

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

PipetteHolder

class biohit_pipettor_plus.deck_structure.PipetteHolder(size_x: float, size_y: float, size_z: float, holders_across_x: int, holders_across_y: int, individual_holder: IndividualPipetteHolder, add_height: float = 0, remove_height: float = 0, offset: tuple[float, float] = (0, 0), x_spacing: float | None = None, y_spacing: float | None = None, labware_id: str | None = None, position: tuple[float, float] | None = None, can_be_stacked_upon: bool = False)[source]

Bases: Labware

Initialize a PipetteHolder instance.

Parameters:
  • size_x (float) – Width of the pipette holder in millimeters.

  • size_y (float) – Depth of the pipette holder in millimeters.

  • size_z (float) – Height of the pipette holder in millimeters.

  • holders_across_x (int) – Number of individual holder positions across X-axis.

  • holders_across_y (int) – Number of individual holder positions across Y-axis.

  • individual_holder (IndividualPipetteHolder) – Template for individual holder positions. If provided, copies will be created for each position in the grid.

  • labware_id (str, optional) – Unique ID for the pipette holder.

  • add_height (float) – Height above the well bottom used when adding liquid (in mm).

  • remove_height (float) – Height above the well bottom used when removing liquid (in mm).

  • x_spacing (float, optional) – Distance along x-axis between hooks in millimeters.

  • y_spacing (float, optional) – Distance along y-axis between hooks in millimeters.

  • position (tuple[float, float], optional) – (x, y) position coordinates of the pipette holder in millimeters. If None, position is not set.

check_col_start_row_multi(col: int, start_row: int) str[source]

Check the occupancy status of 8 consecutive positions starting from (col, start_row).

Parameters:
  • col (int) – Column index

  • start_row (int) – Starting row index

Returns:

Status of the 8 consecutive positions: - “FULLY_OCCUPIED”: All 8 positions exist and are occupied - “FULLY_AVAILABLE”: All 8 positions exist and are empty - “MIXED”: All 8 positions exist but have mixed occupancy - “INVALID”: One or more positions don’t exist (out of bounds)

Return type:

str

each_tip_needs_separate_item() bool

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_all_children() list[IndividualPipetteHolder][source]
get_available_holder_multi() list[tuple[int, int]][source]

Get all columns with starting rows where 8 consecutive available positions exist. No holder is reused - blocks are non-overlapping.

get_available_holders() list[IndividualPipetteHolder][source]

Get all available (unoccupied) holder positions.

Returns:

List of available holders.

Return type:

list[IndividualPipetteHolder]

get_child_at(column: int, row: int) IndividualPipetteHolder | None[source]
get_holder_at(column: int, row: int) IndividualPipetteHolder | None[source]

Get the individual holder at a specific position.

Parameters:
  • column (int) – Column index

  • row (int) – Row index

Returns:

The holder at the position, or None if not found

Return type:

Optional[IndividualPipetteHolder]

get_individual_holders() dict[tuple[int, int], IndividualPipetteHolder][source]

Get all individual holder positions.

get_occupied_holder_multi() list[tuple[int, int]][source]

Get all columns with starting rows where 8 consecutive occupied positions exist. No holder is reused - blocks are non-overlapping.

get_occupied_holders() list[IndividualPipetteHolder][source]

Get all occupied holder positions.

Returns:

List of occupied holders.

Return type:

list[IndividualPipetteHolder]

get_state_snapshot() dict[source]

Return deep copy of all holders’ state

place_consecutive_pipettes_multi(columns: list[int], row: int = 0) None[source]

Place pipettes in consecutive positions within specified columns for multichannel pipettor.

Parameters:
  • columns (list[int]) – List of column indices (0-indexed) where pipettes should be placed.

  • row (int, optional) – Starting row index (0-indexed). Pipettes will be placed from row to row + 7. Default is 0.

Raises:

ValueError – If any column index is out of range, row out of range, or if any position in the specified columns is already occupied.

place_individual_holders()[source]

Create individual holder positions across the grid.

place_pipette_at(column: int, row: int) None[source]

Place a pipette at a specific position.

Parameters:
  • column (int) – Column index (0-indexed).

  • row (int) – Row index (0-indexed).

Raises:

ValueError – If position is out of range, no holder exists, or position is occupied.

remove_consecutive_pipettes_multi(columns: list[int], row: int = 0) None[source]

Remove pipettes from consecutive positions within specified columns for multichannel pipettor.

Parameters:
  • columns (list[int]) – List of column indices (0-indexed) where pipettes should be removed.

  • row (int, optional) – Starting row index (0-indexed). Pipettes will be removed from row to row + 7. Default is 0.

Raises:

ValueError – If any column index is out of range, row out of range, or if any position in the specified columns is already empty.

remove_pipette_at(column: int, row: int) None[source]

Remove a pipette from a specific position.

Parameters:
  • column (int) – Column index (0-indexed).

  • row (int) – Row index (0-indexed).

Raises:

ValueError – If position is out of range, no holder exists, or position is empty.

restore_state_snapshot(snapshot: dict) None[source]

Restore all holders’ state from snapshot

to_dict() dict[source]

Serialize the PipetteHolder instance to a dictionary.

Returns:

Dictionary representation of the pipette holder.

Return type:

dict

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

property grid_x: int

Standard grid dimension

property grid_y: int

Standard grid dimension

property holders_across_x: int

Alias for grid_x

property holders_across_y: int

Alias for grid_y

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

IndividualPipetteHolder

class biohit_pipettor_plus.deck_structure.IndividualPipetteHolder(size_x: float, size_y: float, size_z: float, offset: tuple[float, float] = (0, 0), is_occupied: bool = True, row: int | None = None, column: int | None = None, labware_id: str | None = None, can_be_stacked_upon: bool = False, position: tuple[float, float] | None = None)[source]

Bases: Labware

Represents an individual pipette holder position within a PipetteHolder. Tracks occupancy status and pipette type.

is_occupied

Whether this holder position currently contains a pipette.

Type:

bool

Initialize an IndividualPipetteHolder instance.

Parameters:
  • size_x (float) – Width of the individual holder position in millimeters.

  • size_y (float) – Depth of the individual holder position in millimeters.

  • size_z (float) – Height of the individual holder position in millimeters.

  • is_occupied (bool, optional) – Whether a pipette is currently stored here. Default is False.

  • labware_id (str, optional) – Unique identifier for this holder position. If None, a UUID will be generated.

  • position (tuple[float, float], optional) – (x, y) absolute position coordinates in millimeters. If None, position is not set.

each_tip_needs_separate_item() bool

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_content_info() dict[source]

Standardized content info for Pipette Holders. Matches the return signature of Plate/Reservoir wells.

get_state_snapshot() dict[source]

Return deep copy of mutable state

is_available() bool[source]

Check if this holder position is available for placing a pipette. Returns bool.True if available (not occupied), False otherwise.

place_pipette() None[source]

Mark this holder position as occupied (pipette placed). Raises ValueError If the holder position is already occupied.

remove_pipette() None[source]

Mark this holder position as available (pipette removed).

Raises:

ValueError – If the holder position is already empty.

restore_state_snapshot(snapshot: dict) None[source]

Restore state from snapshot

to_dict() dict[source]

Serialize the IndividualPipetteHolder to a dictionary.

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

ReservoirHolder

class biohit_pipettor_plus.deck_structure.ReservoirHolder(size_x: float, size_y: float, size_z: float, hooks_across_x: int, hooks_across_y: int, reservoir_template: Reservoir | None = None, remove_height: float = -45, add_height: float = 0, offset: tuple[float, float] = (0, 0), labware_id: str | None = None, position: tuple[float, float] | None = None, can_be_stacked_upon: bool = False, x_spacing: float | None = None, y_spacing: float | None = None, each_tip_needs_separate_item=False)[source]

Bases: Labware

Initialize a ReservoirHolder instance that can hold multiple reservoirs.

Parameters:
  • size_x (float) – Width of the ReservoirHolder in millimeters.

  • size_y (float) – Depth of the ReservoirHolder in millimeters.

  • size_z (float) – Height of the ReservoirHolder in millimeters.

  • hooks_across_x (int) – Number of hooks along X-axis.

  • hooks_across_y (int) – Number of hooks along Y-axis (rows of hooks).

  • add_height (float) – relative height at which liquid is dispensed

  • remove_height (float) – relative height at which liquid is aspirated

  • reservoir_template (reservoir) – example or individual reservoir that will be placed across all hooks.

  • labware_id (str, optional) – Unique ID for the holder.

  • position (tuple[float, float], optional) – (x, y) position coordinates of the ReservoirHolder in millimeters. If None, position is not set.

  • x_spacing (float, optional) – Distance along x-axis between hooks in millimeters.

  • y_spacing (float, optional) – Distance along y-axis between hooks in millimeters.

  • each_tip_needs_separate_item (bool, optional) – If True, each pipette tip needs its own reservoir. If False, all tips can access the same reservoir (default: False).

add_content(hook_id: int, content: str, volume: float) None[source]

Add content to a reservoir at a specific hook.

Parameters:
  • hook_id (int) – Hook ID where the reservoir is located

  • content (str) – Type of content to add (e.g., “PBS”, “water”)

  • volume (float) – Volume to add (µL)

Raises:

ValueError – If no reservoir at hook or volume exceeds capacity

each_tip_needs_separate_item() bool[source]

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_all_children() list[Reservoir][source]
get_available_hooks() list[int][source]

Return list of available (empty) hook IDs.

get_child_at(col: int, row: int) Reservoir | None[source]
get_hook_to_reservoir_map() dict[int, Reservoir | None][source]

Return the complete hook to reservoir mapping.

get_occupied_hooks() list[int][source]

Return list of occupied hook IDs.

get_reservoirs() list[Reservoir][source]

Return list of all unique reservoirs (no duplicates).

get_reservoirs_by_content_type(content_type: str) list[Reservoir][source]

Get all unique reservoirs that contain a specific content type.

Parameters:

content_type (str) – Content type to search for (case-insensitive)

Returns:

List of reservoirs containing this content type

Return type:

list[Reservoir]

get_state_snapshot() dict[source]

Return deep copy of all reservoirs’ state

get_waste_reservoirs() list[Reservoir][source]

Get all unique reservoirs that contain ‘waste’ in any content type.

Returns:

List of waste reservoirs

Return type:

list[Reservoir]

hook_id_to_position(hook_id: int) tuple[int, int][source]

Convert hook_id to (col, row) position.

Parameters:

hook_id (int) – Hook ID (1-indexed, 1 to total_hooks)

Returns:

(col, row) where col is 0 to hooks_across_x-1, row is 0 to hooks_across_y-1

Return type:

tuple[int, int]

Example

For hooks_across_x=3, hooks_across_y=2:

hook_id: 1 2 3 4 5 6
layout: [1 2 3]  <- row 0
        [4 5 6]  <- row 1
place_reservoir(hook_ids: list[int], reservoir: Reservoir) None[source]

Place a single reservoir on specific hooks.

Parameters:
  • hook_ids (list[int] or int) – List of hook location to place the reservoir (must form a rectangle).

  • reservoir (Reservoir) – Reservoir instance to place.

Raises:

ValueError – If hook_ids are invalid, don’t form a rectangle, already occupied, or reservoir dimensions incompatible.

place_reservoirs(reservoir_template: Reservoir) None[source]

allocate duplicate reservoir to all available hooks, unless specific hook id specified

If hook_ids is specified, the reservoir will be placed there, given that position is empty Otherwise, calculates required hooks based on dimensions and allocates automatically.

Raises:

ValueError – If a specified hook_id is occupied, insufficient space, or reservoir parameters are invalid.

position_to_hook_id(col: int, row: int) int[source]

Convert (col, row) position to hook_id.

Parameters:
  • col (int) – Column (0 to hooks_across_x-1)

  • row (int) – Row (0 to hooks_across_y-1)

Returns:

hook_id (1-indexed)

Return type:

int

remove_content(hook_id: int, volume: float, return_dict: bool = False) dict[str, float] | None[source]

Remove content from a reservoir at a specific hook.

Parameters:
  • hook_id (int) – Hook ID where the reservoir is located

  • volume (float) – Volume to remove (µL)

  • return_dict (bool, optional) – If True, return a dictionary of removed content types and volumes (default: False)

Returns:

If return_dict is True, returns dictionary mapping content types to removed volumes. Otherwise, returns None.

Return type:

Optional[dict[str, float]]

Raises:

ValueError – If no reservoir at hook or insufficient volume

remove_reservoir(hook_id: int) Reservoir[source]

Remove a reservoir from the holder.

Parameters:

hook_id (int) – Any hook ID occupied by the reservoir to remove

Returns:

The removed reservoir

Return type:

Reservoir

Raises:

ValueError – If no reservoir at the specified hook

restore_state_snapshot(snapshot: dict) None[source]

Restore all reservoirs’ state from snapshot

to_dict() dict[source]

Serialize the ReservoirHolder instance to a dictionary.

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

property grid_x: int
property grid_y: int
property hooks_across_x: int
property hooks_across_y: int
registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

Reservoir

class biohit_pipettor_plus.deck_structure.Reservoir(size_x: float, size_y: float, size_z: float, offset: tuple[float, float] = (0, 0), labware_id: str | None = None, position: tuple[float, float] | None = None, can_be_stacked_upon: bool = False, capacity: float = 30000, content: dict | None = None, hook_ids: list[int] | None = None, row: int | None = None, column: int | None = None, shape: Literal['rectangular', 'circular', 'conical', 'u_bottom'] | None = None)[source]

Bases: Labware

Initialize a Reservoir instance. These are containers that store the medium to be filled in and removed from well.

Parameters:
  • size_x (float) – Width of the reservoir in millimeters.

  • size_y (float) – Depth of the reservoir in millimeters.

  • size_z (float) – Height of the reservoir in millimeters.

  • capacity (float, optional) – Maximum amount of liquid that reservoir can hold. Default is Default_Reservoir_Capacity.

  • content (dict, optional) – Dictionary mapping content types to volumes (µL). Example: {“PBS”: 25000, “water”: 5000}

  • hook_ids (list[int], optional) – List of hook locations on ReservoirHolder where this reservoir is going to be placed.

  • labware_id (str, optional) – Unique ID for the reservoir.

  • position (tuple[float, float], optional) – (x, y) position coordinates of the reservoir in millimeters. If None, position is not set.

add_content(content_type: str, volume: float) None[source]

Add content to the reservoir with intelligent mixing logic.

When adding content to a reservoir: - Same content type: volumes are combined - Different content type: tracked separately (but physically mixed)

Note: Once liquids are mixed in a reservoir, they cannot be separated. Removal is always proportional from all content types.

Parameters:
  • content_type (str) – Content to add (e.g., “PBS”, “water”, “waste”)

  • volume (float) – Volume to add (µL)

Raises:

ValueError – If adding volume would exceed capacity or volume is negative

clear_content() None[source]

Clear all content from the reservoir.

each_tip_needs_separate_item() bool

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_available_volume() float[source]

Get the remaining capacity available in the reservoir.

Returns:

Available volume in µL

Return type:

float

get_content_by_type(content_type: str) float[source]

Get volume of specific content type.

Parameters:

content_type (str) – Type of content to query

Returns:

Volume of specified content type (0 if not present)

Return type:

float

get_content_info() dict[source]

Get current content information.

Returns:

Dictionary with detailed content information

Return type:

dict

get_content_summary() str[source]

Get a human-readable summary of reservoir content.

Returns:

Summary string like “PBS: 25000µL, water: 5000µL” or “empty”

Return type:

str

get_state_snapshot() dict[source]

Return deep copy of mutable state

get_total_volume() float[source]

Get total volume of all content in the reservoir.

Returns:

Total volume in µL

Return type:

float

has_content_type(content_type: str) bool[source]
Check if reservoir contains specific content type.l

True if content type is present

is_waste_reservoir() bool[source]

Check if this is a waste reservoir.

Returns:

True if any content type contains “waste” (case-insensitive)

Return type:

bool

remove_content(volume: float, return_dict: bool = False) dict[str, float] | None[source]

Remove content from the reservoir proportionally.

When content is removed from a reservoir, it’s removed proportionally from all content types since they are mixed together.

Parameters:
  • volume (float) – Volume to remove (µL)

  • return_dict (bool, optional) – If True, return a dictionary of removed content types and volumes (default: False)

Returns:

If return_dict is True, returns dictionary mapping content types to removed volumes. Otherwise, returns None.

Return type:

Optional[dict[str, float]]

Raises:

ValueError – If trying to remove more volume than available or volume is negative

restore_state_snapshot(snapshot: dict) None[source]

Restore state from snapshot

to_dict() dict[source]

Serialize the Reservoir to a dictionary.

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

property grid_height: int

How many grid rows this reservoir occupies.

property grid_width: int

How many grid columns this reservoir occupies.

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

TipDropzone

class biohit_pipettor_plus.deck_structure.TipDropzone(size_x: float, size_y: float, size_z: float, can_be_stacked_upon: bool = False, offset: tuple[float, float] = (0, 0), drop_height_relative: float = 20, position: tuple[float, float] | None = None, labware_id: str | None = None)[source]

Bases: Labware

Represents a Tip Dropzone labware with relative drop position and height.

drop_height_relative

Drop height relative to the labware height.

Type:

float

Initialize a TipDropzone instance.

Parameters:
  • size_x (float) – Width of the drop zone in millimeters.

  • size_y (float) – Depth of the drop zone in millimeters.

  • size_z (float) – Height of the drop zone in millimeters.

  • labware_id (str, optional) – Unique ID for the dropzone object.

  • position (tuple[float, float], optional) – (x, y) position coordinates of the tip dropzone in millimeters. If None, position is not set.

  • drop_height_relative (float, optional) – Height from which tips are dropped relative to the labware. Default is 20.

each_tip_needs_separate_item() bool

For multichannel operation, does each tip need to access a separate item?

Returns:

True: Each tip needs its own item (e.g., Plate - small wells) False: All tips can share one item (e.g., ReservoirHolder - large reservoirs)

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_state_snapshot() dict[source]

Return empty snapshot - TipDropzone has no mutable state

restore_state_snapshot(snapshot: dict) None[source]

No-op - TipDropzone has no mutable state to restore

to_dict() dict[source]

Serialize the TipDropzone instance to a dictionary, extending the base Labware fields.

Returns:

dictionary representation of the tip dropzone.

Return type:

dict

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], row: int, consecutive_rows: int = 1) None

Validate column and row, raising ValueError if invalid.

Convenience method for when you want to raise an error immediately.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

Stack

class biohit_pipettor_plus.deck_structure.Stack(size_x: float, size_y: float, size_z: float, offset: tuple[float, float] = (0.0, 0.0), labware_id: str | None = None, position: tuple[float, float] | None = None, can_be_stacked_upon: bool = True)[source]

Bases: Labware

A labware that serves as a platform for stacking other labware on top. Has no functional elements itself - purely structural.

can_be_stacked_upon

Whether other labware can be placed on top of this stack (default True)

Type:

bool

Initialize a Stack instance.

each_tip_needs_separate_item() bool[source]

Stack has no functional items.

Returns:

False - Stack is purely structural

Return type:

bool

classmethod from_dict(data: dict)

Factory: dispatch to correct subclass based on class field.

get_state_snapshot() dict[source]

Return empty snapshot - Stack has no mutable state

restore_state_snapshot(snapshot: dict) None[source]

No-op - Stack has no mutable state to restore

to_dict() dict[source]

Serialize the Stack to a dictionary.

Returns:

Dictionary containing all stack attributes.

Return type:

dict

validate_col_row(columns: list[int], row: int, consecutive_rows: int = 1) tuple[bool, str]

Validate column indices and row range for grid-based labware operations.

Parameters:
  • columns (list[int]) – List of column indices to validate

  • row (int) – Starting row index

  • consecutive_rows (int, optional) – Number of consecutive rows needed (default: 1). For multichannel pipette, generally 8

Returns:

(is_valid, error_message) - is_valid: True if validation passes, False otherwise - error_message: Empty string if valid, error description if invalid

Return type:

tuple[bool, str]

validate_col_row_or_raise(columns: list[int], start_row: int, consecutive_rows: int = 1) None[source]

Stack has no positions to validate - always raises.

validate_multichannel_compatible(item_size_y: float) tuple[bool, str]

Validate if an item is large enough for multichannel operation.

Parameters:

item_size_y (float) – The Y-dimension of the item to validate (e.g., reservoir, well)

Returns:

(is_valid, error_message)

Return type:

tuple[bool, str]

static validate_positive_dimensions(size_x: float, size_y: float, size_z: float, labware_type: str = 'Labware')

Validate that dimensions are positive

registry: dict[str, type] = {'IndividualPipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.individualpipetteholder.IndividualPipetteHolder'>, 'PipetteHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.pipetteholder.PipetteHolder'>, 'Plate': <class 'biohit_pipettor_plus.deck_structure.labware_classes.plate.Plate'>, 'Reservoir': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoir.Reservoir'>, 'ReservoirHolder': <class 'biohit_pipettor_plus.deck_structure.labware_classes.reservoirHolder.ReservoirHolder'>, 'Stack': <class 'biohit_pipettor_plus.deck_structure.labware_classes.stack.Stack'>, 'TipDropzone': <class 'biohit_pipettor_plus.deck_structure.labware_classes.tipdropzone.TipDropzone'>, 'Well': <class 'biohit_pipettor_plus.deck_structure.labware_classes.well.Well'>}

PipettorPlus

class biohit_pipettor_plus.pipettor_plus.pipettor_plus.PipettorPlus(tip_volume: Literal[200, 1000], *, multichannel: bool, initialize: bool = True, deck: Deck, tip_length: float | None = None, mock_pipettor=False)[source]

Bases: object

Interface to the Biohit Robo pipettor with deck/slot/labware structure

Parameters:
  • tip_volume (Literal[200, 1000]) – The tip volume (must be 1000 if multichannel is True)

  • multichannel (bool) – If True, it is assumed the device uses a multichannel pipet

  • initialize (bool) – If True, the device will be initialized

  • deck (Deck) – The deck containing slots and labware

  • tip_length (float) – The tip length in mm

  • mock_pipettor (bool) – If True, mock_pipettor will be used instead of Biohit Robo pipettor

add_content(content_type: str, volume: float, tip_index: int | None = None) None[source]

Add content to specific tip(s).

Parameters:
  • content_type (str) – Content to add (e.g., “PBS”, “water”)

  • volume (float) – Volume to add (µL)

  • tip_index (int, optional) – Which tip to add to (0-7 for multichannel, 0 for single). If None, adds to all tips equally.

add_medium(source: ReservoirHolder, source_col_row: tuple[int, int], volume_per_well: float, destination: Plate, dest_col_row: List[tuple[int, int]], mix_volume: float = 0) None[source]

Transfer medium from a reservoir to destination plate column(s). Works for both single-channel and multichannel pipettors.

Parameters:
  • source (ReservoirHolder) – ReservoirHolder containing the medium to transfer

  • source_col_row (tuple[int, int]) – (Column, Row) position of reservoir.

  • volume_per_well (float) – Volume (µL) to dispense into each destination well

  • destination (Plate) – Target plate to receive the medium

  • dest_col_row (List[tuple[int, int]]) – List of (Column, Row) positions in destination plate. Row is start row for multichannel pipettor.

  • mix_volume (float, optional) – Volume (µL) to use for mixing after dispensing. 0 = no mixing (default)

aspirate(volume)[source]

Override parent to add simulation mode check

check_tips() None[source]

Check if tip change is required. if yes, do it.

close()[source]

Manually close the pipettor connection and exit context

discard_tips(tip_dropzone: Labware) None[source]

Discard tips to a TipDropzone.

Parameters:

tip_dropzone (labware) – TipDropzone labware

dispense(volume)[source]

Override parent to add simulation mode check

eject_tip()[source]

Override parent to add simulation mode check

find_labware_by_type(labware_type: str) list[Labware][source]

Find a labware instance by its type name (case-sensitive).

Parameters:
  • labware_type (str) – The class name of the labware to find (e.g., “Plate”, “ReservoirHolder”, “PipetteHolder”) Case-sensitive.

  • Labwares (Returns list of) – Any Labware instance of the specified type, placed in deck and slot.

  • ValueError (Raises) – If no labware of the specified type is found in the deck and placed in a slot.

get_tip_status() dict[source]

Get current tip status.

get_total_tip_volume(tip_index: int | None = None) float[source]

Get total volume in tip(s).

Parameters:

tip_index (int, optional) – Specific tip. If None, returns total across all tips.

Returns:

Total volume in µL

Return type:

float

home()[source]
initialize_tips() None[source]

Clear tip content when tips are discarded.

measure_foc(seconds: int, platename: str | None = None, bat_script_path: str | None = None)[source]

Wait for specified seconds, then run FOC measurement script.

move_xy(x: float, y: float)[source]

Override parent to add simulation mode check

move_z(z: float)[source]

Override parent to add simulation mode check

pick_multi_tips(pipette_holder: PipetteHolder, list_col_row: List[tuple[int, int]] | None = None) List[tuple[int, int]][source]

Pick tips from a PipetteHolder using multichannel pipettor.

For multichannel, 8 consecutive tips are picked vertically.

Parameters:
  • pipette_holder (PipetteHolder) – PipetteHolder labware containing tips

  • list_col_row (List[tuple[int, int]], optional) – List of (column, start_row) grid indices to try. start_row indicates where the first pipettor in multichannel would be positioned. If None, automatically finds all grid locations with 8 consecutive occupied tips.

Returns:

Actual (column, start_row) position where tips were picked

Return type:

List[tuple[int, int]]

Raises:
  • ValueError – If not a multichannel pipettor or no tips available

  • RuntimeError – If failed to pick tips from any specified grid location

pick_single_tip(pipette_holder: PipetteHolder, list_col_row: List[tuple[int, int]] | None = None) List[tuple[int, int]][source]

Pick a single tip from a PipetteHolder using single-channel pipettor.

pick_tip(z)[source]

Override parent to add simulation mode check

pick_tips(pipette_holder: PipetteHolder, list_col_row: List[tuple[int, int]] | None = None) List[tuple[int, int]][source]

Pick tips from a PipetteHolder.

Parameters:
  • pipette_holder (PipetteHolder) – PipetteHolder labware containing tips

  • list_col_row (List[tuple[int, int]], optional) – List of (column, row) grid indices to try. If None, automatically finds occupied grid locations.

Returns:

Actual (column, row) positions where tips were picked

Return type:

List[tuple[int, int]]

Raises:

ValueError – If pipettor already has tips or pipette holder not found

pop_state(snapshot)[source]

Restore pipettor and deck state from snapshot.

push_state()[source]

Save complete state snapshot for later restoration.

remove_content(volume: float, tip_index: int | None = None) None[source]

Remove content from tip(s) proportionally.

Parameters:
  • volume (float) – Volume to remove (µL)

  • tip_index (int, optional) – Which tip to remove from. If None, removes from all tips.

remove_medium(source: Plate, source_col_row: List[tuple[int, int]], volume_per_well: float, destination: ReservoirHolder, destination_col_row: tuple[int, int]) None[source]

Remove medium from plate wells to a destination reservoir. Works for both single-channel and multichannel pipettors.

Parameters:
  • source (Plate) – Plate to remove medium from

  • source_col_row (List[tuple[int, int]]) – List of (Column, Row) positions to remove from. Row is start row for multichannel pipettor.

  • volume_per_well (float) – Volume (µL) to remove from each well

  • destination (ReservoirHolder) – ReservoirHolder to receive the liquid

  • destination_col_row (tuple[int, int]) – (Column, Row) position of destination reservoir.

replace_tips(pipette_holder: PipetteHolder, pick_pipette_holder: PipetteHolder | None = None, return_list_col_row: List[tuple[int, int]] | None = None, pick_list_col_row: List[tuple[int, int]] | None = None) dict[source]
return_multi_tips(pipette_holder: PipetteHolder, list_col_row: List[tuple[int, int]] | None = None) List[tuple[int, int]][source]

Return tips to a PipetteHolder using multichannel pipettor.

For multichannel, 8 consecutive tips are returned vertically.

Parameters:
  • pipette_holder (PipetteHolder) – PipetteHolder labware to return tips to

  • list_col_row (List[tuple[int, int]], optional) – List of (column, start_row) grid indices to try. start_row indicates where the first pipettor in multichannel would be positioned. If None, automatically finds all grid locations with 8 consecutive empty positions.

Raises:
  • ValueError – If not a multichannel pipettor or no tips to return

  • RuntimeError – If failed to return tips to any specified grid location

return_single_tip(pipette_holder: PipetteHolder, list_col_row: List[tuple[int, int]] | None = None) List[tuple[int, int]][source]

Return a single tip to a PipetteHolder using single-channel pipettor.

return_tips(pipette_holder: PipetteHolder, list_col_row: List[tuple[int, int]] | None = None) List[tuple[int, int]][source]

return tips to PipetteHolder.

set_simulation_mode(enabled: bool) None[source]

Enable or disable simulation mode.

spit(destination: Labware, dest_col_row: tuple[int, int], volume: float) None[source]

Dispense from tips to a destination labware.

Works with any labware type. If labware supports content tracking, content is tracked. If not, only physical movement is performed.

Parameters:
  • destination (Labware) – Destination labware (Plate, ReservoirHolder, or any other labware)

  • dest_col_row (tuple[int, int]) – (Column, Row) position in destination labware

  • volume (float) – Total volume to dispense across all tips (µL)

suck(source: Labware, source_col_row: tuple[int, int], volume: float) None[source]

Aspirate from a source labware.

Works with any labware type. If labware supports content tracking, content is tracked. If not, only physical movement is performed.

Parameters:
  • source (Labware) – Source labware (Plate, ReservoirHolder, or any other labware)

  • source_col_row (tuple[int, int]) – (Column, Row) position in source labware

  • volume (float) – Total volume to aspirate across all tips (µL)

transfer_plate_to_plate(source: Plate, source_col_row: List[tuple[int, int]], destination: Plate, dest_col_row: List[tuple[int, int]], volume_per_well: float, mix_volume: float = 0) None[source]

Transfer liquid from source plate to destination plate (one-to-one mapping). Works for both single-channel and multichannel pipettors.

Each source position is transferred to the corresponding destination position. source_col_row[i] → dest_col_row[i]

Parameters:
  • source (Plate) – Source plate

  • source_col_row (List[tuple[int, int]]) – List of source (Column, Row) positions. Row is start row for multichannel pipettor.

  • destination (Plate) – Destination plate

  • dest_col_row (List[tuple[int, int]]) – List of destination (Column, Row) positions. Must be same length as source_col_row.

  • volume_per_well (float) – Volume (µL) to transfer from each well

Raises:

ValueError – If source and destination lists have different lengths