mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-04-23 21:00:00 +00:00
feat: update coin cell assembly, bioyond cell workstation, and resource configs
This commit is contained in:
168
CHANGES_2026_03_24.md
Normal file
168
CHANGES_2026_03_24.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 变更说明 2026-03-24
|
||||
|
||||
## 问题背景
|
||||
|
||||
`BioyondElectrolyteDeck`(原 `BIOYOND_YB_Deck`)迁移后,前端物料未能正常上传/同步。
|
||||
|
||||
---
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. `unilabos/resources/bioyond/decks.py`
|
||||
|
||||
- 补回 `setup: bool = False` 参数及 `if setup: self.setup()` 逻辑,与旧版 `BIOYOND_YB_Deck` 保持一致
|
||||
- 工厂函数 `bioyond_electrolyte_deck` 保留显式调用 `deck.setup()`,避免重复初始化
|
||||
|
||||
```python
|
||||
# 修复前(缺少 setup 参数,无法通过 setup=True 触发初始化)
|
||||
def __init__(self, name, size_x, size_y, size_z, category):
|
||||
super().__init__(...)
|
||||
|
||||
# 修复后
|
||||
def __init__(self, name, size_x, size_y, size_z, category, setup: bool = False):
|
||||
super().__init__(...)
|
||||
if setup:
|
||||
self.setup()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. `unilabos/resources/graphio.py`
|
||||
|
||||
- 修复 `resource_bioyond_to_plr` 中两处 `bottle.tracker.liquids` 直接赋值导致的崩溃
|
||||
- `ResourceHolder`(如枪头盒的 TipSpot 槽位)没有 `tracker` 属性,直接访问会抛出 `AttributeError`,阻断整个 Bioyond 同步流程
|
||||
|
||||
```python
|
||||
# 修复前
|
||||
bottle.tracker.liquids = [...]
|
||||
|
||||
# 修复后
|
||||
if hasattr(bottle, 'tracker') and bottle.tracker is not None:
|
||||
bottle.tracker.liquids = [...]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. `unilabos/app/main.py`
|
||||
|
||||
- 保留 `file_path is not None` 条件不变(已还原),并补充注释说明原因
|
||||
- 该逻辑只在**本地文件模式**下有意义:本地 graph 文件只含设备结构,远端有已保存物料,merge 才能将两者合并
|
||||
- 远端模式(`file_path=None`)下,`resource_tree_set` 和 `request_startup_json` 来自同一份数据,merge 为空操作,条件是否加 `file_path is not None` 对结果没有影响
|
||||
|
||||
---
|
||||
|
||||
### 4. `unilabos/devices/workstation/bioyond_studio/station.py` ⭐ 核心修复
|
||||
|
||||
- 当 deck 通过反序列化创建时,不会自动调用 `setup()`,导致 `deck.children` 为空,`warehouses` 始终是 `{}`
|
||||
- 增加兜底逻辑:仓库扫描后仍为空,则主动调用 `deck.setup()` 初始化仓库
|
||||
- 这是导致所有物料放置失败(`warehouse '...' 在deck中不存在。可用warehouses: []`)的根本原因
|
||||
|
||||
```python
|
||||
# 新增兜底
|
||||
if not self.deck.warehouses and hasattr(self.deck, "setup") and callable(self.deck.setup):
|
||||
logger.info("Deck 无仓库子节点,调用 setup() 初始化仓库")
|
||||
self.deck.setup()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 补充修复 2026-03-25:依华扣电组装工站子物料未上传
|
||||
|
||||
### 问题
|
||||
|
||||
`CoinCellAssemblyWorkstation.post_init` 直接上传空 deck,未调用 `deck.setup()`,导致:
|
||||
- 前端子物料(成品弹夹、料盘、瓶架等)不显示
|
||||
- 运行时 `self.deck.get_resource("成品弹夹")` 抛出 `ResourceNotFoundError`
|
||||
|
||||
### 修复文件
|
||||
|
||||
**`unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py`**
|
||||
- `YihuaCoinCellDeck.__init__` 补回 `setup: bool = False` 参数及 `if setup: self.setup()` 逻辑
|
||||
|
||||
**`unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py`**
|
||||
- `post_init` 中增加与 Bioyond 工站相同的兜底逻辑:deck 无子节点时调用 `deck.setup()` 初始化
|
||||
|
||||
```python
|
||||
# post_init 中新增
|
||||
if self.deck and not self.deck.children and hasattr(self.deck, "setup") and callable(self.deck.setup):
|
||||
logger.info("YihuaCoinCellDeck 无子节点,调用 setup() 初始化")
|
||||
self.deck.setup()
|
||||
```
|
||||
|
||||
### 联动 Bug:`MaterialPlate.create_with_holes` 构造顺序错误
|
||||
|
||||
**现象**:`deck.setup()` 被调用后,启动时抛出:
|
||||
```
|
||||
设备后初始化失败: Must specify either `ordered_items` or `ordering`.
|
||||
```
|
||||
|
||||
**根因**:`create_with_holes` 原来的逻辑是先构造空的 `MaterialPlate` 实例,再 assign 洞位:
|
||||
```python
|
||||
# 旧(错误):cls(...) 时 ordered_items=None → ItemizedResource.__init__ 立即报错
|
||||
plate = cls(name=name, ...) # ← 这里就崩了
|
||||
holes = create_ordered_items_2d(...) # ← 根本没走到这里
|
||||
for hole_name, hole in holes.items():
|
||||
plate.assign_child_resource(...)
|
||||
```
|
||||
pylabrobot 的 `ItemizedResource.__init__` 强制要求 `ordered_items` 和 `ordering` 必须有一个不为 `None`,空构造直接失败。
|
||||
|
||||
**修复**:先建洞位,再作为 `ordered_items` 传给构造函数:
|
||||
```python
|
||||
# 新(正确):先建洞位,再一次性传入构造函数
|
||||
holes = create_ordered_items_2d(klass=MaterialHole, num_items_x=4, ...)
|
||||
return cls(name=name, ..., ordered_items=holes)
|
||||
```
|
||||
|
||||
> 此 bug 此前未被触发,是因为 `deck.setup()` 从未被调用到——正是上面 `post_init` 兜底修复引出的联动问题。
|
||||
|
||||
---
|
||||
|
||||
## 补充修复 2026-03-25:3→2→1 转运资源同步失败
|
||||
|
||||
### 问题
|
||||
|
||||
配液工站(Bioyond)完成分液后,调用 `transfer_3_to_2_to_1_auto` 将分液瓶板转运到扣电工站(BatteryStation)。物理 LIMS 转运成功,但数字孪生资源树同步始终失败:
|
||||
```
|
||||
[资源同步] ❌ 失败: 目标设备 'BatteryStation' 中未找到资源 'bottle_rack_6x2'
|
||||
```
|
||||
|
||||
### 根因
|
||||
|
||||
`_get_resource_from_device` 方法负责跨设备查找资源对象,有两个问题:
|
||||
|
||||
1. **原始路径完全失效**:尝试 `from unilabos.app.ros2_app import get_device_plr_resource_by_name`,但该模块不存在,`ImportError` 被 `except Exception: pass` 静默吞掉
|
||||
2. **降级路径搜错地方**:遍历 `self._plr_resources`(Bioyond 自己的资源),不可能找到 BatteryStation 的 `bottle_rack_6x2`
|
||||
|
||||
### 修复文件
|
||||
|
||||
**`unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py`**
|
||||
|
||||
改用全局设备注册表 `registered_devices` 跨设备访问目标 deck:
|
||||
|
||||
```python
|
||||
# 修复前(失效)
|
||||
from unilabos.app.ros2_app import get_device_plr_resource_by_name # 模块不存在
|
||||
return get_device_plr_resource_by_name(device_id, resource_name)
|
||||
|
||||
# 修复后
|
||||
from unilabos.ros.nodes.base_device_node import registered_devices
|
||||
device_info = registered_devices.get(device_id)
|
||||
if device_info is not None:
|
||||
driver = device_info.get("driver_instance") # TypedDict 是 dict,必须用 .get()
|
||||
if driver is not None:
|
||||
deck = getattr(driver, "deck", None)
|
||||
if deck is not None:
|
||||
res = deck.get_resource(resource_name)
|
||||
```
|
||||
|
||||
关键细节:`DeviceInfoType` 是 `TypedDict`(即普通 `dict`),必须用 `device_info.get("driver_instance")` 而非 `getattr(device_info, "driver_instance", None)`——后者对字典永远返回 `None`。
|
||||
|
||||
---
|
||||
|
||||
## 根本原因分析
|
||||
|
||||
旧版以**本地文件模式**启动(有 `graph` 文件),deck 在启动前已通过 `merge_remote_resources` 获得仓库子节点,反序列化时能正确恢复 warehouses。
|
||||
|
||||
新版以**远端模式**启动(`file_path=None`),deck 反序列化时没有仓库子节点,`station.py` 扫描为空,所有物料的 warehouse 匹配失败,Bioyond 同步的 16 个资源全部无法放置到对应仓库位,前端不显示。
|
||||
@@ -621,6 +621,8 @@ def main():
|
||||
continue
|
||||
|
||||
# 如果从远端获取了物料信息,则与本地物料进行同步
|
||||
# 仅在本地文件模式下有意义:本地文件只含设备结构,远端有已保存的物料,需要 merge
|
||||
# 远端模式下 resource_tree_set 与 request_startup_json 来自同一份数据,merge 为空操作
|
||||
if file_path is not None and request_startup_json and "nodes" in request_startup_json:
|
||||
print_status("开始同步远端物料到本地...", "info")
|
||||
remote_tree_set = ResourceTreeSet.from_raw_dict_list(request_startup_json["nodes"])
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -979,10 +979,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
formulation: List[Dict[str, Any]],
|
||||
batch_id: str = "",
|
||||
bottle_type: str = "配液小瓶",
|
||||
mix_time: int = 0,
|
||||
load_shedding_info: float = 0.0,
|
||||
pouch_cell_info: float = 0.0,
|
||||
conductivity_info: float = 0.0,
|
||||
mix_time: List[int] = [],
|
||||
coin_cell_volume: float = 0.0,
|
||||
pouch_cell_volume: float = 0.0,
|
||||
conductivity_volume: float = 0.0,
|
||||
conductivity_bottle_count: int = 0,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -1003,10 +1003,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
]
|
||||
batch_id: 批次ID,若为空则用当前时间戳
|
||||
bottle_type: 配液瓶类型,默认 "配液小瓶"
|
||||
mix_time: 混匀时间(秒)
|
||||
load_shedding_info: 扣电组装分液体积
|
||||
pouch_cell_info: 软包组装分液体积
|
||||
conductivity_info: 电导测试分液体积
|
||||
mix_time: 混匀时间列表(秒),与 formulation 一一对应,不足则补 0
|
||||
coin_cell_volume: 纽扣电池组装分液体积
|
||||
pouch_cell_volume: 软包电池注液组装分液体积
|
||||
conductivity_volume: 电导率测试分液体积
|
||||
conductivity_bottle_count: 电导测试分液瓶数
|
||||
|
||||
Returns:
|
||||
@@ -1039,9 +1039,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
logger.warning(f"[create_orders_formulation] 第 {idx + 1} 个配方无有效物料,跳过")
|
||||
continue
|
||||
|
||||
item_mix_time = mix_time[idx] if idx < len(mix_time) else 0
|
||||
logger.info(f"[create_orders_formulation] 第 {idx + 1} 个配方: orderName={order_name}, "
|
||||
f"loadShedding={load_shedding_info}, pouchCell={pouch_cell_info}, "
|
||||
f"conductivity={conductivity_info}, totalMass={total_mass}, "
|
||||
f"coinCellVolume={coin_cell_volume}, pouchCellVolume={pouch_cell_volume}, "
|
||||
f"conductivityVolume={conductivity_volume}, totalMass={total_mass}, "
|
||||
f"material_count={len(mats)}")
|
||||
|
||||
orders.append({
|
||||
@@ -1049,10 +1050,10 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
"orderName": order_name,
|
||||
"createTime": create_time,
|
||||
"bottleType": bottle_type,
|
||||
"mixTime": mix_time,
|
||||
"loadSheddingInfo": load_shedding_info,
|
||||
"pouchCellInfo": pouch_cell_info,
|
||||
"conductivityInfo": conductivity_info,
|
||||
"mixTime": item_mix_time,
|
||||
"loadSheddingInfo": coin_cell_volume,
|
||||
"pouchCellInfo": pouch_cell_volume,
|
||||
"conductivityInfo": conductivity_volume,
|
||||
"conductivityBottleCount": conductivity_bottle_count,
|
||||
"materialInfos": mats,
|
||||
"totalMass": round(total_mass, 4),
|
||||
@@ -1650,18 +1651,31 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
||||
|
||||
Args:
|
||||
device_id: 目标设备 ID(如 "BatteryStation")
|
||||
resource_name: 资源名称(如 "electrolyte_buffer")
|
||||
resource_name: 资源名称(如 "bottle_rack_6x2")
|
||||
|
||||
Returns:
|
||||
找到的 PLR Resource 对象,未找到则返回 None
|
||||
"""
|
||||
# 优先:通过全局设备注册表直接访问目标设备的 deck
|
||||
# DeviceInfoType 是 TypedDict(即普通 dict),必须用 dict.get() 而非 getattr()
|
||||
try:
|
||||
from unilabos.app.ros2_app import get_device_plr_resource_by_name
|
||||
return get_device_plr_resource_by_name(device_id, resource_name)
|
||||
from unilabos.ros.nodes.base_device_node import registered_devices
|
||||
device_info = registered_devices.get(device_id)
|
||||
if device_info is not None:
|
||||
driver = device_info.get("driver_instance")
|
||||
if driver is not None:
|
||||
deck = getattr(driver, "deck", None)
|
||||
if deck is not None and hasattr(deck, "get_resource"):
|
||||
try:
|
||||
res = deck.get_resource(resource_name)
|
||||
if res is not None:
|
||||
return res
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 降级:遍历 workstation 已注册的 plr_resources 列表
|
||||
# 降级:遍历 workstation 已注册的 plr_resources 列表(仅当前设备)
|
||||
try:
|
||||
for res in getattr(self, "_plr_resources", []):
|
||||
if res.name == resource_name:
|
||||
|
||||
Binary file not shown.
@@ -760,10 +760,9 @@ class BioyondWorkstation(WorkstationBase):
|
||||
except:
|
||||
pass
|
||||
|
||||
# 创建通信模块
|
||||
# 创建通信模块;同步器将在 post_init 中初始化并执行首次同步
|
||||
self._create_communication_module(bioyond_config)
|
||||
self.resource_synchronizer = BioyondResourceSynchronizer(self)
|
||||
self.resource_synchronizer.sync_from_external()
|
||||
self.resource_synchronizer = None
|
||||
|
||||
# TODO: self._ros_node里面拿属性
|
||||
|
||||
@@ -802,6 +801,15 @@ class BioyondWorkstation(WorkstationBase):
|
||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
# Deck 为空时(反序列化未恢复子节点),主动调用 setup() 初始化仓库
|
||||
if self.deck and not self.deck.children and hasattr(self.deck, "setup") and callable(self.deck.setup):
|
||||
logger.info("Deck 无仓库子节点,调用 setup() 初始化仓库")
|
||||
self.deck.setup()
|
||||
|
||||
# 初始化同步器并执行首次同步(需在仓库初始化之后)
|
||||
self.resource_synchronizer = BioyondResourceSynchronizer(self)
|
||||
self.resource_synchronizer.sync_from_external()
|
||||
|
||||
# 启动连接监控
|
||||
try:
|
||||
self.connection_monitor = ConnectionMonitor(self)
|
||||
|
||||
@@ -169,23 +169,28 @@ class MaterialPlate(ItemizedResource[MaterialHole]):
|
||||
model: Optional[str] = None,
|
||||
) -> "MaterialPlate":
|
||||
"""工厂方法:创建带 4x4 洞位的料板(仅用于初始 setup,不在反序列化路径调用)"""
|
||||
plate = cls(name=name, size_x=size_x, size_y=size_y, size_z=size_z, category=category, model=model)
|
||||
# 默认洞位间距(与 _unilabos_state 默认值保持一致)
|
||||
hole_spacing_x = 24.0
|
||||
hole_spacing_y = 24.0
|
||||
# 先建洞位,再作为 ordered_items 传入构造函数
|
||||
# (ItemizedResource.__init__ 要求 ordered_items 或 ordering 二选一必须有值)
|
||||
holes = create_ordered_items_2d(
|
||||
klass=MaterialHole,
|
||||
num_items_x=4,
|
||||
num_items_y=4,
|
||||
dx=(size_x - 4 * plate._unilabos_state["hole_spacing_x"]) / 2,
|
||||
dy=(size_y - 4 * plate._unilabos_state["hole_spacing_y"]) / 2,
|
||||
dx=(size_x - 4 * hole_spacing_x) / 2,
|
||||
dy=(size_y - 4 * hole_spacing_y) / 2,
|
||||
dz=size_z,
|
||||
item_dx=plate._unilabos_state["hole_spacing_x"],
|
||||
item_dy=plate._unilabos_state["hole_spacing_y"],
|
||||
item_dx=hole_spacing_x,
|
||||
item_dy=hole_spacing_y,
|
||||
size_x=16,
|
||||
size_y=16,
|
||||
size_z=16,
|
||||
)
|
||||
for hole_name, hole in holes.items():
|
||||
plate.assign_child_resource(hole, location=hole.location)
|
||||
return plate
|
||||
return cls(
|
||||
name=name, size_x=size_x, size_y=size_y, size_z=size_z,
|
||||
ordered_items=holes, category=category, model=model,
|
||||
)
|
||||
|
||||
def update_locations(self):
|
||||
# TODO:调多次相加
|
||||
@@ -542,6 +547,7 @@ class YihuaCoinCellDeck(Deck):
|
||||
size_z: float = 100.0,
|
||||
origin: Coordinate = Coordinate(-2200, 0, 0),
|
||||
category: str = "coin_cell_deck",
|
||||
setup: bool = False,
|
||||
):
|
||||
super().__init__(
|
||||
name=name,
|
||||
@@ -550,6 +556,8 @@ class YihuaCoinCellDeck(Deck):
|
||||
size_z=100.0,
|
||||
origin=origin,
|
||||
)
|
||||
if setup:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
||||
|
||||
@@ -193,7 +193,12 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
|
||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||
self._ros_node = ros_node
|
||||
#self.deck = create_a_coin_cell_deck()
|
||||
|
||||
# Deck 为空时(反序列化未恢复子节点),主动调用 setup() 初始化子物料
|
||||
if self.deck and not self.deck.children and hasattr(self.deck, "setup") and callable(self.deck.setup):
|
||||
logger.info("YihuaCoinCellDeck 无子节点,调用 setup() 初始化")
|
||||
self.deck.setup()
|
||||
|
||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||
"resources": [self.deck]
|
||||
})
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code,formulation_order_code,formulation_ratio
|
||||
20260325_132011,0.0,12.119999885559082,405.0,3189,20,7,test0008,13163721,,
|
||||
20260325_132301,0.0,12.079999923706055,153.0,3172,20,7,test0008,13200631,,
|
||||
20260325_132516,0.0,12.119999885559082,153.0,3205,20,7,test0008,13224031,,
|
||||
20260325_132758,0.0,12.309999465942383,161.0,3221,20,7,test0008,13251351,,
|
||||
20260325_133215,0.0,12.520000457763672,257.0,3318,20,7,NoRead88,13293861,,
|
||||
20260325_133820,0.0,12.15999984741211,363.0,3269,20,7,NoRead88,13321291,,
|
||||
20260325_134049,0.0,12.100000381469727,149.0,3383,20,7,NoRead88,13381641,,
|
||||
20260325_134327,0.0,12.369999885559082,157.0,3237,20,7,NoRead88,13404651,,
|
||||
20260325_160512,0.0,12.299999237060547,238.0,3577,20,7,NoRead88,16022161,,
|
||||
20260325_160734,0.0,12.40000057220459,155.0,3464,20,7,NoRead88,16045481,,
|
||||
20260325_161010,0.0,12.269999504089355,155.0,3609,20,7,NoRead88,60731181,,
|
||||
20260325_161252,0.0,12.579999923706055,162.0,3496,20,7,NoRead88,16100671,,
|
||||
20260325_161636,0.0,12.619999885559082,223.0,3399,20,7,NoRead88,16135951,,
|
||||
20260325_161909,0.0,12.039999961853027,153.0,3302,20,7,NoRead88,16163351,,
|
||||
20260325_162145,0.0,12.00999927520752,155.0,3350,20,7,NoRead88,16190731,,
|
||||
20260325_162429,0.0,12.329998970031738,163.0,3561,20,7,NoRead88,16214361,,
|
||||
20260325_162841,0.0,12.579999923706055,251.0,3593,20,7,NoRead88,16260311,,
|
||||
20260325_163118,0.0,12.25999927520752,156.0,3545,20,7,NoRead88,16283921,,
|
||||
20260325_163356,0.0,12.220000267028809,157.0,3464,20,7,NoRead88,16311611,,
|
||||
20260325_163641,0.0,12.199999809265137,165.0,3674,20,7,NoRead88,16335401,,
|
||||
20260325_164046,0.0,12.25,244.0,3512,20,7,NoRead88,16380881,,
|
||||
20260325_164321,0.0,12.079999923706055,154.0,3609,20,7,NoRead88,16404401,,
|
||||
20260325_164556,0.0,12.029999732971191,155.0,3593,20,7,NoRead88,16431851,,
|
||||
20260325_164840,0.0,12.100000381469727,163.0,3496,20,7,NoRead88,16455451,,
|
||||
20260325_172206,0.0,12.00999927520752,245.0,3011,20,7,NoRead88,17193041,BSO2026032500006,"{""EMC"": 0.949, ""LiFSI"": 0.051}"
|
||||
20260325_172608,0.0,12.0,242.0,3253,20,7,NoRead88,17233491,BSO2026032500007,"{""EMC"": 0.9582, ""LiFSI"": 0.0418}"
|
||||
20260325_183415,0.0,12.690000534057617,1226.0,3528,20,7,NoRead88,18150131,BSO2026032500011,"{""EMC"": 0.949, ""LiFSI"": 0.051}"
|
||||
20260325_190044,0.0,12.130000114440918,1586.0,3528,20,7,NoRead88,18355771,BSO2026032500012,"{""EMC"": 0.9582, ""LiFSI"": 0.0418}"
|
||||
|
@@ -196,11 +196,11 @@ bioyond_cell:
|
||||
batch_id: ''
|
||||
bottle_type: 配液小瓶
|
||||
conductivity_bottle_count: 0
|
||||
conductivity_info: 0.0
|
||||
conductivity_volume: 0.0
|
||||
formulation: null
|
||||
load_shedding_info: 0.0
|
||||
mix_time: 0
|
||||
pouch_cell_info: 0.0
|
||||
coin_cell_volume: 0.0
|
||||
mix_time: []
|
||||
pouch_cell_volume: 0.0
|
||||
handles:
|
||||
output:
|
||||
- data_key: total_orders
|
||||
@@ -239,9 +239,9 @@ bioyond_cell:
|
||||
default: 0
|
||||
description: 电导测试分液瓶数
|
||||
type: integer
|
||||
conductivity_info:
|
||||
conductivity_volume:
|
||||
default: 0.0
|
||||
description: 电导测试分液体积
|
||||
description: 电导率测试分液体积
|
||||
type: number
|
||||
formulation:
|
||||
description: 配方列表,每个元素代表一个订单(一瓶)
|
||||
@@ -269,17 +269,19 @@ bioyond_cell:
|
||||
- materials
|
||||
type: object
|
||||
type: array
|
||||
load_shedding_info:
|
||||
coin_cell_volume:
|
||||
default: 0.0
|
||||
description: 扣电组装分液体积
|
||||
description: 纽扣电池组装分液体积
|
||||
type: number
|
||||
mix_time:
|
||||
default: 0
|
||||
description: 混匀时间(秒)
|
||||
type: integer
|
||||
pouch_cell_info:
|
||||
default: []
|
||||
description: 混匀时间列表(秒),与 formulation 一一对应
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
pouch_cell_volume:
|
||||
default: 0.0
|
||||
description: 软包组装分液体积
|
||||
description: 软包电池注液组装分液体积
|
||||
type: number
|
||||
required:
|
||||
- formulation
|
||||
|
||||
@@ -104,8 +104,11 @@ class BioyondElectrolyteDeck(Deck):
|
||||
size_y: float = 1400.0,
|
||||
size_z: float = 2670.0,
|
||||
category: str = "deck",
|
||||
setup: bool = False,
|
||||
) -> None:
|
||||
super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0)
|
||||
if setup:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
# 添加仓库
|
||||
|
||||
@@ -797,9 +797,10 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
||||
bottle = plr_material[number] = initialize_resource(
|
||||
{"name": f'{detail["name"]}_{number}', "class": reverse_type_mapping[typeName][0]}, resource_type=ResourcePLR
|
||||
)
|
||||
bottle.tracker.liquids = [
|
||||
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
|
||||
]
|
||||
if hasattr(bottle, 'tracker') and bottle.tracker is not None:
|
||||
bottle.tracker.liquids = [
|
||||
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
|
||||
]
|
||||
bottle.code = detail.get("code", "")
|
||||
logger.debug(f" └─ [子物料] {detail['name']} → {plr_material.name}[{number}] (类型:{typeName})")
|
||||
else:
|
||||
@@ -808,9 +809,10 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
||||
# 只对有 capacity 属性的容器(液体容器)处理液体追踪
|
||||
if hasattr(plr_material, 'capacity'):
|
||||
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
|
||||
bottle.tracker.liquids = [
|
||||
(material["name"], float(material.get("quantity", 0)) if material.get("quantity") else 0)
|
||||
]
|
||||
if hasattr(bottle, 'tracker') and bottle.tracker is not None:
|
||||
bottle.tracker.liquids = [
|
||||
(material["name"], float(material.get("quantity", 0)) if material.get("quantity") else 0)
|
||||
]
|
||||
|
||||
plr_materials.append(plr_material)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user