mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-04-01 16:18:38 +00:00
- TipInfo 新增 tip_above_rack_length 可选字段 - 编辑器支持 tip_above 与 dz 互算,更新中文标签 - 侧视图绘制枪头露出部分并标注,俯视图/侧视图增加 dx/dy/dz 标注 - 预览增加回中按钮,详情页展示新字段 - 导入时自动计算 tip_above_rack_length - 批量更新 PRCXI 枪头物理尺寸及 registry YAML Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
810 lines
27 KiB
Python
810 lines
27 KiB
Python
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
|
||
)
|
||
|
||
|
||
# =========================================================================
|
||
# Plates
|
||
# =========================================================================
|
||
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,
|
||
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,
|
||
num_items_x=12,
|
||
num_items_y=8,
|
||
dx=9.5,
|
||
dy=7.5,
|
||
dz=6.0,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=8.25,
|
||
size_y=8.25,
|
||
size_z=39.3,
|
||
max_volume=2200.0,
|
||
material_z_thickness=0.8,
|
||
bottom_type=WellBottomType.V,
|
||
cross_section_type=CrossSectionType.RECTANGLE,
|
||
),
|
||
)
|
||
def PRCXI_nest_1_troughplate(name: str) -> PRCXI9300Plate:
|
||
"""
|
||
对应 JSON Code: ZX-58-10000 (储液槽)
|
||
原型: pylabrobot.resources.nest.nest_1_troughplate_195000uL_Vb
|
||
"""
|
||
well_size_x = 108.0
|
||
well_size_y = 72.0
|
||
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": 0.9999999999999973,
|
||
}
|
||
|
||
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=9.88,
|
||
dy=6.74,
|
||
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,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=10.4,
|
||
model="BioRad_384_wellplate_50uL_Vb",
|
||
category="plate",
|
||
material_info={"uuid": "853dcfb6226f476e8b23c250217dc7da", "Code": "q3", "Name": "384板", "SupplyType": 1},
|
||
ordered_items=create_ordered_items_2d(
|
||
Well,
|
||
num_items_x=24,
|
||
num_items_y=16,
|
||
dx=10.58,
|
||
dy=7.44,
|
||
dz=1.05,
|
||
item_dx=4.5,
|
||
item_dy=4.5,
|
||
size_x=3.1,
|
||
size_y=3.1,
|
||
size_z=9.35,
|
||
max_volume=50.0,
|
||
material_z_thickness=1.0,
|
||
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
|
||
"""
|
||
well_size_x = 26.0
|
||
well_size_y = 71.2
|
||
well_kwargs = {
|
||
"size_x": well_size_x,
|
||
"size_y": well_size_y,
|
||
"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=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": 1.0,
|
||
}
|
||
|
||
return PRCXI9300Plate(
|
||
name=name,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=43.8,
|
||
lid=None,
|
||
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=27.1,
|
||
item_dy=9.0,
|
||
**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": 0.9999999999999973,
|
||
}
|
||
|
||
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=10.28,
|
||
dy=7.14,
|
||
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
|
||
"""
|
||
return PRCXI9300Plate(
|
||
name=name,
|
||
size_x=127.61,
|
||
size_y=85.24,
|
||
size_z=14.3,
|
||
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.0,
|
||
item_dy=9.0,
|
||
size_x=6.96,
|
||
size_y=6.96,
|
||
size_z=10.04,
|
||
max_volume=300.0,
|
||
material_z_thickness=1.75,
|
||
cross_section_type=CrossSectionType.CIRCLE,
|
||
),
|
||
)
|
||
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,
|
||
plate_type="non-skirted",
|
||
model="PRCXI_PCR_Plate_200uL_nonskirted",
|
||
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.0,
|
||
dy=5.0,
|
||
dz=0.0,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=6.0,
|
||
size_y=6.0,
|
||
size_z=15.17,
|
||
max_volume=428.92164499461444,
|
||
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.0,
|
||
size_y=86.0,
|
||
size_z=21.2,
|
||
plate_type="semi-skirted",
|
||
model="PRCXI_PCR_Plate_200uL_semiskirted",
|
||
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.0,
|
||
dy=8.0,
|
||
dz=0.0,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=6.0,
|
||
size_y=6.0,
|
||
size_z=15.17,
|
||
max_volume=428.92164499461444,
|
||
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.0,
|
||
size_z=16.1,
|
||
plate_type="skirted",
|
||
model="PRCXI_PCR_Plate_200uL_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.0,
|
||
dy=8.49,
|
||
dz=0.8,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=6.0,
|
||
size_y=6.0,
|
||
size_z=15.1,
|
||
max_volume=426.94244162285287,
|
||
bottom_type=WellBottomType.V,
|
||
cross_section_type=CrossSectionType.CIRCLE,
|
||
),
|
||
)
|
||
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",
|
||
category="plate",
|
||
material_info={"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "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.0,
|
||
bottom_type=WellBottomType.UNKNOWN,
|
||
cross_section_type=CrossSectionType.CIRCLE,
|
||
),
|
||
)
|
||
def PRCXI_48_DeepWell(name: str) -> PRCXI9300Plate:
|
||
"""
|
||
Code: 22 (48孔深孔板)
|
||
"""
|
||
return PRCXI9300Plate(
|
||
name=name,
|
||
size_x=127.0,
|
||
size_y=85.0,
|
||
size_z=44.0,
|
||
model="PRCXI_48_DeepWell",
|
||
category="plate",
|
||
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.0,
|
||
dy=10.0,
|
||
dz=1.0,
|
||
item_dx=18.5,
|
||
item_dy=9.0,
|
||
size_x=8.0,
|
||
size_y=8.0,
|
||
size_z=40.0,
|
||
max_volume=2010.6192982974676,
|
||
bottom_type=WellBottomType.UNKNOWN,
|
||
cross_section_type=CrossSectionType.CIRCLE,
|
||
),
|
||
)
|
||
# =========================================================================
|
||
# Tip Racks
|
||
# =========================================================================
|
||
def PRCXI_10ul_eTips(name: str) -> PRCXI9300TipRack:
|
||
"""
|
||
对应 JSON Code: ZX-001-10+
|
||
"""
|
||
return PRCXI9300TipRack(
|
||
name=name,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=58.0,
|
||
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=10.63,
|
||
dy=7.49,
|
||
dz=14.6,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=7.5,
|
||
size_y=7.5,
|
||
size_z=0.0,
|
||
make_tip=lambda: _make_tip_helper(volume=10.0, length=52.0, depth=8.2)
|
||
)
|
||
)
|
||
def PRCXI_300ul_Tips(name: str) -> PRCXI9300TipRack:
|
||
"""
|
||
对应 JSON Code: ZX-001-300
|
||
吸头盒通常比较特殊,需要定义 Tip 对象
|
||
"""
|
||
return PRCXI9300TipRack(
|
||
name=name,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=58.0,
|
||
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=10.63,
|
||
dy=7.49,
|
||
dz=6.6,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=7.5,
|
||
size_y=7.5,
|
||
size_z=0.0,
|
||
make_tip=lambda: _make_tip_helper(volume=300.0, length=60.0, depth=8.2)
|
||
)
|
||
)
|
||
def PRCXI_1250uL_Tips(name: str) -> PRCXI9300TipRack:
|
||
"""
|
||
Code: ZX-001-1250
|
||
"""
|
||
return PRCXI9300TipRack(
|
||
name=name,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=98.0,
|
||
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=10.63,
|
||
dy=7.49,
|
||
dz=6.6,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=7.5,
|
||
size_y=7.5,
|
||
size_z=100.0,
|
||
make_tip=lambda: _make_tip_helper(volume=1250.0, length=100.0, depth=8.2)
|
||
)
|
||
)
|
||
def PRCXI_10uL_Tips(name: str) -> PRCXI9300TipRack:
|
||
"""
|
||
Code: ZX-001-10
|
||
"""
|
||
return PRCXI9300TipRack(
|
||
name=name,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=58.0,
|
||
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.63,
|
||
dy=7.49,
|
||
dz=14.6,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=7.5,
|
||
size_y=7.5,
|
||
size_z=0.0,
|
||
make_tip=lambda: _make_tip_helper(volume=10.0, length=52.0, depth=8.2)
|
||
)
|
||
)
|
||
def PRCXI_1000uL_Tips(name: str) -> PRCXI9300TipRack:
|
||
"""
|
||
Code: ZX-001-1000
|
||
"""
|
||
return PRCXI9300TipRack(
|
||
name=name,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=98.0,
|
||
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=10.63,
|
||
dy=7.49,
|
||
dz=6.6,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=7.5,
|
||
size_y=7.5,
|
||
size_z=100.0,
|
||
make_tip=lambda: _make_tip_helper(volume=1000.0, length=100.0, depth=8.2)
|
||
)
|
||
)
|
||
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=8.24,
|
||
dy=6.81,
|
||
dz=2.0,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=7.0,
|
||
size_y=7.0,
|
||
size_z=0.0,
|
||
make_tip=lambda: _make_tip_helper(volume=300.0, length=60.0, depth=51.0)
|
||
)
|
||
)
|
||
def PRCXI_50uL_tips(name: str) -> PRCXI9300TipRack:
|
||
"""
|
||
Code:
|
||
"""
|
||
return PRCXI9300TipRack(
|
||
name=name,
|
||
size_x=127.76,
|
||
size_y=85.48,
|
||
size_z=58.0,
|
||
model="PRCXI_50uL_tips",
|
||
material_info={"uuid": "", "Code": "", "Name": "", "SupplyType": 1},
|
||
ordered_items=create_ordered_items_2d(
|
||
TipSpot,
|
||
num_items_x=12,
|
||
num_items_y=8,
|
||
dx=10.63,
|
||
dy=7.49,
|
||
dz=13.6,
|
||
item_dx=9.0,
|
||
item_dy=9.0,
|
||
size_x=7.5,
|
||
size_y=7.5,
|
||
size_z=0.0,
|
||
make_tip=lambda: _make_tip_helper(volume=50.0, length=53.0, depth=8.2)
|
||
)
|
||
)
|
||
# =========================================================================
|
||
# Trash
|
||
# =========================================================================
|
||
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}
|
||
)
|
||
# =========================================================================
|
||
# Tube Racks
|
||
# =========================================================================
|
||
def PRCXI_EP_Adapter(name: str) -> PRCXI9300TubeRack:
|
||
"""
|
||
对应 JSON Code: 1 (ep适配器)
|
||
这是一个 4x6 的 EP 管架,适配 1.5mL/2.0mL 离心管
|
||
"""
|
||
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=3.54,
|
||
dy=10.7,
|
||
dz=4.58,
|
||
item_dx=21.0,
|
||
item_dy=18.0,
|
||
size_x=10.6,
|
||
size_y=10.6,
|
||
size_z=40.0,
|
||
max_volume=1500.0
|
||
)
|
||
)
|
||
# =========================================================================
|
||
# Plate Adapters
|
||
# =========================================================================
|
||
def PRCXI_Tip1250_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||
""" Code: ZX-58-1250 """
|
||
return PRCXI9300PlateAdapter(
|
||
name=name,
|
||
size_x=128.0,
|
||
size_y=85.0,
|
||
size_z=20.0,
|
||
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.0,
|
||
size_y=85.0,
|
||
size_z=81.0,
|
||
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.0,
|
||
size_y=85.0,
|
||
size_z=72.3,
|
||
material_info={"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c", "Code": "ZX-58-10", "Name": "吸头10ul 适配器", "SupplyType": 2}
|
||
)
|
||
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.0,
|
||
size_y=91.8,
|
||
size_z=70.0,
|
||
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.0,
|
||
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.0,
|
||
size_y=90.0,
|
||
size_z=86.0,
|
||
material_info={"uuid": "adfabfffa8f24af5abfbba67b8d0f973", "Code": "Fhh478", "Name": "适配器", "SupplyType": 2}
|
||
)
|
||
def PRCXI_30mm_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
||
""" Code: ZX-58-30 """
|
||
return PRCXI9300PlateAdapter(
|
||
name=name,
|
||
size_x=132.0,
|
||
size_y=93.5,
|
||
size_z=30.0,
|
||
material_info={"uuid": "a0757a90d8e44e81a68f306a608694f2", "Code": "ZX-58-30", "Name": "30mm适配器", "SupplyType": 2}
|
||
)
|
||
|
||
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"),
|
||
]
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 协议上传 / 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
|
||
|
||
|
||
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
|