diff --git a/unilabos/app/main.py b/unilabos/app/main.py index c652757c..93751262 100644 --- a/unilabos/app/main.py +++ b/unilabos/app/main.py @@ -1,6 +1,7 @@ import argparse import asyncio import os +import platform import shutil import signal import sys @@ -358,7 +359,7 @@ def main(): if BasicConfig.test_mode: print_status("启用测试模式:所有动作将模拟执行,不调用真实硬件", "warning") BasicConfig.communication_protocol = "websocket" - machine_name = os.popen("hostname").read().strip() + machine_name = platform.node() machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name]) BasicConfig.machine_name = machine_name BasicConfig.vis_2d_enable = args_dict["2d_vis"] diff --git a/unilabos/devices/liquid_handling/prcxi/prcxi.py b/unilabos/devices/liquid_handling/prcxi/prcxi.py index 3f33c960..f34583fe 100644 --- a/unilabos/devices/liquid_handling/prcxi/prcxi.py +++ b/unilabos/devices/liquid_handling/prcxi/prcxi.py @@ -55,6 +55,7 @@ from unilabos.devices.liquid_handling.liquid_handler_abstract import ( TransferLiquidReturn, ) from unilabos.registry.placeholder_type import ResourceSlot +from unilabos.resources.resource_tracker import ResourceTreeSet from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode @@ -90,20 +91,103 @@ class PRCXI9300Deck(Deck): 该类定义了 PRCXI 9300 的工作台布局和槽位信息。 """ - def __init__(self, name: str, size_x: float, size_y: float, size_z: float, **kwargs): + # T1-T16 默认位置 (4列×4行) + _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 + ] + _DEFAULT_SITE_SIZE = {"width": 128.0, "height": 86, "depth": 0} + _DEFAULT_CONTENT_TYPE = ["plate", "tip_rack", "plates", "tip_racks", "tube_rack", "adaptor"] + + def __init__(self, name: str, size_x: float, size_y: float, size_z: float, + sites: Optional[List[Dict[str, Any]]] = None, **kwargs): super().__init__(size_x, size_y, size_z, name) - self.slots = [None] * 16 # PRCXI 9300/9320 最大有 16 个槽位 - self.slot_locations = [Coordinate(0, 0, 0)] * 16 + if sites is not None: + self.sites: List[Dict[str, Any]] = [dict(s) for s in sites] + else: + self.sites = [] + for i, (x, y, z) in enumerate(self._DEFAULT_SITE_POSITIONS): + self.sites.append({ + "label": f"T{i + 1}", + "visible": True, + "position": {"x": x, "y": y, "z": z}, + "size": dict(self._DEFAULT_SITE_SIZE), + "content_type": list(self._DEFAULT_CONTENT_TYPE), + }) + # _ordering: label -> None, 用于外部通过 list(keys()).index(site) 将 Tn 转换为 spot index + self._ordering = collections.OrderedDict( + (site["label"], None) for site in self.sites + ) + + def _get_site_location(self, idx: int) -> Coordinate: + pos = self.sites[idx]["position"] + return Coordinate(pos["x"], pos["y"], pos["z"]) + + def _get_site_resource(self, idx: int) -> Optional[Resource]: + site_loc = self._get_site_location(idx) + for child in self.children: + if child.location == site_loc: + return child + return None + + def assign_child_resource( + self, + resource: Resource, + location: Optional[Coordinate] = None, + reassign: bool = True, + spot: Optional[int] = None, + ): + idx = spot + if spot is not None: + idx = spot + else: + for i, site in enumerate(self.sites): + site_loc = self._get_site_location(i) + if site.get("label") == resource.name: + idx = i + break + if location is not None and site_loc == location: + idx = i + break + + if idx is None: + for i in range(len(self.sites)): + if self._get_site_resource(i) is None: + idx = i + break + + if idx is None: + raise ValueError(f"No available site on deck '{self.name}' for resource '{resource.name}'") + + if not reassign and self._get_site_resource(idx) is not None: + raise ValueError(f"Site {idx} ('{self.sites[idx]['label']}') is already occupied") + + loc = self._get_site_location(idx) + super().assign_child_resource(resource, location=loc, reassign=reassign) def assign_child_at_slot(self, resource: Resource, slot: int, reassign: bool = False) -> None: - if self.slots[slot - 1] is not None and not reassign: - raise ValueError(f"Spot {slot} is already occupied") + self.assign_child_resource(resource, spot=slot - 1, reassign=reassign) - self.slots[slot - 1] = resource - super().assign_child_resource(resource, location=self.slot_locations[slot - 1]) + def serialize(self) -> dict: + data = super().serialize() + sites_out = [] + for i, site in enumerate(self.sites): + occupied = self._get_site_resource(i) + sites_out.append({ + "label": site["label"], + "visible": site.get("visible", True), + "occupied_by": occupied.name if occupied is not None else None, + "position": site["position"], + "size": site["size"], + "content_type": site["content_type"], + }) + data["sites"] = sites_out + return data -class PRCXI9300Container(Plate): +class PRCXI9300Container(Container): """PRCXI 9300 的专用 Container 类,继承自 Plate,用于槽位定位和未知模块。 该类定义了 PRCXI 9300 的工作台布局和槽位信息。 @@ -116,11 +200,10 @@ class PRCXI9300Container(Plate): size_y: float, size_z: float, category: str, - ordering: collections.OrderedDict, model: Optional[str] = None, **kwargs, ): - super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model) + super().__init__(name, size_x, size_y, size_z, category=category, model=model) self._unilabos_state = {} def load_state(self, state: Dict[str, Any]) -> None: @@ -567,14 +650,14 @@ class PRCXI9300Handler(LiquidHandlerAbstract): tablets_info = [] count = 0 for child in deck.children: - if child.children: - if "Material" in child.children[0]._unilabos_state: - number = int(child.name.replace("T", "")) - tablets_info.append( - WorkTablets( - Number=number, Code=f"T{number}", Material=child.children[0]._unilabos_state["Material"] - ) + # 如果放其他类型的物料,是不可以的 + if hasattr(child, "_unilabos_state") and "Material" in child._unilabos_state: + number = int(child.name.replace("T", "")) + tablets_info.append( + WorkTablets( + Number=number, Code=f"T{number}", Material=child._unilabos_state["Material"] ) + ) if is_9320: print("当前设备是9320") # 始终初始化 step_mode 属性 diff --git a/unilabos/registry/registry.py b/unilabos/registry/registry.py index 844d4cf8..02d80cca 100644 --- a/unilabos/registry/registry.py +++ b/unilabos/registry/registry.py @@ -175,7 +175,8 @@ class Registry: "res_id": "unilabos_resources", # 将当前实验室的全部物料id作为下拉框可选择 "device_id": "unilabos_devices", # 将当前实验室的全部设备id作为下拉框可选择 "parent": "unilabos_nodes", # 将当前实验室的设备/物料作为下拉框可选择 - "class_name": "unilabos_class", + "class_name": "unilabos_class", # 当前实验室物料的class name + "slot_on_deck": "unilabos_resource_slot:parent", # 勾选的parent的config中的sites的name,展示name,参数对应slot(index) }, }, "test_latency": { diff --git a/unilabos/resources/container.py b/unilabos/resources/container.py index fe19bacf..ed3871d3 100644 --- a/unilabos/resources/container.py +++ b/unilabos/resources/container.py @@ -1,10 +1,6 @@ -import json from typing import Dict, Any from pylabrobot.resources import Container -from unilabos_msgs.msg import Resource - -from unilabos.ros.msgs.message_converter import convert_from_ros_msg class RegularContainer(Container): @@ -16,12 +12,12 @@ class RegularContainer(Container): kwargs["size_y"] = 0 if "size_z" not in kwargs: kwargs["size_z"] = 0 + self.kwargs = kwargs - self.state = {} super().__init__(*args, category="container", **kwargs) def load_state(self, state: Dict[str, Any]): - self.state = state + super().load_state(state) def get_regular_container(name="container"): @@ -29,7 +25,6 @@ def get_regular_container(name="container"): r.category = "container" return r -# # class RegularContainer(object): # # 第一个参数必须是id传入 # # noinspection PyShadowingBuiltins @@ -89,4 +84,4 @@ def get_regular_container(name="container"): # return to_dict # # def __str__(self): -# return f"{self.id}" \ No newline at end of file +# return f"{self.id}" diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index 8f95ab25..17eaa68a 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -76,7 +76,7 @@ def canonicalize_nodes_data( if sample_id: logger.error(f"{node}的sample_id参数已弃用,sample_id: {sample_id}") for k in list(node.keys()): - if k not in ["id", "uuid", "name", "description", "schema", "model", "icon", "parent_uuid", "parent", "type", "class", "position", "config", "data", "children", "pose"]: + if k not in ["id", "uuid", "name", "description", "schema", "model", "icon", "parent_uuid", "parent", "type", "class", "position", "config", "data", "children", "pose", "extra"]: v = node.pop(k) node["config"][k] = v if outer_host_node_id is not None: diff --git a/unilabos/resources/resource_tracker.py b/unilabos/resources/resource_tracker.py index 8f7cc857..0bab5f10 100644 --- a/unilabos/resources/resource_tracker.py +++ b/unilabos/resources/resource_tracker.py @@ -411,6 +411,15 @@ class ResourceTreeSet(object): "tip_spot": "tip_spot", "tube": "tube", "bottle_carrier": "bottle_carrier", + "material_hole": "material_hole", + "container": "container", + "material_plate": "material_plate", + "electrode_sheet": "electrode_sheet", + "warehouse": "warehouse", + "magazine_holder": "magazine_holder", + "resource_group": "resource_group", + "trash": "trash", + "plate_adapter": "plate_adapter", } if source in replace_info: return replace_info[source] @@ -607,7 +616,7 @@ class ResourceTreeSet(object): plr_resources.append(plr_resource) except Exception as e: - logger.error(f"转换 PLR 资源失败: {e}") + logger.error(f"转换 PLR 资源失败: {e} {str(plr_dict)[:1000]}") import traceback logger.error(f"堆栈: {traceback.format_exc()}") diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index eddb57a2..6ff8cc57 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -915,8 +915,24 @@ class BaseROS2DeviceNode(Node, Generic[T]): else [] ) if target_site is not None and sites is not None and site_names is not None: - site_index = sites.index(original_instance) - site_name = site_names[site_index] + site_index = None + try: + # sites 可能是 Resource 列表或 dict 列表 (如 PRCXI9300Deck) + # 只有itemized_carrier在使用,准备弃用 + site_index = sites.index(original_instance) + except ValueError: + # dict 类型的 sites: 通过name匹配 + for idx, site in enumerate(sites): + if original_instance.name == site["occupied_by"]: + site_index = idx + break + elif (original_instance.location.x == site["position"]["x"] and original_instance.location.y == site["position"]["y"] and original_instance.location.z == site["position"]["z"]): + site_index = idx + break + if site_index is None: + site_name = None + else: + site_name = site_names[site_index] if site_name != target_site: parent = self.transfer_to_new_resource(original_instance, tree, additional_add_params) if parent is not None: @@ -924,6 +940,14 @@ class BaseROS2DeviceNode(Node, Generic[T]): parent_appended = True # 加载状态 + # noinspection PyProtectedMember + original_instance._size_x = plr_resource._size_x + # noinspection PyProtectedMember + original_instance._size_y = plr_resource._size_y + # noinspection PyProtectedMember + original_instance._size_z = plr_resource._size_z + # noinspection PyProtectedMember + original_instance._local_size_z = plr_resource._local_size_z original_instance.location = plr_resource.location original_instance.rotation = plr_resource.rotation original_instance.barcode = plr_resource.barcode @@ -984,7 +1008,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ].call_async( r ) # type: ignore - self.lab_logger().info(f"确认资源云端 Add 结果: {response.response}") + self.lab_logger().trace(f"确认资源云端 Add 结果: {response.response}") results.append(result) elif action == "update": if tree_set is None: @@ -1010,7 +1034,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ].call_async( r ) # type: ignore - self.lab_logger().info(f"确认资源云端 Update 结果: {response.response}") + self.lab_logger().trace(f"确认资源云端 Update 结果: {response.response}") results.append(result) elif action == "remove": result = _handle_remove(resources_uuid) diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 30c5d414..4a868523 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -1195,7 +1195,7 @@ class HostNode(BaseROS2DeviceNode): self.lab_logger().info(f"[Host Node-Resource] UUID映射: {len(uuid_mapping)} 个节点") # 还需要加入到资源图中,暂不实现,考虑资源图新的获取方式 response.response = json.dumps(uuid_mapping) - self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}") + self.lab_logger().info(f"[Host Node-Resource] Resource tree update completed, success: {success}") async def _resource_tree_update_callback(self, request: SerialCommand_Request, response: SerialCommand_Response): """ diff --git a/unilabos/test/experiments/prcxi_9320_slim.json b/unilabos/test/experiments/prcxi_9320_slim.json index 85bc90cf..e97aa3d9 100644 --- a/unilabos/test/experiments/prcxi_9320_slim.json +++ b/unilabos/test/experiments/prcxi_9320_slim.json @@ -1,795 +1,445 @@ { - "nodes": [ - { - "id": "PRCXI", - "name": "PRCXI", - "type": "device", - "class": "liquid_handler.prcxi", - "parent": "", - "pose": { - "size": { - "width": 562, - "height": 394, - "depth": 0 + "nodes": [ + { + "id": "PRCXI", + "name": "PRCXI", + "type": "device", + "class": "liquid_handler.prcxi", + "parent": "", + "pose": { + "size": { + "width": 562, + "height": 394, + "depth": 0 + } + }, + "config": { + "axis": "Left", + "deck": { + "_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck", + "_resource_child_name": "PRCXI_Deck" + }, + "host": "10.20.30.184", + "port": 9999, + "debug": true, + "setup": true, + "is_9320": true, + "timeout": 10, + "matrix_id": "5de524d0-3f95-406c-86dd-f83626ebc7cb", + "simulator": true, + "channel_num": 2 + }, + "data": { + "reset_ok": true + }, + "schema": {}, + "description": "", + "model": null, + "position": { + "x": 0, + "y": 240, + "z": 0 + } + }, + { + "id": "PRCXI_Deck", + "name": "PRCXI_Deck", + "children": [], + "parent": "PRCXI", + "type": "deck", + "class": "", + "position": { + "x": 10, + "y": 10, + "z": 0 + }, + "config": { + "type": "PRCXI9300Deck", + "size_x": 542, + "size_y": 374, + "size_z": 0, + "rotation": { + "x": 0, + "y": 0, + "z": 0, + "type": "Rotation" + }, + "category": "deck", + "barcode": null, + "preferred_pickup_location": null, + "sites": [ + { + "label": "T1", + "visible": true, + "occupied_by": null, + "position": { + "x": 0, + "y": 0, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor" + ] + }, + { + "label": "T2", + "visible": true, + "occupied_by": null, + "position": { + "x": 138, + "y": 0, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor" + ] + }, + { + "label": "T3", + "visible": true, + "occupied_by": null, + "position": { + "x": 276, + "y": 0, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor" + ] + }, + { + "label": "T4", + "visible": true, + "occupied_by": null, + "position": { + "x": 414, + "y": 0, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "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" + ] + }, + { + "label": "T14", + "visible": true, + "occupied_by": null, + "position": { + "x": 138, + "y": 288, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor" + ] + }, + { + "label": "T15", + "visible": true, + "occupied_by": null, + "position": { + "x": 276, + "y": 288, + "z": 0 + }, + "size": { + "width": 128.0, + "height": 86, + "depth": 0 + }, + "content_type": [ + "plate", + "tip_rack", + "plates", + "tip_racks", + "tube_rack", + "adaptor" + ] + }, + { + "label": "T16", + "visible": true, + "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" + ] + } + ] + }, + "data": {} } - }, - "config": { - "axis": "Left", - "deck": { - "_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck", - "_resource_child_name": "PRCXI_Deck" - }, - "host": "10.20.30.184", - "port": 9999, - "debug": true, - "setup": true, - "is_9320": true, - "timeout": 10, - "matrix_id": "5de524d0-3f95-406c-86dd-f83626ebc7cb", - "simulator": true, - "channel_num": 2 - }, - "data": { - "reset_ok": true - }, - "schema": {}, - "description": "", - "model": null, - "position": { - "x": 0, - "y": 240, - "z": 0 - } - }, - { - "id": "PRCXI_Deck", - "name": "PRCXI_Deck", - - "children": [], - "parent": "PRCXI", - "type": "deck", - "class": "", - "position": { - "x": 10, - "y": 10, - "z": 0 - }, - "config": { - "type": "PRCXI9300Deck", - "size_x": 542, - "size_y": 374, - "size_z": 0, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "deck", - "barcode": null - }, - "data": {} - }, - { - "id": "T1", - "name": "T1", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 0, - "y": 288, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T1", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T2", - "name": "T2", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 138, - "y": 288, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T2", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T3", - "name": "T3", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 276, - "y": 288, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T3", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T4", - "name": "T4", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 414, - "y": 288, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T4", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T5", - "name": "T5", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 0, - "y": 192, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T5", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T6", - "name": "T6", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 138, - "y": 192, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T6", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T7", - "name": "T7", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 276, - "y": 192, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T7", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T8", - "name": "T8", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 414, - "y": 192, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T8", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T9", - "name": "T9", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 0, - "y": 96, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T9", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T10", - "name": "T10", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 138, - "y": 96, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T10", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T11", - "name": "T11", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 276, - "y": 96, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T11", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T12", - "name": "T12", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 414, - "y": 96, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T12", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T13", - "name": "T13", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 0, - "y": 0, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T13", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T14", - "name": "T14", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 138, - "y": 0, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T14", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T15", - "name": "T15", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 276, - "y": 0, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T15", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - }, - { - "id": "T16", - "name": "T16", - "children": [], - "parent": "PRCXI_Deck", - "type": "plate", - "class": "", - "position": { - "x": 414, - "y": 0, - "z": 0 - }, - "config": { - "type": "PRCXI9300Container", - "size_x": 127, - "size_y": 85.5, - "size_z": 10, - "rotation": { - "x": 0, - "y": 0, - "z": 0, - "type": "Rotation" - }, - "category": "plate", - "model": null, - "barcode": null, - "ordering": {}, - "sites": [ - { - "label": "T16", - "visible": true, - "position": { "x": 0, "y": 0, "z": 0 }, - "size": { "width": 128.0, "height": 86, "depth": 0 }, - "content_type": [ - "plate", - "tip_rack", - "plates", - "tip_racks", - "tube_rack" - ] - } - ] - }, - "data": {} - } - ], - "edges": [] -} + ], + "edges": [] +} \ No newline at end of file diff --git a/unilabos/workflow/common.py b/unilabos/workflow/common.py index 3a1fee22..3e2fec92 100644 --- a/unilabos/workflow/common.py +++ b/unilabos/workflow/common.py @@ -26,7 +26,7 @@ res_id: plate_slot_{slot} device_id: /PRCXI class_name: PRCXI_BioER_96_wellplate - parent: /PRCXI/PRCXI_Deck/T{slot} + parent: /PRCXI/PRCXI_Deck slot_on_deck: "{slot}" - 输出端口: labware(用于连接 set_liquid_from_plate) - 控制流: create_resource 之间通过 ready 端口串联 @@ -122,7 +122,7 @@ NODE_TYPE_DEFAULT = "ILab" # 所有节点的默认类型 # create_resource 节点默认参数 CREATE_RESOURCE_DEFAULTS = { "device_id": "/PRCXI", - "parent_template": "/PRCXI/PRCXI_Deck/T{slot}", # {slot} 会被替换为实际的 slot 值 + "parent_template": "/PRCXI/PRCXI_Deck", "class_name": "PRCXI_BioER_96_wellplate", } @@ -424,7 +424,7 @@ def build_protocol_graph( "res_id": res_id, "device_id": CREATE_RESOURCE_DEFAULTS["device_id"], "class_name": lw_type, - "parent": CREATE_RESOURCE_DEFAULTS["parent_template"].format(slot=slot), + "parent": CREATE_RESOURCE_DEFAULTS["parent_template"], "bind_locations": {"x": 0.0, "y": 0.0, "z": 0.0}, "slot_on_deck": slot, },