Update graphio together with workstation design.

fix(reaction_station): 为步骤参数添加Value字段传个BY后端

fix(bioyond/warehouses): 修正仓库尺寸和物品排列参数

调整仓库的x轴和z轴物品数量以及物品尺寸参数,使其符合4x1x4的规格要求

fix warehouse serialize/deserialize

fix bioyond converter

fix itemized_carrier.unassign_child_resource

allow not-loaded MSG in registry

add layout serializer & converter

warehouseuse A1-D4; add warehouse layout

fix(graphio): 修正bioyond到plr资源转换中的坐标计算错误

Fix resource assignment and type mapping issues

Corrects resource assignment in ItemizedCarrier by using the correct spot key from _ordering. Updates graphio to use 'typeName' instead of 'name' for type mapping in resource_bioyond_to_plr. Renames DummyWorkstation to BioyondWorkstation in workstation_http_service for clarity.
This commit is contained in:
ZiWei
2025-10-18 18:55:16 +08:00
committed by Xuwznln
parent 37ee43d19a
commit bb3ca645a4
8 changed files with 39 additions and 23 deletions

View File

@@ -605,7 +605,8 @@ class BioyondReactionStation(BioyondWorkstation):
total_params += 1 total_params += 1
step_parameters[step_id][action_name].append({ step_parameters[step_id][action_name].append({
"Key": param_key, "Key": param_key,
"DisplayValue": param_value "DisplayValue": param_value,
"Value": param_value
}) })
successful_params += 1 successful_params += 1
# print(f" ✓ {param_key} = {param_value}") # print(f" ✓ {param_key} = {param_value}")

View File

@@ -668,7 +668,7 @@ __all__ = [
if __name__ == "__main__": if __name__ == "__main__":
# 简单测试HTTP服务 # 简单测试HTTP服务
class DummyWorkstation: class BioyondWorkstation:
device_id = "WS-001" device_id = "WS-001"
def process_step_finish_report(self, report_request): def process_step_finish_report(self, report_request):

View File

@@ -708,6 +708,8 @@ class Registry:
for status_name, status_type in device_config["class"]["status_types"].items(): for status_name, status_type in device_config["class"]["status_types"].items():
device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type] device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type]
for action_name, action_config in device_config["class"]["action_value_mappings"].items(): for action_name, action_config in device_config["class"]["action_value_mappings"].items():
if action_config["type"] not in action_str_type_mapping:
continue
action_config["type"] = action_str_type_mapping[action_config["type"]] action_config["type"] = action_str_type_mapping[action_config["type"]]
# 添加内置的驱动命令动作 # 添加内置的驱动命令动作
self._add_builtin_actions(device_config, device_id) self._add_builtin_actions(device_config, device_id)

View File

@@ -5,15 +5,15 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库""" """创建BioYond 4x1x4仓库"""
return warehouse_factory( return warehouse_factory(
name=name, name=name,
num_items_x=1, num_items_x=4,
num_items_y=4, num_items_y=4,
num_items_z=4, num_items_z=1,
dx=10.0, dx=10.0,
dy=10.0, dy=10.0,
dz=10.0, dz=10.0,
item_dx=137.0, item_dx=147.0,
item_dy=96.0, item_dy=106.0,
item_dz=120.0, item_dz=130.0,
category="warehouse", category="warehouse",
) )

View File

@@ -639,25 +639,24 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
# 处理子物料detail # 处理子物料detail
if material.get("detail") and len(material["detail"]) > 0: if material.get("detail") and len(material["detail"]) > 0:
for bottle in reversed(plr_material.children):
plr_material.unassign_child_resource(bottle)
child_ids = [] child_ids = []
for detail in material["detail"]: for detail in material["detail"]:
number = ( number = (
(detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y (detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y
+ (detail.get("x", 0) - 1) * plr_material.num_items_x + (detail.get("y", 0) - 1) * plr_material.num_items_y
+ (detail.get("y", 0) - 1) + (detail.get("x", 0) - 1)
) )
bottle = plr_material[number] typeName = detail.get("typeName", detail.get("name", ""))
if detail["name"] in type_mapping: if typeName in type_mapping:
# plr_material.unassign_child_resource(bottle) bottle = plr_material[number] = initialize_resource(
plr_material.sites[number] = None {"name": f'{detail["name"]}_{number}', "class": type_mapping[typeName][0]}, resource_type=ResourcePLR
plr_material[number] = initialize_resource(
{"name": f'{detail["name"]}_{number}', "class": type_mapping[detail["name"]][0]}, resource_type=ResourcePLR
) )
else:
bottle.tracker.liquids = [ bottle.tracker.liquids = [
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0) (detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
] ]
bottle.code = detail.get("code", "") bottle.code = detail.get("code", "")
else: else:
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
bottle.tracker.liquids = [ bottle.tracker.liquids = [

View File

@@ -74,6 +74,7 @@ class ItemizedCarrier(ResourcePLR):
num_items_x: int = 0, num_items_x: int = 0,
num_items_y: int = 0, num_items_y: int = 0,
num_items_z: int = 0, num_items_z: int = 0,
layout: str = "x-y",
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None, sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
category: Optional[str] = "carrier", category: Optional[str] = "carrier",
model: Optional[str] = None, model: Optional[str] = None,
@@ -88,6 +89,8 @@ class ItemizedCarrier(ResourcePLR):
) )
self.num_items = len(sites) self.num_items = len(sites)
self.num_items_x, self.num_items_y, self.num_items_z = num_items_x, num_items_y, num_items_z self.num_items_x, self.num_items_y, self.num_items_z = num_items_x, num_items_y, num_items_z
self.layout = "z-y" if self.num_items_z > 1 and self.num_items_x == 1 else "x-z" if self.num_items_z > 1 and self.num_items_y == 1 else "x-y"
if isinstance(sites, dict): if isinstance(sites, dict):
sites = sites or {} sites = sites or {}
self.sites: List[Optional[ResourcePLR]] = list(sites.values()) self.sites: List[Optional[ResourcePLR]] = list(sites.values())
@@ -150,7 +153,7 @@ class ItemizedCarrier(ResourcePLR):
def assign_resource_to_site(self, resource: ResourcePLR, spot: int): def assign_resource_to_site(self, resource: ResourcePLR, spot: int):
if self.sites[spot] is not None and not isinstance(self.sites[spot], ResourceHolder): if self.sites[spot] is not None and not isinstance(self.sites[spot], ResourceHolder):
raise ValueError(f"spot {spot} already has a resource, {resource}") raise ValueError(f"spot {spot} already has a resource, {resource}")
self.assign_child_resource(resource, location=self.child_locations.get(str(spot)), spot=spot) self.assign_child_resource(resource, location=self.child_locations.get(list(self._ordering.keys())[spot]), spot=spot)
def unassign_child_resource(self, resource: ResourcePLR): def unassign_child_resource(self, resource: ResourcePLR):
found = False found = False
@@ -161,8 +164,9 @@ class ItemizedCarrier(ResourcePLR):
break break
if not found: if not found:
raise ValueError(f"Resource {resource} is not assigned to this carrier") raise ValueError(f"Resource {resource} is not assigned to this carrier")
if hasattr(resource, "unassign"): super().unassign_child_resource(resource)
resource.unassign() # if hasattr(resource, "unassign"):
# resource.unassign()
def get_child_identifier(self, child: ResourcePLR): def get_child_identifier(self, child: ResourcePLR):
"""Get the identifier information for a given child resource. """Get the identifier information for a given child resource.
@@ -403,6 +407,7 @@ class ItemizedCarrier(ResourcePLR):
"num_items_x": self.num_items_x, "num_items_x": self.num_items_x,
"num_items_y": self.num_items_y, "num_items_y": self.num_items_y,
"num_items_z": self.num_items_z, "num_items_z": self.num_items_z,
"layout": self.layout,
"sites": [{ "sites": [{
"label": str(identifier), "label": str(identifier),
"visible": True if self[identifier] is not None else False, "visible": True if self[identifier] is not None else False,

View File

@@ -5,10 +5,13 @@ from pylabrobot.resources.carrier import ResourceHolder, create_homogeneous_reso
from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def warehouse_factory( def warehouse_factory(
name: str, name: str,
num_items_x: int = 4, num_items_x: int = 1,
num_items_y: int = 1, num_items_y: int = 4,
num_items_z: int = 4, num_items_z: int = 4,
dx: float = 137.0, dx: float = 137.0,
dy: float = 96.0, dy: float = 96.0,
@@ -33,13 +36,16 @@ def warehouse_factory(
locations.append(Coordinate(x, y, z)) locations.append(Coordinate(x, y, z))
if removed_positions: if removed_positions:
locations = [loc for i, loc in enumerate(locations) if i not in removed_positions] locations = [loc for i, loc in enumerate(locations) if i not in removed_positions]
sites = create_homogeneous_resources( _sites = create_homogeneous_resources(
klass=ResourceHolder, klass=ResourceHolder,
locations=locations, locations=locations,
resource_size_x=127.0, resource_size_x=127.0,
resource_size_y=86.0, resource_size_y=86.0,
name_prefix=name, name_prefix=name,
) )
len_x, len_y = (num_items_x, num_items_y) if num_items_z == 1 else (num_items_y, num_items_z) if num_items_x == 1 else (num_items_x, num_items_z)
keys = [f"{LETTERS[j]}{i + 1}" for i in range(len_x) for j in range(len_y)]
sites = {i: site for i, site in zip(keys, _sites.values())}
return WareHouse( return WareHouse(
name=name, name=name,
@@ -68,6 +74,7 @@ class WareHouse(ItemizedCarrier):
num_items_x: int, num_items_x: int,
num_items_y: int, num_items_y: int,
num_items_z: int, num_items_z: int,
layout: str = "x-y",
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None, sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
category: str = "warehouse", category: str = "warehouse",
model: Optional[str] = None, model: Optional[str] = None,
@@ -83,6 +90,7 @@ class WareHouse(ItemizedCarrier):
num_items_x=num_items_x, num_items_x=num_items_x,
num_items_y=num_items_y, num_items_y=num_items_y,
num_items_z=num_items_z, num_items_z=num_items_z,
layout=layout,
sites=sites, sites=sites,
category=category, category=category,
model=model, model=model,

View File

@@ -342,6 +342,7 @@ class ResourceTreeSet(object):
pos = { pos = {
"size": {"width": d["size_x"], "height": d["size_y"], "depth": d["size_z"]}, "size": {"width": d["size_x"], "height": d["size_y"], "depth": d["size_z"]},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0}, "scale": {"x": 1.0, "y": 1.0, "z": 1.0},
"layout": d.get("layout", "x-y"),
"position": raw_pos, "position": raw_pos,
"position3d": raw_pos, "position3d": raw_pos,
"rotation": d["rotation"], "rotation": d["rotation"],