mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-05 05:45:10 +00:00
feat(workstation): update bioyond config migration and coin cell material search logic
- Migrate bioyond_cell config to JSON structure and remove global variable dependencies - Implement material search confirmation dialog auto-handling - Add documentation: 20260113_物料搜寻确认弹窗自动处理功能.md and 20260113_配置迁移修改总结.md
This commit is contained in:
0
unilabos/resources/battery/__init__.py
Normal file
0
unilabos/resources/battery/__init__.py
Normal file
56
unilabos/resources/battery/bottle_carriers.py
Normal file
56
unilabos/resources/battery/bottle_carriers.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||
|
||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
from unilabos.resources.bioyond.YB_bottles import (
|
||||
YB_pei_ye_xiao_Bottle,
|
||||
)
|
||||
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||
|
||||
|
||||
def YIHUA_Electrolyte_12VialCarrier(name: str) -> BottleCarrier:
|
||||
"""12瓶载架 - 2x6布局"""
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 120.0
|
||||
carrier_size_y = 250.0
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 35.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (6 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=2,
|
||||
num_items_y=6,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="Electrolyte_12VialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 2
|
||||
carrier.num_items_y = 6
|
||||
carrier.num_items_z = 1
|
||||
for i in range(12):
|
||||
carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_vial_{i+1}")
|
||||
return carrier
|
||||
195
unilabos/resources/battery/electrode_sheet.py
Normal file
195
unilabos/resources/battery/electrode_sheet.py
Normal file
@@ -0,0 +1,195 @@
|
||||
from typing import Any, Dict, Optional, TypedDict
|
||||
|
||||
from pylabrobot.resources import Resource as ResourcePLR
|
||||
from pylabrobot.resources import Container
|
||||
|
||||
|
||||
electrode_colors = {
|
||||
"PositiveCan": "#ff0000",
|
||||
"PositiveElectrode": "#cc3333",
|
||||
"NegativeCan": "#000000",
|
||||
"NegativeElectrode": "#666666",
|
||||
"SpringWasher": "#8b7355",
|
||||
"FlatWasher": "a9a9a9",
|
||||
"AluminumFoil": "#ffcccc",
|
||||
"Battery": "#00ff00",
|
||||
}
|
||||
|
||||
class ElectrodeSheetState(TypedDict):
|
||||
diameter: float # 直径 (mm)
|
||||
thickness: float # 厚度 (mm)
|
||||
mass: float # 质量 (g)
|
||||
material_type: str # 材料类型(铜、铝、不锈钢、弹簧钢等)
|
||||
color: str # 材料类型对应的颜色
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
|
||||
class ElectrodeSheet(ResourcePLR):
|
||||
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "极片",
|
||||
size_x: float = 10,
|
||||
size_y: float = 10,
|
||||
size_z: float = 10,
|
||||
category: str = "electrode_sheet",
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化极片
|
||||
|
||||
Args:
|
||||
name: 极片名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
category: 类别
|
||||
model: 型号
|
||||
**kwargs: 其他参数传递给父类
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category=category,
|
||||
model=model,
|
||||
**kwargs
|
||||
)
|
||||
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
|
||||
diameter=14,
|
||||
thickness=0.1,
|
||||
mass=0.5,
|
||||
material_type="copper",
|
||||
color="#8b4513",
|
||||
info=None
|
||||
)
|
||||
|
||||
# TODO: 这个还要不要?给self._unilabos_state赋值的?
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""格式不变"""
|
||||
super().load_state(state)
|
||||
self._unilabos_state = state
|
||||
#序列化
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""格式不变"""
|
||||
data = super().serialize_state()
|
||||
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||
return data
|
||||
|
||||
|
||||
def PositiveCan(name: str) -> ElectrodeSheet:
|
||||
"""创建正极壳"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=3.0, model="PositiveCan")
|
||||
sheet.load_state({"diameter": 20.0, "thickness": 0.5, "mass": 0.5, "material_type": "aluminum", "color": electrode_colors["PositiveCan"], "info": None})
|
||||
return sheet
|
||||
|
||||
|
||||
def PositiveElectrode(name: str) -> ElectrodeSheet:
|
||||
"""创建正极片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="PositiveElectrode")
|
||||
sheet.load_state({"material_type": "positive_electrode", "color": electrode_colors["PositiveElectrode"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def NegativeCan(name: str) -> ElectrodeSheet:
|
||||
"""创建负极壳"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=2.0, model="NegativeCan")
|
||||
sheet.load_state({"material_type": "steel", "color": electrode_colors["NegativeCan"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def NegativeElectrode(name: str) -> ElectrodeSheet:
|
||||
"""创建负极片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="NegativeElectrode")
|
||||
sheet.load_state({"material_type": "negative_electrode", "color": electrode_colors["NegativeElectrode"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def SpringWasher(name: str) -> ElectrodeSheet:
|
||||
"""创建弹片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.5, model="SpringWasher")
|
||||
sheet.load_state({"material_type": "spring_steel", "color": electrode_colors["SpringWasher"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def FlatWasher(name: str) -> ElectrodeSheet:
|
||||
"""创建垫片"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.2, model="FlatWasher")
|
||||
sheet.load_state({"material_type": "steel", "color": electrode_colors["FlatWasher"]})
|
||||
return sheet
|
||||
|
||||
|
||||
def AluminumFoil(name: str) -> ElectrodeSheet:
|
||||
"""创建铝箔"""
|
||||
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.05, model="AluminumFoil")
|
||||
sheet.load_state({"material_type": "aluminum", "color": electrode_colors["AluminumFoil"]})
|
||||
return sheet
|
||||
|
||||
|
||||
class BatteryState(TypedDict):
|
||||
color: str # 材料类型对应的颜色
|
||||
electrolyte_name: str
|
||||
data_electrolyte_code: str
|
||||
open_circuit_voltage: float
|
||||
assembly_pressure: float
|
||||
electrolyte_volume: float
|
||||
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
|
||||
class Battery(Container):
|
||||
"""电池类 - 包含组装好的电池"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "电池",
|
||||
size_x: float = 12,
|
||||
size_y: float = 12,
|
||||
size_z: float = 6,
|
||||
category: str = "battery",
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化电池
|
||||
|
||||
Args:
|
||||
name: 电池名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
category: 类别
|
||||
model: 型号
|
||||
**kwargs: 其他参数传递给父类
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category=category,
|
||||
model=model,
|
||||
**kwargs
|
||||
)
|
||||
self._unilabos_state: BatteryState = BatteryState(
|
||||
color=electrode_colors["Battery"],
|
||||
electrolyte_name="无",
|
||||
data_electrolyte_code="",
|
||||
open_circuit_voltage=0.0,
|
||||
assembly_pressure=0.0,
|
||||
electrolyte_volume=0.0,
|
||||
info=None
|
||||
)
|
||||
|
||||
def load_state(self, state: Dict[str, Any]) -> None:
|
||||
"""格式不变"""
|
||||
super().load_state(state)
|
||||
self._unilabos_state = state
|
||||
|
||||
#序列化
|
||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""格式不变"""
|
||||
data = super().serialize_state()
|
||||
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||
return data
|
||||
344
unilabos/resources/battery/magazine.py
Normal file
344
unilabos/resources/battery/magazine.py
Normal file
@@ -0,0 +1,344 @@
|
||||
from typing import Dict, List, Optional, OrderedDict, Union, Callable
|
||||
import math
|
||||
|
||||
from pylabrobot.resources.coordinate import Coordinate
|
||||
from pylabrobot.resources import Resource, ResourceStack, ItemizedResource
|
||||
from pylabrobot.resources.carrier import create_homogeneous_resources
|
||||
|
||||
from unilabos.resources.battery.electrode_sheet import (
|
||||
PositiveCan, PositiveElectrode,
|
||||
NegativeCan, NegativeElectrode,
|
||||
SpringWasher, FlatWasher,
|
||||
AluminumFoil,
|
||||
Battery
|
||||
)
|
||||
|
||||
|
||||
class Magazine(ResourceStack):
|
||||
"""子弹夹洞位类"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
direction: str = 'z',
|
||||
resources: Optional[List[Resource]] = None,
|
||||
max_sheets: int = 100,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化子弹夹洞位
|
||||
|
||||
Args:
|
||||
name: 洞位名称
|
||||
direction: 堆叠方向
|
||||
resources: 资源列表
|
||||
max_sheets: 最大极片数量
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
direction=direction,
|
||||
resources=resources,
|
||||
)
|
||||
self.max_sheets = max_sheets
|
||||
|
||||
@property
|
||||
def size_x(self) -> float:
|
||||
return self.get_size_x()
|
||||
|
||||
@property
|
||||
def size_y(self) -> float:
|
||||
return self.get_size_y()
|
||||
|
||||
@property
|
||||
def size_z(self) -> float:
|
||||
return self.get_size_z()
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {
|
||||
**super().serialize(),
|
||||
"size_x": self.size_x or 10.0,
|
||||
"size_y": self.size_y or 10.0,
|
||||
"size_z": self.size_z or 10.0,
|
||||
"max_sheets": self.max_sheets,
|
||||
}
|
||||
|
||||
|
||||
class MagazineHolder(ItemizedResource):
|
||||
"""子弹夹类 - 有多个洞位,每个洞位放多个极片"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
ordered_items: Optional[Dict[str, Magazine]] = None,
|
||||
ordering: Optional[OrderedDict[str, str]] = None,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
cross_section_type: str = "circle",
|
||||
category: str = "magazine_holder",
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""初始化子弹夹
|
||||
|
||||
Args:
|
||||
name: 子弹夹名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
hole_diameter: 洞直径 (mm)
|
||||
hole_depth: 洞深度 (mm)
|
||||
max_sheets_per_hole: 每个洞位最大极片数量
|
||||
category: 类别
|
||||
model: 型号
|
||||
"""
|
||||
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
ordered_items=ordered_items,
|
||||
ordering=ordering,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
|
||||
# 保存洞位的直径和深度
|
||||
self.hole_diameter = hole_diameter
|
||||
self.hole_depth = hole_depth
|
||||
self.max_sheets_per_hole = max_sheets_per_hole
|
||||
self.cross_section_type = cross_section_type
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {
|
||||
**super().serialize(),
|
||||
"hole_diameter": self.hole_diameter,
|
||||
"hole_depth": self.hole_depth,
|
||||
"max_sheets_per_hole": self.max_sheets_per_hole,
|
||||
"cross_section_type": self.cross_section_type,
|
||||
}
|
||||
|
||||
|
||||
def magazine_factory(
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
locations: List[Coordinate],
|
||||
klasses: Optional[List[Callable[[str], str]]] = None,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
category: str = "magazine_holder",
|
||||
model: Optional[str] = None,
|
||||
) -> 'MagazineHolder':
|
||||
"""工厂函数:创建子弹夹
|
||||
|
||||
Args:
|
||||
name: 子弹夹名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
locations: 洞位坐标列表
|
||||
klasses: 每个洞位中极片的类列表
|
||||
hole_diameter: 洞直径 (mm)
|
||||
hole_depth: 洞深度 (mm)
|
||||
max_sheets_per_hole: 每个洞位最大极片数量
|
||||
category: 类别
|
||||
model: 型号
|
||||
"""
|
||||
for loc in locations:
|
||||
loc.x -= hole_diameter / 2
|
||||
loc.y -= hole_diameter / 2
|
||||
|
||||
# 创建洞位
|
||||
_sites = create_homogeneous_resources(
|
||||
klass=Magazine,
|
||||
locations=locations,
|
||||
resource_size_x=hole_diameter,
|
||||
resource_size_y=hole_diameter,
|
||||
name_prefix=name,
|
||||
max_sheets=max_sheets_per_hole,
|
||||
)
|
||||
|
||||
# 生成编号键
|
||||
keys = [f"A{i+1}" for i in range(len(locations))]
|
||||
sites = dict(zip(keys, _sites.values()))
|
||||
|
||||
holder = MagazineHolder(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
ordered_items=sites,
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
|
||||
if klasses is not None:
|
||||
for i, klass in enumerate(klasses):
|
||||
hole_key = keys[i]
|
||||
hole = holder.children[i]
|
||||
for j in reversed(range(max_sheets_per_hole)):
|
||||
item_name = f"{hole_key}_sheet{j+1}"
|
||||
item = klass(name=item_name)
|
||||
hole.assign_child_resource(item)
|
||||
return holder
|
||||
|
||||
|
||||
def MagazineHolder_6_Cathode(
|
||||
name: str,
|
||||
size_x: float = 80.0,
|
||||
size_y: float = 80.0,
|
||||
size_z: float = 40.0,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 20.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
) -> MagazineHolder:
|
||||
"""创建6孔子弹夹 - 六边形排布"""
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
|
||||
locations = []
|
||||
|
||||
# 周围6个孔,按六边形排布
|
||||
for i in range(6):
|
||||
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||
x = center_x + hole_spacing * math.cos(angle)
|
||||
y = center_y + hole_spacing * math.sin(angle)
|
||||
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=[FlatWasher, PositiveCan, PositiveCan, FlatWasher, PositiveCan, PositiveCan],
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_6_Cathode",
|
||||
)
|
||||
|
||||
|
||||
def MagazineHolder_6_Anode(
|
||||
name: str,
|
||||
size_x: float = 80.0,
|
||||
size_y: float = 80.0,
|
||||
size_z: float = 40.0,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 20.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
) -> MagazineHolder:
|
||||
"""创建6孔子弹夹 - 六边形排布"""
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
|
||||
locations = []
|
||||
|
||||
# 周围6个孔,按六边形排布
|
||||
for i in range(6):
|
||||
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||
x = center_x + hole_spacing * math.cos(angle)
|
||||
y = center_y + hole_spacing * math.sin(angle)
|
||||
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=[SpringWasher, NegativeCan, NegativeCan, SpringWasher, NegativeCan, NegativeCan],
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_6_Anode",
|
||||
)
|
||||
|
||||
|
||||
def MagazineHolder_6_Battery(
|
||||
name: str,
|
||||
size_x: float = 80.0,
|
||||
size_y: float = 80.0,
|
||||
size_z: float = 40.0,
|
||||
hole_diameter: float = 14.0,
|
||||
hole_depth: float = 10.0,
|
||||
hole_spacing: float = 20.0,
|
||||
max_sheets_per_hole: int = 100,
|
||||
) -> MagazineHolder:
|
||||
"""创建6孔子弹夹 - 六边形排布"""
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
|
||||
locations = []
|
||||
|
||||
# 周围6个孔,按六边形排布
|
||||
for i in range(6):
|
||||
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||
x = center_x + hole_spacing * math.cos(angle)
|
||||
y = center_y + hole_spacing * math.sin(angle)
|
||||
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=None, # 初始化时,不放入装好的电池
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_6_Battery",
|
||||
)
|
||||
|
||||
|
||||
def MagazineHolder_4_Cathode(
|
||||
name: str,
|
||||
) -> MagazineHolder:
|
||||
"""创建4孔子弹夹 - 正方形四角排布"""
|
||||
size_x: float = 80.0
|
||||
size_y: float = 80.0
|
||||
size_z: float = 10.0
|
||||
hole_diameter: float = 14.0
|
||||
hole_depth: float = 10.0
|
||||
hole_spacing: float = 25.0
|
||||
max_sheets_per_hole: int = 100
|
||||
|
||||
# 计算4个洞位的坐标(正方形四角排布)
|
||||
center_x = size_x / 2
|
||||
center_y = size_y / 2
|
||||
offset = hole_spacing / 2
|
||||
|
||||
locations = [
|
||||
Coordinate(center_x - offset, center_y - offset, size_z - hole_depth), # 左下
|
||||
Coordinate(center_x + offset, center_y - offset, size_z - hole_depth), # 右下
|
||||
Coordinate(center_x - offset, center_y + offset, size_z - hole_depth), # 左上
|
||||
Coordinate(center_x + offset, center_y + offset, size_z - hole_depth), # 右上
|
||||
]
|
||||
|
||||
return magazine_factory(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
locations=locations,
|
||||
klasses=[AluminumFoil, PositiveElectrode, PositiveElectrode, PositiveElectrode],
|
||||
hole_diameter=hole_diameter,
|
||||
hole_depth=hole_depth,
|
||||
max_sheets_per_hole=max_sheets_per_hole,
|
||||
category="magazine_holder",
|
||||
model="MagazineHolder_4_Cathode",
|
||||
)
|
||||
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Bioyond 仓库系统开发指南
|
||||
|
||||
本文档详细说明 Bioyond 仓库(Warehouse)系统的架构、配置和使用方法,帮助开发者快速理解和维护仓库相关代码。
|
||||
|
||||
## 📚 目录
|
||||
|
||||
- [系统架构](#系统架构)
|
||||
- [核心概念](#核心概念)
|
||||
- [三层映射关系](#三层映射关系)
|
||||
- [warehouse_factory 详解](#warehouse_factory-详解)
|
||||
- [创建新仓库](#创建新仓库)
|
||||
- [常见问题](#常见问题)
|
||||
- [调试技巧](#调试技巧)
|
||||
|
||||
---
|
||||
|
||||
## 系统架构
|
||||
|
||||
Bioyond 仓库系统采用**三层架构**,实现从前端显示到后端 API 的完整映射:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 前端显示层 (YB_warehouses.py) │
|
||||
│ - warehouse_factory 自动生成库位网格 │
|
||||
│ - 生成库位名称:A01, B02, C03... │
|
||||
│ - 存储在 WareHouse.sites 字典中 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Deck 布局层 (decks.py) │
|
||||
│ - 定义仓库在 Deck 上的物理位置 │
|
||||
│ - 组织多个仓库形成完整布局 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ UUID 映射层 (config.py) │
|
||||
│ - 将库位名称映射到 Bioyond 系统 UUID │
|
||||
│ - 用于 API 调用时的物料入库操作 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 仓库(Warehouse)
|
||||
|
||||
仓库是一个**三维网格**,用于存放物料。由以下参数定义:
|
||||
|
||||
- **num_items_x**: 列数(X 轴)
|
||||
- **num_items_y**: 行数(Y 轴)
|
||||
- **num_items_z**: 层数(Z 轴)
|
||||
|
||||
例如:`5行×3列×1层` = 5×3×1 = 15个库位
|
||||
|
||||
### 库位(Site)
|
||||
|
||||
库位是仓库中的单个存储位置,由**字母行+数字列**命名:
|
||||
|
||||
- **字母行**:A, B, C, D, E, F...(对应 Y 轴)
|
||||
- **数字列**:01, 02, 03, 04...(对应 X 轴或 Z 轴)
|
||||
|
||||
示例:`A01`, `B02`, `C03`
|
||||
|
||||
### 布局模式(Layout)
|
||||
|
||||
控制库位的排序和 Y 坐标计算:
|
||||
|
||||
| 模式 | 说明 | 生成顺序 | Y 坐标计算 | 显示效果 |
|
||||
|------|------|----------|-----------|---------|
|
||||
| `col-major` | 列优先(默认) | A01, B01, C01, A02... | `dy + (num_y - row - 1) * item_dy` | A 可能在下 |
|
||||
| `row-major` | 行优先 | A01, A02, A03, B01... | `dy + row * item_dy` | **A 在上** ✓ |
|
||||
|
||||
**重要:** 使用 `row-major` 可以避免上下颠倒问题!
|
||||
|
||||
---
|
||||
|
||||
## 三层映射关系
|
||||
|
||||
### 示例:手动传递窗右(A01-E03)
|
||||
|
||||
#### 1️⃣ 前端显示层 - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建 5行×3列×1层 仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
row_offset=row_offset,
|
||||
layout="row-major",
|
||||
)
|
||||
```
|
||||
|
||||
**自动生成的库位:** A01, A02, A03, B01, B02, B03, ..., E01, E02, E03
|
||||
|
||||
#### 2️⃣ Deck 布局层 - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
self.warehouses = {
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"手动传递窗右": Coordinate(4160.0, 877.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 创建仓库实例
|
||||
- 设置在 Deck 上的物理坐标
|
||||
|
||||
#### 3️⃣ UUID 映射层 - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
# ... 其他库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 用户拖拽物料到"手动传递窗右"的"A01"位置时
|
||||
- 系统查找 `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]`
|
||||
- 获取 UUID `"3a19deae-2c7a-36f5-5e41-02c5b66feaea"`
|
||||
- 调用 Bioyond API 将物料入库到该 UUID 位置
|
||||
|
||||
---
|
||||
|
||||
## 实际配置案例
|
||||
|
||||
### 案例:手动传递窗左/右的完整配置
|
||||
|
||||
本案例展示如何为"手动传递窗右"和"手动传递窗左"建立完整的三层映射。
|
||||
|
||||
#### 背景需求
|
||||
- **手动传递窗右**: 需要 A01-E03(5行×3列=15个库位)
|
||||
- **手动传递窗左**: 需要 F01-J03(5行×3列=15个库位)
|
||||
- 这两个仓库共享同一个物理堆栈的 UUID("手动堆栈")
|
||||
|
||||
#### 实施步骤
|
||||
|
||||
**1️⃣ 修复前端布局** - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
# 创建新的 5×3×1 仓库函数(之前是错误的 1×3×3)
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建5行×3列×1层仓库,支持行偏移生成不同字母行"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行 ← 修正
|
||||
num_items_z=1, # 1层 ← 修正
|
||||
row_offset=row_offset, # ← 支持 F-J 行
|
||||
layout="row-major", # ← 避免上下颠倒
|
||||
)
|
||||
```
|
||||
|
||||
**2️⃣ 更新 Deck 配置** - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_5x3x1, # 新增导入
|
||||
)
|
||||
|
||||
class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
self.warehouses = {
|
||||
# 修改前: bioyond_warehouse_1x3x3 (错误尺寸)
|
||||
# 修改后: bioyond_warehouse_5x3x1 (正确尺寸)
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
}
|
||||
```
|
||||
|
||||
**3️⃣ 添加 UUID 映射** - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
# 保持原有的"手动堆栈"配置不变(A01-J03共30个库位)
|
||||
"手动堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
# ... A02-E03 共15个
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
# ... F02-J03 共15个
|
||||
}
|
||||
},
|
||||
|
||||
# [新增] 手动传递窗右 - 复用"手动堆栈"的 A01-E03 UUID
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea", # ← 与手动堆栈A01相同
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||
}
|
||||
},
|
||||
|
||||
# [新增] 手动传递窗左 - 复用"手动堆栈"的 F01-J03 UUID
|
||||
"手动传递窗左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a", # ← 与手动堆栈F01相同
|
||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 关键要点
|
||||
|
||||
1. **UUID 可以复用**: 三个仓库(手动堆栈、手动传递窗右、手动传递窗左)可以共享相同的物理库位 UUID
|
||||
2. **库位名称必须匹配**: 前端生成的库位名称(如 F01)必须与 config.py 中的键名完全一致
|
||||
3. **row_offset 的妙用**:
|
||||
- `row_offset=0` → 生成 A-E 行
|
||||
- `row_offset=5` → 生成 F-J 行(跳过前5个字母)
|
||||
|
||||
#### 验证结果
|
||||
|
||||
配置完成后,拖拽测试:
|
||||
|
||||
| 拖拽位置 | 前端库位 | 查找路径 | UUID | 结果 |
|
||||
|---------|---------|---------|------|------|
|
||||
| 手动传递窗右/A01 | A01 | `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 正确入库 |
|
||||
| 手动传递窗左/F01 | F01 | `WAREHOUSE_MAPPING["手动传递窗左"]["site_uuids"]["F01"]` | `3a19...c4a` | ✅ 正确入库 |
|
||||
| 手动堆栈/A01 | A01 | `WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 仍然正常 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## warehouse_factory 详解
|
||||
|
||||
### 函数签名
|
||||
|
||||
```python
|
||||
def warehouse_factory(
|
||||
name: str,
|
||||
num_items_x: int = 1, # 列数
|
||||
num_items_y: int = 4, # 行数
|
||||
num_items_z: int = 4, # 层数
|
||||
dx: float = 137.0, # X 起始偏移
|
||||
dy: float = 96.0, # Y 起始偏移
|
||||
dz: float = 120.0, # Z 起始偏移
|
||||
item_dx: float = 10.0, # X 间距
|
||||
item_dy: float = 10.0, # Y 间距
|
||||
item_dz: float = 10.0, # Z 间距
|
||||
col_offset: int = 0, # 列偏移(影响数字)
|
||||
row_offset: int = 0, # 行偏移(影响字母)
|
||||
layout: str = "col-major", # 布局模式
|
||||
) -> WareHouse:
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
#### 尺寸参数
|
||||
- **num_items_x, y, z**: 定义仓库的网格尺寸
|
||||
- **注意**: 当 `num_items_z > 1` 时,Z 轴会被映射为数字列
|
||||
|
||||
#### 位置参数
|
||||
- **dx, dy, dz**: 第一个库位的起始坐标
|
||||
- **item_dx, dy, dz**: 库位之间的间距
|
||||
|
||||
#### 偏移参数
|
||||
- **col_offset**: 列起始偏移,用于生成 A05-D08 等命名
|
||||
```python
|
||||
col_offset=4 # 生成 A05, A06, A07, A08
|
||||
```
|
||||
|
||||
- **row_offset**: 行起始偏移,用于生成 F01-J03 等命名
|
||||
```python
|
||||
row_offset=5 # 生成 F01, F02, F03(跳过 A-E)
|
||||
```
|
||||
|
||||
#### 布局参数
|
||||
- **layout**:
|
||||
- `"col-major"`: 列优先(默认),可能导致上下颠倒
|
||||
- `"row-major"`: 行优先,**推荐使用**,A 显示在上
|
||||
|
||||
### 库位生成逻辑
|
||||
|
||||
```python
|
||||
# row-major 模式(推荐)
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}"
|
||||
for j in range(num_y)
|
||||
for i in range(num_x)]
|
||||
|
||||
# 示例:num_y=2, num_x=3, row_offset=0, col_offset=0
|
||||
# 生成:A01, A02, A03, B01, B02, B03
|
||||
```
|
||||
|
||||
### Y 坐标计算
|
||||
|
||||
```python
|
||||
if layout == "row-major":
|
||||
# A 在上(Y 较小)
|
||||
y = dy + row * item_dy
|
||||
else:
|
||||
# A 在下(Y 较大)- 不推荐
|
||||
y = dy + (num_items_y - row - 1) * item_dy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 创建新仓库
|
||||
|
||||
### 步骤 1: 在 YB_warehouses.py 中创建函数
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_3x4x1(name: str) -> WareHouse:
|
||||
"""创建 3行×4列×1层 仓库
|
||||
|
||||
布局:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
C01 | C02 | C03 | C04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=3, # 3行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # ⭐ 推荐使用
|
||||
)
|
||||
```
|
||||
|
||||
### 步骤 2: 在 decks.py 中使用
|
||||
|
||||
```python
|
||||
# 1. 导入函数
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_3x4x1, # 新增
|
||||
)
|
||||
|
||||
# 2. 在 setup() 中添加
|
||||
self.warehouses = {
|
||||
"我的新仓库": bioyond_warehouse_3x4x1("我的新仓库"),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"我的新仓库": Coordinate(100.0, 200.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3: 在 config.py 中配置 UUID(可选)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"我的新仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "从 Bioyond 系统获取的 UUID",
|
||||
"A02": "从 Bioyond 系统获取的 UUID",
|
||||
# ... 其他 11 个库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 如果不需要拖拽入库功能,可跳过此步骤。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么库位显示上下颠倒(C 在上,A 在下)?
|
||||
|
||||
**原因:** 使用了默认的 `col-major` 布局。
|
||||
|
||||
**解决:** 在 `warehouse_factory` 中添加 `layout="row-major"`
|
||||
|
||||
```python
|
||||
return warehouse_factory(
|
||||
...
|
||||
layout="row-major", # ← 添加这行
|
||||
)
|
||||
```
|
||||
|
||||
### Q2: 我需要 1×3×3 还是 3×3×1?
|
||||
|
||||
**判断方法:**
|
||||
- **1×3×3**: 1列×3行×3**层**(垂直堆叠,有高度)
|
||||
- **3×3×1**: 3行×3列×1**层**(平面网格)
|
||||
|
||||
**推荐:** 大多数情况使用 `X×Y×1`(平面网格)更直观。
|
||||
|
||||
### Q3: 如何生成 F01-J03 而非 A01-E03?
|
||||
|
||||
**方法:** 使用 `row_offset` 参数
|
||||
|
||||
```python
|
||||
bioyond_warehouse_5x3x1("仓库名", row_offset=5)
|
||||
# row_offset=5 跳过 A-E,从 F 开始
|
||||
```
|
||||
|
||||
### Q4: 拖拽物料后找不到 UUID 怎么办?
|
||||
|
||||
**检查清单:**
|
||||
1. `config.py` 中是否有该仓库的配置?
|
||||
2. 仓库名称是否完全匹配?
|
||||
3. 库位名称(如 A01)是否在 `site_uuids` 中?
|
||||
|
||||
**示例错误:**
|
||||
```python
|
||||
# decks.py
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1(...)
|
||||
|
||||
# config.py - ❌ 名称不匹配
|
||||
"手动传递窗": { ... } # 缺少"右"字
|
||||
```
|
||||
|
||||
### Q5: 库位重叠怎么办?
|
||||
|
||||
**原因:** 间距(`item_dx/dy/dz`)太小。
|
||||
|
||||
**解决:** 增大间距参数
|
||||
|
||||
```python
|
||||
item_dx=150.0, # 增大 X 间距
|
||||
item_dy=130.0, # 增大 Y 间距
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 查看生成的库位
|
||||
|
||||
```python
|
||||
warehouse = bioyond_warehouse_5x3x1("测试仓库")
|
||||
print(list(warehouse.sites.keys()))
|
||||
# 输出:['A01', 'A02', 'A03', 'B01', 'B02', ...]
|
||||
```
|
||||
|
||||
### 2. 检查库位坐标
|
||||
|
||||
```python
|
||||
for name, site in warehouse.sites.items():
|
||||
print(f"{name}: {site.location}")
|
||||
# 输出:
|
||||
# A01: Coordinate(x=10.0, y=10.0, z=120.0)
|
||||
# A02: Coordinate(x=147.0, y=10.0, z=120.0)
|
||||
# ...
|
||||
```
|
||||
|
||||
### 3. 验证 UUID 映射
|
||||
|
||||
```python
|
||||
from unilabos.devices.workstation.bioyond_studio.config import WAREHOUSE_MAPPING
|
||||
|
||||
warehouse_name = "手动传递窗右"
|
||||
location_code = "A01"
|
||||
|
||||
if warehouse_name in WAREHOUSE_MAPPING:
|
||||
uuid = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"].get(location_code)
|
||||
print(f"{warehouse_name}/{location_code} → {uuid}")
|
||||
else:
|
||||
print(f"❌ 未找到仓库: {warehouse_name}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件关系图
|
||||
|
||||
```
|
||||
unilabos/
|
||||
├── resources/
|
||||
│ ├── warehouse.py # warehouse_factory 核心实现
|
||||
│ └── bioyond/
|
||||
│ ├── YB_warehouses.py # ⭐ 仓库函数定义
|
||||
│ ├── decks.py # ⭐ Deck 布局配置
|
||||
│ └── README_WAREHOUSE.md # 📖 本文档
|
||||
└── devices/
|
||||
└── workstation/
|
||||
└── bioyond_studio/
|
||||
├── config.py # ⭐ UUID 映射配置
|
||||
└── bioyond_cell/
|
||||
└── bioyond_cell_workstation.py # 业务逻辑
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v1.1** (2026-01-08): 补充实际配置案例
|
||||
- 添加"手动传递窗右"和"手动传递窗左"的完整配置示例
|
||||
- 展示 UUID 复用的实际应用
|
||||
- 说明三个仓库共享物理堆栈的配置方法
|
||||
|
||||
- **v1.0** (2026-01-07): 初始版本
|
||||
- 新增 `row_offset` 参数支持
|
||||
- 创建 `bioyond_warehouse_5x3x1` 和 `bioyond_warehouse_2x2x1`
|
||||
- 修复多个仓库的上下颠倒问题
|
||||
|
||||
---
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [warehouse.py](../warehouse.py) - 核心工厂函数实现
|
||||
- [YB_warehouses.py](YB_warehouses.py) - 所有仓库定义
|
||||
- [decks.py](decks.py) - Deck 布局配置
|
||||
- [config.py](../../devices/workstation/bioyond_studio/config.py) - UUID 映射
|
||||
|
||||
---
|
||||
|
||||
**维护者:** Uni-Lab-OS 开发团队
|
||||
**最后更新:** 2026-01-07
|
||||
653
unilabos/resources/bioyond/YB_bottle_carriers.py
Normal file
653
unilabos/resources/bioyond/YB_bottle_carriers.py
Normal file
@@ -0,0 +1,653 @@
|
||||
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||
|
||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
from unilabos.resources.bioyond.YB_bottles import (
|
||||
YB_jia_yang_tou_da,
|
||||
YB_ye_Bottle,
|
||||
YB_ye_100ml_Bottle,
|
||||
YB_gao_nian_ye_Bottle,
|
||||
YB_5ml_fenyeping,
|
||||
YB_20ml_fenyeping,
|
||||
YB_pei_ye_xiao_Bottle,
|
||||
YB_pei_ye_da_Bottle,
|
||||
YB_qiang_tou,
|
||||
)
|
||||
# 命名约定:试剂瓶-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
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="Electrolyte_6VialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 3
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
# for i in range(6):
|
||||
# carrier[i] = YB_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="Electrolyte_1BottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
# carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_6StockCarrier(name: str) -> BottleCarrier:
|
||||
"""6瓶载架 - 2x3布局"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 20.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
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="6StockCarrier",
|
||||
)
|
||||
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] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_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
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="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(3):
|
||||
# carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}")
|
||||
# for i in range(3, 6):
|
||||
# carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 1瓶载架 - 单个中央位置
|
||||
def YB_ye(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (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="YB_ye",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_ye_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
|
||||
# 高粘液瓶载架 - 单个中央位置
|
||||
def YB_gaonianye(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (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="YB_gaonianye",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_gao_nian_ye_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
|
||||
# 100ml液体瓶载架 - 单个中央位置
|
||||
def YB_100ml_yeti(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (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="YB_100ml_yeti",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_ye_100ml_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
# 5ml分液瓶板 - 4x2布局,8个位置
|
||||
def YB_5ml_fenyepingban(name: str) -> BottleCarrier:
|
||||
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 15.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="YB_5ml_fenyepingban",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_5ml_fenyeping(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 20ml分液瓶板 - 4x2布局,8个位置
|
||||
def YB_20ml_fenyepingban(name: str) -> BottleCarrier:
|
||||
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 70.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 20.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="YB_20ml_fenyepingban",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_20ml_fenyeping(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 配液瓶(小)板 - 4x2布局,8个位置
|
||||
def YB_peiyepingxiaoban(name: str) -> BottleCarrier:
|
||||
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 65.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="YB_peiyepingxiaoban",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_bottle_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
# 配液瓶(大)板 - 2x2布局,4个位置
|
||||
def YB_peiyepingdaban(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 95.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 55.0
|
||||
bottle_spacing_x = 60.0 # X方向间距
|
||||
bottle_spacing_y = 60.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="YB_peiyepingdaban",
|
||||
)
|
||||
carrier.num_items_x = 2
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "B1", "B2"]
|
||||
for i in range(4):
|
||||
carrier[i] = YB_pei_ye_da_Bottle(f"{name}_bottle_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 加样头(大)板 - 1x1布局,1个位置
|
||||
def YB_jia_yang_tou_da_Carrier(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 95.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=1,
|
||||
num_items_y=1,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="YB_jia_yang_tou_da_Carrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_jia_yang_tou_da(f"{name}_head_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_shi_pei_qi_kuai(name: str) -> BottleCarrier:
|
||||
"""适配器块 - 单个中央位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 30.0
|
||||
|
||||
# 适配器尺寸
|
||||
adapter_diameter = 80.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - adapter_diameter) / 2
|
||||
center_y = (carrier_size_y - adapter_diameter) / 2
|
||||
center_z = 0.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=adapter_diameter,
|
||||
resource_size_y=adapter_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="YB_shi_pei_qi_kuai",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
# 适配器块本身不包含瓶子,只是一个支撑结构
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_qiang_tou_he(name: str) -> BottleCarrier:
|
||||
"""枪头盒 - 8x12布局,96个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 55.0
|
||||
|
||||
# 枪头尺寸
|
||||
tip_diameter = 10.0
|
||||
tip_spacing_x = 9.0 # X方向间距
|
||||
tip_spacing_y = 9.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2
|
||||
start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=tip_spacing_x,
|
||||
item_dy=tip_spacing_y,
|
||||
size_x=tip_diameter,
|
||||
size_y=tip_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="YB_qiang_tou_he",
|
||||
)
|
||||
carrier.num_items_x = 12
|
||||
carrier.num_items_y = 8
|
||||
carrier.num_items_z = 1
|
||||
# 创建96个枪头
|
||||
for i in range(96):
|
||||
row = chr(65 + i // 12) # A-H
|
||||
col = (i % 12) + 1 # 1-12
|
||||
carrier[i] = YB_qiang_tou(f"{name}_tip_{row}{col}")
|
||||
return carrier
|
||||
|
||||
163
unilabos/resources/bioyond/YB_bottles.py
Normal file
163
unilabos/resources/bioyond/YB_bottles.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
# 工厂函数
|
||||
"""加样头(大)"""
|
||||
def YB_jia_yang_tou_da(
|
||||
name: str,
|
||||
diameter: float = 20.0,
|
||||
height: float = 100.0,
|
||||
max_volume: float = 30000.0, # 30mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建粉末瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,# 未知
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_jia_yang_tou_da",
|
||||
)
|
||||
|
||||
"""液1x1"""
|
||||
def YB_ye_Bottle(
|
||||
name: str,
|
||||
diameter: float = 40.0,
|
||||
height: float = 70.0,
|
||||
max_volume: float = 50000.0, # 50mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_ye_Bottle",
|
||||
)
|
||||
|
||||
"""100ml液体"""
|
||||
def YB_ye_100ml_Bottle(
|
||||
name: str,
|
||||
diameter: float = 50.0,
|
||||
height: float = 90.0,
|
||||
max_volume: float = 100000.0, # 100mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建100ml液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_100ml_yeti",
|
||||
)
|
||||
|
||||
"""高粘液"""
|
||||
def YB_gao_nian_ye_Bottle(
|
||||
name: str,
|
||||
diameter: float = 40.0,
|
||||
height: float = 70.0,
|
||||
max_volume: float = 50000.0, # 50mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建高粘液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="High_Viscosity_Liquid",
|
||||
)
|
||||
|
||||
"""5ml分液瓶"""
|
||||
def YB_5ml_fenyeping(
|
||||
name: str,
|
||||
diameter: float = 20.0,
|
||||
height: float = 50.0,
|
||||
max_volume: float = 5000.0, # 5mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建5ml分液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_5ml_fenyeping",
|
||||
)
|
||||
|
||||
"""20ml分液瓶"""
|
||||
def YB_20ml_fenyeping(
|
||||
name: str,
|
||||
diameter: float = 30.0,
|
||||
height: float = 65.0,
|
||||
max_volume: float = 20000.0, # 20mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建20ml分液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_20ml_fenyeping",
|
||||
)
|
||||
|
||||
"""配液瓶(小)"""
|
||||
def YB_pei_ye_xiao_Bottle(
|
||||
name: str,
|
||||
diameter: float = 35.0,
|
||||
height: float = 60.0,
|
||||
max_volume: float = 30000.0, # 30mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(小)"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_pei_ye_xiao_Bottle",
|
||||
)
|
||||
|
||||
"""配液瓶(大)"""
|
||||
def YB_pei_ye_da_Bottle(
|
||||
name: str,
|
||||
diameter: float = 55.0,
|
||||
height: float = 100.0,
|
||||
max_volume: float = 150000.0, # 150mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(大)"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_pei_ye_da_Bottle",
|
||||
)
|
||||
|
||||
"""枪头"""
|
||||
def YB_qiang_tou(
|
||||
name: str,
|
||||
diameter: float = 10.0,
|
||||
height: float = 50.0,
|
||||
max_volume: float = 1000.0, # 1mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建枪头"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_qiang_tou",
|
||||
)
|
||||
384
unilabos/resources/bioyond/YB_warehouses.py
Normal file
384
unilabos/resources/bioyond/YB_warehouses.py
Normal file
@@ -0,0 +1,384 @@
|
||||
from unilabos.resources.warehouse import WareHouse, warehouse_factory
|
||||
|
||||
# ================ 反应站相关堆栈 ================
|
||||
|
||||
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x4x1仓库 (左侧堆栈: A01~D04)
|
||||
|
||||
使用行优先排序,前端展示为:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
C01 | C02 | C03 | C04
|
||||
D01 | D02 | D03 | D04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=4, # 4行
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
col_offset=0, # 从01开始: A01, A02, A03, A04
|
||||
layout="row-major", # ⭐ 改为行优先排序
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x4x4_right(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x4x1仓库 (右侧堆栈: A05~D08)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4,
|
||||
num_items_y=4,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
col_offset=4, # 从05开始: A05, A06, A07, A08
|
||||
layout="row-major", # ⭐ 改为行优先排序
|
||||
)
|
||||
|
||||
def bioyond_warehouse_density_vial(name: str) -> WareHouse:
|
||||
"""创建测量小瓶仓库(测密度) A01~B03"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列(01-03)
|
||||
num_items_y=2, # 2行(A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=40.0,
|
||||
item_dy=40.0,
|
||||
item_dz=50.0,
|
||||
# 用更小的 resource_size 来表现 "小点的孔位"
|
||||
resource_size_x=30.0,
|
||||
resource_size_y=30.0,
|
||||
resource_size_z=12.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
layout="row-major",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_reagent_storage(name: str) -> WareHouse:
|
||||
"""创建BioYond站内试剂存放堆栈(A01~A02, 1行×2列)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2, # 2列(01-02)
|
||||
num_items_y=1, # 1行(A)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_tipbox_storage(name: str) -> WareHouse:
|
||||
"""创建BioYond站内Tip盒堆栈(A01~B03),用于存放枪头盒"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列(01-03)
|
||||
num_items_y=2, # 2行(A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
layout="row-major",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_liquid_preparation(name: str) -> WareHouse:
|
||||
"""已弃用,创建BioYond移液站内10%分装液体准备仓库(A01~B04)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列(01-04)
|
||||
num_items_y=2, # 2行(A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
layout="row-major",
|
||||
)
|
||||
|
||||
# ================ 配液站相关堆栈 ================
|
||||
|
||||
def bioyond_warehouse_reagent_stack(name: str) -> WareHouse:
|
||||
"""创建BioYond 试剂堆栈 2x4x1 (2行×4列: A01-A04, B01-B04)
|
||||
|
||||
使用行优先排序,前端展示为:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列 (01-04)
|
||||
num_items_y=2, # 2行 (A-B)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
col_offset=0, # 从01开始
|
||||
layout="row-major", # ⭐ 使用行优先排序: A01,A02,A03,A04, B01,B02,B03,B04
|
||||
)
|
||||
|
||||
# 定义bioyond的堆栈
|
||||
|
||||
# =================== Other ===================
|
||||
|
||||
def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x2x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
num_items_y=4,
|
||||
num_items_z=2,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
removed_positions=None
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 1x2x2仓库(1列×2行×2层)- 旧版本,已弃用
|
||||
|
||||
布局(2层):
|
||||
层1: A01
|
||||
B01
|
||||
层2: A02
|
||||
B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
num_items_y=2,
|
||||
num_items_z=2,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x2x1仓库(2行×2列×1层)
|
||||
|
||||
布局:
|
||||
A01 | A02
|
||||
B01 | B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2, # 2列
|
||||
num_items_y=2, # 2行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 10x1x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=10,
|
||||
num_items_y=1,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 1x3x3仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
num_items_y=3,
|
||||
num_items_z=3,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0, # 增大Y方向间距以避免重叠
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建BioYond 5x3x1仓库(5行×3列×1层)
|
||||
|
||||
标准布局(row_offset=0):
|
||||
A01 | A02 | A03
|
||||
B01 | B02 | B03
|
||||
C01 | C02 | C03
|
||||
D01 | D02 | D03
|
||||
E01 | E02 | E03
|
||||
|
||||
带偏移布局(row_offset=5):
|
||||
F01 | F02 | F03
|
||||
G01 | G02 | G03
|
||||
H01 | H02 | H03
|
||||
I01 | I02 | I03
|
||||
J01 | J02 | J03
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
row_offset=row_offset, # 支持行偏移
|
||||
layout="row-major", # 使用行优先避免颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 3x3x1仓库(3行×3列×1层)
|
||||
|
||||
布局:
|
||||
A01 | A02 | A03
|
||||
B01 | B02 | B03
|
||||
C01 | C02 | C03
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # ⭐ 使用行优先避免上下颠倒
|
||||
)
|
||||
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x1x3仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=1,
|
||||
num_items_z=3,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
|
||||
"""已弃用:创建BioYond 5x1x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=5,
|
||||
num_items_y=1,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
|
||||
"""已弃用:创建BioYond 3x3x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
num_items_z=1,
|
||||
dx=12.0,
|
||||
dy=12.0,
|
||||
dz=12.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
||||
"""创建BioYond开关盖加液模块台面"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=5,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
removed_positions=None
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x8x4(name: str) -> WareHouse:
|
||||
"""创建BioYond 8x4x1反应站堆栈(A01~D08)"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=8, # 8列(01-08)
|
||||
num_items_y=4, # 4行(A-D)
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=147.0,
|
||||
item_dy=106.0,
|
||||
item_dz=130.0,
|
||||
category="warehouse",
|
||||
)
|
||||
@@ -1,14 +1,16 @@
|
||||
from os import name
|
||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
||||
|
||||
from unilabos.resources.bioyond.warehouses import (
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_1x4x4,
|
||||
bioyond_warehouse_1x4x4_right, # 新增:右侧仓库 (A05~D08)
|
||||
bioyond_warehouse_1x4x2,
|
||||
bioyond_warehouse_reagent_stack, # 新增:试剂堆栈 (A1-B4)
|
||||
bioyond_warehouse_liquid_and_lid_handling,
|
||||
bioyond_warehouse_1x2x2,
|
||||
bioyond_warehouse_2x2x1, # 新增:321和43窗口 (2行×2列)
|
||||
bioyond_warehouse_1x3x3,
|
||||
bioyond_warehouse_5x3x1, # 新增:手动传递窗仓库 (5行×3列)
|
||||
bioyond_warehouse_10x1x1,
|
||||
bioyond_warehouse_3x3x1,
|
||||
bioyond_warehouse_3x3x1_2,
|
||||
@@ -115,10 +117,10 @@ class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
# 添加仓库
|
||||
self.warehouses = {
|
||||
"321窗口": bioyond_warehouse_1x2x2("321窗口"),
|
||||
"43窗口": bioyond_warehouse_1x2x2("43窗口"),
|
||||
"手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"),
|
||||
"手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"),
|
||||
"321窗口": bioyond_warehouse_2x2x1("321窗口"), # 2行×2列
|
||||
"43窗口": bioyond_warehouse_2x2x1("43窗口"), # 2行×2列
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
"加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"),
|
||||
"加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"),
|
||||
|
||||
@@ -126,6 +128,7 @@ class BIOYOND_YB_Deck(Deck):
|
||||
"母液加样右": bioyond_warehouse_3x3x1_2("母液加样右"),
|
||||
"大瓶母液堆栈左": bioyond_warehouse_5x1x1("大瓶母液堆栈左"),
|
||||
"大瓶母液堆栈右": bioyond_warehouse_5x1x1("大瓶母液堆栈右"),
|
||||
"2号手套箱内部堆栈": bioyond_warehouse_3x3x1("2号手套箱内部堆栈"), # 新增:3行×3列 (A01-C03)
|
||||
}
|
||||
# warehouse 的位置
|
||||
self.warehouse_locations = {
|
||||
@@ -140,6 +143,7 @@ class BIOYOND_YB_Deck(Deck):
|
||||
"母液加样右": Coordinate(2152.0, 333.0, 0.0),
|
||||
"大瓶母液堆栈左": Coordinate(1164.0, 676.0, 0.0),
|
||||
"大瓶母液堆栈右": Coordinate(2717.0, 676.0, 0.0),
|
||||
"2号手套箱内部堆栈": Coordinate(-800, -500.0, 0.0), # 新增:位置需根据实际硬件调整
|
||||
}
|
||||
|
||||
for warehouse_name, warehouse in self.warehouses.items():
|
||||
|
||||
@@ -29,7 +29,7 @@ class Bottle(Well):
|
||||
size_x: float = 0.0,
|
||||
size_y: float = 0.0,
|
||||
size_z: float = 0.0,
|
||||
barcode: Optional[str] = "",
|
||||
barcode: Optional[str] = None,
|
||||
category: str = "container",
|
||||
model: Optional[str] = None,
|
||||
**kwargs,
|
||||
@@ -54,7 +54,6 @@ class Bottle(Well):
|
||||
**super().serialize(),
|
||||
"diameter": self.diameter,
|
||||
"height": self.height,
|
||||
"barcode": self.barcode,
|
||||
}
|
||||
|
||||
T = TypeVar("T", bound=ResourceHolder)
|
||||
|
||||
@@ -27,6 +27,7 @@ def warehouse_factory(
|
||||
category: str = "warehouse",
|
||||
model: Optional[str] = None,
|
||||
col_offset: int = 0, # 列起始偏移量,用于生成A05-D08等命名
|
||||
row_offset: int = 0, # 行起始偏移量,用于生成F01-J03等命名
|
||||
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
|
||||
):
|
||||
# 创建位置坐标
|
||||
@@ -65,10 +66,10 @@ def warehouse_factory(
|
||||
if layout == "row-major":
|
||||
# 行优先顺序: A01,A02,A03,A04, B01,B02,B03,B04
|
||||
# locations[0] 对应 row=0, y最大(前端顶部)→ 应该是 A01
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
else:
|
||||
# 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
|
||||
sites = {i: site for i, site in zip(keys, _sites.values())}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user