18 Commits

Author SHA1 Message Date
Calvin Cao
2e17dee121 Merge pull request #167 from lixinyu1011/workstation_dev_YB4
解决奔耀输入配方的,电解液体积为小数的问题
2025-11-16 17:36:50 +08:00
lixinyu1011
c03abb341a 解决奔耀输入配方的,电解液体积为小数的问题 2025-11-16 16:24:59 +08:00
calvincao
b97be6a5d4 feat(battery): 更新电池工作站配置与物料布局
- 修改弹夹尺寸默认值,确保非空时使用实际值
- 调整new_cellconfig3c.json中设备位置和尺寸配置
- 更新CoinCellDeck的尺寸和原点坐标
-重新分配所有物料和弹夹的位置坐标
- 调整电解液缓存位和回收位坐标
- 更新物料板和tip box的布局位置
2025-11-10 21:40:02 +08:00
Calvin Cao
44f830cf00 Merge pull request #163 from sun7151887/yb4-fix
更新YB_Deck堆栈坐标位置,根据图片像素坐标映射到实际尺寸
2025-11-10 19:30:26 +08:00
dijkstra402
04b578a68b 更新YB_Deck堆栈坐标位置,根据图片像素坐标映射到实际尺寸 2025-11-10 18:57:20 +08:00
calvincao
39a799cabd feat(device): 更新设备配置文件路径和图标
- 修改 bioyond_cell.yaml 中的 xlsx 文件路径为用户目录路径- 在 bioyond_cell.yaml 中新增 warehouse_name 字段并设置默认值- 为 bioyond_cell.yaml 添加 resource_tree_transfer 参数结构定义
- 更新 bioyond_cell.yaml 中的状态类型和设备 ID 配置
- 将 coin_cell_workstation.yaml 的图标从 coin_cell_assembly_picture.webp 更改为 koudian.webp
- 移除 bioyond_cell.yaml 中冗余的 display_name 配置项
2025-11-10 18:28:38 +08:00
Junhan Chang
0d64563fb6 fix serialize for magazine 2025-11-10 15:40:29 +08:00
Calvin Cao
fbb9e0963d Merge pull request #162 from sun7151887/yb4-fix
Fix import: change electrodesheet to electrode_sheet
2025-11-10 13:38:16 +08:00
dijkstra402
af411ddfe6 Fix import: change electrodesheet to electrode_sheet
修改路径
2025-11-10 13:34:49 +08:00
calvincao
f5dbcb1bfc feat(bioyond_cell): 更新默认模板路径并添加温度字段- 更新了自动送料函数中的默认 Excel 模板路径- 在物料信息中新增 temperature 字段,默认值为0
- 更新了 create_orders 函数中的默认实验文件路径
- 注释掉了部分调试代码,保留关键示例和说明
- 添加了关于位置码、实验文件和物料模板的注释提示
2025-11-10 13:27:54 +08:00
calvincao
1ecf89ea27 修改excel 2025-11-10 13:21:56 +08:00
Calvin Cao
6efdf6e5a6 Merge pull request #161 from sun7151887/yb4-fix
Fix import: change electrodesheet to electrode_sheet
2025-11-09 22:35:10 +08:00
dijkstra402
e32dc55db0 Fix import: change electrodesheet to electrode_sheet 2025-11-09 22:02:17 +08:00
Calvin Cao
acc45b716d Merge pull request #160 from sun7151887/yb4-fix
Update coin cell assembly and YB_YH materials configuration
2025-11-09 21:44:42 +08:00
dijkstra402
017eaefb8d Update coin cell assembly and YB_YH materials configuration 2025-11-09 21:43:32 +08:00
Calvin Cao
9e8c692702 Merge pull request #159 from dptech-corp/workstation_dev_YB3
Update coin cell assembly configuration: change CSV file reference an…
2025-11-09 20:57:19 +08:00
Calvin Cao
7a284069d2 Merge pull request #158 from dptech-corp/workstation_dev_YB3
Workstation dev yb3
2025-11-09 17:12:41 +08:00
Calvin Cao
a0e92b8e9b Merge pull request #156 from dptech-corp/workstation_dev_YB3
Workstation dev yb3
2025-11-09 15:48:35 +08:00
11 changed files with 215 additions and 679 deletions

View File

@@ -1,3 +1,4 @@
{ {
"nodes": [ "nodes": [
{ {
@@ -53,11 +54,6 @@
], ],
"type": "device", "type": "device",
"class":"coincellassemblyworkstation_device", "class":"coincellassemblyworkstation_device",
"position": {
"x": -600,
"y": -400,
"z": 0
},
"config": { "config": {
"deck": { "deck": {
"data": { "data": {
@@ -66,6 +62,14 @@
} }
}, },
"protocol_type": [] "protocol_type": []
},
"position": {
"size": {"height": 1450, "width": 1450, "depth": 2100},
"position": {
"x": -1500,
"y": 0,
"z": 0
}
} }
}, },
{ {
@@ -75,11 +79,6 @@
"parent": "BatteryStation", "parent": "BatteryStation",
"type": "deck", "type": "deck",
"class": "CoincellDeck", "class": "CoincellDeck",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": { "config": {
"type": "CoincellDeck", "type": "CoincellDeck",
"setup": true, "setup": true,
@@ -94,4 +93,6 @@
} }
], ],
"links": [] "links": []
} }

View File

@@ -11,6 +11,7 @@ from datetime import datetime, timedelta
import re import re
import threading import threading
import json import json
from copy import deepcopy
from urllib3 import response from urllib3 import response
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer
from unilabos.devices.workstation.bioyond_studio.config import ( from unilabos.devices.workstation.bioyond_studio.config import (
@@ -256,7 +257,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
def auto_feeding4to3( def auto_feeding4to3(
self, self,
# ★ 修改点:默认模板路径 # ★ 修改点:默认模板路径
xlsx_path: Optional[str] = "/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx", xlsx_path: Optional[str] = "/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx",
# ---------------- WH4 - 加样头面 (Z=1, 12个点位) ---------------- # ---------------- WH4 - 加样头面 (Z=1, 12个点位) ----------------
WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0, WH4_x1_y1_z1_1_materialName: str = "", WH4_x1_y1_z1_1_quantity: float = 0.0,
WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0, WH4_x2_y1_z1_2_materialName: str = "", WH4_x2_y1_z1_2_quantity: float = 0.0,
@@ -323,6 +324,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
"materialName": str(row[5]).strip(), "materialName": str(row[5]).strip(),
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0, "quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
"temperature": 0,
}) })
# 四号手套箱原液瓶面 # 四号手套箱原液瓶面
for _, row in df.iloc[14:23, 2:9].iterrows(): for _, row in df.iloc[14:23, 2:9].iterrows():
@@ -334,6 +336,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0, "quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "", "materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "", "targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
"temperature": 0,
}) })
# 三号手套箱人工堆栈 # 三号手套箱人工堆栈
for _, row in df.iloc[25:40, 2:7].iterrows(): for _, row in df.iloc[25:40, 2:7].iterrows():
@@ -343,11 +346,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]), "posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "", "materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "", "materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
"quantity": 1 "quantity": 1,
"temperature": 0,
}) })
else: else:
logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。") logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。")
# TODO: 温度下面手动模式没改,上面的改了
# ---------- 模式 2: 手动填写 ---------- # ---------- 模式 2: 手动填写 ----------
if not items: if not items:
params = locals() params = locals()
@@ -473,7 +477,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
- totalMass 自动计算为所有物料质量之和 - totalMass 自动计算为所有物料质量之和
- createTime 缺失或为空时自动填充为当前日期YYYY/M/D - createTime 缺失或为空时自动填充为当前日期YYYY/M/D
""" """
default_path = Path("/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx") default_path = Path("/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx")
path = Path(xlsx_path) if xlsx_path else default_path path = Path(xlsx_path) if xlsx_path else default_path
print(f"[create_orders] 使用 Excel 路径: {path}") print(f"[create_orders] 使用 Excel 路径: {path}")
if path != default_path: if path != default_path:
@@ -544,6 +548,14 @@ class BioyondCellWorkstation(BioyondWorkstation):
except Exception: except Exception:
return default return default
def _as_float(val, default=0.0) -> float:
try:
if pd.isna(val):
return default
return float(val)
except Exception:
return default
def _as_str(val, default="") -> str: def _as_str(val, default="") -> str:
if val is None or (isinstance(val, float) and pd.isna(val)): if val is None or (isinstance(val, float) and pd.isna(val)):
return default return default
@@ -577,9 +589,9 @@ class BioyondCellWorkstation(BioyondWorkstation):
"createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None), "createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None),
"bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶", "bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶",
"mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0, "mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0,
"loadSheddingInfo": _as_int(row[col_load]) if col_load else 0, "loadSheddingInfo": _as_float(row[col_load]) if col_load else 0.0,
"pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0, "pouchCellInfo": _as_float(row[col_pouch]) if col_pouch else 0,
"conductivityInfo": _as_int(row[col_cond]) if col_cond else 0, "conductivityInfo": _as_float(row[col_cond]) if col_cond else 0,
"conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0, "conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0,
"materialInfos": mats, "materialInfos": mats,
"totalMass": round(total_mass, 4) # 自动汇总 "totalMass": round(total_mass, 4) # 自动汇总
@@ -1160,20 +1172,23 @@ if __name__ == "__main__":
lab_registry.setup() lab_registry.setup()
deck = BIOYOND_YB_Deck(setup=True) deck = BIOYOND_YB_Deck(setup=True)
ws = BioyondCellWorkstation(deck=deck) ws = BioyondCellWorkstation(deck=deck)
# ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") # ws.update_push_ip() #直接修改奔耀端的报送ip地址
# logger.info(ws.scheduler_stop()) # ws.create_sample(name="配液瓶", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="E01")
# ws.create_sample(name="分液瓶", board_type="5ml分液瓶板", bottle_type="5ml分液瓶", location_code="D01")
# # logger.info(ws.scheduler_stop())
# logger.info(ws.scheduler_start()) # logger.info(ws.scheduler_start())
# 继续后续流程
# logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱 # logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱
# # # 使用正斜杠或 Path 对象来指定文件路径 # 使用正斜杠或 Path 对象来指定文件路径
# excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx") # excel_path = Path("/Users/calvincao/Desktop/work/uni-lab-all/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx")
# logger.info(ws.create_orders(excel_path)) # logger.info(ws.create_orders(excel_path))
# logger.info(ws.transfer_3_to_2_to_1()) # logger.info(ws.transfer_3_to_2_to_1())
# logger.info(ws.transfer_1_to_2()) # logger.info(ws.transfer_1_to_2())
# logger.info(ws.scheduler_start())
# 1. location code
# 2. 实验文件
# 3. material template file
while True: while True:
time.sleep(1) time.sleep(1)

View File

@@ -1,4 +1,3 @@
""" """
纽扣电池组装工作站物料类定义 纽扣电池组装工作站物料类定义
Button Battery Assembly Station Resource Classes Button Battery Assembly Station Resource Classes
@@ -19,67 +18,11 @@ from pylabrobot.resources.tip_rack import TipRack, TipSpot
from pylabrobot.resources.trash import Trash from pylabrobot.resources.trash import Trash
from pylabrobot.resources.utils import create_ordered_items_2d from pylabrobot.resources.utils import create_ordered_items_2d
from unilabos.resources.battery.magazine import MagazineHolder_4_Cathode, MagazineHolder_6_Cathode, MagazineHolder_6_Anode, MagazineHolder_6_Battery
from unilabos.resources.battery.bottle_carriers import YIHUA_Electrolyte_12VialCarrier
from unilabos.resources.battery.electrode_sheet import ElectrodeSheet
class ElectrodeSheetState(TypedDict):
diameter: float # 直径 (mm)
thickness: float # 厚度 (mm)
mass: float # 质量 (g)
material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)
height: float
electrolyte_name: str
data_electrolyte_code: str
open_circuit_voltage: float
assembly_pressure: float
electrolyte_volume: float
info: Optional[str] # 附加信息
class ElectrodeSheet(Resource):
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
def __init__(
self,
name: str = "极片",
size_x=10,
size_y=10,
size_z=10,
category: str = "electrode_sheet",
model: Optional[str] = None,
):
"""初始化极片
Args:
name: 极片名称
category: 类别
model: 型号
"""
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
category=category,
model=model,
)
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
diameter=14,
thickness=0.1,
mass=0.5,
material_type="copper",
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进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data
# TODO: 这个应该只能放一个极片 # TODO: 这个应该只能放一个极片
class MaterialHoleState(TypedDict): class MaterialHoleState(TypedDict):
@@ -166,7 +109,6 @@ class MaterialHole(Resource):
return self.children[index] return self.children[index]
class MaterialPlateState(TypedDict): class MaterialPlateState(TypedDict):
hole_spacing_x: float hole_spacing_x: float
hole_spacing_y: float hole_spacing_y: float
@@ -328,132 +270,6 @@ class PlateSlot(ResourceStack):
} }
class ClipMagazineHole(Container):
"""子弹夹洞位类"""
def __init__(
self,
name: str,
diameter: float,
depth: float,
max_sheets: int = 100,
category: str = "clip_magazine_hole",
):
"""初始化子弹夹洞位
Args:
name: 洞位名称
diameter: 洞直径 (mm)
depth: 洞深度 (mm)
max_sheets: 最大极片数量
category: 类别
"""
super().__init__(
name=name,
size_x=diameter,
size_y=diameter,
size_z=depth,
category=category,
)
self.diameter = diameter
self.depth = depth
self.max_sheets = max_sheets
self._sheets: List[ElectrodeSheet] = []
def can_add_sheet(self, sheet: ElectrodeSheet) -> bool:
"""检查是否可以添加极片"""
return (len(self._sheets) < self.max_sheets and
sheet.diameter <= self.diameter)
def add_sheet(self, sheet: ElectrodeSheet) -> None:
"""添加极片"""
if not self.can_add_sheet(sheet):
raise ValueError(f"无法向洞位 {self.name} 添加极片")
self._sheets.append(sheet)
def take_sheet(self) -> ElectrodeSheet:
"""取出极片"""
if len(self._sheets) == 0:
raise ValueError(f"洞位 {self.name} 没有极片")
return self._sheets.pop()
def get_sheet_count(self) -> int:
"""获取极片数量"""
return len(self._sheets)
def serialize_state(self) -> Dict[str, Any]:
return {
"sheet_count": len(self._sheets),
"sheets": [sheet.serialize() for sheet in self._sheets],
}
# TODO: 这个要改
class ClipMagazine(ItemizedResource[ClipMagazineHole]):
"""子弹夹类 - 有6个洞位每个洞位放多个极片"""
children: List[ClipMagazineHole]
def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
hole_spacing: float = 25.0,
max_sheets_per_hole: int = 100,
category: str = "clip_magazine",
model: Optional[str] = None,
):
"""初始化子弹夹
Args:
name: 子弹夹名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
hole_diameter: 洞直径 (mm)
hole_depth: 洞深度 (mm)
hole_spacing: 洞位间距 (mm)
max_sheets_per_hole: 每个洞位最大极片数量
category: 类别
model: 型号
"""
# 创建6个洞位排成2x3布局
holes = create_ordered_items_2d(
klass=ClipMagazineHole,
num_items_x=3,
num_items_y=2,
dx=(size_x - 2 * hole_spacing) / 2, # 居中
dy=(size_y - hole_spacing) / 2, # 居中
dz=size_z - 0,
item_dx=hole_spacing,
item_dy=hole_spacing,
diameter=hole_diameter,
depth=hole_depth,
)
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
ordered_items=holes,
category=category,
model=model,
)
# 保存洞位的直径和深度
self.hole_diameter = hole_diameter
self.hole_depth = hole_depth
self.max_sheets_per_hole = max_sheets_per_hole
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,
}
#是一种类型注解不用self #是一种类型注解不用self
class BatteryState(TypedDict): class BatteryState(TypedDict):
"""电池状态字典""" """电池状态字典"""
@@ -596,76 +412,56 @@ class BatteryPressSlot(Resource):
def get_battery_info(self, index: int) -> Battery: def get_battery_info(self, index: int) -> Battery:
return self.children[0] return self.children[0]
# TODO:这个移液枪架子看一下从哪继承
class TipBox64State(TypedDict):
"""电池状态字典"""
tip_diameter: float = 5.0
tip_length: float = 50.0
with_tips: bool = True
class TipBox64(TipRack): def TipBox64(
"""64孔枪头盒类"""
children: List[TipSpot] = []
def __init__(
self,
name: str, name: str,
size_x: float = 127.8, size_x: float = 127.8,
size_y: float = 85.5, size_y: float = 85.5,
size_z: float = 60.0, size_z: float = 60.0,
category: str = "tip_box_64", category: str = "tip_rack",
model: Optional[str] = None, model: Optional[str] = None,
): ):
"""初始化64孔枪头盒 """64孔枪头盒"""
from pylabrobot.resources.tip import Tip
Args: # 创建12x8=96个枪头位
name: 枪头盒名称 def make_tip():
size_x: 长度 (mm) return Tip(
size_y: 宽度 (mm) has_filter=False,
size_z: 高度 (mm) total_tip_length=20.0,
tip_diameter: 枪头直径 (mm) maximal_volume=1000, # 1mL
tip_length: 枪头长度 (mm) fitting_depth=8.0,
category: 类别
model: 型号
with_tips: 是否带枪头
"""
from pylabrobot.resources.tip import Tip
# 创建8x8=64个枪头位
def make_tip():
return Tip(
has_filter=False,
total_tip_length=20.0,
maximal_volume=1000, # 1mL
fitting_depth=8.0,
)
tip_spots = create_ordered_items_2d(
klass=TipSpot,
num_items_x=8,
num_items_y=8,
dx=8.0,
dy=8.0,
dz=0.0,
item_dx=9.0,
item_dy=9.0,
size_x=10,
size_y=10,
size_z=0.0,
make_tip=make_tip,
)
self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate()
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
ordered_items=tip_spots,
category=category,
model=model,
with_tips=True,
) )
tip_spots = create_ordered_items_2d(
klass=TipSpot,
num_items_x=12,
num_items_y=8,
dx=8.0,
dy=8.0,
dz=0.0,
item_dx=9.0,
item_dy=9.0,
size_x=10,
size_y=10,
size_z=0.0,
make_tip=make_tip,
)
idx_available = list(range(0, 32)) + list(range(64, 96))
tip_spots_available = {k: v for i, (k, v) in enumerate(tip_spots.items()) if i in idx_available}
tip_rack = TipRack(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
# ordered_items=tip_spots_available,
ordered_items=tip_spots,
category=category,
model=model,
with_tips=False,
)
tip_rack.set_tip_state([True]*32 + [False]*32 + [True]*32) # 前32和后32个有枪头中间32个无枪头
return tip_rack
class WasteTipBoxstate(TypedDict): class WasteTipBoxstate(TypedDict):
@@ -683,8 +479,12 @@ class WasteTipBox(Trash):
size_x: float = 127.8, size_x: float = 127.8,
size_y: float = 85.5, size_y: float = 85.5,
size_z: float = 60.0, size_z: float = 60.0,
category: str = "waste_tip_box", material_z_thickness=0,
model: Optional[str] = None, max_volume=float("inf"),
category="trash",
model=None,
compute_volume_from_height=None,
compute_height_from_volume=None,
): ):
"""初始化废枪头盒 """初始化废枪头盒
@@ -734,263 +534,16 @@ class WasteTipBox(Trash):
return data return data
class BottleRackState(TypedDict):
""" bottle_diameter: 瓶子直径 (mm)
bottle_height: 瓶子高度 (mm)
position_spacing: 位置间距 (mm)"""
bottle_diameter: float
bottle_height: float
name_to_index: dict
class BottleRack(Resource):
"""瓶架类 - 12个待配位置+12个已配位置"""
children: List[Resource] = []
def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
category: str = "bottle_rack",
model: Optional[str] = None,
num_items_x: int = 3,
num_items_y: int = 4,
position_spacing: float = 35.0,
orientation: str = "horizontal",
padding_x: float = 20.0,
padding_y: float = 20.0,
):
"""初始化瓶架
Args:
name: 瓶架名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
category: 类别
model: 型号
"""
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
category=category,
model=model,
)
# 初始化状态
self._unilabos_state: BottleRackState = BottleRackState(
bottle_diameter=30.0,
bottle_height=100.0,
position_spacing=position_spacing,
name_to_index={},
)
# 基于网格生成瓶位坐标映射(居中摆放)
# 使用内边距避免点跑到容器外前端渲染不按mm等比缩放时更稳妥
origin_x = padding_x
origin_y = padding_y
self.index_to_pos = {}
for j in range(num_items_y):
for i in range(num_items_x):
idx = j * num_items_x + i
if orientation == "vertical":
# 纵向:沿 y 方向优先排列
self.index_to_pos[idx] = Coordinate(
x=origin_x + j * position_spacing,
y=origin_y + i * position_spacing,
z=0,
)
else:
# 横向(默认):沿 x 方向优先排列
self.index_to_pos[idx] = Coordinate(
x=origin_x + i * position_spacing,
y=origin_y + j * position_spacing,
z=0,
)
self.name_to_index = {}
self.name_to_pos = {}
self.num_items_x = num_items_x
self.num_items_y = num_items_y
self.orientation = orientation
self.padding_x = padding_x
self.padding_y = padding_y
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进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data
# TODO: 这里有些问题要重新写一下
def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True):
capacity = self.num_items_x * self.num_items_y
assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子"
index = len(self.children)
location = self.index_to_pos.get(index, Coordinate.zero())
self.name_to_pos[resource.name] = location
self.name_to_index[resource.name] = index
return super().assign_child_resource(resource, location, reassign)
def assign_child_resource(self, resource: Resource, index: int):
capacity = self.num_items_x * self.num_items_y
assert 0 <= index < capacity, "无效的瓶子索引"
self.name_to_index[resource.name] = index
location = self.index_to_pos[index]
return super().assign_child_resource(resource, location)
def unassign_child_resource(self, resource: Bottle):
super().unassign_child_resource(resource)
self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None)
def serialize(self) -> dict:
return {
**super().serialize(),
"num_items_x": self.num_items_x,
"num_items_y": self.num_items_y,
"position_spacing": self._unilabos_state.get("position_spacing", 35.0),
"orientation": self.orientation,
"padding_x": self.padding_x,
"padding_y": self.padding_y,
}
class BottleState(TypedDict):
diameter: float
height: float
electrolyte_name: str
electrolyte_volume: float
max_volume: float
class Bottle(Resource):
"""瓶子类 - 容纳电解液"""
def __init__(
self,
name: str,
category: str = "bottle",
):
"""初始化瓶子
Args:
name: 瓶子名称
diameter: 直径 (mm)
height: 高度 (mm)
max_volume: 最大体积 (μL)
barcode: 二维码
category: 类别
model: 型号
"""
super().__init__(
name=name,
size_x=1,
size_y=1,
size_z=1,
category=category,
)
self._unilabos_state: BottleState = BottleState()
def aspirate_electrolyte(self, volume: float) -> bool:
current_volume = self._unilabos_state["electrolyte_volume"]
assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available."
self._unilabos_state["electrolyte_volume"] -= volume
return True
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进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data
class ClipMagazine_four(ItemizedResource[ClipMagazineHole]):
"""子弹夹类 - 有4个洞位每个洞位放多个极片"""
children: List[ClipMagazineHole]
def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
hole_spacing: float = 25.0,
max_sheets_per_hole: int = 100,
category: str = "clip_magazine_four",
model: Optional[str] = None,
):
"""初始化子弹夹
Args:
name: 子弹夹名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
hole_diameter: 洞直径 (mm)
hole_depth: 洞深度 (mm)
hole_spacing: 洞位间距 (mm)
max_sheets_per_hole: 每个洞位最大极片数量
category: 类别
model: 型号
"""
# 创建4个洞位排成2x2布局
holes = create_ordered_items_2d(
klass=ClipMagazineHole,
num_items_x=2,
num_items_y=2,
dx=(size_x - 2 * hole_spacing) / 2, # 居中
dy=(size_y - hole_spacing) / 2, # 居中
dz=size_z - 0,
item_dx=hole_spacing,
item_dy=hole_spacing,
diameter=hole_diameter,
depth=hole_depth,
)
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
ordered_items=holes,
category=category,
model=model,
)
# 保存洞位的直径和深度
self.hole_diameter = hole_diameter
self.hole_depth = hole_depth
self.max_sheets_per_hole = max_sheets_per_hole
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,
}
class CoincellDeck(Deck): class CoincellDeck(Deck):
"""纽扣电池组装工作站台面类""" """纽扣电池组装工作站台面类"""
def __init__( def __init__(
self, self,
name: str = "coin_cell_deck", name: str = "coin_cell_deck",
size_x: float = 3650.0, # 1m size_x: float = 1450.0, # 1m
size_y: float = 1550.0, # 1m size_y: float = 1450.0, # 1m
size_z: float = 2100.0, # 0.9m size_z: float = 100.0, # 0.9m
origin: Coordinate = Coordinate(-4000, 2000, 0), origin: Coordinate = Coordinate(-2200, 0, 0),
category: str = "coin_cell_deck", category: str = "coin_cell_deck",
setup: bool = False, # 是否自动执行 setup setup: bool = False, # 是否自动执行 setup
): ):
@@ -1007,11 +560,10 @@ class CoincellDeck(Deck):
""" """
super().__init__( super().__init__(
name=name, name=name,
size_x=size_x, size_x=1450.0,
size_y=size_y, size_y=1450.0,
size_z=size_z, size_z=100.0,
origin=origin, origin=origin,
category=category,
) )
if setup: if setup:
self.setup() self.setup()
@@ -1019,150 +571,69 @@ class CoincellDeck(Deck):
def setup(self) -> None: def setup(self) -> None:
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
# ====================================== 子弹夹 ============================================ # ====================================== 子弹夹 ============================================
zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10)
self.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0))
zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10)
self.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0))
zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10)
self.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0))
zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10)
self.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0))
zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10)
self.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0))
zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10)
self.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0))
zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10)
self.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0))
zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10)
self.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0))
# 为子弹夹添加极片 # 正极片4个洞位2x2布局
for i in range(4): zhengji_zip = MagazineHolder_4_Cathode("正极&铝箔弹夹")
jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1) self.assign_child_resource(zhengji_zip, Coordinate(x=402.0, y=830.0, z=0))
zip_dan_jia2.children[i].assign_child_resource(jipian, location=None)
for i in range(4): # 正极壳、平垫片6个洞位2x2+2布局
jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1) zhengjike_zip = MagazineHolder_6_Cathode("正极壳&平垫片弹夹")
zip_dan_jia.children[i].assign_child_resource(jipian2, location=None) self.assign_child_resource(zhengjike_zip, Coordinate(x=566.0, y=272.0, z=0))
for i in range(6):
jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1) # 负极壳、弹垫片6个洞位2x2+2布局
zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None) fujike_zip = MagazineHolder_6_Anode("负极壳&弹垫片弹夹")
for i in range(6): self.assign_child_resource(fujike_zip, Coordinate(x=474.0, y=276.0, z=0))
jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None) # 成品弹夹6个洞位3x2布局
for i in range(6): chengpindanjia_zip = MagazineHolder_6_Battery("成品弹夹")
jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1) self.assign_child_resource(chengpindanjia_zip, Coordinate(x=260.0, y=156.0, z=0))
zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None)
for i in range(6):
jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None)
for i in range(6):
jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None)
for i in range(6):
jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None)
# ====================================== 物料板 ============================================ # ====================================== 物料板 ============================================
# 创建6个4*4的物料板 # 创建物料板料盘carrier- 4x4布局
liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True) # 负极料盘
self.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0)) fujiliaopan = MaterialPlate(name="负极料盘", size_x=120, size_y=100, size_z=10.0, fill=True)
for i in range(16): self.assign_child_resource(fujiliaopan, Coordinate(x=708.0, y=794.0, z=0))
jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1) # for i in range(16):
liaopan1.children[i].assign_child_resource(jipian_1, location=None) # fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
# fujiliaopan.children[i].assign_child_resource(fujipian, location=None)
liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True) # 隔膜料盘
self.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0)) gemoliaopan = MaterialPlate(name="隔膜料盘", size_x=120, size_y=100, size_z=10.0, fill=True)
self.assign_child_resource(gemoliaopan, Coordinate(x=718.0, y=918.0, z=0))
# for i in range(16):
# gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
# gemoliaopan.children[i].assign_child_resource(gemopian, location=None)
liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True)
self.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0))
liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True)
self.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0))
for i in range(16):
jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
liaopan4.children[i].assign_child_resource(jipian_4, location=None)
liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True)
self.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0))
liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True)
self.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0))
# ====================================== 瓶架、移液枪 ============================================ # ====================================== 瓶架、移液枪 ============================================
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒 # 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
bottle_rack_3x4 = BottleRack( # 奔耀上料5ml分液瓶小板 - 由奔曜跨站转运而来,不单独写,但是这里应该有一个堆栈用于摆放分液瓶小板
name="bottle_rack_3x4",
size_x=210.0, # bottle_rack_3x4 = BottleRack(
size_y=140.0, # name="bottle_rack_3x4",
size_z=100.0, # size_x=210.0,
num_items_x=3, # size_y=140.0,
num_items_y=4, # size_z=100.0,
position_spacing=35.0, # num_items_x=2,
orientation="vertical", # num_items_y=4,
) # position_spacing=35.0,
self.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0)) # orientation="vertical",
# )
bottle_rack_6x2 = BottleRack( # self.assign_child_resource(bottle_rack_3x4, Coordinate(x=1542.0, y=717.0, z=0))
name="bottle_rack_6x2",
size_x=120.0, # 电解液缓存位 - 6x2布局
size_y=250.0, bottle_rack_6x2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2")
size_z=100.0, self.assign_child_resource(bottle_rack_6x2, Coordinate(x=1050.0, y=358.0, z=0))
num_items_x=6, # 电解液回收位6x2
num_items_y=2, bottle_rack_6x2_2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2_2")
position_spacing=35.0, self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=914.0, y=358.0, z=0))
orientation="vertical",
)
self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0))
bottle_rack_6x2_2 = BottleRack(
name="bottle_rack_6x2_2",
size_x=120.0,
size_y=250.0,
size_z=100.0,
num_items_x=6,
num_items_y=2,
position_spacing=35.0,
orientation="vertical",
)
self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0))
# 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位
for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y):
sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1)
bottle_rack_3x4.assign_child_resource(sheet, index=idx)
for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y):
sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1)
bottle_rack_6x2.assign_child_resource(sheet, index=idx)
tip_box = TipBox64(name="tip_box_64") tip_box = TipBox64(name="tip_box_64")
self.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0)) self.assign_child_resource(tip_box, Coordinate(x=782.0, y=514.0, z=0))
waste_tip_box = WasteTipBox(name="waste_tip_box") waste_tip_box = WasteTipBox(name="waste_tip_box")
self.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0)) self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0))
print(self)
def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, size_y: float = 1000.0, size_z: float = 900.0) -> CoincellDeck:
"""创建并配置标准的纽扣电池组装工作站台面
Args:
name: 台面名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
Returns:
已配置好的 CoincellDeck 对象
"""
# 创建 CoincellDeck 实例并自动执行 setup 配置
deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z, setup=True)
return deck
if __name__ == "__main__": if __name__ == "__main__":
deck = create_coin_cell_deck() deck = create_coin_cell_deck()
print(deck) print(deck)

View File

@@ -139,7 +139,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
time.sleep(2) time.sleep(2)
if not modbus_client.client.is_socket_open(): if not modbus_client.client.is_socket_open():
raise ValueError('modbus tcp connection failed') raise ValueError('modbus tcp connection failed')
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv')) self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv'))
self.client = modbus_client.register_node_list(self.nodes) self.client = modbus_client.register_node_list(self.nodes)
else: else:
print("测试模式,跳过连接") print("测试模式,跳过连接")
@@ -791,7 +791,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
logger.debug(f"data_electrolyte_code: {data_electrolyte_code}") logger.debug(f"data_electrolyte_code: {data_electrolyte_code}")
logger.debug(f"data_coin_cell_code: {data_coin_cell_code}") logger.debug(f"data_coin_cell_code: {data_coin_cell_code}")
#接收完信息后读取完毕标志位置True #接收完信息后读取完毕标志位置True
liaopan3 = self.deck.get_resource("chengpindanjia") liaopan3 = self.deck.get_resource("成品弹夹")
#把物料解绑后放到另一盘上 #把物料解绑后放到另一盘上
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2) battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2)
battery._unilabos_state = { battery._unilabos_state = {
@@ -1008,7 +1008,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
# time.sleep(1) # time.sleep(1)
# time.sleep(40) # time.sleep(40)
# 数据读取与输出 # 数据读取与输出
def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): def func_read_data_and_output(self, file_path: str="/Users/sml/work"):
# 检查CSV导出是否正在运行已运行则跳出防止同时启动两个while循环 # 检查CSV导出是否正在运行已运行则跳出防止同时启动两个while循环
if self.csv_export_running: if self.csv_export_running:
return False, "读取已在运行中" return False, "读取已在运行中"

View File

@@ -4,7 +4,6 @@ bioyond_cell:
class: class:
action_value_mappings: action_value_mappings:
auto-auto_batch_outbound_from_xlsx: auto-auto_batch_outbound_from_xlsx:
display_name: 批量导入上料
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: goal_default:
@@ -138,7 +137,7 @@ bioyond_cell:
WH4_x5_y1_z1_5_quantity: 0.0 WH4_x5_y1_z1_5_quantity: 0.0
WH4_x5_y2_z1_10_materialName: '' WH4_x5_y2_z1_10_materialName: ''
WH4_x5_y2_z1_10_quantity: 0.0 WH4_x5_y2_z1_10_quantity: 0.0
xlsx_path: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx xlsx_path: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -464,7 +463,7 @@ bioyond_cell:
default: 0.0 default: 0.0
type: number type: number
xlsx_path: xlsx_path:
default: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx default: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
type: string type: string
required: [] required: []
type: object type: object
@@ -600,6 +599,7 @@ bioyond_cell:
bottle_type: null bottle_type: null
location_code: null location_code: null
name: null name: null
warehouse_name: 手动堆栈
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -617,6 +617,9 @@ bioyond_cell:
type: string type: string
name: name:
type: string type: string
warehouse_name:
default: 手动堆栈
type: string
required: required:
- name - name
- board_type - board_type
@@ -785,6 +788,39 @@ bioyond_cell:
title: report_material_change参数 title: report_material_change参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-resource_tree_transfer:
feedback: {}
goal: {}
goal_default:
old_parent: null
parent_resource: null
plr_resource: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
old_parent:
type: object
parent_resource:
type: object
plr_resource:
type: object
required:
- old_parent
- plr_resource
- parent_resource
type: object
result: {}
required:
- goal
title: resource_tree_transfer参数
type: object
type: UniLabJsonCommand
auto-scheduler_continue: auto-scheduler_continue:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -1072,12 +1108,13 @@ bioyond_cell:
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
status_types: {} status_types:
device_id: String
type: python type: python
config_info: [] config_info: []
description: '' description: ''
handles: [] handles: []
icon: '' icon: benyao2.webp
init_param_schema: init_param_schema:
config: config:
properties: properties:
@@ -1090,8 +1127,11 @@ bioyond_cell:
required: [] required: []
type: object type: object
data: data:
properties: {} properties:
required: [] device_id:
type: string
required:
- device_id
type: object type: object
registry_type: device registry_type: device
version: 1.0.0 version: 1.0.0

View File

@@ -502,7 +502,7 @@ coincellassemblyworkstation_device:
config_info: [] config_info: []
description: '' description: ''
handles: [] handles: []
icon: coin_cell_assembly_picture.webp icon: koudian.webp
init_param_schema: init_param_schema:
config: config:
properties: properties:

View File

View File

@@ -52,6 +52,15 @@ class Magazine(ResourceStack):
def size_z(self) -> float: def size_z(self) -> float:
return self.get_size_z() 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): class MagazineHolder(ItemizedResource):
"""子弹夹类 - 有多个洞位,每个洞位放多个极片""" """子弹夹类 - 有多个洞位,每个洞位放多个极片"""

View File

@@ -95,13 +95,13 @@ class BIOYOND_YB_Deck(Deck):
} }
# warehouse 的位置 # warehouse 的位置
self.warehouse_locations = { self.warehouse_locations = {
"自动堆栈-左": Coordinate(-300.0, 158.0, 0.0), "自动堆栈-左": Coordinate(-100.3, 171.5, 0.0),
"自动堆栈-右": Coordinate(4160.0, 158.0, 0.0), "自动堆栈-右": Coordinate(3960.1, 155.9, 0.0),
"手动堆栈-左": Coordinate(-400.0, 877.0, 0.0), "手动堆栈-左": Coordinate(-213.3, 804.4, 0.0),
"手动堆栈-右": Coordinate(4160.0, 877.0, 0.0), "手动堆栈-右": Coordinate(3960.1, 807.6, 0.0),
"粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0), "粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0),
"配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0), "配液站内试剂仓库": Coordinate(2162.0, 437.0, 0.0),
"试剂替换仓库": Coordinate(1173.0, 702.0, 0.0), "试剂替换仓库": Coordinate(1173.0, 802.0, 0.0),
} }
for warehouse_name, warehouse in self.warehouses.items(): for warehouse_name, warehouse in self.warehouses.items():