mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-14 13:14:39 +00:00
* Cleanup registry to be easy-understanding (#76) * delete deprecated mock devices * rename categories * combine chromatographic devices * rename rviz simulation nodes * organic virtual devices * parse vessel_id * run registry completion before merge --------- Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com> * fix: workstation handlers and vessel_id parsing * fix: working dir error when input config path feat: report publish topic when error * modify default discovery_interval to 15s * feat: add trace log level * feat: 添加ChinWe设备控制类,支持串口通信和电机控制功能 (#79) * fix: drop_tips not using auto resource select * fix: discard_tips error * fix: discard_tips * fix: prcxi_res * add: prcxi res fix: startup slow * feat: workstation example * fix pumps and liquid_handler handle * feat: 优化protocol node节点运行日志 * fix all protocol_compilers and remove deprecated devices * feat: 新增use_remote_resource参数 * fix and remove redundant info * bugfixes on organic protocols * fix filter protocol * fix protocol node * 临时兼容错误的driver写法 * fix: prcxi import error * use call_async in all service to avoid deadlock * fix: figure_resource * Update recipe.yaml * add workstation template and battery example * feat: add sk & ak * update workstation base * Create workstation_architecture.md * refactor: workstation_base 重构为仅含业务逻辑,通信和子设备管理交给 ProtocolNode * refactor: ProtocolNode→WorkstationNode * Add:msgs.action (#83) * update: Workstation dev 将版本号从 0.10.3 更新为 0.10.4 (#84) * Add:msgs.action * update: 将版本号从 0.10.3 更新为 0.10.4 * simplify resource system * uncompleted refactor * example for use WorkstationBase * feat: websocket * feat: websocket test * feat: workstation example * feat: action status * fix: station自己的方法注册错误 * fix: 还原protocol node处理方法 * fix: build * fix: missing job_id key * ws test version 1 * ws test version 2 * ws protocol * 增加物料关系上传日志 * 增加物料关系上传日志 * 修正物料关系上传 * 修复工站的tracker实例追踪失效问题 * 增加handle检测,增加material edge关系上传 * 修复event loop错误 * 修复edge上报错误 * 修复async错误 * 更新schema的title字段 * 主机节点信息等支持自动刷新 * 注册表编辑器 * 修复status密集发送时,消息出错 * 增加addr参数 * fix: addr param * fix: addr param * 取消labid 和 强制config输入 * Add action definitions for LiquidHandlerSetGroup and LiquidHandlerTransferGroup - Created LiquidHandlerSetGroup.action with fields for group name, wells, and volumes. - Created LiquidHandlerTransferGroup.action with fields for source and target group names and unit volume. - Both actions include response fields for return information and success status. * Add LiquidHandlerSetGroup and LiquidHandlerTransferGroup actions to CMakeLists * Add set_group and transfer_group methods to PRCXI9300Handler and update liquid_handler.yaml * result_info改为字典类型 * 新增uat的地址替换 * runze multiple pump support (cherry picked from commit49354fcf39) * remove runze multiple software obtainer (cherry picked from commit8bcc92a394) * support multiple backbone (cherry picked from commit4771ff2347) * Update runze pump format * Correct runze multiple backbone * Update runze_multiple_backbone * Correct runze pump multiple receive method. * Correct runze pump multiple receive method. * 对于PRCXI9320的transfer_group,一对多和多对多 * 移除MQTT,更新launch文档,提供注册表示例文件,更新到0.10.5 * fix import error * fix dupe upload registry * refactor ws client * add server timeout * Fix: run-column with correct vessel id (#86) * fix run_column * Update run_column_protocol.py (cherry picked from commite5aa4d940a) * resource_update use resource_add * 新增版位推荐功能 * 重新规定了版位推荐的入参 * update registry with nested obj * fix protocol node log_message, added create_resource return value * fix protocol node log_message, added create_resource return value * try fix add protocol * fix resource_add * 修复移液站错误的aspirate注册表 * Feature/xprbalance-zhida (#80) * feat(devices): add Zhida GC/MS pretreatment automation workstation * feat(devices): add mettler_toledo xpr balance * balance * 重新补全zhida注册表 * PRCXI9320 json * PRCXI9320 json * PRCXI9320 json * fix resource download * remove class for resource * bump version to 0.10.6 * 更新所有注册表 * 修复protocolnode的兼容性 * 修复protocolnode的兼容性 * Update install md * Add Defaultlayout * 更新物料接口 * fix dict to tree/nested-dict converter * coin_cell_station draft * refactor: rename "station_resource" to "deck" * add standardized BIOYOND resources: bottle_carrier, bottle * refactor and add BIOYOND resources tests * add BIOYOND deck assignment and pass all tests * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92) * feat: 新威电池测试系统驱动与注册文件 * feat: bring neware driver & battery.json into workstation_dev_YB2 * add bioyond studio draft * bioyond station with communication init and resource sync * fix bioyond station and registry * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * frontend_docs * create/update resources with POST/PUT for big amount/ small amount data * create/update resources with POST/PUT for big amount/ small amount data * refactor: add itemized_carrier instead of carrier consists of ResourceHolder * create warehouse by factory func * update bioyond launch json * add child_size for itemized_carrier * fix bioyond resource io * Workstation templates: Resources and its CRUD, and workstation tasks (#95) * coin_cell_station draft * refactor: rename "station_resource" to "deck" * add standardized BIOYOND resources: bottle_carrier, bottle * refactor and add BIOYOND resources tests * add BIOYOND deck assignment and pass all tests * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92) * feat: 新威电池测试系统驱动与注册文件 * feat: bring neware driver & battery.json into workstation_dev_YB2 * add bioyond studio draft * bioyond station with communication init and resource sync * fix bioyond station and registry * create/update resources with POST/PUT for big amount/ small amount data * refactor: add itemized_carrier instead of carrier consists of ResourceHolder * create warehouse by factory func * update bioyond launch json * add child_size for itemized_carrier * fix bioyond resource io --------- Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com> Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com> * 更新物料接口 * Workstation dev yb2 (#100) * Refactor and extend reaction station action messages * Refactor dispensing station tasks to enhance parameter clarity and add batch processing capabilities - Updated `create_90_10_vial_feeding_task` to include detailed parameters for 90%/10% vial feeding, improving clarity and usability. - Introduced `create_batch_90_10_vial_feeding_task` for batch processing of 90%/10% vial feeding tasks with JSON formatted input. - Added `create_batch_diamine_solution_task` for batch preparation of diamine solution, also utilizing JSON formatted input. - Refined `create_diamine_solution_task` to include additional parameters for better task configuration. - Enhanced schema descriptions and default values for improved user guidance. * 修复to_plr_resources * add update remove * 支持选择器注册表自动生成 支持转运物料 * 修复资源添加 * 修复transfer_resource_to_another生成 * 更新transfer_resource_to_another参数,支持spot入参 * 新增test_resource动作 * fix host_node error * fix host_node test_resource error * fix host_node test_resource error * 过滤本地动作 * 移动内部action以兼容host node * 修复同步任务报错不显示的bug * feat: 允许返回非本节点物料,后面可以通过decoration进行区分,就不进行warning了 * update todo * modify bioyond/plr converter, bioyond resource registry, and tests * pass the tests * update todo * add conda-pack-build.yml * add auto install script for conda-pack-build.yml (cherry picked from commit172599adcf) * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * Add version in __init__.py Update conda-pack-build.yml Add create_zip_archive.py * Update conda-pack-build.yml * Update conda-pack-build.yml (with mamba) * Update conda-pack-build.yml * Fix FileNotFoundError * Try fix 'charmap' codec can't encode characters in position 16-23: character maps to <undefined> * Fix unilabos msgs search error * Fix environment_check.py * Update recipe.yaml * Update registry. Update uuid loop figure method. Update install docs. * Fix nested conda pack * Fix one-key installation path error * Bump version to 0.10.7 * Workshop bj (#99) * Add LaiYu Liquid device integration and tests Introduce LaiYu Liquid device implementation, including backend, controllers, drivers, configuration, and resource files. Add hardware connection, tip pickup, and simplified test scripts, as well as experiment and registry configuration for LaiYu Liquid. Documentation and .gitignore for the device are also included. * feat(LaiYu_Liquid): 重构设备模块结构并添加硬件文档 refactor: 重新组织LaiYu_Liquid模块目录结构 docs: 添加SOPA移液器和步进电机控制指令文档 fix: 修正设备配置中的最大体积默认值 test: 新增工作台配置测试用例 chore: 删除过时的测试脚本和配置文件 * add * 重构: 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py 并更新所有导入引用 - 使用 git mv 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py - 更新所有相关文件中的导入引用 - 保持代码功能不变,仅改善命名一致性 - 测试确认所有导入正常工作 * 修复: 在 core/__init__.py 中添加 LaiYuLiquidBackend 导出 - 添加 LaiYuLiquidBackend 到导入列表 - 添加 LaiYuLiquidBackend 到 __all__ 导出列表 - 确保所有主要类都可以正确导入 * 修复大小写文件夹名字 * 电池装配工站二次开发教程(带目录)上传至dev (#94) * 电池装配工站二次开发教程 * Update intro.md * 物料教程 * 更新物料教程,json格式注释 * Update prcxi driver & fix transfer_liquid mix_times (#90) * Update prcxi driver & fix transfer_liquid mix_times * fix: correct mix_times type * Update liquid_handler registry * test: prcxi.py * Update registry from pr * fix ony-key script not exist * clean files --------- Co-authored-by: Junhan Chang <changjh@dp.tech> Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com> Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com> Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com> Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com> Co-authored-by: LccLink <1951855008@qq.com> Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com> Co-authored-by: shiyubo0410 <shiyubo@dp.tech>
846 lines
35 KiB
Python
846 lines
35 KiB
Python
from functools import partial
|
||
|
||
import networkx as nx
|
||
import re
|
||
import logging
|
||
import sys
|
||
from typing import List, Dict, Any, Union
|
||
from .utils.vessel_parser import get_vessel
|
||
from .utils.logger_util import action_log
|
||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 确保输出编码为UTF-8
|
||
if hasattr(sys.stdout, 'reconfigure'):
|
||
try:
|
||
sys.stdout.reconfigure(encoding='utf-8')
|
||
sys.stderr.reconfigure(encoding='utf-8')
|
||
except:
|
||
pass
|
||
|
||
def debug_print(message):
|
||
"""调试输出函数 - 支持中文"""
|
||
try:
|
||
# 确保消息是字符串格式
|
||
safe_message = str(message)
|
||
logger.info(f"[SEPARATE] {safe_message}")
|
||
except UnicodeEncodeError:
|
||
# 如果编码失败,尝试替换不支持的字符
|
||
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
|
||
logger.info(f"[SEPARATE] {safe_message}")
|
||
except Exception as e:
|
||
# 最后的安全措施
|
||
fallback_message = f"日志输出错误: {repr(message)}"
|
||
logger.info(f"[SEPARATE] {fallback_message}")
|
||
|
||
create_action_log = partial(action_log, prefix="[SEPARATE]")
|
||
|
||
|
||
def generate_separate_protocol(
|
||
G: nx.DiGraph,
|
||
# 🔧 基础参数,支持XDL的vessel参数
|
||
vessel: dict = None, # 🔧 修改:从字符串改为字典类型
|
||
purpose: str = "separate", # 分离目的
|
||
product_phase: str = "top", # 产物相
|
||
# 🔧 可选的详细参数
|
||
from_vessel: Union[str, dict] = "", # 源容器(通常在separate前已经transfer了)
|
||
separation_vessel: Union[str, dict] = "", # 分离容器(与vessel同义)
|
||
to_vessel: Union[str, dict] = "", # 目标容器(可选)
|
||
waste_phase_to_vessel: Union[str, dict] = "", # 废相目标容器
|
||
product_vessel: Union[str, dict] = "", # XDL: 产物容器(与to_vessel同义)
|
||
waste_vessel: Union[str, dict] = "", # XDL: 废液容器(与waste_phase_to_vessel同义)
|
||
# 🔧 溶剂相关参数
|
||
solvent: str = "", # 溶剂名称
|
||
solvent_volume: Union[str, float] = 0.0, # 溶剂体积
|
||
volume: Union[str, float] = 0.0, # XDL: 体积(与solvent_volume同义)
|
||
# 🔧 操作参数
|
||
through: str = "", # 通过材料
|
||
repeats: int = 1, # 重复次数
|
||
stir_time: float = 30.0, # 搅拌时间(秒)
|
||
stir_speed: float = 300.0, # 搅拌速度
|
||
settling_time: float = 300.0, # 沉降时间(秒)
|
||
**kwargs
|
||
) -> List[Dict[str, Any]]:
|
||
"""
|
||
生成分离操作的协议序列 - 支持vessel字典和体积运算
|
||
|
||
支持XDL参数格式:
|
||
- vessel: 分离容器字典(必需)
|
||
- purpose: "wash", "extract", "separate"
|
||
- product_phase: "top", "bottom"
|
||
- product_vessel: 产物收集容器
|
||
- waste_vessel: 废液收集容器
|
||
- solvent: 溶剂名称
|
||
- volume: "200 mL", "?" 或数值
|
||
- repeats: 重复次数
|
||
|
||
分离流程:
|
||
1. (可选)添加溶剂到分离容器
|
||
2. 搅拌混合
|
||
3. 静置分层
|
||
4. 收集指定相到目标容器
|
||
5. 重复指定次数
|
||
"""
|
||
|
||
# 🔧 核心修改:vessel参数兼容处理
|
||
if vessel is None:
|
||
if isinstance(separation_vessel, dict):
|
||
vessel = separation_vessel
|
||
else:
|
||
raise ValueError("必须提供vessel字典参数")
|
||
|
||
# 🔧 核心修改:从字典中提取容器ID
|
||
vessel_id, vessel_data = get_vessel(vessel)
|
||
|
||
debug_print("🌀" * 20)
|
||
debug_print("🚀 开始生成分离协议(支持vessel字典和体积运算)✨")
|
||
debug_print(f"📝 输入参数:")
|
||
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||
debug_print(f" 🎯 分离目的: '{purpose}'")
|
||
debug_print(f" 📊 产物相: '{product_phase}'")
|
||
debug_print(f" 💧 溶剂: '{solvent}'")
|
||
debug_print(f" 📏 体积: {volume} (类型: {type(volume)})")
|
||
debug_print(f" 🔄 重复次数: {repeats}")
|
||
debug_print(f" 🎯 产物容器: '{product_vessel}'")
|
||
debug_print(f" 🗑️ 废液容器: '{waste_vessel}'")
|
||
debug_print(f" 📦 其他参数: {kwargs}")
|
||
debug_print("🌀" * 20)
|
||
|
||
action_sequence = []
|
||
|
||
# 🔧 新增:记录分离前的容器状态
|
||
debug_print("🔍 记录分离前容器状态...")
|
||
original_liquid_volume = get_vessel_liquid_volume(vessel)
|
||
debug_print(f"📊 分离前液体体积: {original_liquid_volume:.2f}mL")
|
||
|
||
# === 参数验证和标准化 ===
|
||
debug_print("🔍 步骤1: 参数验证和标准化...")
|
||
action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel_id}", "🎬"))
|
||
action_sequence.append(create_action_log(f"分离目的: {purpose}", "🧪"))
|
||
action_sequence.append(create_action_log(f"产物相: {product_phase}", "📊"))
|
||
|
||
# 统一容器参数 - 支持字典和字符串
|
||
def extract_vessel_id(vessel_param):
|
||
if isinstance(vessel_param, dict):
|
||
return vessel_param.get("id", "")
|
||
elif isinstance(vessel_param, str):
|
||
return vessel_param
|
||
else:
|
||
return ""
|
||
|
||
final_vessel_id, _ = vessel_id
|
||
final_to_vessel_id, _ = get_vessel(to_vessel) or get_vessel(product_vessel)
|
||
final_waste_vessel_id, _ = get_vessel(waste_phase_to_vessel) or get_vessel(waste_vessel)
|
||
|
||
# 统一体积参数
|
||
final_volume = parse_volume_input(volume or solvent_volume)
|
||
|
||
# 🔧 修复:确保repeats至少为1
|
||
if repeats <= 0:
|
||
repeats = 1
|
||
debug_print(f"⚠️ 重复次数参数 <= 0,自动设置为 1")
|
||
|
||
debug_print(f"🔧 标准化后的参数:")
|
||
debug_print(f" 🥼 分离容器: '{final_vessel_id}'")
|
||
debug_print(f" 🎯 产物容器: '{final_to_vessel_id}'")
|
||
debug_print(f" 🗑️ 废液容器: '{final_waste_vessel_id}'")
|
||
debug_print(f" 📏 溶剂体积: {final_volume}mL")
|
||
debug_print(f" 🔄 重复次数: {repeats}")
|
||
|
||
action_sequence.append(create_action_log(f"分离容器: {final_vessel_id}", "🧪"))
|
||
action_sequence.append(create_action_log(f"溶剂体积: {final_volume}mL", "📏"))
|
||
action_sequence.append(create_action_log(f"重复次数: {repeats}", "🔄"))
|
||
|
||
# 验证必需参数
|
||
if not purpose:
|
||
purpose = "separate"
|
||
if not product_phase:
|
||
product_phase = "top"
|
||
if purpose not in ["wash", "extract", "separate"]:
|
||
debug_print(f"⚠️ 未知的分离目的 '{purpose}',使用默认值 'separate'")
|
||
purpose = "separate"
|
||
action_sequence.append(create_action_log(f"未知目的,使用: {purpose}", "⚠️"))
|
||
if product_phase not in ["top", "bottom"]:
|
||
debug_print(f"⚠️ 未知的产物相 '{product_phase}',使用默认值 'top'")
|
||
product_phase = "top"
|
||
action_sequence.append(create_action_log(f"未知相别,使用: {product_phase}", "⚠️"))
|
||
|
||
debug_print("✅ 参数验证通过")
|
||
action_sequence.append(create_action_log("参数验证通过", "✅"))
|
||
|
||
# === 查找设备 ===
|
||
debug_print("🔍 步骤2: 查找设备...")
|
||
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
|
||
|
||
# 查找分离器设备
|
||
separator_device = find_separator_device(G, final_vessel_id) # 🔧 使用 final_vessel_id
|
||
if separator_device:
|
||
action_sequence.append(create_action_log(f"找到分离器设备: {separator_device}", "🧪"))
|
||
else:
|
||
debug_print("⚠️ 未找到分离器设备,可能无法执行分离")
|
||
action_sequence.append(create_action_log("未找到分离器设备", "⚠️"))
|
||
|
||
# 查找搅拌器
|
||
stirrer_device = find_connected_stirrer(G, final_vessel_id) # 🔧 使用 final_vessel_id
|
||
if stirrer_device:
|
||
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_device}", "🌪️"))
|
||
else:
|
||
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
|
||
|
||
# 查找溶剂容器(如果需要)
|
||
solvent_vessel = ""
|
||
if solvent and solvent.strip():
|
||
solvent_vessel = find_solvent_vessel(G, solvent)
|
||
if solvent_vessel:
|
||
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "💧"))
|
||
else:
|
||
action_sequence.append(create_action_log(f"未找到溶剂容器: {solvent}", "⚠️"))
|
||
|
||
debug_print(f"📊 设备配置:")
|
||
debug_print(f" 🧪 分离器设备: '{separator_device}'")
|
||
debug_print(f" 🌪️ 搅拌器设备: '{stirrer_device}'")
|
||
debug_print(f" 💧 溶剂容器: '{solvent_vessel}'")
|
||
|
||
# === 执行分离流程 ===
|
||
debug_print("🔍 步骤3: 执行分离流程...")
|
||
action_sequence.append(create_action_log("开始分离工作流程", "🎯"))
|
||
|
||
# 🔧 新增:体积变化跟踪变量
|
||
current_volume = original_liquid_volume
|
||
|
||
try:
|
||
for repeat_idx in range(repeats):
|
||
cycle_num = repeat_idx + 1
|
||
debug_print(f"🔄 第{cycle_num}轮: 开始分离循环 {cycle_num}/{repeats}")
|
||
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 开始", "🔄"))
|
||
|
||
# 步骤3.1: 添加溶剂(如果需要)
|
||
if solvent_vessel and final_volume > 0:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤1: 添加溶剂 {solvent} ({final_volume}mL)")
|
||
action_sequence.append(create_action_log(f"向分离容器添加 {final_volume}mL {solvent}", "💧"))
|
||
|
||
try:
|
||
# 使用pump protocol添加溶剂
|
||
pump_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=solvent_vessel,
|
||
to_vessel=final_vessel_id, # 🔧 使用 final_vessel_id
|
||
volume=final_volume,
|
||
amount="",
|
||
time=0.0,
|
||
viscous=False,
|
||
rinsing_solvent="",
|
||
rinsing_volume=0.0,
|
||
rinsing_repeats=0,
|
||
solid=False,
|
||
flowrate=2.5,
|
||
transfer_flowrate=0.5,
|
||
rate_spec="",
|
||
event="",
|
||
through="",
|
||
**kwargs
|
||
)
|
||
action_sequence.extend(pump_actions)
|
||
debug_print(f"✅ 溶剂添加完成,添加了 {len(pump_actions)} 个动作")
|
||
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||
|
||
# 🔧 新增:更新体积 - 添加溶剂后
|
||
current_volume += final_volume
|
||
update_vessel_volume(vessel, G, current_volume, f"添加{final_volume}mL {solvent}后")
|
||
|
||
except Exception as e:
|
||
debug_print(f"❌ 溶剂添加失败: {str(e)}")
|
||
action_sequence.append(create_action_log(f"溶剂添加失败: {str(e)}", "❌"))
|
||
else:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤1: 无需添加溶剂")
|
||
action_sequence.append(create_action_log("无需添加溶剂", "⏭️"))
|
||
|
||
# 步骤3.2: 启动搅拌(如果有搅拌器)
|
||
if stirrer_device and stir_time > 0:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤2: 开始搅拌 ({stir_speed}rpm,持续 {stir_time}s)")
|
||
action_sequence.append(create_action_log(f"开始搅拌: {stir_speed}rpm,持续 {stir_time}s", "🌪️"))
|
||
|
||
action_sequence.append({
|
||
"device_id": stirrer_device,
|
||
"action_name": "start_stir",
|
||
"action_kwargs": {
|
||
"vessel": {"id": final_vessel_id}, # 🔧 使用 final_vessel_id
|
||
"stir_speed": stir_speed,
|
||
"purpose": f"分离混合 - {purpose}"
|
||
}
|
||
})
|
||
|
||
# 搅拌等待
|
||
stir_minutes = stir_time / 60
|
||
action_sequence.append(create_action_log(f"搅拌中,持续 {stir_minutes:.1f} 分钟", "⏱️"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": stir_time}
|
||
})
|
||
|
||
# 停止搅拌
|
||
action_sequence.append(create_action_log("停止搅拌器", "🛑"))
|
||
action_sequence.append({
|
||
"device_id": stirrer_device,
|
||
"action_name": "stop_stir",
|
||
"action_kwargs": {"vessel": final_vessel_id} # 🔧 使用 final_vessel_id
|
||
})
|
||
|
||
else:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤2: 无需搅拌")
|
||
action_sequence.append(create_action_log("无需搅拌", "⏭️"))
|
||
|
||
# 步骤3.3: 静置分层
|
||
if settling_time > 0:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤3: 静置分层 ({settling_time}s)")
|
||
settling_minutes = settling_time / 60
|
||
action_sequence.append(create_action_log(f"静置分层 ({settling_minutes:.1f} 分钟)", "⚖️"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": settling_time}
|
||
})
|
||
else:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤3: 未指定静置时间")
|
||
action_sequence.append(create_action_log("未指定静置时间", "⏭️"))
|
||
|
||
# 步骤3.4: 执行分离操作
|
||
if separator_device:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤4: 执行分离操作")
|
||
action_sequence.append(create_action_log(f"执行分离: 收集{product_phase}相", "🧪"))
|
||
|
||
# 🔧 替换为具体的分离操作逻辑(基于old版本)
|
||
|
||
# 首先进行分液判断(电导突跃)
|
||
action_sequence.append({
|
||
"device_id": separator_device,
|
||
"action_name": "valve_open",
|
||
"action_kwargs": {
|
||
"command": "delta > 0.05"
|
||
}
|
||
})
|
||
|
||
# 估算每相的体积(假设大致平分)
|
||
phase_volume = current_volume / 2
|
||
|
||
# 智能查找分离容器底部
|
||
separation_vessel_bottom = find_separation_vessel_bottom(G, final_vessel_id) # ✅
|
||
|
||
if product_phase == "bottom":
|
||
debug_print(f"🔄 收集底相产物到 {final_to_vessel_id}")
|
||
action_sequence.append(create_action_log("收集底相产物", "📦"))
|
||
|
||
# 产物转移到目标瓶
|
||
if final_to_vessel_id:
|
||
pump_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=separation_vessel_bottom,
|
||
to_vessel=final_to_vessel_id,
|
||
volume=current_volume,
|
||
flowrate=2.5,
|
||
**kwargs
|
||
)
|
||
action_sequence.extend(pump_actions)
|
||
|
||
# 放出上面那一相,60秒后关阀门
|
||
action_sequence.append({
|
||
"device_id": separator_device,
|
||
"action_name": "valve_open",
|
||
"action_kwargs": {
|
||
"command": "time > 60"
|
||
}
|
||
})
|
||
|
||
# 弃去上面那一相进废液
|
||
if final_waste_vessel_id:
|
||
pump_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=separation_vessel_bottom,
|
||
to_vessel=final_waste_vessel_id,
|
||
volume=current_volume,
|
||
flowrate=2.5,
|
||
**kwargs
|
||
)
|
||
action_sequence.extend(pump_actions)
|
||
|
||
elif product_phase == "top":
|
||
debug_print(f"🔄 收集上相产物到 {final_to_vessel_id}")
|
||
action_sequence.append(create_action_log("收集上相产物", "📦"))
|
||
|
||
# 弃去下面那一相进废液
|
||
if final_waste_vessel_id:
|
||
pump_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=separation_vessel_bottom,
|
||
to_vessel=final_waste_vessel_id,
|
||
volume=phase_volume,
|
||
flowrate=2.5,
|
||
**kwargs
|
||
)
|
||
action_sequence.extend(pump_actions)
|
||
|
||
# 放出上面那一相,60秒后关阀门
|
||
action_sequence.append({
|
||
"device_id": separator_device,
|
||
"action_name": "valve_open",
|
||
"action_kwargs": {
|
||
"command": "time > 60"
|
||
}
|
||
})
|
||
|
||
# 产物转移到目标瓶
|
||
if final_to_vessel_id:
|
||
pump_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=separation_vessel_bottom,
|
||
to_vessel=final_to_vessel_id,
|
||
volume=phase_volume,
|
||
flowrate=2.5,
|
||
**kwargs
|
||
)
|
||
action_sequence.extend(pump_actions)
|
||
|
||
debug_print(f"✅ 分离操作已完成")
|
||
action_sequence.append(create_action_log("分离操作完成", "✅"))
|
||
|
||
# 🔧 新增:分离后体积估算
|
||
separated_volume = phase_volume * 0.95 # 假设5%损失,只保留产物相体积
|
||
update_vessel_volume(vessel, G, separated_volume, f"分离操作后(第{cycle_num}轮)")
|
||
current_volume = separated_volume
|
||
|
||
# 收集结果
|
||
if final_to_vessel_id:
|
||
action_sequence.append(
|
||
create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel_id}", "📦"))
|
||
if final_waste_vessel_id:
|
||
action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel_id}", "🗑️"))
|
||
|
||
else:
|
||
debug_print(f"🔄 第{cycle_num}轮 步骤4: 无分离器设备,跳过分离")
|
||
action_sequence.append(create_action_log("无分离器设备可用", "❌"))
|
||
# 添加等待时间模拟分离
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 10.0}
|
||
})
|
||
|
||
# 🔧 新增:如果不是最后一次,从中转瓶转移回分液漏斗(基于old版本逻辑)
|
||
if repeat_idx < repeats - 1 and final_to_vessel_id and final_to_vessel_id != final_vessel_id:
|
||
debug_print(f"🔄 第{cycle_num}轮: 产物转移回分离容器准备下一轮")
|
||
action_sequence.append(create_action_log("产物转回分离容器,准备下一轮", "🔄"))
|
||
|
||
pump_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=final_to_vessel_id,
|
||
to_vessel=final_vessel_id,
|
||
volume=current_volume,
|
||
flowrate=2.5,
|
||
**kwargs
|
||
)
|
||
action_sequence.extend(pump_actions)
|
||
|
||
# 更新体积回到分离容器
|
||
update_vessel_volume(vessel, G, current_volume, f"产物转回分离容器(第{cycle_num}轮后)")
|
||
|
||
# 循环间等待(除了最后一次)
|
||
if repeat_idx < repeats - 1:
|
||
debug_print(f"🔄 第{cycle_num}轮: 等待下一次循环...")
|
||
action_sequence.append(create_action_log("等待下一次循环...", "⏳"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 5}
|
||
})
|
||
else:
|
||
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 完成", "🌟"))
|
||
|
||
except Exception as e:
|
||
debug_print(f"❌ 分离工作流程执行失败: {str(e)}")
|
||
action_sequence.append(create_action_log(f"分离工作流程失败: {str(e)}", "❌"))
|
||
|
||
# 🔧 新增:分离完成后的最终状态报告
|
||
final_liquid_volume = get_vessel_liquid_volume(vessel)
|
||
|
||
# === 最终结果 ===
|
||
total_time = (stir_time + settling_time + 15) * repeats # 估算总时间
|
||
|
||
debug_print("🌀" * 20)
|
||
debug_print(f"🎉 分离协议生成完成")
|
||
debug_print(f"📊 协议统计:")
|
||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time / 60:.1f} 分钟)")
|
||
debug_print(f" 🥼 分离容器: {final_vessel_id}")
|
||
debug_print(f" 🎯 分离目的: {purpose}")
|
||
debug_print(f" 📊 产物相: {product_phase}")
|
||
debug_print(f" 🔄 重复次数: {repeats}")
|
||
debug_print(f"💧 体积变化统计:")
|
||
debug_print(f" - 分离前体积: {original_liquid_volume:.2f}mL")
|
||
debug_print(f" - 分离后体积: {final_liquid_volume:.2f}mL")
|
||
if solvent:
|
||
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL × {repeats}轮 = {final_volume * repeats:.2f}mL)")
|
||
if final_to_vessel_id:
|
||
debug_print(f" 🎯 产物容器: {final_to_vessel_id}")
|
||
if final_waste_vessel_id:
|
||
debug_print(f" 🗑️ 废液容器: {final_waste_vessel_id}")
|
||
debug_print("🌀" * 20)
|
||
|
||
# 添加完成日志
|
||
summary_msg = f"分离协议完成: {final_vessel_id} ({purpose},{repeats} 次循环)"
|
||
if solvent:
|
||
summary_msg += f",使用 {final_volume * repeats:.2f}mL {solvent}"
|
||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||
|
||
return action_sequence
|
||
|
||
def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||
"""
|
||
解析体积输入,支持带单位的字符串
|
||
|
||
Args:
|
||
volume_input: 体积输入(如 "200 mL", "?", 50.0)
|
||
|
||
Returns:
|
||
float: 体积(毫升)
|
||
"""
|
||
if isinstance(volume_input, (int, float)):
|
||
debug_print(f"📏 体积输入为数值: {volume_input}")
|
||
return float(volume_input)
|
||
|
||
if not volume_input or not str(volume_input).strip():
|
||
debug_print(f"⚠️ 体积输入为空,返回 0.0mL")
|
||
return 0.0
|
||
|
||
volume_str = str(volume_input).lower().strip()
|
||
debug_print(f"🔍 解析体积输入: '{volume_str}'")
|
||
|
||
# 处理未知体积
|
||
if volume_str in ['?', 'unknown', 'tbd', 'to be determined', '未知', '待定']:
|
||
default_volume = 100.0 # 默认100mL
|
||
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL")
|
||
return default_volume
|
||
|
||
# 移除空格并提取数字和单位
|
||
volume_clean = re.sub(r'\s+', '', volume_str)
|
||
|
||
# 匹配数字和单位的正则表达式
|
||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter|毫升|升|微升)?', volume_clean)
|
||
|
||
if not match:
|
||
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值 100mL")
|
||
return 100.0
|
||
|
||
value = float(match.group(1))
|
||
unit = match.group(2) or 'ml' # 默认单位为毫升
|
||
|
||
# 转换为毫升
|
||
if unit in ['l', 'liter', '升']:
|
||
volume = value * 1000.0 # L -> mL
|
||
debug_print(f"🔄 体积转换: {value}L -> {volume}mL")
|
||
elif unit in ['μl', 'ul', 'microliter', '微升']:
|
||
volume = value / 1000.0 # μL -> mL
|
||
debug_print(f"🔄 体积转换: {value}μL -> {volume}mL")
|
||
else: # ml, milliliter, 毫升 或默认
|
||
volume = value # 已经是mL
|
||
debug_print(f"✅ 体积已为毫升单位: {volume}mL")
|
||
|
||
return volume
|
||
|
||
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||
"""查找溶剂容器,支持多种匹配模式"""
|
||
if not solvent or not solvent.strip():
|
||
debug_print("⏭️ 未指定溶剂,跳过溶剂容器查找")
|
||
return ""
|
||
|
||
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器...")
|
||
|
||
# 🔧 方法1:直接搜索 data.reagent_name 和 config.reagent
|
||
debug_print(f"📋 方法1: 搜索试剂字段...")
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node].get('data', {})
|
||
node_type = G.nodes[node].get('type', '')
|
||
config_data = G.nodes[node].get('config', {})
|
||
|
||
# 只搜索容器类型的节点
|
||
if node_type == 'container':
|
||
reagent_name = node_data.get('reagent_name', '').lower()
|
||
config_reagent = config_data.get('reagent', '').lower()
|
||
|
||
# 精确匹配
|
||
if reagent_name == solvent.lower() or config_reagent == solvent.lower():
|
||
debug_print(f"✅ 通过试剂字段精确匹配找到容器: {node}")
|
||
return node
|
||
|
||
# 模糊匹配
|
||
if (solvent.lower() in reagent_name and reagent_name) or \
|
||
(solvent.lower() in config_reagent and config_reagent):
|
||
debug_print(f"✅ 通过试剂字段模糊匹配找到容器: {node}")
|
||
return node
|
||
|
||
# 🔧 方法2:常见的容器命名规则
|
||
debug_print(f"📋 方法2: 使用命名规则...")
|
||
solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_')
|
||
possible_names = [
|
||
f"flask_{solvent_clean}",
|
||
f"bottle_{solvent_clean}",
|
||
f"vessel_{solvent_clean}",
|
||
f"{solvent_clean}_flask",
|
||
f"{solvent_clean}_bottle",
|
||
f"solvent_{solvent_clean}",
|
||
f"reagent_{solvent_clean}",
|
||
f"reagent_bottle_{solvent_clean}",
|
||
f"reagent_bottle_1", # 通用试剂瓶
|
||
f"reagent_bottle_2",
|
||
f"reagent_bottle_3"
|
||
]
|
||
|
||
debug_print(f"🎯 尝试的容器名称: {possible_names[:5]}... (共 {len(possible_names)} 个)")
|
||
|
||
for name in possible_names:
|
||
if name in G.nodes():
|
||
node_type = G.nodes[name].get('type', '')
|
||
if node_type == 'container':
|
||
debug_print(f"✅ 通过命名规则找到容器: {name}")
|
||
return name
|
||
|
||
# 🔧 方法3:使用第一个试剂瓶作为备选
|
||
debug_print(f"📋 方法3: 查找备用试剂瓶...")
|
||
for node_id in G.nodes():
|
||
node_data = G.nodes[node_id]
|
||
if (node_data.get('type') == 'container' and
|
||
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
|
||
debug_print(f"⚠️ 未找到专用容器,使用备用容器: {node_id}")
|
||
return node_id
|
||
|
||
debug_print(f"❌ 无法找到溶剂 '{solvent}' 的容器")
|
||
return ""
|
||
|
||
def find_separator_device(G: nx.DiGraph, vessel: str) -> str:
|
||
"""查找分离器设备,支持多种查找方式"""
|
||
debug_print(f"🔍 正在查找容器 '{vessel}' 的分离器设备...")
|
||
|
||
# 方法1:查找连接到容器的分离器设备
|
||
debug_print(f"📋 方法1: 检查连接的分离器...")
|
||
separator_nodes = []
|
||
for node in G.nodes():
|
||
node_class = G.nodes[node].get('class', '').lower()
|
||
if 'separator' in node_class:
|
||
separator_nodes.append(node)
|
||
debug_print(f"📋 发现分离器设备: {node}")
|
||
|
||
# 检查是否连接到目标容器
|
||
if G.has_edge(node, vessel) or G.has_edge(vessel, node):
|
||
debug_print(f"✅ 找到连接的分离器: {node}")
|
||
return node
|
||
|
||
debug_print(f"📊 找到的分离器总数: {len(separator_nodes)}")
|
||
|
||
# 方法2:根据命名规则查找
|
||
debug_print(f"📋 方法2: 使用命名规则...")
|
||
possible_names = [
|
||
f"{vessel}_controller",
|
||
f"{vessel}_separator",
|
||
vessel, # 容器本身可能就是分离器
|
||
"separator_1",
|
||
"virtual_separator",
|
||
"liquid_handler_1", # 液体处理器也可能用于分离
|
||
"controller_1"
|
||
]
|
||
|
||
debug_print(f"🎯 尝试的分离器名称: {possible_names}")
|
||
|
||
for name in possible_names:
|
||
if name in G.nodes():
|
||
node_class = G.nodes[name].get('class', '').lower()
|
||
if 'separator' in node_class or 'controller' in node_class:
|
||
debug_print(f"✅ 通过命名规则找到分离器: {name}")
|
||
return name
|
||
|
||
# 方法3:查找第一个分离器设备
|
||
debug_print(f"📋 方法3: 使用第一个可用分离器...")
|
||
if separator_nodes:
|
||
debug_print(f"⚠️ 使用第一个分离器设备: {separator_nodes[0]}")
|
||
return separator_nodes[0]
|
||
|
||
debug_print(f"❌ 未找到分离器设备")
|
||
return ""
|
||
|
||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||
"""查找连接到指定容器的搅拌器"""
|
||
debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...")
|
||
|
||
stirrer_nodes = []
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
if 'stirrer' in node_class.lower():
|
||
stirrer_nodes.append(node)
|
||
debug_print(f"📋 发现搅拌器: {node}")
|
||
|
||
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
|
||
|
||
# 检查哪个搅拌器与目标容器相连
|
||
for stirrer in stirrer_nodes:
|
||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||
debug_print(f"✅ 找到连接的搅拌器: {stirrer}")
|
||
return stirrer
|
||
|
||
# 如果没有连接的搅拌器,返回第一个可用的
|
||
if stirrer_nodes:
|
||
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
|
||
return stirrer_nodes[0]
|
||
|
||
debug_print("❌ 未找到搅拌器")
|
||
return ""
|
||
|
||
def get_vessel_liquid_volume(vessel: dict) -> float:
|
||
"""
|
||
获取容器中的液体体积 - 支持vessel字典
|
||
|
||
Args:
|
||
vessel: 容器字典
|
||
|
||
Returns:
|
||
float: 液体体积(mL)
|
||
"""
|
||
if not vessel or "data" not in vessel:
|
||
debug_print(f"⚠️ 容器数据为空,返回 0.0mL")
|
||
return 0.0
|
||
|
||
vessel_data = vessel["data"]
|
||
vessel_id = vessel.get("id", "unknown")
|
||
|
||
debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}")
|
||
|
||
# 检查liquid_volume字段
|
||
if "liquid_volume" in vessel_data:
|
||
liquid_volume = vessel_data["liquid_volume"]
|
||
|
||
# 处理列表格式
|
||
if isinstance(liquid_volume, list):
|
||
if len(liquid_volume) > 0:
|
||
volume = liquid_volume[0]
|
||
if isinstance(volume, (int, float)):
|
||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)")
|
||
return float(volume)
|
||
|
||
# 处理直接数值格式
|
||
elif isinstance(liquid_volume, (int, float)):
|
||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)")
|
||
return float(liquid_volume)
|
||
|
||
# 检查其他可能的体积字段
|
||
volume_keys = ['current_volume', 'total_volume', 'volume']
|
||
for key in volume_keys:
|
||
if key in vessel_data:
|
||
try:
|
||
volume = float(vessel_data[key])
|
||
if volume > 0:
|
||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})")
|
||
return volume
|
||
except (ValueError, TypeError):
|
||
continue
|
||
|
||
debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 的体积,返回默认值 50.0mL")
|
||
return 50.0
|
||
|
||
def update_vessel_volume(vessel: dict, G: nx.DiGraph, new_volume: float, description: str = "") -> None:
|
||
"""
|
||
更新容器体积(同时更新vessel字典和图节点)
|
||
|
||
Args:
|
||
vessel: 容器字典
|
||
G: 网络图
|
||
new_volume: 新体积
|
||
description: 更新描述
|
||
"""
|
||
vessel_id = vessel.get("id", "unknown")
|
||
|
||
if description:
|
||
debug_print(f"🔧 更新容器体积 - {description}")
|
||
|
||
# 更新vessel字典中的体积
|
||
if "data" in vessel:
|
||
if "liquid_volume" in vessel["data"]:
|
||
current_volume = vessel["data"]["liquid_volume"]
|
||
if isinstance(current_volume, list):
|
||
if len(current_volume) > 0:
|
||
vessel["data"]["liquid_volume"][0] = new_volume
|
||
else:
|
||
vessel["data"]["liquid_volume"] = [new_volume]
|
||
else:
|
||
vessel["data"]["liquid_volume"] = new_volume
|
||
else:
|
||
vessel["data"]["liquid_volume"] = new_volume
|
||
else:
|
||
vessel["data"] = {"liquid_volume": new_volume}
|
||
|
||
# 同时更新图中的容器数据
|
||
if vessel_id in G.nodes():
|
||
if 'data' not in G.nodes[vessel_id]:
|
||
G.nodes[vessel_id]['data'] = {}
|
||
|
||
vessel_node_data = G.nodes[vessel_id]['data']
|
||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||
|
||
if isinstance(current_node_volume, list):
|
||
if len(current_node_volume) > 0:
|
||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume
|
||
else:
|
||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume]
|
||
else:
|
||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
|
||
|
||
debug_print(f"📊 容器 '{vessel_id}' 体积已更新为: {new_volume:.2f}mL")
|
||
|
||
|
||
def find_separation_vessel_bottom(G: nx.DiGraph, vessel_id: str) -> str:
|
||
"""
|
||
智能查找分离容器的底部容器(假设为flask或vessel类型)
|
||
|
||
Args:
|
||
G: 网络图
|
||
vessel_id: 分离容器ID
|
||
|
||
Returns:
|
||
str: 底部容器ID
|
||
"""
|
||
debug_print(f"🔍 查找分离容器 {vessel_id} 的底部容器...")
|
||
|
||
# 方法1:根据命名规则推测
|
||
possible_bottoms = [
|
||
f"{vessel_id}_bottom",
|
||
f"flask_{vessel_id}",
|
||
f"vessel_{vessel_id}",
|
||
f"{vessel_id}_flask",
|
||
f"{vessel_id}_vessel"
|
||
]
|
||
|
||
debug_print(f"📋 尝试的底部容器名称: {possible_bottoms}")
|
||
|
||
for bottom_id in possible_bottoms:
|
||
if bottom_id in G.nodes():
|
||
node_type = G.nodes[bottom_id].get('type', '')
|
||
if node_type == 'container':
|
||
debug_print(f"✅ 通过命名规则找到底部容器: {bottom_id}")
|
||
return bottom_id
|
||
|
||
# 方法2:查找与分离器相连的容器(假设底部容器会与分离器相连)
|
||
debug_print(f"📋 方法2: 查找连接的容器...")
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
if 'separator' in node_class.lower():
|
||
# 检查分离器的输入端
|
||
if G.has_edge(node, vessel_id):
|
||
for neighbor in G.neighbors(node):
|
||
if neighbor != vessel_id:
|
||
neighbor_type = G.nodes[neighbor].get('type', '')
|
||
if neighbor_type == 'container':
|
||
debug_print(f"✅ 通过连接找到底部容器: {neighbor}")
|
||
return neighbor
|
||
|
||
debug_print(f"❌ 无法找到分离容器 {vessel_id} 的底部容器")
|
||
return ""
|
||
|