mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-06 06:25:06 +00:00
Merge branch 'dev' into feature/organic-extraction
This commit is contained in:
@@ -46,7 +46,7 @@ requirements:
|
|||||||
- jinja2
|
- jinja2
|
||||||
- requests
|
- requests
|
||||||
- uvicorn
|
- uvicorn
|
||||||
- opcua
|
- opcua # [not osx]
|
||||||
- pyserial
|
- pyserial
|
||||||
- pandas
|
- pandas
|
||||||
- pymodbus
|
- pymodbus
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
||||||
|
|
||||||
from typing_extensions import TypedDict
|
|
||||||
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
||||||
from unilabos.devices.liquid_handling.rviz_backend import UniLiquidHandlerRvizBackend
|
|
||||||
from unilabos.devices.liquid_handling.laiyu.backend.laiyu_v_backend import UniLiquidHandlerLaiyuBackend
|
|
||||||
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
|
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
|
||||||
from pylabrobot.liquid_handling.standard import GripDirection
|
from pylabrobot.liquid_handling.standard import GripDirection
|
||||||
from pylabrobot.resources import (
|
from pylabrobot.resources import (
|
||||||
@@ -28,14 +24,27 @@ from pylabrobot.resources import (
|
|||||||
Tip,
|
Tip,
|
||||||
)
|
)
|
||||||
|
|
||||||
from unilabos.registry.placeholder_type import ResourceSlot
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||||
from unilabos.resources.resource_tracker import ResourceTreeSet
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleReturn(TypedDict):
|
class SimpleReturn(TypedDict):
|
||||||
samples: list
|
samples: List[List[ResourceDict]]
|
||||||
volumes: list
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
|
class SetLiquidReturn(TypedDict):
|
||||||
|
wells: List[List[ResourceDict]]
|
||||||
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
|
class SetLiquidFromPlateReturn(TypedDict):
|
||||||
|
plate: List[List[ResourceDict]]
|
||||||
|
wells: List[List[ResourceDict]]
|
||||||
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
|
class TransferLiquidReturn(TypedDict):
|
||||||
|
sources: List[List[ResourceDict]]
|
||||||
|
targets: List[List[ResourceDict]]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SetLiquidReturn(TypedDict):
|
class SetLiquidReturn(TypedDict):
|
||||||
@@ -678,40 +687,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
||||||
res_volumes.append(volume)
|
res_volumes.append(volume)
|
||||||
|
|
||||||
return SetLiquidReturn(
|
return SimpleReturn(samples=res_samples, volumes=res_volumes)
|
||||||
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), volumes=res_volumes # type: ignore
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_liquid_from_plate(
|
|
||||||
cls, plate: ResourceSlot, well_names: list[str], liquid_names: list[str], volumes: list[float]
|
|
||||||
) -> SetLiquidFromPlateReturn:
|
|
||||||
"""Set the liquid in wells of a plate by well names (e.g., A1, A2, B3).
|
|
||||||
|
|
||||||
如果 liquid_names 和 volumes 为空,但 plate 和 well_names 不为空,直接返回 plate 和 wells。
|
|
||||||
"""
|
|
||||||
# 根据 well_names 获取对应的 Well 对象
|
|
||||||
wells = [plate.get_well(name) for name in well_names]
|
|
||||||
res_volumes = []
|
|
||||||
|
|
||||||
# 如果 liquid_names 和 volumes 都为空,直接返回
|
|
||||||
if not liquid_names and not volumes:
|
|
||||||
return SetLiquidFromPlateReturn(
|
|
||||||
plate=ResourceTreeSet.from_plr_resources([plate], known_newly_created=False).dump(), # type: ignore
|
|
||||||
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), # type: ignore
|
|
||||||
volumes=res_volumes,
|
|
||||||
)
|
|
||||||
|
|
||||||
for well, liquid_name, volume in zip(wells, liquid_names, volumes):
|
|
||||||
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
|
||||||
res_volumes.append(volume)
|
|
||||||
|
|
||||||
return SetLiquidFromPlateReturn(
|
|
||||||
plate=ResourceTreeSet.from_plr_resources([plate], known_newly_created=False).dump(), # type: ignore
|
|
||||||
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), # type: ignore
|
|
||||||
volumes=res_volumes,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# REMOVE LIQUID --------------------------------------------------
|
# REMOVE LIQUID --------------------------------------------------
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
@@ -1111,7 +1087,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
):
|
) -> TransferLiquidReturn:
|
||||||
"""Transfer liquid with automatic mode detection.
|
"""Transfer liquid with automatic mode detection.
|
||||||
|
|
||||||
Supports three transfer modes:
|
Supports three transfer modes:
|
||||||
@@ -1251,6 +1227,11 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
"Supported modes: 1->N, N->1, or N->N."
|
"Supported modes: 1->N, N->1, or N->N."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return TransferLiquidReturn(
|
||||||
|
sources=ResourceTreeSet.from_plr_resources(list(sources), known_newly_created=False).dump(), # type: ignore
|
||||||
|
targets=ResourceTreeSet.from_plr_resources(list(targets), known_newly_created=False).dump(), # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
async def _transfer_one_to_one(
|
async def _transfer_one_to_one(
|
||||||
self,
|
self,
|
||||||
sources: Sequence[Container],
|
sources: Sequence[Container],
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ from unilabos.devices.liquid_handling.liquid_handler_abstract import (
|
|||||||
SimpleReturn,
|
SimpleReturn,
|
||||||
SetLiquidReturn,
|
SetLiquidReturn,
|
||||||
SetLiquidFromPlateReturn,
|
SetLiquidFromPlateReturn,
|
||||||
|
TransferLiquidReturn,
|
||||||
)
|
)
|
||||||
from unilabos.registry.placeholder_type import ResourceSlot
|
from unilabos.registry.placeholder_type import ResourceSlot
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||||
@@ -154,25 +155,29 @@ class PRCXI9300Plate(Plate):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
# 如果 ordered_items 不为 None,直接使用
|
# 如果 ordered_items 不为 None,直接使用
|
||||||
|
items = None
|
||||||
|
ordering_param = None
|
||||||
if ordered_items is not None:
|
if ordered_items is not None:
|
||||||
items = ordered_items
|
items = ordered_items
|
||||||
elif ordering is not None:
|
elif ordering is not None:
|
||||||
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
|
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
|
||||||
# 如果是字符串,说明这是位置名称,需要让 Plate 自己创建 Well 对象
|
# 如果是字符串,说明这是位置名称,需要让 Plate 自己创建 Well 对象
|
||||||
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
|
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
|
||||||
if ordering and isinstance(next(iter(ordering.values()), None), str):
|
if ordering:
|
||||||
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
|
values = list(ordering.values())
|
||||||
# 传递 ordering 参数而不是 ordered_items,让 Plate 自己创建 Well 对象
|
value = values[0]
|
||||||
items = None
|
if isinstance(value, str):
|
||||||
# 使用 ordering 参数,只包含位置信息(键)
|
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
|
||||||
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
# 传递 ordering 参数而不是 ordered_items,让 Plate 自己创建 Well 对象
|
||||||
|
items = None
|
||||||
|
# 使用 ordering 参数,只包含位置信息(键)
|
||||||
|
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
||||||
|
elif value is None:
|
||||||
|
ordering_param = ordering
|
||||||
else:
|
else:
|
||||||
# ordering 的值已经是对象,可以直接使用
|
# ordering 的值已经是对象,可以直接使用
|
||||||
items = ordering
|
items = ordering
|
||||||
ordering_param = None
|
ordering_param = None
|
||||||
else:
|
|
||||||
items = None
|
|
||||||
ordering_param = None
|
|
||||||
|
|
||||||
# 根据情况传递不同的参数
|
# 根据情况传递不同的参数
|
||||||
if items is not None:
|
if items is not None:
|
||||||
@@ -590,7 +595,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
return super().set_liquid(wells, liquid_names, volumes)
|
return super().set_liquid(wells, liquid_names, volumes)
|
||||||
|
|
||||||
def set_liquid_from_plate(
|
def set_liquid_from_plate(
|
||||||
self, plate: ResourceSlot, well_names: list[str], liquid_names: list[str], volumes: list[float]
|
self, plate: List[ResourceSlot], well_names: list[str], liquid_names: list[str], volumes: list[float]
|
||||||
) -> SetLiquidFromPlateReturn:
|
) -> SetLiquidFromPlateReturn:
|
||||||
return super().set_liquid_from_plate(plate, well_names, liquid_names, volumes)
|
return super().set_liquid_from_plate(plate, well_names, liquid_names, volumes)
|
||||||
|
|
||||||
@@ -713,7 +718,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
):
|
) -> TransferLiquidReturn:
|
||||||
return await super().transfer_liquid(
|
return await super().transfer_liquid(
|
||||||
sources,
|
sources,
|
||||||
targets,
|
targets,
|
||||||
|
|||||||
@@ -638,7 +638,7 @@ liquid_handler:
|
|||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 吸头迭代函数。用于自动管理和切换吸头架中的吸头,实现批量实验中的吸头自动分配和追踪。该函数监控吸头使用状态,自动切换到下一个可用吸头位置,确保实验流程的连续性。适用于高通量实验、批量处理、自动化流水线等需要大量吸头管理的应用场景。
|
description: 吸头迭代函数。用于自动管理和切换枪头盒中的吸头,实现批量实验中的吸头自动分配和追踪。该函数监控吸头使用状态,自动切换到下一个可用吸头位置,确保实验流程的连续性。适用于高通量实验、批量处理、自动化流水线等需要大量吸头管理的应用场景。
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -712,6 +712,43 @@ liquid_handler:
|
|||||||
title: set_group参数
|
title: set_group参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-set_liquid_from_plate:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
liquid_names: null
|
||||||
|
plate: null
|
||||||
|
volumes: null
|
||||||
|
well_names: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
liquid_names:
|
||||||
|
type: string
|
||||||
|
plate:
|
||||||
|
type: string
|
||||||
|
volumes:
|
||||||
|
type: string
|
||||||
|
well_names:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- plate
|
||||||
|
- well_names
|
||||||
|
- liquid_names
|
||||||
|
- volumes
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_liquid_from_plate参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-set_tiprack:
|
auto-set_tiprack:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -721,7 +758,7 @@ liquid_handler:
|
|||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 吸头架设置函数。用于配置和初始化液体处理系统的吸头架信息,包括吸头架位置、类型、容量等参数。该函数建立吸头资源管理系统,为后续的吸头选择和使用提供基础配置。适用于系统初始化、吸头架更换、实验配置等需要吸头资源管理的操作场景。
|
description: 枪头盒设置函数。用于配置和初始化液体处理系统的枪头盒信息,包括枪头盒位置、类型、容量等参数。该函数建立吸头资源管理系统,为后续的吸头选择和使用提供基础配置。适用于系统初始化、枪头盒更换、实验配置等需要吸头资源管理的操作场景。
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -4093,32 +4130,32 @@ liquid_handler:
|
|||||||
- 0
|
- 0
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: liquid
|
- data_key: sources
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: sources
|
handler_key: sources
|
||||||
label: sources
|
label: 待移动液体
|
||||||
- data_key: liquid
|
- data_key: targets
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: targets
|
|
||||||
label: targets
|
|
||||||
- data_key: liquid
|
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: tip_rack
|
|
||||||
label: tip_rack
|
|
||||||
output:
|
|
||||||
- data_key: liquid
|
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
|
handler_key: targets
|
||||||
|
label: 转移目标
|
||||||
|
- data_key: tip_racks
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: tip_rack
|
||||||
|
label: 枪头盒
|
||||||
|
output:
|
||||||
|
- data_key: sources.@flatten
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
handler_key: sources_out
|
handler_key: sources_out
|
||||||
label: sources
|
label: 移液后源孔
|
||||||
- data_key: liquid
|
- data_key: targets.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: targets_out
|
handler_key: targets_out
|
||||||
label: targets
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
@@ -5114,19 +5151,34 @@ liquid_handler.biomek:
|
|||||||
- 0
|
- 0
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: liquid
|
- data_key: sources
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: liquid-input
|
handler_key: sources
|
||||||
io_type: target
|
io_type: target
|
||||||
label: Liquid Input
|
label: 待移动液体
|
||||||
|
- data_key: targets
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: targets
|
||||||
|
label: 转移目标
|
||||||
|
- data_key: tip_racks
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: tip_rack
|
||||||
|
label: 枪头盒
|
||||||
output:
|
output:
|
||||||
- data_key: liquid
|
- data_key: sources.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: liquid-output
|
handler_key: sources_out
|
||||||
io_type: source
|
io_type: source
|
||||||
label: Liquid Output
|
label: 移液后源孔
|
||||||
|
- data_key: targets.@flatten
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: targets_out
|
||||||
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
@@ -9451,78 +9503,81 @@ liquid_handler.prcxi:
|
|||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
plate:
|
plate:
|
||||||
properties:
|
items:
|
||||||
category:
|
properties:
|
||||||
type: string
|
category:
|
||||||
children:
|
|
||||||
items:
|
|
||||||
type: string
|
type: string
|
||||||
type: array
|
children:
|
||||||
config:
|
items:
|
||||||
type: string
|
type: string
|
||||||
data:
|
type: array
|
||||||
type: string
|
config:
|
||||||
id:
|
type: string
|
||||||
type: string
|
data:
|
||||||
name:
|
type: string
|
||||||
type: string
|
id:
|
||||||
parent:
|
type: string
|
||||||
type: string
|
name:
|
||||||
pose:
|
type: string
|
||||||
properties:
|
parent:
|
||||||
orientation:
|
type: string
|
||||||
properties:
|
pose:
|
||||||
w:
|
properties:
|
||||||
type: number
|
orientation:
|
||||||
x:
|
properties:
|
||||||
type: number
|
w:
|
||||||
y:
|
type: number
|
||||||
type: number
|
x:
|
||||||
z:
|
type: number
|
||||||
type: number
|
y:
|
||||||
required:
|
type: number
|
||||||
- x
|
z:
|
||||||
- y
|
type: number
|
||||||
- z
|
required:
|
||||||
- w
|
- x
|
||||||
title: orientation
|
- y
|
||||||
type: object
|
- z
|
||||||
position:
|
- w
|
||||||
properties:
|
title: orientation
|
||||||
x:
|
type: object
|
||||||
type: number
|
position:
|
||||||
y:
|
properties:
|
||||||
type: number
|
x:
|
||||||
z:
|
type: number
|
||||||
type: number
|
y:
|
||||||
required:
|
type: number
|
||||||
- x
|
z:
|
||||||
- y
|
type: number
|
||||||
- z
|
required:
|
||||||
title: position
|
- x
|
||||||
type: object
|
- y
|
||||||
required:
|
- z
|
||||||
- position
|
title: position
|
||||||
- orientation
|
type: object
|
||||||
title: pose
|
required:
|
||||||
type: object
|
- position
|
||||||
sample_id:
|
- orientation
|
||||||
type: string
|
title: pose
|
||||||
type:
|
type: object
|
||||||
type: string
|
sample_id:
|
||||||
required:
|
type: string
|
||||||
- id
|
type:
|
||||||
- name
|
type: string
|
||||||
- sample_id
|
required:
|
||||||
- children
|
- id
|
||||||
- parent
|
- name
|
||||||
- type
|
- sample_id
|
||||||
- category
|
- children
|
||||||
- pose
|
- parent
|
||||||
- config
|
- type
|
||||||
- data
|
- category
|
||||||
|
- pose
|
||||||
|
- config
|
||||||
|
- data
|
||||||
|
title: plate
|
||||||
|
type: object
|
||||||
title: plate
|
title: plate
|
||||||
type: object
|
type: array
|
||||||
volumes:
|
volumes:
|
||||||
items:
|
items:
|
||||||
type: number
|
type: number
|
||||||
@@ -9544,8 +9599,7 @@ liquid_handler.prcxi:
|
|||||||
title: Plate
|
title: Plate
|
||||||
type: array
|
type: array
|
||||||
volumes:
|
volumes:
|
||||||
items:
|
items: {}
|
||||||
type: number
|
|
||||||
title: Volumes
|
title: Volumes
|
||||||
type: array
|
type: array
|
||||||
wells:
|
wells:
|
||||||
@@ -9922,18 +9976,18 @@ liquid_handler.prcxi:
|
|||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: tip_rack_identifier
|
handler_key: tip_rack_identifier
|
||||||
label: 墙头盒
|
label: 枪头盒
|
||||||
output:
|
output:
|
||||||
- data_key: liquid
|
- data_key: sources.@flatten
|
||||||
data_source: handle
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: sources_out
|
handler_key: sources_out
|
||||||
label: sources
|
label: 移液后源孔
|
||||||
- data_key: liquid
|
- data_key: targets.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: targets_out
|
handler_key: targets_out
|
||||||
label: targets
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
|
|||||||
@@ -151,12 +151,40 @@ def canonicalize_links_ports(links: List[Dict[str, Any]], resource_tree_set: Res
|
|||||||
"""
|
"""
|
||||||
# 构建 id 到 uuid 的映射
|
# 构建 id 到 uuid 的映射
|
||||||
id_to_uuid: Dict[str, str] = {}
|
id_to_uuid: Dict[str, str] = {}
|
||||||
|
uuid_to_id: Dict[str, str] = {}
|
||||||
for node in resource_tree_set.all_nodes:
|
for node in resource_tree_set.all_nodes:
|
||||||
id_to_uuid[node.res_content.id] = node.res_content.uuid
|
id_to_uuid[node.res_content.id] = node.res_content.uuid
|
||||||
|
uuid_to_id[node.res_content.uuid] = node.res_content.id
|
||||||
|
|
||||||
|
# 第三遍处理:为每个 link 添加 source_uuid 和 target_uuid
|
||||||
|
for link in links:
|
||||||
|
source_id = link.get("source")
|
||||||
|
target_id = link.get("target")
|
||||||
|
|
||||||
|
# 添加 source_uuid
|
||||||
|
if source_id and source_id in id_to_uuid:
|
||||||
|
link["source_uuid"] = id_to_uuid[source_id]
|
||||||
|
|
||||||
|
# 添加 target_uuid
|
||||||
|
if target_id and target_id in id_to_uuid:
|
||||||
|
link["target_uuid"] = id_to_uuid[target_id]
|
||||||
|
|
||||||
|
source_uuid = link.get("source_uuid")
|
||||||
|
target_uuid = link.get("target_uuid")
|
||||||
|
|
||||||
|
# 添加 source_uuid
|
||||||
|
if source_uuid and source_uuid in uuid_to_id:
|
||||||
|
link["source"] = uuid_to_id[source_uuid]
|
||||||
|
|
||||||
|
# 添加 target_uuid
|
||||||
|
if target_uuid and target_uuid in uuid_to_id:
|
||||||
|
link["target"] = uuid_to_id[target_uuid]
|
||||||
|
|
||||||
# 第一遍处理:将字符串类型的port转换为字典格式
|
# 第一遍处理:将字符串类型的port转换为字典格式
|
||||||
for link in links:
|
for link in links:
|
||||||
port = link.get("port")
|
port = link.get("port")
|
||||||
|
if port is None:
|
||||||
|
continue
|
||||||
if link.get("type", "physical") == "physical":
|
if link.get("type", "physical") == "physical":
|
||||||
link["type"] = "fluid"
|
link["type"] = "fluid"
|
||||||
if isinstance(port, int):
|
if isinstance(port, int):
|
||||||
@@ -179,13 +207,15 @@ def canonicalize_links_ports(links: List[Dict[str, Any]], resource_tree_set: Res
|
|||||||
link["port"] = {link["source"]: None, link["target"]: None}
|
link["port"] = {link["source"]: None, link["target"]: None}
|
||||||
|
|
||||||
# 构建边字典,键为(source节点, target节点),值为对应的port信息
|
# 构建边字典,键为(source节点, target节点),值为对应的port信息
|
||||||
edges = {(link["source"], link["target"]): link["port"] for link in links}
|
edges = {(link["source"], link["target"]): link["port"] for link in links if link.get("port")}
|
||||||
|
|
||||||
# 第二遍处理:填充反向边的dest信息
|
# 第二遍处理:填充反向边的dest信息
|
||||||
delete_reverses = []
|
delete_reverses = []
|
||||||
for i, link in enumerate(links):
|
for i, link in enumerate(links):
|
||||||
s, t = link["source"], link["target"]
|
s, t = link["source"], link["target"]
|
||||||
current_port = link["port"]
|
current_port = link.get("port")
|
||||||
|
if current_port is None:
|
||||||
|
continue
|
||||||
if current_port.get(t) is None:
|
if current_port.get(t) is None:
|
||||||
reverse_key = (t, s)
|
reverse_key = (t, s)
|
||||||
reverse_port = edges.get(reverse_key)
|
reverse_port = edges.get(reverse_key)
|
||||||
@@ -200,20 +230,6 @@ def canonicalize_links_ports(links: List[Dict[str, Any]], resource_tree_set: Res
|
|||||||
current_port[t] = current_port[s]
|
current_port[t] = current_port[s]
|
||||||
# 删除已被使用反向端口信息的反向边
|
# 删除已被使用反向端口信息的反向边
|
||||||
standardized_links = [link for i, link in enumerate(links) if i not in delete_reverses]
|
standardized_links = [link for i, link in enumerate(links) if i not in delete_reverses]
|
||||||
|
|
||||||
# 第三遍处理:为每个 link 添加 source_uuid 和 target_uuid
|
|
||||||
for link in standardized_links:
|
|
||||||
source_id = link.get("source")
|
|
||||||
target_id = link.get("target")
|
|
||||||
|
|
||||||
# 添加 source_uuid
|
|
||||||
if source_id and source_id in id_to_uuid:
|
|
||||||
link["source_uuid"] = id_to_uuid[source_id]
|
|
||||||
|
|
||||||
# 添加 target_uuid
|
|
||||||
if target_id and target_id in id_to_uuid:
|
|
||||||
link["target_uuid"] = id_to_uuid[target_id]
|
|
||||||
|
|
||||||
return standardized_links
|
return standardized_links
|
||||||
|
|
||||||
|
|
||||||
@@ -284,6 +300,8 @@ def modify_to_backend_format(data: list[dict[str, Any]]) -> list[dict[str, Any]]
|
|||||||
edge["sourceHandle"] = port[source]
|
edge["sourceHandle"] = port[source]
|
||||||
elif "source_port" in edge:
|
elif "source_port" in edge:
|
||||||
edge["sourceHandle"] = edge.pop("source_port")
|
edge["sourceHandle"] = edge.pop("source_port")
|
||||||
|
elif "source_handle" in edge:
|
||||||
|
edge["sourceHandle"] = edge.pop("source_handle")
|
||||||
else:
|
else:
|
||||||
typ = edge.get("type")
|
typ = edge.get("type")
|
||||||
if typ == "communication":
|
if typ == "communication":
|
||||||
@@ -292,6 +310,8 @@ def modify_to_backend_format(data: list[dict[str, Any]]) -> list[dict[str, Any]]
|
|||||||
edge["targetHandle"] = port[target]
|
edge["targetHandle"] = port[target]
|
||||||
elif "target_port" in edge:
|
elif "target_port" in edge:
|
||||||
edge["targetHandle"] = edge.pop("target_port")
|
edge["targetHandle"] = edge.pop("target_port")
|
||||||
|
elif "target_handle" in edge:
|
||||||
|
edge["targetHandle"] = edge.pop("target_handle")
|
||||||
else:
|
else:
|
||||||
typ = edge.get("type")
|
typ = edge.get("type")
|
||||||
if typ == "communication":
|
if typ == "communication":
|
||||||
|
|||||||
@@ -1581,7 +1581,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||||
)
|
)
|
||||||
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
||||||
|
# todo: 默认反报送
|
||||||
return function(**function_args)
|
return function(**function_args)
|
||||||
except KeyError as ex:
|
except KeyError as ex:
|
||||||
raise JsonCommandInitError(
|
raise JsonCommandInitError(
|
||||||
@@ -1614,8 +1614,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
timeout = 30.0
|
timeout = 30.0
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
while not future.done() and elapsed < timeout:
|
while not future.done() and elapsed < timeout:
|
||||||
time.sleep(0.05)
|
time.sleep(0.02)
|
||||||
elapsed += 0.05
|
elapsed += 0.02
|
||||||
|
|
||||||
if not future.done():
|
if not future.done():
|
||||||
raise Exception(f"资源查询超时: {uuids_list}")
|
raise Exception(f"资源查询超时: {uuids_list}")
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
assign_sample_id(action_kwargs)
|
assign_sample_id(action_kwargs)
|
||||||
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
||||||
|
|
||||||
self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
# self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
||||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}")
|
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}")
|
||||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
||||||
action_client.wait_for_server()
|
action_client.wait_for_server()
|
||||||
@@ -1180,7 +1180,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
"""
|
"""
|
||||||
更新节点信息回调
|
更新节点信息回调
|
||||||
"""
|
"""
|
||||||
# self.lab_logger().info(f"[Host Node] Node info update request received: {request}")
|
self.lab_logger().trace(f"[Host Node] Node info update request received: {request}")
|
||||||
try:
|
try:
|
||||||
from unilabos.app.communication import get_communication_client
|
from unilabos.app.communication import get_communication_client
|
||||||
from unilabos.app.web.client import HTTPClient, http_client
|
from unilabos.app.web.client import HTTPClient, http_client
|
||||||
|
|||||||
795
unilabos/test/experiments/prcxi_9320_slim.json
Normal file
795
unilabos/test/experiments/prcxi_9320_slim.json
Normal file
@@ -0,0 +1,795 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "PRCXI",
|
||||||
|
"name": "PRCXI",
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler.prcxi",
|
||||||
|
"parent": "",
|
||||||
|
"pose": {
|
||||||
|
"size": {
|
||||||
|
"width": 562,
|
||||||
|
"height": 394,
|
||||||
|
"depth": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"axis": "Left",
|
||||||
|
"deck": {
|
||||||
|
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck",
|
||||||
|
"_resource_child_name": "PRCXI_Deck"
|
||||||
|
},
|
||||||
|
"host": "10.20.30.184",
|
||||||
|
"port": 9999,
|
||||||
|
"debug": true,
|
||||||
|
"setup": true,
|
||||||
|
"is_9320": true,
|
||||||
|
"timeout": 10,
|
||||||
|
"matrix_id": "5de524d0-3f95-406c-86dd-f83626ebc7cb",
|
||||||
|
"simulator": true,
|
||||||
|
"channel_num": 2
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"reset_ok": true
|
||||||
|
},
|
||||||
|
"schema": {},
|
||||||
|
"description": "",
|
||||||
|
"model": null,
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 240,
|
||||||
|
"z": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PRCXI_Deck",
|
||||||
|
"name": "PRCXI_Deck",
|
||||||
|
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI",
|
||||||
|
"type": "deck",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 10,
|
||||||
|
"y": 10,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Deck",
|
||||||
|
"size_x": 542,
|
||||||
|
"size_y": 374,
|
||||||
|
"size_z": 0,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "deck",
|
||||||
|
"barcode": null
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T1",
|
||||||
|
"name": "T1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T1",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T2",
|
||||||
|
"name": "T2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T2",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T3",
|
||||||
|
"name": "T3",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T3",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T4",
|
||||||
|
"name": "T4",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T4",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T5",
|
||||||
|
"name": "T5",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T5",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T6",
|
||||||
|
"name": "T6",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T6",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T7",
|
||||||
|
"name": "T7",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T7",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T8",
|
||||||
|
"name": "T8",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T8",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T9",
|
||||||
|
"name": "T9",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T9",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T10",
|
||||||
|
"name": "T10",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T10",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T11",
|
||||||
|
"name": "T11",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T11",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T12",
|
||||||
|
"name": "T12",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T12",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T13",
|
||||||
|
"name": "T13",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T13",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T14",
|
||||||
|
"name": "T14",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T14",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T15",
|
||||||
|
"name": "T15",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T15",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T16",
|
||||||
|
"name": "T16",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T16",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": []
|
||||||
|
}
|
||||||
@@ -19,7 +19,9 @@
|
|||||||
|
|
||||||
第一步: 按 slot 去重创建 create_resource 节点(创建板子)
|
第一步: 按 slot 去重创建 create_resource 节点(创建板子)
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
- 首先创建一个 Group 节点(type="Group", minimized=true),用于包含所有 create_resource 节点
|
||||||
- 遍历所有 reagent,按 slot 去重,为每个唯一的 slot 创建一个板子
|
- 遍历所有 reagent,按 slot 去重,为每个唯一的 slot 创建一个板子
|
||||||
|
- 所有 create_resource 节点的 parent_uuid 指向 Group 节点,minimized=true
|
||||||
- 生成参数:
|
- 生成参数:
|
||||||
res_id: plate_slot_{slot}
|
res_id: plate_slot_{slot}
|
||||||
device_id: /PRCXI
|
device_id: /PRCXI
|
||||||
@@ -29,11 +31,13 @@
|
|||||||
- 输出端口: labware(用于连接 set_liquid_from_plate)
|
- 输出端口: labware(用于连接 set_liquid_from_plate)
|
||||||
- 控制流: create_resource 之间通过 ready 端口串联
|
- 控制流: create_resource 之间通过 ready 端口串联
|
||||||
|
|
||||||
示例: slot=1, slot=4 -> 创建 2 个 create_resource 节点
|
示例: slot=1, slot=4 -> 创建 1 个 Group + 2 个 create_resource 节点
|
||||||
|
|
||||||
第二步: 为每个 reagent 创建 set_liquid_from_plate 节点(设置液体)
|
第二步: 为每个 reagent 创建 set_liquid_from_plate 节点(设置液体)
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
- 首先创建一个 Group 节点(type="Group", minimized=true),用于包含所有 set_liquid_from_plate 节点
|
||||||
- 遍历所有 reagent,为每个试剂创建 set_liquid_from_plate 节点
|
- 遍历所有 reagent,为每个试剂创建 set_liquid_from_plate 节点
|
||||||
|
- 所有 set_liquid_from_plate 节点的 parent_uuid 指向 Group 节点,minimized=true
|
||||||
- 生成参数:
|
- 生成参数:
|
||||||
plate: [](通过连接传递,来自 create_resource 的 labware)
|
plate: [](通过连接传递,来自 create_resource 的 labware)
|
||||||
well_names: ["A1", "A3", "A5"](来自 reagent 的 well 数组)
|
well_names: ["A1", "A3", "A5"](来自 reagent 的 well 数组)
|
||||||
@@ -76,6 +80,13 @@ transfer_liquid:
|
|||||||
输入: sources -> sources_identifier, targets -> targets_identifier
|
输入: sources -> sources_identifier, targets -> targets_identifier
|
||||||
输出: sources -> sources_out, targets -> targets_out
|
输出: sources -> sources_out, targets -> targets_out
|
||||||
|
|
||||||
|
==================== 设备名配置 (device_name) ====================
|
||||||
|
|
||||||
|
每个节点都有 device_name 字段,指定在哪个设备上执行:
|
||||||
|
- create_resource: device_name = "host_node"(固定)
|
||||||
|
- set_liquid_from_plate: device_name = "PRCXI"(可配置,见 DEVICE_NAME_DEFAULT)
|
||||||
|
- transfer_liquid 等动作: device_name = "PRCXI"(可配置,见 DEVICE_NAME_DEFAULT)
|
||||||
|
|
||||||
==================== 校验规则 ====================
|
==================== 校验规则 ====================
|
||||||
|
|
||||||
- 检查 sources/targets 是否在 reagent 中定义
|
- 检查 sources/targets 是否在 reagent 中定义
|
||||||
@@ -97,6 +108,13 @@ Json = Dict[str, Any]
|
|||||||
|
|
||||||
# ==================== 默认配置 ====================
|
# ==================== 默认配置 ====================
|
||||||
|
|
||||||
|
# 设备名配置
|
||||||
|
DEVICE_NAME_HOST = "host_node" # create_resource 固定在 host_node 上执行
|
||||||
|
DEVICE_NAME_DEFAULT = "PRCXI" # transfer_liquid, set_liquid_from_plate 等动作的默认设备名
|
||||||
|
|
||||||
|
# 节点类型
|
||||||
|
NODE_TYPE_DEFAULT = "ILab" # 所有节点的默认类型
|
||||||
|
|
||||||
# create_resource 节点默认参数
|
# create_resource 节点默认参数
|
||||||
CREATE_RESOURCE_DEFAULTS = {
|
CREATE_RESOURCE_DEFAULTS = {
|
||||||
"device_id": "/PRCXI",
|
"device_id": "/PRCXI",
|
||||||
@@ -367,6 +385,21 @@ def build_protocol_graph(
|
|||||||
"res_id": res_id,
|
"res_id": res_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 创建 Group 节点,包含所有 create_resource 节点
|
||||||
|
group_node_id = str(uuid.uuid4())
|
||||||
|
G.add_node(
|
||||||
|
group_node_id,
|
||||||
|
name="Resources Group",
|
||||||
|
type="Group",
|
||||||
|
parent_uuid="",
|
||||||
|
lab_node_type="Device",
|
||||||
|
template_name="",
|
||||||
|
resource_name="",
|
||||||
|
footer="",
|
||||||
|
minimized=True,
|
||||||
|
param=None,
|
||||||
|
)
|
||||||
|
|
||||||
# 为每个唯一的 slot 创建 create_resource 节点
|
# 为每个唯一的 slot 创建 create_resource 节点
|
||||||
res_index = 0
|
res_index = 0
|
||||||
last_create_resource_id = None
|
last_create_resource_id = None
|
||||||
@@ -383,6 +416,10 @@ def build_protocol_graph(
|
|||||||
description=f"Create plate on slot {slot}",
|
description=f"Create plate on slot {slot}",
|
||||||
lab_node_type="Labware",
|
lab_node_type="Labware",
|
||||||
footer="create_resource-host_node",
|
footer="create_resource-host_node",
|
||||||
|
device_name=DEVICE_NAME_HOST,
|
||||||
|
type=NODE_TYPE_DEFAULT,
|
||||||
|
parent_uuid=group_node_id, # 指向 Group 节点
|
||||||
|
minimized=True, # 折叠显示
|
||||||
param={
|
param={
|
||||||
"res_id": res_id,
|
"res_id": res_id,
|
||||||
"device_id": CREATE_RESOURCE_DEFAULTS["device_id"],
|
"device_id": CREATE_RESOURCE_DEFAULTS["device_id"],
|
||||||
@@ -400,6 +437,21 @@ def build_protocol_graph(
|
|||||||
last_create_resource_id = node_id
|
last_create_resource_id = node_id
|
||||||
|
|
||||||
# ==================== 第二步:为每个 reagent 创建 set_liquid_from_plate 节点 ====================
|
# ==================== 第二步:为每个 reagent 创建 set_liquid_from_plate 节点 ====================
|
||||||
|
# 创建 Group 节点,包含所有 set_liquid_from_plate 节点
|
||||||
|
set_liquid_group_id = str(uuid.uuid4())
|
||||||
|
G.add_node(
|
||||||
|
set_liquid_group_id,
|
||||||
|
name="SetLiquid Group",
|
||||||
|
type="Group",
|
||||||
|
parent_uuid="",
|
||||||
|
lab_node_type="Device",
|
||||||
|
template_name="",
|
||||||
|
resource_name="",
|
||||||
|
footer="",
|
||||||
|
minimized=True,
|
||||||
|
param=None,
|
||||||
|
)
|
||||||
|
|
||||||
set_liquid_index = 0
|
set_liquid_index = 0
|
||||||
last_set_liquid_id = last_create_resource_id # set_liquid_from_plate 连接在 create_resource 之后
|
last_set_liquid_id = last_create_resource_id # set_liquid_from_plate 连接在 create_resource 之后
|
||||||
|
|
||||||
@@ -430,6 +482,10 @@ def build_protocol_graph(
|
|||||||
description=f"Set liquid: {labware_id}",
|
description=f"Set liquid: {labware_id}",
|
||||||
lab_node_type="Reagent",
|
lab_node_type="Reagent",
|
||||||
footer="set_liquid_from_plate-liquid_handler.prcxi",
|
footer="set_liquid_from_plate-liquid_handler.prcxi",
|
||||||
|
device_name=DEVICE_NAME_DEFAULT,
|
||||||
|
type=NODE_TYPE_DEFAULT,
|
||||||
|
parent_uuid=set_liquid_group_id, # 指向 Group 节点
|
||||||
|
minimized=True, # 折叠显示
|
||||||
param={
|
param={
|
||||||
"plate": [], # 通过连接传递
|
"plate": [], # 通过连接传递
|
||||||
"well_names": wells, # 孔位名数组,如 ["A1", "A3", "A5"]
|
"well_names": wells, # 孔位名数组,如 ["A1", "A3", "A5"]
|
||||||
@@ -544,9 +600,11 @@ def build_protocol_graph(
|
|||||||
if param_key in params:
|
if param_key in params:
|
||||||
params[param_key] = []
|
params[param_key] = []
|
||||||
|
|
||||||
# 更新 step 的 param 和 footer
|
# 更新 step 的 param、footer、device_name 和 type
|
||||||
step_copy = step.copy()
|
step_copy = step.copy()
|
||||||
step_copy["param"] = params
|
step_copy["param"] = params
|
||||||
|
step_copy["device_name"] = DEVICE_NAME_DEFAULT # 动作节点使用默认设备名
|
||||||
|
step_copy["type"] = NODE_TYPE_DEFAULT # 节点类型
|
||||||
|
|
||||||
# 如果有警告,修改 footer 添加警告标记(警告放前面)
|
# 如果有警告,修改 footer 添加警告标记(警告放前面)
|
||||||
if warnings:
|
if warnings:
|
||||||
|
|||||||
Reference in New Issue
Block a user