diff --git a/CHANGES_2026_03_24.md b/CHANGES_2026_03_24.md new file mode 100644 index 00000000..a514d165 --- /dev/null +++ b/CHANGES_2026_03_24.md @@ -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 个资源全部无法放置到对应仓库位,前端不显示。 diff --git a/unilabos/app/main.py b/unilabos/app/main.py index 6c097682..546e9594 100644 --- a/unilabos/app/main.py +++ b/unilabos/app/main.py @@ -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"]) diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323-1.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323-1.xlsx index d2c41c5a..6905ad87 100644 Binary files a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323-1.xlsx and b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323-1.xlsx differ diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323.xlsx index 7c56216a..ef9bca40 100644 Binary files a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323.xlsx and b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/20260323.xlsx differ diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py index c03bf776..0e577abc 100644 --- a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py +++ b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/bioyond_cell_workstation.py @@ -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: diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template5.xlsx b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template5.xlsx new file mode 100644 index 00000000..27a311d7 Binary files /dev/null and b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template5.xlsx differ diff --git a/unilabos/devices/workstation/bioyond_studio/station.py b/unilabos/devices/workstation/bioyond_studio/station.py index 60c18e1e..6a493ced 100644 --- a/unilabos/devices/workstation/bioyond_studio/station.py +++ b/unilabos/devices/workstation/bioyond_studio/station.py @@ -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) diff --git a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py index 9a1cb2ff..41ccd1f0 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py +++ b/unilabos/devices/workstation/coin_cell_assembly/YB_YH_materials.py @@ -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: """设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置""" diff --git a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py index dbb05e8c..b4333a57 100644 --- a/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py +++ b/unilabos/devices/workstation/coin_cell_assembly/coin_cell_assembly.py @@ -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] }) diff --git a/unilabos/devices/workstation/coin_cell_assembly/date_20260325.csv b/unilabos/devices/workstation/coin_cell_assembly/date_20260325.csv new file mode 100644 index 00000000..4fc794b4 --- /dev/null +++ b/unilabos/devices/workstation/coin_cell_assembly/date_20260325.csv @@ -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}" diff --git a/unilabos/registry/devices/bioyond_cell.yaml b/unilabos/registry/devices/bioyond_cell.yaml index aa6abd96..9b3f293b 100644 --- a/unilabos/registry/devices/bioyond_cell.yaml +++ b/unilabos/registry/devices/bioyond_cell.yaml @@ -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 diff --git a/unilabos/resources/bioyond/decks.py b/unilabos/resources/bioyond/decks.py index fdc470e4..f711b1d1 100644 --- a/unilabos/resources/bioyond/decks.py +++ b/unilabos/resources/bioyond/decks.py @@ -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: # 添加仓库 diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index c8f1cc2c..67f594db 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -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)