Compare commits

..

8 Commits

Author SHA1 Message Date
Xuwznln
dfc635189c fix comprehensive_station.json 2025-10-16 13:52:07 +08:00
Xuwznln
d8f3ebac15 fix comprehensive_station.json 2025-10-16 13:46:59 +08:00
Xuwznln
4a1e703a3a support no size init 2025-10-16 13:35:59 +08:00
Xuwznln
55d22a7c29 Update regular container method 2025-10-16 13:33:28 +08:00
Xuwznln
03a4e4ecba fix to plr type error 2025-10-16 13:19:59 +08:00
Xuwznln
2316c34cb5 fix to plr type error 2025-10-16 13:12:21 +08:00
Xuwznln
a8887161d3 pack repo info 2025-10-16 13:06:13 +08:00
Xuwznln
25834f5ba0 provide error info when cant find plr type 2025-10-16 13:05:44 +08:00
6 changed files with 164 additions and 77 deletions

View File

@@ -242,6 +242,10 @@ jobs:
echo Adding: verify_installation.py echo Adding: verify_installation.py
copy scripts\verify_installation.py dist-package\ copy scripts\verify_installation.py dist-package\
rem Copy source code repository (including .git)
echo Adding: Uni-Lab-OS source repository
robocopy . dist-package\Uni-Lab-OS /E /XD dist-package /NFL /NDL /NJH /NJS /NC /NS || if %ERRORLEVEL% LSS 8 exit /b 0
rem Create README using Python script rem Create README using Python script
echo Creating: README.txt echo Creating: README.txt
python scripts\create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package\README.txt python scripts\create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package\README.txt
@@ -274,6 +278,10 @@ jobs:
echo "Adding: verify_installation.py" echo "Adding: verify_installation.py"
cp scripts/verify_installation.py dist-package/ cp scripts/verify_installation.py dist-package/
# Copy source code repository (including .git)
echo "Adding: Uni-Lab-OS source repository"
rsync -a --exclude='dist-package' . dist-package/Uni-Lab-OS
# Create README using Python script # Create README using Python script
echo "Creating: README.txt" echo "Creating: README.txt"
python scripts/create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package/README.txt python scripts/create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package/README.txt

View File

@@ -170,7 +170,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0 "max_volume": 1000.0,
"type": "RegularContainer",
"category": "container",
"size_x": 200,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -194,7 +199,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0 "max_volume": 1000.0,
"type": "RegularContainer",
"category": "container",
"size_x": 200,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -218,7 +228,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0 "max_volume": 1000.0,
"type": "RegularContainer",
"category": "container",
"size_x": 300,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -242,7 +257,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0 "max_volume": 1000.0,
"type": "RegularContainer",
"category": "container",
"size_x": 900,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -266,7 +286,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0 "max_volume": 1000.0,
"type": "RegularContainer",
"category": "container",
"size_x": 950,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -335,6 +360,8 @@
}, },
"config": { "config": {
"max_volume": 500.0, "max_volume": 500.0,
"type": "RegularContainer",
"category": "container",
"max_temp": 200.0, "max_temp": 200.0,
"min_temp": -20.0, "min_temp": -20.0,
"has_stirrer": true, "has_stirrer": true,
@@ -419,7 +446,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 2000.0 "max_volume": 2000.0,
"type": "RegularContainer",
"category": "container",
"size_x": 500,
"size_y": 400,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -439,7 +471,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 2000.0 "max_volume": 2000.0,
"type": "RegularContainer",
"category": "container",
"size_x": 1100,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -649,7 +686,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 250.0 "max_volume": 250.0,
"type": "RegularContainer",
"category": "container",
"size_x": 900,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -669,7 +711,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 250.0 "max_volume": 250.0,
"type": "RegularContainer",
"category": "container",
"size_x": 950,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -689,7 +736,12 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 250.0 "max_volume": 250.0,
"type": "RegularContainer",
"category": "container",
"size_x": 1050,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [ "liquids": [
@@ -733,6 +785,11 @@
}, },
"config": { "config": {
"max_volume": 500.0, "max_volume": 500.0,
"size_x": 550,
"size_y": 250,
"size_z": 0,
"type": "RegularContainer",
"category": "container",
"reagent": "sodium_chloride", "reagent": "sodium_chloride",
"physical_state": "solid" "physical_state": "solid"
}, },
@@ -756,6 +813,11 @@
}, },
"config": { "config": {
"volume": 500.0, "volume": 500.0,
"size_x": 600,
"size_y": 250,
"size_z": 0,
"type": "RegularContainer",
"category": "container",
"reagent": "sodium_carbonate", "reagent": "sodium_carbonate",
"physical_state": "solid" "physical_state": "solid"
}, },
@@ -779,6 +841,11 @@
}, },
"config": { "config": {
"volume": 500.0, "volume": 500.0,
"size_x": 650,
"size_y": 250,
"size_z": 0,
"type": "RegularContainer",
"category": "container",
"reagent": "magnesium_chloride", "reagent": "magnesium_chloride",
"physical_state": "solid" "physical_state": "solid"
}, },

View File

@@ -3,7 +3,7 @@ container:
- container - container
class: class:
module: unilabos.resources.container:RegularContainer module: unilabos.resources.container:RegularContainer
type: unilabos type: pylabrobot
description: regular organic container description: regular organic container
handles: handles:
- data_key: fluid_in - data_key: fluid_in

View File

@@ -1,67 +1,81 @@
import json import json
from pylabrobot.resources import Container
from unilabos_msgs.msg import Resource from unilabos_msgs.msg import Resource
from unilabos.ros.msgs.message_converter import convert_from_ros_msg from unilabos.ros.msgs.message_converter import convert_from_ros_msg
class RegularContainer(object): class RegularContainer(Container):
# 第一个参数必须是id传入 def __init__(self, *args, **kwargs):
# noinspection PyShadowingBuiltins if "size_x" not in kwargs:
def __init__(self, id: str): kwargs["size_x"] = 0
self.id = id if "size_y" not in kwargs:
self.ulr_resource = Resource() kwargs["size_y"] = 0
self._data = None if "size_z" not in kwargs:
kwargs["size_z"] = 0
self.kwargs = kwargs
super().__init__(*args, **kwargs)
@property
def ulr_resource_data(self):
if self._data is None:
self._data = json.loads(self.ulr_resource.data) if self.ulr_resource.data else {}
return self._data
@ulr_resource_data.setter #
def ulr_resource_data(self, value: dict): # class RegularContainer(object):
self._data = value # # 第一个参数必须是id传入
self.ulr_resource.data = json.dumps(self._data) # # noinspection PyShadowingBuiltins
# def __init__(self, id: str):
@property # self.id = id
def liquid_type(self): # self.ulr_resource = Resource()
return self.ulr_resource_data.get("liquid_type", None) # self._data = None
#
@liquid_type.setter # @property
def liquid_type(self, value: str): # def ulr_resource_data(self):
if value is not None: # if self._data is None:
self.ulr_resource_data["liquid_type"] = value # self._data = json.loads(self.ulr_resource.data) if self.ulr_resource.data else {}
else: # return self._data
self.ulr_resource_data.pop("liquid_type", None) #
# @ulr_resource_data.setter
@property # def ulr_resource_data(self, value: dict):
def liquid_volume(self): # self._data = value
return self.ulr_resource_data.get("liquid_volume", None) # self.ulr_resource.data = json.dumps(self._data)
#
@liquid_volume.setter # @property
def liquid_volume(self, value: float): # def liquid_type(self):
if value is not None: # return self.ulr_resource_data.get("liquid_type", None)
self.ulr_resource_data["liquid_volume"] = value #
else: # @liquid_type.setter
self.ulr_resource_data.pop("liquid_volume", None) # def liquid_type(self, value: str):
# if value is not None:
def get_ulr_resource(self) -> Resource: # self.ulr_resource_data["liquid_type"] = value
""" # else:
获取UlrResource对象 # self.ulr_resource_data.pop("liquid_type", None)
:return: UlrResource对象 #
""" # @property
self.ulr_resource_data = self.ulr_resource_data # 确保数据被更新 # def liquid_volume(self):
return self.ulr_resource # return self.ulr_resource_data.get("liquid_volume", None)
#
def get_ulr_resource_as_dict(self) -> Resource: # @liquid_volume.setter
""" # def liquid_volume(self, value: float):
获取UlrResource对象 # if value is not None:
:return: UlrResource对象 # self.ulr_resource_data["liquid_volume"] = value
""" # else:
to_dict = convert_from_ros_msg(self.get_ulr_resource()) # self.ulr_resource_data.pop("liquid_volume", None)
to_dict["type"] = "container" #
return to_dict # def get_ulr_resource(self) -> Resource:
# """
def __str__(self): # 获取UlrResource对象
return f"{self.id}" # :return: UlrResource对象
# """
# self.ulr_resource_data = self.ulr_resource_data # 确保数据被更新
# return self.ulr_resource
#
# def get_ulr_resource_as_dict(self) -> Resource:
# """
# 获取UlrResource对象
# :return: UlrResource对象
# """
# to_dict = convert_from_ros_msg(self.get_ulr_resource())
# to_dict["type"] = "container"
# return to_dict
#
# def __str__(self):
# return f"{self.id}"

View File

@@ -781,6 +781,7 @@ def initialize_resource(resource_config: dict, resource_type: Any = None) -> Uni
else: else:
r = resource_plr r = resource_plr
elif resource_class_config["type"] == "unilabos": elif resource_class_config["type"] == "unilabos":
raise ValueError(f"No more support for unilabos Resource class {resource_class_config}")
res_instance: RegularContainer = RESOURCE(id=resource_config["name"]) res_instance: RegularContainer = RESOURCE(id=resource_config["name"])
res_instance.ulr_resource = convert_to_ros_msg( res_instance.ulr_resource = convert_to_ros_msg(
Resource, {k: v for k, v in resource_config.items() if k != "class"} Resource, {k: v for k, v in resource_config.items() if k != "class"}

View File

@@ -306,10 +306,7 @@ class ResourceTreeSet(object):
replace_info = { replace_info = {
"plate": "plate", "plate": "plate",
"well": "well", "well": "well",
"tip_spot": "container",
"trash": "container",
"deck": "deck", "deck": "deck",
"tip_rack": "container",
} }
if source in replace_info: if source in replace_info:
return replace_info[source] return replace_info[source]
@@ -388,7 +385,7 @@ class ResourceTreeSet(object):
import inspect import inspect
# 类型映射 # 类型映射
TYPE_MAP = {"plate": "plate", "well": "well", "container": "tip_spot", "deck": "deck", "tip_rack": "tip_rack"} TYPE_MAP = {"plate": "Plate", "well": "Well", "deck": "Deck"}
def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict): def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict):
"""一次遍历收集 name_to_uuid 和 all_states""" """一次遍历收集 name_to_uuid 和 all_states"""
@@ -400,13 +397,13 @@ class ResourceTreeSet(object):
def node_to_plr_dict(node: ResourceDictInstance, has_model: bool): def node_to_plr_dict(node: ResourceDictInstance, has_model: bool):
"""转换节点为 PLR 字典格式""" """转换节点为 PLR 字典格式"""
res = node.res_content res = node.res_content
plr_type = TYPE_MAP.get(res.type, "tip_spot") plr_type = TYPE_MAP.get(res.type, res.type)
if res.type not in TYPE_MAP: if res.type not in TYPE_MAP:
logger.warning(f"未知类型 {res.type},使用默认类型 tip_spot") logger.warning(f"未知类型 {res.type},使用默认类型 tip_spot")
d = { d = {
"name": res.name, "name": res.name,
"type": plr_type, "type": res.type,
"size_x": res.config.get("size_x", 0), "size_x": res.config.get("size_x", 0),
"size_y": res.config.get("size_y", 0), "size_y": res.config.get("size_y", 0),
"size_z": res.config.get("size_z", 0), "size_z": res.config.get("size_z", 0),
@@ -417,7 +414,7 @@ class ResourceTreeSet(object):
"type": "Coordinate", "type": "Coordinate",
}, },
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"}, "rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
"category": plr_type, "category": res.config.get("category", plr_type),
"children": [node_to_plr_dict(child, has_model) for child in node.children], "children": [node_to_plr_dict(child, has_model) for child in node.children],
"parent_name": res.parent_instance_name, "parent_name": res.parent_instance_name,
**res.config, **res.config,
@@ -439,7 +436,7 @@ class ResourceTreeSet(object):
try: try:
sub_cls = find_subclass(plr_dict["type"], PLRResource) sub_cls = find_subclass(plr_dict["type"], PLRResource)
if sub_cls is None: if sub_cls is None:
raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类") raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类。原始信息:{tree.root_node.res_content}")
spec = inspect.signature(sub_cls) spec = inspect.signature(sub_cls)
if "category" not in spec.parameters: if "category" not in spec.parameters:
plr_dict.pop("category", None) plr_dict.pop("category", None)