diff --git a/test/resources/bioyond_materials.json b/test/resources/bioyond_materials.json new file mode 100644 index 00000000..9e6d7f6d --- /dev/null +++ b/test/resources/bioyond_materials.json @@ -0,0 +1,198 @@ +{ + "data": [ + { + "id": "3a1c67a9-aed7-b94d-9e24-bfdf10c8baa9", + "typeName": "烧杯", + "code": "0006-00160", + "barCode": "", + "name": "ODA", + "quantity": 120000.00000000000000000000000, + "lockQuantity": 695374.00000000000000000000000, + "unit": "微升", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a14aa17-0d49-11d7-a6e1-f236b3e5e5a3", + "whid": "3a14aa17-0d49-dce4-486e-4b5c85c8b366", + "whName": "堆栈1", + "code": "0001-0001", + "x": 1, + "y": 1, + "z": 1, + "quantity": 0 + } + ], + "detail": [] + }, + { + "id": "3a1c67a9-aed9-1ade-5fe1-cc04b24b171c", + "typeName": "烧杯", + "code": "0006-00161", + "barCode": "", + "name": "MPDA", + "quantity": 120000.00000000000000000000000, + "lockQuantity": 681618.00000000000000000000000, + "unit": "", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a14aa17-0d49-4bc5-8836-517b75473f5f", + "whid": "3a14aa17-0d49-dce4-486e-4b5c85c8b366", + "whName": "堆栈1", + "code": "0001-0002", + "x": 1, + "y": 2, + "z": 1, + "quantity": 0 + } + ], + "detail": [] + }, + { + "id": "3a1c67a9-aed9-2864-6783-2cee4e701ba6", + "typeName": "试剂瓶", + "code": "0004-00041", + "barCode": "", + "name": "NMP", + "quantity": 300000.00000000000000000000000, + "lockQuantity": 380000.00000000000000000000000, + "unit": "微升", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a14aa3b-9fab-adac-7b9c-e1ee446b51d5", + "whid": "3a14aa3b-9fab-9d8e-d1a7-828f01f51f0c", + "whName": "站内试剂存放堆栈", + "code": "0003-0001", + "x": 1, + "y": 1, + "z": 1, + "quantity": 0 + } + ], + "detail": [] + }, + { + "id": "3a1c67a9-aed9-32c7-5809-3ba1b8db1aa1", + "typeName": "试剂瓶", + "code": "0004-00042", + "barCode": "", + "name": "PGME", + "quantity": 300000.00000000000000000000000, + "lockQuantity": 337892.00000000000000000000000, + "unit": "", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a14aa3b-9fab-ca72-febc-b7c304476c78", + "whid": "3a14aa3b-9fab-9d8e-d1a7-828f01f51f0c", + "whName": "站内试剂存放堆栈", + "code": "0003-0002", + "x": 1, + "y": 2, + "z": 1, + "quantity": 0 + } + ], + "detail": [] + }, + { + "id": "3a1c68c8-0574-d748-725e-97a2e549f085", + "typeName": "样品板", + "code": "0001-00004", + "barCode": "", + "name": "0917", + "quantity": 1.0000000000000000000000000000, + "lockQuantity": 4.0000000000000000000000000000, + "unit": "块", + "status": 1, + "isUse": false, + "locations": [ + { + "id": "3a14aa17-0d49-f49c-6b66-b27f185a3b32", + "whid": "3a14aa17-0d49-dce4-486e-4b5c85c8b366", + "whName": "堆栈1", + "code": "0001-0009", + "x": 2, + "y": 1, + "z": 1, + "quantity": 0 + } + ], + "detail": [ + { + "id": "3a1c68c8-0574-69a1-9858-4637e0193451", + "detailMaterialId": "3a1c68c8-0574-3630-bd42-bbf3623c5208", + "code": null, + "name": "SIDA", + "quantity": "300000", + "lockQuantity": "4", + "unit": "微升", + "x": 1, + "y": 2, + "z": 1, + "associateId": null + }, + { + "id": "3a1c68c8-0574-8d51-3191-a31f5be421e5", + "detailMaterialId": "3a1c68c8-0574-3b20-9ad7-90755f123d53", + "code": null, + "name": "BTDA-2", + "quantity": "300000", + "lockQuantity": "4", + "unit": "微升", + "x": 2, + "y": 2, + "z": 1, + "associateId": null + }, + { + "id": "3a1c68c8-0574-da80-735b-53ae2197a360", + "detailMaterialId": "3a1c68c8-0574-f2e4-33b3-90d813567939", + "code": null, + "name": "BTDA-DD", + "quantity": "300000", + "lockQuantity": "28", + "unit": "微升", + "x": 1, + "y": 1, + "z": 1, + "associateId": null + }, + { + "id": "3a1c68c8-0574-e717-1b1b-99891f875455", + "detailMaterialId": "3a1c68c8-0574-a0ef-e636-68cdc98960e2", + "code": null, + "name": "BTDA-3", + "quantity": "300000", + "lockQuantity": "4", + "unit": "微升", + "x": 2, + "y": 3, + "z": 1, + "associateId": null + }, + { + "id": "3a1c68c8-0574-e9bd-6cca-5e261b4f89cb", + "detailMaterialId": "3a1c68c8-0574-9d11-5115-283e8e5510b1", + "code": null, + "name": "BTDA-1", + "quantity": "300000", + "lockQuantity": "4", + "unit": "微升", + "x": 2, + "y": 1, + "z": 1, + "associateId": null + } + ] + } + ], + "code": 1, + "message": "", + "timestamp": 1758560573511 +} \ No newline at end of file diff --git a/test/resources/bottle_carrier.py b/test/resources/test_bottle_carrier.py similarity index 76% rename from test/resources/bottle_carrier.py rename to test/resources/test_bottle_carrier.py index 162ead9e..22ef6f51 100644 --- a/test/resources/bottle_carrier.py +++ b/test/resources/test_bottle_carrier.py @@ -1,7 +1,7 @@ import pytest -from unilabos.resources.bioyond.bottle_carrier import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier -from unilabos.resources.bioyond.bottle import create_powder_bottle, create_solution_beaker, create_reagent_bottle +from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier +from unilabos.resources.bioyond.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle @pytest.fixture @@ -17,9 +17,9 @@ def bottle_carrier() -> "BottleCarrier": print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}") # 创建瓶子和烧杯 - powder_bottle = create_powder_bottle("powder_bottle_01") - solution_beaker = create_solution_beaker("solution_beaker_01") - reagent_bottle = create_reagent_bottle("reagent_bottle_01") + powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01") + solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01") + reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01") print(f"\n创建的物料:") print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL") diff --git a/test/resources/test_converter_bioyond.py b/test/resources/test_converter_bioyond.py new file mode 100644 index 00000000..6c054c29 --- /dev/null +++ b/test/resources/test_converter_bioyond.py @@ -0,0 +1,31 @@ +import pytest +import json +import os + +from unilabos.resources.graphio import resource_bioyond_to_plr +from unilabos.registry.registry import lab_registry + +lab_registry.setup() + + +type_mapping = { + "烧杯": "BIOYOND_PolymerStation_1FlaskCarrier", + "试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier", + "样品板": "BIOYOND_PolymerStation_6VialCarrier", +} + +@pytest.fixture +def bioyond_materials() -> list[dict]: + print("加载 BioYond 物料数据...") + print(os.getcwd()) + with open("bioyond_materials.json", "r", encoding="utf-8") as f: + data = json.load(f)["data"] + print(f"加载了 {len(data)} 条物料数据") + return data + + +def test_bioyond_to_plr(bioyond_materials) -> list[dict]: + print("将 BioYond 物料数据转换为 PLR 格式...") + output = resource_bioyond_to_plr(bioyond_materials, type_mapping=type_mapping) + print([resource.serialize() for resource in output]) + print([resource.serialize_all_state() for resource in output]) diff --git a/unilabos/registry/resources/bioyond/bottle_carriers.yaml b/unilabos/registry/resources/bioyond/bottle_carriers.yaml new file mode 100644 index 00000000..221e6c74 --- /dev/null +++ b/unilabos/registry/resources/bioyond/bottle_carriers.yaml @@ -0,0 +1,38 @@ +BIOYOND_PolymerStation_6VialCarrier: + category: + - bottle_carriers + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6VialCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_6VialCarrier + handles: [ ] + icon: '' + init_param_schema: { } + registry_type: resource + version: 1.0.0 + +BIOYOND_PolymerStation_1BottleCarrier: + category: + - bottle_carriers + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1BottleCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_1BottleCarrier + handles: [ ] + icon: '' + init_param_schema: { } + registry_type: resource + version: 1.0.0 + +BIOYOND_PolymerStation_1FlaskCarrier: + category: + - bottle_carriers + class: + module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1FlaskCarrier + type: pylabrobot + description: BIOYOND_PolymerStation_1FlaskCarrier + handles: [ ] + icon: '' + init_param_schema: { } + registry_type: resource + version: 1.0.0 \ No newline at end of file diff --git a/unilabos/resources/bioyond/bottle_carrier.py b/unilabos/resources/bioyond/bottle_carrier.py deleted file mode 100644 index e7ac488e..00000000 --- a/unilabos/resources/bioyond/bottle_carrier.py +++ /dev/null @@ -1,78 +0,0 @@ -from unilabos.resources.bottle_carrier import Bottle, BottleCarrier -from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder - - -# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial - -def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier: - """6瓶载架 - 2x3布局""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 50.0 - - # 瓶位尺寸 - bottle_diameter = 30.0 - bottle_spacing_x = 42.0 # X方向间距 - bottle_spacing_y = 35.0 # Y方向间距 - - # 计算起始位置 (居中排列) - start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 - start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 - - # 创建6个位置坐标 (2行 x 3列) - locations = [] - for row in range(2): - for col in range(3): - x = start_x + col * bottle_spacing_x - y = start_y + row * bottle_spacing_y - z = 5.0 # 架位底部 - locations.append(Coordinate(x, y, z)) - - return BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=locations, - resource_size_x=bottle_diameter, - resource_size_y=bottle_diameter, - name_prefix=name, - ), - model="BIOYOND_Electrolyte_6VialCarrier", - ) - - -def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier: - """1瓶载架 - 单个中央位置""" - - # 载架尺寸 (mm) - carrier_size_x = 127.8 - carrier_size_y = 85.5 - carrier_size_z = 100.0 - - # 烧杯尺寸 - beaker_diameter = 80.0 - - # 计算中央位置 - center_x = (carrier_size_x - beaker_diameter) / 2 - center_y = (carrier_size_y - beaker_diameter) / 2 - center_z = 5.0 - - return BottleCarrier( - name=name, - size_x=carrier_size_x, - size_y=carrier_size_y, - size_z=carrier_size_z, - sites=create_homogeneous_resources( - klass=ResourceHolder, - locations=[Coordinate(center_x, center_y, center_z)], - resource_size_x=beaker_diameter, - resource_size_y=beaker_diameter, - name_prefix=name, - ), - model="BIOYOND_Electrolyte_1BottleCarrier", - ) \ No newline at end of file diff --git a/unilabos/resources/bioyond/bottle_carriers.py b/unilabos/resources/bioyond/bottle_carriers.py new file mode 100644 index 00000000..cd84944b --- /dev/null +++ b/unilabos/resources/bioyond/bottle_carriers.py @@ -0,0 +1,213 @@ +from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder + +from unilabos.resources.bottle_carrier import Bottle, BottleCarrier +from unilabos.resources.bioyond.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle +# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial + + +def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier: + """6瓶载架 - 2x3布局""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 50.0 + + # 瓶位尺寸 + bottle_diameter = 30.0 + bottle_spacing_x = 42.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + # 创建6个位置坐标 (2行 x 3列) + locations = [] + for row in range(2): + for col in range(3): + x = start_x + col * bottle_spacing_x + y = start_y + row * bottle_spacing_y + z = 5.0 # 架位底部 + locations.append(Coordinate(x, y, z)) + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=locations, + resource_size_x=bottle_diameter, + resource_size_y=bottle_diameter, + name_prefix=name, + ), + model="BIOYOND_Electrolyte_6VialCarrier", + ) + carrier.num_items_x = 3 + carrier.num_items_y = 2 + carrier.num_items_z = 1 + for i in range(6): + carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_vial_{i+1}") + return carrier + + +def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier: + """1瓶载架 - 单个中央位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 100.0 + + # 烧杯尺寸 + beaker_diameter = 80.0 + + # 计算中央位置 + center_x = (carrier_size_x - beaker_diameter) / 2 + center_y = (carrier_size_y - beaker_diameter) / 2 + center_z = 5.0 + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(center_x, center_y, center_z)], + resource_size_x=beaker_diameter, + resource_size_y=beaker_diameter, + name_prefix=name, + ), + model="BIOYOND_Electrolyte_1BottleCarrier", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + carrier[0] = BIOYOND_PolymerStation_Solution_Beaker(f"{name}_beaker_1") + return carrier + + +def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier: + """6瓶载架 - 2x3布局""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 50.0 + + # 瓶位尺寸 + bottle_diameter = 30.0 + bottle_spacing_x = 42.0 # X方向间距 + bottle_spacing_y = 35.0 # Y方向间距 + + # 计算起始位置 (居中排列) + start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2 + start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2 + + # 创建6个位置坐标 (2行 x 3列) + locations = [] + for row in range(2): + for col in range(3): + x = start_x + col * bottle_spacing_x + y = start_y + row * bottle_spacing_y + z = 5.0 # 架位底部 + locations.append(Coordinate(x, y, z)) + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=locations, + resource_size_x=bottle_diameter, + resource_size_y=bottle_diameter, + name_prefix=name, + ), + model="BIOYOND_PolymerStation_6VialCarrier", + ) + carrier.num_items_x = 3 + carrier.num_items_y = 2 + carrier.num_items_z = 1 + ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 + for i in range(6): + carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_vial_{ordering[i]}") + return carrier + + +def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier: + """1瓶载架 - 单个中央位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 20.0 + + # 烧杯尺寸 + beaker_diameter = 60.0 + + # 计算中央位置 + center_x = (carrier_size_x - beaker_diameter) / 2 + center_y = (carrier_size_y - beaker_diameter) / 2 + center_z = 5.0 + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(center_x, center_y, center_z)], + resource_size_x=beaker_diameter, + resource_size_y=beaker_diameter, + name_prefix=name, + ), + model="BIOYOND_PolymerStation_1BottleCarrier", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_flask_1") + return carrier + + +def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier: + """1瓶载架 - 单个中央位置""" + + # 载架尺寸 (mm) + carrier_size_x = 127.8 + carrier_size_y = 85.5 + carrier_size_z = 20.0 + + # 烧杯尺寸 + beaker_diameter = 70.0 + + # 计算中央位置 + center_x = (carrier_size_x - beaker_diameter) / 2 + center_y = (carrier_size_y - beaker_diameter) / 2 + center_z = 5.0 + + carrier = BottleCarrier( + name=name, + size_x=carrier_size_x, + size_y=carrier_size_y, + size_z=carrier_size_z, + sites=create_homogeneous_resources( + klass=ResourceHolder, + locations=[Coordinate(center_x, center_y, center_z)], + resource_size_x=beaker_diameter, + resource_size_y=beaker_diameter, + name_prefix=name, + ), + model="BIOYOND_PolymerStation_1FlaskCarrier", + ) + carrier.num_items_x = 1 + carrier.num_items_y = 1 + carrier.num_items_z = 1 + carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_bottle_1") + return carrier diff --git a/unilabos/resources/bioyond/bottle.py b/unilabos/resources/bioyond/bottles.py similarity index 53% rename from unilabos/resources/bioyond/bottle.py rename to unilabos/resources/bioyond/bottles.py index f642ca2e..b3725de9 100644 --- a/unilabos/resources/bioyond/bottle.py +++ b/unilabos/resources/bioyond/bottles.py @@ -2,11 +2,12 @@ from unilabos.resources.bottle_carrier import Bottle, BottleCarrier # 工厂函数 -def create_powder_bottle( +def BIOYOND_PolymerStation_Solid_Vial( name: str, - diameter: float = 30.0, - height: float = 50.0, - max_volume: float = 50000.0, # 50mL + diameter: float = 20.0, + height: float = 100.0, + max_volume: float = 30000.0, # 30mL + barcode: str = None, ) -> Bottle: """创建粉末瓶""" return Bottle( @@ -14,15 +15,17 @@ def create_powder_bottle( diameter=diameter, height=height, max_volume=max_volume, - category="powder_bottle", + barcode=barcode, + model="BIOYOND_PolymerStation_Solid_Vial", ) -def create_solution_beaker( +def BIOYOND_PolymerStation_Solution_Beaker( name: str, - diameter: float = 80.0, - height: float = 100.0, - max_volume: float = 500000.0, # 500mL + diameter: float = 60.0, + height: float = 70.0, + max_volume: float = 200000.0, # 200mL + barcode: str = None, ) -> Bottle: """创建溶液烧杯""" return Bottle( @@ -30,15 +33,17 @@ def create_solution_beaker( diameter=diameter, height=height, max_volume=max_volume, - category="solution_beaker", + barcode=barcode, + model="BIOYOND_PolymerStation_Solution_Beaker", ) -def create_reagent_bottle( +def BIOYOND_PolymerStation_Reagent_Bottle( name: str, - diameter: float = 20.0, - height: float = 40.0, - max_volume: float = 15000.0, # 15mL + diameter: float = 70.0, + height: float = 120.0, + max_volume: float = 500000.0, # 500mL + barcode: str = None, ) -> Bottle: """创建试剂瓶""" return Bottle( @@ -46,5 +51,6 @@ def create_reagent_bottle( diameter=diameter, height=height, max_volume=max_volume, - category="reagent_bottle", - ) \ No newline at end of file + barcode=barcode, + model="BIOYOND_PolymerStation_Reagent_Bottle", + ) diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py new file mode 100644 index 00000000..56f556ca --- /dev/null +++ b/unilabos/resources/bioyond/decks.py @@ -0,0 +1,48 @@ +from pylabrobot.resources import Deck, Coordinate + +from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling + + +class BIOYOND_PolymerReactionStation_Deck(Deck): + def __init__(self, name: str = "PolymerReactionStation_Deck") -> None: + super().__init__(name=name, size_x=2700.0, size_y=1080.0, size_z=1500.0) + + def setup(self) -> None: + # 添加仓库 + self.warehouses = { + "io_warehouse_left": bioyond_warehouse_1x4x4("io_warehouse_left"), + "io_warehouse_right": bioyond_warehouse_1x4x4("io_warehouse_right"), + "liquid_and_lid_handling": bioyond_warehouse_liquid_and_lid_handling("liquid_and_lid_handling"), + } + self.warehouse_locations = { + "io_warehouse_left": Coordinate(0.0, 650.0, 0.0), + "io_warehouse_right": Coordinate(2550.0, 650.0, 0.0), + "liquid_and_lid_handling": Coordinate(800.0, 475.0, 0.0), + } + + for warehouse_name, warehouse in self.warehouses.items(): + self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) + + +class BIOYOND_PolymerPreparationStation_Deck(Deck): + def __init__(self, name: str = "PolymerPreparationStation_Deck") -> None: + super().__init__(name=name, size_x=2700.0, size_y=1080.0, size_z=1500.0) + self.warehouses = {} + + def setup(self) -> None: + # 添加仓库 + self.warehouses = { + "io_warehouse_left": bioyond_warehouse_1x4x4("io_warehouse_left"), + "io_warehouse_right": bioyond_warehouse_1x4x4("io_warehouse_right"), + "solutions": bioyond_warehouse_1x4x2("warehouse_solutions"), + "liquid_and_lid_handling": bioyond_warehouse_liquid_and_lid_handling("warehouse_liquid_and_lid_handling"), + } + self.warehouse_locations = { + "io_warehouse_left": Coordinate(0.0, 650.0, 0.0), + "io_warehouse_right": Coordinate(2550.0, 650.0, 0.0), + "solutions": Coordinate(1915.0, 900.0, 0.0), + "liquid_and_lid_handling": Coordinate(1330.0, 490.0, 0.0), + } + + for warehouse_name, warehouse in self.warehouses.items(): + self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) diff --git a/unilabos/resources/bioyond/warehouse.py b/unilabos/resources/bioyond/warehouses.py similarity index 90% rename from unilabos/resources/bioyond/warehouse.py rename to unilabos/resources/bioyond/warehouses.py index c468f875..d075e559 100644 --- a/unilabos/resources/bioyond/warehouse.py +++ b/unilabos/resources/bioyond/warehouses.py @@ -18,12 +18,12 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse: ) -def bioyond_warehouse_1x3x2(name: str) -> WareHouse: - """创建BioYond 3x1x2仓库""" +def bioyond_warehouse_1x4x2(name: str) -> WareHouse: + """创建BioYond 4x1x2仓库""" return WareHouse( name=name, num_items_x=1, - num_items_y=3, + num_items_y=4, num_items_z=2, dx=137.0, dy=96.0, diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index 093aa7d5..c56abc56 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -480,7 +480,43 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w return r -def initialize_resource(resource_config: dict) -> list[dict]: +def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict = {}, location_id_mapping: dict = None) -> list[dict]: + """ + 将 bioyond 物料格式转换为 ulab 物料格式 + + Args: + bioyond_materials: bioyond 系统的物料查询结果列表 + type_mapping: 物料类型映射字典,格式 {bioyond_type: plr_class_name} + location_id_mapping: 库位 ID 到名称的映射字典,格式 {location_id: location_name} + + Returns: + pylabrobot 格式的物料列表 + """ + plr_materials = [] + + for material in bioyond_materials: + className = type_mapping.get(material.get("typeName"), "RegularContainer") if type_mapping else "RegularContainer" + + plr_material: ResourcePLR = initialize_resource({"name": material["name"], "class": className}, resource_type=ResourcePLR) + plr_material.code = material.get("code", "") and material.get("barCode", "") or "" + + # 处理子物料(detail) + if material.get("detail") and len(material["detail"]) > 0: + child_ids = [] + for detail in material["detail"]: + number = (detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y + \ + (detail.get("x", 0) - 1) * plr_material.num_items_x + \ + (detail.get("y", 0) - 1) + bottle = plr_material[number].resource + bottle.code = detail.get("code", "") + bottle.tracker.liquids = [(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)] + + plr_materials.append(plr_material) + + return plr_materials + + +def initialize_resource(resource_config: dict, resource_type: Any = None) -> Union[list[dict], ResourcePLR]: """Initializes a resource based on its configuration. If the config is detailed, then do nothing; @@ -512,11 +548,14 @@ def initialize_resource(resource_config: dict) -> list[dict]: if resource_class_config["type"] == "pylabrobot": resource_plr = RESOURCE(name=resource_config["name"]) - r = resource_plr_to_ulab(resource_plr=resource_plr, parent_name=resource_config.get("parent", None)) - # r = resource_plr_to_ulab(resource_plr=resource_plr) - if resource_config.get("position") is not None: - r["position"] = resource_config["position"] - r = tree_to_list([r]) + if resource_type != ResourcePLR: + r = resource_plr_to_ulab(resource_plr=resource_plr, parent_name=resource_config.get("parent", None)) + # r = resource_plr_to_ulab(resource_plr=resource_plr) + if resource_config.get("position") is not None: + r["position"] = resource_config["position"] + r = tree_to_list([r]) + else: + r = resource_plr elif resource_class_config["type"] == "unilabos": res_instance: RegularContainer = RESOURCE(id=resource_config["name"]) res_instance.ulr_resource = convert_to_ros_msg(Resource, {k:v for k,v in resource_config.items() if k != "class"}) diff --git a/unilabos/resources/warehouse.py b/unilabos/resources/warehouse.py index 5561b94e..11b945ed 100644 --- a/unilabos/resources/warehouse.py +++ b/unilabos/resources/warehouse.py @@ -24,6 +24,9 @@ class WareHouse(Carrier[ResourceHolder]): category: str = "warehouse", model: Optional[str] = None, ): + self.num_items_x = num_items_x + self.num_items_y = num_items_y + self.num_items_z = num_items_z # 创建16个板架位 (4层 x 4位置) locations = []