mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
refactor and add BIOYOND resources tests
This commit is contained in:
198
test/resources/bioyond_materials.json
Normal file
198
test/resources/bioyond_materials.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from unilabos.resources.bioyond.bottle_carrier import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier
|
from unilabos.resources.bioyond.bottle_carriers 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.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -17,9 +17,9 @@ def bottle_carrier() -> "BottleCarrier":
|
|||||||
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
||||||
|
|
||||||
# 创建瓶子和烧杯
|
# 创建瓶子和烧杯
|
||||||
powder_bottle = create_powder_bottle("powder_bottle_01")
|
powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01")
|
||||||
solution_beaker = create_solution_beaker("solution_beaker_01")
|
solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01")
|
||||||
reagent_bottle = create_reagent_bottle("reagent_bottle_01")
|
reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01")
|
||||||
|
|
||||||
print(f"\n创建的物料:")
|
print(f"\n创建的物料:")
|
||||||
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")
|
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")
|
||||||
31
test/resources/test_converter_bioyond.py
Normal file
31
test/resources/test_converter_bioyond.py
Normal file
@@ -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])
|
||||||
38
unilabos/registry/resources/bioyond/bottle_carriers.yaml
Normal file
38
unilabos/registry/resources/bioyond/bottle_carriers.yaml
Normal file
@@ -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
|
||||||
@@ -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",
|
|
||||||
)
|
|
||||||
213
unilabos/resources/bioyond/bottle_carriers.py
Normal file
213
unilabos/resources/bioyond/bottle_carriers.py
Normal file
@@ -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
|
||||||
@@ -2,11 +2,12 @@ from unilabos.resources.bottle_carrier import Bottle, BottleCarrier
|
|||||||
# 工厂函数
|
# 工厂函数
|
||||||
|
|
||||||
|
|
||||||
def create_powder_bottle(
|
def BIOYOND_PolymerStation_Solid_Vial(
|
||||||
name: str,
|
name: str,
|
||||||
diameter: float = 30.0,
|
diameter: float = 20.0,
|
||||||
height: float = 50.0,
|
height: float = 100.0,
|
||||||
max_volume: float = 50000.0, # 50mL
|
max_volume: float = 30000.0, # 30mL
|
||||||
|
barcode: str = None,
|
||||||
) -> Bottle:
|
) -> Bottle:
|
||||||
"""创建粉末瓶"""
|
"""创建粉末瓶"""
|
||||||
return Bottle(
|
return Bottle(
|
||||||
@@ -14,15 +15,17 @@ def create_powder_bottle(
|
|||||||
diameter=diameter,
|
diameter=diameter,
|
||||||
height=height,
|
height=height,
|
||||||
max_volume=max_volume,
|
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,
|
name: str,
|
||||||
diameter: float = 80.0,
|
diameter: float = 60.0,
|
||||||
height: float = 100.0,
|
height: float = 70.0,
|
||||||
max_volume: float = 500000.0, # 500mL
|
max_volume: float = 200000.0, # 200mL
|
||||||
|
barcode: str = None,
|
||||||
) -> Bottle:
|
) -> Bottle:
|
||||||
"""创建溶液烧杯"""
|
"""创建溶液烧杯"""
|
||||||
return Bottle(
|
return Bottle(
|
||||||
@@ -30,15 +33,17 @@ def create_solution_beaker(
|
|||||||
diameter=diameter,
|
diameter=diameter,
|
||||||
height=height,
|
height=height,
|
||||||
max_volume=max_volume,
|
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,
|
name: str,
|
||||||
diameter: float = 20.0,
|
diameter: float = 70.0,
|
||||||
height: float = 40.0,
|
height: float = 120.0,
|
||||||
max_volume: float = 15000.0, # 15mL
|
max_volume: float = 500000.0, # 500mL
|
||||||
|
barcode: str = None,
|
||||||
) -> Bottle:
|
) -> Bottle:
|
||||||
"""创建试剂瓶"""
|
"""创建试剂瓶"""
|
||||||
return Bottle(
|
return Bottle(
|
||||||
@@ -46,5 +51,6 @@ def create_reagent_bottle(
|
|||||||
diameter=diameter,
|
diameter=diameter,
|
||||||
height=height,
|
height=height,
|
||||||
max_volume=max_volume,
|
max_volume=max_volume,
|
||||||
category="reagent_bottle",
|
barcode=barcode,
|
||||||
|
model="BIOYOND_PolymerStation_Reagent_Bottle",
|
||||||
)
|
)
|
||||||
48
unilabos/resources/bioyond/decks.py
Normal file
48
unilabos/resources/bioyond/decks.py
Normal file
@@ -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])
|
||||||
@@ -18,12 +18,12 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def bioyond_warehouse_1x3x2(name: str) -> WareHouse:
|
def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||||
"""创建BioYond 3x1x2仓库"""
|
"""创建BioYond 4x1x2仓库"""
|
||||||
return WareHouse(
|
return WareHouse(
|
||||||
name=name,
|
name=name,
|
||||||
num_items_x=1,
|
num_items_x=1,
|
||||||
num_items_y=3,
|
num_items_y=4,
|
||||||
num_items_z=2,
|
num_items_z=2,
|
||||||
dx=137.0,
|
dx=137.0,
|
||||||
dy=96.0,
|
dy=96.0,
|
||||||
@@ -480,7 +480,43 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w
|
|||||||
return r
|
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.
|
"""Initializes a resource based on its configuration.
|
||||||
|
|
||||||
If the config is detailed, then do nothing;
|
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":
|
if resource_class_config["type"] == "pylabrobot":
|
||||||
resource_plr = RESOURCE(name=resource_config["name"])
|
resource_plr = RESOURCE(name=resource_config["name"])
|
||||||
|
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, parent_name=resource_config.get("parent", None))
|
||||||
# r = resource_plr_to_ulab(resource_plr=resource_plr)
|
# r = resource_plr_to_ulab(resource_plr=resource_plr)
|
||||||
if resource_config.get("position") is not None:
|
if resource_config.get("position") is not None:
|
||||||
r["position"] = resource_config["position"]
|
r["position"] = resource_config["position"]
|
||||||
r = tree_to_list([r])
|
r = tree_to_list([r])
|
||||||
|
else:
|
||||||
|
r = resource_plr
|
||||||
elif resource_class_config["type"] == "unilabos":
|
elif resource_class_config["type"] == "unilabos":
|
||||||
res_instance: RegularContainer = RESOURCE(id=resource_config["name"])
|
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"})
|
res_instance.ulr_resource = convert_to_ros_msg(Resource, {k:v for k,v in resource_config.items() if k != "class"})
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ class WareHouse(Carrier[ResourceHolder]):
|
|||||||
category: str = "warehouse",
|
category: str = "warehouse",
|
||||||
model: Optional[str] = None,
|
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位置)
|
# 创建16个板架位 (4层 x 4位置)
|
||||||
locations = []
|
locations = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user