mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-04-01 17:03:06 +00:00
新增 labware_manager 模块: - Web UI 支持耗材 CRUD、SVG 俯视图/侧面图实时预览 - SVG 支持触控板双指缩放(pinch-to-zoom)和平移 - 网格排列自动居中按钮(autoCenter) - 表单参数标签中英文双语显示 - 从已有代码/YAML 导入、Python/YAML 代码生成 更新 CLAUDE.md:补充 labware manager、decorator 注册模式、CI 说明 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
3.6 KiB
Python
120 lines
3.6 KiB
Python
"""JSON → Registry YAML 文件生成。
|
|
|
|
按 type 分组输出到对应 YAML 文件(与现有格式完全一致)。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from typing import Dict, List
|
|
|
|
import yaml
|
|
|
|
from unilabos.labware_manager.models import LabwareDB, LabwareItem
|
|
|
|
_REGISTRY_DIR = Path(__file__).resolve().parents[1] / "registry" / "resources" / "prcxi"
|
|
|
|
# type → yaml 文件名
|
|
_TYPE_TO_YAML = {
|
|
"plate": "plates",
|
|
"tip_rack": "tip_racks",
|
|
"trash": "trash",
|
|
"tube_rack": "tube_racks",
|
|
"plate_adapter": "plate_adapters",
|
|
}
|
|
|
|
|
|
def _build_entry(item: LabwareItem) -> dict:
|
|
"""构建单个 YAML 条目(与现有格式完全一致)。"""
|
|
mi = item.material_info
|
|
desc = item.registry_description
|
|
if not desc:
|
|
desc = f'{mi.Name} (Code: {mi.Code})' if mi.Name and mi.Code else item.function_name
|
|
|
|
return {
|
|
"category": list(item.registry_category),
|
|
"class": {
|
|
"module": f"unilabos.devices.liquid_handling.prcxi.prcxi_labware:{item.function_name}",
|
|
"type": "pylabrobot",
|
|
},
|
|
"description": desc,
|
|
"handles": [],
|
|
"icon": "",
|
|
"init_param_schema": {},
|
|
"version": "1.0.0",
|
|
}
|
|
|
|
|
|
class _YAMLDumper(yaml.SafeDumper):
|
|
"""自定义 Dumper: 空列表输出为 [],空字典输出为 {}。"""
|
|
pass
|
|
|
|
|
|
def _represent_list(dumper, data):
|
|
if not data:
|
|
return dumper.represent_sequence("tag:yaml.org,2002:seq", data, flow_style=True)
|
|
return dumper.represent_sequence("tag:yaml.org,2002:seq", data)
|
|
|
|
|
|
def _represent_dict(dumper, data):
|
|
if not data:
|
|
return dumper.represent_mapping("tag:yaml.org,2002:map", data, flow_style=True)
|
|
return dumper.represent_mapping("tag:yaml.org,2002:map", data)
|
|
|
|
|
|
def _represent_str(dumper, data):
|
|
if '\n' in data or ':' in data or "'" in data:
|
|
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="'")
|
|
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
|
|
|
|
|
|
_YAMLDumper.add_representer(list, _represent_list)
|
|
_YAMLDumper.add_representer(dict, _represent_dict)
|
|
_YAMLDumper.add_representer(str, _represent_str)
|
|
|
|
|
|
def generate_yaml(db: LabwareDB, test_mode: bool = True) -> List[Path]:
|
|
"""生成所有 registry YAML 文件,返回输出文件路径列表。"""
|
|
suffix = "_test" if test_mode else ""
|
|
|
|
# 按 type 分组
|
|
groups: Dict[str, Dict[str, dict]] = defaultdict(dict)
|
|
for item in db.items:
|
|
yaml_key = _TYPE_TO_YAML.get(item.type)
|
|
if yaml_key is None:
|
|
continue
|
|
groups[yaml_key][item.function_name] = _build_entry(item)
|
|
|
|
out_paths: List[Path] = []
|
|
for yaml_key, entries in groups.items():
|
|
out_path = _REGISTRY_DIR / f"{yaml_key}{suffix}.yaml"
|
|
|
|
# 备份
|
|
if out_path.exists():
|
|
bak = out_path.with_suffix(".yaml.bak")
|
|
shutil.copy2(out_path, bak)
|
|
|
|
# 按函数名排序
|
|
sorted_entries = dict(sorted(entries.items()))
|
|
|
|
content = yaml.dump(sorted_entries, Dumper=_YAMLDumper, allow_unicode=True,
|
|
default_flow_style=False, sort_keys=False)
|
|
out_path.write_text(content, encoding="utf-8")
|
|
out_paths.append(out_path)
|
|
|
|
return out_paths
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from unilabos.labware_manager.importer import load_db
|
|
db = load_db()
|
|
if not db.items:
|
|
print("labware_db.json 为空,请先运行 importer.py")
|
|
else:
|
|
paths = generate_yaml(db, test_mode=True)
|
|
print(f"已生成 {len(paths)} 个 YAML 文件:")
|
|
for p in paths:
|
|
print(f" {p}")
|