49 Commits

Author SHA1 Message Date
Calvin Cao
d2a30fe33b Merge pull request #177 from sun7151887/yb4-fix
Yb4默认仿真机
2025-11-27 18:49:41 +08:00
dijkstra402
096875e910 默认仿真机 2025-11-27 18:22:46 +08:00
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
dijkstra402
ee4ed26846 Merge branch 'workstation_dev_YB4' of https://github.com/dptech-corp/Uni-Lab-OS into workstation_dev_YB4 2025-11-11 09:54:22 +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
dijkstra402
19dffcb5db 更新YB_Deck堆栈坐标位置,根据图片像素坐标映射到实际尺寸 2025-11-10 18:57:10 +08:00
dijkstra402
b441362cd2 Merge branch 'workstation_dev_YB4' of https://github.com/dptech-corp/Uni-Lab-OS into workstation_dev_YB4 2025-11-10 18:35:41 +08:00
dijkstra402
ed53ef2f64 Update bioyond_cell and YAML configurations: modified default Excel paths and added new bottle carrier resources. Removed unused fields and updated descriptions for clarity. 2025-11-10 18:35:37 +08:00
dijkstra402
0c9f26e8fc Update Excel files: modified bioyond_cell and material_template with new data 2025-11-10 18:35:21 +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
calvincao
beb90f20d2 Update coin cell assembly configuration: change CSV file reference and modify resource names; enhance workstation initialization and packing functions. 2025-11-09 20:56:12 +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
4a2d862333 Merge pull request #157 from sun7151887/fix/yb3-material-names-and-model
Update YB resources: add YB_ prefix to models and update deck configu…
2025-11-09 17:11:24 +08:00
dijkstra402
538891fcbe Update YB resources: add YB_ prefix to models and update deck configurations 2025-11-09 17:04:52 +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
Calvin Cao
1d77225912 Merge branch 'workstation_dev_YB4' into workstation_dev_YB3 2025-11-09 15:48:22 +08:00
Calvin Cao
06e6ab0b7f Merge pull request #155 from sun7151887/fix/yb3-material-names-and-model
Fix warehouse mapping: use actual parent warehouse name instead of ha…
2025-11-09 15:15:55 +08:00
dijkstra402
5399c6c1cf Fix warehouse mapping: use actual parent warehouse name instead of hardcoded '手动堆栈' 2025-11-09 15:13:20 +08:00
Junhan Chang
f872d3ef56 add electrode_sheets definition, and fix magazines 2025-11-09 01:00:05 +08:00
Xuwznln
9bba4620b7 fix resource_get param 2025-11-08 14:39:36 +08:00
Xuwznln
d7494ca458 fix json dumps 2025-11-08 13:39:15 +08:00
Xuwznln
85dc46cd38 support name change during materials change 2025-11-08 13:39:13 +08:00
Xuwznln
5a0c2f9850 enable slave mode 2025-11-08 13:39:11 +08:00
Xuwznln
d897d70c3e change uuid logger to trace level 2025-11-08 13:39:09 +08:00
Xuwznln
d9dffc6bf8 correct remove_resource stats 2025-11-08 13:39:07 +08:00
Xuwznln
30b202bea0 disable slave connect websocket 2025-11-08 13:39:05 +08:00
Xuwznln
1b2c0dbcd7 adjust with_children param 2025-11-08 13:39:04 +08:00
Xuwznln
0f341e9b4d modify devices to use correct executor (sleep, create_task) 2025-11-08 13:39:01 +08:00
Xuwznln
4c3972820b support sleep and create_task in node 2025-11-08 13:39:00 +08:00
Xuwznln
a2a8ee9088 fix run async execution error 2025-11-08 13:39:00 +08:00
Junhan Chang
966b51042d rename and fix all Yihua Materials: ClipMagazineHole→Magazine(ResourceStack), and use factory functions 2025-11-06 00:59:46 +08:00
Calvin Cao
d81638e20b Merge pull request #148 from lixinyu1011/workstation_dev_YB4
YB4branc_bylixinyu
2025-11-04 20:27:30 +08:00
lixinyu1011
3c583008aa YB4branc_bylixinyu 2025-11-04 20:19:27 +08:00
Calvin Cao
1d4e4c8377 Merge pull request #146 from sun7151887/feature/update-yb-deck-coordinates
依华扣电工站物料信息正确
2025-11-04 02:05:36 +08:00
Calvin Cao
54f749bcdb Merge branch 'workstation_dev_YB4' into feature/update-yb-deck-coordinates 2025-11-04 02:05:18 +08:00
dijkstra402
16ad4bbecc 更新奔耀和依华工站的Deck坐标配置
- 更新奔耀YB工站deck坐标(基于图片像素精确计算)
  * 将粉末加样头堆栈拆分为左右两部分
  * 将试剂替换仓库拆分为左右两部分
  * 更新所有堆栈的坐标位置

- 更新依华扣电工站deck坐标(使用精确的像素-毫米转换)
  * 修正所有子弹夹的坐标位置(铝箔、正极片、正极壳等)
  * 更新料盘坐标(负极料盘、隔膜料盘)
  * 更新瓶架坐标(奔耀上料瓶架、电解液缓存位、回收位)
  * 更新枪头盒和废枪头盒坐标
  * 确保所有坐标在deck范围内(3650×1550mm)

- 转换比例说明:
  * 奔耀工站:deck左上角(206,446),使用1.56mm/像素
  * 依华工站:deck左上角(494,444)到右下角(2430,1608)
    X方向:1.885mm/像素,Y方向:1.332mm/像素
2025-11-04 02:01:44 +08:00
26 changed files with 1450 additions and 1646 deletions

View File

@@ -99,7 +99,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine_four", "type": "MagazineHolder_4",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -140,7 +140,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -235,7 +235,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -330,7 +330,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -425,7 +425,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -523,7 +523,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine_four", "type": "MagazineHolder_4",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -564,7 +564,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -659,7 +659,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -754,7 +754,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -849,7 +849,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -949,7 +949,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -992,7 +992,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1087,7 +1087,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1182,7 +1182,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1277,7 +1277,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1372,7 +1372,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1467,7 +1467,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1567,7 +1567,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -1610,7 +1610,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1705,7 +1705,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1800,7 +1800,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1895,7 +1895,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1990,7 +1990,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2085,7 +2085,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2185,7 +2185,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -2228,7 +2228,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2323,7 +2323,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2418,7 +2418,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2513,7 +2513,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2608,7 +2608,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2703,7 +2703,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2803,7 +2803,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -2846,7 +2846,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2941,7 +2941,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3036,7 +3036,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3131,7 +3131,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3226,7 +3226,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3321,7 +3321,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3421,7 +3421,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -3464,7 +3464,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3559,7 +3559,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3654,7 +3654,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3749,7 +3749,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3844,7 +3844,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3939,7 +3939,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4039,7 +4039,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -4082,7 +4082,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4177,7 +4177,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4272,7 +4272,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4367,7 +4367,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4462,7 +4462,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4557,7 +4557,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,

54
new_cellconfig.json Normal file
View File

@@ -0,0 +1,54 @@
{
"nodes": [
{
"id": "BatteryStation",
"name": "扣电工作站",
"parent": null,
"children": [
"coin_cell_deck"
],
"type": "device",
"class":"coincellassemblyworkstation_device",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"deck": {
"data": {
"_resource_child_name": "YB_YH_Deck",
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
}
},
"debug_mode": true,
"protocol_type": []
}
},
{
"id": "YB_YH_Deck",
"name": "YB_YH_Deck",
"children": [],
"parent": "BatteryStation",
"type": "deck",
"class": "CoincellDeck",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "CoincellDeck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {}
}
],
"links": []
}

View File

@@ -1,3 +1,4 @@
{ {
"nodes": [ "nodes": [
{ {
@@ -47,22 +48,51 @@
{ {
"id": "BatteryStation", "id": "BatteryStation",
"name": "扣电工作站", "name": "扣电工作站",
"parent": null,
"children": [ "children": [
"coin_cell_deck" "coin_cell_deck"
], ],
"parent": null,
"type": "device", "type": "device",
"class": "coincellassemblyworkstation_device", "class":"coincellassemblyworkstation_device",
"position": {
"x": 600,
"y": 400,
"z": 0
},
"config": { "config": {
"debug_mode": false, "deck": {
"data": {
"_resource_child_name": "YB_YH_Deck",
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
}
},
"protocol_type": [] "protocol_type": []
},
"position": {
"size": {"height": 1450, "width": 1450, "depth": 2100},
"position": {
"x": -1500,
"y": 0,
"z": 0
}
} }
},
{
"id": "YB_YH_Deck",
"name": "YB_YH_Deck",
"children": [],
"parent": "BatteryStation",
"type": "deck",
"class": "CoincellDeck",
"config": {
"type": "CoincellDeck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {}
} }
], ],
"links": [] "links": []
} }

View File

@@ -11,12 +11,14 @@ 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 (
API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS
) )
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
from unilabos.resources.bioyond.decks import BIOYOND_YB_Deck
from unilabos.utils.log import logger from unilabos.utils.log import logger
from unilabos.registry.registry import lab_registry from unilabos.registry.registry import lab_registry
@@ -543,6 +545,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
@@ -576,9 +586,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) # 自动汇总
@@ -594,7 +604,8 @@ class BioyondCellWorkstation(BioyondWorkstation):
print(f"[create_orders] ⚠️ 第 {idx+1} 行未找到有效物料") print(f"[create_orders] ⚠️ 第 {idx+1} 行未找到有效物料")
orders.append(order_data) orders.append(order_data)
print("================================================")
print("orders:", orders)
print(f"[create_orders] 即将提交订单数量: {len(orders)}") print(f"[create_orders] 即将提交订单数量: {len(orders)}")
response = self._post_lims("/api/lims/order/orders", orders) response = self._post_lims("/api/lims/order/orders", orders)
@@ -1075,7 +1086,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
if bottle_moudle == moudle_name: if bottle_moudle == moudle_name:
bottle_type = key bottle_type = key
break break
self.create_sample(plr_resource.name, board_type,bottle_type,site)
# 从 parent_resource 获取仓库名称
warehouse_name = parent_resource.name if parent_resource else "手动堆栈"
logger.info(f"拖拽上料: {plr_resource.name} -> {warehouse_name} / {site}")
self.create_sample(plr_resource.name, board_type, bottle_type, site, warehouse_name)
return return
self.lab_logger().warning(f"无库位的上料,不处理,{plr_resource} 挂载到 {parent_resource}") self.lab_logger().warning(f"无库位的上料,不处理,{plr_resource} 挂载到 {parent_resource}")
@@ -1084,17 +1100,31 @@ class BioyondCellWorkstation(BioyondWorkstation):
name: str, name: str,
board_type: str, board_type: str,
bottle_type: str, bottle_type: str,
location_code: str location_code: str,
warehouse_name: str = "手动堆栈"
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""创建配液板物料并自动入库。 """创建配液板物料并自动入库。
Args: Args:
material_name: 物料名称,支持 "5ml分液瓶板"/"5ml分液瓶""配液瓶(小)板"/"配液瓶(小)" name: 物料名称
quantity: 主物料与明细的数量,默认 1。 board_type: 板类型,如 "5ml分液瓶板""配液瓶(小)板"
location_code: 库位编号,例如 "A01",将自动映射为 "手动堆栈" 下的 UUID。 bottle_type: 瓶类型,如 "5ml分液瓶""配液瓶(小)"
location_code: 库位编号,例如 "A01"
warehouse_name: 仓库名称,默认为 "手动堆栈",支持 "自动堆栈-左""自动堆栈-右"
""" """
carrier_type_id = MATERIAL_TYPE_MAPPINGS[board_type][1] carrier_type_id = MATERIAL_TYPE_MAPPINGS[board_type][1]
bottle_type_id = MATERIAL_TYPE_MAPPINGS[bottle_type][1] bottle_type_id = MATERIAL_TYPE_MAPPINGS[bottle_type][1]
location_id = WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"][location_code]
# 从指定仓库获取库位UUID
if warehouse_name not in WAREHOUSE_MAPPING:
logger.error(f"未找到仓库: {warehouse_name},回退到手动堆栈")
warehouse_name = "手动堆栈"
if location_code not in WAREHOUSE_MAPPING[warehouse_name]["site_uuids"]:
logger.error(f"仓库 {warehouse_name} 中未找到库位 {location_code}")
raise ValueError(f"库位 {location_code} 在仓库 {warehouse_name} 中不存在")
location_id = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"][location_code]
logger.info(f"创建样品入库: {name} -> {warehouse_name}/{location_code} (UUID: {location_id})")
# 新建小瓶 # 新建小瓶
details = [] details = []
@@ -1137,19 +1167,14 @@ class BioyondCellWorkstation(BioyondWorkstation):
if __name__ == "__main__": if __name__ == "__main__":
lab_registry.setup() lab_registry.setup()
ws = BioyondCellWorkstation() deck = BIOYOND_YB_Deck(setup=True)
ws = BioyondCellWorkstation(deck=deck)
# ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01") # ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01")
# logger.info(ws.scheduler_stop()) # logger.info(ws.scheduler_stop())
# logger.info(ws.scheduler_start()) # logger.info(ws.scheduler_start())
# results = ws.create_materials(SOLID_LIQUID_MAPPINGS)
# for r in results:
# logger.info(r)
# 从CSV文件读取物料列表并批量创建入库
# result = ws.create_and_inbound_materials()
# 继续后续流程 # 继续后续流程
# 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("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx")
# logger.info(ws.create_orders(excel_path)) # logger.info(ws.create_orders(excel_path))

View File

@@ -113,7 +113,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine_four", "type": "MagazineHolder_4",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -154,7 +154,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -249,7 +249,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -344,7 +344,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -439,7 +439,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -537,7 +537,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine_four", "type": "MagazineHolder_4",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -578,7 +578,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -673,7 +673,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -768,7 +768,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -863,7 +863,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -963,7 +963,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -1006,7 +1006,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1101,7 +1101,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1196,7 +1196,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1291,7 +1291,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1386,7 +1386,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1481,7 +1481,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1581,7 +1581,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -1624,7 +1624,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1719,7 +1719,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1814,7 +1814,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -1909,7 +1909,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2004,7 +2004,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2099,7 +2099,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2199,7 +2199,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -2242,7 +2242,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2337,7 +2337,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2432,7 +2432,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2527,7 +2527,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2622,7 +2622,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2717,7 +2717,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2817,7 +2817,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -2860,7 +2860,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -2955,7 +2955,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3050,7 +3050,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3145,7 +3145,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3240,7 +3240,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3335,7 +3335,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3435,7 +3435,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -3478,7 +3478,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3573,7 +3573,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3668,7 +3668,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3763,7 +3763,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3858,7 +3858,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -3953,7 +3953,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4053,7 +4053,7 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"type": "ClipMagazine", "type": "MagazineHolder_6",
"size_x": 80, "size_x": 80,
"size_y": 80, "size_y": 80,
"size_z": 10, "size_z": 10,
@@ -4096,7 +4096,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4191,7 +4191,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4286,7 +4286,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4381,7 +4381,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4476,7 +4476,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,
@@ -4571,7 +4571,7 @@
"z": 10 "z": 10
}, },
"config": { "config": {
"type": "ClipMagazineHole", "type": "Magazine",
"size_x": 14.0, "size_x": 14.0,
"size_y": 14.0, "size_y": 14.0,
"size_z": 10.0, "size_z": 10.0,

View File

@@ -9,7 +9,7 @@ import os
API_CONFIG = { API_CONFIG = {
# API 连接配置 # API 连接配置
# "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机 # "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.7.149:44388"),# 仿真机 "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),# 仿真机
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), "api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")), "timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
@@ -17,7 +17,7 @@ API_CONFIG = {
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), "report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
# HTTP 服务配置 # HTTP 服务配置
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.2.140"), # HTTP服务监听地址监听计算机飞连ip地址 "HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.2"), # HTTP服务监听地址监听计算机飞连ip地址
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
"debug_mode": False,# 调试模式 "debug_mode": False,# 调试模式
} }
@@ -237,7 +237,7 @@ MATERIAL_TYPE_MAPPINGS = {
"100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"), "100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
"": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"), "": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
"高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"), "高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
"加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"), "加样头(大)": ("YB_jia_yang_tou_da_Carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"), # "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
"5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"), "5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
"5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"), "5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"),

View File

@@ -18,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):
@@ -165,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
@@ -327,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):
"""电池状态字典""" """电池状态字典"""
@@ -595,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):
@@ -682,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,
): ):
"""初始化废枪头盒 """初始化废枪头盒
@@ -733,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
): ):
@@ -1006,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()
@@ -1018,146 +571,67 @@ 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__":

View File

@@ -109,44 +109,22 @@ def _coerce_deck_input(deck: Any) -> Optional[Deck]:
#构建物料系统 #构建物料系统
class CoinCellAssemblyWorkstation(WorkstationBase): class CoinCellAssemblyWorkstation(WorkstationBase):
def __init__( def __init__(self,
self, config: dict = None,
deck: Deck=None, deck=None,
address: str = "172.16.28.102", address: str = "172.16.28.102",
port: str = "502", port: str = "502",
debug_mode: bool = False, debug_mode: bool = False,
*args, *args,
**kwargs, **kwargs):
):
if deck is None and "deck" in kwargs:
deck = kwargs.pop("deck")
else:
kwargs.pop("deck", None)
normalized_deck = _coerce_deck_input(deck) if deck is None and config:
deck = config.get('deck')
if deck is None and isinstance(normalized_deck, Deck): if deck is None:
deck = normalized_deck logger.info("没有传入依华deck检查启动json文件")
super().__init__(deck=deck, *args, **kwargs,)
super().__init__(
#桌子
deck=deck,
*args,
**kwargs,
)
self.debug_mode = debug_mode self.debug_mode = debug_mode
# 如果没有传入 deck则创建标准配置的 deck
if self.deck is None:
self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-800, 0, 0),setup=True)
else:
# 如果传入了 deck 但还没有 setup可以选择是否 setup
if self.deck is not None and len(self.deck.children) == 0:
# deck 为空,执行 setup
self.deck.setup()
# 否则使用传入的 deck可能已经配置好了
self.deck = self.deck
""" 连接初始化 """ """ 连接初始化 """
modbus_client = TCPClient(addr=address, port=port) modbus_client = TCPClient(addr=address, port=port)
logger.debug(f"创建 Modbus 客户端: {modbus_client}") logger.debug(f"创建 Modbus 客户端: {modbus_client}")
@@ -161,26 +139,21 @@ 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.client = modbus_client.register_node_list(self.nodes)
else: else:
print("测试模式,跳过连接") print("测试模式,跳过连接")
self.nodes, self.client = None, None
""" 工站的配置 """ """ 工站的配置 """
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv'))
self.client = modbus_client.register_node_list(self.nodes)
self.success = False self.success = False
self.allow_data_read = False #允许读取函数运行标志位 self.allow_data_read = False #允许读取函数运行标志位
self.csv_export_thread = None self.csv_export_thread = None
self.csv_export_running = False self.csv_export_running = False
self.csv_export_file = None self.csv_export_file = None
self.coin_num_N = 0 #已组装电池数量 self.coin_num_N = 0 #已组装电池数量
#创建一个物料台面,包含两个极片板
#self._ros_node.update_resource(self.deck)
#ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
# "resources": [self.deck]
#})
def post_init(self, ros_node: ROS2WorkstationNode): def post_init(self, ros_node: ROS2WorkstationNode):
self._ros_node = ros_node self._ros_node = ros_node
#self.deck = create_a_coin_cell_deck() #self.deck = create_a_coin_cell_deck()
@@ -818,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("\u7535\u6c60\u6599\u76d8") 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 = {
@@ -1035,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, "读取已在运行中"
@@ -1233,7 +1206,13 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
if __name__ == "__main__": if __name__ == "__main__":
# 简单测试 # 简单测试
workstation = CoinCellAssemblyWorkstation() workstation = CoinCellAssemblyWorkstation(deck=CoincellDeck(setup=True, name="coin_cell_deck"))
workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False) # workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False)
print(f"工作站创建成功: {workstation.deck.name}") # print(f"工作站创建成功: {workstation.deck.name}")
print(f"料盘数量: {len(workstation.deck.children)}") # print(f"料盘数量: {len(workstation.deck.children)}")
workstation.func_pack_device_init()
workstation.func_pack_device_auto()
workstation.func_pack_device_start()
workstation.func_pack_send_bottle_num(16)
workstation.func_allpack_cmd(elec_num=16, elec_use_num=16, elec_vol=50, assembly_type=7, assembly_pressure=4200, file_path="/Users/calvincao/Desktop/work/Uni-Lab-OS-hhm")

View File

@@ -1,583 +0,0 @@
"""
工作站物料管理基类
Workstation Material Management Base Class
基于PyLabRobot的物料管理系统
"""
from typing import Dict, Any, List, Optional, Union, Type
from abc import ABC, abstractmethod
import json
from pylabrobot.resources import (
Resource as PLRResource,
Container,
Deck,
Coordinate as PLRCoordinate,
)
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
from unilabos.utils.log import logger
from unilabos.resources.graphio import resource_plr_to_ulab, resource_ulab_to_plr
class MaterialManagementBase(ABC):
"""物料管理基类
定义工作站物料管理的标准接口:
1. 物料初始化 - 根据配置创建物料资源
2. 物料追踪 - 实时跟踪物料位置和状态
3. 物料查找 - 按类型、位置、状态查找物料
4. 物料转换 - PyLabRobot与UniLab资源格式转换
"""
def __init__(
self,
device_id: str,
deck_config: Dict[str, Any],
resource_tracker: DeviceNodeResourceTracker,
children_config: Dict[str, Dict[str, Any]] = None
):
self.device_id = device_id
self.deck_config = deck_config
self.resource_tracker = resource_tracker
self.children_config = children_config or {}
# 创建主台面
self.plr_deck = self._create_deck()
# 扩展ResourceTracker
self._extend_resource_tracker()
# 注册deck到resource tracker
self.resource_tracker.add_resource(self.plr_deck)
# 初始化子资源
self.plr_resources = {}
self._initialize_materials()
def _create_deck(self) -> Deck:
"""创建主台面"""
return Deck(
name=f"{self.device_id}_deck",
size_x=self.deck_config.get("size_x", 1000.0),
size_y=self.deck_config.get("size_y", 1000.0),
size_z=self.deck_config.get("size_z", 500.0),
origin=PLRCoordinate(0, 0, 0)
)
def _extend_resource_tracker(self):
"""扩展ResourceTracker以支持PyLabRobot特定功能"""
def find_by_type(resource_type):
"""按类型查找资源"""
return self._find_resources_by_type_recursive(self.plr_deck, resource_type)
def find_by_category(category: str):
"""按类别查找资源"""
found = []
for resource in self._get_all_resources():
if hasattr(resource, 'category') and resource.category == category:
found.append(resource)
return found
def find_by_name_pattern(pattern: str):
"""按名称模式查找资源"""
import re
found = []
for resource in self._get_all_resources():
if re.search(pattern, resource.name):
found.append(resource)
return found
# 动态添加方法到resource_tracker
self.resource_tracker.find_by_type = find_by_type
self.resource_tracker.find_by_category = find_by_category
self.resource_tracker.find_by_name_pattern = find_by_name_pattern
def _find_resources_by_type_recursive(self, resource, target_type):
"""递归查找指定类型的资源"""
found = []
if isinstance(resource, target_type):
found.append(resource)
# 递归查找子资源
children = getattr(resource, "children", [])
for child in children:
found.extend(self._find_resources_by_type_recursive(child, target_type))
return found
def _get_all_resources(self) -> List[PLRResource]:
"""获取所有资源"""
all_resources = []
def collect_resources(resource):
all_resources.append(resource)
children = getattr(resource, "children", [])
for child in children:
collect_resources(child)
collect_resources(self.plr_deck)
return all_resources
def _initialize_materials(self):
"""初始化物料"""
try:
# 确定创建顺序,确保父资源先于子资源创建
creation_order = self._determine_creation_order()
# 按顺序创建资源
for resource_id in creation_order:
config = self.children_config[resource_id]
self._create_plr_resource(resource_id, config)
logger.info(f"物料管理系统初始化完成,共创建 {len(self.plr_resources)} 个资源")
except Exception as e:
logger.error(f"物料初始化失败: {e}")
def _determine_creation_order(self) -> List[str]:
"""确定资源创建顺序"""
order = []
visited = set()
def visit(resource_id: str):
if resource_id in visited:
return
visited.add(resource_id)
config = self.children_config.get(resource_id, {})
parent_id = config.get("parent")
# 如果有父资源,先访问父资源
if parent_id and parent_id in self.children_config:
visit(parent_id)
order.append(resource_id)
for resource_id in self.children_config:
visit(resource_id)
return order
def _create_plr_resource(self, resource_id: str, config: Dict[str, Any]):
"""创建PyLabRobot资源"""
try:
resource_type = config.get("type", "unknown")
data = config.get("data", {})
location_config = config.get("location", {})
# 创建位置坐标
location = PLRCoordinate(
x=location_config.get("x", 0.0),
y=location_config.get("y", 0.0),
z=location_config.get("z", 0.0)
)
# 根据类型创建资源
resource = self._create_resource_by_type(resource_id, resource_type, config, data, location)
if resource:
# 设置父子关系
parent_id = config.get("parent")
if parent_id and parent_id in self.plr_resources:
parent_resource = self.plr_resources[parent_id]
parent_resource.assign_child_resource(resource, location)
else:
# 直接放在deck上
self.plr_deck.assign_child_resource(resource, location)
# 保存资源引用
self.plr_resources[resource_id] = resource
# 注册到resource tracker
self.resource_tracker.add_resource(resource)
logger.debug(f"创建资源成功: {resource_id} ({resource_type})")
except Exception as e:
logger.error(f"创建资源失败 {resource_id}: {e}")
@abstractmethod
def _create_resource_by_type(
self,
resource_id: str,
resource_type: str,
config: Dict[str, Any],
data: Dict[str, Any],
location: PLRCoordinate
) -> Optional[PLRResource]:
"""根据类型创建资源 - 子类必须实现"""
pass
# ============ 物料查找接口 ============
def find_materials_by_type(self, material_type: str) -> List[PLRResource]:
"""按材料类型查找物料"""
return self.resource_tracker.find_by_category(material_type)
def find_material_by_id(self, resource_id: str) -> Optional[PLRResource]:
"""按ID查找物料"""
return self.plr_resources.get(resource_id)
def find_available_positions(self, position_type: str) -> List[PLRResource]:
"""查找可用位置"""
positions = self.resource_tracker.find_by_category(position_type)
available = []
for pos in positions:
if hasattr(pos, 'is_available') and pos.is_available():
available.append(pos)
elif hasattr(pos, 'children') and len(pos.children) == 0:
available.append(pos)
return available
def get_material_inventory(self) -> Dict[str, int]:
"""获取物料库存统计"""
inventory = {}
for resource in self._get_all_resources():
if hasattr(resource, 'category'):
category = resource.category
inventory[category] = inventory.get(category, 0) + 1
return inventory
# ============ 物料状态更新接口 ============
def update_material_location(self, material_id: str, new_location: PLRCoordinate) -> bool:
"""更新物料位置"""
try:
material = self.find_material_by_id(material_id)
if material:
material.location = new_location
return True
return False
except Exception as e:
logger.error(f"更新物料位置失败: {e}")
return False
def move_material(self, material_id: str, target_container_id: str) -> bool:
"""移动物料到目标容器"""
try:
material = self.find_material_by_id(material_id)
target = self.find_material_by_id(target_container_id)
if material and target:
# 从原位置移除
if material.parent:
material.parent.unassign_child_resource(material)
# 添加到新位置
target.assign_child_resource(material)
return True
return False
except Exception as e:
logger.error(f"移动物料失败: {e}")
return False
# ============ 资源转换接口 ============
def convert_to_unilab_format(self, plr_resource: PLRResource) -> Dict[str, Any]:
"""将PyLabRobot资源转换为UniLab格式"""
return resource_plr_to_ulab(plr_resource)
def convert_from_unilab_format(self, unilab_resource: Dict[str, Any]) -> PLRResource:
"""将UniLab格式转换为PyLabRobot资源"""
return resource_ulab_to_plr(unilab_resource)
def get_deck_state(self) -> Dict[str, Any]:
"""获取Deck状态"""
try:
return {
"deck_info": {
"name": self.plr_deck.name,
"size": {
"x": self.plr_deck.size_x,
"y": self.plr_deck.size_y,
"z": self.plr_deck.size_z
},
"children_count": len(self.plr_deck.children)
},
"resources": {
resource_id: self.convert_to_unilab_format(resource)
for resource_id, resource in self.plr_resources.items()
},
"inventory": self.get_material_inventory()
}
except Exception as e:
logger.error(f"获取Deck状态失败: {e}")
return {"error": str(e)}
# ============ 数据持久化接口 ============
def save_state_to_file(self, file_path: str) -> bool:
"""保存状态到文件"""
try:
state = self.get_deck_state()
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(state, f, indent=2, ensure_ascii=False)
logger.info(f"状态已保存到: {file_path}")
return True
except Exception as e:
logger.error(f"保存状态失败: {e}")
return False
def load_state_from_file(self, file_path: str) -> bool:
"""从文件加载状态"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
state = json.load(f)
# 重新创建资源
self._recreate_resources_from_state(state)
logger.info(f"状态已从文件加载: {file_path}")
return True
except Exception as e:
logger.error(f"加载状态失败: {e}")
return False
def _recreate_resources_from_state(self, state: Dict[str, Any]):
"""从状态重新创建资源"""
# 清除现有资源
self.plr_resources.clear()
self.plr_deck.children.clear()
# 从状态重新创建
resources_data = state.get("resources", {})
for resource_id, resource_data in resources_data.items():
try:
plr_resource = self.convert_from_unilab_format(resource_data)
self.plr_resources[resource_id] = plr_resource
self.plr_deck.assign_child_resource(plr_resource)
except Exception as e:
logger.error(f"重新创建资源失败 {resource_id}: {e}")
class CoinCellMaterialManagement(MaterialManagementBase):
"""纽扣电池物料管理类
从 button_battery_station 抽取的物料管理功能
"""
def _create_resource_by_type(
self,
resource_id: str,
resource_type: str,
config: Dict[str, Any],
data: Dict[str, Any],
location: PLRCoordinate
) -> Optional[PLRResource]:
"""根据类型创建纽扣电池相关资源"""
# 导入纽扣电池资源类
from unilabos.device_comms.button_battery_station import (
MaterialPlate, PlateSlot, ClipMagazine, BatteryPressSlot,
TipBox64, WasteTipBox, BottleRack, Battery, ElectrodeSheet
)
try:
if resource_type == "material_plate":
return self._create_material_plate(resource_id, config, data, location)
elif resource_type == "plate_slot":
return self._create_plate_slot(resource_id, config, data, location)
elif resource_type == "clip_magazine":
return self._create_clip_magazine(resource_id, config, data, location)
elif resource_type == "battery_press_slot":
return self._create_battery_press_slot(resource_id, config, data, location)
elif resource_type == "tip_box":
return self._create_tip_box(resource_id, config, data, location)
elif resource_type == "waste_tip_box":
return self._create_waste_tip_box(resource_id, config, data, location)
elif resource_type == "bottle_rack":
return self._create_bottle_rack(resource_id, config, data, location)
elif resource_type == "battery":
return self._create_battery(resource_id, config, data, location)
else:
logger.warning(f"未知的资源类型: {resource_type}")
return None
except Exception as e:
logger.error(f"创建资源失败 {resource_id} ({resource_type}): {e}")
return None
def _create_material_plate(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建料板"""
from unilabos.device_comms.button_battery_station import MaterialPlate, ElectrodeSheet
plate = MaterialPlate(
name=resource_id,
size_x=config.get("size_x", 80.0),
size_y=config.get("size_y", 80.0),
size_z=config.get("size_z", 10.0),
hole_diameter=config.get("hole_diameter", 15.0),
hole_depth=config.get("hole_depth", 8.0),
hole_spacing_x=config.get("hole_spacing_x", 20.0),
hole_spacing_y=config.get("hole_spacing_y", 20.0),
number=data.get("number", "")
)
plate.location = location
# 如果有预填充的极片数据,创建极片
electrode_sheets = data.get("electrode_sheets", [])
for i, sheet_data in enumerate(electrode_sheets):
if i < len(plate.children): # 确保不超过洞位数量
hole = plate.children[i]
sheet = ElectrodeSheet(
name=f"{resource_id}_sheet_{i}",
diameter=sheet_data.get("diameter", 14.0),
thickness=sheet_data.get("thickness", 0.1),
mass=sheet_data.get("mass", 0.01),
material_type=sheet_data.get("material_type", "cathode"),
info=sheet_data.get("info", "")
)
hole.place_electrode_sheet(sheet)
return plate
def _create_plate_slot(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建板槽位"""
from unilabos.device_comms.button_battery_station import PlateSlot
slot = PlateSlot(
name=resource_id,
max_plates=config.get("max_plates", 8)
)
slot.location = location
return slot
def _create_clip_magazine(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建子弹夹"""
from unilabos.device_comms.button_battery_station import ClipMagazine
magazine = ClipMagazine(
name=resource_id,
size_x=config.get("size_x", 150.0),
size_y=config.get("size_y", 100.0),
size_z=config.get("size_z", 50.0),
hole_diameter=config.get("hole_diameter", 15.0),
hole_depth=config.get("hole_depth", 40.0),
hole_spacing=config.get("hole_spacing", 25.0),
max_sheets_per_hole=config.get("max_sheets_per_hole", 100)
)
magazine.location = location
return magazine
def _create_battery_press_slot(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建电池压制槽"""
from unilabos.device_comms.button_battery_station import BatteryPressSlot
slot = BatteryPressSlot(
name=resource_id,
diameter=config.get("diameter", 20.0),
depth=config.get("depth", 15.0)
)
slot.location = location
return slot
def _create_tip_box(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建枪头盒"""
from unilabos.device_comms.button_battery_station import TipBox64
tip_box = TipBox64(
name=resource_id,
size_x=config.get("size_x", 127.8),
size_y=config.get("size_y", 85.5),
size_z=config.get("size_z", 60.0),
with_tips=data.get("with_tips", True)
)
tip_box.location = location
return tip_box
def _create_waste_tip_box(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建废枪头盒"""
from unilabos.device_comms.button_battery_station import WasteTipBox
waste_box = WasteTipBox(
name=resource_id,
size_x=config.get("size_x", 127.8),
size_y=config.get("size_y", 85.5),
size_z=config.get("size_z", 60.0),
max_tips=config.get("max_tips", 100)
)
waste_box.location = location
return waste_box
def _create_bottle_rack(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建瓶架"""
from unilabos.device_comms.button_battery_station import BottleRack
rack = BottleRack(
name=resource_id,
size_x=config.get("size_x", 210.0),
size_y=config.get("size_y", 140.0),
size_z=config.get("size_z", 100.0),
bottle_diameter=config.get("bottle_diameter", 30.0),
bottle_height=config.get("bottle_height", 100.0),
position_spacing=config.get("position_spacing", 35.0)
)
rack.location = location
return rack
def _create_battery(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
"""创建电池"""
from unilabos.device_comms.button_battery_station import Battery
battery = Battery(
name=resource_id,
diameter=config.get("diameter", 20.0),
height=config.get("height", 3.2),
max_volume=config.get("max_volume", 100.0),
barcode=data.get("barcode", "")
)
battery.location = location
return battery
# ============ 纽扣电池特定查找方法 ============
def find_material_plates(self):
"""查找所有料板"""
from unilabos.device_comms.button_battery_station import MaterialPlate
return self.resource_tracker.find_by_type(MaterialPlate)
def find_batteries(self):
"""查找所有电池"""
from unilabos.device_comms.button_battery_station import Battery
return self.resource_tracker.find_by_type(Battery)
def find_electrode_sheets(self):
"""查找所有极片"""
found = []
plates = self.find_material_plates()
for plate in plates:
for hole in plate.children:
if hasattr(hole, 'has_electrode_sheet') and hole.has_electrode_sheet():
found.append(hole._electrode_sheet)
return found
def find_plate_slots(self):
"""查找所有板槽位"""
from unilabos.device_comms.button_battery_station import PlateSlot
return self.resource_tracker.find_by_type(PlateSlot)
def find_clip_magazines(self):
"""查找所有子弹夹"""
from unilabos.device_comms.button_battery_station import ClipMagazine
return self.resource_tracker.find_by_type(ClipMagazine)
def find_press_slots(self):
"""查找所有压制槽"""
from unilabos.device_comms.button_battery_station import BatteryPressSlot
return self.resource_tracker.find_by_type(BatteryPressSlot)

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:
@@ -33,112 +32,7 @@ bioyond_cell:
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: goal_default:
WH3_x1_y1_z3_1_materialId: '' xlsx_path: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
WH3_x1_y1_z3_1_materialType: ''
WH3_x1_y1_z3_1_quantity: 0
WH3_x1_y2_z3_4_materialId: ''
WH3_x1_y2_z3_4_materialType: ''
WH3_x1_y2_z3_4_quantity: 0
WH3_x1_y3_z3_7_materialId: ''
WH3_x1_y3_z3_7_materialType: ''
WH3_x1_y3_z3_7_quantity: 0
WH3_x1_y4_z3_10_materialId: ''
WH3_x1_y4_z3_10_materialType: ''
WH3_x1_y4_z3_10_quantity: 0
WH3_x1_y5_z3_13_materialId: ''
WH3_x1_y5_z3_13_materialType: ''
WH3_x1_y5_z3_13_quantity: 0
WH3_x2_y1_z3_2_materialId: ''
WH3_x2_y1_z3_2_materialType: ''
WH3_x2_y1_z3_2_quantity: 0
WH3_x2_y2_z3_5_materialId: ''
WH3_x2_y2_z3_5_materialType: ''
WH3_x2_y2_z3_5_quantity: 0
WH3_x2_y3_z3_8_materialId: ''
WH3_x2_y3_z3_8_materialType: ''
WH3_x2_y3_z3_8_quantity: 0
WH3_x2_y4_z3_11_materialId: ''
WH3_x2_y4_z3_11_materialType: ''
WH3_x2_y4_z3_11_quantity: 0
WH3_x2_y5_z3_14_materialId: ''
WH3_x2_y5_z3_14_materialType: ''
WH3_x2_y5_z3_14_quantity: 0
WH3_x3_y1_z3_3_materialId: ''
WH3_x3_y1_z3_3_materialType: ''
WH3_x3_y1_z3_3_quantity: 0
WH3_x3_y2_z3_6_materialId: ''
WH3_x3_y2_z3_6_materialType: ''
WH3_x3_y2_z3_6_quantity: 0
WH3_x3_y3_z3_9_materialId: ''
WH3_x3_y3_z3_9_materialType: ''
WH3_x3_y3_z3_9_quantity: 0
WH3_x3_y4_z3_12_materialId: ''
WH3_x3_y4_z3_12_materialType: ''
WH3_x3_y4_z3_12_quantity: 0
WH3_x3_y5_z3_15_materialId: ''
WH3_x3_y5_z3_15_materialType: ''
WH3_x3_y5_z3_15_quantity: 0
WH4_x1_y1_z1_1_materialName: ''
WH4_x1_y1_z1_1_quantity: 0.0
WH4_x1_y1_z2_1_materialName: ''
WH4_x1_y1_z2_1_materialType: ''
WH4_x1_y1_z2_1_quantity: 0.0
WH4_x1_y1_z2_1_targetWH: ''
WH4_x1_y2_z1_6_materialName: ''
WH4_x1_y2_z1_6_quantity: 0.0
WH4_x1_y2_z2_4_materialName: ''
WH4_x1_y2_z2_4_materialType: ''
WH4_x1_y2_z2_4_quantity: 0.0
WH4_x1_y2_z2_4_targetWH: ''
WH4_x1_y3_z1_11_materialName: ''
WH4_x1_y3_z1_11_quantity: 0.0
WH4_x1_y3_z2_7_materialName: ''
WH4_x1_y3_z2_7_materialType: ''
WH4_x1_y3_z2_7_quantity: 0.0
WH4_x1_y3_z2_7_targetWH: ''
WH4_x2_y1_z1_2_materialName: ''
WH4_x2_y1_z1_2_quantity: 0.0
WH4_x2_y1_z2_2_materialName: ''
WH4_x2_y1_z2_2_materialType: ''
WH4_x2_y1_z2_2_quantity: 0.0
WH4_x2_y1_z2_2_targetWH: ''
WH4_x2_y2_z1_7_materialName: ''
WH4_x2_y2_z1_7_quantity: 0.0
WH4_x2_y2_z2_5_materialName: ''
WH4_x2_y2_z2_5_materialType: ''
WH4_x2_y2_z2_5_quantity: 0.0
WH4_x2_y2_z2_5_targetWH: ''
WH4_x2_y3_z1_12_materialName: ''
WH4_x2_y3_z1_12_quantity: 0.0
WH4_x2_y3_z2_8_materialName: ''
WH4_x2_y3_z2_8_materialType: ''
WH4_x2_y3_z2_8_quantity: 0.0
WH4_x2_y3_z2_8_targetWH: ''
WH4_x3_y1_z1_3_materialName: ''
WH4_x3_y1_z1_3_quantity: 0.0
WH4_x3_y1_z2_3_materialName: ''
WH4_x3_y1_z2_3_materialType: ''
WH4_x3_y1_z2_3_quantity: 0.0
WH4_x3_y1_z2_3_targetWH: ''
WH4_x3_y2_z1_8_materialName: ''
WH4_x3_y2_z1_8_quantity: 0.0
WH4_x3_y2_z2_6_materialName: ''
WH4_x3_y2_z2_6_materialType: ''
WH4_x3_y2_z2_6_quantity: 0.0
WH4_x3_y2_z2_6_targetWH: ''
WH4_x3_y3_z2_9_materialName: ''
WH4_x3_y3_z2_9_materialType: ''
WH4_x3_y3_z2_9_quantity: 0.0
WH4_x3_y3_z2_9_targetWH: ''
WH4_x4_y1_z1_4_materialName: ''
WH4_x4_y1_z1_4_quantity: 0.0
WH4_x4_y2_z1_9_materialName: ''
WH4_x4_y2_z1_9_quantity: 0.0
WH4_x5_y1_z1_5_materialName: ''
WH4_x5_y1_z1_5_quantity: 0.0
WH4_x5_y2_z1_10_materialName: ''
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
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -464,7 +358,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 +494,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 +512,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 +683,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 +1003,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 +1022,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

@@ -79,7 +79,7 @@ coincellassemblyworkstation_device:
elec_num: null elec_num: null
elec_use_num: null elec_use_num: null
elec_vol: 50 elec_vol: 50
file_path: C:\Users\67484\Desktop file_path: /Users/sml/work
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -103,7 +103,7 @@ coincellassemblyworkstation_device:
default: 50 default: 50
type: integer type: integer
file_path: file_path:
default: C:\Users\67484\Desktop default: /Users/sml/work
type: string type: string
required: required:
- elec_num - elec_num
@@ -332,7 +332,7 @@ coincellassemblyworkstation_device:
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: goal_default:
file_path: D:\coin_cell_data file_path: /Users/sml/work
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -343,7 +343,7 @@ coincellassemblyworkstation_device:
goal: goal:
properties: properties:
file_path: file_path:
default: D:\coin_cell_data default: /Users/sml/work
type: string type: string
required: [] required: []
type: object type: object
@@ -502,18 +502,20 @@ 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:
address: address:
default: 172.21.32.111 default: 172.16.28.102
type: string type: string
config:
type: object
debug_mode: debug_mode:
default: false default: false
type: boolean type: boolean
deck: deck:
type: object type: string
port: port:
default: '502' default: '502'
type: string type: string

View File

@@ -654,6 +654,31 @@ liquid_handler:
title: iter_tips参数 title: iter_tips参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: string
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-set_group: auto-set_group:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -6170,6 +6195,31 @@ liquid_handler.prcxi:
title: move_to参数 title: move_to参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-run_protocol: auto-run_protocol:
feedback: {} feedback: {}
goal: {} goal: {}

View File

@@ -45,6 +45,31 @@ virtual_centrifuge:
title: initialize参数 title: initialize参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
centrifuge: centrifuge:
feedback: feedback:
current_speed: current_speed current_speed: current_speed
@@ -335,6 +360,31 @@ virtual_column:
title: initialize参数 title: initialize参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
run_column: run_column:
feedback: feedback:
current_status: current_status current_status: current_status
@@ -732,6 +782,31 @@ virtual_filter:
title: initialize参数 title: initialize参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
filter: filter:
feedback: feedback:
current_status: current_status current_status: current_status
@@ -1358,6 +1433,31 @@ virtual_heatchill:
title: initialize参数 title: initialize参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
heat_chill: heat_chill:
feedback: feedback:
status: status status: status
@@ -2358,6 +2458,31 @@ virtual_rotavap:
title: initialize参数 title: initialize参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
evaporate: evaporate:
feedback: feedback:
current_device: current_device current_device: current_device
@@ -2690,6 +2815,31 @@ virtual_separator:
title: initialize参数 title: initialize参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
separate: separate:
feedback: feedback:
current_status: status current_status: status
@@ -3600,6 +3750,31 @@ virtual_solenoid_valve:
title: is_closed参数 title: is_closed参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-reset: auto-reset:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -4177,6 +4352,31 @@ virtual_solid_dispenser:
title: parse_mol_string参数 title: parse_mol_string参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
status_types: status_types:
current_reagent: str current_reagent: str
@@ -4278,6 +4478,31 @@ virtual_stirrer:
title: initialize参数 title: initialize参数
type: object type: object
type: UniLabJsonCommandAsync type: UniLabJsonCommandAsync
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
start_stir: start_stir:
feedback: feedback:
status: status status: status
@@ -4995,6 +5220,31 @@ virtual_transfer_pump:
title: is_full参数 title: is_full参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
ros_node:
type: object
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-pull_plunger: auto-pull_plunger:
feedback: {} feedback: {}
goal: {} goal: {}

View File

@@ -1,16 +1,3 @@
YB_qiang_tou:
category:
- yb3
- YB_bottle
class:
module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou
type: pylabrobot
description: YB_qiang_tou
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_20ml_fenyeping: YB_20ml_fenyeping:
category: category:
- yb3 - yb3
@@ -37,6 +24,19 @@ YB_5ml_fenyeping:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_jia_yang_tou_da:
category:
- yb3
- YB_bottle
class:
module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da
type: pylabrobot
description: YB_jia_yang_tou_da
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_pei_ye_da_Bottle: YB_pei_ye_da_Bottle:
category: category:
- yb3 - yb3
@@ -63,3 +63,30 @@ YB_pei_ye_xiao_Bottle:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_qiang_tou:
category:
- yb3
- YB_bottle
class:
module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou
type: pylabrobot
description: YB_qiang_tou
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_ye_Bottle:
category:
- yb3
- YB_bottle_carriers
- YB_bottle
class:
module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle
type: pylabrobot
description: YB_ye_Bottle
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0

View File

@@ -11,71 +11,6 @@ YB_100ml_yeti:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_1BottleCarrier:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier
type: pylabrobot
description: YB_1BottleCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_gaonianye:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye
type: pylabrobot
description: YB_gaonianye
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_peiyepingdaban:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban
type: pylabrobot
description: YB_peiyepingdaban
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_6StockCarrier:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier
type: pylabrobot
description: YB_6StockCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_6VialCarrier:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier
type: pylabrobot
description: YB_6VialCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_20ml_fenyepingban: YB_20ml_fenyepingban:
category: category:
- yb3 - yb3
@@ -102,40 +37,27 @@ YB_5ml_fenyepingban:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_peiyepingxiaoban: YB_6StockCarrier:
category: category:
- yb3 - yb3
- YB_bottle_carriers - YB_bottle_carriers
class: class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier
type: pylabrobot type: pylabrobot
description: YB_peiyepingxiaoban description: YB_6StockCarrier
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_shi_pei_qi_kuai: YB_6VialCarrier:
category: category:
- yb3 - yb3
- YB_bottle_carriers - YB_bottle_carriers
class: class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier
type: pylabrobot type: pylabrobot
description: YB_shi_pei_qi_kuai description: YB_6VialCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_qiang_tou_he:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he
type: pylabrobot
description: YB_qiang_tou_he
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
@@ -154,14 +76,14 @@ YB_gao_nian_ye_Bottle:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_jia_yang_tou_da: YB_gaonianye:
category: category:
- yb3 - yb3
- YB_bottle_carriers - YB_bottle_carriers
class: class:
module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye
type: pylabrobot type: pylabrobot
description: YB_jia_yang_tou_da description: YB_gaonianye
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
@@ -180,6 +102,71 @@ YB_jia_yang_tou_da_Carrier:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_peiyepingdaban:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban
type: pylabrobot
description: YB_peiyepingdaban
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_peiyepingxiaoban:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban
type: pylabrobot
description: YB_peiyepingxiaoban
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_qiang_tou_he:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he
type: pylabrobot
description: YB_qiang_tou_he
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_shi_pei_qi_kuai:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai
type: pylabrobot
description: YB_shi_pei_qi_kuai
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_ye:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_ye
type: pylabrobot
description: YB_ye_Bottle_Carrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
YB_ye_100ml_Bottle: YB_ye_100ml_Bottle:
category: category:
- yb3 - yb3
@@ -193,16 +180,3 @@ YB_ye_100ml_Bottle:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_ye_Bottle:
category:
- yb3
- YB_bottle_carriers
class:
module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle
type: pylabrobot
description: YB_ye_Bottle
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0

View File

@@ -34,3 +34,15 @@ BIOYOND_YB_Deck:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
CoincellDeck:
category:
- deck
class:
module: unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck
type: pylabrobot
description: CoincellDeck
handles: []
icon: yihua.webp
init_param_schema: {}
registry_type: resource
version: 1.0.0

View File

View 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

View File

@@ -0,0 +1,179 @@
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):
mass: float # 质量 (g)
material_type: str # 材料类型(铜、铝、不锈钢、弹簧钢等)
color: str # 材料类型对应的颜色
class ElectrodeSheet(ResourcePLR):
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
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
def PositiveCan(name: str) -> ElectrodeSheet:
"""创建正极壳"""
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=3.0, model="PositiveCan")
sheet.load_state({"material_type": "aluminum", "color": electrode_colors["PositiveCan"]})
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=12,
size_y=12,
size_z=6,
category: str = "battery",
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: 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进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data

View 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",
)

View File

@@ -206,7 +206,7 @@ def YB_6VialCarrier(name: str) -> BottleCarrier:
return carrier return carrier
# 1瓶载架 - 单个中央位置 # 1瓶载架 - 单个中央位置
def YB_1BottleCarrier(name: str) -> BottleCarrier: def YB_ye(name: str) -> BottleCarrier:
# 载架尺寸 (mm) # 载架尺寸 (mm)
carrier_size_x = 127.8 carrier_size_x = 127.8
@@ -233,7 +233,7 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier:
resource_size_y=beaker_diameter, resource_size_y=beaker_diameter,
name_prefix=name, name_prefix=name,
), ),
model="YB_1BottleCarrier", model="YB_ye",
) )
carrier.num_items_x = 1 carrier.num_items_x = 1
carrier.num_items_y = 1 carrier.num_items_y = 1

View File

@@ -33,7 +33,7 @@ def YB_ye_Bottle(
height=height, height=height,
max_volume=max_volume, max_volume=max_volume,
barcode=barcode, barcode=barcode,
model="Liquid_Bottle", model="YB_ye_Bottle",
) )
"""100ml液体""" """100ml液体"""

View File

@@ -89,23 +89,19 @@ class BIOYOND_YB_Deck(Deck):
"自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"), "自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"),
"手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"), "手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"),
"手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"), "手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"),
"粉末加样头堆栈-左": bioyond_warehouse_10x1x1("粉末加样头堆栈-左"), "粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"),
"粉末加样头堆栈-右": bioyond_warehouse_10x1x1("粉末加样头堆栈-右"),
"配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"), "配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"),
"试剂替换仓库-左": bioyond_warehouse_5x1x1("试剂替换仓库-左"), "试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"),
"试剂替换仓库-右": bioyond_warehouse_5x1x1("试剂替换仓库-右"),
} }
# 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(2200.0, 1304.0, 0.0), "配液站内试剂仓库": Coordinate(2162.0, 437.0, 0.0),
"配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0), "试剂替换仓库": Coordinate(1173.0, 802.0, 0.0),
"试剂替换仓库-左": Coordinate(1173.0, 702.0, 0.0),
"试剂替换仓库-右": Coordinate(2721.0, 739.0, 0.0),
} }
for warehouse_name, warehouse in self.warehouses.items(): for warehouse_name, warehouse in self.warehouses.items():

View File

@@ -29,7 +29,7 @@ class Bottle(Well):
size_x: float = 0.0, size_x: float = 0.0,
size_y: float = 0.0, size_y: float = 0.0,
size_z: float = 0.0, size_z: float = 0.0,
barcode: Optional[str] = "", barcode: Optional[str] = None,
category: str = "container", category: str = "container",
model: Optional[str] = None, model: Optional[str] = None,
**kwargs, **kwargs,