diff --git a/unilabos/devices/liquid_handling/prcxi/prcxi.py b/unilabos/devices/liquid_handling/prcxi/prcxi.py index 47b213ad..3b7c7b13 100644 --- a/unilabos/devices/liquid_handling/prcxi/prcxi.py +++ b/unilabos/devices/liquid_handling/prcxi/prcxi.py @@ -45,6 +45,7 @@ from pylabrobot.resources import ( Trash, PlateAdapter, TubeRack, + create_homogeneous_resources, ) from unilabos.devices.liquid_handling.liquid_handler_abstract import ( @@ -55,6 +56,7 @@ from unilabos.devices.liquid_handling.liquid_handler_abstract import ( TransferLiquidReturn, ) from unilabos.registry.placeholder_type import ResourceSlot +from unilabos.resources.itemized_carrier import ItemizedCarrier from unilabos.resources.resource_tracker import ResourceTreeSet from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode @@ -91,15 +93,15 @@ class PRCXI9300Deck(Deck): 该类定义了 PRCXI 9300 的工作台布局和槽位信息。 """ - # T1-T16 默认位置 (4列×4行) + # T1-T16 默认位置 (4列×4行, Y轴从上往下递减, T1在左上角) _DEFAULT_SITE_POSITIONS = [ - (0, 0, 0), (138, 0, 0), (276, 0, 0), (414, 0, 0), # T1-T4 - (0, 96, 0), (138, 96, 0), (276, 96, 0), (414, 96, 0), # T5-T8 - (0, 192, 0), (138, 192, 0), (276, 192, 0), (414, 192, 0), # T9-T12 - (0, 288, 0), (138, 288, 0), (276, 288, 0), (414, 288, 0), # T13-T16 + (0, 288, 0), (138, 288, 0), (276, 288, 0), (414, 288, 0), # T1-T4 (第1行, 最上) + (0, 192, 0), (138, 192, 0), (276, 192, 0), (414, 192, 0), # T5-T8 (第2行) + (0, 96, 0), (138, 96, 0), (276, 96, 0), (414, 96, 0), # T9-T12 (第3行) + (0, 0, 0), (138, 0, 0), (276, 0, 0), (414, 0, 0), # T13-T16 (第4行, 最下) ] _DEFAULT_SITE_SIZE = {"width": 128.0, "height": 86, "depth": 0} - _DEFAULT_CONTENT_TYPE = ["plate", "tip_rack", "plates", "tip_racks", "tube_rack", "adaptor"] + _DEFAULT_CONTENT_TYPE = ["plate", "tip_rack", "plates", "tip_racks", "tube_rack", "adaptor", "plateadapter", "module"] def __init__(self, name: str, size_x: float, size_y: float, size_z: float, sites: Optional[List[Dict[str, Any]]] = None, **kwargs): @@ -544,6 +546,108 @@ class PRCXI9300TubeRack(TubeRack): return data +class PRCXI9300ModuleSite(ItemizedCarrier): + """ + PRCXI 功能模块的基础站点类(加热/冷却/震荡/磁吸等)。 + + - 继承 ItemizedCarrier,可被拖放到 Deck 槽位上 + - 顶面有一个 ResourceHolder 站点,可吸附板类资源(叠放) + - content_type 包含 "plateadapter" 以支持适配器叠放 + - 支持 material_info 注入 + """ + + def __init__(self, name: str, size_x: float, size_y: float, size_z: float, + material_info: Optional[Dict[str, Any]] = None, **kwargs): + sites = create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(0, 0, 0)], + resource_size_x=size_x, + resource_size_y=size_y, + resource_size_z=size_z, + name_prefix=name, + )[0] + + kwargs.pop('layout', None) + sites_in = kwargs.pop('sites', None) + + sites_dict = {name: sites} + + content_type = [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "plateadapter", + ] + + if sites_in is not None and isinstance(sites_in, dict): + for site_key, site_value in sites_in.items(): + if site_key in sites_dict: + sites_dict[site_key] = site_value + + super().__init__( + name, size_x, size_y, size_z, + sites=sites_dict, + num_items_x=kwargs.pop('num_items_x', 1), + num_items_y=kwargs.pop('num_items_y', 1), + num_items_z=kwargs.pop('num_items_z', 1), + content_type=content_type, + **kwargs, + ) + self._unilabos_state = {} + if material_info: + self._unilabos_state["Material"] = material_info + + def assign_child_resource(self, resource, location=Coordinate(0, 0, 0), reassign=True, spot=None): + from pylabrobot.resources.resource import Resource + Resource.assign_child_resource(self, resource, location=location, reassign=reassign) + + def unassign_child_resource(self, resource): + from pylabrobot.resources.resource import Resource + Resource.unassign_child_resource(self, resource) + + def serialize_state(self) -> Dict[str, Dict[str, Any]]: + try: + data = super().serialize_state() + except AttributeError: + data = {} + + if hasattr(self, 'sites') and self.sites: + sites_info = [] + for site in self.sites: + if hasattr(site, '__class__') and 'pylabrobot' in str(site.__class__.__module__): + sites_info.append({ + "__pylabrobot_object__": True, + "class": site.__class__.__name__, + "module": site.__class__.__module__, + "name": getattr(site, 'name', str(site)) + }) + else: + sites_info.append(site) + data['sites'] = sites_info + + if hasattr(self, "_unilabos_state") and self._unilabos_state: + safe_state: Dict[str, Any] = {} + for k, v in self._unilabos_state.items(): + if k == "Material" and isinstance(v, dict): + safe_material: Dict[str, Any] = {} + for mk, mv in v.items(): + if isinstance(mv, (str, int, float, bool, list, dict, type(None))): + safe_material[mk] = mv + safe_state[k] = safe_material + elif isinstance(v, (str, int, float, bool, list, dict, type(None))): + safe_state[k] = v + data.update(safe_state) + + return data + + def load_state(self, state: Dict[str, Any]) -> None: + super().load_state(state) + if 'sites' in state: + self.sites = [state['sites']] + + class PRCXI9300PlateAdapter(PlateAdapter): """ 专用板式适配器类:用于承载 Plate 的底座(如 PCR 适配器、磁吸架等)。 diff --git a/unilabos/devices/liquid_handling/prcxi/prcxi_modules.py b/unilabos/devices/liquid_handling/prcxi/prcxi_modules.py new file mode 100644 index 00000000..b7cef1b0 --- /dev/null +++ b/unilabos/devices/liquid_handling/prcxi/prcxi_modules.py @@ -0,0 +1,150 @@ +from typing import Any, Dict, Optional + +from .prcxi import PRCXI9300ModuleSite + + +class PRCXI9300FunctionalModule(PRCXI9300ModuleSite): + """ + PRCXI 9300 功能模块基类(加热/冷却/震荡/加热震荡/磁吸等)。 + + 设计目标: + - 作为一个可以在工作台上拖拽摆放的实体资源(继承自 PRCXI9300ModuleSite -> ItemizedCarrier)。 + - 顶面存在一个站点(site),可吸附标准板类资源(plate / tip_rack / tube_rack 等)。 + - 支持注入 `material_info` (UUID 等),并且在 serialize_state 时做安全过滤。 + """ + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + module_type: Optional[str] = None, + category: str = "module", + model: Optional[str] = None, + material_info: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ): + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + material_info=material_info, + model=model, + category=category, + **kwargs, + ) + + # 记录模块类型(加热 / 冷却 / 震荡 / 加热震荡 / 磁吸) + self.module_type = module_type or "generic" + + # 与 PRCXI9300PlateAdapter 一致,使用 _unilabos_state 保存扩展信息 + if not hasattr(self, "_unilabos_state") or self._unilabos_state is None: + self._unilabos_state = {} + + # super().__init__ 已经在有 material_info 时写入 "Material",这里仅确保存在 + if material_info is not None and "Material" not in self._unilabos_state: + self._unilabos_state["Material"] = material_info + + # 额外标记 category 和模块类型,便于前端或上层逻辑区分 + self._unilabos_state.setdefault("category", category) + self._unilabos_state["module_type"] = module_type + + +# ============================================================================ +# 具体功能模块定义 +# 这里的尺寸和 material_info 目前为占位参数,后续可根据实际测量/JSON 配置进行更新。 +# 顶面站点尺寸与模块外形一致,保证可以吸附标准 96 板/储液槽等。 +# ============================================================================ + + +def PRCXI_Heating_Module(name: str) -> PRCXI9300FunctionalModule: + """加热模块(顶面可吸附标准板)。""" + return PRCXI9300FunctionalModule( + name=name, + size_x=127.76, + size_y=85.48, + size_z=40.0, + module_type="heating", + model="PRCXI_Heating_Module", + material_info={ + "uuid": "TODO-HEATING-MODULE-UUID", + "Code": "HEAT-MOD", + "Name": "PRCXI 加热模块", + "SupplyType": 3, + }, + ) + + +def PRCXI_MetalCooling_Module(name: str) -> PRCXI9300FunctionalModule: + """金属冷却模块(顶面可吸附标准板)。""" + return PRCXI9300FunctionalModule( + name=name, + size_x=127.76, + size_y=85.48, + size_z=40.0, + module_type="metal_cooling", + model="PRCXI_MetalCooling_Module", + material_info={ + "uuid": "TODO-METAL-COOLING-MODULE-UUID", + "Code": "METAL-COOL-MOD", + "Name": "PRCXI 金属冷却模块", + "SupplyType": 3, + }, + ) + + +def PRCXI_Shaking_Module(name: str) -> PRCXI9300FunctionalModule: + """震荡模块(顶面可吸附标准板)。""" + return PRCXI9300FunctionalModule( + name=name, + size_x=127.76, + size_y=85.48, + size_z=50.0, + module_type="shaking", + model="PRCXI_Shaking_Module", + material_info={ + "uuid": "TODO-SHAKING-MODULE-UUID", + "Code": "SHAKE-MOD", + "Name": "PRCXI 震荡模块", + "SupplyType": 3, + }, + ) + + +def PRCXI_Heating_Shaking_Module(name: str) -> PRCXI9300FunctionalModule: + """加热震荡模块(顶面可吸附标准板)。""" + return PRCXI9300FunctionalModule( + name=name, + size_x=127.76, + size_y=85.48, + size_z=55.0, + module_type="heating_shaking", + model="PRCXI_Heating_Shaking_Module", + material_info={ + "uuid": "TODO-HEATING-SHAKING-MODULE-UUID", + "Code": "HEAT-SHAKE-MOD", + "Name": "PRCXI 加热震荡模块", + "SupplyType": 3, + }, + ) + + +def PRCXI_Magnetic_Module(name: str) -> PRCXI9300FunctionalModule: + """磁吸模块(顶面可吸附标准板)。""" + return PRCXI9300FunctionalModule( + name=name, + size_x=127.76, + size_y=85.48, + size_z=30.0, + module_type="magnetic", + model="PRCXI_Magnetic_Module", + material_info={ + "uuid": "TODO-MAGNETIC-MODULE-UUID", + "Code": "MAG-MOD", + "Name": "PRCXI 磁吸模块", + "SupplyType": 3, + }, + ) + diff --git a/unilabos/registry/resources/prcxi/modules.yaml b/unilabos/registry/resources/prcxi/modules.yaml new file mode 100644 index 00000000..b35a6ece --- /dev/null +++ b/unilabos/registry/resources/prcxi/modules.yaml @@ -0,0 +1,70 @@ +PRCXI_Heating_Module: + category: + - prcxi + - modules + class: + module: unilabos.devices.liquid_handling.prcxi.prcxi_modules:PRCXI_Heating_Module + type: pylabrobot + description: '加热模块 (Code: HEAT-MOD)' + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +PRCXI_MetalCooling_Module: + category: + - prcxi + - modules + class: + module: unilabos.devices.liquid_handling.prcxi.prcxi_modules:PRCXI_MetalCooling_Module + type: pylabrobot + description: '金属冷却模块 (Code: METAL-COOL-MOD)' + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +PRCXI_Shaking_Module: + category: + - prcxi + - modules + class: + module: unilabos.devices.liquid_handling.prcxi.prcxi_modules:PRCXI_Shaking_Module + type: pylabrobot + description: '震荡模块 (Code: SHAKE-MOD)' + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +PRCXI_Heating_Shaking_Module: + category: + - prcxi + - modules + class: + module: unilabos.devices.liquid_handling.prcxi.prcxi_modules:PRCXI_Heating_Shaking_Module + type: pylabrobot + description: '加热震荡模块 (Code: HEAT-SHAKE-MOD)' + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + +PRCXI_Magnetic_Module: + category: + - prcxi + - modules + class: + module: unilabos.devices.liquid_handling.prcxi.prcxi_modules:PRCXI_Magnetic_Module + type: pylabrobot + description: '磁吸模块 (Code: MAG-MOD)' + handles: [] + icon: '' + init_param_schema: {} + registry_type: resource + version: 1.0.0 + diff --git a/unilabos/resources/plr_additional_res_reg.py b/unilabos/resources/plr_additional_res_reg.py index 1c019ded..a4be086e 100644 --- a/unilabos/resources/plr_additional_res_reg.py +++ b/unilabos/resources/plr_additional_res_reg.py @@ -9,6 +9,9 @@ def register(): from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300TipRack from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Trash from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300TubeRack + from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300ModuleSite + # noinspection PyUnresolvedReferences + from unilabos.devices.liquid_handling.prcxi.prcxi_modules import PRCXI9300FunctionalModule # noinspection PyUnresolvedReferences from unilabos.devices.workstation.workstation_base import WorkStationContainer diff --git a/unilabos/resources/resource_tracker.py b/unilabos/resources/resource_tracker.py index 288ddc12..6a0755b5 100644 --- a/unilabos/resources/resource_tracker.py +++ b/unilabos/resources/resource_tracker.py @@ -459,6 +459,8 @@ class ResourceTreeSet(object): "reagent_bottle": "reagent_bottle", "flask": "flask", "beaker": "beaker", + "module": "module", + "carrier": "carrier", } if source in replace_info: return replace_info[source] @@ -596,6 +598,8 @@ class ResourceTreeSet(object): "deck": "Deck", "container": "RegularContainer", "tip_spot": "TipSpot", + "module": "PRCXI9300ModuleSite", + "carrier": "ItemizedCarrier", } def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict, name_to_extra: dict): @@ -958,6 +962,17 @@ class ResourceTreeSet(object): f"从远端同步了 {added_count} 个物料子树" ) else: + # 二级是物料 + if remote_child_name not in local_children_map: + # 本地不存在该物料,直接引入 + remote_child.res_content.parent = local_device.res_content + local_device.children.append(remote_child) + local_children_map[remote_child_name] = remote_child + logger.info( + f"物料 '{remote_root_id}/{remote_child_name}': " + f"从远端同步了整个子树" + ) + continue # 二级物料已存在,比较三级子节点是否缺失 local_material = local_children_map[remote_child_name] local_material_children_map = {child.res_content.name: child for child in diff --git a/unilabos/test/experiments/prcxi_9320_slim.json b/unilabos/test/experiments/prcxi_9320_slim.json index 2aaee6a7..1ad470d1 100644 --- a/unilabos/test/experiments/prcxi_9320_slim.json +++ b/unilabos/test/experiments/prcxi_9320_slim.json @@ -74,7 +74,7 @@ "occupied_by": null, "position": { "x": 0, - "y": 0, + "y": 288, "z": 0 }, "size": { @@ -89,7 +89,9 @@ "plates", "tip_racks", "tube_rack", - "adaptor" + "adaptor", + "plateadapter", + "module" ] }, { @@ -98,7 +100,7 @@ "occupied_by": null, "position": { "x": 138, - "y": 0, + "y": 288, "z": 0 }, "size": { @@ -112,7 +114,9 @@ "plates", "tip_racks", "tube_rack", - "adaptor" + "adaptor", + "plateadapter", + "module" ] }, { @@ -121,7 +125,7 @@ "occupied_by": null, "position": { "x": 276, - "y": 0, + "y": 288, "z": 0 }, "size": { @@ -135,7 +139,9 @@ "plates", "tip_racks", "tube_rack", - "adaptor" + "adaptor", + "plateadapter", + "module" ] }, { @@ -144,6 +150,231 @@ "occupied_by": null, "position": { "x": 414, + "y": 288, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T5", + "visible": true, + "occupied_by": null, + "position": { + "x": 0, + "y": 192, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T6", + "visible": true, + "occupied_by": null, + "position": { + "x": 138, + "y": 192, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T7", + "visible": true, + "occupied_by": null, + "position": { + "x": 276, + "y": 192, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T8", + "visible": true, + "occupied_by": null, + "position": { + "x": 414, + "y": 192, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T9", + "visible": true, + "occupied_by": null, + "position": { + "x": 0, + "y": 96, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T10", + "visible": true, + "occupied_by": null, + "position": { + "x": 138, + "y": 96, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T11", + "visible": true, + "occupied_by": null, + "position": { + "x": 276, + "y": 96, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T12", + "visible": true, + "occupied_by": null, + "position": { + "x": 414, + "y": 96, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor", + "plateadapter", + "module" + ] + }, + { + "label": "T13", + "visible": true, + "occupied_by": null, + "position": { + "x": 0, "y": 0, "z": 0 }, @@ -158,214 +389,9 @@ "plates", "tip_racks", "tube_rack", - "adaptor" - ] - }, - { - "label": "T5", - "visible": true, - "occupied_by": null, - "position": { - "x": 0, - "y": 96, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T6", - "visible": true, - "occupied_by": null, - "position": { - "x": 138, - "y": 96, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T7", - "visible": true, - "occupied_by": null, - "position": { - "x": 276, - "y": 96, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T8", - "visible": true, - "occupied_by": null, - "position": { - "x": 414, - "y": 96, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T9", - "visible": true, - "occupied_by": null, - "position": { - "x": 0, - "y": 192, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T10", - "visible": true, - "occupied_by": null, - "position": { - "x": 138, - "y": 192, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T11", - "visible": true, - "occupied_by": null, - "position": { - "x": 276, - "y": 192, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T12", - "visible": true, - "occupied_by": null, - "position": { - "x": 414, - "y": 192, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" - ] - }, - { - "label": "T13", - "visible": true, - "occupied_by": null, - "position": { - "x": 0, - "y": 288, - "z": 0 - }, - "size": { - "width": 128.0, - "height": 86, - "depth": 0 - }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack", - "adaptor" + "adaptor", + "plateadapter", + "module" ] }, { @@ -374,7 +400,7 @@ "occupied_by": null, "position": { "x": 138, - "y": 288, + "y": 0, "z": 0 }, "size": { @@ -388,7 +414,9 @@ "plates", "tip_racks", "tube_rack", - "adaptor" + "adaptor", + "plateadapter", + "module" ] }, { @@ -397,7 +425,7 @@ "occupied_by": null, "position": { "x": 276, - "y": 288, + "y": 0, "z": 0 }, "size": { @@ -411,7 +439,9 @@ "plates", "tip_racks", "tube_rack", - "adaptor" + "adaptor", + "plateadapter", + "module" ] }, { @@ -420,7 +450,7 @@ "occupied_by": null, "position": { "x": 414, - "y": 288, + "y": 0, "z": 0 }, "size": { @@ -434,7 +464,9 @@ "plates", "tip_racks", "tube_rack", - "adaptor" + "adaptor", + "plateadapter", + "module" ] } ] diff --git a/unilabos/test/experiments/prcxi_9320_with_res_test.json b/unilabos/test/experiments/prcxi_9320_with_res_test.json index 831cecce..2cf9ffb6 100644 --- a/unilabos/test/experiments/prcxi_9320_with_res_test.json +++ b/unilabos/test/experiments/prcxi_9320_with_res_test.json @@ -108,7 +108,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -153,7 +154,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -198,7 +200,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -243,7 +246,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -288,7 +292,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -333,7 +338,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -378,7 +384,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -423,7 +430,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -468,7 +476,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -513,7 +522,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -558,7 +568,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -603,7 +614,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -648,7 +660,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -693,7 +706,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -738,7 +752,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ] @@ -783,7 +798,8 @@ "tip_rack", "plates", "tip_racks", - "tube_rack" + "tube_rack", + "plateadapter" ] } ]