feat(app): 模型上传与注册增强 — normalize_model、upload_model_package、backend client

- model_upload.py: normalize_model_package 标准化模型目录 + upload_model_package 上传到后端
- register.py: 设备注册时自动检测并上传本地模型文件
- web/client.py: BackendClient 新增 get_model_upload_urls/publish_model/update_template_model
- tests: test_model_upload.py、test_normalize_model.py 单元测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Junhan Chang
2026-03-24 23:02:18 +08:00
parent 48e13a7b4d
commit 090d5c5cb5
6 changed files with 856 additions and 1 deletions

View File

@@ -5,6 +5,48 @@ from unilabos.utils.log import logger
from unilabos.utils.tools import normalize_json as _normalize_device
def normalize_model_for_upload(model_dict: dict) -> dict:
"""将 Registry YAML 的 model 字段映射为后端 DeviceModel 结构化格式。
保留所有原始字段,额外做以下标准化:
1. 自动推断 format如果 YAML 未指定)
2. 将 children_mesh 扁平字段映射为结构化 children_mesh 对象
"""
if not model_dict:
return model_dict
result = {**model_dict}
# 自动推断 format
if "format" not in result and result.get("path"):
path = result["path"]
if path.endswith(".xacro"):
result["format"] = "xacro"
elif path.endswith(".urdf"):
result["format"] = "urdf"
elif path.endswith(".stl"):
result["format"] = "stl"
elif path.endswith((".gltf", ".glb")):
result["format"] = "gltf"
# 将 children_mesh 扁平字段 → 结构化 children_mesh 对象
if "children_mesh" in result and isinstance(result["children_mesh"], str):
cm_path = result.pop("children_mesh")
cm_tf = result.pop("children_mesh_tf", None)
cm_oss = result.pop("children_mesh_path", None)
result["children_mesh"] = {
"path": cm_oss or cm_path,
"format": "stl" if cm_path.endswith(".stl") else "gltf",
"default_visible": True,
}
if cm_tf and len(cm_tf) >= 3:
result["children_mesh"]["local_offset"] = cm_tf[:3]
if cm_tf and len(cm_tf) >= 6:
result["children_mesh"]["local_rotation"] = cm_tf[3:6]
return result
def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
"""
注册设备和资源到服务器仅支持HTTP
@@ -16,11 +58,18 @@ def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[
devices_to_register = {}
for device_info in lab_registry.obtain_registry_device_info():
devices_to_register[device_info["id"]] = _normalize_device(device_info)
normalized = _normalize_device(device_info)
# 标准化 model 字段
if normalized.get("model"):
normalized["model"] = normalize_model_for_upload(normalized["model"])
devices_to_register[device_info["id"]] = normalized
logger.trace(f"[UniLab Register] 收集设备: {device_info['id']}")
resources_to_register = {}
for resource_info in lab_registry.obtain_registry_resource_info():
# 标准化 model 字段
if resource_info.get("model"):
resource_info["model"] = normalize_model_for_upload(resource_info["model"])
resources_to_register[resource_info["id"]] = resource_info
logger.trace(f"[UniLab Register] 收集资源: {resource_info['id']}")