mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-24 09:49:16 +00:00
v0.10.19
fast registry load minor fix on skill & registry stripe ros2 schema desc add create-device-skill new registry system backwards to yaml remove not exist resource new registry sys exp. support with add device add ai conventions correct raise create resource error ret info fix revert ret info fix fix prcxi check add create_resource schema re signal host ready event add websocket connection timeout and improve reconnection logic add open_timeout parameter to websocket connection add TimeoutError and InvalidStatus exception handling implement exponential backoff for reconnection attempts simplify reconnection logic flow add gzip change pose extra to any add isFlapY
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
# 统一的资源字典模型,parent 自动序列化为 parent_uuid,children 不序列化
|
||||
@@ -147,8 +140,9 @@ class ResourceDict(BaseModel):
|
||||
klass: str = Field(alias="class", description="Resource class name")
|
||||
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")
|
||||
extra: Dict[str, Any] = Field(description="Extra data")
|
||||
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"]):
|
||||
@@ -204,22 +198,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"] = {}
|
||||
@@ -230,16 +232,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)
|
||||
@@ -407,7 +411,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
|
||||
"""
|
||||
@@ -430,13 +434,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):
|
||||
@@ -491,7 +502,7 @@ class ResourceTreeSet(object):
|
||||
k: v
|
||||
for k, v in d.items()
|
||||
if k
|
||||
not in [
|
||||
not in ([
|
||||
"name",
|
||||
"children",
|
||||
"parent_name",
|
||||
@@ -502,7 +513,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,
|
||||
@@ -801,7 +820,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]
|
||||
@@ -848,14 +868,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)
|
||||
# 检查是否已存在
|
||||
@@ -878,7 +911,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 序列化为嵌套列表格式
|
||||
|
||||
@@ -894,6 +927,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
|
||||
|
||||
Reference in New Issue
Block a user