Merge branch 'dev' into feat/lab_resource

This commit is contained in:
q434343
2026-03-25 19:48:39 +08:00
parent ad2e5a1c04
commit 1ab1ed69d4
94 changed files with 13141 additions and 5618 deletions

View File

@@ -75,14 +75,6 @@ class ResourceDictPositionObject(BaseModel):
z: float = Field(description="Z coordinate", default=0.0)
class ResourceDictPoseExtraObjectType(BaseModel):
z_index: int
class ResourceDictPoseExtraObject(BaseModel):
z_index: Optional[int] = Field(alias="zIndex", default=None)
class ResourceDictPositionType(TypedDict):
size: ResourceDictPositionSizeType
scale: ResourceDictPositionScaleType
@@ -109,7 +101,7 @@ class ResourceDictPosition(BaseModel):
cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"] = Field(
description="Cross section type", default="rectangle"
)
extra: Optional[ResourceDictPoseExtraObject] = Field(description="Extra data", default=None)
extra: Optional[Dict[str, Any]] = Field(description="Extra data", default=None)
class ResourceDictType(TypedDict):
@@ -128,6 +120,7 @@ class ResourceDictType(TypedDict):
config: Dict[str, Any]
data: Dict[str, Any]
extra: Dict[str, Any]
machine_name: str
class ResourceDictType(TypedDict):
@@ -167,6 +160,7 @@ class ResourceDict(BaseModel):
config: Dict[str, Any] = Field(description="Resource configuration")
data: Dict[str, Any] = Field(description="Resource data, eg: container liquid data")
extra: Dict[str, Any] = Field(description="Extra data, eg: slot index")
machine_name: str = Field(description="Machine this resource belongs to", default="")
@field_serializer("parent_uuid")
def _serialize_parent(self, parent_uuid: Optional["ResourceDict"]):
@@ -222,22 +216,30 @@ class ResourceDictInstance(object):
self.typ = "dict"
@classmethod
def get_resource_instance_from_dict(cls, content: Dict[str, Any]) -> "ResourceDictInstance":
def get_resource_instance_from_dict(cls, content: ResourceDictType) -> "ResourceDictInstance":
"""从字典创建资源实例"""
if "id" not in content:
content["id"] = content["name"]
if "uuid" not in content:
content["uuid"] = str(uuid.uuid4())
if "description" in content and content["description"] is None:
# noinspection PyTypedDict
del content["description"]
if "model" in content and content["model"] is None:
# noinspection PyTypedDict
del content["model"]
# noinspection PyTypedDict
if "schema" in content and content["schema"] is None:
# noinspection PyTypedDict
del content["schema"]
# noinspection PyTypedDict
if "x" in content.get("position", {}):
# 说明是老版本的position格式转换成新的
# noinspection PyTypedDict
content["position"] = {"position": content["position"]}
# noinspection PyTypedDict
if not content.get("class"):
# noinspection PyTypedDict
content["class"] = ""
if not content.get("config"): # todo: 后续从后端保证字段非空
content["config"] = {}
@@ -248,16 +250,18 @@ class ResourceDictInstance(object):
if "position" in content:
pose = content.get("pose", {})
if "position" not in pose:
# noinspection PyTypedDict
if "position" in content["position"]:
# noinspection PyTypedDict
pose["position"] = content["position"]["position"]
else:
pose["position"] = {"x": 0, "y": 0, "z": 0}
pose["position"] = ResourceDictPositionObjectType(x=0, y=0, z=0)
if "size" not in pose:
pose["size"] = {
"width": content["config"].get("size_x", 0),
"height": content["config"].get("size_y", 0),
"depth": content["config"].get("size_z", 0),
}
pose["size"] = ResourceDictPositionSizeType(
width= content["config"].get("size_x", 0),
height= content["config"].get("size_y", 0),
depth= content["config"].get("size_z", 0),
)
content["pose"] = pose
try:
res_dict = ResourceDict.model_validate(content)
@@ -425,7 +429,7 @@ class ResourceTreeSet(object):
)
@classmethod
def from_plr_resources(cls, resources: List["PLRResource"], known_newly_created=False) -> "ResourceTreeSet":
def from_plr_resources(cls, resources: List["PLRResource"], known_newly_created=False, old_size=False) -> "ResourceTreeSet":
"""
从plr资源创建ResourceTreeSet
"""
@@ -448,13 +452,20 @@ class ResourceTreeSet(object):
"resource_group": "resource_group",
"trash": "trash",
"plate_adapter": "plate_adapter",
"consumable": "consumable",
"tool": "tool",
"condenser": "condenser",
"crucible": "crucible",
"reagent_bottle": "reagent_bottle",
"flask": "flask",
"beaker": "beaker",
}
if source in replace_info:
return replace_info[source]
elif source is None:
return ""
else:
print("转换pylabrobot的时候出现未知类型", source)
logger.trace(f"转换pylabrobot的时候出现未知类型 {source}")
return source
def build_uuid_mapping(res: "PLRResource", uuid_list: list, parent_uuid: Optional[str] = None):
@@ -509,7 +520,7 @@ class ResourceTreeSet(object):
k: v
for k, v in d.items()
if k
not in [
not in ([
"name",
"children",
"parent_name",
@@ -520,7 +531,15 @@ class ResourceTreeSet(object):
"size_z",
"cross_section_type",
"bottom_type",
]
] if not old_size else [
"name",
"children",
"parent_name",
"location",
"rotation",
"cross_section_type",
"bottom_type",
])
},
"data": states[d["name"]],
"extra": extra,
@@ -920,7 +939,8 @@ class ResourceTreeSet(object):
if remote_root_type == "device":
# 情况1: 一级是 device
if remote_root_id not in local_device_map:
logger.warning(f"Device '{remote_root_id}' 在本地不存在,跳过该 device 下的物料同步")
if remote_root_id != "host_node":
logger.warning(f"Device '{remote_root_id}' 在本地不存在,跳过该 device 下的物料同步")
continue
local_device = local_device_map[remote_root_id]
@@ -967,14 +987,27 @@ class ResourceTreeSet(object):
f"从远端同步了 {added_count} 个物料子树"
)
else:
# 情况2: 二级物料(不是 device
if remote_child_name not in local_children_map:
# 引入整个子树
remote_child.res_content.parent = local_device.res_content
local_device.children.append(remote_child)
logger.info(f"Device '{remote_root_id}': 从远端同步物料子树 '{remote_child_name}'")
else:
logger.info(f"物料 '{remote_root_id}/{remote_child_name}' 已存在,跳过")
# 二级物料已存在,比较三级子节点是否缺失
local_material = local_children_map[remote_child_name]
local_material_children_map = {child.res_content.name: child for child in
local_material.children}
added_count = 0
for remote_sub in remote_child.children:
remote_sub_name = remote_sub.res_content.name
if remote_sub_name not in local_material_children_map:
remote_sub.res_content.parent = local_material.res_content
local_material.children.append(remote_sub)
added_count += 1
else:
logger.info(
f"物料 '{remote_root_id}/{remote_child_name}/{remote_sub_name}' "
f"已存在,跳过"
)
if added_count > 0:
logger.info(
f"物料 '{remote_root_id}/{remote_child_name}': "
f"从远端同步了 {added_count} 个子物料"
)
else:
# 情况1: 一级节点是物料(不是 device
# 检查是否已存在
@@ -997,7 +1030,7 @@ class ResourceTreeSet(object):
return self
def dump(self) -> List[List[Dict[str, Any]]]:
def dump(self, old_position=False) -> List[List[Dict[str, Any]]]:
"""
将 ResourceTreeSet 序列化为嵌套列表格式
@@ -1013,6 +1046,10 @@ class ResourceTreeSet(object):
# 获取树的所有节点并序列化
tree_nodes = [node.res_content.model_dump(by_alias=True) for node in tree.get_all_nodes()]
result.append(tree_nodes)
if old_position:
for r in result:
for rr in r:
rr["position"] = rr["pose"]["position"]
return result
@classmethod