Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
48e13a7
P1 Edge: 关节桥接重构 — 直接订阅 /joint_states + 资源跟随 + 吞吐优化
TablewareBox Mar 23, 2026
090d5c5
feat(app): 模型上传与注册增强 — normalize_model、upload_model_package、backend c…
TablewareBox Mar 24, 2026
18296d3
Update model_upload.py
TablewareBox Mar 25, 2026
9862415
Update test_model_upload.py
TablewareBox Mar 25, 2026
044fd2f
add phate display related device registy, located in ./unilabos/devic…
Apr 9, 2026
ea34c82
Add 3D mesh to devicesneeded in phage_display (mostly using fallbackb…
Apr 10, 2026
7f5e086
modify 3D xarco files to expose access points and avoid collision (st…
Apr 10, 2026
1e94266
add overview and prompt files for 3D mesh building (guesing)
Apr 10, 2026
1f09a26
update 3D model path and add lab layout graph json
Apr 14, 2026
62206aa
fix bug: graph json id change . to _ and vintage_backend set default …
Apr 14, 2026
6f791ef
修改yaml与moveit
q434343 Apr 14, 2026
c302ae4
Merge remote-tracking branch 'upstream/feat/3d_builder_and_motion_vis…
q434343 Apr 14, 2026
2f68e85
修改模型尺寸
q434343 Apr 14, 2026
572c7d6
修改布局
q434343 Apr 14, 2026
8a01f62
更新pick and place的默认输入,以及对应的判断条件
q434343 Apr 16, 2026
af2302d
update past and new layout versions in .project_phage_display
q434343 Apr 16, 2026
162761b
删除测试文件夹
q434343 Apr 16, 2026
1239421
fix hotel.yaml and add 3d demo json
Apr 17, 2026
b671210
修改 sealer与它的backend
q434343 Apr 17, 2026
2e5d1ef
修改AST方法,并添加mock类防止初始化失败
q434343 Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions tests/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"""normalize_model_for_upload 单元测试"""

import unittest
import sys
import os

# 添加项目根目录到 sys.path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))

from unilabos.app.register import normalize_model_for_upload


class TestNormalizeModelForUpload(unittest.TestCase):
"""测试 Registry YAML model 字段标准化"""

def test_empty_input(self):
"""空 dict 直接返回"""
self.assertEqual(normalize_model_for_upload({}), {})
self.assertIsNone(normalize_model_for_upload(None))

def test_format_infer_xacro(self):
"""自动从 path 后缀推断 format=xacro"""
model = {
"path": "https://oss.example.com/devices/arm/macro_device.xacro",
"mesh": "arm_slider",
"type": "device",
}
result = normalize_model_for_upload(model)
self.assertEqual(result["format"], "xacro")

def test_format_infer_urdf(self):
"""自动推断 format=urdf"""
model = {"path": "https://example.com/robot.urdf", "type": "device"}
result = normalize_model_for_upload(model)
self.assertEqual(result["format"], "urdf")

def test_format_infer_stl(self):
"""自动推断 format=stl"""
model = {"path": "https://example.com/part.stl"}
result = normalize_model_for_upload(model)
self.assertEqual(result["format"], "stl")

def test_format_infer_gltf(self):
"""自动推断 format=gltf(.gltf 和 .glb)"""
for ext in [".gltf", ".glb"]:
model = {"path": f"https://example.com/model{ext}"}
result = normalize_model_for_upload(model)
self.assertEqual(result["format"], "gltf", f"failed for {ext}")

def test_format_not_overwritten(self):
"""已有 format 字段时不覆盖"""
model = {
"path": "https://example.com/model.xacro",
"format": "custom",
}
result = normalize_model_for_upload(model)
self.assertEqual(result["format"], "custom")

def test_format_no_path(self):
"""没有 path 时不推断 format"""
model = {"mesh": "arm_slider", "type": "device"}
result = normalize_model_for_upload(model)
self.assertNotIn("format", result)

def test_children_mesh_string_to_struct(self):
"""将 children_mesh 字符串(旧格式)转为结构化对象"""
model = {
"path": "https://example.com/rack.xacro",
"type": "resource",
"children_mesh": "tip/meshes/tip.stl",
"children_mesh_tf": [0.0045, 0.0045, 0, 0, 0, 1.57],
"children_mesh_path": "https://oss.example.com/tip.stl",
}
result = normalize_model_for_upload(model)

# children_mesh 应变为 dict
cm = result["children_mesh"]
self.assertIsInstance(cm, dict)
self.assertEqual(cm["path"], "https://oss.example.com/tip.stl") # 优先使用 OSS URL
self.assertEqual(cm["format"], "stl")
self.assertTrue(cm["default_visible"])
self.assertEqual(cm["local_offset"], [0.0045, 0.0045, 0])
self.assertEqual(cm["local_rotation"], [0, 0, 1.57])

# 旧字段应被移除
self.assertNotIn("children_mesh_tf", result)
self.assertNotIn("children_mesh_path", result)

def test_children_mesh_no_oss_fallback(self):
"""children_mesh 无 OSS URL 时 fallback 到本地路径"""
model = {
"path": "https://example.com/rack.xacro",
"children_mesh": "plate_96/meshes/plate_96.stl",
}
result = normalize_model_for_upload(model)
cm = result["children_mesh"]
self.assertEqual(cm["path"], "plate_96/meshes/plate_96.stl")
self.assertEqual(cm["format"], "stl")

def test_children_mesh_gltf_format(self):
"""children_mesh .glb 文件推断 format=gltf"""
model = {
"path": "https://example.com/rack.xacro",
"children_mesh": "meshes/child.glb",
}
result = normalize_model_for_upload(model)
self.assertEqual(result["children_mesh"]["format"], "gltf")

def test_children_mesh_partial_tf(self):
"""children_mesh_tf 只有 3 个值时只有 offset 无 rotation"""
model = {
"path": "https://example.com/rack.xacro",
"children_mesh": "tip.stl",
"children_mesh_tf": [0.01, 0.02, 0.03],
}
result = normalize_model_for_upload(model)
cm = result["children_mesh"]
self.assertEqual(cm["local_offset"], [0.01, 0.02, 0.03])
self.assertNotIn("local_rotation", cm)

def test_children_mesh_no_tf(self):
"""children_mesh 无 tf 时不加 offset/rotation"""
model = {
"path": "https://example.com/rack.xacro",
"children_mesh": "tip.stl",
}
result = normalize_model_for_upload(model)
cm = result["children_mesh"]
self.assertNotIn("local_offset", cm)
self.assertNotIn("local_rotation", cm)

def test_children_mesh_already_dict(self):
"""children_mesh 已经是 dict 时不重新映射"""
model = {
"path": "https://example.com/rack.xacro",
"children_mesh": {
"path": "https://example.com/tip.stl",
"format": "stl",
"default_visible": False,
},
}
result = normalize_model_for_upload(model)
cm = result["children_mesh"]
self.assertIsInstance(cm, dict)
self.assertFalse(cm["default_visible"])

def test_original_not_mutated(self):
"""原始 dict 不被修改"""
original = {
"path": "https://example.com/model.xacro",
"mesh": "arm",
}
original_copy = {**original}
normalize_model_for_upload(original)
self.assertEqual(original, original_copy)

def test_preserves_existing_fields(self):
"""所有原始字段都被保留"""
model = {
"path": "https://example.com/model.xacro",
"mesh": "arm_slider",
"type": "device",
"mesh_tf": [0, 0, 0, 0, 0, 0],
"custom_field": "should_survive",
}
result = normalize_model_for_upload(model)
self.assertEqual(result["custom_field"], "should_survive")
self.assertEqual(result["mesh_tf"], [0, 0, 0, 0, 0, 0])


if __name__ == "__main__":
unittest.main()
Loading