Files
Uni-Lab-OS/unilabos/devices/liquid_handling/prcxi/prcxi_labware.py
2026-03-31 14:33:50 +08:00

939 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Any, Callable, Dict, List, Optional, Tuple
from pylabrobot.resources import Tube, Coordinate
from pylabrobot.resources.well import Well, WellBottomType, CrossSectionType
from pylabrobot.resources.tip import Tip, TipCreator
from pylabrobot.resources.tip_rack import TipRack, TipSpot
from pylabrobot.resources.utils import create_ordered_items_2d
from pylabrobot.resources.height_volume_functions import (
compute_height_from_volume_rectangle,
compute_volume_from_height_rectangle,
)
from .prcxi import PRCXI9300Plate, PRCXI9300TipRack, PRCXI9300Trash, PRCXI9300TubeRack, PRCXI9300PlateAdapter
def _make_tip_helper(volume: float, length: float, depth: float) -> Tip:
"""
PLR 的 Tip 类参数名为: maximal_volume, total_tip_length, fitting_depth
"""
return Tip(
has_filter=False, # 默认无滤芯
maximal_volume=volume,
total_tip_length=length,
fitting_depth=depth
)
# =========================================================================
# 标准品 参照 PLR 标准库的参数,但是用 PRCXI9300Plate 实例化,并注入 UUID
# =========================================================================
def PRCXI_BioER_96_wellplate(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: ZX-019-2.2 (2.2ml 深孔板)
原型: pylabrobot.resources.bioer.BioER_96_wellplate_Vb_2200uL
"""
return PRCXI9300Plate(
name=name,
size_x=127.1,
size_y=85.0,
size_z=44.2,
lid=None,
model="PRCXI_BioER_96_wellplate",
category="plate",
material_info={
"uuid": "ca877b8b114a4310b429d1de4aae96ee",
"Code": "ZX-019-2.2",
"Name": "2.2ml 深孔板",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
size_x=8.25,
size_y=8.25,
size_z=39.3, # 修改过
dx=9.5,
dy=7.5,
dz=6,
material_z_thickness=0.8,
item_dx=9.0,
item_dy=9.0,
num_items_x=12,
num_items_y=8,
cross_section_type=CrossSectionType.RECTANGLE,
bottom_type=WellBottomType.V, # 是否需要修改?
max_volume=2200,
),
)
def PRCXI_nest_1_troughplate(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: ZX-58-10000 (储液槽)
原型: pylabrobot.resources.nest.nest_1_troughplate_195000uL_Vb
"""
well_size_x = 127.76 - (14.38 - 9 / 2) * 2
well_size_y = 85.48 - (11.24 - 9 / 2) * 2
well_kwargs = {
"size_x": well_size_x,
"size_y": well_size_y,
"size_z": 26.85,
"bottom_type": WellBottomType.V,
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
liquid_volume=liquid_volume, well_length=well_size_x, well_width=well_size_y
),
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
liquid_height=liquid_height, well_length=well_size_x, well_width=well_size_y
),
"material_z_thickness": 31.4 - 26.85 - 3.55,
}
return PRCXI9300Plate(
name=name,
size_x=127.76,
size_y=85.48,
size_z=31.4,
lid=None,
model="PRCXI_Nest_1_troughplate",
category="plate",
material_info={
"uuid": "04211a2dc93547fe9bf6121eac533650",
"Code": "ZX-58-10000",
"Name": "储液槽",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=1,
num_items_y=1,
dx=14.38 - 9 / 2,
dy=11.24 - 9 / 2,
dz=3.55,
item_dx=9.0,
item_dy=9.0,
**well_kwargs, # 传入上面计算好的孔参数
),
)
def PRCXI_BioRad_384_wellplate(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: q3 (384板)
原型: pylabrobot.resources.biorad.BioRad_384_wellplate_50uL_Vb
"""
return PRCXI9300Plate(
name=name,
# 直接抄录 PLR 标准品的物理尺寸
size_x=127.76,
size_y=85.48,
size_z=10.40,
model="BioRad_384_wellplate_50uL_Vb",
category="plate",
# 2. 注入 Unilab 必须的 UUID 信息
material_info={
"uuid": "853dcfb6226f476e8b23c250217dc7da",
"Code": "q3",
"Name": "384板",
"SupplyType": 1,
},
# 3. 定义孔的排列 (抄录标准参数)
ordered_items=create_ordered_items_2d(
Well,
num_items_x=24,
num_items_y=16,
dx=10.58, # A1 左边缘距离板子左边缘 需要进一步测量
dy=7.44, # P1 下边缘距离板子下边缘 需要进一步测量
dz=1.05,
item_dx=4.5,
item_dy=4.5,
size_x=3.10,
size_y=3.10,
size_z=9.35,
max_volume=50,
material_z_thickness=1,
bottom_type=WellBottomType.V,
cross_section_type=CrossSectionType.CIRCLE,
)
)
def PRCXI_AGenBio_4_troughplate(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: sdfrth654 (4道储液槽)
原型: pylabrobot.resources.agenbio.AGenBio_4_troughplate_75000uL_Vb
"""
INNER_WELL_WIDTH = 26.1
INNER_WELL_LENGTH = 71.2
well_kwargs = {
"size_x": 26,
"size_y": 71.2,
"size_z": 42.55,
"bottom_type": WellBottomType.FLAT,
"cross_section_type": CrossSectionType.RECTANGLE,
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
liquid_volume,
INNER_WELL_LENGTH,
INNER_WELL_WIDTH,
),
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
liquid_height,
INNER_WELL_LENGTH,
INNER_WELL_WIDTH,
),
"material_z_thickness": 1,
}
return PRCXI9300Plate(
name=name,
size_x=127.76,
size_y=85.48,
size_z=43.80,
model="PRCXI_AGenBio_4_troughplate",
category="plate",
material_info={
"uuid": "01953864f6f140ccaa8ddffd4f3e46f5",
"Code": "sdfrth654",
"Name": "4道储液槽",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=4,
num_items_y=1,
dx=9.8,
dy=7.2,
dz=0.9,
item_dx=INNER_WELL_WIDTH + 1, # 1 mm wall thickness
item_dy=INNER_WELL_LENGTH,
**well_kwargs,
),
)
def PRCXI_nest_12_troughplate(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: 12道储液槽 (12道储液槽)
原型: pylabrobot.resources.nest.nest_12_troughplate_15000uL_Vb
"""
well_size_x = 8.2
well_size_y = 71.2
well_kwargs = {
"size_x": well_size_x,
"size_y": well_size_y,
"size_z": 26.85,
"bottom_type": WellBottomType.V,
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
liquid_volume=liquid_volume, well_length=well_size_x, well_width=well_size_y
),
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
liquid_height=liquid_height, well_length=well_size_x, well_width=well_size_y
),
"material_z_thickness": 31.4 - 26.85 - 3.55,
}
return PRCXI9300Plate(
name=name,
size_x=127.76,
size_y=85.48,
size_z=31.4,
lid=None,
model="PRCXI_nest_12_troughplate",
category="plate",
material_info={
"uuid": "0f1639987b154e1fac78f4fb29a1f7c1",
"Code": "12道储液槽",
"Name": "12道储液槽",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=12,
num_items_y=1,
dx=14.38 - 8.2 / 2,
dy=(85.48 - 71.2) / 2,
dz=3.55,
item_dx=9.0,
item_dy=9.0,
**well_kwargs,
),
)
def PRCXI_CellTreat_96_wellplate(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: ZX-78-096 (细菌培养皿)
原型: pylabrobot.resources.celltreat.CellTreat_96_wellplate_350ul_Fb
"""
well_kwargs = {
"size_x": 6.96,
"size_y": 6.96,
"size_z": 10.04,
"bottom_type": WellBottomType.FLAT,
"material_z_thickness": 1.75,
"cross_section_type": CrossSectionType.CIRCLE,
"max_volume": 300,
}
return PRCXI9300Plate(
name=name,
size_x=127.61,
size_y=85.24,
size_z=14.30,
lid=None,
model="PRCXI_CellTreat_96_wellplate",
category="plate",
material_info={
"uuid": "b05b3b2aafd94ec38ea0cd3215ecea8f",
"Code": "ZX-78-096",
"Name": "细菌培养皿",
"materialEnum": 4,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=12,
num_items_y=8,
dx=10.83,
dy=7.67,
dz=4.05,
item_dx=9,
item_dy=9,
**well_kwargs,
),
)
# =========================================================================
# 自定义/需测量品 (Custom Measurement)
# =========================================================================
def PRCXI_10ul_eTips(name: str) -> PRCXI9300TipRack:
"""
对应 JSON Code: ZX-001-10+
"""
return PRCXI9300TipRack(
name=name,
size_x=122.11,
size_y=85.48, #修改
size_z=58.23,
model="PRCXI_10ul_eTips",
material_info={
"uuid": "068b3815e36b4a72a59bae017011b29f",
"Code": "ZX-001-10+",
"Name": "10μL加长 Tip头",
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
TipSpot,
num_items_x=12,
num_items_y=8,
dx=7.97, #需要修改
dy=5.0, #需修改
dz=2.0, #需修改
item_dx=9.0,
item_dy=9.0,
size_x=7.0,
size_y=7.0,
size_z=0,
make_tip=lambda: _make_tip_helper(volume=10, length=52.0, depth=45.1)
)
)
def PRCXI_300ul_Tips(name: str) -> PRCXI9300TipRack:
"""
对应 JSON Code: ZX-001-300
吸头盒通常比较特殊,需要定义 Tip 对象
"""
return PRCXI9300TipRack(
name=name,
size_x=122.11,
size_y=85.48, #修改
size_z=58.23,
model="PRCXI_300ul_Tips",
material_info={
"uuid": "076250742950465b9d6ea29a225dfb00",
"Code": "ZX-001-300",
"Name": "300μL Tip头",
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
TipSpot,
num_items_x=12,
num_items_y=8,
dx=7.97, #需要修改
dy=5.0, #需修改
dz=2.0, #需修改
item_dx=9.0,
item_dy=9.0,
size_x=7.0,
size_y=7.0,
size_z=0,
make_tip=lambda: _make_tip_helper(volume=300, length=60.0, depth=51.0)
)
)
def PRCXI_PCR_Plate_200uL_nonskirted(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
"""
return PRCXI9300Plate(
name=name,
size_x=119.5,
size_y=80.0,
size_z=26.0,
model="PRCXI_PCR_Plate_200uL_nonskirted",
plate_type="non-skirted",
category="plate",
material_info={
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
"Code": "ZX-023-0.2",
"Name": "0.2ml PCR 板",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=12,
num_items_y=8,
dx=7,
dy=5,
dz=0.0,
item_dx=9,
item_dy=9,
size_x=6,
size_y=6,
size_z=15.17,
bottom_type=WellBottomType.V,
cross_section_type=CrossSectionType.CIRCLE,
),
)
def PRCXI_PCR_Plate_200uL_semiskirted(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
"""
return PRCXI9300Plate(
name=name,
size_x=126,
size_y=86,
size_z=21.2,
model="PRCXI_PCR_Plate_200uL_semiskirted",
plate_type="semi-skirted",
category="plate",
material_info={
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
"Code": "ZX-023-0.2",
"Name": "0.2ml PCR 板",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=12,
num_items_y=8,
dx=11,
dy=8,
dz=0.0,
item_dx=9,
item_dy=9,
size_x=6,
size_y=6,
size_z=15.17,
bottom_type=WellBottomType.V,
cross_section_type=CrossSectionType.CIRCLE,
),
)
def PRCXI_PCR_Plate_200uL_skirted(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
"""
return PRCXI9300Plate(
name=name,
size_x=127.76,
size_y=86,
size_z=16.1,
model="PRCXI_PCR_Plate_200uL_skirted",
plate_type="skirted",
category="plate",
material_info={
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
"Code": "ZX-023-0.2",
"Name": "0.2ml PCR 板",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=12,
num_items_y=8,
dx=11,
dy=8.49,
dz=0.8,
item_dx=9,
item_dy=9,
size_x=6,
size_y=6,
size_z=15.1,
bottom_type=WellBottomType.V,
cross_section_type=CrossSectionType.CIRCLE,
),
)
def PRCXI_trash(name: str = "trash") -> PRCXI9300Trash:
"""
对应 JSON Code: q1 (废弃槽)
"""
return PRCXI9300Trash(
name="trash",
size_x=126.59,
size_y=84.87,
size_z=89.5, # 修改
category="trash",
model="PRCXI_trash",
material_info={
"uuid": "730067cf07ae43849ddf4034299030e9",
"Code": "q1",
"Name": "废弃槽",
"materialEnum": 0,
"SupplyType": 1
}
)
def PRCXI_96_DeepWell(name: str) -> PRCXI9300Plate:
"""
对应 JSON Code: q2 (96深孔板)
"""
return PRCXI9300Plate(
name=name,
size_x=127.3,
size_y=85.35,
size_z=45.0, #修改
model="PRCXI_96_DeepWell",
material_info={
"uuid": "57b1e4711e9e4a32b529f3132fc5931f", # 对应 q2 uuid
"Code": "q2",
"Name": "96深孔板",
"materialEnum": 0
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=12,
num_items_y=8,
dx=10.9,
dy=8.25,
dz=2.0,
item_dx=9.0,
item_dy=9.0,
size_x=8.2,
size_y=8.2,
size_z=42.0,
max_volume=2200
)
)
def PRCXI_EP_Adapter(name: str) -> PRCXI9300TubeRack:
"""
对应 JSON Code: 1 (ep适配器)
这是一个 4x6 的 EP 管架,适配 1.5mL/2.0mL 离心管
"""
ep_tube_prototype = Tube(
name="EP_Tube_1.5mL",
size_x=10.6,
size_y=10.6,
size_z=40.0, # 管子本身的高度,通常比架子孔略高或持平
max_volume=1500,
model="EP_Tube_1.5mL"
)
# 计算 PRCXI9300TubeRack 中孔的起始位置 dx, dy
dy_calc = 85.8 - 10.5 - (3 * 18) - 10.6
dx_calc = 3.54
return PRCXI9300TubeRack(
name=name,
size_x=128.04,
size_y=85.8,
size_z=42.66,
model="PRCXI_EP_Adapter",
category="tube_rack",
material_info={
"uuid": "e146697c395e4eabb3d6b74f0dd6aaf7",
"Code": "1",
"Name": "ep适配器",
"materialEnum": 0,
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Tube,
num_items_x=6,
num_items_y=4,
dx=dx_calc,
dy=dy_calc,
dz=42.66 - 38.08, # 架高 - 孔深
item_dx=21.0,
item_dy=18.0,
size_x=10.6,
size_y=10.6,
size_z=40.0,
max_volume=1500
)
)
# =========================================================================
# 无实物,需要测量
# =========================================================================
def PRCXI_Tip1250_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: ZX-58-1250 """
return PRCXI9300PlateAdapter(
name=name,
size_x=128,
size_y=85,
size_z=20,
material_info={
"uuid": "3b6f33ffbf734014bcc20e3c63e124d4",
"Code": "ZX-58-1250",
"Name": "Tip头适配器 1250uL",
"SupplyType": 2
}
)
def PRCXI_Tip300_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: ZX-58-300 """
return PRCXI9300PlateAdapter(
name=name,
size_x=127,
size_y=85,
size_z=81,
material_info={
"uuid": "7c822592b360451fb59690e49ac6b181",
"Code": "ZX-58-300",
"Name": "ZHONGXI 适配器 300uL",
"SupplyType": 2
}
)
def PRCXI_Tip10_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: ZX-58-10 """
return PRCXI9300PlateAdapter(
name=name,
size_x=128,
size_y=85,
size_z=72.3,
material_info={
"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c",
"Code": "ZX-58-10",
"Name": "吸头10ul 适配器",
"SupplyType": 2
}
)
def PRCXI_1250uL_Tips(name: str) -> PRCXI9300TipRack:
""" Code: ZX-001-1250 """
return PRCXI9300TipRack(
name=name,
size_x=118.09,
size_y=80.7,
size_z=107.67,
model="PRCXI_1250uL_Tips",
material_info={
"uuid": "7960f49ddfe9448abadda89bd1556936",
"Code": "ZX-001-1250",
"Name": "1250μL Tip头",
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
TipSpot,
num_items_x=12,
num_items_y=8,
dx=9.545 - 7.95/2,
dy=8.85 - 7.95/2,
dz=2.0,
item_dx=9,
item_dy=9,
size_x=7.0,
size_y=7.0,
size_z=0,
make_tip=lambda: _make_tip_helper(volume=1250, length=107.67, depth=8)
)
)
def PRCXI_10uL_Tips(name: str) -> PRCXI9300TipRack:
""" Code: ZX-001-10 """
return PRCXI9300TipRack(
name=name,
size_x=120.98,
size_y=82.12,
size_z=67,
model="PRCXI_10uL_Tips",
material_info={
"uuid": "45f2ed3ad925484d96463d675a0ebf66",
"Code": "ZX-001-10",
"Name": "10μL Tip头",
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
TipSpot,
num_items_x=12,
num_items_y=8,
dx=10.99 - 5/2,
dy=9.56 - 5/2,
dz=2.0,
item_dx=9,
item_dy=9,
size_x=7.0,
size_y=7.0,
size_z=0,
make_tip=lambda: _make_tip_helper(volume=1250, length=52.0, depth=5)
)
)
def PRCXI_1000uL_Tips(name: str) -> PRCXI9300TipRack:
""" Code: ZX-001-1000 """
return PRCXI9300TipRack(
name=name,
size_x=128.09,
size_y=85.8,
size_z=98,
model="PRCXI_1000uL_Tips",
material_info={
"uuid": "80652665f6a54402b2408d50b40398df",
"Code": "ZX-001-1000",
"Name": "1000μL Tip头",
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
TipSpot,
num_items_x=12,
num_items_y=8,
dx=14.5 - 7.95/2,
dy=7.425,
dz=2.0,
item_dx=9,
item_dy=9,
size_x=7.0,
size_y=7.0,
size_z=0,
make_tip=lambda: _make_tip_helper(volume=1000, length=55.0, depth=8)
)
)
def PRCXI_200uL_Tips(name: str) -> PRCXI9300TipRack:
""" Code: ZX-001-200 """
return PRCXI9300TipRack(
name=name,
size_x=120.98,
size_y=82.12,
size_z=66.9,
model="PRCXI_200uL_Tips",
material_info={
"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7",
"Code": "ZX-001-200",
"Name": "200μL Tip头",
"SupplyType": 1},
ordered_items=create_ordered_items_2d(
TipSpot,
num_items_x=12,
num_items_y=8,
dx=10.99 - 5.5/2,
dy=9.56 - 5.5/2,
dz=2.0,
item_dx=9,
item_dy=9,
size_x=7.0,
size_z=0,
size_y=7.0,
make_tip=lambda: _make_tip_helper(volume=200, length=52.0, depth=5)
)
)
def PRCXI_PCR_Adapter(name: str) -> PRCXI9300PlateAdapter:
"""
对应 JSON Code: ZX-58-0001 (全裙边 PCR适配器)
"""
return PRCXI9300PlateAdapter(
name=name,
size_x=127.76,
size_y=85.48,
size_z=21.69,
model="PRCXI_PCR_Adapter",
material_info={
"uuid": "4a043a07c65a4f9bb97745e1f129b165",
"Code": "ZX-58-0001",
"Name": "全裙边 PCR适配器",
"materialEnum": 3,
"SupplyType": 2
}
)
def PRCXI_Reservoir_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: ZX-ADP-001 """
return PRCXI9300PlateAdapter(
name=name,
size_x=133,
size_y=91.8,
size_z=70,
material_info={
"uuid": "6bdfdd7069df453896b0806df50f2f4d",
"Code": "ZX-ADP-001",
"Name": "储液槽 适配器",
"SupplyType": 2
}
)
def PRCXI_Deep300_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: ZX-002-300 """
return PRCXI9300PlateAdapter(
name=name,
size_x=136.4,
size_y=93.8,
size_z=96,
material_info={
"uuid": "9a439bed8f3344549643d6b3bc5a5eb4",
"Code": "ZX-002-300",
"Name": "300ul深孔板适配器",
"SupplyType": 2
}
)
def PRCXI_Deep10_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: ZX-002-10 """
return PRCXI9300PlateAdapter(
name=name,
size_x=136.5,
size_y=93.8,
size_z=121.5,
material_info={
"uuid": "4dc8d6ecfd0449549683b8ef815a861b",
"Code": "ZX-002-10",
"Name": "10ul专用深孔板适配器",
"SupplyType": 2
}
)
def PRCXI_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: Fhh478 """
return PRCXI9300PlateAdapter(
name=name,
size_x=120,
size_y=90,
size_z=86,
material_info={
"uuid": "adfabfffa8f24af5abfbba67b8d0f973",
"Code": "Fhh478",
"Name": "适配器",
"SupplyType": 2
}
)
def PRCXI_48_DeepWell(name: str) -> PRCXI9300Plate:
""" Code: 22 (48孔深孔板) """
print("Warning: Code '22' (48孔深孔板) dimensions are null in JSON.")
return PRCXI9300Plate(
name=name,
size_x=127,
size_y=85,
size_z=44,
model="PRCXI_48_DeepWell",
material_info={
"uuid": "026c5d5cf3d94e56b4e16b7fb53a995b",
"Code": "22",
"Name": "48孔深孔板",
"SupplyType": 1
},
ordered_items=create_ordered_items_2d(
Well,
num_items_x=6,
num_items_y=8,
dx=10,
dy=10,
dz=1,
item_dx=18.5,
item_dy=9,
size_x=8,
size_y=8,
size_z=40
)
)
def PRCXI_30mm_Adapter(name: str) -> PRCXI9300PlateAdapter:
""" Code: ZX-58-30 """
return PRCXI9300PlateAdapter(
name=name,
size_x=132,
size_y=93.5,
size_z=30,
material_info={
"uuid": "a0757a90d8e44e81a68f306a608694f2",
"Code": "ZX-58-30",
"Name": "30mm适配器",
"SupplyType": 2
}
)
# ---------------------------------------------------------------------------
# 协议上传 / workflow 用:与设备端耗材字典字段对齐的模板描述(供 common 自动匹配)
# ---------------------------------------------------------------------------
_PRCXI_TEMPLATE_SPECS_CACHE: Optional[List[Dict[str, Any]]] = None
def _probe_prcxi_resource(factory: Callable[..., Any]) -> Any:
probe = "__unilab_template_probe__"
if factory.__name__ == "PRCXI_trash":
return factory()
return factory(probe)
def _first_child_capacity_for_match(resource: Any) -> float:
"""Well max_volume 或 Tip 的 maximal_volume用于与设备端 Volume 类似的打分。"""
ch = getattr(resource, "children", None) or []
if not ch:
return 0.0
c0 = ch[0]
mv = getattr(c0, "max_volume", None)
if mv is not None:
return float(mv)
tip = getattr(c0, "tip", None)
if tip is not None:
mv2 = getattr(tip, "maximal_volume", None)
if mv2 is not None:
return float(mv2)
return 0.0
# (factory, kind) — 不含各类 Adapter避免与真实板子误匹配
PRCXI_TEMPLATE_FACTORY_KINDS: List[Tuple[Callable[..., Any], str]] = [
(PRCXI_BioER_96_wellplate, "plate"),
(PRCXI_nest_1_troughplate, "plate"),
(PRCXI_BioRad_384_wellplate, "plate"),
(PRCXI_AGenBio_4_troughplate, "plate"),
(PRCXI_nest_12_troughplate, "plate"),
(PRCXI_CellTreat_96_wellplate, "plate"),
(PRCXI_10ul_eTips, "tip_rack"),
(PRCXI_300ul_Tips, "tip_rack"),
(PRCXI_PCR_Plate_200uL_nonskirted, "plate"),
(PRCXI_PCR_Plate_200uL_semiskirted, "plate"),
(PRCXI_PCR_Plate_200uL_skirted, "plate"),
(PRCXI_trash, "trash"),
(PRCXI_96_DeepWell, "plate"),
(PRCXI_EP_Adapter, "tube_rack"),
(PRCXI_1250uL_Tips, "tip_rack"),
(PRCXI_10uL_Tips, "tip_rack"),
(PRCXI_1000uL_Tips, "tip_rack"),
(PRCXI_200uL_Tips, "tip_rack"),
(PRCXI_48_DeepWell, "plate"),
]
def get_prcxi_labware_template_specs() -> List[Dict[str, Any]]:
"""返回与 ``prcxi._match_and_create_matrix`` 中耗材字段兼容的模板列表,用于按孔数+容量打分。"""
global _PRCXI_TEMPLATE_SPECS_CACHE
if _PRCXI_TEMPLATE_SPECS_CACHE is not None:
return _PRCXI_TEMPLATE_SPECS_CACHE
out: List[Dict[str, Any]] = []
for factory, kind in PRCXI_TEMPLATE_FACTORY_KINDS:
try:
r = _probe_prcxi_resource(factory)
except Exception:
continue
nx = int(getattr(r, "num_items_x", None) or 0)
ny = int(getattr(r, "num_items_y", None) or 0)
nchild = len(getattr(r, "children", []) or [])
hole_count = nx * ny if nx > 0 and ny > 0 else nchild
hole_row = ny if nx > 0 and ny > 0 else 0
hole_col = nx if nx > 0 and ny > 0 else 0
mi = getattr(r, "material_info", None) or {}
vol = _first_child_capacity_for_match(r)
menum = mi.get("materialEnum")
if menum is None and kind == "tip_rack":
menum = 1
elif menum is None and kind == "trash":
menum = 6
out.append(
{
"class_name": factory.__name__,
"kind": kind,
"materialEnum": menum,
"HoleRow": hole_row,
"HoleColum": hole_col,
"Volume": vol,
"hole_count": hole_count,
"material_uuid": mi.get("uuid"),
"material_code": mi.get("Code"),
}
)
_PRCXI_TEMPLATE_SPECS_CACHE = out
return out