diff --git a/test/resources/test_resourcetreeset.py b/test/resources/test_resourcetreeset.py new file mode 100644 index 00000000..1ba9ab20 --- /dev/null +++ b/test/resources/test_resourcetreeset.py @@ -0,0 +1,68 @@ +import pytest +import json +import os + +from pylabrobot.resources import Resource as ResourcePLR +from unilabos.resources.graphio import resource_bioyond_to_plr +from unilabos.ros.nodes.resource_tracker import ResourceTreeSet +from unilabos.registry.registry import lab_registry + +from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck + +lab_registry.setup() + + +type_mapping = { + "烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), + "试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""), + "样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), + "分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), + "样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), + "90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), + "10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), +} + + +@pytest.fixture +def bioyond_materials_reaction() -> list[dict]: + print("加载 BioYond 物料数据...") + print(os.getcwd()) + with open("bioyond_materials_reaction.json", "r", encoding="utf-8") as f: + data = json.load(f) + print(f"加载了 {len(data)} 条物料数据") + return data + + +@pytest.fixture +def bioyond_materials_liquidhandling_1() -> list[dict]: + print("加载 BioYond 物料数据...") + print(os.getcwd()) + with open("bioyond_materials_liquidhandling_1.json", "r", encoding="utf-8") as f: + data = json.load(f) + print(f"加载了 {len(data)} 条物料数据") + return data + + +@pytest.fixture +def bioyond_materials_liquidhandling_2() -> list[dict]: + print("加载 BioYond 物料数据...") + print(os.getcwd()) + with open("bioyond_materials_liquidhandling_2.json", "r", encoding="utf-8") as f: + data = json.load(f) + print(f"加载了 {len(data)} 条物料数据") + return data + + +@pytest.mark.parametrize("materials_fixture", [ + "bioyond_materials_reaction", + "bioyond_materials_liquidhandling_1", +]) +def test_resourcetreeset_from_plr(materials_fixture, request) -> list[dict]: + materials = request.getfixturevalue(materials_fixture) + deck = BIOYOND_PolymerReactionStation_Deck("test_deck") + output = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=deck) + print(deck.summary()) + + r = ResourceTreeSet.from_plr_resources([deck]) + print(r.dump()) + # json.dump(deck.serialize(), open("test.json", "w", encoding="utf-8"), indent=4) diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py index 22919c5c..d4afaad3 100644 --- a/unilabos/ros/nodes/resource_tracker.py +++ b/unilabos/ros/nodes/resource_tracker.py @@ -42,6 +42,7 @@ class ResourceDictPosition(BaseModel): rotation: ResourceDictPositionObject = Field( description="Resource rotation", default_factory=ResourceDictPositionObject ) + cross_section_type: Literal["rectangle"] = Field(description="Cross section type", default="rectangle") # 统一的资源字典模型,parent 自动序列化为 parent_uuid,children 不序列化 @@ -58,6 +59,7 @@ class ResourceDict(BaseModel): type: Literal["device"] | str = Field(description="Resource type") klass: str = Field(alias="class", description="Resource class name") position: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition) + pose: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition) config: Dict[str, Any] = Field(description="Resource configuration") data: Dict[str, Any] = Field(description="Resource data") @@ -136,6 +138,8 @@ class ResourceDictInstance(object): content["config"] = {} if not content.get("data"): content["data"] = {} + if "pose" not in content: + content["pose"] = content["position"] return ResourceDictInstance(ResourceDict.model_validate(content)) def get_nested_dict(self) -> Dict[str, Any]: @@ -330,6 +334,20 @@ class ResourceTreeSet(object): ) -> ResourceDictInstance: current_uuid = uuids.pop(0) + raw_pos = ( + {"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]} + if d["location"] + else {"x": 0, "y": 0, "z": 0} + ) + pos = { + "size": {"width": d["size_x"], "height": d["size_y"], "depth": d["size_z"]}, + "scale": {"x": 1.0, "y": 1.0, "z": 1.0}, + "position": raw_pos, + "position3d": raw_pos, + "rotation": d["rotation"], + "cross_section_type": d.get("cross_section_type", "rectangle"), + } + # 先构建当前节点的字典(不包含children) r_dict = { "id": d["name"], @@ -338,12 +356,10 @@ class ResourceTreeSet(object): "parent": parent_resource, # 直接传入 ResourceDict 对象 "type": replace_plr_type(d.get("category", "")), "class": d.get("class", ""), - "position": ( - {"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]} - if d["location"] - else {"x": 0, "y": 0, "z": 0} - ), - "config": {k: v for k, v in d.items() if k not in ["name", "children", "parent_name", "location"]}, + "position": pos, + "pose": pos, + "config": {k: v for k, v in d.items() if k not in + ["name", "children", "parent_name", "location", "rotation", "size_x", "size_y", "size_z", "cross_section_type", "bottom_type"]}, "data": states[d["name"]], }