新增 WMS 搬运规则与任务管理

支持搬运规则维护、候选预览、任务生成、预占、下发、取消和完成,并完善仓储管理前端交互。
This commit is contained in:
ArtoriasWu
2026-06-25 11:02:22 +08:00
parent 1cb8091bbe
commit 15405ba114
101 changed files with 4167704 additions and 152 deletions
+139
View File
@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
"""
扫描 agv/*.glb 中的网格节点,生成最小化 JSON 配置骨架。
- 组件显示名为:组件1、组件2、组件3 …(不沿用 Blender 原始节点名)
- 每个组件仅生成默认骨架(static / 无 motion);不识别灯光、轮子等类型
- 灯光/轮子等专用字段请在查看器中改类型后保存,或由你手写 JSON
- 不生成 actions;基准在 baseline,动作在查看器按组件配置
用法:
python analyze_agv.py
python analyze_agv.py 英招
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
from pygltflib import GLTF2
ROOT = Path(__file__).resolve().parent
AGV_DIR = ROOT / "agv"
def component_label(index_1based: int) -> str:
return f"组件{index_1based}"
def _detect_glb_root(glb_path: Path) -> str:
try:
g = GLTF2().load(str(glb_path))
if g.scenes and g.scenes[0].nodes:
idx = g.scenes[0].nodes[0]
if 0 <= idx < len(g.nodes) and g.nodes[idx].name:
return g.nodes[idx].name
except Exception:
pass
return "SceneRoot"
def update_manifest() -> Path:
AGV_DIR.mkdir(parents=True, exist_ok=True)
models = []
for path in sorted(AGV_DIR.glob("*.glb")):
cfg = path.with_suffix(".json")
models.append(
{
"id": path.stem,
"name": path.stem.replace("_", " "),
"file": path.name,
"root": _detect_glb_root(path),
"config": cfg.name if cfg.exists() else None,
}
)
manifest = AGV_DIR / "manifest.json"
manifest.write_text(json.dumps({"models": models}, ensure_ascii=False, indent=2), encoding="utf-8")
return manifest
def list_mesh_nodes(glb_path: Path, root_name: str) -> list[dict[str, Any]]:
"""列出除根节点外的所有命名节点(含无 mesh 的空节点,便于完整对应)。"""
g = GLTF2().load(str(glb_path))
out = []
for node in g.nodes:
if not node.name or node.name == root_name:
continue
out.append({"name": node.name, "hasMesh": node.mesh is not None})
return out
def analyze_glb(glb_path: Path, model_id: str | None = None) -> dict[str, Any]:
model_id = model_id or glb_path.stem
root = _detect_glb_root(glb_path)
# 仅含实际网格的节点,避免把空集合/根分组也算成组件
nodes = [n for n in list_mesh_nodes(glb_path, root) if n["hasMesh"]]
components: dict[str, Any] = {}
for i, n in enumerate(nodes, start=1):
label = component_label(i)
components[label] = {
"glbNode": (n["name"] or "").replace("\u00a0", " ").strip(),
"type": "static",
"motion": None,
"hasMesh": n["hasMesh"],
}
return {
"version": 2,
"description": "analyze 仅生成组件骨架;类型与动作请在查看器按组件配置后导出。",
"model": {"id": model_id, "file": glb_path.name, "root": root},
"baseline": components,
"groups": {},
"actions": {},
}
def write_config(glb_path: Path, model_id: str | None = None) -> Path:
cfg = analyze_glb(glb_path, model_id)
out = Path(AGV_DIR) / f"{cfg['model']['id']}.json"
out.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8")
return out
def analyze_all() -> list[Path]:
written = []
for glb in sorted(Path(AGV_DIR).glob("*.glb")):
written.append(write_config(glb))
update_manifest()
return written
def main(argv: list[str]) -> None:
if len(argv) > 1:
arg = argv[1]
path = Path(arg)
if not path.is_absolute():
path = Path(AGV_DIR) / (arg if arg.endswith(".glb") else f"{arg}.glb")
if not path.exists():
print(f"文件不存在: {path}")
sys.exit(1)
out = write_config(path)
update_manifest()
print(f"已生成: {out}")
data = json.loads(out.read_text(encoding="utf-8"))
print(f"{len(data.get('baseline', data.get('components', {})))} 个基准组件(组件1 …)")
print(" actions: 空(动作在查看器按组件配置)")
return
paths = analyze_all()
print(f"已分析 {len(paths)} 个模型:")
for p in paths:
data = json.loads(p.read_text(encoding="utf-8"))
bl = data.get("baseline", data.get("components", {}))
print(f" {p.name}: {len(bl)} 个基准组件, actions={len(data.get('actions', data.get('states', {})))}")
if __name__ == "__main__":
main(sys.argv)