diff --git a/.gitignore b/.gitignore
index e2c20639..f915811b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -232,4 +232,5 @@ CATKIN_IGNORE
/**/local_config.py
-*.graphml
\ No newline at end of file
+*.graphml
+unilabos/device_mesh/view_robot.rviz
diff --git a/README.md b/README.md
index 9d63eb84..34352760 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
# Currently, you need to install the `unilabos_msgs` package
# You can download the system-specific package from the Release page
-conda install ros-humble-unilabos-msgs-0.9.1-xxxxx.tar.bz2
+conda install ros-humble-unilabos-msgs-0.9.4-xxxxx.tar.bz2
# Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
diff --git a/README_zh.md b/README_zh.md
index 28671ebd..4f3607d7 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装
-conda install ros-humble-unilabos-msgs-0.9.1-xxxxx.tar.bz2
+conda install ros-humble-unilabos-msgs-0.9.4-xxxxx.tar.bz2
# 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
diff --git a/recipes/ros-humble-unilabos-msgs/recipe.yaml b/recipes/ros-humble-unilabos-msgs/recipe.yaml
index 2ee7f1f8..febca425 100644
--- a/recipes/ros-humble-unilabos-msgs/recipe.yaml
+++ b/recipes/ros-humble-unilabos-msgs/recipe.yaml
@@ -1,6 +1,6 @@
package:
name: ros-humble-unilabos-msgs
- version: 0.9.1
+ version: 0.9.4
source:
path: ../../unilabos_msgs
folder: ros-humble-unilabos-msgs/src/work
diff --git a/recipes/unilabos/recipe.yaml b/recipes/unilabos/recipe.yaml
index 5b036306..51ddea1f 100644
--- a/recipes/unilabos/recipe.yaml
+++ b/recipes/unilabos/recipe.yaml
@@ -1,6 +1,6 @@
package:
name: unilabos
- version: "0.9.1"
+ version: "0.9.4"
source:
path: ../..
diff --git a/setup.cfg b/setup.cfg
index e152926a..5197889b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,4 +1,2 @@
-[develop]
-script_dir=$base/lib/unilabos
[install]
install_scripts=$base/lib/unilabos
diff --git a/setup.py b/setup.py
index 847098a5..038d820d 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup(
name=package_name,
- version='0.9.1',
+ version='0.9.4',
packages=find_packages(),
include_package_data=True,
install_requires=['setuptools'],
diff --git a/test/experiments/biomek.json b/test/experiments/biomek.json
new file mode 100644
index 00000000..604adccc
--- /dev/null
+++ b/test/experiments/biomek.json
@@ -0,0 +1,22 @@
+{
+ "nodes": [
+ {
+ "id": "BIOMEK",
+ "name": "BIOMEK",
+ "parent": null,
+ "type": "device",
+ "class": "liquid_handler.biomek",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ },
+ "data": {},
+ "children": [
+ ]
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_all.json b/test/experiments/mock_devices/mock_all.json
new file mode 100644
index 00000000..f263b471
--- /dev/null
+++ b/test/experiments/mock_devices/mock_all.json
@@ -0,0 +1,296 @@
+{
+ "nodes": [
+ {
+ "id": "MockChiller1",
+ "name": "模拟冷却器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_chiller",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "current_temperature": 25.0,
+ "target_temperature": 25.0,
+ "status": "Idle",
+ "is_cooling": false,
+ "is_heating": false,
+ "vessel": "",
+ "purpose": ""
+ }
+ },
+ {
+ "id": "MockFilter1",
+ "name": "模拟过滤器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_filter",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "is_filtering": false,
+ "flow_rate": 0.0,
+ "filter_life": 100.0,
+ "vessel": "",
+ "filtrate_vessel": "",
+ "filtered_volume": 0.0,
+ "target_volume": 0.0,
+ "progress": 0.0,
+ "stir": false,
+ "stir_speed": 0.0,
+ "temperature": 25.0,
+ "continue_heatchill": false
+ }
+ },
+ {
+ "id": "MockHeater1",
+ "name": "模拟加热器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_heater",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "current_temperature": 25.0,
+ "target_temperature": 25.0,
+ "status": "Idle",
+ "is_heating": false,
+ "heating_power": 0.0,
+ "max_temperature": 300.0,
+ "vessel": "Unknown",
+ "purpose": "Unknown",
+ "stir": false,
+ "stir_speed": 0.0
+ }
+ },
+ {
+ "id": "MockPump1",
+ "name": "模拟泵设备",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_pump",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "current_device": "MockPump1",
+ "pump_state": "Stopped",
+ "flow_rate": 0.0,
+ "target_flow_rate": 0.0,
+ "pressure": 0.0,
+ "total_volume": 0.0,
+ "max_flow_rate": 100.0,
+ "max_pressure": 10.0,
+ "from_vessel": "",
+ "to_vessel": "",
+ "transfer_volume": 0.0,
+ "amount": "",
+ "transfer_time": 0.0,
+ "is_viscous": false,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "is_solid": false,
+ "time_spent": 0.0,
+ "time_remaining": 0.0
+ }
+ },
+ {
+ "id": "MockRotavap1",
+ "name": "模拟旋转蒸发器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_rotavap",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "rotate_state": "Stopped",
+ "rotate_time": 0.0,
+ "rotate_speed": 0.0,
+ "pump_state": "Stopped",
+ "pump_time": 0.0,
+ "vacuum_level": 1013.25,
+ "temperature": 25.0,
+ "target_temperature": 25.0,
+ "success": "True"
+ }
+ },
+ {
+ "id": "MockSeparator1",
+ "name": "模拟分离器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_separator",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "settling_time": 0.0,
+ "valve_state": "Closed",
+ "shake_time": 0.0,
+ "shake_status": "Not Shaking",
+ "current_device": "MockSeparator1",
+ "purpose": "",
+ "product_phase": "",
+ "from_vessel": "",
+ "separation_vessel": "",
+ "to_vessel": "",
+ "waste_phase_to_vessel": "",
+ "solvent": "",
+ "solvent_volume": 0.0,
+ "through": "",
+ "repeats": 1,
+ "stir_time": 0.0,
+ "stir_speed": 0.0,
+ "time_spent": 0.0,
+ "time_remaining": 0.0
+ }
+ },
+ {
+ "id": "MockSolenoidValve1",
+ "name": "模拟电磁阀",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_solenoid_valve",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "valve_status": "Closed"
+ }
+ },
+ {
+ "id": "MockStirrer1NEW",
+ "name": "模拟搅拌器(new)",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_stirrer_new",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "vessel": "",
+ "purpose": "",
+ "stir_speed": 0.0,
+ "target_stir_speed": 0.0,
+ "stir_state": "Stopped",
+ "stir_time": 0.0,
+ "settling_time": 0.0,
+ "progress": 0.0,
+ "max_stir_speed": 2000.0
+ }
+ },
+ {
+ "id": "MockStirrer1",
+ "name": "模拟搅拌器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_stirrer",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "stir_speed": 0.0,
+ "target_stir_speed": 0.0,
+ "stir_state": "Stopped",
+ "temperature": 25.0,
+ "target_temperature": 25.0,
+ "heating_state": "Off",
+ "heating_power": 0.0,
+ "max_stir_speed": 2000.0,
+ "max_temperature": 300.0
+ }
+ },
+ {
+ "id": "MockVacuum1",
+ "name": "模拟真空泵",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_vacuum",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "power_state": "Off",
+ "pump_state": "Stopped",
+ "vacuum_level": 1013.25,
+ "target_vacuum": 50.0,
+ "pump_speed": 0.0,
+ "pump_efficiency": 95.0,
+ "max_pump_speed": 100.0
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_chiller.json b/test/experiments/mock_devices/mock_chiller.json
new file mode 100644
index 00000000..fc0e2f4c
--- /dev/null
+++ b/test/experiments/mock_devices/mock_chiller.json
@@ -0,0 +1,30 @@
+{
+ "nodes": [
+ {
+ "id": "MockChiller1",
+ "name": "模拟冷却器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_chiller",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "current_temperature": 25.0,
+ "target_temperature": 25.0,
+ "status": "Idle",
+ "is_cooling": false,
+ "is_heating": false,
+ "vessel": "",
+ "purpose": ""
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_filter.json b/test/experiments/mock_devices/mock_filter.json
new file mode 100644
index 00000000..d8d80299
--- /dev/null
+++ b/test/experiments/mock_devices/mock_filter.json
@@ -0,0 +1,36 @@
+{
+ "nodes": [
+ {
+ "id": "MockFilter1",
+ "name": "模拟过滤器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_filter",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "is_filtering": false,
+ "flow_rate": 0.0,
+ "filter_life": 100.0,
+ "vessel": "",
+ "filtrate_vessel": "",
+ "filtered_volume": 0.0,
+ "target_volume": 0.0,
+ "progress": 0.0,
+ "stir": false,
+ "stir_speed": 0.0,
+ "temperature": 25.0,
+ "continue_heatchill": false
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_heater.json b/test/experiments/mock_devices/mock_heater.json
new file mode 100644
index 00000000..1aca9688
--- /dev/null
+++ b/test/experiments/mock_devices/mock_heater.json
@@ -0,0 +1,33 @@
+{
+ "nodes": [
+ {
+ "id": "MockHeater1",
+ "name": "模拟加热器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_heater",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "current_temperature": 25.0,
+ "target_temperature": 25.0,
+ "status": "Idle",
+ "is_heating": false,
+ "heating_power": 0.0,
+ "max_temperature": 300.0,
+ "vessel": "Unknown",
+ "purpose": "Unknown",
+ "stir": false,
+ "stir_speed": 0.0
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_pump.json b/test/experiments/mock_devices/mock_pump.json
new file mode 100644
index 00000000..d1c13945
--- /dev/null
+++ b/test/experiments/mock_devices/mock_pump.json
@@ -0,0 +1,44 @@
+{
+ "nodes": [
+ {
+ "id": "MockPump1",
+ "name": "模拟泵设备",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_pump",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "current_device": "MockPump1",
+ "pump_state": "Stopped",
+ "flow_rate": 0.0,
+ "target_flow_rate": 0.0,
+ "pressure": 0.0,
+ "total_volume": 0.0,
+ "max_flow_rate": 100.0,
+ "max_pressure": 10.0,
+ "from_vessel": "",
+ "to_vessel": "",
+ "transfer_volume": 0.0,
+ "amount": "",
+ "transfer_time": 0.0,
+ "is_viscous": false,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "is_solid": false,
+ "time_spent": 0.0,
+ "time_remaining": 0.0
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_rotavap.json b/test/experiments/mock_devices/mock_rotavap.json
new file mode 100644
index 00000000..b28cfe2a
--- /dev/null
+++ b/test/experiments/mock_devices/mock_rotavap.json
@@ -0,0 +1,33 @@
+{
+ "nodes": [
+ {
+ "id": "MockRotavap1",
+ "name": "模拟旋转蒸发器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_rotavap",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "rotate_state": "Stopped",
+ "rotate_time": 0.0,
+ "rotate_speed": 0.0,
+ "pump_state": "Stopped",
+ "pump_time": 0.0,
+ "vacuum_level": 1013.25,
+ "temperature": 25.0,
+ "target_temperature": 25.0,
+ "success": "True"
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_separator.json b/test/experiments/mock_devices/mock_separator.json
new file mode 100644
index 00000000..20f26718
--- /dev/null
+++ b/test/experiments/mock_devices/mock_separator.json
@@ -0,0 +1,43 @@
+{
+ "nodes": [
+ {
+ "id": "MockSeparator1",
+ "name": "模拟分离器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_separator",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "settling_time": 0.0,
+ "valve_state": "Closed",
+ "shake_time": 0.0,
+ "shake_status": "Not Shaking",
+ "current_device": "MockSeparator1",
+ "purpose": "",
+ "product_phase": "",
+ "from_vessel": "",
+ "separation_vessel": "",
+ "to_vessel": "",
+ "waste_phase_to_vessel": "",
+ "solvent": "",
+ "solvent_volume": 0.0,
+ "through": "",
+ "repeats": 1,
+ "stir_time": 0.0,
+ "stir_speed": 0.0,
+ "time_spent": 0.0,
+ "time_remaining": 0.0
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_solenoid_valve.json b/test/experiments/mock_devices/mock_solenoid_valve.json
new file mode 100644
index 00000000..e4e23cc1
--- /dev/null
+++ b/test/experiments/mock_devices/mock_solenoid_valve.json
@@ -0,0 +1,25 @@
+{
+ "nodes": [
+ {
+ "id": "MockSolenoidValve1",
+ "name": "模拟电磁阀",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_solenoid_valve",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "valve_status": "Closed"
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_stirrer.json b/test/experiments/mock_devices/mock_stirrer.json
new file mode 100644
index 00000000..9dfc59c7
--- /dev/null
+++ b/test/experiments/mock_devices/mock_stirrer.json
@@ -0,0 +1,33 @@
+{
+ "nodes": [
+ {
+ "id": "MockStirrer1",
+ "name": "模拟搅拌器",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_stirrer",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "stir_speed": 0.0,
+ "target_stir_speed": 0.0,
+ "stir_state": "Stopped",
+ "temperature": 25.0,
+ "target_temperature": 25.0,
+ "heating_state": "Off",
+ "heating_power": 0.0,
+ "max_stir_speed": 2000.0,
+ "max_temperature": 300.0
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_stirrer_new.json b/test/experiments/mock_devices/mock_stirrer_new.json
new file mode 100644
index 00000000..837b2fec
--- /dev/null
+++ b/test/experiments/mock_devices/mock_stirrer_new.json
@@ -0,0 +1,33 @@
+{
+ "nodes": [
+ {
+ "id": "MockStirrer1COPY",
+ "name": "模拟搅拌器(Copy)",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_stirrer_new",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "vessel": "",
+ "purpose": "",
+ "stir_speed": 0.0,
+ "target_stir_speed": 0.0,
+ "stir_state": "Stopped",
+ "stir_time": 0.0,
+ "settling_time": 0.0,
+ "progress": 0.0,
+ "max_stir_speed": 2000.0
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_devices/mock_vacuum.json b/test/experiments/mock_devices/mock_vacuum.json
new file mode 100644
index 00000000..31406aea
--- /dev/null
+++ b/test/experiments/mock_devices/mock_vacuum.json
@@ -0,0 +1,31 @@
+{
+ "nodes": [
+ {
+ "id": "MockVacuum1",
+ "name": "模拟真空泵",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "mock_vacuum",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "port": "MOCK"
+ },
+ "data": {
+ "status": "Idle",
+ "power_state": "Off",
+ "pump_state": "Stopped",
+ "vacuum_level": 1013.25,
+ "target_vacuum": 50.0,
+ "pump_speed": 0.0,
+ "pump_efficiency": 95.0,
+ "max_pump_speed": 100.0
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/addteststation.json b/test/experiments/mock_protocol/addteststation.json
new file mode 100644
index 00000000..dd16a1fe
--- /dev/null
+++ b/test/experiments/mock_protocol/addteststation.json
@@ -0,0 +1,250 @@
+{
+ "nodes": [
+ {
+ "id": "AddTestStation",
+ "name": "添加试剂测试工作站",
+ "children": [
+ "pump_add",
+ "flask_1",
+ "flask_2",
+ "flask_3",
+ "flask_4",
+ "reactor",
+ "stirrer",
+ "flask_air"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "pump_add",
+ "name": "pump_add",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "device",
+ "class": "virtual_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 25.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "stirrer",
+ "name": "stirrer",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "device",
+ "class": "virtual_stirrer",
+ "position": {
+ "x": 698.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "flask_1",
+ "name": "通用试剂瓶1",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_2",
+ "name": "通用试剂瓶2",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_3",
+ "name": "通用试剂瓶3",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_4",
+ "name": "通用试剂瓶4",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 550,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "reactor",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 5000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_air",
+ "name": "flask_air",
+ "children": [],
+ "parent": "AddTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 800,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "stirrer",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "stirrer": "top",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_1",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "flask_1": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_2",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_2": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_3",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_3": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_4",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_4": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_air",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_air": "top"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/centrifugeteststation.json b/test/experiments/mock_protocol/centrifugeteststation.json
new file mode 100644
index 00000000..e6918349
--- /dev/null
+++ b/test/experiments/mock_protocol/centrifugeteststation.json
@@ -0,0 +1,271 @@
+{
+ "nodes": [
+ {
+ "id": "CentrifugeTestStation",
+ "name": "离心机测试工作站",
+ "children": [
+ "pump_add",
+ "flask_1",
+ "flask_2",
+ "flask_3",
+ "reactor",
+ "stirrer",
+ "centrifuge_1",
+ "flask_air"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "CentrifugeProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "pump_add",
+ "name": "pump_add",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "device",
+ "class": "virtual_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 25.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "stirrer",
+ "name": "stirrer",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "device",
+ "class": "virtual_stirrer",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "centrifuge_1",
+ "name": "离心机",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "device",
+ "class": "virtual_centrifuge",
+ "position": {
+ "x": 800,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_speed": 15000.0,
+ "max_temp": 40.0,
+ "min_temp": 4.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "flask_1",
+ "name": "样品瓶1",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1500.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_2",
+ "name": "样品瓶2",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1500.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_3",
+ "name": "缓冲液瓶",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 5000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_air",
+ "name": "空气瓶",
+ "children": [],
+ "parent": "CentrifugeTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 950,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "stirrer",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "stirrer": "top",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_1",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "flask_1": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_2",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_2": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_3",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_3": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_air",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_air": "top"
+ }
+ },
+ {
+ "source": "centrifuge_1",
+ "target": "reactor",
+ "type": "logical",
+ "port": {
+ "centrifuge_1": "chamber",
+ "reactor": "vessel"
+ }
+ },
+ {
+ "source": "centrifuge_1",
+ "target": "flask_1",
+ "type": "logical",
+ "port": {
+ "centrifuge_1": "chamber",
+ "flask_1": "vessel"
+ }
+ },
+ {
+ "source": "centrifuge_1",
+ "target": "flask_2",
+ "type": "logical",
+ "port": {
+ "centrifuge_1": "chamber",
+ "flask_2": "vessel"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/cleanvesselteststation.json b/test/experiments/mock_protocol/cleanvesselteststation.json
new file mode 100644
index 00000000..c5d30868
--- /dev/null
+++ b/test/experiments/mock_protocol/cleanvesselteststation.json
@@ -0,0 +1,362 @@
+{
+ "nodes": [
+ {
+ "id": "CleanVesselTestStation",
+ "name": "容器清洗测试工作站",
+ "children": [
+ "transfer_pump_cleaner",
+ "heatchill_1",
+ "flask_water",
+ "flask_ethanol",
+ "flask_acetone",
+ "flask_waste",
+ "reactor",
+ "flask_buffer",
+ "flask_sample",
+ "flask_air"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["CleanVesselProtocol", "TransferProtocol", "AddProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "transfer_pump_cleaner",
+ "name": "清洗转移泵",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "device",
+ "class": "virtual_transfer_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 50.0,
+ "transfer_rate": 10.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_volume": 0.0,
+ "max_volume": 50.0,
+ "transfer_rate": 10.0,
+ "from_vessel": "",
+ "to_vessel": "",
+ "progress": 0.0,
+ "transferred_volume": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "heatchill_1",
+ "name": "加热冷却器",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "device",
+ "class": "virtual_heatchill",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 150.0,
+ "min_temp": -20.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_temp": 25.0,
+ "target_temp": 25.0,
+ "vessel": "",
+ "purpose": "",
+ "progress": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "flask_water",
+ "name": "水溶剂瓶",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "water",
+ "volume": 1500.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_ethanol",
+ "name": "乙醇溶剂瓶",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "ethanol",
+ "volume": 1500.0,
+ "concentration": 99.5
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_acetone",
+ "name": "丙酮溶剂瓶",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "acetone",
+ "volume": 1800.0,
+ "concentration": 99.9
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_waste",
+ "name": "废液瓶",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 550,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 5000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "residue",
+ "volume": 50.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_buffer",
+ "name": "缓冲液瓶",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 850,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "buffer",
+ "volume": 1000.0,
+ "concentration": 10.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1000,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 500.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_air",
+ "name": "空气瓶",
+ "children": [],
+ "parent": "CleanVesselTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 950,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "flask_water",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "1",
+ "flask_water": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "flask_ethanol",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "2",
+ "flask_ethanol": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "flask_acetone",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "3",
+ "flask_acetone": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "flask_waste",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "4",
+ "flask_waste": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "5",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "flask_buffer",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "6",
+ "flask_buffer": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "7",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_cleaner",
+ "target": "flask_air",
+ "type": "physical",
+ "port": {
+ "transfer_pump_cleaner": "8",
+ "flask_air": "top"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "flask_sample": "bottom"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/dissolveteststation.json b/test/experiments/mock_protocol/dissolveteststation.json
new file mode 100644
index 00000000..8b7ad28f
--- /dev/null
+++ b/test/experiments/mock_protocol/dissolveteststation.json
@@ -0,0 +1,343 @@
+{
+ "nodes": [
+ {
+ "id": "DissolveTestStation",
+ "name": "溶解测试工作站",
+ "children": [
+ "transfer_pump_1",
+ "heatchill_1",
+ "stirrer_1",
+ "flask_water",
+ "flask_ethanol",
+ "flask_dmso",
+ "reactor",
+ "flask_sample",
+ "flask_buffer"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["DissolveProtocol", "TransferProtocol", "HeatChillProtocol", "StirProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "transfer_pump_1",
+ "name": "转移泵",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "device",
+ "class": "virtual_transfer_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 50.0,
+ "transfer_rate": 10.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_volume": 0.0,
+ "max_volume": 50.0,
+ "transfer_rate": 10.0,
+ "from_vessel": "",
+ "to_vessel": "",
+ "progress": 0.0,
+ "transferred_volume": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "heatchill_1",
+ "name": "加热冷却器",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "device",
+ "class": "virtual_heatchill",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 150.0,
+ "min_temp": -20.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_temp": 25.0,
+ "target_temp": 25.0,
+ "vessel": "",
+ "purpose": "",
+ "progress": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "stirrer_1",
+ "name": "搅拌器",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "device",
+ "class": "virtual_stirrer",
+ "position": {
+ "x": 750.1111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "flask_water",
+ "name": "水溶剂瓶",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "water",
+ "volume": 1500.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_ethanol",
+ "name": "乙醇溶剂瓶",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "ethanol",
+ "volume": 1500.0,
+ "concentration": 99.5
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_dmso",
+ "name": "DMSO溶剂瓶",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "dmso",
+ "volume": 800.0,
+ "concentration": 99.9
+ }
+ ]
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "solid_sample",
+ "volume": 10.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1000,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 500.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_buffer",
+ "name": "缓冲液瓶",
+ "children": [],
+ "parent": "DissolveTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 850,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "buffer",
+ "volume": 1000.0,
+ "concentration": 10.0
+ }
+ ]
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_water",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "1",
+ "flask_water": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_ethanol",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "2",
+ "flask_ethanol": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_dmso",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "3",
+ "flask_dmso": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "4",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "5",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_buffer",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "6",
+ "flask_buffer": "top"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "flask_sample": "bottom"
+ }
+ },
+ {
+ "source": "stirrer_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "stirrer_1": "stir_rod",
+ "reactor": "center"
+ }
+ },
+ {
+ "source": "stirrer_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "stirrer_1": "stir_rod",
+ "flask_sample": "center"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/evaporateteststation.json b/test/experiments/mock_protocol/evaporateteststation.json
new file mode 100644
index 00000000..e69de29b
diff --git a/test/experiments/mock_protocol/filterteststation.json b/test/experiments/mock_protocol/filterteststation.json
new file mode 100644
index 00000000..a816def8
--- /dev/null
+++ b/test/experiments/mock_protocol/filterteststation.json
@@ -0,0 +1,270 @@
+{
+ "nodes": [
+ {
+ "id": "FilterTestStation",
+ "name": "过滤器测试工作站",
+ "children": [
+ "pump_add",
+ "flask_sample",
+ "flask_filtrate",
+ "flask_buffer",
+ "reactor",
+ "stirrer",
+ "filter_1",
+ "flask_air"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "FilterProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "pump_add",
+ "name": "pump_add",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "device",
+ "class": "virtual_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 25.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "stirrer",
+ "name": "stirrer",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "device",
+ "class": "virtual_stirrer",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "filter_1",
+ "name": "过滤器",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "device",
+ "class": "virtual_filter",
+ "position": {
+ "x": 800,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_stir_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_filtrate",
+ "name": "滤液瓶",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_buffer",
+ "name": "缓冲液瓶",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 5000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_air",
+ "name": "空气瓶",
+ "children": [],
+ "parent": "FilterTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 950,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "stirrer",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "stirrer": "top",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_filtrate",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_filtrate": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_buffer",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_buffer": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_air",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_air": "top"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "reactor",
+ "type": "logical",
+ "port": {
+ "filter_1": "input",
+ "reactor": "vessel"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "flask_sample",
+ "type": "logical",
+ "port": {
+ "filter_1": "input",
+ "flask_sample": "vessel"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "flask_filtrate",
+ "type": "logical",
+ "port": {
+ "filter_1": "output",
+ "flask_filtrate": "vessel"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/filterthroughteststation.json b/test/experiments/mock_protocol/filterthroughteststation.json
new file mode 100644
index 00000000..b250df5d
--- /dev/null
+++ b/test/experiments/mock_protocol/filterthroughteststation.json
@@ -0,0 +1,388 @@
+{
+ "nodes": [
+ {
+ "id": "FilterThroughTestStation",
+ "name": "过滤通过测试工作站",
+ "children": [
+ "transfer_pump_1",
+ "filter_1",
+ "flask_ethanol",
+ "flask_water",
+ "flask_methanol",
+ "reactor",
+ "collection_flask",
+ "waste_flask",
+ "flask_sample",
+ "flask_celite",
+ "flask_silica"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["FilterThroughProtocol", "TransferProtocol", "FilterProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "transfer_pump_1",
+ "name": "转移泵",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "device",
+ "class": "virtual_transfer_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 50.0,
+ "transfer_rate": 10.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_volume": 0.0,
+ "max_volume": 50.0,
+ "transfer_rate": 10.0,
+ "from_vessel": "",
+ "to_vessel": "",
+ "progress": 0.0,
+ "transferred_volume": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "filter_1",
+ "name": "过滤器",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "device",
+ "class": "virtual_filter",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_stir_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle",
+ "filter_state": "Ready",
+ "current_temp": 25.0,
+ "target_temp": 25.0,
+ "max_temp": 100.0,
+ "stir_speed": 0.0,
+ "max_stir_speed": 1000.0,
+ "filtered_volume": 0.0,
+ "progress": 0.0,
+ "message": ""
+ }
+ },
+ {
+ "id": "flask_ethanol",
+ "name": "乙醇溶剂瓶",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "ethanol",
+ "volume": 1500.0,
+ "concentration": 99.5
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_water",
+ "name": "水溶剂瓶",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "water",
+ "volume": 1800.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_methanol",
+ "name": "甲醇溶剂瓶",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "methanol",
+ "volume": 800.0,
+ "concentration": 99.9
+ }
+ ]
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "crude_product",
+ "volume": 200.0,
+ "concentration": 80.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "collection_flask",
+ "name": "收集瓶",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 850,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "waste_flask",
+ "name": "废液瓶",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1000,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 550,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 500.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "sample_mixture",
+ "volume": 100.0,
+ "concentration": 50.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_celite",
+ "name": "硅藻土容器",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 150,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 500.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "celite",
+ "volume": 50.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_silica",
+ "name": "硅胶容器",
+ "children": [],
+ "parent": "FilterThroughTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 300,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 500.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "silica",
+ "volume": 30.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_ethanol",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "1",
+ "flask_ethanol": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_water",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "2",
+ "flask_water": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_methanol",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "3",
+ "flask_methanol": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "4",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "collection_flask",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "5",
+ "collection_flask": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "waste_flask",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "6",
+ "waste_flask": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "7",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "collection_flask",
+ "type": "physical",
+ "port": {
+ "filter_1": "filter_element",
+ "collection_flask": "top"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "filter_1": "filter_element",
+ "reactor": "top"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/heatchillteststation.json b/test/experiments/mock_protocol/heatchillteststation.json
new file mode 100644
index 00000000..9d243b84
--- /dev/null
+++ b/test/experiments/mock_protocol/heatchillteststation.json
@@ -0,0 +1,262 @@
+{
+ "nodes": [
+ {
+ "id": "HeatChillTestStation",
+ "name": "加热冷却测试工作站",
+ "children": [
+ "pump_add",
+ "flask_sample",
+ "flask_buffer1",
+ "flask_buffer2",
+ "reactor",
+ "stirrer",
+ "heatchill_1",
+ "flask_air"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "pump_add",
+ "name": "pump_add",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "device",
+ "class": "virtual_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 25.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "stirrer",
+ "name": "stirrer",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "device",
+ "class": "virtual_stirrer",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "heatchill_1",
+ "name": "加热冷却器",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "device",
+ "class": "virtual_heatchill",
+ "position": {
+ "x": 800,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 200.0,
+ "min_temp": -80.0,
+ "max_stir_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_buffer1",
+ "name": "缓冲液瓶1",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_buffer2",
+ "name": "缓冲液瓶2",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 5000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_air",
+ "name": "空气瓶",
+ "children": [],
+ "parent": "HeatChillTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 950,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "stirrer",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "stirrer": "top",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_buffer1",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_buffer1": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_buffer2",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_buffer2": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_air",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_air": "top"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "reactor",
+ "type": "logical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "reactor": "vessel"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "flask_sample",
+ "type": "logical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "flask_sample": "vessel"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/runcolumnteststation.json b/test/experiments/mock_protocol/runcolumnteststation.json
new file mode 100644
index 00000000..51741f1c
--- /dev/null
+++ b/test/experiments/mock_protocol/runcolumnteststation.json
@@ -0,0 +1,412 @@
+{
+ "nodes": [
+ {
+ "id": "RunColumnTestStation",
+ "name": "柱层析测试工作站",
+ "children": [
+ "transfer_pump_1",
+ "column_1",
+ "flask_sample",
+ "flask_hexane",
+ "flask_ethyl_acetate",
+ "flask_methanol",
+ "collection_flask_1",
+ "collection_flask_2",
+ "collection_flask_3",
+ "waste_flask",
+ "reactor"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["RunColumnProtocol", "TransferProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "transfer_pump_1",
+ "name": "转移泵",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "device",
+ "class": "virtual_transfer_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 50.0,
+ "transfer_rate": 10.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_volume": 0.0,
+ "max_volume": 50.0,
+ "transfer_rate": 10.0,
+ "from_vessel": "",
+ "to_vessel": "",
+ "progress": 0.0,
+ "transferred_volume": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "column_1",
+ "name": "柱层析设备",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "device",
+ "class": "virtual_column",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_flow_rate": 5.0,
+ "column_length": 30.0,
+ "column_diameter": 2.5
+ },
+ "data": {
+ "status": "Idle",
+ "column_state": "Ready",
+ "current_flow_rate": 0.0,
+ "max_flow_rate": 5.0,
+ "column_length": 30.0,
+ "column_diameter": 2.5,
+ "processed_volume": 0.0,
+ "progress": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 500.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "crude_mixture",
+ "volume": 200.0,
+ "concentration": 70.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_hexane",
+ "name": "正己烷洗脱剂",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "hexane",
+ "volume": 1500.0,
+ "concentration": 99.8
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_ethyl_acetate",
+ "name": "乙酸乙酯洗脱剂",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "ethyl_acetate",
+ "volume": 1500.0,
+ "concentration": 99.5
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_methanol",
+ "name": "甲醇洗脱剂",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 550,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "methanol",
+ "volume": 800.0,
+ "concentration": 99.9
+ }
+ ]
+ }
+ },
+ {
+ "id": "collection_flask_1",
+ "name": "收集瓶1",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 750,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "collection_flask_2",
+ "name": "收集瓶2",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 900,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "collection_flask_3",
+ "name": "收集瓶3",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1050,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "waste_flask",
+ "name": "废液瓶",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1200,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "RunColumnTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "reaction_mixture",
+ "volume": 300.0,
+ "concentration": 85.0
+ }
+ ]
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "1",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_hexane",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "2",
+ "flask_hexane": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_ethyl_acetate",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "3",
+ "flask_ethyl_acetate": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_methanol",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "4",
+ "flask_methanol": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "column_1",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "5",
+ "column_1": "inlet"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "collection_flask_1",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "6",
+ "collection_flask_1": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "collection_flask_2",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "7",
+ "collection_flask_2": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "collection_flask_3",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "8",
+ "collection_flask_3": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "waste_flask",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "9",
+ "waste_flask": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "10",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "column_1",
+ "target": "collection_flask_1",
+ "type": "physical",
+ "port": {
+ "column_1": "outlet",
+ "collection_flask_1": "top"
+ }
+ },
+ {
+ "source": "column_1",
+ "target": "collection_flask_2",
+ "type": "physical",
+ "port": {
+ "column_1": "outlet",
+ "collection_flask_2": "top"
+ }
+ },
+ {
+ "source": "column_1",
+ "target": "collection_flask_3",
+ "type": "physical",
+ "port": {
+ "column_1": "outlet",
+ "collection_flask_3": "top"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/stirteststation.json b/test/experiments/mock_protocol/stirteststation.json
new file mode 100644
index 00000000..20694d1b
--- /dev/null
+++ b/test/experiments/mock_protocol/stirteststation.json
@@ -0,0 +1,250 @@
+{
+ "nodes": [
+ {
+ "id": "StirTestStation",
+ "name": "搅拌测试工作站",
+ "children": [
+ "pump_add",
+ "flask_sample",
+ "flask_buffer1",
+ "flask_buffer2",
+ "reactor",
+ "stirrer",
+ "flask_waste",
+ "flask_air"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "pump_add",
+ "name": "添加泵",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "device",
+ "class": "virtual_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 25.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "stirrer",
+ "name": "搅拌器",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "device",
+ "class": "virtual_stirrer",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_buffer1",
+ "name": "缓冲液瓶1",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_buffer2",
+ "name": "缓冲液瓶2",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 5000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_waste",
+ "name": "废液瓶",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 850,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 3000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_air",
+ "name": "空气瓶",
+ "children": [],
+ "parent": "StirTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 950,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "stirrer",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "stirrer": "top",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_buffer1",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_buffer1": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_buffer2",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_buffer2": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_waste",
+ "type": "physical",
+ "port": {
+ "pump_add": "outlet",
+ "flask_waste": "top"
+ }
+ },
+ {
+ "source": "pump_add",
+ "target": "flask_air",
+ "type": "physical",
+ "port": {
+ "pump_add": "inlet",
+ "flask_air": "top"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/transferteststation.json b/test/experiments/mock_protocol/transferteststation.json
new file mode 100644
index 00000000..cbe485b7
--- /dev/null
+++ b/test/experiments/mock_protocol/transferteststation.json
@@ -0,0 +1,249 @@
+{
+ "nodes": [
+ {
+ "id": "TransferTestStation",
+ "name": "液体转移测试工作站",
+ "children": [
+ "transfer_pump",
+ "flask_source1",
+ "flask_source2",
+ "flask_target1",
+ "flask_target2",
+ "reactor",
+ "flask_waste",
+ "flask_rinsing"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["TransferProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "transfer_pump",
+ "name": "转移泵",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "device",
+ "class": "virtual_transfer_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 50.0,
+ "transfer_rate": 5.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "flask_source1",
+ "name": "源容器1",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_source2",
+ "name": "源容器2",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_target1",
+ "name": "目标容器1",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_target2",
+ "name": "目标容器2",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 550,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_waste",
+ "name": "废液瓶",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 850,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_rinsing",
+ "name": "冲洗液瓶",
+ "children": [],
+ "parent": "TransferTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 950,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "transfer_pump",
+ "target": "flask_source1",
+ "type": "physical",
+ "port": {
+ "transfer_pump": "inlet",
+ "flask_source1": "top"
+ }
+ },
+ {
+ "source": "transfer_pump",
+ "target": "flask_source2",
+ "type": "physical",
+ "port": {
+ "transfer_pump": "inlet",
+ "flask_source2": "top"
+ }
+ },
+ {
+ "source": "transfer_pump",
+ "target": "flask_target1",
+ "type": "physical",
+ "port": {
+ "transfer_pump": "outlet",
+ "flask_target1": "top"
+ }
+ },
+ {
+ "source": "transfer_pump",
+ "target": "flask_target2",
+ "type": "physical",
+ "port": {
+ "transfer_pump": "outlet",
+ "flask_target2": "top"
+ }
+ },
+ {
+ "source": "transfer_pump",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "transfer_pump": "outlet",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "transfer_pump",
+ "target": "flask_waste",
+ "type": "physical",
+ "port": {
+ "transfer_pump": "outlet",
+ "flask_waste": "top"
+ }
+ },
+ {
+ "source": "transfer_pump",
+ "target": "flask_rinsing",
+ "type": "physical",
+ "port": {
+ "transfer_pump": "inlet",
+ "flask_rinsing": "top"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/mock_protocol/washsolidteststation.json b/test/experiments/mock_protocol/washsolidteststation.json
new file mode 100644
index 00000000..170c825c
--- /dev/null
+++ b/test/experiments/mock_protocol/washsolidteststation.json
@@ -0,0 +1,494 @@
+{
+ "nodes": [
+ {
+ "id": "WashSolidTestStation",
+ "name": "固体清洗测试工作站",
+ "children": [
+ "transfer_pump_1",
+ "heatchill_1",
+ "stirrer_1",
+ "filter_1",
+ "flask_ethanol",
+ "flask_water",
+ "flask_acetone",
+ "flask_methanol",
+ "reactor",
+ "collection_flask",
+ "waste_flask",
+ "flask_sample",
+ "filtrate_flask"
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "workstation",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "protocol_type": ["WashSolidProtocol", "TransferProtocol", "FilterProtocol", "HeatChillProtocol", "StirProtocol"]
+ },
+ "data": {}
+ },
+ {
+ "id": "transfer_pump_1",
+ "name": "转移泵",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "device",
+ "class": "virtual_transfer_pump",
+ "position": {
+ "x": 520.6111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_volume": 50.0,
+ "transfer_rate": 10.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_volume": 0.0,
+ "max_volume": 50.0,
+ "transfer_rate": 10.0,
+ "from_vessel": "",
+ "to_vessel": "",
+ "progress": 0.0,
+ "transferred_volume": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "heatchill_1",
+ "name": "加热冷却器",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "device",
+ "class": "virtual_heatchill",
+ "position": {
+ "x": 650.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 150.0,
+ "min_temp": -20.0
+ },
+ "data": {
+ "status": "Idle",
+ "current_temp": 25.0,
+ "target_temp": 25.0,
+ "vessel": "",
+ "purpose": "",
+ "progress": 0.0,
+ "current_status": "Ready"
+ }
+ },
+ {
+ "id": "stirrer_1",
+ "name": "搅拌器",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "device",
+ "class": "virtual_stirrer",
+ "position": {
+ "x": 750.1111111111111,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle"
+ }
+ },
+ {
+ "id": "filter_1",
+ "name": "过滤器",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "device",
+ "class": "virtual_filter",
+ "position": {
+ "x": 850.1111111111111,
+ "y": 478,
+ "z": 0
+ },
+ "config": {
+ "port": "VIRTUAL",
+ "max_temp": 100.0,
+ "max_stir_speed": 1000.0
+ },
+ "data": {
+ "status": "Idle",
+ "filter_state": "Ready",
+ "current_temp": 25.0,
+ "target_temp": 25.0,
+ "max_temp": 100.0,
+ "stir_speed": 0.0,
+ "max_stir_speed": 1000.0,
+ "filtered_volume": 0.0,
+ "progress": 0.0,
+ "message": ""
+ }
+ },
+ {
+ "id": "flask_ethanol",
+ "name": "乙醇清洗剂",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 100,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "ethanol",
+ "volume": 1500.0,
+ "concentration": 99.5
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_water",
+ "name": "水清洗剂",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 250,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "water",
+ "volume": 1800.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_acetone",
+ "name": "丙酮清洗剂",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 400,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "acetone",
+ "volume": 800.0,
+ "concentration": 99.8
+ }
+ ]
+ }
+ },
+ {
+ "id": "flask_methanol",
+ "name": "甲醇清洗剂",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 550,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "methanol",
+ "volume": 800.0,
+ "concentration": 99.9
+ }
+ ]
+ }
+ },
+ {
+ "id": "reactor",
+ "name": "反应器",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 698.1111111111111,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "solid_product",
+ "volume": 50.0,
+ "concentration": 100.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "collection_flask",
+ "name": "收集瓶",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 850,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "waste_flask",
+ "name": "废液瓶",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1000,
+ "y": 428,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 2000.0
+ },
+ "data": {
+ "liquid": []
+ }
+ },
+ {
+ "id": "flask_sample",
+ "name": "样品瓶",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1150,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 500.0
+ },
+ "data": {
+ "liquid": [
+ {
+ "name": "crude_solid",
+ "volume": 30.0,
+ "concentration": 80.0
+ }
+ ]
+ }
+ },
+ {
+ "id": "filtrate_flask",
+ "name": "滤液收集瓶",
+ "children": [],
+ "parent": "WashSolidTestStation",
+ "type": "container",
+ "class": null,
+ "position": {
+ "x": 1000,
+ "y": 300,
+ "z": 0
+ },
+ "config": {
+ "max_volume": 1500.0
+ },
+ "data": {
+ "liquid": []
+ }
+ }
+ ],
+ "links": [
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_ethanol",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "1",
+ "flask_ethanol": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_water",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "2",
+ "flask_water": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_acetone",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "3",
+ "flask_acetone": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_methanol",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "4",
+ "flask_methanol": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "5",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "collection_flask",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "6",
+ "collection_flask": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "waste_flask",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "7",
+ "waste_flask": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "8",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "transfer_pump_1",
+ "target": "filtrate_flask",
+ "type": "physical",
+ "port": {
+ "transfer_pump_1": "9",
+ "filtrate_flask": "top"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "reactor": "bottom"
+ }
+ },
+ {
+ "source": "heatchill_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "heatchill_1": "heating_element",
+ "flask_sample": "bottom"
+ }
+ },
+ {
+ "source": "stirrer_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "stirrer_1": "stir_rod",
+ "reactor": "center"
+ }
+ },
+ {
+ "source": "stirrer_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "stirrer_1": "stir_rod",
+ "flask_sample": "center"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "reactor",
+ "type": "physical",
+ "port": {
+ "filter_1": "filter_element",
+ "reactor": "top"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "flask_sample",
+ "type": "physical",
+ "port": {
+ "filter_1": "filter_element",
+ "flask_sample": "top"
+ }
+ },
+ {
+ "source": "filter_1",
+ "target": "filtrate_flask",
+ "type": "physical",
+ "port": {
+ "filter_1": "filter_element",
+ "filtrate_flask": "top"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/experiments/plr_test_converted.json b/test/experiments/plr_test_converted.json
index 533d99c3..6b5cae4e 100644
--- a/test/experiments/plr_test_converted.json
+++ b/test/experiments/plr_test_converted.json
@@ -47,6 +47,9 @@
},
"config": {
"type": "OTDeck",
+ "size_x": 624.3,
+ "size_y": 565.2,
+ "size_z": 900,
"with_trash": false,
"rotation": {
"x": 0,
@@ -163,7 +166,7 @@
"type": "plate",
"class": "opentrons_96_filtertiprack_1000ul",
"position": {
- "x": 0,
+ "x": 265.0,
"y": 0,
"z": 69
},
@@ -281,6 +284,10 @@
},
"data": {}
},
+
+
+
+
{
"id": "tip_rack_A1",
"name": "tip_rack_A1",
@@ -290,14 +297,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 68.3,
+ "x": 11.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -309,19 +316,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -330,10 +337,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -346,14 +353,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 59.3,
+ "x": 11.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -365,19 +372,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -386,10 +393,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -402,14 +409,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 50.3,
+ "x": 11.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -421,19 +428,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -442,10 +449,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -458,14 +465,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 41.3,
+ "x": 11.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -477,19 +484,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -498,10 +505,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -514,14 +521,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 32.3,
+ "x": 11.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -533,19 +540,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -554,10 +561,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -570,14 +577,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 23.3,
+ "x": 11.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -589,19 +596,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -610,10 +617,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -626,14 +633,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 14.3,
+ "x": 11.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -645,19 +652,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -666,10 +673,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -682,14 +689,14 @@
"type": "device",
"class": "",
"position": {
- "x": 7.2,
- "y": 5.3,
+ "x": 11.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -701,19 +708,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -722,10 +729,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -738,14 +745,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 68.3,
+ "x": 20.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -757,19 +764,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -778,10 +785,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -794,14 +801,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 59.3,
+ "x": 20.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -813,19 +820,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -834,10 +841,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -850,14 +857,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 50.3,
+ "x": 20.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -869,19 +876,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -890,10 +897,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -906,14 +913,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 41.3,
+ "x": 20.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -925,19 +932,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -946,10 +953,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -962,14 +969,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 32.3,
+ "x": 20.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -981,19 +988,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1002,10 +1009,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1018,14 +1025,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 23.3,
+ "x": 20.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1037,19 +1044,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1058,10 +1065,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1074,14 +1081,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 14.3,
+ "x": 20.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1093,19 +1100,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1114,10 +1121,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1130,14 +1137,14 @@
"type": "device",
"class": "",
"position": {
- "x": 16.2,
- "y": 5.3,
+ "x": 20.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1149,19 +1156,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1170,10 +1177,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1186,14 +1193,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 68.3,
+ "x": 29.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1205,19 +1212,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1226,10 +1233,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1242,14 +1249,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 59.3,
+ "x": 29.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1261,19 +1268,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1282,10 +1289,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1298,14 +1305,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 50.3,
+ "x": 29.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1317,19 +1324,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1338,10 +1345,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1354,14 +1361,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 41.3,
+ "x": 29.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1373,19 +1380,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1394,10 +1401,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1410,14 +1417,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 32.3,
+ "x": 29.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1429,19 +1436,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1450,10 +1457,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1466,14 +1473,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 23.3,
+ "x": 29.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1485,19 +1492,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1506,10 +1513,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1522,14 +1529,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 14.3,
+ "x": 29.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1541,19 +1548,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1562,10 +1569,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1578,14 +1585,14 @@
"type": "device",
"class": "",
"position": {
- "x": 25.2,
- "y": 5.3,
+ "x": 29.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1597,19 +1604,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1618,10 +1625,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1634,14 +1641,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 68.3,
+ "x": 38.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1653,19 +1660,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1674,10 +1681,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1690,14 +1697,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 59.3,
+ "x": 38.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1709,19 +1716,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1730,10 +1737,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1746,14 +1753,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 50.3,
+ "x": 38.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1765,19 +1772,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1786,10 +1793,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1802,14 +1809,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 41.3,
+ "x": 38.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1821,19 +1828,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1842,10 +1849,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1858,14 +1865,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 32.3,
+ "x": 38.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1877,19 +1884,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1898,10 +1905,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1914,14 +1921,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 23.3,
+ "x": 38.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1933,19 +1940,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -1954,10 +1961,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -1970,14 +1977,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 14.3,
+ "x": 38.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -1989,19 +1996,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2010,10 +2017,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2026,14 +2033,14 @@
"type": "device",
"class": "",
"position": {
- "x": 34.2,
- "y": 5.3,
+ "x": 38.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2045,19 +2052,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2066,10 +2073,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2082,14 +2089,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 68.3,
+ "x": 47.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2101,19 +2108,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2122,10 +2129,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2138,14 +2145,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 59.3,
+ "x": 47.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2157,19 +2164,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2178,10 +2185,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2194,14 +2201,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 50.3,
+ "x": 47.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2213,19 +2220,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2234,10 +2241,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2250,14 +2257,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 41.3,
+ "x": 47.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2269,19 +2276,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2290,10 +2297,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2306,14 +2313,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 32.3,
+ "x": 47.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2325,19 +2332,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2346,10 +2353,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2362,14 +2369,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 23.3,
+ "x": 47.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2381,19 +2388,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2402,10 +2409,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2418,14 +2425,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 14.3,
+ "x": 47.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2437,19 +2444,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2458,10 +2465,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2474,14 +2481,14 @@
"type": "device",
"class": "",
"position": {
- "x": 43.2,
- "y": 5.3,
+ "x": 47.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2493,19 +2500,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2514,10 +2521,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2530,14 +2537,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 68.3,
+ "x": 56.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2549,19 +2556,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2570,10 +2577,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2586,14 +2593,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 59.3,
+ "x": 56.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2605,19 +2612,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2626,10 +2633,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2642,14 +2649,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 50.3,
+ "x": 56.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2661,19 +2668,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2682,10 +2689,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2698,14 +2705,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 41.3,
+ "x": 56.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2717,19 +2724,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2738,10 +2745,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2754,14 +2761,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 32.3,
+ "x": 56.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2773,19 +2780,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2794,10 +2801,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2810,14 +2817,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 23.3,
+ "x": 56.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2829,19 +2836,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2850,10 +2857,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2866,14 +2873,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 14.3,
+ "x": 56.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2885,19 +2892,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2906,10 +2913,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2922,14 +2929,14 @@
"type": "device",
"class": "",
"position": {
- "x": 52.2,
- "y": 5.3,
+ "x": 56.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2941,19 +2948,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -2962,10 +2969,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -2978,14 +2985,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 68.3,
+ "x": 65.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -2997,19 +3004,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3018,10 +3025,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3034,14 +3041,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 59.3,
+ "x": 65.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3053,19 +3060,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3074,10 +3081,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3090,14 +3097,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 50.3,
+ "x": 65.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3109,19 +3116,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3130,10 +3137,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3146,14 +3153,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 41.3,
+ "x": 65.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3165,19 +3172,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3186,10 +3193,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3202,14 +3209,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 32.3,
+ "x": 65.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3221,19 +3228,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3242,10 +3249,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3258,14 +3265,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 23.3,
+ "x": 65.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3277,19 +3284,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3298,10 +3305,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3314,14 +3321,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 14.3,
+ "x": 65.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3333,19 +3340,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3354,10 +3361,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3370,14 +3377,14 @@
"type": "device",
"class": "",
"position": {
- "x": 61.2,
- "y": 5.3,
+ "x": 65.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3389,19 +3396,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3410,10 +3417,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3426,14 +3433,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 68.3,
+ "x": 74.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3445,19 +3452,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3466,10 +3473,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3482,14 +3489,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 59.3,
+ "x": 74.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3501,19 +3508,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3522,10 +3529,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3538,14 +3545,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 50.3,
+ "x": 74.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3557,19 +3564,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3578,10 +3585,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3594,14 +3601,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 41.3,
+ "x": 74.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3613,19 +3620,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3634,10 +3641,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3650,14 +3657,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 32.3,
+ "x": 74.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3669,19 +3676,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3690,10 +3697,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3706,14 +3713,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 23.3,
+ "x": 74.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3725,19 +3732,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3746,10 +3753,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3762,14 +3769,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 14.3,
+ "x": 74.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3781,19 +3788,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3802,10 +3809,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3818,14 +3825,14 @@
"type": "device",
"class": "",
"position": {
- "x": 70.2,
- "y": 5.3,
+ "x": 74.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3837,19 +3844,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3858,10 +3865,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3874,14 +3881,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 68.3,
+ "x": 83.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3893,19 +3900,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3914,10 +3921,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3930,14 +3937,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 59.3,
+ "x": 83.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -3949,19 +3956,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -3970,10 +3977,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -3986,14 +3993,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 50.3,
+ "x": 83.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4005,19 +4012,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4026,10 +4033,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4042,14 +4049,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 41.3,
+ "x": 83.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4061,19 +4068,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4082,10 +4089,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4098,14 +4105,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 32.3,
+ "x": 83.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4117,19 +4124,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4138,10 +4145,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4154,14 +4161,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 23.3,
+ "x": 83.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4173,19 +4180,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4194,10 +4201,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4210,14 +4217,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 14.3,
+ "x": 83.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4229,19 +4236,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4250,10 +4257,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4266,14 +4273,14 @@
"type": "device",
"class": "",
"position": {
- "x": 79.2,
- "y": 5.3,
+ "x": 83.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4285,19 +4292,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4306,10 +4313,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4322,14 +4329,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 68.3,
+ "x": 92.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4341,19 +4348,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4362,10 +4369,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4378,14 +4385,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 59.3,
+ "x": 92.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4397,19 +4404,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4418,10 +4425,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4434,14 +4441,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 50.3,
+ "x": 92.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4453,19 +4460,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4474,10 +4481,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4490,14 +4497,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 41.3,
+ "x": 92.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4509,19 +4516,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4530,10 +4537,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4546,14 +4553,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 32.3,
+ "x": 92.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4565,19 +4572,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4586,10 +4593,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4602,14 +4609,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 23.3,
+ "x": 92.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4621,19 +4628,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4642,10 +4649,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4658,14 +4665,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 14.3,
+ "x": 92.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4677,19 +4684,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4698,10 +4705,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4714,14 +4721,14 @@
"type": "device",
"class": "",
"position": {
- "x": 88.2,
- "y": 5.3,
+ "x": 92.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4733,19 +4740,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4754,10 +4761,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4770,14 +4777,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 68.3,
+ "x": 101.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4789,19 +4796,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4810,10 +4817,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4826,14 +4833,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 59.3,
+ "x": 101.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4845,19 +4852,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4866,10 +4873,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4882,14 +4889,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 50.3,
+ "x": 101.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4901,19 +4908,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4922,10 +4929,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4938,14 +4945,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 41.3,
+ "x": 101.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -4957,19 +4964,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -4978,10 +4985,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -4994,14 +5001,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 32.3,
+ "x": 101.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5013,19 +5020,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5034,10 +5041,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5050,14 +5057,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 23.3,
+ "x": 101.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5069,19 +5076,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5090,10 +5097,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5106,14 +5113,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 14.3,
+ "x": 101.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5125,19 +5132,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5146,10 +5153,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5162,14 +5169,14 @@
"type": "device",
"class": "",
"position": {
- "x": 97.2,
- "y": 5.3,
+ "x": 101.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5181,19 +5188,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5202,10 +5209,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5218,14 +5225,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 68.3,
+ "x": 110.804,
+ "y": 71.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5237,19 +5244,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5258,10 +5265,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5274,14 +5281,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 59.3,
+ "x": 110.804,
+ "y": 62.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5293,19 +5300,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5314,10 +5321,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5330,14 +5337,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 50.3,
+ "x": 110.804,
+ "y": 53.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5349,19 +5356,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5370,10 +5377,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5386,14 +5393,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 41.3,
+ "x": 110.804,
+ "y": 44.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5405,19 +5412,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5426,10 +5433,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5442,14 +5449,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 32.3,
+ "x": 110.804,
+ "y": 35.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5461,19 +5468,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5482,10 +5489,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5498,14 +5505,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 23.3,
+ "x": 110.804,
+ "y": 26.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5517,19 +5524,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5538,10 +5545,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5554,14 +5561,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 14.3,
+ "x": 110.804,
+ "y": 17.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5573,19 +5580,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5594,10 +5601,10 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
@@ -5610,14 +5617,14 @@
"type": "device",
"class": "",
"position": {
- "x": 106.2,
- "y": 5.3,
+ "x": 110.804,
+ "y": 8.704,
"z": 9.47
},
"config": {
"type": "TipSpot",
- "size_x": 9.0,
- "size_y": 9.0,
+ "size_x": 5.112,
+ "size_y": 5.112,
"size_z": 0,
"rotation": {
"x": 0,
@@ -5629,19 +5636,19 @@
"model": null,
"prototype_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
},
"data": {
"tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
},
"tip_state": {
"liquids": [],
@@ -5650,13 +5657,16 @@
},
"pending_tip": {
"type": "Tip",
- "total_tip_length": 39.2,
+ "total_tip_length": 88,
"has_filter": true,
- "maximal_volume": 20.0,
- "fitting_depth": 3.29
+ "maximal_volume": 1000.0,
+ "fitting_depth": 7.95
}
}
},
+
+
+
{
"id": "plate_well",
"name": "plate_well",
@@ -5677,7 +5687,7 @@
"plate_well_E2",
"plate_well_F2",
"plate_well_G2",
- "plate_well_H2",
+ "plate_well_H2",
"plate_well_A3",
"plate_well_B3",
"plate_well_C3",
@@ -5756,14 +5766,15 @@
"plate_well_D12",
"plate_well_E12",
"plate_well_F12",
- "plate_well_G12"
+ "plate_well_G12",
+ "plate_well_H12"
],
"parent": "deck",
"type": "plate",
"class": "nest_96_wellplate_2ml_deep",
"position": {
- "x": 265.0,
- "y": 0,
+ "x": 0,
+ "y": 90.5,
"z": 69
},
"config": {
@@ -5880,6 +5891,11 @@
},
"data": {}
},
+
+
+
+
+
{
"id": "plate_well_A1",
"name": "plate_well_A1",
@@ -5889,15 +5905,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 70.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -5906,16 +5922,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -5928,15 +5944,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 61.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -5945,16 +5961,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -5967,15 +5983,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 52.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -5984,16 +6000,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -6006,15 +6022,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 43.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6023,16 +6039,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -6045,15 +6061,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 34.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6062,16 +6078,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -6084,15 +6100,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 25.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6101,16 +6117,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -6123,15 +6139,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 16.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6140,16 +6156,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -6162,15 +6178,15 @@
"type": "device",
"class": "",
"position": {
- "x": 10.87,
- "y": 7.77,
- "z": 3.03
+ "x": 10.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6179,16 +6195,16 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
- "liquids": [],
- "pending_liquids": [],
+ "liquids": [["water", 50.0]],
+ "pending_liquids": [["water", 50.0]],
"liquid_history": []
}
},
@@ -6201,15 +6217,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 70.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6218,12 +6234,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6240,15 +6256,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 61.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6257,12 +6273,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6279,15 +6295,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 52.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6296,12 +6312,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6318,15 +6334,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 43.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6335,12 +6351,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6357,15 +6373,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 34.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6374,12 +6390,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6396,15 +6412,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 25.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6413,12 +6429,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6435,15 +6451,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 16.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6452,12 +6468,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6474,15 +6490,15 @@
"type": "device",
"class": "",
"position": {
- "x": 19.87,
- "y": 7.77,
- "z": 3.03
+ "x": 19.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6491,12 +6507,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6513,15 +6529,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 70.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6530,12 +6546,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6552,15 +6568,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 61.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6569,12 +6585,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6591,15 +6607,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 52.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6608,12 +6624,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6630,15 +6646,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 43.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6647,12 +6663,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6669,15 +6685,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 34.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6686,12 +6702,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6708,15 +6724,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 25.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6725,12 +6741,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6747,15 +6763,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 16.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6764,12 +6780,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6786,15 +6802,15 @@
"type": "device",
"class": "",
"position": {
- "x": 28.87,
- "y": 7.77,
- "z": 3.03
+ "x": 28.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6803,12 +6819,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6825,15 +6841,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 70.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6842,12 +6858,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6864,15 +6880,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 61.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6881,12 +6897,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6903,15 +6919,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 52.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6920,12 +6936,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6942,15 +6958,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 43.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6959,12 +6975,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -6981,15 +6997,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 34.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -6998,12 +7014,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7020,15 +7036,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 25.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7037,12 +7053,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7059,15 +7075,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 16.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7076,12 +7092,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7098,15 +7114,15 @@
"type": "device",
"class": "",
"position": {
- "x": 37.87,
- "y": 7.77,
- "z": 3.03
+ "x": 37.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7115,12 +7131,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7137,15 +7153,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 70.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7154,12 +7170,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7176,15 +7192,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 61.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7193,12 +7209,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7215,15 +7231,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 52.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7232,12 +7248,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7254,15 +7270,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 43.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7271,12 +7287,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7293,15 +7309,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 34.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7310,12 +7326,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7332,15 +7348,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 25.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7349,12 +7365,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7371,15 +7387,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 16.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7388,12 +7404,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7410,15 +7426,15 @@
"type": "device",
"class": "",
"position": {
- "x": 46.87,
- "y": 7.77,
- "z": 3.03
+ "x": 46.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7427,12 +7443,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7449,15 +7465,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 70.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7466,12 +7482,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7488,15 +7504,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 61.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7505,12 +7521,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7527,15 +7543,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 52.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7544,12 +7560,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7566,15 +7582,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 43.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7583,12 +7599,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7605,15 +7621,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 34.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7622,12 +7638,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7644,15 +7660,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 25.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7661,12 +7677,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7683,15 +7699,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 16.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7700,12 +7716,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7722,15 +7738,15 @@
"type": "device",
"class": "",
"position": {
- "x": 55.87,
- "y": 7.77,
- "z": 3.03
+ "x": 55.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7739,12 +7755,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7761,15 +7777,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 70.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7778,12 +7794,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7800,15 +7816,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 61.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7817,12 +7833,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7839,15 +7855,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 52.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7856,12 +7872,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7878,15 +7894,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 43.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7895,12 +7911,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7917,15 +7933,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 34.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7934,12 +7950,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7956,15 +7972,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 25.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -7973,12 +7989,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -7995,15 +8011,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 16.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8012,12 +8028,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8034,15 +8050,15 @@
"type": "device",
"class": "",
"position": {
- "x": 64.87,
- "y": 7.77,
- "z": 3.03
+ "x": 64.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8051,12 +8067,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8073,15 +8089,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 70.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8090,12 +8106,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8112,15 +8128,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 61.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8129,12 +8145,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8151,15 +8167,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 52.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8168,12 +8184,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8190,15 +8206,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 43.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8207,12 +8223,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8229,15 +8245,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 34.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8246,12 +8262,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8268,15 +8284,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 25.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8285,12 +8301,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8307,15 +8323,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 16.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8324,12 +8340,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8346,15 +8362,15 @@
"type": "device",
"class": "",
"position": {
- "x": 73.87,
- "y": 7.77,
- "z": 3.03
+ "x": 73.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8363,12 +8379,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8385,15 +8401,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 70.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8402,12 +8418,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8424,15 +8440,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 61.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8441,12 +8457,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8463,15 +8479,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 52.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8480,12 +8496,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8502,15 +8518,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 43.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8519,12 +8535,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8541,15 +8557,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 34.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8558,12 +8574,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8580,15 +8596,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 25.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8597,12 +8613,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8619,15 +8635,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 16.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8636,12 +8652,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8658,15 +8674,15 @@
"type": "device",
"class": "",
"position": {
- "x": 82.87,
- "y": 7.77,
- "z": 3.03
+ "x": 82.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8675,12 +8691,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8697,15 +8713,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 70.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8714,12 +8730,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8736,15 +8752,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 61.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8753,12 +8769,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8775,15 +8791,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 52.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8792,12 +8808,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8814,15 +8830,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 43.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8831,12 +8847,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8853,15 +8869,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 34.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8870,12 +8886,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8892,15 +8908,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 25.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8909,12 +8925,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8931,15 +8947,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 16.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8948,12 +8964,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -8970,15 +8986,15 @@
"type": "device",
"class": "",
"position": {
- "x": 91.87,
- "y": 7.77,
- "z": 3.03
+ "x": 91.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -8987,12 +9003,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9009,15 +9025,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 70.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9026,12 +9042,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9048,15 +9064,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 61.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9065,12 +9081,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9087,15 +9103,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 52.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9104,12 +9120,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9126,15 +9142,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 43.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9143,12 +9159,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9165,15 +9181,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 34.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9182,12 +9198,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9204,15 +9220,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 25.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9221,12 +9237,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9243,15 +9259,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 16.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9260,12 +9276,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9282,15 +9298,15 @@
"type": "device",
"class": "",
"position": {
- "x": 100.87,
- "y": 7.77,
- "z": 3.03
+ "x": 100.2,
+ "y": 7.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9299,12 +9315,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9321,15 +9337,15 @@
"type": "device",
"class": "",
"position": {
- "x": 109.87,
- "y": 70.77,
- "z": 3.03
+ "x": 109.2,
+ "y": 70.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9338,12 +9354,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9360,15 +9376,15 @@
"type": "device",
"class": "",
"position": {
- "x": 109.87,
- "y": 61.77,
- "z": 3.03
+ "x": 109.2,
+ "y": 61.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9377,12 +9393,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9399,15 +9415,15 @@
"type": "device",
"class": "",
"position": {
- "x": 109.87,
- "y": 52.77,
- "z": 3.03
+ "x": 109.2,
+ "y": 52.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9416,12 +9432,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9438,15 +9454,15 @@
"type": "device",
"class": "",
"position": {
- "x": 109.87,
- "y": 43.77,
- "z": 3.03
+ "x": 109.2,
+ "y": 43.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9455,12 +9471,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9477,15 +9493,15 @@
"type": "device",
"class": "",
"position": {
- "x": 109.87,
- "y": 34.77,
- "z": 3.03
+ "x": 109.2,
+ "y": 34.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9494,12 +9510,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9516,15 +9532,15 @@
"type": "device",
"class": "",
"position": {
- "x": 109.87,
- "y": 25.77,
- "z": 3.03
+ "x": 109.2,
+ "y": 25.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9533,12 +9549,12 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
@@ -9555,15 +9571,15 @@
"type": "device",
"class": "",
"position": {
- "x": 109.87,
- "y": 16.77,
- "z": 3.03
+ "x": 109.2,
+ "y": 16.05,
+ "z": 3
},
"config": {
"type": "Well",
- "size_x": 6.86,
- "size_y": 6.86,
- "size_z": 10.67,
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
"rotation": {
"x": 0,
"y": 0,
@@ -9572,18 +9588,126 @@
},
"category": "well",
"model": null,
- "max_volume": 360,
- "material_z_thickness": 0.5,
+ "max_volume": 2000,
+ "material_z_thickness": null,
"compute_volume_from_height": null,
"compute_height_from_volume": null,
- "bottom_type": "flat",
- "cross_section_type": "circle"
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
},
"data": {
"liquids": [],
"pending_liquids": [],
"liquid_history": []
}
+ },
+ {
+ "id": "plate_well_H12",
+ "name": "plate_well_H12",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 109.2,
+ "y": 7.05,
+ "z": 3
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 8.2,
+ "size_y": 8.2,
+ "size_z": 38,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 2000,
+ "material_z_thickness": null,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "unknown",
+ "cross_section_type": "rectangle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+
+
+
+
+ {
+ "id": "arm_slider",
+ "name": "arm_slider",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "moveit.arm_slider",
+ "position": {
+ "x": -500,
+ "y": 1000,
+ "z": -100
+ },
+ "config": {
+ "moveit_type": "arm_slider",
+ "joint_poses": {
+ "arm": {
+ "hotel_1": [
+ 1.05,
+ 0.568,
+ -1.0821,
+ 0.0,
+ 1.0821
+ ],
+ "home": [
+ 0.865,
+ 0.09,
+ 0.8727,
+ 0.0,
+ -0.8727
+ ]
+ }
+ },
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": -1.5708,
+ "type": "Rotation"
+ },
+ "device_config": {}
+ },
+ "data": {}
+ },
+ {
+ "id": "hotel",
+ "name": "hotel",
+ "children": [],
+ "parent": null,
+ "type": "device",
+ "class": "hotel.thermo_orbitor_rs2_hotel",
+ "position": {
+ "x": 0,
+ "y": -700,
+ "z": -10
+ },
+ "config": {
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "device_config": {}
+ },
+ "data": {}
}
],
"links": []
diff --git a/test/experiments/plr_test_converted_slim.json b/test/experiments/plr_test_converted_slim.json
new file mode 100644
index 00000000..43e05e7c
--- /dev/null
+++ b/test/experiments/plr_test_converted_slim.json
@@ -0,0 +1,1710 @@
+{
+ "nodes": [
+ {
+ "id": "PLR_STATION",
+ "name": "PLR_LH_TEST",
+ "parent": null,
+ "type": "device",
+ "class": "liquid_handler",
+ "position": {
+ "x": 620.6111111111111,
+ "y": 171,
+ "z": 0
+ },
+ "config": {
+ "data": {
+ "children": [
+ {
+ "_resource_child_name": "deck",
+ "_resource_type": "pylabrobot.resources.opentrons.deck:OTDeck"
+ }
+ ],
+ "backend": {
+ "type": "LiquidHandlerChatterboxBackend"
+ }
+ }
+ },
+ "data": {},
+ "children": [
+ "deck"
+ ]
+ },
+ {
+ "id": "deck",
+ "name": "deck",
+ "sample_id": null,
+ "children": [
+ "tip_rack",
+ "plate_well"
+ ],
+ "parent": "PLR_STATION",
+ "type": "deck",
+ "class": "OTDeck",
+ "position": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "config": {
+ "type": "OTDeck",
+ "with_trash": false,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ }
+ },
+ "data": {}
+ },
+ {
+ "id": "tip_rack",
+ "name": "tip_rack",
+ "sample_id": null,
+ "children": [
+ "tip_rack_A1",
+ "tip_rack_B1",
+ "tip_rack_C1",
+ "tip_rack_D1",
+ "tip_rack_E1",
+ "tip_rack_F1",
+ "tip_rack_G1",
+ "tip_rack_H1",
+ "tip_rack_A2",
+ "tip_rack_B2",
+ "tip_rack_C2",
+ "tip_rack_D2",
+ "tip_rack_E2",
+ "tip_rack_F2",
+ "tip_rack_G2",
+ "tip_rack_H2"
+ ],
+ "parent": "deck",
+ "type": "plate",
+ "class": "opentrons_96_filtertiprack_1000ul",
+ "position": {
+ "x": 0,
+ "y": 0,
+ "z": 69
+ },
+ "config": {
+ "type": "TipRack",
+ "size_x": 122.4,
+ "size_y": 82.6,
+ "size_z": 20.0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_rack",
+ "model": "HTF",
+ "ordering": [
+ "A1",
+ "B1",
+ "C1",
+ "D1",
+ "E1",
+ "F1",
+ "G1",
+ "H1",
+ "A2",
+ "B2",
+ "C2",
+ "D2",
+ "E2",
+ "F2",
+ "G2",
+ "H2"
+ ]
+ },
+ "data": {}
+ },
+ {
+ "id": "tip_rack_A1",
+ "name": "tip_rack_A1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 68.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_B1",
+ "name": "tip_rack_B1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 59.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_C1",
+ "name": "tip_rack_C1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 50.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_D1",
+ "name": "tip_rack_D1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 41.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_E1",
+ "name": "tip_rack_E1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 32.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_F1",
+ "name": "tip_rack_F1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 23.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_G1",
+ "name": "tip_rack_G1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 14.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_H1",
+ "name": "tip_rack_H1",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 7.2,
+ "y": 5.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_A2",
+ "name": "tip_rack_A2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 68.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_B2",
+ "name": "tip_rack_B2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 59.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_C2",
+ "name": "tip_rack_C2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 50.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_D2",
+ "name": "tip_rack_D2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 41.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_E2",
+ "name": "tip_rack_E2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 32.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_F2",
+ "name": "tip_rack_F2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 23.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_G2",
+ "name": "tip_rack_G2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 14.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "tip_rack_H2",
+ "name": "tip_rack_H2",
+ "sample_id": null,
+ "children": [],
+ "parent": "tip_rack",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 16.2,
+ "y": 5.3,
+ "z": 9.47
+ },
+ "config": {
+ "type": "TipSpot",
+ "size_x": 9.0,
+ "size_y": 9.0,
+ "size_z": 0,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "tip_spot",
+ "model": null,
+ "prototype_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ },
+ "data": {
+ "tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ },
+ "tip_state": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ },
+ "pending_tip": {
+ "type": "Tip",
+ "total_tip_length": 39.2,
+ "has_filter": true,
+ "maximal_volume": 20.0,
+ "fitting_depth": 3.29
+ }
+ }
+ },
+ {
+ "id": "plate_well",
+ "name": "plate_well",
+ "sample_id": null,
+ "children": [
+ "plate_well_A1",
+ "plate_well_B1",
+ "plate_well_C1",
+ "plate_well_D1",
+ "plate_well_E1",
+ "plate_well_F1",
+ "plate_well_G1",
+ "plate_well_H1",
+ "plate_well_A11",
+ "plate_well_B11",
+ "plate_well_C11",
+ "plate_well_D11",
+ "plate_well_E11",
+ "plate_well_F11",
+ "plate_well_G11",
+ "plate_well_H11"
+ ],
+ "parent": "deck",
+ "type": "plate",
+ "class": "nest_96_wellplate_2ml_deep",
+ "position": {
+ "x": 265.0,
+ "y": 0,
+ "z": 69
+ },
+ "config": {
+ "type": "Plate",
+ "size_x": 127.76,
+ "size_y": 85.48,
+ "size_z": 14.2,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "plate",
+ "model": "Cor_96_wellplate_360ul_Fb",
+ "ordering": [
+ "A1",
+ "B1",
+ "C1",
+ "D1",
+ "E1",
+ "F1",
+ "G1",
+ "H1",
+ "A11",
+ "B11",
+ "C11",
+ "D11",
+ "E11",
+ "F11",
+ "G11",
+ "H11"
+ ]
+ },
+ "data": {}
+ },
+ {
+ "id": "plate_well_A1",
+ "name": "plate_well_A1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 70.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_B1",
+ "name": "plate_well_B1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 61.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_C1",
+ "name": "plate_well_C1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 52.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_D1",
+ "name": "plate_well_D1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 43.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_E1",
+ "name": "plate_well_E1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 34.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_F1",
+ "name": "plate_well_F1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 25.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_G1",
+ "name": "plate_well_G1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 16.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_H1",
+ "name": "plate_well_H1",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 10.87,
+ "y": 7.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_A11",
+ "name": "plate_well_A11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 70.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_B11",
+ "name": "plate_well_B11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 61.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_C11",
+ "name": "plate_well_C11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 52.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_D11",
+ "name": "plate_well_D11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 43.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_E11",
+ "name": "plate_well_E11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 34.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_F11",
+ "name": "plate_well_F11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 25.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_G11",
+ "name": "plate_well_G11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 16.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ },
+ {
+ "id": "plate_well_H11",
+ "name": "plate_well_H11",
+ "sample_id": null,
+ "children": [],
+ "parent": "plate_well",
+ "type": "device",
+ "class": "",
+ "position": {
+ "x": 100.87,
+ "y": 7.77,
+ "z": 3.03
+ },
+ "config": {
+ "type": "Well",
+ "size_x": 6.86,
+ "size_y": 6.86,
+ "size_z": 10.67,
+ "rotation": {
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "type": "Rotation"
+ },
+ "category": "well",
+ "model": null,
+ "max_volume": 360,
+ "material_z_thickness": 0.5,
+ "compute_volume_from_height": null,
+ "compute_height_from_volume": null,
+ "bottom_type": "flat",
+ "cross_section_type": "circle"
+ },
+ "data": {
+ "liquids": [],
+ "pending_liquids": [],
+ "liquid_history": []
+ }
+ }
+ ],
+ "links": []
+}
\ No newline at end of file
diff --git a/test/experiments/test_moveit.json b/test/experiments/test_moveit.json
new file mode 100644
index 00000000..41b003b1
--- /dev/null
+++ b/test/experiments/test_moveit.json
@@ -0,0 +1,35 @@
+{
+ "nodes": [
+
+ {
+ "id": "benyao",
+ "name": "benyao",
+ "children": [
+ ],
+ "parent": null,
+ "type": "device",
+ "class": "moveit.arm_slider",
+ "position": {
+ "x": 0,
+ "y": 0,
+ "z": 0
+ },
+ "config": {
+ "moveit_type": "arm_slider",
+ "joint_poses": {
+ "arm": {
+ "home": [0.0, 0.2, 0.0, 0.0, 0.0],
+ "pick": [1.2, 0.0, 0.0, 0.0, 0.0]
+ }
+ },
+ "device_config": {
+ }
+ },
+ "data": {
+ }
+ }
+ ],
+ "links": [
+
+ ]
+}
\ No newline at end of file
diff --git a/unilabos/app/main.py b/unilabos/app/main.py
index 0db290a0..0f6b2f42 100644
--- a/unilabos/app/main.py
+++ b/unilabos/app/main.py
@@ -10,6 +10,8 @@ from copy import deepcopy
import yaml
+from unilabos.resources.graphio import tree_to_list
+
# 首先添加项目根目录到路径
current_dir = os.path.dirname(os.path.abspath(__file__))
unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
@@ -144,19 +146,19 @@ def main():
else read_graphml(args_dict["graph"])
)
devices_and_resources = dict_from_graph(graph_res.physical_setup_graph)
- args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values()))
+ # args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values()))
+ args_dict["resources_config"] = list(devices_and_resources.values())
args_dict["devices_config"] = dict_to_nested_dict(deepcopy(devices_and_resources), devices_only=False)
- # args_dict["resources_config"] = dict_to_tree(devices_and_resources, devices_only=False)
-
args_dict["graph"] = graph_res.physical_setup_graph
else:
if args_dict["devices"] is None or args_dict["resources"] is None:
print_status("Either graph or devices and resources must be provided.", "error")
sys.exit(1)
args_dict["devices_config"] = json.load(open(args_dict["devices"], encoding="utf-8"))
- args_dict["resources_config"] = initialize_resources(
- list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
- )
+ # args_dict["resources_config"] = initialize_resources(
+ # list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
+ # )
+ args_dict["resources_config"] = list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
print_status(f"{len(args_dict['resources_config'])} Resources loaded:", "info")
for i in args_dict["resources_config"]:
diff --git a/unilabos/app/mq.py b/unilabos/app/mq.py
index 9f870691..fbd57b93 100644
--- a/unilabos/app/mq.py
+++ b/unilabos/app/mq.py
@@ -1,6 +1,7 @@
import json
import time
import traceback
+from typing import Optional
import uuid
import paho.mqtt.client as mqtt
@@ -161,12 +162,14 @@ class MQTTClient:
status = {"data": device_status.get(device_id, {}), "device_id": device_id}
address = f"labs/{MQConfig.lab_id}/devices/"
self.client.publish(address, json.dumps(status), qos=2)
- logger.critical(f"Device status published: address: {address}, {status}")
+ logger.debug(f"Device status published: address: {address}, {status}")
- def publish_job_status(self, feedback_data: dict, job_id: str, status: str):
+ def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[str] = None):
if self.mqtt_disable:
return
- jobdata = {"job_id": job_id, "data": feedback_data, "status": status}
+ if return_info is None:
+ return_info = "{}"
+ jobdata = {"job_id": job_id, "data": feedback_data, "status": status, "return_info": return_info}
self.client.publish(f"labs/{MQConfig.lab_id}/job/list/", json.dumps(jobdata), qos=2)
def publish_registry(self, device_id: str, device_info: dict):
diff --git a/unilabos/app/web/client.py b/unilabos/app/web/client.py
index da5d0696..69329489 100644
--- a/unilabos/app/web/client.py
+++ b/unilabos/app/web/client.py
@@ -30,18 +30,18 @@ class HTTPClient:
self.auth = MQConfig.lab_id
info(f"HTTPClient 初始化完成: remote_addr={self.remote_addr}")
- def resource_add(self, resources: List[Dict[str, Any]]) -> requests.Response:
+ def resource_add(self, resources: List[Dict[str, Any]], database_process_later:bool) -> requests.Response:
"""
添加资源
Args:
resources: 要添加的资源列表
-
+ database_process_later: 后台处理资源
Returns:
Response: API响应对象
"""
response = requests.post(
- f"{self.remote_addr}/lab/resource/",
+ f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}",
json=resources,
headers={"Authorization": f"lab {self.auth}"},
timeout=5,
@@ -60,7 +60,7 @@ class HTTPClient:
Dict: 返回的资源数据
"""
response = requests.get(
- f"{self.remote_addr}/lab/resource/",
+ f"{self.remote_addr}/lab/resource/?edge_format=1",
params={"id": id, "with_children": with_children},
headers={"Authorization": f"lab {self.auth}"},
timeout=5,
@@ -96,7 +96,7 @@ class HTTPClient:
Response: API响应对象
"""
response = requests.patch(
- f"{self.remote_addr}/lab/resource/batch_update/",
+ f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
json=resources,
headers={"Authorization": f"lab {self.auth}"},
timeout=5,
diff --git a/unilabos/compile/__init__.py b/unilabos/compile/__init__.py
index 820f43f1..fa61e12f 100644
--- a/unilabos/compile/__init__.py
+++ b/unilabos/compile/__init__.py
@@ -5,6 +5,17 @@ from .separate_protocol import generate_separate_protocol
from .evaporate_protocol import generate_evaporate_protocol
from .evacuateandrefill_protocol import generate_evacuateandrefill_protocol
from .agv_transfer_protocol import generate_agv_transfer_protocol
+from .add_protocol import generate_add_protocol
+from .centrifuge_protocol import generate_centrifuge_protocol
+from .filter_protocol import generate_filter_protocol
+from .heatchill_protocol import generate_heat_chill_protocol, generate_heat_chill_start_protocol, generate_heat_chill_stop_protocol
+from .stir_protocol import generate_stir_protocol, generate_start_stir_protocol, generate_stop_stir_protocol
+from .transfer_protocol import generate_transfer_protocol
+from .clean_vessel_protocol import generate_clean_vessel_protocol
+from .dissolve_protocol import generate_dissolve_protocol
+from .filter_through_protocol import generate_filter_through_protocol
+from .run_column_protocol import generate_run_column_protocol
+from .wash_solid_protocol import generate_wash_solid_protocol
# Define a dictionary of protocol generators.
@@ -15,5 +26,19 @@ action_protocol_generators = {
EvaporateProtocol: generate_evaporate_protocol,
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
AGVTransferProtocol: generate_agv_transfer_protocol,
+ CentrifugeProtocol: generate_centrifuge_protocol,
+ AddProtocol: generate_add_protocol,
+ FilterProtocol: generate_filter_protocol,
+ HeatChillProtocol: generate_heat_chill_protocol,
+ HeatChillStartProtocol: generate_heat_chill_start_protocol,
+ HeatChillStopProtocol: generate_heat_chill_stop_protocol,
+ StirProtocol: generate_stir_protocol,
+ StartStirProtocol: generate_start_stir_protocol,
+ StopStirProtocol: generate_stop_stir_protocol,
+ TransferProtocol: generate_transfer_protocol,
+ CleanVesselProtocol: generate_clean_vessel_protocol,
+ DissolveProtocol: generate_dissolve_protocol,
+ FilterThroughProtocol: generate_filter_through_protocol,
+ RunColumnProtocol: generate_run_column_protocol,
+ WashSolidProtocol: generate_wash_solid_protocol,
}
-# End Protocols
diff --git a/unilabos/compile/add_protocol.py b/unilabos/compile/add_protocol.py
new file mode 100644
index 00000000..e2cdc3c2
--- /dev/null
+++ b/unilabos/compile/add_protocol.py
@@ -0,0 +1,74 @@
+import networkx as nx
+from typing import List, Dict, Any
+
+def generate_add_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ reagent: str,
+ volume: float,
+ mass: float,
+ amount: str,
+ time: float,
+ stir: bool,
+ stir_speed: float,
+ viscous: bool,
+ purpose: str
+) -> List[Dict[str, Any]]:
+ """
+ 生成添加试剂的协议序列 - 严格按照 Add.action
+ """
+ action_sequence = []
+
+ # 如果指定了体积,执行液体转移
+ if volume > 0:
+ # 查找可用的试剂瓶
+ available_flasks = [node for node in G.nodes()
+ if node.startswith('flask_')
+ and G.nodes[node].get('type') == 'container']
+
+ if not available_flasks:
+ raise ValueError("没有找到可用的试剂容器")
+
+ reagent_vessel = available_flasks[0]
+
+ # 查找泵设备
+ pump_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_pump']
+
+ if pump_nodes:
+ pump_id = pump_nodes[0]
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": reagent_vessel,
+ "to_vessel": vessel,
+ "volume": volume,
+ "amount": amount,
+ "time": time,
+ "viscous": viscous,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 如果需要搅拌,使用 StartStir 而不是 Stir
+ if stir:
+ stirrer_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_stirrer']
+
+ if stirrer_nodes:
+ stirrer_id = stirrer_nodes[0]
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "start_stir", # 使用 start_stir 而不是 stir
+ "action_kwargs": {
+ "vessel": vessel,
+ "stir_speed": stir_speed,
+ "purpose": f"添加 {reagent} 后搅拌"
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/centrifuge_protocol.py b/unilabos/compile/centrifuge_protocol.py
new file mode 100644
index 00000000..e55644d2
--- /dev/null
+++ b/unilabos/compile/centrifuge_protocol.py
@@ -0,0 +1,123 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_centrifuge_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ speed: float,
+ time: float,
+ temp: float = 25.0
+) -> List[Dict[str, Any]]:
+ """
+ 生成离心操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 离心容器名称
+ speed: 离心速度 (rpm)
+ time: 离心时间 (秒)
+ temp: 温度 (摄氏度,可选)
+
+ Returns:
+ List[Dict[str, Any]]: 离心操作的动作序列
+
+ Raises:
+ ValueError: 当找不到离心机设备时抛出异常
+
+ Examples:
+ centrifuge_protocol = generate_centrifuge_protocol(G, "reactor", 5000, 300, 4.0)
+ """
+ action_sequence = []
+
+ # 查找离心机设备
+ centrifuge_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_centrifuge']
+
+ if not centrifuge_nodes:
+ raise ValueError("没有找到可用的离心机设备")
+
+ # 使用第一个可用的离心机
+ centrifuge_id = centrifuge_nodes[0]
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ # 执行离心操作
+ action_sequence.append({
+ "device_id": centrifuge_id,
+ "action_name": "centrifuge",
+ "action_kwargs": {
+ "vessel": vessel,
+ "speed": speed,
+ "time": time,
+ "temp": temp
+ }
+ })
+
+ return action_sequence
+
+
+def generate_multi_step_centrifuge_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ steps: List[Dict[str, Any]]
+) -> List[Dict[str, Any]]:
+ """
+ 生成多步骤离心操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 离心容器名称
+ steps: 离心步骤列表,每个步骤包含 speed, time, temp 参数
+
+ Returns:
+ List[Dict[str, Any]]: 多步骤离心操作的动作序列
+
+ Examples:
+ steps = [
+ {"speed": 1000, "time": 60, "temp": 4.0}, # 低速预离心
+ {"speed": 12000, "time": 600, "temp": 4.0} # 高速离心
+ ]
+ protocol = generate_multi_step_centrifuge_protocol(G, "reactor", steps)
+ """
+ action_sequence = []
+
+ # 查找离心机设备
+ centrifuge_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_centrifuge']
+
+ if not centrifuge_nodes:
+ raise ValueError("没有找到可用的离心机设备")
+
+ centrifuge_id = centrifuge_nodes[0]
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ # 执行每个离心步骤
+ for i, step in enumerate(steps):
+ speed = step.get('speed', 5000)
+ time = step.get('time', 300)
+ temp = step.get('temp', 25.0)
+
+ action_sequence.append({
+ "device_id": centrifuge_id,
+ "action_name": "centrifuge",
+ "action_kwargs": {
+ "vessel": vessel,
+ "speed": speed,
+ "time": time,
+ "temp": temp
+ }
+ })
+
+ # 步骤间等待时间(除了最后一步)
+ if i < len(steps) - 1:
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": 3}
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/clean_vessel_protocol.py b/unilabos/compile/clean_vessel_protocol.py
new file mode 100644
index 00000000..d8a746fe
--- /dev/null
+++ b/unilabos/compile/clean_vessel_protocol.py
@@ -0,0 +1,126 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_clean_vessel_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ solvent: str,
+ volume: float,
+ temp: float,
+ repeats: int = 1
+) -> List[Dict[str, Any]]:
+ """
+ 生成容器清洗操作的协议序列,使用transfer操作实现清洗
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 要清洗的容器名称
+ solvent: 用于清洗容器的溶剂名称
+ volume: 清洗溶剂的体积
+ temp: 清洗时的温度
+ repeats: 清洗操作的重复次数,默认为 1
+
+ Returns:
+ List[Dict[str, Any]]: 容器清洗操作的动作序列
+
+ Raises:
+ ValueError: 当找不到必要的设备时抛出异常
+
+ Examples:
+ clean_vessel_protocol = generate_clean_vessel_protocol(G, "reactor", "water", 50.0, 25.0, 2)
+ """
+ action_sequence = []
+
+ # 查找虚拟转移泵设备进行清洗操作
+ pump_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_transfer_pump']
+
+ if not pump_nodes:
+ raise ValueError("没有找到可用的转移泵设备进行容器清洗")
+
+ pump_id = pump_nodes[0]
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ # 查找溶剂容器
+ solvent_vessel = f"flask_{solvent}"
+ if solvent_vessel not in G.nodes():
+ raise ValueError(f"溶剂容器 {solvent_vessel} 不存在于图中")
+
+ # 查找废液容器
+ waste_vessel = "flask_waste"
+ if waste_vessel not in G.nodes():
+ raise ValueError(f"废液容器 {waste_vessel} 不存在于图中")
+
+ # 查找加热设备(如果需要加热)
+ heatchill_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_heatchill']
+
+ heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
+
+ # 执行清洗操作序列
+ for repeat in range(repeats):
+ # 1. 如果需要加热,先设置温度
+ if temp > 25.0 and heatchill_id:
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_start",
+ "action_kwargs": {
+ "vessel": vessel,
+ "temp": temp,
+ "purpose": "cleaning"
+ }
+ })
+
+ # 2. 使用transfer操作:从溶剂容器转移清洗溶剂到目标容器
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": solvent_vessel,
+ "to_vessel": vessel,
+ "volume": volume,
+ "amount": f"cleaning with {solvent} - cycle {repeat + 1}",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 3. 等待清洗作用时间(可选,可以添加wait操作)
+ # 这里省略wait操作,直接进行下一步
+
+ # 4. 将清洗后的溶剂转移到废液容器
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": vessel,
+ "to_vessel": waste_vessel,
+ "volume": volume,
+ "amount": f"waste from cleaning {vessel} - cycle {repeat + 1}",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 5. 如果加热了,停止加热
+ if temp > 25.0 and heatchill_id:
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_stop",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/dissolve_protocol.py b/unilabos/compile/dissolve_protocol.py
new file mode 100644
index 00000000..eda88cd6
--- /dev/null
+++ b/unilabos/compile/dissolve_protocol.py
@@ -0,0 +1,162 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_dissolve_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ solvent: str,
+ volume: float,
+ amount: str = "",
+ temp: float = 25.0,
+ time: float = 0.0,
+ stir_speed: float = 0.0
+) -> List[Dict[str, Any]]:
+ """
+ 生成溶解操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 装有要溶解物质的容器名称
+ solvent: 用于溶解物质的溶剂名称
+ volume: 溶剂的体积,可选参数
+ amount: 要溶解物质的量,可选参数
+ temp: 溶解时的温度,可选参数
+ time: 溶解的时间,可选参数
+ stir_speed: 搅拌速度,可选参数
+
+ Returns:
+ List[Dict[str, Any]]: 溶解操作的动作序列
+
+ Raises:
+ ValueError: 当找不到必要的设备时抛出异常
+
+ Examples:
+ dissolve_protocol = generate_dissolve_protocol(G, "reactor", "water", 100.0, "NaCl 5g", 60.0, 300.0, 500.0)
+ """
+ action_sequence = []
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ # 查找溶剂容器
+ solvent_vessel = f"flask_{solvent}"
+ if solvent_vessel not in G.nodes():
+ # 如果没有找到特定溶剂容器,查找可用的源容器
+ available_vessels = [node for node in G.nodes()
+ if node.startswith('flask_') and
+ G.nodes[node].get('type') == 'container']
+ if available_vessels:
+ solvent_vessel = available_vessels[0]
+ else:
+ raise ValueError(f"没有找到溶剂容器 {solvent}")
+
+ # 查找转移泵设备
+ pump_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_transfer_pump']
+
+ if not pump_nodes:
+ raise ValueError("没有找到可用的转移泵设备")
+
+ pump_id = pump_nodes[0]
+
+ # 查找加热设备(如果需要加热)
+ heatchill_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_heatchill']
+
+ heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
+
+ # 查找搅拌设备(如果需要搅拌)
+ stirrer_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_stirrer']
+
+ stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
+
+ # 步骤1:如果需要加热,先设置温度
+ if temp > 25.0 and heatchill_id:
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_start",
+ "action_kwargs": {
+ "vessel": vessel,
+ "temp": temp,
+ "purpose": "dissolution"
+ }
+ })
+
+ # 步骤2:添加溶剂到容器中
+ if volume > 0:
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": solvent_vessel,
+ "to_vessel": vessel,
+ "volume": volume,
+ "amount": f"solvent {solvent} for dissolving {amount}",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 步骤3:如果需要搅拌,开始搅拌
+ if stir_speed > 0 and stirrer_id:
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "start_stir",
+ "action_kwargs": {
+ "vessel": vessel,
+ "stir_speed": stir_speed,
+ "purpose": f"dissolving {amount} in {solvent}"
+ }
+ })
+
+ # 步骤4:如果指定了溶解时间,等待溶解完成
+ if time > 0:
+ # 这里可以添加等待操作,或者使用搅拌操作来模拟溶解时间
+ if stirrer_id and stir_speed > 0:
+ # 停止之前的搅拌,使用定时搅拌
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "stop_stir",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ # 开始定时搅拌
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "stir",
+ "action_kwargs": {
+ "stir_time": time,
+ "stir_speed": stir_speed,
+ "settling_time": 10.0 # 搅拌后静置10秒
+ }
+ })
+
+ # 步骤5:如果加热了,停止加热
+ if temp > 25.0 and heatchill_id:
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_stop",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ # 步骤6:如果还在搅拌,停止搅拌(除非已经用定时搅拌)
+ if stir_speed > 0 and stirrer_id and time == 0:
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "stop_stir",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/evacuateandrefill_protocol.py b/unilabos/compile/evacuateandrefill_protocol.py
index 9cde400f..66e3ca3c 100644
--- a/unilabos/compile/evacuateandrefill_protocol.py
+++ b/unilabos/compile/evacuateandrefill_protocol.py
@@ -69,14 +69,14 @@ def generate_evacuateandrefill_protocol(
"device_id": vacuum_backbone["pump"],
"action_name": "set_status",
"action_kwargs": {
- "command": "ON"
+ "string": "ON"
}
},
{
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
- "command": "OFF"
+ "string": "OFF"
}
}
])
@@ -106,14 +106,14 @@ def generate_evacuateandrefill_protocol(
"device_id": vacuum_backbone["pump"],
"action_name": "set_status",
"action_kwargs": {
- "command": "OFF"
+ "string": "OFF"
}
},
{
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
- "command": "ON"
+ "string": "ON"
}
}
])
@@ -125,7 +125,7 @@ def generate_evacuateandrefill_protocol(
"device_id": vacuum_backbone["gas"],
"action_name": "set_status",
"action_kwargs": {
- "command": "OFF"
+ "string": "OFF"
}
}
)
diff --git a/unilabos/compile/filter_protocol.py b/unilabos/compile/filter_protocol.py
new file mode 100644
index 00000000..2847c5dc
--- /dev/null
+++ b/unilabos/compile/filter_protocol.py
@@ -0,0 +1,70 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_filter_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ filtrate_vessel: str = "",
+ stir: bool = False,
+ stir_speed: float = 300.0,
+ temp: float = 25.0,
+ continue_heatchill: bool = False,
+ volume: float = 0.0
+) -> List[Dict[str, Any]]:
+ """
+ 生成过滤操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 过滤容器
+ filtrate_vessel: 滤液容器(可选)
+ stir: 是否搅拌
+ stir_speed: 搅拌速度(可选)
+ temp: 温度(可选,摄氏度)
+ continue_heatchill: 是否继续加热冷却
+ volume: 过滤体积(可选)
+
+ Returns:
+ List[Dict[str, Any]]: 过滤操作的动作序列
+
+ Raises:
+ ValueError: 当找不到过滤设备时抛出异常
+
+ Examples:
+ filter_protocol = generate_filter_protocol(G, "reactor", "filtrate_vessel", stir=True, volume=100.0)
+ """
+ action_sequence = []
+
+ # 查找过滤设备
+ filter_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_filter']
+
+ if not filter_nodes:
+ raise ValueError("没有找到可用的过滤设备")
+
+ # 使用第一个可用的过滤器
+ filter_id = filter_nodes[0]
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"过滤容器 {vessel} 不存在于图中")
+
+ if filtrate_vessel and filtrate_vessel not in G.nodes():
+ raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
+
+ # 执行过滤操作
+ action_sequence.append({
+ "device_id": filter_id,
+ "action_name": "filter_sample",
+ "action_kwargs": {
+ "vessel": vessel,
+ "filtrate_vessel": filtrate_vessel,
+ "stir": stir,
+ "stir_speed": stir_speed,
+ "temp": temp,
+ "continue_heatchill": continue_heatchill,
+ "volume": volume
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/filter_through_protocol.py b/unilabos/compile/filter_through_protocol.py
new file mode 100644
index 00000000..009756b1
--- /dev/null
+++ b/unilabos/compile/filter_through_protocol.py
@@ -0,0 +1,150 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_filter_through_protocol(
+ G: nx.DiGraph,
+ from_vessel: str,
+ to_vessel: str,
+ filter_through: str,
+ eluting_solvent: str = "",
+ eluting_volume: float = 0.0,
+ eluting_repeats: int = 0,
+ residence_time: float = 0.0
+) -> List[Dict[str, Any]]:
+ """
+ 生成通过过滤介质过滤的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ from_vessel: 源容器的名称,即物质起始所在的容器
+ to_vessel: 目标容器的名称,物质过滤后要到达的容器
+ filter_through: 过滤时所通过的介质,如滤纸、柱子等
+ eluting_solvent: 洗脱溶剂的名称,可选参数
+ eluting_volume: 洗脱溶剂的体积,可选参数
+ eluting_repeats: 洗脱操作的重复次数,默认为 0
+ residence_time: 物质在过滤介质中的停留时间,可选参数
+
+ Returns:
+ List[Dict[str, Any]]: 过滤操作的动作序列
+
+ Raises:
+ ValueError: 当找不到必要的设备时抛出异常
+
+ Examples:
+ filter_through_protocol = generate_filter_through_protocol(
+ G, "reactor", "collection_flask", "celite", "ethanol", 50.0, 2, 60.0
+ )
+ """
+ action_sequence = []
+
+ # 验证容器是否存在
+ if from_vessel not in G.nodes():
+ raise ValueError(f"源容器 {from_vessel} 不存在于图中")
+
+ if to_vessel not in G.nodes():
+ raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
+
+ # 查找转移泵设备(用于液体转移)
+ pump_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_transfer_pump']
+
+ if not pump_nodes:
+ raise ValueError("没有找到可用的转移泵设备")
+
+ pump_id = pump_nodes[0]
+
+ # 查找过滤设备(可选,如果有专门的过滤设备)
+ filter_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_filter']
+
+ filter_id = filter_nodes[0] if filter_nodes else None
+
+ # 查找洗脱溶剂容器(如果需要洗脱)
+ eluting_vessel = None
+ if eluting_solvent and eluting_volume > 0:
+ eluting_vessel = f"flask_{eluting_solvent}"
+ if eluting_vessel not in G.nodes():
+ # 查找可用的溶剂容器
+ available_vessels = [node for node in G.nodes()
+ if node.startswith('flask_') and
+ G.nodes[node].get('type') == 'container']
+ if available_vessels:
+ eluting_vessel = available_vessels[0]
+ else:
+ raise ValueError(f"没有找到洗脱溶剂容器 {eluting_solvent}")
+
+ # 步骤1:将样品从源容器转移到过滤装置(模拟通过过滤介质)
+ # 这里我们将过滤过程分解为多个转移步骤来模拟通过介质的过程
+
+ # 首先转移样品(模拟样品通过过滤介质)
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": from_vessel,
+ "to_vessel": to_vessel,
+ "volume": 0.0, # 转移所有液体,体积由系统确定
+ "amount": f"通过 {filter_through} 过滤",
+ "time": residence_time if residence_time > 0 else 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": True # 通过过滤介质可能涉及固体分离
+ }
+ })
+
+ # 步骤2:如果有专门的过滤设备,使用过滤设备处理
+ if filter_id:
+ action_sequence.append({
+ "device_id": filter_id,
+ "action_name": "filter_sample",
+ "action_kwargs": {
+ "vessel": to_vessel,
+ "filtrate_vessel": to_vessel,
+ "stir": False,
+ "stir_speed": 0.0,
+ "temp": 25.0,
+ "continue_heatchill": False,
+ "volume": 0.0
+ }
+ })
+
+ # 步骤3:洗脱操作(如果指定了洗脱溶剂和重复次数)
+ if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel:
+ for repeat in range(eluting_repeats):
+ # 添加洗脱溶剂
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": eluting_vessel,
+ "to_vessel": to_vessel,
+ "volume": eluting_volume,
+ "amount": f"洗脱溶剂 {eluting_solvent} - 第 {repeat + 1} 次",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 如果有过滤设备,再次过滤洗脱液
+ if filter_id:
+ action_sequence.append({
+ "device_id": filter_id,
+ "action_name": "filter_sample",
+ "action_kwargs": {
+ "vessel": to_vessel,
+ "filtrate_vessel": to_vessel,
+ "stir": False,
+ "stir_speed": 0.0,
+ "temp": 25.0,
+ "continue_heatchill": False,
+ "volume": eluting_volume
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/heatchill_protocol.py b/unilabos/compile/heatchill_protocol.py
new file mode 100644
index 00000000..ac8ca172
--- /dev/null
+++ b/unilabos/compile/heatchill_protocol.py
@@ -0,0 +1,117 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_heat_chill_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ temp: float,
+ time: float,
+ stir: bool,
+ stir_speed: float,
+ purpose: str
+) -> List[Dict[str, Any]]:
+ """
+ 生成加热/冷却操作的协议序列 - 严格按照 HeatChill.action
+ """
+ action_sequence = []
+
+ # 查找加热/冷却设备
+ heatchill_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_heatchill']
+
+ if not heatchill_nodes:
+ raise ValueError("没有找到可用的加热/冷却设备")
+
+ heatchill_id = heatchill_nodes[0]
+
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill",
+ "action_kwargs": {
+ "vessel": vessel,
+ "temp": temp,
+ "time": time,
+ "stir": stir,
+ "stir_speed": stir_speed,
+ "purpose": purpose
+ }
+ })
+
+ return action_sequence
+
+
+def generate_heat_chill_start_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ temp: float,
+ purpose: str
+) -> List[Dict[str, Any]]:
+ """
+ 生成开始加热/冷却操作的协议序列 - 严格按照 HeatChillStart.action
+ """
+ action_sequence = []
+
+ heatchill_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_heatchill']
+
+ if not heatchill_nodes:
+ raise ValueError("没有找到可用的加热/冷却设备")
+
+ heatchill_id = heatchill_nodes[0]
+
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_start",
+ "action_kwargs": {
+ "vessel": vessel,
+ "temp": temp,
+ "purpose": purpose
+ }
+ })
+
+ return action_sequence
+
+
+def generate_heat_chill_stop_protocol(
+ G: nx.DiGraph,
+ vessel: str
+) -> List[Dict[str, Any]]:
+ """
+ 生成停止加热/冷却操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 容器名称
+
+ Returns:
+ List[Dict[str, Any]]: 停止加热/冷却操作的动作序列
+ """
+ action_sequence = []
+
+ # 查找加热/冷却设备
+ heatchill_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_heatchill']
+
+ if not heatchill_nodes:
+ raise ValueError("没有找到可用的加热/冷却设备")
+
+ heatchill_id = heatchill_nodes[0]
+
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_stop",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/pump_protocol.py b/unilabos/compile/pump_protocol.py
index 60670286..ffd8efca 100644
--- a/unilabos/compile/pump_protocol.py
+++ b/unilabos/compile/pump_protocol.py
@@ -24,10 +24,27 @@ def generate_pump_protocol(
# 生成泵操作的动作序列
pump_action_sequence = []
- nodes = G.nodes(data=True)
- # 从from_vessel到to_vessel的最短路径
- shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
- print(shortest_path)
+
+ # 检查节点是否存在
+ if from_vessel not in G.nodes:
+ print(f"Warning: Source vessel '{from_vessel}' not found in graph. Skipping.")
+ return []
+
+ if to_vessel not in G.nodes:
+ print(f"Warning: Target vessel '{to_vessel}' not found in graph. Skipping.")
+ return []
+
+ # 检查是否存在路径
+ try:
+ shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
+ except nx.NetworkXNoPath:
+ print(f"Warning: No path from '{from_vessel}' to '{to_vessel}'. Skipping.")
+ return []
+ except nx.NodeNotFound as e:
+ print(f"Warning: Node not found: {e}. Skipping.")
+ return []
+
+ print(f"Shortest path: {shortest_path}")
pump_backbone = shortest_path
if not from_vessel.startswith("pump"):
@@ -35,10 +52,34 @@ def generate_pump_protocol(
if not to_vessel.startswith("pump"):
pump_backbone = pump_backbone[:-1]
+ print(f"Pump backbone: {pump_backbone}")
+
+ # 修复:检查pump_backbone是否为空
+ if not pump_backbone:
+ print(f"Warning: No pumps found in path from '{from_vessel}' to '{to_vessel}'. Skipping.")
+ return []
+
if transfer_flowrate == 0:
transfer_flowrate = flowrate
- min_transfer_volume = min([nodes[pump]["max_volume"] for pump in pump_backbone])
+ # 修复:正确访问节点数据
+ pump_max_volumes = []
+ for pump in pump_backbone:
+ # 直接使用 G.nodes[pump] 来访问节点数据
+ pump_data = G.nodes[pump] if pump in G.nodes else {}
+ # 尝试多种可能的键名,并提供默认值
+ max_vol = pump_data.get('max_volume') or pump_data.get('max_vol') or pump_data.get('volume')
+ if max_vol is None:
+ # 如果是设备节点,尝试从config中获取
+ config = pump_data.get('config', {})
+ max_vol = config.get('max_volume', 25.0)
+ pump_max_volumes.append(float(max_vol))
+
+ if pump_max_volumes:
+ min_transfer_volume = min(pump_max_volumes)
+ else:
+ min_transfer_volume = 25.0 # 默认值
+
repeats = int(np.ceil(volume / min_transfer_volume))
if repeats > 1 and (from_vessel.startswith("pump") or to_vessel.startswith("pump")):
raise ValueError("Cannot transfer volume larger than min_transfer_volume between two pumps.")
@@ -48,84 +89,102 @@ def generate_pump_protocol(
# 生成泵操作的动作序列
for i in range(repeats):
# 单泵依次执行阀指令、活塞指令,将液体吸入与之相连的第一台泵
- if not from_vessel.startswith("pump"):
- pump_action_sequence.extend([
- {
- "device_id": pump_backbone[0],
- "action_name": "set_valve_position",
- "action_kwargs": {
- "command": G.get_edge_data(pump_backbone[0], from_vessel)["port"][pump_backbone[0]]
+ if not from_vessel.startswith("pump") and pump_backbone:
+ # 修复:添加边缘数据检查
+ edge_data = G.get_edge_data(pump_backbone[0], from_vessel)
+ if edge_data and "port" in edge_data:
+ pump_action_sequence.extend([
+ {
+ "device_id": pump_backbone[0],
+ "action_name": "set_valve_position",
+ "action_kwargs": {
+ "command": edge_data["port"][pump_backbone[0]]
+ }
+ },
+ {
+ "device_id": pump_backbone[0],
+ "action_name": "set_position",
+ "action_kwargs": {
+ "position": float(min(volume_left, min_transfer_volume)),
+ "max_velocity": transfer_flowrate
+ }
}
- },
- {
- "device_id": pump_backbone[0],
- "action_name": "set_position",
- "action_kwargs": {
- "position": float(min(volume_left, min_transfer_volume)),
- "max_velocity": transfer_flowrate
- }
- }
- ])
- pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
- for pumpA, pumpB in zip(pump_backbone[:-1], pump_backbone[1:]):
- # 相邻两泵同时切换阀门至连通位置
- pump_action_sequence.append([
- {
- "device_id": pumpA,
- "action_name": "set_valve_position",
- "action_kwargs": {
- "command": G.get_edge_data(pumpA, pumpB)["port"][pumpA]
- }
- },
- {
- "device_id": pumpB,
- "action_name": "set_valve_position",
- "action_kwargs": {
- "command": G.get_edge_data(pumpB, pumpA)["port"][pumpB],
- }
- }
- ])
- # 相邻两泵液体转移:泵A排出液体,泵B吸入液体
- pump_action_sequence.append([
- {
- "device_id": pumpA,
- "action_name": "set_position",
- "action_kwargs": {
- "position": 0.0,
- "max_velocity": transfer_flowrate
- }
- },
- {
- "device_id": pumpB,
- "action_name": "set_position",
- "action_kwargs": {
- "position": float(min(volume_left, min_transfer_volume)),
- "max_velocity": transfer_flowrate
- }
- }
- ])
- pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ ])
+ pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ else:
+ print(f"Warning: No edge data found between {pump_backbone[0]} and {from_vessel}")
- if not to_vessel.startswith("pump"):
+ # 修复:检查pump_backbone长度,避免多泵操作时出错
+ if len(pump_backbone) > 1:
+ for pumpA, pumpB in zip(pump_backbone[:-1], pump_backbone[1:]):
+ # 相邻两泵同时切换阀门至连通位置
+ edge_AB = G.get_edge_data(pumpA, pumpB)
+ edge_BA = G.get_edge_data(pumpB, pumpA)
+
+ if edge_AB and "port" in edge_AB and edge_BA and "port" in edge_BA:
+ pump_action_sequence.append([
+ {
+ "device_id": pumpA,
+ "action_name": "set_valve_position",
+ "action_kwargs": {
+ "command": edge_AB["port"][pumpA]
+ }
+ },
+ {
+ "device_id": pumpB,
+ "action_name": "set_valve_position",
+ "action_kwargs": {
+ "command": edge_BA["port"][pumpB],
+ }
+ }
+ ])
+ # 相邻两泵液体转移:泵A排出液体,泵B吸入液体
+ pump_action_sequence.append([
+ {
+ "device_id": pumpA,
+ "action_name": "set_position",
+ "action_kwargs": {
+ "position": 0.0,
+ "max_velocity": transfer_flowrate
+ }
+ },
+ {
+ "device_id": pumpB,
+ "action_name": "set_position",
+ "action_kwargs": {
+ "position": float(min(volume_left, min_transfer_volume)),
+ "max_velocity": transfer_flowrate
+ }
+ }
+ ])
+ pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ else:
+ print(f"Warning: No edge data found between {pumpA} and {pumpB}")
+
+ if not to_vessel.startswith("pump") and pump_backbone:
# 单泵依次执行阀指令、活塞指令,将最后一台泵液体缓慢加入容器B
- pump_action_sequence.extend([
- {
- "device_id": pump_backbone[-1],
- "action_name": "set_valve_position",
- "action_kwargs": {
- "command": G.get_edge_data(pump_backbone[-1], to_vessel)["port"][pump_backbone[-1]]
+ edge_data = G.get_edge_data(pump_backbone[-1], to_vessel)
+ if edge_data and "port" in edge_data:
+ pump_action_sequence.extend([
+ {
+ "device_id": pump_backbone[-1],
+ "action_name": "set_valve_position",
+ "action_kwargs": {
+ "command": edge_data["port"][pump_backbone[-1]]
+ }
+ },
+ {
+ "device_id": pump_backbone[-1],
+ "action_name": "set_position",
+ "action_kwargs": {
+ "position": 0.0,
+ "max_velocity": flowrate
+ }
}
- },
- {
- "device_id": pump_backbone[-1],
- "action_name": "set_position",
- "action_kwargs": {
- "position": 0.0,
- "max_velocity": flowrate
- }
- }
- ])
- pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ ])
+ pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ else:
+ print(f"Warning: No edge data found between {pump_backbone[-1]} and {to_vessel}")
volume_left -= min_transfer_volume
return pump_action_sequence
@@ -174,18 +233,52 @@ def generate_pump_protocol_with_rinsing(
Examples:
pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 0.1, rinsing_solvent="water")
"""
- air_vessel = "flask_air"
- waste_vessel = f"waste_workup"
+ # 修复:使用实际存在的节点名称
+ air_vessel = "flask_air" # 这个在你的配置中存在
- shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
- pump_backbone = shortest_path[1: -1]
- nodes = G.nodes(data=True)
- min_transfer_volume = float(min([nodes[pump]["max_volume"] for pump in pump_backbone]))
+ # 寻找合适的废料容器,如果没有找到则使用空的容器作为替代
+ waste_vessel = None
+ available_vessels = [node for node in G.nodes if node.startswith("flask_") and node != air_vessel]
+ if available_vessels:
+ # 使用第一个可用的容器作为废料容器
+ waste_vessel = available_vessels[0]
+ print(f"Using {waste_vessel} as waste vessel")
+ else:
+ waste_vessel = "flask_1" # 备用选择
+
+ # 修复:添加路径检查
+ try:
+ shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
+ pump_backbone = shortest_path[1: -1]
+ except (nx.NetworkXNoPath, nx.NodeNotFound) as e:
+ print(f"Warning: Cannot find path from {from_vessel} to {to_vessel}: {e}")
+ return []
+
+ # 修复:正确访问节点数据
+ pump_max_volumes = []
+ for pump in pump_backbone:
+ # 直接使用 G.nodes[pump] 来访问节点数据
+ pump_data = G.nodes[pump] if pump in G.nodes else {}
+ # 尝试多种可能的键名,并提供默认值
+ max_vol = pump_data.get('max_volume') or pump_data.get('max_vol') or pump_data.get('volume')
+ if max_vol is None:
+ # 如果是设备节点,尝试从config中获取
+ config = pump_data.get('config', {})
+ max_vol = config.get('max_volume', 25.0)
+ pump_max_volumes.append(float(max_vol))
+
+ if pump_max_volumes:
+ min_transfer_volume = float(min(pump_max_volumes))
+ else:
+ min_transfer_volume = 25.0 # 默认值
+
if time != 0:
flowrate = transfer_flowrate = volume / time
pump_action_sequence = generate_pump_protocol(G, from_vessel, to_vessel, float(volume), flowrate, transfer_flowrate)
- if rinsing_solvent != "air":
+
+ # 修复:只在需要清洗且相关节点存在时才执行清洗步骤
+ if rinsing_solvent != "air" and pump_backbone:
if "," in rinsing_solvent:
rinsing_solvents = rinsing_solvent.split(",")
assert len(rinsing_solvents) == rinsing_repeats, "Number of rinsing solvents must match number of rinsing repeats."
@@ -194,20 +287,32 @@ def generate_pump_protocol_with_rinsing(
for rinsing_solvent in rinsing_solvents:
solvent_vessel = f"flask_{rinsing_solvent}"
- # 清洗泵
- pump_action_sequence.extend(
- generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate, transfer_flowrate) +
- generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate, transfer_flowrate) +
- generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate, transfer_flowrate)
- )
- # 如果转移的是溶液,第一种冲洗溶剂请选用溶液的溶剂,稀释泵内、转移管道内的溶液。后续冲洗溶剂不需要此操作。
- if rinsing_solvent == rinsing_solvents[0]:
- pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
- pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
- pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
- pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
- pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
- pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
+
+ # 检查溶剂容器是否存在
+ if solvent_vessel not in G.nodes:
+ print(f"Warning: Solvent vessel '{solvent_vessel}' not found in graph. Skipping rinsing step.")
+ continue
+
+ # 清洗泵 - 只有当所有必需的节点都存在且pump_backbone不为空时才执行
+ if pump_backbone and len(pump_backbone) > 0 and waste_vessel in G.nodes:
+ pump_action_sequence.extend(
+ generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate, transfer_flowrate) +
+ generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate, transfer_flowrate) +
+ generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate, transfer_flowrate)
+ )
+
+ # 如果转移的是溶液,第一种冲洗溶剂请选用溶液的溶剂,稀释泵内、转移管道内的溶液。后续冲洗溶剂不需要此操作。
+ if rinsing_solvent == rinsing_solvents[0]:
+ pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
+ pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
+
+ pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
+ pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
+
+ # 最后的空气清洗 - 只有当节点存在时才执行
+ if air_vessel in G.nodes:
+ pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
+ pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
return pump_action_sequence
# End Protocols
diff --git a/unilabos/compile/run_column_protocol.py b/unilabos/compile/run_column_protocol.py
new file mode 100644
index 00000000..5aebc2b6
--- /dev/null
+++ b/unilabos/compile/run_column_protocol.py
@@ -0,0 +1,102 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_run_column_protocol(
+ G: nx.DiGraph,
+ from_vessel: str,
+ to_vessel: str,
+ column: str
+) -> List[Dict[str, Any]]:
+ """
+ 生成柱层析分离的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ from_vessel: 源容器的名称,即样品起始所在的容器
+ to_vessel: 目标容器的名称,分离后的样品要到达的容器
+ column: 所使用的柱子的名称
+
+ Returns:
+ List[Dict[str, Any]]: 柱层析分离操作的动作序列
+
+ Raises:
+ ValueError: 当找不到必要的设备时抛出异常
+
+ Examples:
+ run_column_protocol = generate_run_column_protocol(G, "reactor", "collection_flask", "silica_column")
+ """
+ action_sequence = []
+
+ # 验证容器是否存在
+ if from_vessel not in G.nodes():
+ raise ValueError(f"源容器 {from_vessel} 不存在于图中")
+
+ if to_vessel not in G.nodes():
+ raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
+
+ # 查找转移泵设备(用于样品转移)
+ pump_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_transfer_pump']
+
+ if not pump_nodes:
+ raise ValueError("没有找到可用的转移泵设备")
+
+ pump_id = pump_nodes[0]
+
+ # 查找柱层析设备
+ column_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_column']
+
+ if not column_nodes:
+ raise ValueError("没有找到可用的柱层析设备")
+
+ column_id = column_nodes[0]
+
+ # 步骤1:将样品从源容器转移到柱子上
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": from_vessel,
+ "to_vessel": column_id, # 将样品转移到柱子设备
+ "volume": 0.0, # 转移所有液体,体积由系统确定
+ "amount": f"样品上柱 - 使用 {column}",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 步骤2:运行柱层析分离
+ action_sequence.append({
+ "device_id": column_id,
+ "action_name": "run_column",
+ "action_kwargs": {
+ "from_vessel": from_vessel,
+ "to_vessel": to_vessel,
+ "column": column
+ }
+ })
+
+ # 步骤3:将分离后的产物从柱子转移到目标容器
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": column_id, # 从柱子设备转移
+ "to_vessel": to_vessel,
+ "volume": 0.0, # 转移所有液体,体积由系统确定
+ "amount": f"收集分离产物 - 来自 {column}",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/stir_protocol.py b/unilabos/compile/stir_protocol.py
new file mode 100644
index 00000000..90a207cd
--- /dev/null
+++ b/unilabos/compile/stir_protocol.py
@@ -0,0 +1,137 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_stir_protocol(
+ G: nx.DiGraph,
+ stir_time: float,
+ stir_speed: float,
+ settling_time: float
+) -> List[Dict[str, Any]]:
+ """
+ 生成搅拌操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ stir_time: 搅拌时间 (秒)
+ stir_speed: 搅拌速度 (rpm)
+ settling_time: 沉降时间 (秒)
+
+ Returns:
+ List[Dict[str, Any]]: 搅拌操作的动作序列
+
+ Raises:
+ ValueError: 当找不到搅拌设备时抛出异常
+
+ Examples:
+ stir_protocol = generate_stir_protocol(G, 300.0, 500.0, 60.0)
+ """
+ action_sequence = []
+
+ # 查找搅拌设备
+ stirrer_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_stirrer']
+
+ if not stirrer_nodes:
+ raise ValueError("没有找到可用的搅拌设备")
+
+ # 使用第一个可用的搅拌器
+ stirrer_id = stirrer_nodes[0]
+
+ # 执行搅拌操作
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "stir",
+ "action_kwargs": {
+ "stir_time": stir_time,
+ "stir_speed": stir_speed,
+ "settling_time": settling_time
+ }
+ })
+
+ return action_sequence
+
+
+def generate_start_stir_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ stir_speed: float,
+ purpose: str
+) -> List[Dict[str, Any]]:
+ """
+ 生成开始搅拌操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 搅拌容器
+ stir_speed: 搅拌速度 (rpm)
+ purpose: 搅拌目的
+
+ Returns:
+ List[Dict[str, Any]]: 开始搅拌操作的动作序列
+ """
+ action_sequence = []
+
+ # 查找搅拌设备
+ stirrer_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_stirrer']
+
+ if not stirrer_nodes:
+ raise ValueError("没有找到可用的搅拌设备")
+
+ stirrer_id = stirrer_nodes[0]
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "start_stir",
+ "action_kwargs": {
+ "vessel": vessel,
+ "stir_speed": stir_speed,
+ "purpose": purpose
+ }
+ })
+
+ return action_sequence
+
+
+def generate_stop_stir_protocol(
+ G: nx.DiGraph,
+ vessel: str
+) -> List[Dict[str, Any]]:
+ """
+ 生成停止搅拌操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 搅拌容器
+
+ Returns:
+ List[Dict[str, Any]]: 停止搅拌操作的动作序列
+ """
+ action_sequence = []
+
+ # 查找搅拌设备
+ stirrer_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_stirrer']
+
+ if not stirrer_nodes:
+ raise ValueError("没有找到可用的搅拌设备")
+
+ stirrer_id = stirrer_nodes[0]
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 {vessel} 不存在于图中")
+
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "stop_stir",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/transfer_protocol.py b/unilabos/compile/transfer_protocol.py
new file mode 100644
index 00000000..202b009f
--- /dev/null
+++ b/unilabos/compile/transfer_protocol.py
@@ -0,0 +1,79 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_transfer_protocol(
+ G: nx.DiGraph,
+ from_vessel: str,
+ to_vessel: str,
+ volume: float,
+ amount: str = "",
+ time: float = 0,
+ viscous: bool = False,
+ rinsing_solvent: str = "",
+ rinsing_volume: float = 0.0,
+ rinsing_repeats: int = 0,
+ solid: bool = False
+) -> List[Dict[str, Any]]:
+ """
+ 生成液体转移操作的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ from_vessel: 源容器
+ to_vessel: 目标容器
+ volume: 转移体积 (mL)
+ amount: 数量描述 (可选)
+ time: 转移时间 (秒,可选)
+ viscous: 是否为粘性液体
+ rinsing_solvent: 冲洗溶剂 (可选)
+ rinsing_volume: 冲洗体积 (mL,可选)
+ rinsing_repeats: 冲洗重复次数
+ solid: 是否涉及固体
+
+ Returns:
+ List[Dict[str, Any]]: 转移操作的动作序列
+
+ Raises:
+ ValueError: 当找不到合适的转移设备时抛出异常
+
+ Examples:
+ transfer_protocol = generate_transfer_protocol(G, "flask_1", "reactor", 10.0)
+ """
+ action_sequence = []
+
+ # 查找虚拟转移泵设备用于液体转移 - 修复:应该查找 virtual_transfer_pump
+ pump_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_transfer_pump']
+
+ if not pump_nodes:
+ raise ValueError("没有找到可用的转移泵设备进行液体转移")
+
+ # 使用第一个可用的泵
+ pump_id = pump_nodes[0]
+
+ # 验证容器是否存在
+ if from_vessel not in G.nodes():
+ raise ValueError(f"源容器 {from_vessel} 不存在于图中")
+
+ if to_vessel not in G.nodes():
+ raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
+
+ # 执行液体转移操作 - 参数完全匹配Transfer.action
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": from_vessel,
+ "to_vessel": to_vessel,
+ "volume": volume,
+ "amount": amount,
+ "time": time,
+ "viscous": viscous,
+ "rinsing_solvent": rinsing_solvent,
+ "rinsing_volume": rinsing_volume,
+ "rinsing_repeats": rinsing_repeats,
+ "solid": solid
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/compile/wash_solid_protocol.py b/unilabos/compile/wash_solid_protocol.py
new file mode 100644
index 00000000..a792b8f0
--- /dev/null
+++ b/unilabos/compile/wash_solid_protocol.py
@@ -0,0 +1,216 @@
+from typing import List, Dict, Any
+import networkx as nx
+
+def generate_wash_solid_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ solvent: str,
+ volume: float,
+ filtrate_vessel: str = "",
+ temp: float = 25.0,
+ stir: bool = False,
+ stir_speed: float = 0.0,
+ time: float = 0.0,
+ repeats: int = 1
+) -> List[Dict[str, Any]]:
+ """
+ 生成固体清洗的协议序列
+
+ Args:
+ G: 有向图,节点为设备和容器
+ vessel: 装有固体物质的容器名称
+ solvent: 用于清洗固体的溶剂名称
+ volume: 清洗溶剂的体积
+ filtrate_vessel: 滤液要收集到的容器名称,可选参数
+ temp: 清洗时的温度,可选参数
+ stir: 是否在清洗过程中搅拌,默认为 False
+ stir_speed: 搅拌速度,可选参数
+ time: 清洗的时间,可选参数
+ repeats: 清洗操作的重复次数,默认为 1
+
+ Returns:
+ List[Dict[str, Any]]: 固体清洗操作的动作序列
+
+ Raises:
+ ValueError: 当找不到必要的设备时抛出异常
+
+ Examples:
+ wash_solid_protocol = generate_wash_solid_protocol(
+ G, "reactor", "ethanol", 100.0, "waste_flask", 60.0, True, 300.0, 600.0, 3
+ )
+ """
+ action_sequence = []
+
+ # 验证容器是否存在
+ if vessel not in G.nodes():
+ raise ValueError(f"固体容器 {vessel} 不存在于图中")
+
+ if filtrate_vessel and filtrate_vessel not in G.nodes():
+ raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
+
+ # 查找转移泵设备(用于添加溶剂和转移滤液)
+ pump_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_transfer_pump']
+
+ if not pump_nodes:
+ raise ValueError("没有找到可用的转移泵设备")
+
+ pump_id = pump_nodes[0]
+
+ # 查找加热设备(如果需要加热)
+ heatchill_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_heatchill']
+
+ heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
+
+ # 查找搅拌设备(如果需要搅拌)
+ stirrer_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_stirrer']
+
+ stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
+
+ # 查找过滤设备(用于分离固体和滤液)
+ filter_nodes = [node for node in G.nodes()
+ if G.nodes[node].get('class') == 'virtual_filter']
+
+ filter_id = filter_nodes[0] if filter_nodes else None
+
+ # 查找溶剂容器
+ solvent_vessel = f"flask_{solvent}"
+ if solvent_vessel not in G.nodes():
+ # 如果没有找到特定溶剂容器,查找可用的源容器
+ available_vessels = [node for node in G.nodes()
+ if node.startswith('flask_') and
+ G.nodes[node].get('type') == 'container']
+ if available_vessels:
+ solvent_vessel = available_vessels[0]
+ else:
+ raise ValueError(f"没有找到溶剂容器 {solvent}")
+
+ # 如果没有指定滤液容器,使用废液容器
+ if not filtrate_vessel:
+ waste_vessels = [node for node in G.nodes()
+ if 'waste' in node.lower() and
+ G.nodes[node].get('type') == 'container']
+ filtrate_vessel = waste_vessels[0] if waste_vessels else "waste_flask"
+
+ # 重复清洗操作
+ for repeat in range(repeats):
+ repeat_num = repeat + 1
+
+ # 步骤1:如果需要加热,先设置温度
+ if temp > 25.0 and heatchill_id:
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_start",
+ "action_kwargs": {
+ "vessel": vessel,
+ "temp": temp,
+ "purpose": f"固体清洗 - 第 {repeat_num} 次"
+ }
+ })
+
+ # 步骤2:添加清洗溶剂到固体容器
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": solvent_vessel,
+ "to_vessel": vessel,
+ "volume": volume,
+ "amount": f"清洗溶剂 {solvent} - 第 {repeat_num} 次",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 步骤3:如果需要搅拌,开始搅拌
+ if stir and stir_speed > 0 and stirrer_id:
+ if time > 0:
+ # 定时搅拌
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "stir",
+ "action_kwargs": {
+ "stir_time": time,
+ "stir_speed": stir_speed,
+ "settling_time": 30.0 # 搅拌后静置30秒
+ }
+ })
+ else:
+ # 开始搅拌(需要手动停止)
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "start_stir",
+ "action_kwargs": {
+ "vessel": vessel,
+ "stir_speed": stir_speed,
+ "purpose": f"固体清洗搅拌 - 第 {repeat_num} 次"
+ }
+ })
+
+ # 步骤4:如果指定了清洗时间但没有搅拌,等待清洗时间
+ if time > 0 and (not stir or stir_speed == 0):
+ # 这里可以添加等待操作,暂时跳过
+ pass
+
+ # 步骤5:如果有搅拌且没有定时,停止搅拌
+ if stir and stir_speed > 0 and time == 0 and stirrer_id:
+ action_sequence.append({
+ "device_id": stirrer_id,
+ "action_name": "stop_stir",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ # 步骤6:过滤分离固体和滤液
+ if filter_id:
+ action_sequence.append({
+ "device_id": filter_id,
+ "action_name": "filter_sample",
+ "action_kwargs": {
+ "vessel": vessel,
+ "filtrate_vessel": filtrate_vessel,
+ "stir": False,
+ "stir_speed": 0.0,
+ "temp": temp,
+ "continue_heatchill": temp > 25.0,
+ "volume": volume
+ }
+ })
+ else:
+ # 没有专门的过滤设备,使用转移泵模拟过滤过程
+ # 将滤液转移到滤液容器
+ action_sequence.append({
+ "device_id": pump_id,
+ "action_name": "transfer",
+ "action_kwargs": {
+ "from_vessel": vessel,
+ "to_vessel": filtrate_vessel,
+ "volume": volume,
+ "amount": f"转移滤液 - 第 {repeat_num} 次清洗",
+ "time": 0.0,
+ "viscous": False,
+ "rinsing_solvent": "",
+ "rinsing_volume": 0.0,
+ "rinsing_repeats": 0,
+ "solid": False
+ }
+ })
+
+ # 步骤7:如果加热了,停止加热(在最后一次清洗后)
+ if temp > 25.0 and heatchill_id and repeat_num == repeats:
+ action_sequence.append({
+ "device_id": heatchill_id,
+ "action_name": "heat_chill_stop",
+ "action_kwargs": {
+ "vessel": vessel
+ }
+ })
+
+ return action_sequence
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/config/initial_positions.yaml b/unilabos/device_mesh/devices/arm_slider/config/initial_positions.yaml
new file mode 100644
index 00000000..94fb9f55
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/initial_positions.yaml
@@ -0,0 +1,9 @@
+# Default initial positions for full_dev's ros2_control fake system
+
+initial_positions:
+ arm_base_joint: 0
+ arm_link_1_joint: 0
+ arm_link_2_joint: 0
+ arm_link_3_joint: 0
+ gripper_base_joint: 0
+ gripper_right_joint: 0.03
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/config/joint_limits.yaml b/unilabos/device_mesh/devices/arm_slider/config/joint_limits.yaml
new file mode 100644
index 00000000..d4dffc37
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/joint_limits.yaml
@@ -0,0 +1,40 @@
+# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
+
+# For beginners, we downscale velocity and acceleration limits.
+# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
+default_velocity_scaling_factor: 0.1
+default_acceleration_scaling_factor: 0.1
+
+# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
+# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
+joint_limits:
+ arm_base_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ arm_link_1_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ arm_link_2_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ arm_link_3_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ gripper_base_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ gripper_right_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/config/kinematics.yaml b/unilabos/device_mesh/devices/arm_slider/config/kinematics.yaml
new file mode 100644
index 00000000..c9a5d608
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/kinematics.yaml
@@ -0,0 +1,4 @@
+arm:
+ kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
+ kinematics_solver_search_resolution: 0.0050000000000000001
+ kinematics_solver_timeout: 0.0050000000000000001
diff --git a/unilabos/device_mesh/devices/arm_slider/config/macro.ros2_control.xacro b/unilabos/device_mesh/devices/arm_slider/config/macro.ros2_control.xacro
new file mode 100644
index 00000000..026d977d
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/macro.ros2_control.xacro
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+ mock_components/GenericSystem
+
+
+
+
+ ${initial_positions['arm_base_joint']}
+
+
+
+
+
+
+ ${initial_positions['arm_link_1_joint']}
+
+
+
+
+
+
+ ${initial_positions['arm_link_2_joint']}
+
+
+
+
+
+
+ ${initial_positions['arm_link_3_joint']}
+
+
+
+
+
+
+ ${initial_positions['gripper_base_joint']}
+
+
+
+
+
+
+ ${initial_positions['gripper_right_joint']}
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/arm_slider/config/macro.srdf.xacro b/unilabos/device_mesh/devices/arm_slider/config/macro.srdf.xacro
new file mode 100644
index 00000000..9c3ef9d4
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/macro.srdf.xacro
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/arm_slider/config/move_group.json b/unilabos/device_mesh/devices/arm_slider/config/move_group.json
new file mode 100644
index 00000000..5be9ad34
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/move_group.json
@@ -0,0 +1,14 @@
+{
+ "arm":
+ {
+ "joint_names": [
+ "arm_base_joint",
+ "arm_link_1_joint",
+ "arm_link_2_joint",
+ "arm_link_3_joint",
+ "gripper_base_joint"
+ ],
+ "base_link_name": "device_link",
+ "end_effector_name": "gripper_base"
+ }
+}
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/config/moveit_controllers.yaml b/unilabos/device_mesh/devices/arm_slider/config/moveit_controllers.yaml
new file mode 100644
index 00000000..70a1b55d
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/moveit_controllers.yaml
@@ -0,0 +1,29 @@
+# MoveIt uses this configuration for controller management
+
+moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
+
+moveit_simple_controller_manager:
+ controller_names:
+ - arm_controller
+ - gripper_controller
+
+ arm_controller:
+ type: FollowJointTrajectory
+ action_ns: follow_joint_trajectory
+ default: true
+ joints:
+ - arm_base_joint
+ - arm_link_1_joint
+ - arm_link_2_joint
+ - arm_link_3_joint
+ - gripper_base_joint
+ action_ns: follow_joint_trajectory
+ default: true
+ gripper_controller:
+ type: FollowJointTrajectory
+ action_ns: follow_joint_trajectory
+ default: true
+ joints:
+ - gripper_right_joint
+ action_ns: follow_joint_trajectory
+ default: true
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/config/moveit_planners.yaml b/unilabos/device_mesh/devices/arm_slider/config/moveit_planners.yaml
new file mode 100644
index 00000000..8560e1cb
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/moveit_planners.yaml
@@ -0,0 +1,2 @@
+planner_configs:
+ - ompl_interface/OMPLPlanner
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/config/pilz_cartesian_limits.yaml b/unilabos/device_mesh/devices/arm_slider/config/pilz_cartesian_limits.yaml
new file mode 100644
index 00000000..b2997caf
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/pilz_cartesian_limits.yaml
@@ -0,0 +1,6 @@
+# Limits for the Pilz planner
+cartesian_limits:
+ max_trans_vel: 1.0
+ max_trans_acc: 2.25
+ max_trans_dec: -5.0
+ max_rot_vel: 1.57
diff --git a/unilabos/device_mesh/devices/arm_slider/config/ros2_controllers.yaml b/unilabos/device_mesh/devices/arm_slider/config/ros2_controllers.yaml
new file mode 100644
index 00000000..9c68cbf1
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/config/ros2_controllers.yaml
@@ -0,0 +1,39 @@
+# This config file is used by ros2_control
+controller_manager:
+ ros__parameters:
+ update_rate: 100 # Hz
+
+ arm_controller:
+ type: joint_trajectory_controller/JointTrajectoryController
+
+
+ gripper_controller:
+ type: joint_trajectory_controller/JointTrajectoryController
+
+
+ joint_state_broadcaster:
+ type: joint_state_broadcaster/JointStateBroadcaster
+
+arm_controller:
+ ros__parameters:
+ joints:
+ - arm_base_joint
+ - arm_link_1_joint
+ - arm_link_2_joint
+ - arm_link_3_joint
+ - gripper_base_joint
+ command_interfaces:
+ - position
+ state_interfaces:
+ - position
+ - velocity
+
+gripper_controller:
+ ros__parameters:
+ joints:
+ - gripper_right_joint
+ command_interfaces:
+ - position
+ state_interfaces:
+ - position
+ - velocity
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/joint_limit.yaml b/unilabos/device_mesh/devices/arm_slider/joint_limit.yaml
new file mode 100644
index 00000000..b1412620
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/joint_limit.yaml
@@ -0,0 +1,44 @@
+joint_limits:
+
+ arm_base_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 1.5
+
+ arm_link_1_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 0.6
+
+ arm_link_2_joint:
+ effort: 50
+ velocity: 1.0
+ lower: !degrees -95
+ upper: !degrees 95
+
+ arm_link_3_joint:
+ effort: 50
+ velocity: 1.0
+ lower: !degrees -195
+ upper: !degrees 195
+
+ gripper_base_joint:
+ effort: 50
+ velocity: 1.0
+ lower: !degrees -95
+ upper: !degrees 95
+
+
+ gripper_right_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 0.03
+
+ gripper_left_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 0.03
diff --git a/unilabos/device_mesh/devices/arm_slider/macro_device.xacro b/unilabos/device_mesh/devices/arm_slider/macro_device.xacro
new file mode 100644
index 00000000..871229d8
--- /dev/null
+++ b/unilabos/device_mesh/devices/arm_slider/macro_device.xacro
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/arm_base.STL b/unilabos/device_mesh/devices/arm_slider/meshes/arm_base.STL
new file mode 100644
index 00000000..804e1c69
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/arm_base.STL differ
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_1.STL b/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_1.STL
new file mode 100644
index 00000000..dd1d7c14
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_1.STL differ
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_2.STL b/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_2.STL
new file mode 100644
index 00000000..6042fa0f
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_2.STL differ
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_3.STL b/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_3.STL
new file mode 100644
index 00000000..e8510fbc
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/arm_link_3.STL differ
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/arm_slideway.STL b/unilabos/device_mesh/devices/arm_slider/meshes/arm_slideway.STL
new file mode 100644
index 00000000..65737ab5
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/arm_slideway.STL differ
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/gripper_base.STL b/unilabos/device_mesh/devices/arm_slider/meshes/gripper_base.STL
new file mode 100644
index 00000000..5de88d0c
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/gripper_base.STL differ
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/gripper_left.STL b/unilabos/device_mesh/devices/arm_slider/meshes/gripper_left.STL
new file mode 100644
index 00000000..0a5fd526
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/gripper_left.STL differ
diff --git a/unilabos/device_mesh/devices/arm_slider/meshes/gripper_right.STL b/unilabos/device_mesh/devices/arm_slider/meshes/gripper_right.STL
new file mode 100644
index 00000000..0c5ac69f
Binary files /dev/null and b/unilabos/device_mesh/devices/arm_slider/meshes/gripper_right.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/initial_positions.yaml b/unilabos/device_mesh/devices/benyao_arm/config/initial_positions.yaml
new file mode 100644
index 00000000..94fb9f55
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/initial_positions.yaml
@@ -0,0 +1,9 @@
+# Default initial positions for full_dev's ros2_control fake system
+
+initial_positions:
+ arm_base_joint: 0
+ arm_link_1_joint: 0
+ arm_link_2_joint: 0
+ arm_link_3_joint: 0
+ gripper_base_joint: 0
+ gripper_right_joint: 0.03
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/joint_limits.yaml b/unilabos/device_mesh/devices/benyao_arm/config/joint_limits.yaml
new file mode 100644
index 00000000..d4dffc37
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/joint_limits.yaml
@@ -0,0 +1,40 @@
+# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
+
+# For beginners, we downscale velocity and acceleration limits.
+# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
+default_velocity_scaling_factor: 0.1
+default_acceleration_scaling_factor: 0.1
+
+# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
+# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
+joint_limits:
+ arm_base_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ arm_link_1_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ arm_link_2_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ arm_link_3_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ gripper_base_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ gripper_right_joint:
+ has_velocity_limits: true
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/kinematics.yaml b/unilabos/device_mesh/devices/benyao_arm/config/kinematics.yaml
new file mode 100644
index 00000000..c9a5d608
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/kinematics.yaml
@@ -0,0 +1,4 @@
+arm:
+ kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
+ kinematics_solver_search_resolution: 0.0050000000000000001
+ kinematics_solver_timeout: 0.0050000000000000001
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/macro.ros2_control.xacro b/unilabos/device_mesh/devices/benyao_arm/config/macro.ros2_control.xacro
new file mode 100644
index 00000000..70f8634f
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/macro.ros2_control.xacro
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+ mock_components/GenericSystem
+
+
+
+
+ ${initial_positions['arm_base_joint']}
+
+
+
+
+
+
+ ${initial_positions['arm_link_1_joint']}
+
+
+
+
+
+
+ ${initial_positions['arm_link_2_joint']}
+
+
+
+
+
+
+ ${initial_positions['arm_link_3_joint']}
+
+
+
+
+
+
+ ${initial_positions['gripper_base_joint']}
+
+
+
+
+
+
+ ${initial_positions['gripper_right_joint']}
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/macro.srdf.xacro b/unilabos/device_mesh/devices/benyao_arm/config/macro.srdf.xacro
new file mode 100644
index 00000000..aeb56777
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/macro.srdf.xacro
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/move_group.json b/unilabos/device_mesh/devices/benyao_arm/config/move_group.json
new file mode 100644
index 00000000..5be9ad34
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/move_group.json
@@ -0,0 +1,14 @@
+{
+ "arm":
+ {
+ "joint_names": [
+ "arm_base_joint",
+ "arm_link_1_joint",
+ "arm_link_2_joint",
+ "arm_link_3_joint",
+ "gripper_base_joint"
+ ],
+ "base_link_name": "device_link",
+ "end_effector_name": "gripper_base"
+ }
+}
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/moveit_controllers.yaml b/unilabos/device_mesh/devices/benyao_arm/config/moveit_controllers.yaml
new file mode 100644
index 00000000..70a1b55d
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/moveit_controllers.yaml
@@ -0,0 +1,29 @@
+# MoveIt uses this configuration for controller management
+
+moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
+
+moveit_simple_controller_manager:
+ controller_names:
+ - arm_controller
+ - gripper_controller
+
+ arm_controller:
+ type: FollowJointTrajectory
+ action_ns: follow_joint_trajectory
+ default: true
+ joints:
+ - arm_base_joint
+ - arm_link_1_joint
+ - arm_link_2_joint
+ - arm_link_3_joint
+ - gripper_base_joint
+ action_ns: follow_joint_trajectory
+ default: true
+ gripper_controller:
+ type: FollowJointTrajectory
+ action_ns: follow_joint_trajectory
+ default: true
+ joints:
+ - gripper_right_joint
+ action_ns: follow_joint_trajectory
+ default: true
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/moveit_planners.yaml b/unilabos/device_mesh/devices/benyao_arm/config/moveit_planners.yaml
new file mode 100644
index 00000000..8560e1cb
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/moveit_planners.yaml
@@ -0,0 +1,2 @@
+planner_configs:
+ - ompl_interface/OMPLPlanner
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/pilz_cartesian_limits.yaml b/unilabos/device_mesh/devices/benyao_arm/config/pilz_cartesian_limits.yaml
new file mode 100644
index 00000000..b2997caf
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/pilz_cartesian_limits.yaml
@@ -0,0 +1,6 @@
+# Limits for the Pilz planner
+cartesian_limits:
+ max_trans_vel: 1.0
+ max_trans_acc: 2.25
+ max_trans_dec: -5.0
+ max_rot_vel: 1.57
diff --git a/unilabos/device_mesh/devices/benyao_arm/config/ros2_controllers.yaml b/unilabos/device_mesh/devices/benyao_arm/config/ros2_controllers.yaml
new file mode 100644
index 00000000..9c68cbf1
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/config/ros2_controllers.yaml
@@ -0,0 +1,39 @@
+# This config file is used by ros2_control
+controller_manager:
+ ros__parameters:
+ update_rate: 100 # Hz
+
+ arm_controller:
+ type: joint_trajectory_controller/JointTrajectoryController
+
+
+ gripper_controller:
+ type: joint_trajectory_controller/JointTrajectoryController
+
+
+ joint_state_broadcaster:
+ type: joint_state_broadcaster/JointStateBroadcaster
+
+arm_controller:
+ ros__parameters:
+ joints:
+ - arm_base_joint
+ - arm_link_1_joint
+ - arm_link_2_joint
+ - arm_link_3_joint
+ - gripper_base_joint
+ command_interfaces:
+ - position
+ state_interfaces:
+ - position
+ - velocity
+
+gripper_controller:
+ ros__parameters:
+ joints:
+ - gripper_right_joint
+ command_interfaces:
+ - position
+ state_interfaces:
+ - position
+ - velocity
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/benyao_arm/joint_limit.yaml b/unilabos/device_mesh/devices/benyao_arm/joint_limit.yaml
new file mode 100644
index 00000000..b1412620
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/joint_limit.yaml
@@ -0,0 +1,44 @@
+joint_limits:
+
+ arm_base_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 1.5
+
+ arm_link_1_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 0.6
+
+ arm_link_2_joint:
+ effort: 50
+ velocity: 1.0
+ lower: !degrees -95
+ upper: !degrees 95
+
+ arm_link_3_joint:
+ effort: 50
+ velocity: 1.0
+ lower: !degrees -195
+ upper: !degrees 195
+
+ gripper_base_joint:
+ effort: 50
+ velocity: 1.0
+ lower: !degrees -95
+ upper: !degrees 95
+
+
+ gripper_right_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 0.03
+
+ gripper_left_joint:
+ effort: 50
+ velocity: 1.0
+ lower: 0
+ upper: 0.03
diff --git a/unilabos/device_mesh/devices/benyao_arm/macro_device.xacro b/unilabos/device_mesh/devices/benyao_arm/macro_device.xacro
new file mode 100644
index 00000000..137c916d
--- /dev/null
+++ b/unilabos/device_mesh/devices/benyao_arm/macro_device.xacro
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/arm_base.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_base.STL
new file mode 100644
index 00000000..804e1c69
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_base.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_1.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_1.STL
new file mode 100644
index 00000000..dd1d7c14
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_1.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_2.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_2.STL
new file mode 100644
index 00000000..6042fa0f
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_2.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_3.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_3.STL
new file mode 100644
index 00000000..e8510fbc
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_3.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/arm_slideway.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_slideway.STL
new file mode 100644
index 00000000..65737ab5
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/arm_slideway.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_base.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_base.STL
new file mode 100644
index 00000000..5de88d0c
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_base.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_left.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_left.STL
new file mode 100644
index 00000000..0a5fd526
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_left.STL differ
diff --git a/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_right.STL b/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_right.STL
new file mode 100644
index 00000000..0c5ac69f
Binary files /dev/null and b/unilabos/device_mesh/devices/benyao_arm/meshes/gripper_right.STL differ
diff --git a/unilabos/device_mesh/devices/opentrons_liquid_handler/macro_device.xacro b/unilabos/device_mesh/devices/opentrons_liquid_handler/macro_device.xacro
index 4e660557..f76f654f 100644
--- a/unilabos/device_mesh/devices/opentrons_liquid_handler/macro_device.xacro
+++ b/unilabos/device_mesh/devices/opentrons_liquid_handler/macro_device.xacro
@@ -3,11 +3,10 @@
-
+ params="parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0 mesh_path:=''">
-
+
diff --git a/unilabos/device_mesh/devices/slide_w140/macro_device.xacro b/unilabos/device_mesh/devices/slide_w140/macro_device.xacro
index 7e43242e..d6d93f96 100644
--- a/unilabos/device_mesh/devices/slide_w140/macro_device.xacro
+++ b/unilabos/device_mesh/devices/slide_w140/macro_device.xacro
@@ -8,11 +8,11 @@
For more information, please see http://wiki.ros.org/sw_urdf_exporter -->
-
+
-
+
diff --git a/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/hotel.png b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/hotel.png
new file mode 100644
index 00000000..daecc639
Binary files /dev/null and b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/hotel.png differ
diff --git a/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/macro_device.xacro b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/macro_device.xacro
new file mode 100644
index 00000000..52c9536e
--- /dev/null
+++ b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/macro_device.xacro
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meshes/hotel.glb b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meshes/hotel.glb
new file mode 100644
index 00000000..724b85f7
Binary files /dev/null and b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meshes/hotel.glb differ
diff --git a/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meshes/hotel.stl b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meshes/hotel.stl
new file mode 100644
index 00000000..59cb9084
Binary files /dev/null and b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meshes/hotel.stl differ
diff --git a/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meta.json b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meta.json
new file mode 100644
index 00000000..b8f35cb5
--- /dev/null
+++ b/unilabos/device_mesh/devices/thermo_orbitor_rs2_hotel/meta.json
@@ -0,0 +1,10 @@
+{
+ "fileName": "thermo_orbitor_rs2_hotel",
+ "related": [
+ "thermo_skyline_stacker",
+ "thermo_cytomat2c_stacker_15",
+ "thermo_fisher_cytomat_stacker_16",
+ "thermo_cytomat2c_stacker_21",
+ "thermo_orbitor_rs2_hotel"
+ ]
+}
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/full_dev.urdf.xacro b/unilabos/device_mesh/devices/toyo_xyz/config/full_dev.urdf.xacro
new file mode 100644
index 00000000..29bd48de
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/full_dev.urdf.xacro
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/initial_positions.yaml b/unilabos/device_mesh/devices/toyo_xyz/config/initial_positions.yaml
new file mode 100644
index 00000000..d44ccee9
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/initial_positions.yaml
@@ -0,0 +1,6 @@
+# Default initial positions for full_dev's ros2_control fake system
+
+initial_positions:
+ slider1_joint: 0
+ slider2_joint: 0
+ slider3_joint: 0
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/joint_limits.yaml b/unilabos/device_mesh/devices/toyo_xyz/config/joint_limits.yaml
new file mode 100644
index 00000000..251b26c1
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/joint_limits.yaml
@@ -0,0 +1,25 @@
+# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
+
+# For beginners, we downscale velocity and acceleration limits.
+# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
+default_velocity_scaling_factor: 0.1
+default_acceleration_scaling_factor: 0.1
+
+# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
+# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
+joint_limits:
+ slider1_joint:
+ has_velocity_limits: false
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ slider2_joint:
+ has_velocity_limits: false
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
+ slider3_joint:
+ has_velocity_limits: false
+ max_velocity: 0
+ has_acceleration_limits: false
+ max_acceleration: 0
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/kinematics.yaml b/unilabos/device_mesh/devices/toyo_xyz/config/kinematics.yaml
new file mode 100644
index 00000000..d11fa7bf
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/kinematics.yaml
@@ -0,0 +1,4 @@
+toyo_xyz:
+ kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
+ kinematics_solver_search_resolution: 0.0050000000000000001
+ kinematics_solver_timeout: 0.0050000000000000001
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/macro.ros2_control.xacro b/unilabos/device_mesh/devices/toyo_xyz/config/macro.ros2_control.xacro
new file mode 100644
index 00000000..5bc3404d
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/macro.ros2_control.xacro
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+ mock_components/GenericSystem
+
+
+
+
+ ${initial_positions['slider1_joint']}
+
+
+
+
+
+
+ ${initial_positions['slider2_joint']}
+
+
+
+
+
+
+ ${initial_positions['slider3_joint']}
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/macro.srdf.xacro b/unilabos/device_mesh/devices/toyo_xyz/config/macro.srdf.xacro
new file mode 100644
index 00000000..753382fe
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/macro.srdf.xacro
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/move_group.json b/unilabos/device_mesh/devices/toyo_xyz/config/move_group.json
new file mode 100644
index 00000000..d9006744
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/move_group.json
@@ -0,0 +1,12 @@
+{
+ "toyo_xyz":
+ {
+ "joint_names": [
+ "slider1_joint",
+ "slider2_joint",
+ "slider3_joint"
+ ],
+ "base_link_name": "device_link",
+ "end_effector_name": "slider3_link"
+ }
+}
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/moveit_controllers.yaml b/unilabos/device_mesh/devices/toyo_xyz/config/moveit_controllers.yaml
new file mode 100644
index 00000000..0fcac512
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/moveit_controllers.yaml
@@ -0,0 +1,16 @@
+# MoveIt uses this configuration for controller management
+
+moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
+
+moveit_simple_controller_manager:
+ controller_names:
+ - toyo_xyz_controller
+
+ toyo_xyz_controller:
+ type: FollowJointTrajectory
+ action_ns: follow_joint_trajectory
+ default: true
+ joints:
+ - slider1_joint
+ - slider2_joint
+ - slider3_joint
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/pilz_cartesian_limits.yaml b/unilabos/device_mesh/devices/toyo_xyz/config/pilz_cartesian_limits.yaml
new file mode 100644
index 00000000..b2997caf
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/pilz_cartesian_limits.yaml
@@ -0,0 +1,6 @@
+# Limits for the Pilz planner
+cartesian_limits:
+ max_trans_vel: 1.0
+ max_trans_acc: 2.25
+ max_trans_dec: -5.0
+ max_rot_vel: 1.57
diff --git a/unilabos/device_mesh/devices/toyo_xyz/config/ros2_controllers.yaml b/unilabos/device_mesh/devices/toyo_xyz/config/ros2_controllers.yaml
new file mode 100644
index 00000000..1d1a552e
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/config/ros2_controllers.yaml
@@ -0,0 +1,34 @@
+# This config file is used by ros2_control
+controller_manager:
+
+ ros__parameters:
+ update_rate: 100 # Hz
+
+ joint_state_broadcaster:
+ type: joint_state_broadcaster/JointStateBroadcaster
+ # action_ns: $(var device_id)
+ toyo_xyz_controller:
+ type: joint_trajectory_controller/JointTrajectoryController
+
+# joint_state_broadcaster:
+# ros__parameters: {}
+
+toyo_xyz_controller:
+ ros__parameters:
+ joints:
+ - slider1_joint
+ - slider2_joint
+ - slider3_joint
+ command_interfaces:
+ - position
+ state_interfaces:
+ - position
+ - velocity
+ allow_partial_joints_goal: false
+ open_loop_control: true
+ allow_integration_in_goal_trajectories: true
+ action_monitor_rate: 20.0
+ # goal_time: 0.0
+ # constraints:
+ # stopped_velocity_tolerance: 0.01
+ # goal_time: 0.0
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/toyo_xyz/joint_config.json b/unilabos/device_mesh/devices/toyo_xyz/joint_config.json
new file mode 100644
index 00000000..26bda262
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/joint_config.json
@@ -0,0 +1,14 @@
+{
+ "slider1_joint": {
+ "child":"slider1_link",
+ "axis" : "x"
+ },
+ "slider2_joint": {
+ "child":"slider2_link",
+ "axis" : "x"
+ },
+ "slider3_joint": {
+ "child":"slider3_link",
+ "axis" : "x"
+ }
+}
\ No newline at end of file
diff --git a/unilabos/device_mesh/devices/toyo_xyz/macro_device.xacro b/unilabos/device_mesh/devices/toyo_xyz/macro_device.xacro
new file mode 100644
index 00000000..5694c353
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/macro_device.xacro
@@ -0,0 +1,465 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/base2_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/base2_link.STL
new file mode 100755
index 00000000..ded0641d
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/base2_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/base2_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/base2_link.fbx
new file mode 100644
index 00000000..a278cc67
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/base2_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/base3_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/base3_link.STL
new file mode 100755
index 00000000..d1dba510
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/base3_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/base3_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/base3_link.fbx
new file mode 100644
index 00000000..8c28d394
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/base3_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/base_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/base_link.STL
new file mode 100755
index 00000000..24205e50
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/base_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/base_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/base_link.fbx
new file mode 100644
index 00000000..5a243395
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/base_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/chain_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/chain_link.STL
new file mode 100755
index 00000000..fcbfdce5
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/chain_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/chain_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/chain_link.fbx
new file mode 100644
index 00000000..8bd35dbf
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/chain_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/end2_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/end2_link.STL
new file mode 100755
index 00000000..1b72d5db
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/end2_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/end2_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/end2_link.fbx
new file mode 100644
index 00000000..01079615
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/end2_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/end3_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/end3_link.STL
new file mode 100755
index 00000000..ce0dd9f1
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/end3_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/end3_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/end3_link.fbx
new file mode 100644
index 00000000..f0e55b56
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/end3_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/end_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/end_link.STL
new file mode 100755
index 00000000..6f5e5044
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/end_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/end_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/end_link.fbx
new file mode 100644
index 00000000..4ba363c7
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/end_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/fixed_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/fixed_link.STL
new file mode 100755
index 00000000..7a064ccf
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/fixed_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/fixed_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/fixed_link.fbx
new file mode 100644
index 00000000..ea46d658
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/fixed_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/length1_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/length1_link.STL
new file mode 100755
index 00000000..9eb85cdc
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/length1_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/length1_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/length1_link.fbx
new file mode 100644
index 00000000..5375fcec
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/length1_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/length2_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/length2_link.STL
new file mode 100755
index 00000000..3ffad0f2
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/length2_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/length2_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/length2_link.fbx
new file mode 100644
index 00000000..f94c5a6d
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/length2_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/length3_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/length3_link.STL
new file mode 100755
index 00000000..1afcdd2c
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/length3_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/length3_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/length3_link.fbx
new file mode 100644
index 00000000..a812b4c5
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/length3_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/slider1_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider1_link.STL
new file mode 100755
index 00000000..9ef91e28
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider1_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/slider1_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider1_link.fbx
new file mode 100644
index 00000000..e2a7c861
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider1_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/slider2_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider2_link.STL
new file mode 100755
index 00000000..abd8c568
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider2_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/slider2_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider2_link.fbx
new file mode 100644
index 00000000..a46df101
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider2_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/slider3_link.STL b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider3_link.STL
new file mode 100755
index 00000000..a0bfb168
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider3_link.STL differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/meshes/slider3_link.fbx b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider3_link.fbx
new file mode 100644
index 00000000..6cec9641
Binary files /dev/null and b/unilabos/device_mesh/devices/toyo_xyz/meshes/slider3_link.fbx differ
diff --git a/unilabos/device_mesh/devices/toyo_xyz/param_config.json b/unilabos/device_mesh/devices/toyo_xyz/param_config.json
new file mode 100644
index 00000000..457332b6
--- /dev/null
+++ b/unilabos/device_mesh/devices/toyo_xyz/param_config.json
@@ -0,0 +1,20 @@
+{
+ "private_param":
+ {
+ "min_d1": 0.01 ,
+ "max_d1": 0.01 ,
+ "slider_d1": 0.135,
+ "min_d2": 0.01 ,
+ "max_d2": 0.01 ,
+ "slider_d2": 0.116,
+ "min_d3": 0.01 ,
+ "max_d3": 0.01 ,
+ "slider_d3": 0.09
+ },
+ "public_param":
+ {
+ "length1" :0.5,
+ "length2" :0.2,
+ "length3" :0.2
+ }
+}
\ No newline at end of file
diff --git a/unilabos/device_mesh/resource_visalization.py b/unilabos/device_mesh/resource_visalization.py
index b840789f..cea7afcd 100644
--- a/unilabos/device_mesh/resource_visalization.py
+++ b/unilabos/device_mesh/resource_visalization.py
@@ -1,13 +1,38 @@
+import json
import os
from pathlib import Path
+import re
+
+import yaml
from launch import LaunchService
from launch import LaunchDescription
from launch_ros.actions import Node as nd
import xacro
from lxml import etree
-
+from launch_param_builder import load_yaml
+from launch_ros.parameter_descriptions import ParameterFile
from unilabos.registry.registry import lab_registry
+from ament_index_python.packages import get_package_share_directory
+def get_pattern_matches(folder, pattern):
+ """Given all the files in the folder, find those that match the pattern.
+
+ If there are groups defined, the groups are returned. Otherwise the path to the matches are returned.
+ """
+ matches = []
+ if not folder.exists():
+ return matches
+ for child in folder.iterdir():
+ if not child.is_file():
+ continue
+ m = pattern.search(child.name)
+ if m:
+ groups = m.groups()
+ if groups:
+ matches.append(groups[0])
+ else:
+ matches.append(child)
+ return matches
class ResourceVisualization:
def __init__(self, device: dict, resource: dict, enable_rviz: bool = True):
@@ -31,9 +56,8 @@ class ResourceVisualization:
self.enable_rviz = enable_rviz
registry = lab_registry
- self.srdf_str = '''
-
-
+ self.srdf_str = '''
+
'''
@@ -43,23 +67,46 @@ class ResourceVisualization:
'''
self.root = etree.fromstring(self.robot_state_str)
+ self.root_srdf = etree.fromstring(self.srdf_str)
xacro_uri = self.root.nsmap["xacro"]
+ self.moveit_nodes = {}
+ self.moveit_nodes_kinematics = {}
+ self.moveit_controllers_yaml = {
+ "moveit_controller_manager": "moveit_simple_controller_manager/MoveItSimpleControllerManager",
+ "moveit_simple_controller_manager": {
+ "controller_names": []
+ }
+ }
+ self.ros2_controllers_yaml = {
+ "controller_manager": {
+ "ros__parameters": {
+ "update_rate": 100,
+ "joint_state_broadcaster": {
+ "type": "joint_state_broadcaster/JointStateBroadcaster",
+ }
+ }
+ }
+ }
+
# 遍历设备节点
for node in device.values():
- if node['type'] == 'device' and node['class'] != '':
- device_class = node['class']
- # 检查设备类型是否在注册表中
- if device_class not in registry.device_type_registry.keys():
- raise ValueError(f"设备类型 {device_class} 未在注册表中注册")
- elif node['type'] in self.resource_type:
- # print(registry.resource_type_registry)
- resource_class = node['class']
- if resource_class not in registry.resource_type_registry.keys():
- raise ValueError(f"资源类型 {resource_class} 未在注册表中注册")
- elif "model" in registry.resource_type_registry[resource_class].keys():
- model_config = registry.resource_type_registry[resource_class]['model']
+ if node['type'] in self.resource_type or (node['type'] == 'device' and node['class'] != ''):
+ model_config = {}
+ if node['type'] in self.resource_type:
+ resource_class = node['class']
+ if resource_class not in registry.resource_type_registry.keys():
+ raise ValueError(f"资源类型 {resource_class} 未在注册表中注册")
+ elif "model" in registry.resource_type_registry[resource_class].keys():
+ model_config = registry.resource_type_registry[resource_class]['model']
+ elif node['type'] == 'device' and node['class'] != '':
+ device_class = node['class']
+ if device_class not in registry.device_type_registry.keys():
+ raise ValueError(f"设备类型 {device_class} 未在注册表中注册")
+ elif "model" in registry.device_type_registry[device_class].keys():
+ model_config = registry.device_type_registry[device_class]['model']
+ if model_config:
if model_config['type'] == 'resource':
self.resource_model[node['id']] = {
'mesh': f"{str(self.mesh_path)}/resources/{model_config['mesh']}",
@@ -71,18 +118,45 @@ class ResourceVisualization:
'mesh_tf': model_config['children_mesh_tf']
}
elif model_config['type'] == 'device':
+
new_include = etree.SubElement(self.root, f"{{{xacro_uri}}}include")
new_include.set("filename", f"{str(self.mesh_path)}/devices/{model_config['mesh']}/macro_device.xacro")
new_dev = etree.SubElement(self.root, f"{{{xacro_uri}}}{model_config['mesh']}")
new_dev.set("parent_link", "world")
new_dev.set("mesh_path", str(self.mesh_path))
new_dev.set("device_name", node["id"]+"_")
- new_dev.set("station_name", node["parent"]+'_')
+ # if node["parent"] is not None:
+ # new_dev.set("station_name", node["parent"]+'_')
+
+ print('o'*20)
+ node["parent"]
+ node["id"]
+ print('o'*20)
new_dev.set("x",str(float(node["position"]["x"])/1000))
new_dev.set("y",str(float(node["position"]["y"])/1000))
new_dev.set("z",str(float(node["position"]["z"])/1000))
if "rotation" in node["config"]:
- new_dev.set("r",str(float(node["config"]["rotation"]["z"])/1000))
+ new_dev.set("rx",str(float(node["config"]["rotation"]["x"])))
+ new_dev.set("ry",str(float(node["config"]["rotation"]["y"])))
+ new_dev.set("r",str(float(node["config"]["rotation"]["z"])))
+ if "device_config" in node["config"]:
+ for key, value in node["config"]["device_config"].items():
+ new_dev.set(key, str(float(value)))
+
+ # 添加ros2_controller
+ if node['class'].startswith('moveit.'):
+ new_include_controller = etree.SubElement(self.root, f"{{{xacro_uri}}}include")
+ new_include_controller.set("filename", f"{str(self.mesh_path)}/devices/{model_config['mesh']}/config/macro.ros2_control.xacro")
+ new_controller = etree.SubElement(self.root, f"{{{xacro_uri}}}{model_config['mesh']}_ros2_control")
+ new_controller.set("device_name", node["id"]+"_")
+ new_controller.set("mesh_path", str(self.mesh_path))
+
+ # 添加moveit的srdf
+ new_include_srdf = etree.SubElement(self.root_srdf, f"{{{xacro_uri}}}include")
+ new_include_srdf.set("filename", f"{str(self.mesh_path)}/devices/{model_config['mesh']}/config/macro.srdf.xacro")
+ new_srdf = etree.SubElement(self.root_srdf, f"{{{xacro_uri}}}{model_config['mesh']}_srdf")
+ new_srdf.set("device_name", node["id"]+"_")
+ self.moveit_nodes[node["id"]] = model_config['mesh']
else:
print("错误的注册表类型!")
re = etree.tostring(self.root, encoding="unicode")
@@ -90,8 +164,37 @@ class ResourceVisualization:
xacro.process_doc(doc)
self.urdf_str = doc.toxml()
+ re_srdf = etree.tostring(self.root_srdf, encoding="unicode")
+ doc_srdf = xacro.parse(re_srdf)
+ xacro.process_doc(doc_srdf)
+ self.urdf_str_srdf = doc_srdf.toxml()
+ if self.moveit_nodes:
+ self.moveit_init()
- def create_launch_description(self, urdf_str: str) -> LaunchDescription:
+ def moveit_init(self):
+
+ for name, config in self.moveit_nodes.items():
+ controller_dict = yaml.safe_load(open(f"{str(self.mesh_path)}/devices/{config}/config/ros2_controllers.yaml", "r"))
+ moveit_dict = yaml.safe_load(open(f"{str(self.mesh_path)}/devices/{config}/config/moveit_controllers.yaml", "r"))
+ kinematics_dict = yaml.safe_load(open(f"{str(self.mesh_path)}/devices/{config}/config/kinematics.yaml", "r"))
+
+ for key_kinematics, value_kinematics in kinematics_dict.items():
+ self.moveit_nodes_kinematics[f'{name}_{key_kinematics}'] = value_kinematics
+
+ for key, value in controller_dict['controller_manager']['ros__parameters'].items():
+ if key == 'update_rate' or key == 'joint_state_broadcaster':
+ continue
+ self.ros2_controllers_yaml['controller_manager']['ros__parameters'][f"{name}_{key}"] = value
+ controller_dict[key]['ros__parameters']['joints'] = [f"{name}_{joint}" for joint in controller_dict[key]['ros__parameters']['joints']]
+ self.ros2_controllers_yaml[f"{name}_{key}"] = controller_dict[key]
+
+ for controller_name in moveit_dict['moveit_simple_controller_manager']['controller_names']:
+ self.moveit_controllers_yaml['moveit_simple_controller_manager']['controller_names'].append(f"{name}_{controller_name}")
+ moveit_dict['moveit_simple_controller_manager'][controller_name]['joints'] = [f"{name}_{joint}" for joint in moveit_dict['moveit_simple_controller_manager'][controller_name]['joints']]
+ self.moveit_controllers_yaml['moveit_simple_controller_manager'][f"{name}_{controller_name}"] = moveit_dict['moveit_simple_controller_manager'][controller_name]
+
+
+ def create_launch_description(self) -> LaunchDescription:
"""
创建launch描述,包含robot_state_publisher和move_group节点
@@ -101,10 +204,93 @@ class ResourceVisualization:
Returns:
LaunchDescription: launch描述对象
"""
+ moveit_configs_utils_path = Path(get_package_share_directory("moveit_configs_utils"))
+ default_folder = moveit_configs_utils_path / "default_configs"
+ planning_pattern = re.compile("^(.*)_planning.yaml$")
+ pipelines = []
-
+ for pipeline in get_pattern_matches(default_folder, planning_pattern):
+ if pipeline not in pipelines:
+ pipelines.append(pipeline)
+
+ if "ompl" in pipelines:
+ default_planning_pipeline = "ompl"
+ else:
+ default_planning_pipeline = pipelines[0]
+
+ planning_pipelines = {
+ "planning_pipelines": pipelines,
+ "default_planning_pipeline": default_planning_pipeline,
+ }
+
+ for pipeline in pipelines:
+ planning_pipelines[pipeline] = load_yaml(
+ default_folder / f"{pipeline}_planning.yaml"
+ )
+
+ if "ompl" in planning_pipelines:
+ ompl_config = planning_pipelines["ompl"]
+ if "planner_configs" not in ompl_config:
+ ompl_config.update(load_yaml(default_folder / "ompl_defaults.yaml"))
+
+ yaml.safe_dump(self.ros2_controllers_yaml, open(f"{str(self.mesh_path)}/ros2_controllers.yaml", "w"))
+
+ robot_description_planning = {
+ "default_velocity_scaling_factor": 0.1,
+ "default_acceleration_scaling_factor": 0.1,
+ "cartesian_limits": {
+ "max_trans_vel": 1.0,
+ "max_trans_acc": 2.25,
+ "max_trans_dec": -5.0,
+ "max_rot_vel": 1.57
+ }
+ }
# 解析URDF文件
- robot_description = urdf_str
+ robot_description = self.urdf_str
+ urdf_str_srdf = self.urdf_str_srdf
+
+ kinematics_dict = self.moveit_nodes_kinematics
+
+ if self.moveit_nodes:
+ controllers = []
+ ros2_controllers = ParameterFile(f"{str(self.mesh_path)}/ros2_controllers.yaml", allow_substs=True)
+
+ controllers.append(
+ nd(
+ package="controller_manager",
+ executable="ros2_control_node",
+ output='screen',
+ parameters=[
+ {"robot_description": robot_description},
+ ros2_controllers,
+ ]
+ )
+ )
+ for controller in self.moveit_controllers_yaml['moveit_simple_controller_manager']['controller_names']:
+ controllers.append(
+ nd(
+ package="controller_manager",
+ executable="spawner",
+ arguments=[f"{controller}", "--controller-manager", f"controller_manager"],
+ output="screen",
+ )
+ )
+ controllers.append(
+ nd(
+ package="controller_manager",
+ executable="spawner",
+ arguments=["joint_state_broadcaster", "--controller-manager", f"controller_manager"],
+ output="screen",
+ )
+ )
+ for i in controllers:
+ self.launch_description.add_action(i)
+ else:
+ ros2_controllers = None
+
+
+
+
# 创建robot_state_publisher节点
robot_state_publisher = nd(
@@ -115,23 +301,21 @@ class ResourceVisualization:
parameters=[{
'robot_description': robot_description,
'use_sim_time': False
- }]
+ },
+ # kinematics_dict
+ ]
)
- # joint_state_publisher_node = nd(
- # package='joint_state_publisher_gui', # 或 joint_state_publisher
- # executable='joint_state_publisher_gui',
- # name='joint_state_publisher',
- # output='screen'
- # )
# 创建move_group节点
move_group = nd(
package='moveit_ros_move_group',
executable='move_group',
output='screen',
parameters=[{
+ 'allow_trajectory_execution': True,
'robot_description': robot_description,
- 'robot_description_semantic': self.srdf_str,
+ 'robot_description_semantic': urdf_str_srdf,
+ 'robot_description_kinematics': kinematics_dict,
'capabilities': '',
'disable_capabilities': '',
'monitor_dynamics': False,
@@ -141,7 +325,13 @@ class ResourceVisualization:
'publish_geometry_updates': True,
'publish_state_updates': True,
'publish_transforms_updates': True,
- }]
+ # 'robot_description_planning': robot_description_planning,
+ },
+ self.moveit_controllers_yaml,
+ # ompl_planning_pipeline_config,
+ robot_description_planning,
+ planning_pipelines,
+ ]
)
# 将节点添加到launch描述中
@@ -156,7 +346,14 @@ class ResourceVisualization:
executable='rviz2',
name='rviz2',
arguments=['-d', f"{str(self.mesh_path)}/view_robot.rviz"],
- output='screen'
+ output='screen',
+ parameters=[
+ {'robot_description_kinematics': kinematics_dict,
+ },
+ robot_description_planning,
+ planning_pipelines,
+
+ ]
)
self.launch_description.add_action(rviz_node)
@@ -169,6 +366,11 @@ class ResourceVisualization:
Args:
urdf_str: URDF文件路径
"""
- launch_description = self.create_launch_description(self.urdf_str)
+ launch_description = self.create_launch_description()
+ # print('--------------------------------')
+ # print(self.moveit_controllers_yaml)
+ # print('--------------------------------')
+ # print(self.urdf_str)
+ # print('--------------------------------')
self.launch_service.include_launch_description(launch_description)
self.launch_service.run()
\ No newline at end of file
diff --git a/unilabos/device_mesh/ros2_controllers.yaml b/unilabos/device_mesh/ros2_controllers.yaml
new file mode 100644
index 00000000..7dbad85c
--- /dev/null
+++ b/unilabos/device_mesh/ros2_controllers.yaml
@@ -0,0 +1,31 @@
+arm_slider_arm_controller:
+ ros__parameters:
+ command_interfaces:
+ - position
+ joints:
+ - arm_slider_arm_base_joint
+ - arm_slider_arm_link_1_joint
+ - arm_slider_arm_link_2_joint
+ - arm_slider_arm_link_3_joint
+ - arm_slider_gripper_base_joint
+ state_interfaces:
+ - position
+ - velocity
+arm_slider_gripper_controller:
+ ros__parameters:
+ command_interfaces:
+ - position
+ joints:
+ - arm_slider_gripper_right_joint
+ state_interfaces:
+ - position
+ - velocity
+controller_manager:
+ ros__parameters:
+ arm_slider_arm_controller:
+ type: joint_trajectory_controller/JointTrajectoryController
+ arm_slider_gripper_controller:
+ type: joint_trajectory_controller/JointTrajectoryController
+ joint_state_broadcaster:
+ type: joint_state_broadcaster/JointStateBroadcaster
+ update_rate: 100
diff --git a/unilabos/device_mesh/view_robot.rviz b/unilabos/device_mesh/view_robot.rviz
index 64f6b358..50e0543e 100644
--- a/unilabos/device_mesh/view_robot.rviz
+++ b/unilabos/device_mesh/view_robot.rviz
@@ -1,23 +1,15 @@
Panels:
- Class: rviz_common/Displays
- Help Height: 138
+ Help Height: 0
Name: Displays
Property Tree Widget:
Expanded:
- - /Global Options1
- - /TF1
- /TF1/Tree1
- - /RobotModel1
- - /PlanningScene1
- - /PlanningScene1/Scene Geometry1
- - /RobotState1
- - /RobotState1/Links1
- - /MotionPlanning1
- /MotionPlanning1/Scene Geometry1
- /MotionPlanning1/Scene Robot1
- /MotionPlanning1/Planning Request1
- Splitter Ratio: 0.5
- Tree Height: 345
+ Splitter Ratio: 0.5016146302223206
+ Tree Height: 1112
- Class: rviz_common/Selection
Name: Selection
- Class: rviz_common/Tool Properties
@@ -93,7 +85,7 @@ Visualization Manager:
Value: false
Visual Enabled: true
- Class: moveit_rviz_plugin/PlanningScene
- Enabled: false
+ Enabled: true
Move Group Namespace: ""
Name: PlanningScene
Planning Scene Topic: /monitored_planning_scene
@@ -104,7 +96,7 @@ Visualization Manager:
Scene Display Time: 0.009999999776482582
Show Scene Geometry: true
Voxel Coloring: Z-Axis
- Voxel Rendering: Occupied Voxels
+ Voxel Rendering: Disabled
Scene Robot:
Attached Body Color: 150; 50; 150
Links:
@@ -113,10 +105,108 @@ Visualization Manager:
Expand Link Details: false
Expand Tree: false
Link Tree Style: Links in Alphabetic Order
+ arm_slider_arm_base:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_arm_link_1:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_arm_link_2:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_arm_link_3:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_arm_slideway:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ arm_slider_gripper_base:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_gripper_left:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_gripper_right:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_first_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_fourth_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_main_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_second_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_socketTypeGenericSbsFootprint:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_socketTypeHEPAModule:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_third_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ hotel_base_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ hotel_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ hotel_socketTypeGenericSbsFootprint:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ world:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
Robot Alpha: 1
Show Robot Collision: false
Show Robot Visual: false
- Value: false
+ Value: true
- Attached Body Color: 150; 50; 150
Class: moveit_rviz_plugin/RobotState
Collision Enabled: false
@@ -166,43 +256,100 @@ Visualization Manager:
Expand Link Details: false
Expand Tree: false
Link Tree Style: Links in Alphabetic Order
- PLR_STATION_deck_device_link:
- Alpha: 1
- Show Axes: false
- Show Trail: false
- PLR_STATION_deck_first_link:
+ arm_slider_arm_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_fourth_link:
+ arm_slider_arm_link_1:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_main_link:
+ arm_slider_arm_link_2:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_second_link:
+ arm_slider_arm_link_3:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_socketTypeGenericSbsFootprint:
- Alpha: 1
- Show Axes: false
- Show Trail: false
- PLR_STATION_deck_socketTypeHEPAModule:
- Alpha: 1
- Show Axes: false
- Show Trail: false
- PLR_STATION_deck_third_link:
+ arm_slider_arm_slideway:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
+ arm_slider_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ arm_slider_gripper_base:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_gripper_left:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_gripper_right:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_first_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_fourth_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_main_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_second_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_socketTypeGenericSbsFootprint:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_socketTypeHEPAModule:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_third_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ hotel_base_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ hotel_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ hotel_socketTypeGenericSbsFootprint:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
world:
Alpha: 1
Show Axes: false
@@ -230,7 +377,7 @@ Visualization Manager:
Goal State Color: 250; 128; 0
Interactive Marker Size: 0
Joint Violation Color: 255; 0; 255
- Planning Group: ""
+ Planning Group: arm_slider_arm
Query Goal State: false
Query Start State: false
Show Workspace: false
@@ -242,9 +389,9 @@ Visualization Manager:
Scene Alpha: 0.8999999761581421
Scene Color: 50; 230; 50
Scene Display Time: 0.009999999776482582
- Show Scene Geometry: true
- Voxel Coloring: Z-Axis
- Voxel Rendering: Occupied Voxels
+ Show Scene Geometry: false
+ Voxel Coloring: Cell Probability
+ Voxel Rendering: All Voxels
Scene Robot:
Attached Body Color: 150; 50; 150
Links:
@@ -253,43 +400,100 @@ Visualization Manager:
Expand Link Details: false
Expand Tree: false
Link Tree Style: Links in Alphabetic Order
- PLR_STATION_deck_device_link:
- Alpha: 1
- Show Axes: false
- Show Trail: false
- PLR_STATION_deck_first_link:
+ arm_slider_arm_base:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_fourth_link:
+ arm_slider_arm_link_1:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_main_link:
+ arm_slider_arm_link_2:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_second_link:
+ arm_slider_arm_link_3:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
- PLR_STATION_deck_socketTypeGenericSbsFootprint:
- Alpha: 1
- Show Axes: false
- Show Trail: false
- PLR_STATION_deck_socketTypeHEPAModule:
- Alpha: 1
- Show Axes: false
- Show Trail: false
- PLR_STATION_deck_third_link:
+ arm_slider_arm_slideway:
Alpha: 1
Show Axes: false
Show Trail: false
Value: true
+ arm_slider_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ arm_slider_gripper_base:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_gripper_left:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ arm_slider_gripper_right:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_first_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_fourth_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_main_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_second_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ deck_socketTypeGenericSbsFootprint:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_socketTypeHEPAModule:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ deck_third_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ hotel_base_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ Value: true
+ hotel_device_link:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
+ hotel_socketTypeGenericSbsFootprint:
+ Alpha: 1
+ Show Axes: false
+ Show Trail: false
world:
Alpha: 1
Show Axes: false
@@ -345,43 +549,43 @@ Visualization Manager:
Views:
Current:
Class: rviz_default_plugins/Orbit
- Distance: 1.0284695625305176
+ Distance: 2.622864246368408
Enable Stereo Rendering:
Stereo Eye Separation: 0.05999999865889549
Stereo Focal Distance: 1
Swap Stereo Eyes: false
Value: false
Focal Point:
- X: 0.29730814695358276
- Y: 0.21228469908237457
- Z: 0.20008830726146698
+ X: -0.2880733013153076
+ Y: -0.16004444658756256
+ Z: -0.16730672121047974
Focal Shape Fixed Size: true
Focal Shape Size: 0.05000000074505806
Invert Z Axis: false
Name: Current View
Near Clip Distance: 0.009999999776482582
- Pitch: 0.38979560136795044
+ Pitch: 0.4297958016395569
Target Frame:
Value: Orbit (rviz)
- Yaw: 0.06074193865060806
+ Yaw: 0.3525616228580475
Saved: ~
Window Geometry:
Displays:
collapsed: false
- Height: 1656
+ Height: 2032
Hide Left Dock: false
Hide Right Dock: true
MotionPlanning:
- collapsed: false
+ collapsed: true
MotionPlanning - Trajectory Slider:
collapsed: false
- QMainWindow State: 000000ff00000000fd0000000400000000000003a3000005dcfc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b000000b000fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000006e000002510000018200fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000280020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000000000000000fb00000044004d006f00740069006f006e0050006c0061006e006e0069006e00670020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000007a00fffffffb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e006701000002cb0000037f000002b800ffffff000000010000010f00000387fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000003870000013200fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d0065010000000000000450000000000000000000000627000005dc00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
+ QMainWindow State: 000000ff00000000fd0000000400000000000003a30000079bfc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b000000b000fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c0061007900730100000027000004c60000018200fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000280020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000000000000000fb00000044004d006f00740069006f006e0050006c0061006e006e0069006e00670020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000007a00fffffffb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e006701000004f9000002c9000002b800ffffff000000010000010f00000387fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000003870000013200fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d0065010000000000000450000000000000000000000bc50000079b00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730000000000ffffffff0000000000000000
Selection:
collapsed: false
Tool Properties:
collapsed: false
Views:
collapsed: true
- Width: 2518
- X: 385
- Y: 120
+ Width: 3956
+ X: 140
+ Y: 54
diff --git a/unilabos/devices/liquid_handling/biomek.py b/unilabos/devices/liquid_handling/biomek.py
new file mode 100644
index 00000000..3fe3049d
--- /dev/null
+++ b/unilabos/devices/liquid_handling/biomek.py
@@ -0,0 +1,1098 @@
+import json
+import pathlib
+from typing import Sequence, Optional, List, Union, Literal
+
+import requests
+from geometry_msgs.msg import Point
+from pylabrobot.liquid_handling import LiquidHandler
+from pylabrobot.resources import (
+ TipRack,
+ Container,
+ Coordinate,
+)
+import copy
+from unilabos_msgs.msg import Resource
+
+from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker # type: ignore
+
+
+class LiquidHandlerBiomek:
+ """
+ Biomek液体处理器的实现类,继承自LiquidHandlerAbstract。
+ 该类用于处理Biomek液体处理器的特定操作。
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._status = "Idle" # 初始状态为 Idle
+ self._success = False # 初始成功状态为 False
+ self._status_queue = kwargs.get("status_queue", None) # 状态队列
+ self.temp_protocol = {}
+ self.py32_path = "/opt/py32" # Biomek的Python 3.2路径
+
+ # 预定义的仪器分类
+ self.tip_racks = [
+ "BC230", "BC1025F", "BC50", "TipRack200", "TipRack1000",
+ "tip", "tips", "Tip", "Tips"
+ ]
+
+ self.reservoirs = [
+ "AgilentReservoir", "nest_12_reservoir_15ml", "nest_1_reservoir_195ml",
+ "reservoir", "Reservoir", "waste", "Waste"
+ ]
+
+ self.plates_96 = [
+ "BCDeep96Round", "Matrix96_750uL", "NEST 2ml Deep Well Plate", "nest_96_wellplate_100ul_pcr_full_skirt",
+ "nest_96_wellplate_200ul_flat", "Matrix96", "96", "plate", "Plate"
+ ]
+
+ self.aspirate_techniques = {
+ 'MC P300 high':{
+ 'Position': 'P1',
+ 'Height': -2.0,
+ 'Volume': '50',
+ 'liquidtype': 'Well Contents',
+ 'WellsX': 12,
+ 'LabwareClass': 'Matrix96_750uL',
+ 'AutoSelectPrototype': True,
+ 'ColsFirst': True,
+ 'CustomHeight': False,
+ 'DataSetPattern': False,
+ 'HeightFrom': 0,
+ 'LocalPattern': True,
+ 'Operation': 'Aspirate',
+ 'OverrideHeight': False,
+ 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True),
+ 'Prototype': 'MC P300 High',
+ 'ReferencedPattern': '',
+ 'RowsFirst': False,
+ 'SectionExpression': '',
+ 'SelectionInfo': (1,),
+ 'SetMark': True,
+ 'Source': True,
+ 'StartAtMark': False,
+ 'StartAtSelection': True,
+ 'UseExpression': False},
+ }
+
+ self.dispense_techniques = {
+ 'MC P300 high':{
+ 'Position': 'P11',
+ 'Height': -2.0,
+ 'Volume': '50',
+ 'liquidtype': 'Tip Contents',
+ 'WellsX': 12,
+ 'LabwareClass': 'Matrix96_750uL',
+ 'AutoSelectPrototype': True,
+ 'ColsFirst': True,
+ 'CustomHeight': False,
+ 'DataSetPattern': False,
+ 'HeightFrom': 0,
+ 'LocalPattern': True,
+ 'Operation': 'Dispense',
+ 'OverrideHeight': False,
+ 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True),
+ 'Prototype': 'MC P300 High',
+ 'ReferencedPattern': '',
+ 'RowsFirst': False,
+ 'SectionExpression': '',
+ 'SelectionInfo': (1,),
+ 'SetMark': True,
+ 'Source': False,
+ 'StartAtMark': False,
+ 'StartAtSelection': True,
+ 'UseExpression': False}
+ }
+
+ def _get_instrument_type(self, class_name: str) -> str:
+ """
+ 根据class_name判断仪器类型
+
+ Returns:
+ str: "tip_rack", "reservoir", "plate_96", 或 "unknown"
+ """
+ # 检查是否是枪头架
+ for tip_name in self.tip_racks:
+ if tip_name in class_name:
+ return "tip_rack"
+
+ # 检查是否是储液槽
+ for reservoir_name in self.reservoirs:
+ if reservoir_name in class_name:
+ return "reservoir"
+
+ # 检查是否是96孔板
+ for plate_name in self.plates_96:
+ if plate_name in class_name:
+ return "plate_96"
+
+ return "unknown"
+
+ @classmethod
+ def deserialize(cls, data: dict, allow_marshal: bool = False) -> LiquidHandler:
+ return LiquidHandler.deserialize(data, allow_marshal)
+
+ @property
+ def success(self):
+ """
+ 获取操作是否成功的状态。
+
+ Returns:
+ bool: 如果操作成功,返回True;否则返回False。
+ """
+ return self._success
+
+ def create_protocol(
+ self,
+ protocol_name: str,
+ protocol_description: str,
+ protocol_version: str,
+ protocol_author: str,
+ protocol_date: str,
+ protocol_type: str,
+ none_keys: List[str] = [],
+ ):
+ """
+ 创建一个新的协议。
+
+ Args:
+ protocol_name (str): 协议名称
+ protocol_description (str): 协议描述
+ protocol_version (str): 协议版本
+ protocol_author (str): 协议作者
+ protocol_date (str): 协议日期
+ protocol_type (str): 协议类型
+ none_keys (List[str]): 需要设置为None的键列表
+
+ Returns:
+ dict: 创建的协议字典
+ """
+ self.temp_protocol = {
+ "meta": {
+ "name": protocol_name,
+ "description": protocol_description,
+ "version": protocol_version,
+ "author": protocol_author,
+ "date": protocol_date,
+ "type": protocol_type,
+ },
+ "labwares": {},
+ "steps": [],
+ }
+ return self.temp_protocol
+
+ def run_protocol(self):
+ """
+ 执行创建的实验流程。
+ 工作站的完整执行流程是,
+ 从 create_protocol 开始,创建新的 method,
+ 随后执行 transfer_liquid 等操作向实验流程添加步骤,
+ 最后 run_protocol 执行整个方法。
+
+ Returns:
+ dict: 执行结果
+ """
+ #use popen or subprocess to create py32 process and communicate send the temp protocol to it
+ if not self.temp_protocol:
+ raise ValueError("No protocol created. Please create a protocol first.")
+
+ # 模拟执行协议
+ self._status = "Running"
+ self._success = True
+ # 在这里可以添加实际执行协议的逻辑
+
+ response = requests.post("localhost:5000/api/protocols", json=self.temp_protocol)
+
+ def create_resource(
+ self,
+ resource_tracker: DeviceNodeResourceTracker,
+ resources: list[Resource],
+ bind_parent_id: str,
+ bind_location: dict[str, float],
+ liquid_input_slot: list[int],
+ liquid_type: list[str],
+ liquid_volume: list[int],
+ slot_on_deck: int,
+ ):
+ """
+ 创建一个新的资源。
+
+ Args:
+ device_id (str): 设备ID
+ res_id (str): 资源ID
+ class_name (str): 资源类名
+ parent (str): 父级ID
+ bind_locations (Point): 绑定位置
+ liquid_input_slot (list[int]): 液体输入槽列表
+ liquid_type (list[str]): 液体类型列表
+ liquid_volume (list[int]): 液体体积列表
+ slot_on_deck (int): 甲板上的槽位
+
+ Returns:
+ dict: 创建的资源字典
+ """
+ # TODO:需要对好接口,下面这个是临时的
+ for resource in resources:
+ res_id = resource.id
+ class_name = resource.name
+ parent = bind_parent_id
+ liquid_input_slot = liquid_input_slot
+ liquid_type = liquid_type
+ liquid_volume = liquid_volume
+ slot_on_deck = slot_on_deck
+
+ resource = {
+ "id": res_id,
+ "class": class_name,
+ "parent": parent,
+ "bind_locations": bind_location,
+ "liquid_input_slot": liquid_input_slot,
+ "liquid_type": liquid_type,
+ "liquid_volume": liquid_volume,
+ "slot_on_deck": slot_on_deck,
+ }
+ self.temp_protocol["labwares"].append(resource)
+ return resources
+
+ def transfer_liquid(
+ self,
+ sources: Sequence[Container],
+ targets: Sequence[Container],
+ tip_racks: Sequence[TipRack],
+ *,
+ use_channels: Optional[List[int]] = None,
+ asp_vols: Union[List[float], float],
+ dis_vols: Union[List[float], float],
+ asp_flow_rates: Optional[List[Optional[float]]] = None,
+ dis_flow_rates: Optional[List[Optional[float]]] = None,
+ offsets: Optional[List[Coordinate]] = None,
+ touch_tip: bool = False,
+ liquid_height: Optional[List[Optional[float]]] = None,
+ blow_out_air_volume: Optional[List[Optional[float]]] = None,
+ spread: Literal["wide", "tight", "custom"] = "wide",
+ is_96_well: bool = False,
+ mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
+ mix_times: Optional[int] = None,
+ mix_vol: Optional[int] = None,
+ mix_rate: Optional[int] = None,
+ mix_liquid_height: Optional[float] = None,
+ delays: Optional[List[int]] = None,
+ none_keys: List[str] = []
+ ):
+
+ transfer_params = {
+ "Span8": False,
+ "Pod": "Pod1",
+ "items": {},
+ "Wash": False,
+ "Dynamic?": True,
+ "AutoSelectActiveWashTechnique": False,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": False,
+ "ChangeTipsBetweenSources": False,
+ "DefaultCaption": "",
+ "UseExpression": False,
+ "LeaveTipsOn": False,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": False,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": False,
+ "ShowTransferDetails": True,
+ "Solvent": "Well Content",
+ "Span8Wash": False,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": False,
+ "SplitVolumeCleaning": False,
+ "Stop": "Destinations",
+ "TipLocation": "BC1025F",
+ "UseCurrentTips": False,
+ "UseDisposableTips": True,
+ "UseFixedTips": False,
+ "UseJIT": True,
+ "UseMandrelSelection": True,
+ "UseProbes": [True, True, True, True, True, True, True, True],
+ "WashCycles": "1",
+ "WashVolume": "110%",
+ "Wizard": False
+ }
+
+ items: dict = {}
+ for idx, (src, dst) in enumerate(zip(sources, targets)):
+ items[str(idx)] = {
+ "Source": str(src),
+ "Destination": str(dst),
+ "Volume": dis_vols[idx]
+ }
+ transfer_params["items"] = items
+
+ transfer_params["Solvent"] = "Water"
+ TipLocation = tip_racks[0].name
+ transfer_params["TipLocation"] = TipLocation
+
+ if len(tip_racks) == 1:
+ transfer_params['UseCurrentTips'] = True
+ elif len(tip_racks) > 1:
+ transfer_params["ChangeTipsBetweenDests"] = True
+
+ self.temp_protocol["steps"].append(transfer_params)
+
+ return
+
+ def instrument_setup_biomek(
+ self,
+ id: str,
+ parent: str,
+ slot_on_deck: str,
+ class_name: str,
+ liquid_type: list[str],
+ liquid_volume: list[int],
+ liquid_input_wells: list[str],
+ ):
+ """
+ 设置Biomek仪器的参数配置,按照DeckItems格式
+
+ 根据不同的仪器类型(容器、tip rack等)设置相应的参数结构
+ 位置作为键,配置列表作为值
+ """
+
+ # 判断仪器类型
+ instrument_type = self._get_instrument_type(class_name)
+
+ config = None # 初始化为None
+
+ if instrument_type == "reservoir":
+ # 储液槽类型配置
+ config = {
+ "Properties": {
+ "Name": id, # 使用id作为名称
+ "Device": "",
+ "liquidtype": liquid_type[0] if liquid_type else "Water",
+ "BarCode": "",
+ "SenseEveryTime": False
+ },
+ "Known": True,
+ "Class": f"LabwareClasses\\{class_name}",
+ "DataSets": {"Volume": {}},
+ "RuntimeDataSets": {"Volume": {}},
+ "EvalAmounts": (float(liquid_volume[0]),) if liquid_volume else (0,),
+ "Nominal": False,
+ "EvalLiquids": (liquid_type[0],) if liquid_type else ("Water",)
+ }
+
+ elif instrument_type == "plate_96":
+ # 96孔板类型配置
+ volume_per_well = float(liquid_volume[0]) if liquid_volume else 0
+ liquid_per_well = liquid_type[0] if liquid_type else "Water"
+
+ config = {
+ "Properties": {
+ "Name": id, # 使用id作为名称
+ "Device": "",
+ "liquidtype": liquid_per_well,
+ "BarCode": "",
+ "SenseEveryTime": False
+ },
+ "Known": True,
+ "Class": f"LabwareClasses\\{class_name}",
+ "DataSets": {"Volume": {}},
+ "RuntimeDataSets": {"Volume": {}},
+ "EvalAmounts": tuple([volume_per_well] * 96),
+ "Nominal": False,
+ "EvalLiquids": tuple([liquid_per_well] * 96)
+ }
+
+ elif instrument_type == "tip_rack":
+ # 枪头架类型配置
+ tip_config = {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": False,
+ "RT_Used": False,
+ "Dirty": False,
+ "RT_Dirty": False,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ }
+
+ config = {
+ "Tips": tip_config,
+ "RT_Tips": tip_config.copy(),
+ "Properties": {},
+ "Known": False,
+ "Class": f"LabwareClasses\\{class_name}",
+ "DataSets": {"Volume": {}},
+ "RuntimeDataSets": {"Volume": {}}
+ }
+
+ # 按照DeckItems格式存储:位置作为键,配置列表作为值
+ if config is not None:
+ self.temp_protocol["labwares"][slot_on_deck] = [config]
+ else:
+ # 空位置
+ self.temp_protocol["labwares"][slot_on_deck] = []
+
+ return
+
+ def transfer_biomek(
+ self,
+ source: str,
+ target: str,
+ tip_rack: str,
+ volume: float,
+ aspirate_techniques: str,
+ dispense_techniques: str,
+ ):
+ """
+ 处理Biomek的液体转移操作。
+
+ """
+ items = []
+
+ asp_params = copy.deepcopy(self.aspirate_techniques[aspirate_techniques])
+ dis_params = copy.deepcopy(self.dispense_techniques[dispense_techniques])
+
+ asp_params['Position'] = source
+ dis_params['Position'] = target
+ asp_params['Volume'] = str(volume)
+ dis_params['Volume'] = str(volume)
+
+ items.append(asp_params)
+ items.append(dis_params)
+
+ transfer_params = {
+ "Span8": False,
+ "Pod": "Pod1",
+ "items": [],
+ "Wash": False,
+ "Dynamic?": True,
+ "AutoSelectActiveWashTechnique": False,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": True,
+ "ChangeTipsBetweenSources": False,
+ "DefaultCaption": "",
+ "UseExpression": False,
+ "LeaveTipsOn": False,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": False,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": False,
+ "ShowTransferDetails": True,
+ "Solvent": "Water",
+ "Span8Wash": False,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": False,
+ "SplitVolumeCleaning": False,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": False,
+ "UseDisposableTips": False,
+ "UseFixedTips": False,
+ "UseJIT": True,
+ "UseMandrelSelection": True,
+ "UseProbes": [True, True, True, True, True, True, True, True],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": False
+ }
+ transfer_params["items"] = items
+ transfer_params["Solvent"] = 'Water'
+ transfer_params["TipLocation"] = tip_rack
+ tmp={'transfer': transfer_params}
+ self.temp_protocol["steps"].append(tmp)
+
+ return
+
+
+ def move_biomek(
+ self,
+ source: str,
+ target: str,
+ ):
+ """
+ 处理Biomek移动板子的操作。
+
+ """
+
+ move_params = {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": source,
+ "Target": target,
+ "LeaveBottomLabware": False,
+ }
+ self.temp_protocol["steps"].append(move_params)
+
+ return
+
+ def incubation_biomek(
+ self,
+ time: int,
+ ):
+ """
+ 处理Biomek的孵育操作。
+ """
+ incubation_params = {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": time,
+ "Mode": "TimedResource"
+ }
+ self.temp_protocol["steps"].append(incubation_params)
+
+ return
+
+ def oscillation_biomek(
+ self,
+ rpm: int,
+ time: int,
+ ):
+ """
+ 处理Biomek的振荡操作。
+ """
+ oscillation_params = {
+ 'Device': 'OrbitalShaker0',
+ 'Parameters': (str(rpm), '2', str(time), 'CounterClockwise'),
+ 'Command': 'Timed Shake'
+ }
+ self.temp_protocol["steps"].append(oscillation_params)
+
+ return
+
+
+if __name__ == "__main__":
+
+ print("=== Biomek完整流程测试 ===")
+ print("包含: 仪器设置 + 完整实验步骤")
+
+ # 完整的步骤信息(从biomek.py复制)
+ steps_info = '''
+ {
+ "steps": [
+ {
+ "step_number": 1,
+ "operation": "transfer",
+ "description": "转移PCR产物或酶促反应液至0.5ml 96孔板中",
+ "parameters": {
+ "source": "P1",
+ "target": "P11",
+ "tip_rack": "BC230",
+ "volume": 50
+ }
+ },
+ {
+ "step_number": 2,
+ "operation": "transfer",
+ "description": "加入2倍体积的Bind Beads BC至产物中",
+ "parameters": {
+ "source": "P2",
+ "target": "P11",
+ "tip_rack": "BC230",
+ "volume": 100
+ }
+ },
+ {
+ "step_number": 3,
+ "operation": "oscillation",
+ "description": "振荡混匀300秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 300
+ }
+ },
+ {
+ "step_number": 4,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附3分钟",
+ "parameters": {
+ "source": "P11",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 5,
+ "operation": "incubation",
+ "description": "吸附3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 6,
+ "operation": "transfer",
+ "description": "吸弃或倒除上清液",
+ "parameters": {
+ "source": "P12",
+ "target": "P22",
+ "tip_rack": "BC230",
+ "volume": 150
+ }
+ },
+ {
+ "step_number": 7,
+ "operation": "transfer",
+ "description": "加入300-500μl 75%乙醇",
+ "parameters": {
+ "source": "P3",
+ "target": "P12",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 8,
+ "operation": "move_labware",
+ "description": "移动至振荡器进行振荡混匀",
+ "parameters": {
+ "source": "P12",
+ "target": "Orbital1"
+ }
+ },
+ {
+ "step_number": 9,
+ "operation": "oscillation",
+ "description": "振荡混匀60秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 60
+ }
+ },
+ {
+ "step_number": 10,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附3分钟",
+ "parameters": {
+ "source": "Orbital1",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 11,
+ "operation": "incubation",
+ "description": "吸附3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 12,
+ "operation": "transfer",
+ "description": "吸弃或倒弃废液",
+ "parameters": {
+ "source": "P12",
+ "target": "P22",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 13,
+ "operation": "transfer",
+ "description": "重复加入75%乙醇",
+ "parameters": {
+ "source": "P3",
+ "target": "P12",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 14,
+ "operation": "move_labware",
+ "description": "移动至振荡器进行振荡混匀",
+ "parameters": {
+ "source": "P12",
+ "target": "Orbital1"
+ }
+ },
+ {
+ "step_number": 15,
+ "operation": "oscillation",
+ "description": "振荡混匀60秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 60
+ }
+ },
+ {
+ "step_number": 16,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附3分钟",
+ "parameters": {
+ "source": "Orbital1",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 17,
+ "operation": "incubation",
+ "description": "吸附3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 18,
+ "operation": "transfer",
+ "description": "吸弃或倒弃废液",
+ "parameters": {
+ "source": "P12",
+ "target": "P22",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 19,
+ "operation": "move_labware",
+ "description": "正放96孔板,空气干燥15分钟",
+ "parameters": {
+ "source": "P12",
+ "target": "P13"
+ }
+ },
+ {
+ "step_number": 20,
+ "operation": "incubation",
+ "description": "空气干燥15分钟",
+ "parameters": {
+ "time": 900
+ }
+ },
+ {
+ "step_number": 21,
+ "operation": "transfer",
+ "description": "加入30-50μl Elution Buffer",
+ "parameters": {
+ "source": "P4",
+ "target": "P13",
+ "tip_rack": "BC230",
+ "volume": 40
+ }
+ },
+ {
+ "step_number": 22,
+ "operation": "move_labware",
+ "description": "移动至振荡器进行振荡混匀",
+ "parameters": {
+ "source": "P13",
+ "target": "Orbital1"
+ }
+ },
+ {
+ "step_number": 23,
+ "operation": "oscillation",
+ "description": "振荡混匀60秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 60
+ }
+ },
+ {
+ "step_number": 24,
+ "operation": "move_labware",
+ "description": "室温静置3分钟",
+ "parameters": {
+ "source": "Orbital1",
+ "target": "P13"
+ }
+ },
+ {
+ "step_number": 25,
+ "operation": "incubation",
+ "description": "室温静置3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 26,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附2分钟",
+ "parameters": {
+ "source": "P13",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 27,
+ "operation": "incubation",
+ "description": "吸附2分钟",
+ "parameters": {
+ "time": 120
+ }
+ },
+ {
+ "step_number": 28,
+ "operation": "transfer",
+ "description": "将DNA转移至新的板中",
+ "parameters": {
+ "source": "P12",
+ "target": "P14",
+ "tip_rack": "BC230",
+ "volume": 40
+ }
+ }
+ ]
+ }
+'''
+ # 完整的labware配置信息
+ labware_with_liquid = '''
+ [
+ {
+ "id": "Tip Rack BC230 TL1",
+ "parent": "deck",
+ "slot_on_deck": "TL1",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL2",
+ "parent": "deck",
+ "slot_on_deck": "TL2",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL3",
+ "parent": "deck",
+ "slot_on_deck": "TL3",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL4",
+ "parent": "deck",
+ "slot_on_deck": "TL4",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL5",
+ "parent": "deck",
+ "slot_on_deck": "TL5",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P5",
+ "parent": "deck",
+ "slot_on_deck": "P5",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P6",
+ "parent": "deck",
+ "slot_on_deck": "P6",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P15",
+ "parent": "deck",
+ "slot_on_deck": "P15",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P16",
+ "parent": "deck",
+ "slot_on_deck": "P16",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "stock plate on P1",
+ "parent": "deck",
+ "slot_on_deck": "P1",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["PCR product"],
+ "liquid_volume": [5000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "stock plate on P2",
+ "parent": "deck",
+ "slot_on_deck": "P2",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["bind beads"],
+ "liquid_volume": [100000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "stock plate on P3",
+ "parent": "deck",
+ "slot_on_deck": "P3",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["75% ethanol"],
+ "liquid_volume": [100000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "stock plate on P4",
+ "parent": "deck",
+ "slot_on_deck": "P4",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["Elution Buffer"],
+ "liquid_volume": [5000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "working plate on P11",
+ "parent": "deck",
+ "slot_on_deck": "P11",
+ "class_name": "BCDeep96Round",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "working plate on P13",
+ "parent": "deck",
+ "slot_on_deck": "P13",
+ "class_name": "BCDeep96Round",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "working plate on P14",
+ "parent": "deck",
+ "slot_on_deck": "P14",
+ "class_name": "BCDeep96Round",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "waste on P22",
+ "parent": "deck",
+ "slot_on_deck": "P22",
+ "class_name": "AgilentReservoir",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "oscillation",
+ "parent": "deck",
+ "slot_on_deck": "Orbital1",
+ "class_name": "Orbital",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ }
+ ]
+ '''
+
+ # 创建handler实例
+ handler = LiquidHandlerBiomek()
+
+ # 创建协议
+ protocol = handler.create_protocol(
+ protocol_name="DNA纯化完整流程",
+ protocol_description="使用磁珠进行DNA纯化的完整自动化流程",
+ protocol_version="1.0",
+ protocol_author="Biomek系统",
+ protocol_date="2024-01-01",
+ protocol_type="DNA_purification"
+ )
+
+ print("\n=== 第一步:设置所有仪器 ===")
+ # 解析labware配置
+ labwares = json.loads(labware_with_liquid)
+
+ # 设置所有仪器
+ instrument_count = 0
+ for labware in labwares:
+ print(f"设置仪器: {labware['id']} ({labware['class_name']}) 在位置 {labware['slot_on_deck']}")
+ handler.instrument_setup_biomek(
+ id=labware['id'],
+ parent=labware['parent'],
+ slot_on_deck=labware['slot_on_deck'],
+ class_name=labware['class_name'],
+ liquid_type=labware['liquid_type'],
+ liquid_volume=labware['liquid_volume'],
+ liquid_input_wells=labware['liquid_input_wells']
+ )
+ instrument_count += 1
+
+ print(f"总共设置了 {instrument_count} 个仪器位置")
+
+ print("\n=== 第二步:执行实验步骤 ===")
+ # 解析步骤信息
+ input_steps = json.loads(steps_info)
+
+ # 执行所有步骤
+ step_count = 0
+ for step in input_steps['steps']:
+ operation = step['operation']
+ parameters = step['parameters']
+ description = step['description']
+
+ print(f"步骤 {step['step_number']}: {description}")
+
+ if operation == 'transfer':
+ handler.transfer_biomek(
+ source=parameters['source'],
+ target=parameters['target'],
+ volume=parameters['volume'],
+ tip_rack=parameters['tip_rack'],
+ aspirate_techniques='MC P300 high',
+ dispense_techniques='MC P300 high'
+ )
+ elif operation == 'move_labware':
+ handler.move_biomek(
+ source=parameters['source'],
+ target=parameters['target']
+ )
+ elif operation == 'oscillation':
+ handler.oscillation_biomek(
+ rpm=parameters['rpm'],
+ time=parameters['time']
+ )
+ elif operation == 'incubation':
+ handler.incubation_biomek(
+ time=parameters['time']
+ )
+
+ step_count += 1
+
+ print(f"总共执行了 {step_count} 个步骤")
+
+ print("\n=== 第三步:保存完整协议 ===")
+ # 获取脚本目录
+ script_dir = pathlib.Path(__file__).parent
+
+ # 保存完整协议
+ complete_output_path = script_dir / "complete_biomek_protocol_0608.json"
+ with open(complete_output_path, 'w', encoding='utf-8') as f:
+ json.dump(handler.temp_protocol, f, indent=4, ensure_ascii=False)
+
+ print(f"完整协议已保存到: {complete_output_path}")
+
+ print("\n=== 测试完成 ===")
+ print("完整的DNA纯化流程已成功转换为Biomek格式!")
diff --git a/unilabos/devices/liquid_handling/biomek.txt b/unilabos/devices/liquid_handling/biomek.txt
new file mode 100644
index 00000000..2d830a6d
--- /dev/null
+++ b/unilabos/devices/liquid_handling/biomek.txt
@@ -0,0 +1,642 @@
+
+ 当前方法 Method0530 包含 36 个步骤
+
+ 步骤 0:
+ Bitmap: OStepUI.ocx,START
+ Let: {}
+ Weak: {}
+ Prompt: {}
+
+ 步骤 1:
+ BarcodeInput?: False
+ DeckItems: {'P1': [{'Properties': {'Name': '', 'Device': '', 'liquidtype': 'Water', 'BarCode': '', 'SenseEveryTime': False}, 'Known': True, 'Class': 'LabwareClasses\\Matrix96_750uL', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}, 'EvalAmounts': (500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0), 'Nominal': False, 'EvalLiquids': ('Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water')}], 'P10': [{'Tips': {'Class': 'TipClasses\\T50F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T50F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC50F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P8': [], 'P9': [{'Tips': {'Class': 'TipClasses\\T50F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T50F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC50F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P11': [{'Properties': {'Name': '', 'Device': '', 'liquidtype': 'Water', 'BarCode': '', 'SenseEveryTime': False}, 'Known': True, 'Class': 'LabwareClasses\\BCDeep96Round', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}, 'EvalAmounts': (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), 'Nominal': False, 'EvalLiquids': ('Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water')}], 'P12': [], 'P13': [{'Properties': {'Name': '', 'Device': '', 'liquidtype': 'Water', 'BarCode': '', 'SenseEveryTime': False}, 'Known': True, 'Class': 'LabwareClasses\\BCDeep96Round', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}, 'EvalAmounts': (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), 'Nominal': False, 'EvalLiquids': ('Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water')}], 'P14': [], 'P15': [], 'P16': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P17': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P18': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P19': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P20': [], 'P2': [{'Properties': {'Name': '', 'Device': '', 'liquidtype': 'Water', 'BarCode': '', 'SenseEveryTime': False}, 'Known': True, 'Class': 'LabwareClasses\\Matrix96_750uL', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}, 'EvalAmounts': (500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0), 'Nominal': False, 'EvalLiquids': ('Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water')}], 'P21': [], 'P22': [{'Properties': {'Name': '', 'Device': '', 'liquidtype': 'Water', 'BarCode': '', 'SenseEveryTime': False}, 'Known': True, 'Class': 'LabwareClasses\\AgilentReservoir', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}, 'EvalAmounts': (0.0,), 'Nominal': False, 'EvalLiquids': ('Water',)}], 'P23': [], 'P24': [], 'P25': [], 'P26': [], 'P27': [], 'P28': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P29': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'P30': [], 'P3': [{'Properties': {'Name': '', 'Device': '', 'liquidtype': 'Water', 'BarCode': '', 'SenseEveryTime': False}, 'Known': True, 'Class': 'LabwareClasses\\AgilentReservoir', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}, 'EvalAmounts': (300000.0,), 'Nominal': False, 'EvalLiquids': ('Water',)}], 'P4': [{'Properties': {'Name': '', 'Device': '', 'liquidtype': 'Water', 'BarCode': '', 'SenseEveryTime': False}, 'Known': True, 'Class': 'LabwareClasses\\Matrix96_750uL', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}, 'EvalAmounts': (500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0, 500.0), 'Nominal': False, 'EvalLiquids': ('Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water', 'Water')}], 'P5': [], 'P6': [], 'P7': [], 'TL1': [{'Tips': {'Class': 'TipClasses\\T230', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T230', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC230', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'TL2': [{'Tips': {'Class': 'TipClasses\\T230', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T230', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC230', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'TL3': [{'Tips': {'Class': 'TipClasses\\T230', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T230', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC230', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'TL4': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'TL5': [{'Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'RT_Tips': {'Class': 'TipClasses\\T1025F', 'Contents': [], '_RT_Contents': [], 'Used': False, 'RT_Used': False, 'Dirty': False, 'RT_Dirty': False, 'MaxVolumeUsed': 0.0, 'RT_MaxVolumeUsed': 0.0}, 'Properties': {}, 'Known': False, 'Class': 'LabwareClasses\\BC1025F', 'DataSets': {'Volume': {}}, 'RuntimeDataSets': {'Volume': {}}}], 'TR1': [], 'WS1': 'Water'}
+ Layout: Multichannel
+ Pause?: True
+ PodSetup: {'LeftHasTips': False, 'LeftTipType': '', 'RightHasTips': False, 'RightTipType': ''}
+ SplitterPosition: 206
+ VerifyPodSetup?: True
+
+ 步骤 2:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P1', 'Height': -2.0, 'Volume': '50', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'Matrix96_750uL', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P11', 'Height': -2.0, 'Volume': '50', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 50 µL from P1 to P11
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC230
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 3:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P2', 'Height': -2.0, 'Volume': '100', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'Matrix96_750uL', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P11', 'Height': -2.0, 'Volume': '100', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 100 µL from P2 to P11
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC230
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 4:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: P11
+ Target: Orbital1
+ LeaveBottomLabware: False
+
+ 步骤 5:
+ Device:
+ Parameters: ()
+ Command:
+
+ 步骤 6:
+ Device:
+ Parameters: ()
+ Command:
+
+ 步骤 7:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: Orbital1
+ Target: P11
+ LeaveBottomLabware: False
+
+ 步骤 8:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: P11
+ Target: P12
+ LeaveBottomLabware: False
+
+ 步骤 9:
+ Message: Paused
+ Location: the whole system
+ Time: 180
+ Mode: TimedResource
+
+ 步骤 10:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P12', 'Height': -2.0, 'Volume': '150', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P22', 'Height': -2.0, 'Volume': '150', 'liquidtype': 'Tip Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 150 µL from P12 to P22
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC230
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 11:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P3', 'Height': -2.0, 'Volume': '400', 'liquidtype': 'Well Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P3 to P12
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 12:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P3', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Well Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P3 to P12
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 13:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: P12
+ Target: Orbital1
+ LeaveBottomLabware: False
+
+ 步骤 14:
+ Device:
+ Parameters: ()
+ Command:
+
+ 步骤 15:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: Orbital1
+ Target: P12
+ LeaveBottomLabware: False
+
+ 步骤 16:
+ Message: Paused
+ Location: the whole system
+ Time: 180
+ Mode: TimedResource
+
+ 步骤 17:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P22', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P12 to P22
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 18:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P22', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P12 to P22
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 19:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P3', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Well Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P3 to P12
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 20:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P3', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Well Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P3 to P12
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 21:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: P12
+ Target: Orbital1
+ LeaveBottomLabware: False
+
+ 步骤 22:
+ Device: OrbitalShaker0
+ Parameters: ('800', '3', '45', 'CounterClockwise', None)
+ Command: Timed Shake
+
+ 步骤 23:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: Orbital1
+ Target: P12
+ LeaveBottomLabware: False
+
+ 步骤 24:
+ Message: Paused
+ Location: the whole system
+ Time: 180
+ Mode: TimedResource
+
+ 步骤 25:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P22', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P12 to P22
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 26:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P12', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P22', 'Height': -2.0, 'Volume': '200', 'liquidtype': 'Tip Contents', 'LabwareClass': 'AgilentReservoir', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True,), 'Prototype': 'MC P300 High', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 200 µL from P12 to P22
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC1025F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 27:
+ Message: Paused
+ Location: the whole system
+ Time: 900
+ Mode: TimedResource
+
+ 步骤 28:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P4', 'Height': -2.0, 'Volume': '40', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'Matrix96_750uL', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P12', 'Height': -2.0, 'Volume': '40', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 40 µL from P4 to P12
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC50F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 29:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: P12
+ Target: Orbital1
+ LeaveBottomLabware: False
+
+ 步骤 30:
+ Device: OrbitalShaker0
+ Parameters: ('800', '3', '60', 'CounterClockwise', None)
+ Command: Timed Shake
+
+ 步骤 31:
+ Message: Paused
+ Location: the whole system
+ Time: 180
+ Mode: TimedResource
+
+ 步骤 32:
+ Pod: Pod1
+ GripSide: A1 near
+ Source: Orbital1
+ Target: P12
+ LeaveBottomLabware: False
+
+ 步骤 33:
+ Message: Paused
+ Location: the whole system
+ Time: 120
+ Mode: TimedResource
+
+ 步骤 34:
+ Span8: False
+ Pod: Pod1
+ Wash: False
+ items: [{'Position': 'P12', 'Height': -2.0, 'Volume': '40', 'liquidtype': 'Well Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Aspirate', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': True, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}, {'Position': 'P13', 'Height': -2.0, 'Volume': '40', 'liquidtype': 'Tip Contents', 'WellsX': 12, 'LabwareClass': 'BCDeep96Round', 'AutoSelectPrototype': True, 'ColsFirst': True, 'CustomHeight': False, 'DataSetPattern': False, 'HeightFrom': 0, 'LocalPattern': True, 'Operation': 'Dispense', 'OverrideHeight': False, 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True), 'Prototype': 'MC', 'ReferencedPattern': '', 'RowsFirst': False, 'SectionExpression': '', 'SelectionInfo': (1,), 'SetMark': True, 'Source': False, 'StartAtMark': False, 'StartAtSelection': True, 'UseExpression': False}]
+ Dynamic?: True
+ AutoSelectActiveWashTechnique: False
+ ActiveWashTechnique:
+ ChangeTipsBetweenDests: True
+ ChangeTipsBetweenSources: False
+ DefaultCaption: Transfer 40 µL from P12 to P13
+ UseExpression: False
+ LeaveTipsOn: False
+ MandrelExpression:
+ Repeats: 1
+ RepeatsByVolume: False
+ Replicates: 1
+ ShowTipHandlingDetails: True
+ ShowTransferDetails: True
+ Solvent: Water
+ Span8Wash: False
+ Span8WashVolume: 2
+ Span8WasteVolume: 1
+ SplitVolume: False
+ SplitVolumeCleaning: False
+ Stop: Destinations
+ TipLocation: BC50F
+ UseCurrentTips: False
+ UseDisposableTips: False
+ UseFixedTips: False
+ UseJIT: True
+ UseMandrelSelection: True
+ UseProbes: (True, True, True, True, True, True, True, True)
+ WashCycles: 4
+ WashVolume: 110%
+ Wizard: False
+
+ 步骤 35:
+ Type:
+ Bitmap: OStepUI.ocx,FINISH
+ cleardeck: True
+ cleardevices: True
+ cleanuppods: True
+ PodsToMaxZ: True
+ ClearGlobals: True
+ ParkPods: True
+ Authent: False
+ Collapsed: True
+ ConnectionString:
+ Password:
+ Path:
+ Report: False
+ Server:
+ TableName:
+ UserName:
+ Catalog:
diff --git a/unilabos/devices/liquid_handling/biomek_temporary_protocol.json b/unilabos/devices/liquid_handling/biomek_temporary_protocol.json
new file mode 100644
index 00000000..129c3ba2
--- /dev/null
+++ b/unilabos/devices/liquid_handling/biomek_temporary_protocol.json
@@ -0,0 +1,2697 @@
+{
+ "meta": {},
+ "labwares": [],
+ "steps": [
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ },
+ {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/unilabos/devices/liquid_handling/biomek_test.py b/unilabos/devices/liquid_handling/biomek_test.py
new file mode 100644
index 00000000..af6339a1
--- /dev/null
+++ b/unilabos/devices/liquid_handling/biomek_test.py
@@ -0,0 +1,1006 @@
+# import requests
+from typing import List, Sequence, Optional, Union, Literal
+# from geometry_msgs.msg import Point
+# from unilabos_msgs.msg import Resource
+# from pylabrobot.resources import (
+# Resource,
+# TipRack,
+# Container,
+# Coordinate,
+# Well
+# )
+# from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker # type: ignore
+# from .liquid_handler_abstract import LiquidHandlerAbstract
+
+import json
+import pathlib
+from typing import Sequence, Optional, List, Union, Literal
+import copy
+
+
+
+#class LiquidHandlerBiomek(LiquidHandlerAbstract):
+
+
+class LiquidHandlerBiomek:
+ """
+ Biomek液体处理器的实现类,继承自LiquidHandlerAbstract。
+ 该类用于处理Biomek液体处理器的特定操作。
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._status = "Idle" # 初始状态为 Idle
+ self._success = False # 初始成功状态为 False
+ self._status_queue = kwargs.get("status_queue", None) # 状态队列
+ self.temp_protocol = {}
+ self.py32_path = "/opt/py32" # Biomek的Python 3.2路径
+
+ # 预定义的仪器分类
+ self.tip_racks = [
+ "BC230", "BC1025F", "BC50", "TipRack200", "TipRack1000",
+ "tip", "tips", "Tip", "Tips"
+ ]
+
+ self.reservoirs = [
+ "AgilentReservoir", "nest_12_reservoir_15ml", "nest_1_reservoir_195ml",
+ "reservoir", "Reservoir", "waste", "Waste"
+ ]
+
+ self.plates_96 = [
+ "BCDeep96Round", "Matrix96_750uL", "NEST 2ml Deep Well Plate", "nest_96_wellplate_100ul_pcr_full_skirt",
+ "nest_96_wellplate_200ul_flat", "Matrix96", "96", "plate", "Plate"
+ ]
+
+ self.aspirate_techniques = {
+ 'MC P300 high':{
+ 'Position': 'P1',
+ 'Height': -2.0,
+ 'Volume': '50',
+ 'liquidtype': 'Well Contents',
+ 'WellsX': 12,
+ 'LabwareClass': 'Matrix96_750uL',
+ 'AutoSelectPrototype': True,
+ 'ColsFirst': True,
+ 'CustomHeight': False,
+ 'DataSetPattern': False,
+ 'HeightFrom': 0,
+ 'LocalPattern': True,
+ 'Operation': 'Aspirate',
+ 'OverrideHeight': False,
+ 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True),
+ 'Prototype': 'MC P300 High',
+ 'ReferencedPattern': '',
+ 'RowsFirst': False,
+ 'SectionExpression': '',
+ 'SelectionInfo': (1,),
+ 'SetMark': True,
+ 'Source': True,
+ 'StartAtMark': False,
+ 'StartAtSelection': True,
+ 'UseExpression': False},
+ }
+
+ self.dispense_techniques = {
+ 'MC P300 high':{
+ 'Position': 'P11',
+ 'Height': -2.0,
+ 'Volume': '50',
+ 'liquidtype': 'Tip Contents',
+ 'WellsX': 12,
+ 'LabwareClass': 'Matrix96_750uL',
+ 'AutoSelectPrototype': True,
+ 'ColsFirst': True,
+ 'CustomHeight': False,
+ 'DataSetPattern': False,
+ 'HeightFrom': 0,
+ 'LocalPattern': True,
+ 'Operation': 'Dispense',
+ 'OverrideHeight': False,
+ 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True),
+ 'Prototype': 'MC P300 High',
+ 'ReferencedPattern': '',
+ 'RowsFirst': False,
+ 'SectionExpression': '',
+ 'SelectionInfo': (1,),
+ 'SetMark': True,
+ 'Source': False,
+ 'StartAtMark': False,
+ 'StartAtSelection': True,
+ 'UseExpression': False}
+ }
+
+
+ def _get_instrument_type(self, class_name: str) -> str:
+ """
+ 根据class_name判断仪器类型
+
+ Returns:
+ str: "tip_rack", "reservoir", "plate_96", 或 "unknown"
+ """
+ # 检查是否是枪头架
+ for tip_name in self.tip_racks:
+ if tip_name in class_name:
+ return "tip_rack"
+
+ # 检查是否是储液槽
+ for reservoir_name in self.reservoirs:
+ if reservoir_name in class_name:
+ return "reservoir"
+
+ # 检查是否是96孔板
+ for plate_name in self.plates_96:
+ if plate_name in class_name:
+ return "plate_96"
+
+ return "unknown"
+
+ def create_protocol(
+ self,
+ protocol_name: str,
+ protocol_description: str,
+ protocol_version: str,
+ protocol_author: str,
+ protocol_date: str,
+ protocol_type: str,
+ none_keys: List[str] = [],
+ ):
+ """
+ 创建一个新的协议。
+
+ Args:
+ protocol_name (str): 协议名称
+ protocol_description (str): 协议描述
+ protocol_version (str): 协议版本
+ protocol_author (str): 协议作者
+ protocol_date (str): 协议日期
+ protocol_type (str): 协议类型
+ none_keys (List[str]): 需要设置为None的键列表
+
+ Returns:
+ dict: 创建的协议字典
+ """
+ self.temp_protocol = {
+ "meta": {
+ "name": protocol_name,
+ "description": protocol_description,
+ "version": protocol_version,
+ "author": protocol_author,
+ "date": protocol_date,
+ "type": protocol_type,
+ },
+ "labwares": {}, # 改为字典格式以匹配DeckItems
+ "steps": [],
+ }
+ return self.temp_protocol
+
+# def run_protocol(self):
+# """
+# 执行创建的实验流程。
+# 工作站的完整执行流程是,
+# 从 create_protocol 开始,创建新的 method,
+# 随后执行 transfer_liquid 等操作向实验流程添加步骤,
+# 最后 run_protocol 执行整个方法。
+
+# Returns:
+# dict: 执行结果
+# """
+# #use popen or subprocess to create py32 process and communicate send the temp protocol to it
+# if not self.temp_protocol:
+# raise ValueError("No protocol created. Please create a protocol first.")
+
+# # 模拟执行协议
+# self._status = "Running"
+# self._success = True
+# # 在这里可以添加实际执行协议的逻辑
+
+# response = requests.post("localhost:5000/api/protocols", json=self.temp_protocol)
+
+# def create_resource(
+# self,
+# resource_tracker: DeviceNodeResourceTracker,
+# resources: list[Resource],
+# bind_parent_id: str,
+# bind_location: dict[str, float],
+# liquid_input_slot: list[int],
+# liquid_type: list[str],
+# liquid_volume: list[int],
+# slot_on_deck: int,
+# res_id,
+# class_name,
+# bind_locations,
+# parent
+# ):
+# """
+# 创建一个新的资源。
+
+# Args:
+# device_id (str): 设备ID
+# res_id (str): 资源ID
+# class_name (str): 资源类名
+# parent (str): 父级ID
+# bind_locations (Point): 绑定位置
+# liquid_input_slot (list[int]): 液体输入槽列表
+# liquid_type (list[str]): 液体类型列表
+# liquid_volume (list[int]): 液体体积列表
+# slot_on_deck (int): 甲板上的槽位
+
+# Returns:
+# dict: 创建的资源字典
+# """
+# # TODO:需要对好接口,下面这个是临时的
+# resource = {
+# "id": res_id,
+# "class": class_name,
+# "parent": parent,
+# "bind_locations": bind_locations.to_dict(),
+# "liquid_input_slot": liquid_input_slot,
+# "liquid_type": liquid_type,
+# "liquid_volume": liquid_volume,
+# "slot_on_deck": slot_on_deck,
+# }
+# self.temp_protocol["labwares"].append(resource)
+# return resource
+ def instrument_setup_biomek(
+ self,
+ id: str,
+ parent: str,
+ slot_on_deck: str,
+ class_name: str,
+ liquid_type: list[str],
+ liquid_volume: list[int],
+ liquid_input_wells: list[str],
+ ):
+ """
+ 设置Biomek仪器的参数配置,按照DeckItems格式
+
+ 根据不同的仪器类型(容器、tip rack等)设置相应的参数结构
+ 位置作为键,配置列表作为值
+ """
+
+ # 判断仪器类型
+ instrument_type = self._get_instrument_type(class_name)
+
+ config = None # 初始化为None
+
+ if instrument_type == "reservoir":
+ # 储液槽类型配置
+ config = {
+ "Properties": {
+ "Name": id, # 使用id作为名称
+ "Device": "",
+ "liquidtype": liquid_type[0] if liquid_type else "Water",
+ "BarCode": "",
+ "SenseEveryTime": False
+ },
+ "Known": True,
+ "Class": f"LabwareClasses\\{class_name}",
+ "DataSets": {"Volume": {}},
+ "RuntimeDataSets": {"Volume": {}},
+ "EvalAmounts": (float(liquid_volume[0]),) if liquid_volume else (0,),
+ "Nominal": False,
+ "EvalLiquids": (liquid_type[0],) if liquid_type else ("Water",)
+ }
+
+ elif instrument_type == "plate_96":
+ # 96孔板类型配置
+ volume_per_well = float(liquid_volume[0]) if liquid_volume else 0
+ liquid_per_well = liquid_type[0] if liquid_type else "Water"
+
+ config = {
+ "Properties": {
+ "Name": id, # 使用id作为名称
+ "Device": "",
+ "liquidtype": liquid_per_well,
+ "BarCode": "",
+ "SenseEveryTime": False
+ },
+ "Known": True,
+ "Class": f"LabwareClasses\\{class_name}",
+ "DataSets": {"Volume": {}},
+ "RuntimeDataSets": {"Volume": {}},
+ "EvalAmounts": tuple([volume_per_well] * 96),
+ "Nominal": False,
+ "EvalLiquids": tuple([liquid_per_well] * 96)
+ }
+
+ elif instrument_type == "tip_rack":
+ # 枪头架类型配置
+ tip_config = {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": False,
+ "RT_Used": False,
+ "Dirty": False,
+ "RT_Dirty": False,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ }
+
+ config = {
+ "Tips": tip_config,
+ "RT_Tips": tip_config.copy(),
+ "Properties": {},
+ "Known": False,
+ "Class": f"LabwareClasses\\{class_name}",
+ "DataSets": {"Volume": {}},
+ "RuntimeDataSets": {"Volume": {}}
+ }
+
+ # 按照DeckItems格式存储:位置作为键,配置列表作为值
+ if config is not None:
+ self.temp_protocol["labwares"][slot_on_deck] = [config]
+ else:
+ # 空位置
+ self.temp_protocol["labwares"][slot_on_deck] = []
+
+ return
+
+ def transfer_biomek(
+ self,
+ source: str,
+ target: str,
+ tip_rack: str,
+ volume: float,
+ aspirate_techniques: str,
+ dispense_techniques: str,
+ ):
+ """
+ 处理Biomek的液体转移操作。
+
+ """
+ items = []
+
+ asp_params = copy.deepcopy(self.aspirate_techniques[aspirate_techniques])
+ dis_params = copy.deepcopy(self.dispense_techniques[dispense_techniques])
+
+
+ asp_params['Position'] = source
+ dis_params['Position'] = target
+ asp_params['Volume'] = str(volume)
+ dis_params['Volume'] = str(volume)
+
+ items.append(asp_params)
+ items.append(dis_params)
+
+ transfer_params = {
+ "Span8": False,
+ "Pod": "Pod1",
+ "items": [],
+ "Wash": False,
+ "Dynamic?": True,
+ "AutoSelectActiveWashTechnique": False,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": True,
+ "ChangeTipsBetweenSources": False,
+ "DefaultCaption": "",
+ "UseExpression": False,
+ "LeaveTipsOn": False,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": False,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": False,
+ "ShowTransferDetails": True,
+ "Solvent": "Water",
+ "Span8Wash": False,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": False,
+ "SplitVolumeCleaning": False,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": False,
+ "UseDisposableTips": False,
+ "UseFixedTips": False,
+ "UseJIT": True,
+ "UseMandrelSelection": True,
+ "UseProbes": [True, True, True, True, True, True, True, True],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": False
+ }
+ transfer_params["items"] = items
+ transfer_params["Solvent"] = 'Water'
+ transfer_params["TipLocation"] = tip_rack
+ tmp={'transfer': transfer_params}
+
+ self.temp_protocol["steps"].append(tmp)
+
+
+ return
+
+ def move_biomek(
+ self,
+ source: str,
+ target: str,
+ ):
+ """
+ 处理Biomek移动板子的操作。
+
+ """
+
+ move_params = {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": source,
+ "Target": target,
+ "LeaveBottomLabware": False,
+ }
+ tmp={'move': move_params}
+ self.temp_protocol["steps"].append(tmp)
+
+ return
+
+ def incubation_biomek(
+ self,
+ time: int,
+ ):
+ """
+ 处理Biomek的孵育操作。
+ """
+ incubation_params = {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": time,
+ "Mode": "TimedResource"
+ }
+ tmp={'incubation': incubation_params}
+ self.temp_protocol["steps"].append(tmp)
+
+ return
+
+ def oscillation_biomek(
+ self,
+ rpm: int,
+ time: int,
+ ):
+ """
+ 处理Biomek的振荡操作。
+ """
+ oscillation_params = {
+ 'Device': 'OrbitalShaker0',
+ 'Parameters': (str(rpm), '2', str(time), 'CounterClockwise'),
+ 'Command': 'Timed Shake'
+ }
+ tmp={'oscillation': oscillation_params}
+ self.temp_protocol["steps"].append(tmp)
+
+ return
+
+
+
+if __name__ == "__main__":
+
+ print("=== Biomek完整流程测试 ===")
+ print("包含: 仪器设置 + 完整实验步骤")
+
+ # 完整的步骤信息(从biomek.py复制)
+ steps_info = '''
+ {
+ "steps": [
+ {
+ "step_number": 1,
+ "operation": "transfer",
+ "description": "转移PCR产物或酶促反应液至0.5ml 96孔板中",
+ "parameters": {
+ "source": "P1",
+ "target": "P11",
+ "tip_rack": "BC230",
+ "volume": 50
+ }
+ },
+ {
+ "step_number": 2,
+ "operation": "transfer",
+ "description": "加入2倍体积的Bind Beads BC至产物中",
+ "parameters": {
+ "source": "P2",
+ "target": "P11",
+ "tip_rack": "BC230",
+ "volume": 100
+ }
+ },
+ {
+ "step_number": 3,
+ "operation": "oscillation",
+ "description": "振荡混匀300秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 300
+ }
+ },
+ {
+ "step_number": 4,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附3分钟",
+ "parameters": {
+ "source": "P11",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 5,
+ "operation": "incubation",
+ "description": "吸附3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 6,
+ "operation": "transfer",
+ "description": "吸弃或倒除上清液",
+ "parameters": {
+ "source": "P12",
+ "target": "P22",
+ "tip_rack": "BC230",
+ "volume": 150
+ }
+ },
+ {
+ "step_number": 7,
+ "operation": "transfer",
+ "description": "加入300-500μl 75%乙醇",
+ "parameters": {
+ "source": "P3",
+ "target": "P12",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 8,
+ "operation": "move_labware",
+ "description": "移动至振荡器进行振荡混匀",
+ "parameters": {
+ "source": "P12",
+ "target": "Orbital1"
+ }
+ },
+ {
+ "step_number": 9,
+ "operation": "oscillation",
+ "description": "振荡混匀60秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 60
+ }
+ },
+ {
+ "step_number": 10,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附3分钟",
+ "parameters": {
+ "source": "Orbital1",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 11,
+ "operation": "incubation",
+ "description": "吸附3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 12,
+ "operation": "transfer",
+ "description": "吸弃或倒弃废液",
+ "parameters": {
+ "source": "P12",
+ "target": "P22",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 13,
+ "operation": "transfer",
+ "description": "重复加入75%乙醇",
+ "parameters": {
+ "source": "P3",
+ "target": "P12",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 14,
+ "operation": "move_labware",
+ "description": "移动至振荡器进行振荡混匀",
+ "parameters": {
+ "source": "P12",
+ "target": "Orbital1"
+ }
+ },
+ {
+ "step_number": 15,
+ "operation": "oscillation",
+ "description": "振荡混匀60秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 60
+ }
+ },
+ {
+ "step_number": 16,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附3分钟",
+ "parameters": {
+ "source": "Orbital1",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 17,
+ "operation": "incubation",
+ "description": "吸附3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 18,
+ "operation": "transfer",
+ "description": "吸弃或倒弃废液",
+ "parameters": {
+ "source": "P12",
+ "target": "P22",
+ "tip_rack": "BC230",
+ "volume": 400
+ }
+ },
+ {
+ "step_number": 19,
+ "operation": "move_labware",
+ "description": "正放96孔板,空气干燥15分钟",
+ "parameters": {
+ "source": "P12",
+ "target": "P13"
+ }
+ },
+ {
+ "step_number": 20,
+ "operation": "incubation",
+ "description": "空气干燥15分钟",
+ "parameters": {
+ "time": 900
+ }
+ },
+ {
+ "step_number": 21,
+ "operation": "transfer",
+ "description": "加入30-50μl Elution Buffer",
+ "parameters": {
+ "source": "P4",
+ "target": "P13",
+ "tip_rack": "BC230",
+ "volume": 40
+ }
+ },
+ {
+ "step_number": 22,
+ "operation": "move_labware",
+ "description": "移动至振荡器进行振荡混匀",
+ "parameters": {
+ "source": "P13",
+ "target": "Orbital1"
+ }
+ },
+ {
+ "step_number": 23,
+ "operation": "oscillation",
+ "description": "振荡混匀60秒",
+ "parameters": {
+ "rpm": 800,
+ "time": 60
+ }
+ },
+ {
+ "step_number": 24,
+ "operation": "move_labware",
+ "description": "室温静置3分钟",
+ "parameters": {
+ "source": "Orbital1",
+ "target": "P13"
+ }
+ },
+ {
+ "step_number": 25,
+ "operation": "incubation",
+ "description": "室温静置3分钟",
+ "parameters": {
+ "time": 180
+ }
+ },
+ {
+ "step_number": 26,
+ "operation": "move_labware",
+ "description": "转移至96孔磁力架上吸附2分钟",
+ "parameters": {
+ "source": "P13",
+ "target": "P12"
+ }
+ },
+ {
+ "step_number": 27,
+ "operation": "incubation",
+ "description": "吸附2分钟",
+ "parameters": {
+ "time": 120
+ }
+ },
+ {
+ "step_number": 28,
+ "operation": "transfer",
+ "description": "将DNA转移至新的板中",
+ "parameters": {
+ "source": "P12",
+ "target": "P14",
+ "tip_rack": "BC230",
+ "volume": 40
+ }
+ }
+ ]
+ }
+'''
+ # 完整的labware配置信息
+ labware_with_liquid = '''
+ [
+ {
+ "id": "Tip Rack BC230 TL1",
+ "parent": "deck",
+ "slot_on_deck": "TL1",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL2",
+ "parent": "deck",
+ "slot_on_deck": "TL2",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL3",
+ "parent": "deck",
+ "slot_on_deck": "TL3",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL4",
+ "parent": "deck",
+ "slot_on_deck": "TL4",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 TL5",
+ "parent": "deck",
+ "slot_on_deck": "TL5",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P5",
+ "parent": "deck",
+ "slot_on_deck": "P5",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P6",
+ "parent": "deck",
+ "slot_on_deck": "P6",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P15",
+ "parent": "deck",
+ "slot_on_deck": "P15",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "Tip Rack BC230 P16",
+ "parent": "deck",
+ "slot_on_deck": "P16",
+ "class_name": "BC230",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "stock plate on P1",
+ "parent": "deck",
+ "slot_on_deck": "P1",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["PCR product"],
+ "liquid_volume": [5000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "stock plate on P2",
+ "parent": "deck",
+ "slot_on_deck": "P2",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["bind beads"],
+ "liquid_volume": [100000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "stock plate on P3",
+ "parent": "deck",
+ "slot_on_deck": "P3",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["75% ethanol"],
+ "liquid_volume": [100000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "stock plate on P4",
+ "parent": "deck",
+ "slot_on_deck": "P4",
+ "class_name": "AgilentReservoir",
+ "liquid_type": ["Elution Buffer"],
+ "liquid_volume": [5000],
+ "liquid_input_wells": ["A1"]
+ },
+ {
+ "id": "working plate on P11",
+ "parent": "deck",
+ "slot_on_deck": "P11",
+ "class_name": "BCDeep96Round",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "working plate on P13",
+ "parent": "deck",
+ "slot_on_deck": "P13",
+ "class_name": "BCDeep96Round",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "working plate on P14",
+ "parent": "deck",
+ "slot_on_deck": "P14",
+ "class_name": "BCDeep96Round",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "waste on P22",
+ "parent": "deck",
+ "slot_on_deck": "P22",
+ "class_name": "AgilentReservoir",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ },
+ {
+ "id": "oscillation",
+ "parent": "deck",
+ "slot_on_deck": "Orbital1",
+ "class_name": "Orbital",
+ "liquid_type": [],
+ "liquid_volume": [],
+ "liquid_input_wells": []
+ }
+ ]
+ '''
+
+ # 创建handler实例
+ handler = LiquidHandlerBiomek()
+
+ # 创建协议
+ protocol = handler.create_protocol(
+ protocol_name="DNA纯化完整流程",
+ protocol_description="使用磁珠进行DNA纯化的完整自动化流程",
+ protocol_version="1.0",
+ protocol_author="Biomek系统",
+ protocol_date="2024-01-01",
+ protocol_type="DNA_purification"
+ )
+
+ print("\n=== 第一步:设置所有仪器 ===")
+ # 解析labware配置
+ labwares = json.loads(labware_with_liquid)
+
+ # 设置所有仪器
+ instrument_count = 0
+ for labware in labwares:
+ print(f"设置仪器: {labware['id']} ({labware['class_name']}) 在位置 {labware['slot_on_deck']}")
+ handler.instrument_setup_biomek(
+ id=labware['id'],
+ parent=labware['parent'],
+ slot_on_deck=labware['slot_on_deck'],
+ class_name=labware['class_name'],
+ liquid_type=labware['liquid_type'],
+ liquid_volume=labware['liquid_volume'],
+ liquid_input_wells=labware['liquid_input_wells']
+ )
+ instrument_count += 1
+
+ print(f"总共设置了 {instrument_count} 个仪器位置")
+
+ print("\n=== 第二步:执行实验步骤 ===")
+ # 解析步骤信息
+ input_steps = json.loads(steps_info)
+
+ # 执行所有步骤
+ step_count = 0
+ for step in input_steps['steps']:
+ operation = step['operation']
+ parameters = step['parameters']
+ description = step['description']
+
+ print(f"步骤 {step['step_number']}: {description}")
+
+ if operation == 'transfer':
+
+ handler.transfer_biomek(
+ source=parameters['source'],
+ target=parameters['target'],
+ volume=parameters['volume'],
+ tip_rack=parameters['tip_rack'],
+ aspirate_techniques='MC P300 high',
+ dispense_techniques='MC P300 high'
+ )
+ elif operation == 'move_labware':
+ handler.move_biomek(
+ source=parameters['source'],
+ target=parameters['target']
+ )
+ elif operation == 'oscillation':
+ handler.oscillation_biomek(
+ rpm=parameters['rpm'],
+ time=parameters['time']
+ )
+ elif operation == 'incubation':
+ handler.incubation_biomek(
+ time=parameters['time']
+ )
+
+ step_count += 1
+
+ print(f"总共执行了 {step_count} 个步骤")
+
+ print("\n=== 第三步:保存完整协议 ===")
+ # 获取脚本目录
+ script_dir = pathlib.Path(__file__).parent
+
+ # 保存完整协议
+ complete_output_path = script_dir / "complete_biomek_protocol_0608.json"
+ with open(complete_output_path, 'w', encoding='utf-8') as f:
+ json.dump(handler.temp_protocol, f, indent=4, ensure_ascii=False)
+
+ print(f"完整协议已保存到: {complete_output_path}")
+
+ print("\n=== 测试完成 ===")
+ print("完整的DNA纯化流程已成功转换为Biomek格式!")
diff --git a/unilabos/devices/liquid_handling/complete_biomek_protocol.json b/unilabos/devices/liquid_handling/complete_biomek_protocol.json
new file mode 100644
index 00000000..2c0e95fd
--- /dev/null
+++ b/unilabos/devices/liquid_handling/complete_biomek_protocol.json
@@ -0,0 +1,3760 @@
+{
+ "meta": {
+ "name": "DNA纯化完整流程",
+ "description": "使用磁珠进行DNA纯化的完整自动化流程",
+ "version": "1.0",
+ "author": "Biomek系统",
+ "date": "2024-01-01",
+ "type": "DNA_purification"
+ },
+ "labwares": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on TL1",
+ "Position": "TL1"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on TL2",
+ "Position": "TL2"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on TL3",
+ "Position": "TL3"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on TL4",
+ "Position": "TL4"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on TL5",
+ "Position": "TL5"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on P5",
+ "Position": "P5"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on P6",
+ "Position": "P6"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on P15",
+ "Position": "P15"
+ },
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "Name": "Tip Rack BC230 on P16",
+ "Position": "P16"
+ },
+ {
+ "Properties": {
+ "Name": "stock plate on P1",
+ "Device": "",
+ "liquidtype": "master_mix",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\nest_12_reservoir_15ml",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 10000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "master_mix"
+ ],
+ "Name": "stock plate on P1",
+ "Position": "P1"
+ },
+ {
+ "Properties": {
+ "Name": "stock plate on P2",
+ "Device": "",
+ "liquidtype": "bind beads",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\nest_12_reservoir_15ml",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 10000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "bind beads"
+ ],
+ "Name": "stock plate on P2",
+ "Position": "P2"
+ },
+ {
+ "Properties": {
+ "Name": "stock plate on P3",
+ "Device": "",
+ "liquidtype": "ethyl alcohol",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\nest_12_reservoir_15ml",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 10000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "ethyl alcohol"
+ ],
+ "Name": "stock plate on P3",
+ "Position": "P3"
+ },
+ {
+ "Properties": {
+ "Name": "elution buffer on P4",
+ "Device": "",
+ "liquidtype": "elution buffer",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\nest_12_reservoir_15ml",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 5000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "elution buffer"
+ ],
+ "Name": "elution buffer on P4",
+ "Position": "P4"
+ },
+ {
+ "Properties": {
+ "Name": "working plate on P11",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\NEST 2ml Deep Well Plate",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water"
+ ],
+ "Name": "working plate on P11",
+ "Position": "P11"
+ },
+ {
+ "Properties": {
+ "Name": "working plate on P13",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\NEST 2ml Deep Well Plate",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0,
+ 500.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water"
+ ],
+ "Name": "working plate on P13",
+ "Position": "P13"
+ },
+ {
+ "Properties": {
+ "Name": "waste on P22",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\nest_1_reservoir_195ml",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 0.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water"
+ ],
+ "Name": "waste on P22",
+ "Position": "P22"
+ }
+ ],
+ "steps": [
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P11",
+ "Target": "Orbital1",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "300",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "Orbital1",
+ "Target": "P11",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P11",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P12",
+ "Target": "Orbital1",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "45",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "Orbital1",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P12",
+ "Target": "Orbital1",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "45",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "Orbital1",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 900,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P12",
+ "Target": "Orbital1",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "60",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "Orbital1",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P13",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/unilabos/devices/liquid_handling/complete_biomek_protocol_0608.json b/unilabos/devices/liquid_handling/complete_biomek_protocol_0608.json
new file mode 100644
index 00000000..24b3d455
--- /dev/null
+++ b/unilabos/devices/liquid_handling/complete_biomek_protocol_0608.json
@@ -0,0 +1,4201 @@
+{
+ "meta": {
+ "name": "DNA纯化完整流程",
+ "description": "使用磁珠进行DNA纯化的完整自动化流程",
+ "version": "1.0",
+ "author": "Biomek系统",
+ "date": "2024-01-01",
+ "type": "DNA_purification"
+ },
+ "labwares": {
+ "TL1": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "TL2": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "TL3": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "TL4": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "TL5": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "P5": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "P6": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "P15": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "P16": [
+ {
+ "Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "RT_Tips": {
+ "Class": "TipClasses\\T230",
+ "Contents": [],
+ "_RT_Contents": [],
+ "Used": false,
+ "RT_Used": false,
+ "Dirty": false,
+ "RT_Dirty": false,
+ "MaxVolumeUsed": 0.0,
+ "RT_MaxVolumeUsed": 0.0
+ },
+ "Properties": {},
+ "Known": false,
+ "Class": "LabwareClasses\\BC230",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ }
+ }
+ ],
+ "P1": [
+ {
+ "Properties": {
+ "Name": "stock plate on P1",
+ "Device": "",
+ "liquidtype": "PCR product",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\AgilentReservoir",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 5000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "PCR product"
+ ]
+ }
+ ],
+ "P2": [
+ {
+ "Properties": {
+ "Name": "stock plate on P2",
+ "Device": "",
+ "liquidtype": "bind beads",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\AgilentReservoir",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 100000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "bind beads"
+ ]
+ }
+ ],
+ "P3": [
+ {
+ "Properties": {
+ "Name": "stock plate on P3",
+ "Device": "",
+ "liquidtype": "75% ethanol",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\AgilentReservoir",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 100000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "75% ethanol"
+ ]
+ }
+ ],
+ "P4": [
+ {
+ "Properties": {
+ "Name": "stock plate on P4",
+ "Device": "",
+ "liquidtype": "Elution Buffer",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\AgilentReservoir",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 5000.0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Elution Buffer"
+ ]
+ }
+ ],
+ "P11": [
+ {
+ "Properties": {
+ "Name": "working plate on P11",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\BCDeep96Round",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water"
+ ]
+ }
+ ],
+ "P12": [
+ {
+ "Properties": {
+ "Name": "working plate on P12",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\BCDeep96Round",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water"
+ ]
+ }
+ ],
+ "P13": [
+ {
+ "Properties": {
+ "Name": "working plate on P13",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\BCDeep96Round",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water"
+ ]
+ }
+ ],
+ "P14": [
+ {
+ "Properties": {
+ "Name": "working plate on P14",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\BCDeep96Round",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water",
+ "Water"
+ ]
+ }
+ ],
+ "P22": [
+ {
+ "Properties": {
+ "Name": "waste on P22",
+ "Device": "",
+ "liquidtype": "Water",
+ "BarCode": "",
+ "SenseEveryTime": false
+ },
+ "Known": true,
+ "Class": "LabwareClasses\\AgilentReservoir",
+ "DataSets": {
+ "Volume": {}
+ },
+ "RuntimeDataSets": {
+ "Volume": {}
+ },
+ "EvalAmounts": [
+ 0
+ ],
+ "Nominal": false,
+ "EvalLiquids": [
+ "Water"
+ ]
+ }
+ ],
+ "Orbital1": []
+ },
+ "steps": [
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "300",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P11",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P12",
+ "Target": "Orbital1",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "60",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "Orbital1",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P12",
+ "Target": "Orbital1",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "60",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "Orbital1",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P12",
+ "Target": "P13",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 900,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P13",
+ "Target": "Orbital1",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "oscillation": {
+ "Device": "OrbitalShaker0",
+ "Parameters": [
+ "800",
+ "2",
+ "60",
+ "CounterClockwise"
+ ],
+ "Command": "Timed Shake"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "Orbital1",
+ "Target": "P13",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 180,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "move": {
+ "Pod": "Pod1",
+ "GripSide": "A1 near",
+ "Source": "P13",
+ "Target": "P12",
+ "LeaveBottomLabware": false
+ }
+ },
+ {
+ "incubation": {
+ "Message": "Paused",
+ "Location": "the whole system",
+ "Time": 120,
+ "Mode": "TimedResource"
+ }
+ },
+ {
+ "transfer": {
+ "Span8": false,
+ "Pod": "Pod1",
+ "items": [
+ {
+ "Position": "P12",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Well Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Aspirate",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": true,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ },
+ {
+ "Position": "P14",
+ "Height": -2.0,
+ "Volume": "40",
+ "liquidtype": "Tip Contents",
+ "WellsX": 12,
+ "LabwareClass": "Matrix96_750uL",
+ "AutoSelectPrototype": true,
+ "ColsFirst": true,
+ "CustomHeight": false,
+ "DataSetPattern": false,
+ "HeightFrom": 0,
+ "LocalPattern": true,
+ "Operation": "Dispense",
+ "OverrideHeight": false,
+ "Pattern": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "Prototype": "MC P300 High",
+ "ReferencedPattern": "",
+ "RowsFirst": false,
+ "SectionExpression": "",
+ "SelectionInfo": [
+ 1
+ ],
+ "SetMark": true,
+ "Source": false,
+ "StartAtMark": false,
+ "StartAtSelection": true,
+ "UseExpression": false
+ }
+ ],
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": false,
+ "ShowTransferDetails": true,
+ "Solvent": "Water",
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC230",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true
+ ],
+ "WashCycles": "4",
+ "WashVolume": "110%",
+ "Wizard": false
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/unilabos/devices/liquid_handling/converted protocol/sci-lucif-assay4_plr_background_tested.ipynb b/unilabos/devices/liquid_handling/converted protocol/sci-lucif-assay4_plr_background_tested.ipynb
index daf47e23..f380fc68 100644
--- a/unilabos/devices/liquid_handling/converted protocol/sci-lucif-assay4_plr_background_tested.ipynb
+++ b/unilabos/devices/liquid_handling/converted protocol/sci-lucif-assay4_plr_background_tested.ipynb
@@ -2,18 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 9,
"id": "6e581f88",
- "metadata": {},
- "outputs": [],
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T15:32:44.429407Z",
+ "start_time": "2025-06-08T15:32:43.584559Z"
+ }
+ },
"source": [
"# NF‑κB Luciferase Reporter Assay – pylabrobot version\n",
"\n",
"import os\n",
"import sys\n",
- "os.getcwd()\n",
- "sys.path.append('/Users/guangxinzhang/Documents/Deep Potential/pylabrobot/myfile')\n",
- "\n",
"from pylabrobot.resources import Coordinate\n",
"from pylabrobot.liquid_handling.backends.chatterbox import LiquidHandlerChatterboxBackend\n",
"from pylabrobot.visualizer.visualizer import Visualizer\n",
@@ -24,33 +24,26 @@
" nest_1_reservoir_195ml,\n",
" opentrons_96_tiprack_300ul\n",
")\n",
- "from High_level_function.action_definition import DPLiquidHandler"
- ]
+ "from pylabrobot.liquid_handling import LiquidHandler"
+ ],
+ "outputs": [],
+ "execution_count": 1
},
{
"cell_type": "code",
- "execution_count": 10,
"id": "c3127d6e",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Setting up the liquid handler.\n",
- "Resource deck was assigned to the liquid handler.\n",
- "Resource trash_container was assigned to the liquid handler.\n",
- "Resource tiprack_1 was assigned to the liquid handler.\n",
- "Resource tiprack_4 was assigned to the liquid handler.\n",
- "Resource tiprack_8 was assigned to the liquid handler.\n",
- "Resource tiprack_11 was assigned to the liquid handler.\n",
- "Resource working_plate was assigned to the liquid handler.\n",
- "Resource reagent_stock was assigned to the liquid handler.\n",
- "Resource waste_liq was assigned to the liquid handler.\n"
- ]
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T15:32:45.234924Z",
+ "start_time": "2025-06-08T15:32:45.194957Z"
}
- ],
+ },
"source": [
+ "\n",
+ "# from pylabrobot.resources import set_volume_tracking\n",
+ "# from pylabrobot.resources import set_tip_tracking\n",
+ "# set_volume_tracking(enabled=True)\n",
+ "# set_tip_tracking(enabled=True)\n",
"# ──────────────────────────────────────\n",
"# User‑configurable constants (µL)\n",
"MEDIUM_VOL = 100 # volume of spent medium to remove\n",
@@ -62,7 +55,7 @@
"\n",
"# ──────────────────────────────────────\n",
"\n",
- "lh = DPLiquidHandler(backend=LiquidHandlerChatterboxBackend(), deck=OTDeck())\n",
+ "lh = LiquidHandler(backend=LiquidHandlerChatterboxBackend(), deck=OTDeck())\n",
"await lh.setup()\n",
"#vis = Visualizer(resource=lh)\n",
"#await vis.setup()\n",
@@ -88,14 +81,36 @@
"# 1‑channel waste reservoir at slot 9\n",
"waste_liq = nest_1_reservoir_195ml(name='waste_liq')\n",
"lh.deck.assign_child_at_slot(waste_liq, slot=9)"
- ]
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Setting up the liquid handler.\n",
+ "Resource deck was assigned to the liquid handler.\n",
+ "Resource trash_container was assigned to the liquid handler.\n",
+ "Resource tiprack_1 was assigned to the liquid handler.\n",
+ "Resource tiprack_4 was assigned to the liquid handler.\n",
+ "Resource tiprack_8 was assigned to the liquid handler.\n",
+ "Resource tiprack_11 was assigned to the liquid handler.\n",
+ "Resource working_plate was assigned to the liquid handler.\n",
+ "Resource reagent_stock was assigned to the liquid handler.\n",
+ "Resource waste_liq was assigned to the liquid handler.\n"
+ ]
+ }
+ ],
+ "execution_count": 2
},
{
"cell_type": "code",
- "execution_count": 11,
"id": "b5313453",
- "metadata": {},
- "outputs": [],
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T15:32:45.425953Z",
+ "start_time": "2025-06-08T15:32:45.420965Z"
+ }
+ },
"source": [
"pbs = reagent_stock[0][0]\n",
"lysis = reagent_stock[1][0]\n",
@@ -103,14 +118,19 @@
"waste_liq = waste_liq[0]\n",
"wells_name = [f\"A{i}\" for i in range(1, 13)]\n",
"cells_all = working_plate[wells_name] # A1–A12"
- ]
+ ],
+ "outputs": [],
+ "execution_count": 3
},
{
"cell_type": "code",
- "execution_count": 12,
"id": "e85d6752",
- "metadata": {},
- "outputs": [],
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T15:32:46.480024Z",
+ "start_time": "2025-06-08T15:32:46.473896Z"
+ }
+ },
"source": [
"working_plate_volumes = [\n",
" ('culture medium', MEDIUM_VOL) if i % 8 == 0 else (None, 0)\n",
@@ -119,14 +139,54 @@
"working_plate.set_well_liquids(working_plate_volumes)\n",
"reagent_info = [('PBS Buffer', 5000), ('Lysis Buffer', 5000), ('Luciferase Reagent', 5000)]+[ (None, 0) ]* 9\n",
"reagent_stock.set_well_liquids(reagent_info)\n",
- "lh.set_tiprack(list(tipracks.values()))"
- ]
+ "# lh.deck.set_tiprack(list(tipracks.values()))"
+ ],
+ "outputs": [],
+ "execution_count": 4
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T15:32:47.742983Z",
+ "start_time": "2025-06-08T15:32:47.731878Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# state = reagent_stock.serialize_all_state()\n",
+ "# print(state)\n",
+ "reagent_stock.load_all_state({'reagent_stock': {}, 'reagent_stock_A1': {'liquids': [['PBS Buffer', 5000]], 'pending_liquids': [], 'liquid_history': []}, 'reagent_stock_A2': {'liquids': [['Lysis Buffer', 5000]], 'pending_liquids': [['Lysis Buffer', 5000]], 'liquid_history': ['Lysis Buffer']}, 'reagent_stock_A3': {'liquids': [['Luciferase Reagent', 5000]], 'pending_liquids': [['Luciferase Reagent', 5000]], 'liquid_history': ['Luciferase Reagent']}, 'reagent_stock_A4': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A5': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A6': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A7': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A8': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A9': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A10': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A11': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}, 'reagent_stock_A12': {'liquids': [[None, 0]], 'pending_liquids': [[None, 0]], 'liquid_history': [None]}})\n",
+ "\n",
+ "reagent_stock[0][0].tracker.liquids"
+ ],
+ "id": "cd6ad8fb6494f14a",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[('PBS Buffer', 5000)]"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 5
},
{
"cell_type": "code",
- "execution_count": 13,
"id": "9dbfb0e2",
- "metadata": {},
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T15:32:02.047523Z",
+ "start_time": "2025-06-08T15:32:02.042034Z"
+ }
+ },
+ "source": [
+ "from pylabrobot.resources import set_tip_tracking, set_volume_tracking\n",
+ "set_tip_tracking(True), set_volume_tracking(True)"
+ ],
"outputs": [
{
"data": {
@@ -134,185 +194,9106 @@
"(None, None)"
]
},
- "execution_count": 13,
+ "execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
- "source": [
- "from pylabrobot.resources import set_tip_tracking, set_volume_tracking\n",
- "set_tip_tracking(True), set_volume_tracking(True)"
- ]
+ "execution_count": 12
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T16:04:23.597819Z",
+ "start_time": "2025-06-08T16:04:23.427478Z"
+ }
+ },
+ "cell_type": "code",
+ "source": "lh.serialize()",
+ "id": "6b2801f34ac96ef6",
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'name': 'lh_deck',\n",
+ " 'type': 'LiquidHandler',\n",
+ " 'size_x': 624.3,\n",
+ " 'size_y': 565.2,\n",
+ " 'size_z': 900,\n",
+ " 'location': {'x': 0, 'y': 0, 'z': 0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'liquid_handler',\n",
+ " 'model': None,\n",
+ " 'children': [{'name': 'deck',\n",
+ " 'type': 'OTDeck',\n",
+ " 'size_x': 624.3,\n",
+ " 'size_y': 565.2,\n",
+ " 'size_z': 900,\n",
+ " 'location': {'x': 0, 'y': 0, 'z': 0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'deck',\n",
+ " 'children': [{'name': 'trash_container',\n",
+ " 'type': 'Resource',\n",
+ " 'size_x': 172.86,\n",
+ " 'size_y': 165.86,\n",
+ " 'size_z': 82,\n",
+ " 'location': {'x': 265.0, 'y': 271.5, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': None,\n",
+ " 'model': None,\n",
+ " 'children': [{'name': 'trash',\n",
+ " 'type': 'Trash',\n",
+ " 'size_x': 172.86,\n",
+ " 'size_y': 165.86,\n",
+ " 'size_z': 82,\n",
+ " 'location': {'x': 0, 'y': 0, 'z': 0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'trash',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'trash_container',\n",
+ " 'max_volume': 'Infinity',\n",
+ " 'material_z_thickness': 0,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None}],\n",
+ " 'parent_name': 'deck'},\n",
+ " {'name': 'tiprack_1',\n",
+ " 'type': 'TipRack',\n",
+ " 'size_x': 127.76,\n",
+ " 'size_y': 85.48,\n",
+ " 'size_z': 64.49,\n",
+ " 'location': {'x': 0.0, 'y': 0.0, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_rack',\n",
+ " 'model': 'Opentrons OT-2 96 Tip Rack 300 µL',\n",
+ " 'children': [{'name': 'tiprack_1_A1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_A12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_B12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_C12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_D12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_E12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_F12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_G12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_1_H12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_1',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}}],\n",
+ " 'parent_name': 'deck',\n",
+ " 'ordering': ['A1',\n",
+ " 'B1',\n",
+ " 'C1',\n",
+ " 'D1',\n",
+ " 'E1',\n",
+ " 'F1',\n",
+ " 'G1',\n",
+ " 'H1',\n",
+ " 'A2',\n",
+ " 'B2',\n",
+ " 'C2',\n",
+ " 'D2',\n",
+ " 'E2',\n",
+ " 'F2',\n",
+ " 'G2',\n",
+ " 'H2',\n",
+ " 'A3',\n",
+ " 'B3',\n",
+ " 'C3',\n",
+ " 'D3',\n",
+ " 'E3',\n",
+ " 'F3',\n",
+ " 'G3',\n",
+ " 'H3',\n",
+ " 'A4',\n",
+ " 'B4',\n",
+ " 'C4',\n",
+ " 'D4',\n",
+ " 'E4',\n",
+ " 'F4',\n",
+ " 'G4',\n",
+ " 'H4',\n",
+ " 'A5',\n",
+ " 'B5',\n",
+ " 'C5',\n",
+ " 'D5',\n",
+ " 'E5',\n",
+ " 'F5',\n",
+ " 'G5',\n",
+ " 'H5',\n",
+ " 'A6',\n",
+ " 'B6',\n",
+ " 'C6',\n",
+ " 'D6',\n",
+ " 'E6',\n",
+ " 'F6',\n",
+ " 'G6',\n",
+ " 'H6',\n",
+ " 'A7',\n",
+ " 'B7',\n",
+ " 'C7',\n",
+ " 'D7',\n",
+ " 'E7',\n",
+ " 'F7',\n",
+ " 'G7',\n",
+ " 'H7',\n",
+ " 'A8',\n",
+ " 'B8',\n",
+ " 'C8',\n",
+ " 'D8',\n",
+ " 'E8',\n",
+ " 'F8',\n",
+ " 'G8',\n",
+ " 'H8',\n",
+ " 'A9',\n",
+ " 'B9',\n",
+ " 'C9',\n",
+ " 'D9',\n",
+ " 'E9',\n",
+ " 'F9',\n",
+ " 'G9',\n",
+ " 'H9',\n",
+ " 'A10',\n",
+ " 'B10',\n",
+ " 'C10',\n",
+ " 'D10',\n",
+ " 'E10',\n",
+ " 'F10',\n",
+ " 'G10',\n",
+ " 'H10',\n",
+ " 'A11',\n",
+ " 'B11',\n",
+ " 'C11',\n",
+ " 'D11',\n",
+ " 'E11',\n",
+ " 'F11',\n",
+ " 'G11',\n",
+ " 'H11',\n",
+ " 'A12',\n",
+ " 'B12',\n",
+ " 'C12',\n",
+ " 'D12',\n",
+ " 'E12',\n",
+ " 'F12',\n",
+ " 'G12',\n",
+ " 'H12']},\n",
+ " {'name': 'tiprack_4',\n",
+ " 'type': 'TipRack',\n",
+ " 'size_x': 127.76,\n",
+ " 'size_y': 85.48,\n",
+ " 'size_z': 64.49,\n",
+ " 'location': {'x': 0.0, 'y': 90.5, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_rack',\n",
+ " 'model': 'Opentrons OT-2 96 Tip Rack 300 µL',\n",
+ " 'children': [{'name': 'tiprack_4_A1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_A12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_B12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_C12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_D12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_E12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_F12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_G12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_4_H12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_4',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}}],\n",
+ " 'parent_name': 'deck',\n",
+ " 'ordering': ['A1',\n",
+ " 'B1',\n",
+ " 'C1',\n",
+ " 'D1',\n",
+ " 'E1',\n",
+ " 'F1',\n",
+ " 'G1',\n",
+ " 'H1',\n",
+ " 'A2',\n",
+ " 'B2',\n",
+ " 'C2',\n",
+ " 'D2',\n",
+ " 'E2',\n",
+ " 'F2',\n",
+ " 'G2',\n",
+ " 'H2',\n",
+ " 'A3',\n",
+ " 'B3',\n",
+ " 'C3',\n",
+ " 'D3',\n",
+ " 'E3',\n",
+ " 'F3',\n",
+ " 'G3',\n",
+ " 'H3',\n",
+ " 'A4',\n",
+ " 'B4',\n",
+ " 'C4',\n",
+ " 'D4',\n",
+ " 'E4',\n",
+ " 'F4',\n",
+ " 'G4',\n",
+ " 'H4',\n",
+ " 'A5',\n",
+ " 'B5',\n",
+ " 'C5',\n",
+ " 'D5',\n",
+ " 'E5',\n",
+ " 'F5',\n",
+ " 'G5',\n",
+ " 'H5',\n",
+ " 'A6',\n",
+ " 'B6',\n",
+ " 'C6',\n",
+ " 'D6',\n",
+ " 'E6',\n",
+ " 'F6',\n",
+ " 'G6',\n",
+ " 'H6',\n",
+ " 'A7',\n",
+ " 'B7',\n",
+ " 'C7',\n",
+ " 'D7',\n",
+ " 'E7',\n",
+ " 'F7',\n",
+ " 'G7',\n",
+ " 'H7',\n",
+ " 'A8',\n",
+ " 'B8',\n",
+ " 'C8',\n",
+ " 'D8',\n",
+ " 'E8',\n",
+ " 'F8',\n",
+ " 'G8',\n",
+ " 'H8',\n",
+ " 'A9',\n",
+ " 'B9',\n",
+ " 'C9',\n",
+ " 'D9',\n",
+ " 'E9',\n",
+ " 'F9',\n",
+ " 'G9',\n",
+ " 'H9',\n",
+ " 'A10',\n",
+ " 'B10',\n",
+ " 'C10',\n",
+ " 'D10',\n",
+ " 'E10',\n",
+ " 'F10',\n",
+ " 'G10',\n",
+ " 'H10',\n",
+ " 'A11',\n",
+ " 'B11',\n",
+ " 'C11',\n",
+ " 'D11',\n",
+ " 'E11',\n",
+ " 'F11',\n",
+ " 'G11',\n",
+ " 'H11',\n",
+ " 'A12',\n",
+ " 'B12',\n",
+ " 'C12',\n",
+ " 'D12',\n",
+ " 'E12',\n",
+ " 'F12',\n",
+ " 'G12',\n",
+ " 'H12']},\n",
+ " {'name': 'tiprack_8',\n",
+ " 'type': 'TipRack',\n",
+ " 'size_x': 127.76,\n",
+ " 'size_y': 85.48,\n",
+ " 'size_z': 64.49,\n",
+ " 'location': {'x': 132.5, 'y': 181.0, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_rack',\n",
+ " 'model': 'Opentrons OT-2 96 Tip Rack 300 µL',\n",
+ " 'children': [{'name': 'tiprack_8_A1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_A12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_B12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_C12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_D12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_E12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_F12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_G12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_8_H12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_8',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}}],\n",
+ " 'parent_name': 'deck',\n",
+ " 'ordering': ['A1',\n",
+ " 'B1',\n",
+ " 'C1',\n",
+ " 'D1',\n",
+ " 'E1',\n",
+ " 'F1',\n",
+ " 'G1',\n",
+ " 'H1',\n",
+ " 'A2',\n",
+ " 'B2',\n",
+ " 'C2',\n",
+ " 'D2',\n",
+ " 'E2',\n",
+ " 'F2',\n",
+ " 'G2',\n",
+ " 'H2',\n",
+ " 'A3',\n",
+ " 'B3',\n",
+ " 'C3',\n",
+ " 'D3',\n",
+ " 'E3',\n",
+ " 'F3',\n",
+ " 'G3',\n",
+ " 'H3',\n",
+ " 'A4',\n",
+ " 'B4',\n",
+ " 'C4',\n",
+ " 'D4',\n",
+ " 'E4',\n",
+ " 'F4',\n",
+ " 'G4',\n",
+ " 'H4',\n",
+ " 'A5',\n",
+ " 'B5',\n",
+ " 'C5',\n",
+ " 'D5',\n",
+ " 'E5',\n",
+ " 'F5',\n",
+ " 'G5',\n",
+ " 'H5',\n",
+ " 'A6',\n",
+ " 'B6',\n",
+ " 'C6',\n",
+ " 'D6',\n",
+ " 'E6',\n",
+ " 'F6',\n",
+ " 'G6',\n",
+ " 'H6',\n",
+ " 'A7',\n",
+ " 'B7',\n",
+ " 'C7',\n",
+ " 'D7',\n",
+ " 'E7',\n",
+ " 'F7',\n",
+ " 'G7',\n",
+ " 'H7',\n",
+ " 'A8',\n",
+ " 'B8',\n",
+ " 'C8',\n",
+ " 'D8',\n",
+ " 'E8',\n",
+ " 'F8',\n",
+ " 'G8',\n",
+ " 'H8',\n",
+ " 'A9',\n",
+ " 'B9',\n",
+ " 'C9',\n",
+ " 'D9',\n",
+ " 'E9',\n",
+ " 'F9',\n",
+ " 'G9',\n",
+ " 'H9',\n",
+ " 'A10',\n",
+ " 'B10',\n",
+ " 'C10',\n",
+ " 'D10',\n",
+ " 'E10',\n",
+ " 'F10',\n",
+ " 'G10',\n",
+ " 'H10',\n",
+ " 'A11',\n",
+ " 'B11',\n",
+ " 'C11',\n",
+ " 'D11',\n",
+ " 'E11',\n",
+ " 'F11',\n",
+ " 'G11',\n",
+ " 'H11',\n",
+ " 'A12',\n",
+ " 'B12',\n",
+ " 'C12',\n",
+ " 'D12',\n",
+ " 'E12',\n",
+ " 'F12',\n",
+ " 'G12',\n",
+ " 'H12']},\n",
+ " {'name': 'tiprack_11',\n",
+ " 'type': 'TipRack',\n",
+ " 'size_x': 127.76,\n",
+ " 'size_y': 85.48,\n",
+ " 'size_z': 64.49,\n",
+ " 'location': {'x': 132.5, 'y': 271.5, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_rack',\n",
+ " 'model': 'Opentrons OT-2 96 Tip Rack 300 µL',\n",
+ " 'children': [{'name': 'tiprack_11_A1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H1',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 12.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H2',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 21.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H3',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 30.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H4',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 39.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H5',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 48.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H6',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 57.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H7',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 66.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H8',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 75.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H9',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 84.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 72.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 63.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 54.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 45.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 36.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 27.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 18.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H10',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 93.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H11',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 102.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_A12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 72.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_B12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 63.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_C12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 54.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_D12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 45.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_E12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 36.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_F12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 27.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_G12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531,\n",
+ " 'y': 18.391,\n",
+ " 'z': 5.39,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}},\n",
+ " {'name': 'tiprack_11_H12',\n",
+ " 'type': 'TipSpot',\n",
+ " 'size_x': 3.698,\n",
+ " 'size_y': 3.698,\n",
+ " 'size_z': 0,\n",
+ " 'location': {'x': 111.531, 'y': 9.391, 'z': 5.39, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'tip_spot',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'tiprack_11',\n",
+ " 'prototype_tip': {'type': 'Tip',\n",
+ " 'total_tip_length': 59.3,\n",
+ " 'has_filter': False,\n",
+ " 'maximal_volume': 300.0,\n",
+ " 'fitting_depth': 7.47}}],\n",
+ " 'parent_name': 'deck',\n",
+ " 'ordering': ['A1',\n",
+ " 'B1',\n",
+ " 'C1',\n",
+ " 'D1',\n",
+ " 'E1',\n",
+ " 'F1',\n",
+ " 'G1',\n",
+ " 'H1',\n",
+ " 'A2',\n",
+ " 'B2',\n",
+ " 'C2',\n",
+ " 'D2',\n",
+ " 'E2',\n",
+ " 'F2',\n",
+ " 'G2',\n",
+ " 'H2',\n",
+ " 'A3',\n",
+ " 'B3',\n",
+ " 'C3',\n",
+ " 'D3',\n",
+ " 'E3',\n",
+ " 'F3',\n",
+ " 'G3',\n",
+ " 'H3',\n",
+ " 'A4',\n",
+ " 'B4',\n",
+ " 'C4',\n",
+ " 'D4',\n",
+ " 'E4',\n",
+ " 'F4',\n",
+ " 'G4',\n",
+ " 'H4',\n",
+ " 'A5',\n",
+ " 'B5',\n",
+ " 'C5',\n",
+ " 'D5',\n",
+ " 'E5',\n",
+ " 'F5',\n",
+ " 'G5',\n",
+ " 'H5',\n",
+ " 'A6',\n",
+ " 'B6',\n",
+ " 'C6',\n",
+ " 'D6',\n",
+ " 'E6',\n",
+ " 'F6',\n",
+ " 'G6',\n",
+ " 'H6',\n",
+ " 'A7',\n",
+ " 'B7',\n",
+ " 'C7',\n",
+ " 'D7',\n",
+ " 'E7',\n",
+ " 'F7',\n",
+ " 'G7',\n",
+ " 'H7',\n",
+ " 'A8',\n",
+ " 'B8',\n",
+ " 'C8',\n",
+ " 'D8',\n",
+ " 'E8',\n",
+ " 'F8',\n",
+ " 'G8',\n",
+ " 'H8',\n",
+ " 'A9',\n",
+ " 'B9',\n",
+ " 'C9',\n",
+ " 'D9',\n",
+ " 'E9',\n",
+ " 'F9',\n",
+ " 'G9',\n",
+ " 'H9',\n",
+ " 'A10',\n",
+ " 'B10',\n",
+ " 'C10',\n",
+ " 'D10',\n",
+ " 'E10',\n",
+ " 'F10',\n",
+ " 'G10',\n",
+ " 'H10',\n",
+ " 'A11',\n",
+ " 'B11',\n",
+ " 'C11',\n",
+ " 'D11',\n",
+ " 'E11',\n",
+ " 'F11',\n",
+ " 'G11',\n",
+ " 'H11',\n",
+ " 'A12',\n",
+ " 'B12',\n",
+ " 'C12',\n",
+ " 'D12',\n",
+ " 'E12',\n",
+ " 'F12',\n",
+ " 'G12',\n",
+ " 'H12']},\n",
+ " {'name': 'working_plate',\n",
+ " 'type': 'Plate',\n",
+ " 'size_x': 127.76,\n",
+ " 'size_y': 85.47,\n",
+ " 'size_z': 14.22,\n",
+ " 'location': {'x': 265.0, 'y': 90.5, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'plate',\n",
+ " 'model': 'Corning 96 Well Plate 360 µL Flat',\n",
+ " 'children': [{'name': 'working_plate_A1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 11.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 20.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 29.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 38.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 47.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 56.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 65.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 74.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 83.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 92.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 101.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_A12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 71.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_B12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 62.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_C12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 53.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_D12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 44.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_E12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 35.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_F12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 26.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_G12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 17.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'},\n",
+ " {'name': 'working_plate_H12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 4.851,\n",
+ " 'size_y': 4.851,\n",
+ " 'size_z': 10.67,\n",
+ " 'location': {'x': 110.9545,\n",
+ " 'y': 8.8145,\n",
+ " 'z': 3.55,\n",
+ " 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'working_plate',\n",
+ " 'max_volume': 360,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'circle'}],\n",
+ " 'parent_name': 'deck',\n",
+ " 'ordering': ['A1',\n",
+ " 'B1',\n",
+ " 'C1',\n",
+ " 'D1',\n",
+ " 'E1',\n",
+ " 'F1',\n",
+ " 'G1',\n",
+ " 'H1',\n",
+ " 'A2',\n",
+ " 'B2',\n",
+ " 'C2',\n",
+ " 'D2',\n",
+ " 'E2',\n",
+ " 'F2',\n",
+ " 'G2',\n",
+ " 'H2',\n",
+ " 'A3',\n",
+ " 'B3',\n",
+ " 'C3',\n",
+ " 'D3',\n",
+ " 'E3',\n",
+ " 'F3',\n",
+ " 'G3',\n",
+ " 'H3',\n",
+ " 'A4',\n",
+ " 'B4',\n",
+ " 'C4',\n",
+ " 'D4',\n",
+ " 'E4',\n",
+ " 'F4',\n",
+ " 'G4',\n",
+ " 'H4',\n",
+ " 'A5',\n",
+ " 'B5',\n",
+ " 'C5',\n",
+ " 'D5',\n",
+ " 'E5',\n",
+ " 'F5',\n",
+ " 'G5',\n",
+ " 'H5',\n",
+ " 'A6',\n",
+ " 'B6',\n",
+ " 'C6',\n",
+ " 'D6',\n",
+ " 'E6',\n",
+ " 'F6',\n",
+ " 'G6',\n",
+ " 'H6',\n",
+ " 'A7',\n",
+ " 'B7',\n",
+ " 'C7',\n",
+ " 'D7',\n",
+ " 'E7',\n",
+ " 'F7',\n",
+ " 'G7',\n",
+ " 'H7',\n",
+ " 'A8',\n",
+ " 'B8',\n",
+ " 'C8',\n",
+ " 'D8',\n",
+ " 'E8',\n",
+ " 'F8',\n",
+ " 'G8',\n",
+ " 'H8',\n",
+ " 'A9',\n",
+ " 'B9',\n",
+ " 'C9',\n",
+ " 'D9',\n",
+ " 'E9',\n",
+ " 'F9',\n",
+ " 'G9',\n",
+ " 'H9',\n",
+ " 'A10',\n",
+ " 'B10',\n",
+ " 'C10',\n",
+ " 'D10',\n",
+ " 'E10',\n",
+ " 'F10',\n",
+ " 'G10',\n",
+ " 'H10',\n",
+ " 'A11',\n",
+ " 'B11',\n",
+ " 'C11',\n",
+ " 'D11',\n",
+ " 'E11',\n",
+ " 'F11',\n",
+ " 'G11',\n",
+ " 'H11',\n",
+ " 'A12',\n",
+ " 'B12',\n",
+ " 'C12',\n",
+ " 'D12',\n",
+ " 'E12',\n",
+ " 'F12',\n",
+ " 'G12',\n",
+ " 'H12']},\n",
+ " {'name': 'reagent_stock',\n",
+ " 'type': 'Plate',\n",
+ " 'size_x': 127.76,\n",
+ " 'size_y': 85.48,\n",
+ " 'size_z': 31.4,\n",
+ " 'location': {'x': 265.0, 'y': 0.0, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'plate',\n",
+ " 'model': 'NEST 12 Well Reservoir 15 mL',\n",
+ " 'children': [{'name': 'reagent_stock_A1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 10.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A2',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 19.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A3',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 28.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A4',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 37.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A5',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 46.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A6',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 55.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A7',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 64.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A8',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 73.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A9',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 82.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A10',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 91.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A11',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 100.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'},\n",
+ " {'name': 'reagent_stock_A12',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 8.2,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 26.85,\n",
+ " 'location': {'x': 109.28, 'y': 7.18, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'reagent_stock',\n",
+ " 'max_volume': 15000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'}],\n",
+ " 'parent_name': 'deck',\n",
+ " 'ordering': ['A1',\n",
+ " 'A2',\n",
+ " 'A3',\n",
+ " 'A4',\n",
+ " 'A5',\n",
+ " 'A6',\n",
+ " 'A7',\n",
+ " 'A8',\n",
+ " 'A9',\n",
+ " 'A10',\n",
+ " 'A11',\n",
+ " 'A12']},\n",
+ " {'name': 'waste_liq',\n",
+ " 'type': 'Plate',\n",
+ " 'size_x': 127.76,\n",
+ " 'size_y': 85.48,\n",
+ " 'size_z': 31.4,\n",
+ " 'location': {'x': 265.0, 'y': 181.0, 'z': 0.0, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'plate',\n",
+ " 'model': 'NEST 1 Well Reservoir 195 mL',\n",
+ " 'children': [{'name': 'waste_liq_A1',\n",
+ " 'type': 'Well',\n",
+ " 'size_x': 106.8,\n",
+ " 'size_y': 71.2,\n",
+ " 'size_z': 25,\n",
+ " 'location': {'x': 10.48, 'y': 7.14, 'z': 4.55, 'type': 'Coordinate'},\n",
+ " 'rotation': {'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation'},\n",
+ " 'category': 'well',\n",
+ " 'model': None,\n",
+ " 'children': [],\n",
+ " 'parent_name': 'waste_liq',\n",
+ " 'max_volume': 195000,\n",
+ " 'material_z_thickness': None,\n",
+ " 'compute_volume_from_height': None,\n",
+ " 'compute_height_from_volume': None,\n",
+ " 'bottom_type': 'unknown',\n",
+ " 'cross_section_type': 'rectangle'}],\n",
+ " 'parent_name': 'deck',\n",
+ " 'ordering': ['A1']}],\n",
+ " 'parent_name': 'lh_deck'}],\n",
+ " 'parent_name': None,\n",
+ " 'backend': {'type': 'LiquidHandlerChatterboxBackend', 'num_channels': 8}}"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 6
},
{
"cell_type": "code",
- "execution_count": 14,
"id": "70094125",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_A1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A1 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_B1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A2 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_C1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A3 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_D1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A4 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_E1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A5 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_F1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A6 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_G1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A7 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_H1 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A8 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_A2 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A9 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_B2 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A10 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_C2 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A11 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n",
- "Picking up tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: tiprack_1_D2 0,0,0 Tip 300.0 7.47 59.3 No \n",
- "Aspirating:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 working_plate_A12 -2.5,0,0 0.2 None 0.2 \n",
- "[Well(name=waste_liq_A1, location=Coordinate(010.480, 007.140, 004.550), size_x=106.8, size_y=71.2, size_z=25, category=well)]\n",
- "Dispensing:\n",
- "pip# vol(ul) resource offset flow rate blowout lld_z \n",
- " p0: 100.0 waste_liq_A1 0,0,-5 3.0 None 0.0 \n",
- "Dropping tips:\n",
- "pip# resource offset tip type max volume (µL) fitting depth (mm) tip length (mm) filter \n",
- " p0: trash 0,0.0,0 Tip 300.0 7.47 59.3 No \n"
- ]
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-06-08T14:25:19.507693Z",
+ "start_time": "2025-06-08T14:25:18.487272Z"
}
- ],
+ },
"source": [
"await lh.remove_liquid(\n",
" vols=[MEDIUM_VOL]*12,\n",
@@ -323,7 +9304,21 @@
" flow_rates=[0.2,3],\n",
" offsets=[Coordinate(-2.5, 0, 0),Coordinate(0, 0, -5)]\n",
")"
- ]
+ ],
+ "outputs": [
+ {
+ "ename": "AttributeError",
+ "evalue": "'LiquidHandler' object has no attribute 'remove_liquid'",
+ "output_type": "error",
+ "traceback": [
+ "\u001B[31m---------------------------------------------------------------------------\u001B[39m",
+ "\u001B[31mAttributeError\u001B[39m Traceback (most recent call last)",
+ "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[6]\u001B[39m\u001B[32m, line 1\u001B[39m\n\u001B[32m----> \u001B[39m\u001B[32m1\u001B[39m \u001B[38;5;28;01mawait\u001B[39;00m \u001B[43mlh\u001B[49m\u001B[43m.\u001B[49m\u001B[43mremove_liquid\u001B[49m(\n\u001B[32m 2\u001B[39m vols=[MEDIUM_VOL]*\u001B[32m12\u001B[39m,\n\u001B[32m 3\u001B[39m sources=cells_all,\n\u001B[32m 4\u001B[39m waste_liquid=waste_liq,\n\u001B[32m 5\u001B[39m top=[-\u001B[32m0.2\u001B[39m],\n\u001B[32m 6\u001B[39m liquid_height=[\u001B[32m0.2\u001B[39m,\u001B[32m0\u001B[39m],\n\u001B[32m 7\u001B[39m flow_rates=[\u001B[32m0.2\u001B[39m,\u001B[32m3\u001B[39m],\n\u001B[32m 8\u001B[39m offsets=[Coordinate(-\u001B[32m2.5\u001B[39m, \u001B[32m0\u001B[39m, \u001B[32m0\u001B[39m),Coordinate(\u001B[32m0\u001B[39m, \u001B[32m0\u001B[39m, -\u001B[32m5\u001B[39m)]\n\u001B[32m 9\u001B[39m )\n",
+ "\u001B[31mAttributeError\u001B[39m: 'LiquidHandler' object has no attribute 'remove_liquid'"
+ ]
+ }
+ ],
+ "execution_count": 6
},
{
"cell_type": "code",
diff --git a/unilabos/devices/liquid_handling/liquid_handler_abstract.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py
index c349403e..4faa0427 100644
--- a/unilabos/devices/liquid_handling/liquid_handler_abstract.py
+++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py
@@ -6,13 +6,8 @@ import asyncio
import time
from pylabrobot.liquid_handling import LiquidHandler
-from pylabrobot.resources import (
- Resource,
- TipRack,
- Container,
- Coordinate,
- Well
-)
+from pylabrobot.resources import Resource, TipRack, Container, Coordinate, Well
+
class LiquidHandlerAbstract(LiquidHandler):
"""Extended LiquidHandler with additional operations."""
@@ -21,6 +16,19 @@ class LiquidHandlerAbstract(LiquidHandler):
# REMOVE LIQUID --------------------------------------------------
# ---------------------------------------------------------------
+ async def create_protocol(
+ self,
+ protocol_name: str,
+ protocol_description: str,
+ protocol_version: str,
+ protocol_author: str,
+ protocol_date: str,
+ protocol_type: str,
+ none_keys: List[str] = [],
+ ):
+ """Create a new protocol with the given metadata."""
+ pass
+
async def remove_liquid(
self,
vols: List[float],
@@ -35,26 +43,26 @@ class LiquidHandlerAbstract(LiquidHandler):
spread: Optional[Literal["wide", "tight", "custom"]] = "wide",
delays: Optional[List[int]] = None,
is_96_well: Optional[bool] = False,
- top: Optional[List(float)] = None,
- none_keys: List[str] = []
+ top: Optional[List[float]] = None,
+ none_keys: List[str] = [],
):
"""A complete *remove* (aspirate → waste) operation."""
trash = self.deck.get_trash_area()
try:
if is_96_well:
- pass # This mode is not verified
+ pass # This mode is not verified
else:
if len(vols) != len(sources):
raise ValueError("Length of `vols` must match `sources`.")
for src, vol in zip(sources, vols):
- self.move_to(src, dis_to_top=top[0] if top else 0)
+ await self.move_to(src, dis_to_top=top[0] if top else 0)
tip = next(self.current_tip)
await self.pick_up_tips(tip)
await self.aspirate(
resources=[src],
vols=[vol],
- use_channels=use_channels, # only aspirate96 used, default to None
+ use_channels=use_channels, # only aspirate96 used, default to None
flow_rates=[flow_rates[0]] if flow_rates else None,
offsets=[offsets[0]] if offsets else None,
liquid_height=[liquid_height[0]] if liquid_height else None,
@@ -64,15 +72,15 @@ class LiquidHandlerAbstract(LiquidHandler):
await self.custom_delay(seconds=delays[0] if delays else 0)
await self.dispense(
resources=waste_liquid,
- vols=[vol],
- use_channels=use_channels,
- flow_rates=[flow_rates[1]] if flow_rates else None,
- offsets=[offsets[1]] if offsets else None,
- liquid_height=[liquid_height[1]] if liquid_height else None,
- blow_out_air_volume=blow_out_air_volume[1] if blow_out_air_volume else None,
- spread=spread,
- )
- await self.discard_tips() # For now, each of tips is discarded after use
+ vols=[vol],
+ use_channels=use_channels,
+ flow_rates=[flow_rates[1]] if flow_rates else None,
+ offsets=[offsets[1]] if offsets else None,
+ liquid_height=[liquid_height[1]] if liquid_height else None,
+ blow_out_air_volume=blow_out_air_volume[1] if blow_out_air_volume else None,
+ spread=spread,
+ )
+ await self.discard_tips() # For now, each of tips is discarded after use
except Exception as e:
raise RuntimeError(f"Liquid removal failed: {e}") from e
@@ -100,13 +108,13 @@ class LiquidHandlerAbstract(LiquidHandler):
mix_vol: Optional[int] = None,
mix_rate: Optional[int] = None,
mix_liquid_height: Optional[float] = None,
- none_keys: List[str] = []
+ none_keys: List[str] = [],
):
"""A complete *add* (aspirate reagent → dispense into targets) operation."""
try:
if is_96_well:
- pass # This mode is not verified.
+ pass # This mode is not verified.
else:
if len(asp_vols) != len(targets):
raise ValueError("Length of `vols` must match `targets`.")
@@ -122,7 +130,7 @@ class LiquidHandlerAbstract(LiquidHandler):
offsets=[offsets[0]] if offsets else None,
liquid_height=[liquid_height[0]] if liquid_height else None,
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume else None,
- spread=spread
+ spread=spread,
)
if delays is not None:
await self.custom_delay(seconds=delays[0])
@@ -144,7 +152,8 @@ class LiquidHandlerAbstract(LiquidHandler):
mix_vol=mix_vol,
offsets=offsets if offsets else None,
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
- mix_rate=mix_rate if mix_rate else None)
+ mix_rate=mix_rate if mix_rate else None,
+ )
if delays is not None:
await self.custom_delay(seconds=delays[1])
await self.touch_tip(targets[_])
@@ -158,13 +167,13 @@ class LiquidHandlerAbstract(LiquidHandler):
# ---------------------------------------------------------------
async def transfer_liquid(
self,
- asp_vols: Union[List[float], float],
- dis_vols: Union[List[float], float],
sources: Sequence[Container],
targets: Sequence[Container],
tip_racks: Sequence[TipRack],
*,
use_channels: Optional[List[int]] = None,
+ asp_vols: Union[List[float], float],
+ dis_vols: Union[List[float], float],
asp_flow_rates: Optional[List[Optional[float]]] = None,
dis_flow_rates: Optional[List[Optional[float]]] = None,
offsets: Optional[List[Coordinate]] = None,
@@ -179,7 +188,7 @@ class LiquidHandlerAbstract(LiquidHandler):
mix_rate: Optional[int] = None,
mix_liquid_height: Optional[float] = None,
delays: Optional[List[int]] = None,
- none_keys: List[str] = []
+ none_keys: List[str] = [],
):
"""Transfer liquid from each *source* well/plate to the corresponding *target*.
@@ -201,14 +210,15 @@ class LiquidHandlerAbstract(LiquidHandler):
# 96‑channel head mode
# ------------------------------------------------------------------
if is_96_well:
- pass # This mode is not verified
+ pass # This mode is not verified
else:
if not (len(asp_vols) == len(sources) and len(dis_vols) == len(targets)):
raise ValueError("`sources`, `targets`, and `vols` must have the same length.")
tip_iter = self.iter_tips(tip_racks)
- for src, tgt, asp_vol, asp_flow_rate, dis_vol, dis_flow_rate in (
- zip(sources, targets, asp_vols, asp_flow_rates, dis_vols, dis_flow_rates)):
+ for src, tgt, asp_vol, asp_flow_rate, dis_vol, dis_flow_rate in zip(
+ sources, targets, asp_vols, asp_flow_rates, dis_vols, dis_flow_rates
+ ):
tip = next(tip_iter)
await self.pick_up_tips(tip)
# Aspirate from source
@@ -247,9 +257,9 @@ class LiquidHandlerAbstract(LiquidHandler):
except Exception as exc:
raise RuntimeError(f"Liquid transfer failed: {exc}") from exc
-# ---------------------------------------------------------------
-# Helper utilities
-# ---------------------------------------------------------------
+ # ---------------------------------------------------------------
+ # Helper utilities
+ # ---------------------------------------------------------------
async def custom_delay(self, seconds=0, msg=None):
"""
@@ -266,28 +276,26 @@ class LiquidHandlerAbstract(LiquidHandler):
print(f"Done: {msg}")
print(f"Current time: {time.strftime('%H:%M:%S')}")
- async def touch_tip(self,
- targets: Sequence[Container],
- ):
+ async def touch_tip(self, targets: Sequence[Container]):
"""Touch the tip to the side of the well."""
await self.aspirate(
resources=[targets],
vols=[0],
use_channels=None,
flow_rates=None,
- offsets=[Coordinate(x=-targets.get_size_x()/2,y=0,z=0)],
+ offsets=[Coordinate(x=-targets.get_size_x() / 2, y=0, z=0)],
liquid_height=None,
- blow_out_air_volume=None
+ blow_out_air_volume=None,
)
- #await self.custom_delay(seconds=1) # In the simulation, we do not need to wait
+ # await self.custom_delay(seconds=1) # In the simulation, we do not need to wait
await self.aspirate(
resources=[targets],
vols=[0],
use_channels=None,
flow_rates=None,
- offsets=[Coordinate(x=targets.get_size_x()/2,y=0,z=0)],
+ offsets=[Coordinate(x=targets.get_size_x() / 2, y=0, z=0)],
liquid_height=None,
- blow_out_air_volume=None
+ blow_out_air_volume=None,
)
async def mix(
@@ -298,9 +306,9 @@ class LiquidHandlerAbstract(LiquidHandler):
height_to_bottom: Optional[float] = None,
offsets: Optional[Coordinate] = None,
mix_rate: Optional[float] = None,
- none_keys: List[str] = []
+ none_keys: List[str] = [],
):
- if mix_time is None: # No mixing required
+ if mix_time is None: # No mixing required
return
"""Mix the liquid in the target wells."""
for _ in range(mix_time):
@@ -333,7 +341,7 @@ class LiquidHandlerAbstract(LiquidHandler):
tip_iter = self.iter_tips(tip_racks)
self.current_tip = tip_iter
- async def move_to(self, well: Well, dis_to_top: float = 0 , channel: int = 0):
+ async def move_to(self, well: Well, dis_to_top: float = 0, channel: int = 0):
"""
Move a single channel to a specific well with a given z-height.
@@ -352,4 +360,3 @@ class LiquidHandlerAbstract(LiquidHandler):
await self.move_channel_x(channel, abs_loc.x)
await self.move_channel_y(channel, abs_loc.y)
await self.move_channel_z(channel, abs_loc.z + well_height + dis_to_top)
-
diff --git a/unilabos/devices/liquid_handling/test liquid handler/convert_biomek.py b/unilabos/devices/liquid_handling/test liquid handler/convert_biomek.py
new file mode 100644
index 00000000..712fdd9f
--- /dev/null
+++ b/unilabos/devices/liquid_handling/test liquid handler/convert_biomek.py
@@ -0,0 +1,154 @@
+
+import json
+from typing import Sequence, Optional, List, Union, Literal
+json_path = "/Users/guangxinzhang/Documents/Deep Potential/opentrons/convert/protocols/enriched_steps/sci-lucif-assay4.json"
+
+with open(json_path, "r") as f:
+ data = json.load(f)
+
+transfer_example = data[0]
+#print(transfer_example)
+
+
+temp_protocol = []
+TipLocation = "BC1025F" # Assuming this is a fixed tip location for the transfer
+sources = transfer_example["sources"] # Assuming sources is a list of Container objects
+targets = transfer_example["targets"] # Assuming targets is a list of Container objects
+tip_racks = transfer_example["tip_racks"] # Assuming tip_racks is a list of TipRack objects
+asp_vols = transfer_example["asp_vols"] # Assuming asp_vols is a list of volumes
+solvent = "PBS"
+
+def transfer_liquid(
+ #self,
+ sources,#: Sequence[Container],
+ targets,#: Sequence[Container],
+ tip_racks,#: Sequence[TipRack],
+ TipLocation,
+ # *,
+ # use_channels: Optional[List[int]] = None,
+ asp_vols: Union[List[float], float],
+ solvent: Optional[str] = None,
+ # dis_vols: Union[List[float], float],
+ # asp_flow_rates: Optional[List[Optional[float]]] = None,
+ # dis_flow_rates: Optional[List[Optional[float]]] = None,
+ # offsets,#: Optional[List[]] = None,
+ # touch_tip: bool = False,
+ # liquid_height: Optional[List[Optional[float]]] = None,
+ # blow_out_air_volume: Optional[List[Optional[float]]] = None,
+ # spread: Literal["wide", "tight", "custom"] = "wide",
+ # is_96_well: bool = False,
+ # mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
+ # mix_times,#: Optional[list() = None,
+ # mix_vol: Optional[int] = None,
+ # mix_rate: Optional[int] = None,
+ # mix_liquid_height: Optional[float] = None,
+ # delays: Optional[List[int]] = None,
+ # none_keys: List[str] = []
+ ):
+ # -------- Build Biomek transfer step --------
+ # 1) Construct default parameter scaffold (values mirror Biomek “Transfer” block).
+
+ transfer_params = {
+ "Span8": False,
+ "Pod": "Pod1",
+ "items": {}, # to be filled below
+ "Wash": False,
+ "Dynamic?": True,
+ "AutoSelectActiveWashTechnique": False,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": False,
+ "ChangeTipsBetweenSources": False,
+ "DefaultCaption": "", # filled after we know first pair/vol
+ "UseExpression": False,
+ "LeaveTipsOn": False,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": False,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": False,
+ "ShowTransferDetails": True,
+ "Solvent": "Water",
+ "Span8Wash": False,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": False,
+ "SplitVolumeCleaning": False,
+ "Stop": "Destinations",
+ "TipLocation": "BC1025F",
+ "UseCurrentTips": False,
+ "UseDisposableTips": True,
+ "UseFixedTips": False,
+ "UseJIT": True,
+ "UseMandrelSelection": True,
+ "UseProbes": [True, True, True, True, True, True, True, True],
+ "WashCycles": "1",
+ "WashVolume": "110%",
+ "Wizard": False
+ }
+
+ items: dict = {}
+ for idx, (src, dst) in enumerate(zip(sources, targets)):
+ items[str(idx)] = {
+ "Source": str(src),
+ "Destination": str(dst),
+ "Volume": asp_vols[idx]
+ }
+ transfer_params["items"] = items
+ transfer_params["Solvent"] = solvent if solvent else "Water"
+ transfer_params["TipLocation"] = TipLocation
+
+ if len(tip_racks) == 1:
+ transfer_params['UseCurrentTips'] = True
+ elif len(tip_racks) > 1:
+ transfer_params["ChangeTipsBetweenDests"] = True
+
+ return transfer_params
+
+action = transfer_liquid(sources=sources,targets=targets,tip_racks=tip_racks, asp_vols=asp_vols,solvent = solvent, TipLocation=TipLocation)
+print(json.dumps(action,indent=2))
+# print(action)
+
+
+
+
+"""
+ "transfer": {
+
+ "items": {},
+ "Wash": false,
+ "Dynamic?": true,
+ "AutoSelectActiveWashTechnique": false,
+ "ActiveWashTechnique": "",
+ "ChangeTipsBetweenDests": true,
+ "ChangeTipsBetweenSources": false,
+ "DefaultCaption": "Transfer 100 µL from P13 to P3",
+ "UseExpression": false,
+ "LeaveTipsOn": false,
+ "MandrelExpression": "",
+ "Repeats": "1",
+ "RepeatsByVolume": false,
+ "Replicates": "1",
+ "ShowTipHandlingDetails": true,
+ "ShowTransferDetails": true,
+
+ "Span8Wash": false,
+ "Span8WashVolume": "2",
+ "Span8WasteVolume": "1",
+ "SplitVolume": false,
+ "SplitVolumeCleaning": false,
+ "Stop": "Destinations",
+ "TipLocation": "BC1025F",
+ "UseCurrentTips": false,
+ "UseDisposableTips": false,
+ "UseFixedTips": false,
+ "UseJIT": true,
+ "UseMandrelSelection": true,
+ "UseProbes": [true, true, true, true, true, true, true, true],
+ "WashCycles": "3",
+ "WashVolume": "110%",
+ "Wizard": false
+"""
+
+
+
+
diff --git a/unilabos/devices/liquid_handling/test liquid handler/sci-lucif-assay4.json b/unilabos/devices/liquid_handling/test liquid handler/sci-lucif-assay4.json
new file mode 100644
index 00000000..012a1606
--- /dev/null
+++ b/unilabos/devices/liquid_handling/test liquid handler/sci-lucif-assay4.json
@@ -0,0 +1,4033 @@
+[
+ {
+ "template": "transfer",
+ "sources": [
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ }
+ ],
+ "targets": [
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ }
+ ],
+ "tip_racks": [
+ {
+ "well": "A1",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A2",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A3",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A4",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A5",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A6",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A7",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A8",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A9",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A10",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A11",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ },
+ {
+ "well": "A12",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 8
+ }
+ ],
+ "use_channels": null,
+ "asp_vols": [
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0
+ ],
+ "asp_flow_rates": [
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8
+ ],
+ "disp_vols": [
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0,
+ 120.0
+ ],
+ "dis_flow_rates": [
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0
+ ],
+ "offsets": null,
+ "touch_tip": false,
+ "liquid_height": null,
+ "blow_out_air_volume": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "is_96_well": false,
+ "mix_stage": "none",
+ "mix_times": 0,
+ "mix_vol": null,
+ "mix_rate": null,
+ "mix_liquid_height": null,
+ "delays": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "top": [
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5
+ ],
+ "bottom": [
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null
+ ],
+ "move": [
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null
+ ],
+ "center": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ],
+ "move_to": [
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ],
+ "mix_detail": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ]
+ },
+ {
+ "template": "transfer",
+ "sources": [
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "reagent stock",
+ "slot": 3
+ }
+ ],
+ "targets": [
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ }
+ ],
+ "tip_racks": [
+ {
+ "well": "A1",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ }
+ ],
+ "use_channels": null,
+ "asp_vols": [
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0
+ ],
+ "asp_flow_rates": [
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0
+ ],
+ "disp_vols": [
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0,
+ 70.0
+ ],
+ "dis_flow_rates": [
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2
+ ],
+ "offsets": null,
+ "touch_tip": true,
+ "liquid_height": null,
+ "blow_out_air_volume": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "is_96_well": false,
+ "mix_stage": "none",
+ "mix_times": 0,
+ "mix_vol": null,
+ "mix_rate": null,
+ "mix_liquid_height": null,
+ "delays": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "top": [
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2,
+ null,
+ -2
+ ],
+ "bottom": [
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null
+ ],
+ "move": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "center": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ],
+ "move_to": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ],
+ "mix_detail": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ]
+ },
+ {
+ "template": "transfer",
+ "sources": [
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ }
+ ],
+ "targets": [
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ },
+ {
+ "well": "A1",
+ "labware": "waste",
+ "slot": 9
+ }
+ ],
+ "tip_racks": [
+ {
+ "well": "A2",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A3",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A4",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A5",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A6",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A7",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A8",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A9",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A10",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A11",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A12",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 11
+ },
+ {
+ "well": "A1",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ }
+ ],
+ "use_channels": null,
+ "asp_vols": [
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0
+ ],
+ "asp_flow_rates": [
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8,
+ 18.8
+ ],
+ "disp_vols": [
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0
+ ],
+ "dis_flow_rates": [
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0
+ ],
+ "offsets": null,
+ "touch_tip": false,
+ "liquid_height": null,
+ "blow_out_air_volume": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "is_96_well": false,
+ "mix_stage": "none",
+ "mix_times": 0,
+ "mix_vol": null,
+ "mix_rate": null,
+ "mix_liquid_height": null,
+ "delays": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "top": [
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5,
+ null,
+ -5
+ ],
+ "bottom": [
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null,
+ 0.2,
+ null
+ ],
+ "move": [
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null,
+ [
+ -2.5,
+ 0.0,
+ 0.0
+ ],
+ null
+ ],
+ "center": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ],
+ "move_to": [
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ -0.2
+ ],
+ "bottom": [
+ null
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ],
+ "mix_detail": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ]
+ },
+ {
+ "template": "transfer",
+ "sources": [
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "reagent stock",
+ "slot": 3
+ }
+ ],
+ "targets": [
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ }
+ ],
+ "tip_racks": [
+ {
+ "well": "A2",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ }
+ ],
+ "use_channels": null,
+ "asp_vols": [
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0
+ ],
+ "asp_flow_rates": [
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0,
+ 47.0
+ ],
+ "disp_vols": [
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0,
+ 30.0
+ ],
+ "dis_flow_rates": [
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2,
+ 28.2
+ ],
+ "offsets": null,
+ "touch_tip": true,
+ "liquid_height": null,
+ "blow_out_air_volume": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "is_96_well": false,
+ "mix_stage": "none",
+ "mix_times": 0,
+ "mix_vol": null,
+ "mix_rate": null,
+ "mix_liquid_height": null,
+ "delays": [
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 0
+ ],
+ "top": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "bottom": [
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5,
+ 0.5,
+ 5
+ ],
+ "move": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "center": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ],
+ "move_to": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ],
+ "mix_detail": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ]
+ },
+ {
+ "template": "transfer",
+ "sources": [
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "reagent stock",
+ "slot": 3
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ }
+ ],
+ "targets": [
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A1",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A2",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A3",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A4",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A5",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A6",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A7",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A8",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A9",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A10",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A11",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ },
+ {
+ "well": "A12",
+ "labware": "working plate",
+ "slot": 6
+ }
+ ],
+ "tip_racks": [
+ {
+ "well": "A3",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A4",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A5",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A6",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A7",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A8",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A9",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A10",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A11",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A12",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 1
+ },
+ {
+ "well": "A1",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 4
+ },
+ {
+ "well": "A2",
+ "type": "Opentrons OT-2 96 Tip Rack 300 \u00b5L",
+ "slot": 4
+ }
+ ],
+ "use_channels": null,
+ "asp_vols": [
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0
+ ],
+ "asp_flow_rates": [
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0
+ ],
+ "disp_vols": [
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0,
+ 75.0
+ ],
+ "dis_flow_rates": [
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0,
+ 282.0
+ ],
+ "offsets": null,
+ "touch_tip": true,
+ "liquid_height": null,
+ "blow_out_air_volume": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "is_96_well": false,
+ "mix_stage": "after",
+ "mix_times": [
+ 3
+ ],
+ "mix_vol": 75.0,
+ "mix_rate": null,
+ "mix_liquid_height": null,
+ "delays": [
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2,
+ 2
+ ],
+ "top": [
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5,
+ null,
+ -0.5
+ ],
+ "bottom": [
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null,
+ 0.5,
+ null
+ ],
+ "move": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "center": [
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false
+ ],
+ "move_to": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ }
+ ],
+ "mix_detail": [
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ },
+ {
+ "top": [],
+ "bottom": [],
+ "move": [],
+ "center": []
+ },
+ {
+ "top": [
+ null
+ ],
+ "bottom": [
+ 0.5
+ ],
+ "move": [
+ null
+ ],
+ "center": [
+ false
+ ]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/unilabos/devices/mock/__init__.py b/unilabos/devices/mock/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/unilabos/devices/mock/mock_chiller.py b/unilabos/devices/mock/mock_chiller.py
new file mode 100644
index 00000000..fbb823c9
--- /dev/null
+++ b/unilabos/devices/mock/mock_chiller.py
@@ -0,0 +1,177 @@
+import time
+import threading
+
+
+class MockChiller:
+ def __init__(self, port: str = "MOCK"):
+ self.port = port
+ self._current_temperature: float = 25.0 # 室温开始
+ self._target_temperature: float = 25.0
+ self._status: str = "Idle"
+ self._is_cooling: bool = False
+ self._is_heating: bool = False
+ self._vessel = "Unknown"
+ self._purpose = "Unknown"
+
+ # 模拟温度变化的线程
+ self._temperature_thread = None
+ self._running = True
+ self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
+ self._temperature_thread.daemon = True
+ self._temperature_thread.start()
+
+ @property
+ def current_temperature(self) -> float:
+ """当前温度 - 会被自动识别的设备属性"""
+ return self._current_temperature
+
+ @property
+ def target_temperature(self) -> float:
+ """目标温度"""
+ return self._target_temperature
+
+ @property
+ def status(self) -> str:
+ """设备状态 - 会被自动识别的设备属性"""
+ return self._status
+
+ @property
+ def is_cooling(self) -> bool:
+ """是否正在冷却"""
+ return self._is_cooling
+
+ @property
+ def is_heating(self) -> bool:
+ """是否正在加热"""
+ return self._is_heating
+
+ @property
+ def vessel(self) -> str:
+ """当前操作的容器名称"""
+ return self._vessel
+
+ @property
+ def purpose(self) -> str:
+ """当前操作目的"""
+ return self._purpose
+
+ def heat_chill_start(self, vessel: str, temp: float, purpose: str):
+ """设置目标温度并记录容器和目的"""
+ self._vessel = str(vessel)
+ self._purpose = str(purpose)
+ self._target_temperature = float(temp)
+
+ diff = self._target_temperature - self._current_temperature
+ if abs(diff) < 0.1:
+ self._status = "At Target Temperature"
+ self._is_cooling = False
+ self._is_heating = False
+ elif diff < 0:
+ self._status = "Cooling"
+ self._is_cooling = True
+ self._is_heating = False
+ else:
+ self._status = "Heating"
+ self._is_heating = True
+ self._is_cooling = False
+
+ self._start_temperature_control()
+ return True
+
+ def heat_chill_stop(self, vessel: str):
+ """停止加热/制冷"""
+ if vessel != self._vessel:
+ return {"success": False, "status": f"Wrong vessel: expected {self._vessel}, got {vessel}"}
+
+ # 停止温度控制线程,锁定当前温度
+ self._stop_temperature_control()
+
+ # 更新状态
+ self._status = "Stopped"
+ self._is_cooling = False
+ self._is_heating = False
+
+ # 重新启动线程但保持温度
+ self._running = True
+ self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
+ self._temperature_thread.daemon = True
+ self._temperature_thread.start()
+
+ return {"success": True, "status": self._status}
+
+ def _start_temperature_control(self):
+ """启动温度控制线程"""
+ self._running = True
+ if self._temperature_thread is None or not self._temperature_thread.is_alive():
+ self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
+ self._temperature_thread.daemon = True
+ self._temperature_thread.start()
+
+ def _stop_temperature_control(self):
+ """停止温度控制"""
+ self._running = False
+ if self._temperature_thread:
+ self._temperature_thread.join(timeout=1.0)
+
+ def _temperature_control_loop(self):
+ """温度控制循环 - 模拟真实冷却器的温度变化"""
+ while self._running:
+ # 如果状态是 Stopped,不改变温度
+ if self._status == "Stopped":
+ time.sleep(1.0)
+ continue
+
+ temp_diff = self._target_temperature - self._current_temperature
+
+ if abs(temp_diff) < 0.1:
+ self._status = "At Target Temperature"
+ self._is_cooling = False
+ self._is_heating = False
+ elif temp_diff < 0:
+ self._status = "Cooling"
+ self._is_cooling = True
+ self._is_heating = False
+ self._current_temperature -= 0.5
+ else:
+ self._status = "Heating"
+ self._is_heating = True
+ self._is_cooling = False
+ self._current_temperature += 0.3
+
+ time.sleep(1.0)
+
+ def emergency_stop(self):
+ """紧急停止"""
+ self._status = "Emergency Stop"
+ self._stop_temperature_control()
+ self._is_cooling = False
+ self._is_heating = False
+
+ def get_status_info(self) -> dict:
+ """获取完整状态信息"""
+ return {
+ "current_temperature": self._current_temperature,
+ "target_temperature": self._target_temperature,
+ "status": self._status,
+ "is_cooling": self._is_cooling,
+ "is_heating": self._is_heating,
+ "vessel": self._vessel,
+ "purpose": self._purpose,
+ }
+
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ chiller = MockChiller()
+
+ # 测试基本功能
+ print("启动冷却器测试...")
+ print(f"初始状态: {chiller.get_status_info()}")
+
+ # 模拟运行10秒
+ for i in range(10):
+ time.sleep(1)
+ print(f"第{i+1}秒: 当前温度={chiller.current_temperature:.1f}°C, 状态={chiller.status}")
+
+ chiller.emergency_stop()
+ print("测试完成")
diff --git a/unilabos/devices/mock/mock_filter.py b/unilabos/devices/mock/mock_filter.py
new file mode 100644
index 00000000..f54e41ed
--- /dev/null
+++ b/unilabos/devices/mock/mock_filter.py
@@ -0,0 +1,235 @@
+import time
+import threading
+
+
+class MockFilter:
+ def __init__(self, port: str = "MOCK"):
+ # 基本参数初始化
+ self.port = port
+ self._status: str = "Idle"
+ self._is_filtering: bool = False
+
+ # 过滤性能参数
+ self._flow_rate: float = 1.0 # 流速(L/min)
+ self._pressure_drop: float = 0.0 # 压降(Pa)
+ self._filter_life: float = 100.0 # 滤芯寿命(%)
+
+ # 过滤操作参数
+ self._vessel: str = "" # 源容器
+ self._filtrate_vessel: str = "" # 目标容器
+ self._stir: bool = False # 是否搅拌
+ self._stir_speed: float = 0.0 # 搅拌速度
+ self._temperature: float = 25.0 # 温度(℃)
+ self._continue_heatchill: bool = False # 是否继续加热/制冷
+ self._target_volume: float = 0.0 # 目标过滤体积(L)
+ self._filtered_volume: float = 0.0 # 已过滤体积(L)
+ self._progress: float = 0.0 # 过滤进度(%)
+
+ # 线程控制
+ self._filter_thread = None
+ self._running = False
+
+ @property
+ def status(self) -> str:
+ return self._status
+
+ @property
+ def is_filtering(self) -> bool:
+ return self._is_filtering
+
+ @property
+ def flow_rate(self) -> float:
+ return self._flow_rate
+
+ @property
+ def pressure_drop(self) -> float:
+ return self._pressure_drop
+
+ @property
+ def filter_life(self) -> float:
+ return self._filter_life
+ # 新增 property
+ @property
+ def vessel(self) -> str:
+ return self._vessel
+
+ @property
+ def filtrate_vessel(self) -> str:
+ return self._filtrate_vessel
+
+ @property
+ def filtered_volume(self) -> float:
+ return self._filtered_volume
+
+ @property
+ def progress(self) -> float:
+ return self._progress
+
+ @property
+ def stir(self) -> bool:
+ return self._stir
+
+ @property
+ def stir_speed(self) -> float:
+ return self._stir_speed
+
+ @property
+ def temperature(self) -> float:
+ return self._temperature
+
+ @property
+ def continue_heatchill(self) -> bool:
+ return self._continue_heatchill
+
+ @property
+ def target_volume(self) -> float:
+ return self._target_volume
+
+ def filter(self, vessel: str, filtrate_vessel: str, stir: bool = False, stir_speed: float = 0.0, temp: float = 25.0, continue_heatchill: bool = False, volume: float = 0.0) -> dict:
+ """新的过滤操作"""
+ # 停止任何正在进行的过滤
+ if self._is_filtering:
+ self.stop_filtering()
+ # 验证参数
+ if volume <= 0:
+ return {"success": False, "message": "Target volume must be greater than 0"}
+ # 设置新的过滤参数
+ self._vessel = vessel
+ self._filtrate_vessel = filtrate_vessel
+ self._stir = stir
+ self._stir_speed = stir_speed
+ self._temperature = temp
+ self._continue_heatchill = continue_heatchill
+ self._target_volume = volume
+ # 重置过滤状态
+ self._filtered_volume = 0.0
+ self._progress = 0.0
+ self._status = "Starting Filter"
+ # 启动过滤过程
+ self._flow_rate = 1.0 # 设置默认流速
+ self._start_filter_process()
+
+ return {"success": True, "message": "Filter started"}
+
+ def stop_filtering(self):
+ """停止过滤"""
+ self._status = "Stopping Filter"
+ self._stop_filter_process()
+ self._flow_rate = 0.0
+ self._is_filtering = False
+ self._status = "Stopped"
+ return True
+
+ def replace_filter(self):
+ """更换滤芯"""
+ self._filter_life = 100.0
+ self._status = "Filter Replaced"
+ return True
+
+ def _start_filter_process(self):
+ """启动过滤过程线程"""
+ if not self._running:
+ self._running = True
+ self._is_filtering = True
+ self._filter_thread = threading.Thread(target=self._filter_loop)
+ self._filter_thread.daemon = True
+ self._filter_thread.start()
+
+ def _stop_filter_process(self):
+ """停止过滤过程"""
+ self._running = False
+ if self._filter_thread:
+ self._filter_thread.join(timeout=1.0)
+
+ def _filter_loop(self):
+ """过滤进程主循环"""
+ update_interval = 1.0 # 更新间隔(秒)
+
+ while self._running and self._is_filtering:
+ try:
+ self._status = "Filtering"
+
+ # 计算这一秒过滤的体积 (L/min -> L/s)
+ volume_increment = (self._flow_rate / 60.0) * update_interval
+
+ # 更新已过滤体积
+ self._filtered_volume += volume_increment
+
+ # 更新进度 (避免除零错误)
+ if self._target_volume > 0:
+ self._progress = min(100.0, (self._filtered_volume / self._target_volume) * 100.0)
+
+ # 更新滤芯寿命 (每过滤1L减少0.5%寿命)
+ self._filter_life = max(0.0, self._filter_life - (volume_increment * 0.5))
+
+ # 更新压降 (根据滤芯寿命和流速动态计算)
+ life_factor = self._filter_life / 100.0 # 将寿命转换为0-1的因子
+ flow_factor = self._flow_rate / 2.0 # 将流速标准化(假设2L/min是标准流速)
+ base_pressure = 100.0 # 基础压降
+ # 压降随滤芯寿命降低而增加,随流速增加而增加
+ self._pressure_drop = base_pressure * (2 - life_factor) * flow_factor
+
+ # 检查是否完成目标体积
+ if self._target_volume > 0 and self._filtered_volume >= self._target_volume:
+ self._status = "Completed"
+ self._progress = 100.0
+ self.stop_filtering()
+ break
+
+ # 检查滤芯寿命
+ if self._filter_life <= 10.0:
+ self._status = "Filter Needs Replacement"
+
+ time.sleep(update_interval)
+
+ except Exception as e:
+ print(f"Error in filter loop: {e}")
+ self.emergency_stop()
+ break
+
+ def emergency_stop(self):
+ """紧急停止"""
+ self._status = "Emergency Stop"
+ self._stop_filter_process()
+ self._is_filtering = False
+ self._flow_rate = 0.0
+
+ def get_status_info(self) -> dict:
+ """扩展的状态信息"""
+ return {
+ "status": self._status,
+ "is_filtering": self._is_filtering,
+ "flow_rate": self._flow_rate,
+ "pressure_drop": self._pressure_drop,
+ "filter_life": self._filter_life,
+ "vessel": self._vessel,
+ "filtrate_vessel": self._filtrate_vessel,
+ "filtered_volume": self._filtered_volume,
+ "target_volume": self._target_volume,
+ "progress": self._progress,
+ "temperature": self._temperature,
+ "stir": self._stir,
+ "stir_speed": self._stir_speed
+ }
+
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ filter_device = MockFilter()
+
+ # 测试基本功能
+ print("启动过滤器测试...")
+ print(f"初始状态: {filter_device.get_status_info()}")
+
+
+
+ # 模拟运行10秒
+ for i in range(10):
+ time.sleep(1)
+ print(
+ f"第{i+1}秒: "
+ f"寿命={filter_device.filter_life:.1f}%, 状态={filter_device.status}"
+ )
+
+ filter_device.emergency_stop()
+ print("测试完成")
diff --git a/unilabos/devices/mock/mock_heater.py b/unilabos/devices/mock/mock_heater.py
new file mode 100644
index 00000000..47dd8d85
--- /dev/null
+++ b/unilabos/devices/mock/mock_heater.py
@@ -0,0 +1,247 @@
+import time
+import threading
+
+class MockHeater:
+ def __init__(self, port: str = "MOCK"):
+ self.port = port
+ self._current_temperature: float = 25.0 # 室温开始
+ self._target_temperature: float = 25.0
+ self._status: str = "Idle"
+ self._is_heating: bool = False
+ self._heating_power: float = 0.0 # 加热功率百分比 0-100
+ self._max_temperature: float = 300.0 # 最大加热温度
+
+ # 新增加的属性
+ self._vessel: str = "Unknown"
+ self._purpose: str = "Unknown"
+ self._stir: bool = False
+ self._stir_speed: float = 0.0
+
+ # 模拟加热过程的线程
+ self._heating_thread = None
+ self._running = True
+ self._heating_thread = threading.Thread(target=self._heating_control_loop)
+ self._heating_thread.daemon = True
+ self._heating_thread.start()
+
+ @property
+ def current_temperature(self) -> float:
+ """当前温度 - 会被自动识别的设备属性"""
+ return self._current_temperature
+
+ @property
+ def target_temperature(self) -> float:
+ """目标温度"""
+ return self._target_temperature
+
+ @property
+ def status(self) -> str:
+ """设备状态 - 会被自动识别的设备属性"""
+ return self._status
+
+ @property
+ def is_heating(self) -> bool:
+ """是否正在加热"""
+ return self._is_heating
+
+ @property
+ def heating_power(self) -> float:
+ """加热功率百分比"""
+ return self._heating_power
+
+ @property
+ def max_temperature(self) -> float:
+ """最大加热温度"""
+ return self._max_temperature
+
+ @property
+ def vessel(self) -> str:
+ """当前操作的容器名称"""
+ return self._vessel
+
+ @property
+ def purpose(self) -> str:
+ """操作目的"""
+ return self._purpose
+
+ @property
+ def stir(self) -> bool:
+ """是否搅拌"""
+ return self._stir
+
+ @property
+ def stir_speed(self) -> float:
+ """搅拌速度"""
+ return self._stir_speed
+
+ def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> dict:
+ """开始加热/制冷过程"""
+ self._vessel = str(vessel)
+ self._purpose = str(purpose)
+ self._target_temperature = float(temp)
+
+ diff = self._target_temperature - self._current_temperature
+ if abs(diff) < 0.1:
+ self._status = "At Target Temperature"
+ self._is_heating = False
+ elif diff > 0:
+ self._status = "Heating"
+ self._is_heating = True
+ else:
+ self._status = "Cooling Down"
+ self._is_heating = False
+
+ return {"success": True, "status": self._status}
+
+ def heat_chill_stop(self, vessel: str) -> dict:
+ """停止加热/制冷"""
+ if vessel != self._vessel:
+ return {"success": False, "status": f"Wrong vessel: expected {self._vessel}, got {vessel}"}
+
+ self._status = "Stopped"
+ self._is_heating = False
+ self._heating_power = 0.0
+
+ return {"success": True, "status": self._status}
+
+ def heat_chill(self, vessel: str, temp: float, time: float,
+ stir: bool = False, stir_speed: float = 0.0,
+ purpose: str = "Unknown") -> dict:
+ """完整的加热/制冷控制"""
+ self._vessel = str(vessel)
+ self._target_temperature = float(temp)
+ self._purpose = str(purpose)
+ self._stir = stir
+ self._stir_speed = stir_speed
+
+ diff = self._target_temperature - self._current_temperature
+ if abs(diff) < 0.1:
+ self._status = "At Target Temperature"
+ self._is_heating = False
+ elif diff > 0:
+ self._status = "Heating"
+ self._is_heating = True
+ else:
+ self._status = "Cooling Down"
+ self._is_heating = False
+
+ return {"success": True, "status": self._status}
+
+ def set_temperature(self, temperature: float):
+ """设置目标温度 - 需要在注册表添加的设备动作"""
+ try:
+ temperature = float(temperature)
+ except ValueError:
+ self._status = "Error: Invalid temperature value"
+ return False
+
+ if temperature > self._max_temperature:
+ self._status = f"Error: Temperature exceeds maximum ({self._max_temperature}°C)"
+ return False
+
+ self._target_temperature = temperature
+ self._status = "Setting Temperature"
+
+ # 启动加热控制
+ self._start_heating_control()
+ return True
+
+ def set_heating_power(self, power: float):
+ """设置加热功率"""
+ try:
+ power = float(power)
+ except ValueError:
+ self._status = "Error: Invalid power value"
+ return False
+
+ self._heating_power = max(0.0, min(100.0, power)) # 限制在0-100%
+ return True
+
+ def _start_heating_control(self):
+ """启动加热控制线程"""
+ if not self._running:
+ self._running = True
+ self._heating_thread = threading.Thread(target=self._heating_control_loop)
+ self._heating_thread.daemon = True
+ self._heating_thread.start()
+
+ def _stop_heating_control(self):
+ """停止加热控制"""
+ self._running = False
+ if self._heating_thread:
+ self._heating_thread.join(timeout=1.0)
+
+ def _heating_control_loop(self):
+ """加热控制循环"""
+ while self._running:
+ # 如果状态是 Stopped,不改变温度
+ if self._status == "Stopped":
+ time.sleep(1.0)
+ continue
+
+ temp_diff = self._target_temperature - self._current_temperature
+
+ if abs(temp_diff) < 0.1:
+ self._status = "At Target Temperature"
+ self._is_heating = False
+ self._heating_power = 10.0
+ elif temp_diff > 0:
+ self._status = "Heating"
+ self._is_heating = True
+ self._heating_power = min(100.0, abs(temp_diff) * 2)
+ self._current_temperature += 0.5
+ else:
+ self._status = "Cooling Down"
+ self._is_heating = False
+ self._heating_power = 0.0
+ self._current_temperature -= 0.2
+
+ time.sleep(1.0)
+
+ def emergency_stop(self):
+ """紧急停止"""
+ self._status = "Emergency Stop"
+ self._stop_heating_control()
+ self._is_heating = False
+ self._heating_power = 0.0
+
+ def get_status_info(self) -> dict:
+ """获取完整状态信息"""
+ return {
+ "current_temperature": self._current_temperature,
+ "target_temperature": self._target_temperature,
+ "status": self._status,
+ "is_heating": self._is_heating,
+ "heating_power": self._heating_power,
+ "max_temperature": self._max_temperature,
+ "vessel": self._vessel,
+ "purpose": self._purpose,
+ "stir": self._stir,
+ "stir_speed": self._stir_speed
+ }
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ heater = MockHeater()
+
+ print("启动加热器测试...")
+ print(f"初始状态: {heater.get_status_info()}")
+
+ # 设置目标温度为80度
+ heater.set_temperature(80.0)
+
+ # 模拟运行15秒
+ try:
+ for i in range(15):
+ time.sleep(1)
+ status = heater.get_status_info()
+ print(
+ f"\r温度: {status['current_temperature']:.1f}°C / {status['target_temperature']:.1f}°C | "
+ f"功率: {status['heating_power']:.1f}% | 状态: {status['status']}",
+ end=""
+ )
+ except KeyboardInterrupt:
+ heater.emergency_stop()
+ print("\n测试被手动停止")
+
+ print("\n测试完成")
\ No newline at end of file
diff --git a/unilabos/devices/mock/mock_pump.py b/unilabos/devices/mock/mock_pump.py
new file mode 100644
index 00000000..43cbf007
--- /dev/null
+++ b/unilabos/devices/mock/mock_pump.py
@@ -0,0 +1,360 @@
+import time
+import threading
+from datetime import datetime, timedelta
+
+class MockPump:
+ def __init__(self, port: str = "MOCK"):
+ self.port = port
+
+ # 设备基本状态属性
+ self._current_device = "MockPump1" # 设备标识符
+ self._status: str = "Idle" # 设备状态:Idle, Running, Error, Stopped
+ self._pump_state: str = "Stopped" # 泵运行状态:Running, Stopped, Paused
+
+ # 流量相关属性
+ self._flow_rate: float = 0.0 # 当前流速 (mL/min)
+ self._target_flow_rate: float = 0.0 # 目标流速 (mL/min)
+ self._max_flow_rate: float = 100.0 # 最大流速 (mL/min)
+ self._total_volume: float = 0.0 # 累计流量 (mL)
+
+ # 压力相关属性
+ self._pressure: float = 0.0 # 当前压力 (bar)
+ self._max_pressure: float = 10.0 # 最大压力 (bar)
+
+ # 运行控制线程
+ self._pump_thread = None
+ self._running = False
+ self._thread_lock = threading.Lock()
+
+ # 新增 PumpTransfer 相关属性
+ self._from_vessel: str = ""
+ self._to_vessel: str = ""
+ self._transfer_volume: float = 0.0
+ self._amount: str = ""
+ self._transfer_time: float = 0.0
+ self._is_viscous: bool = False
+ self._rinsing_solvent: str = ""
+ self._rinsing_volume: float = 0.0
+ self._rinsing_repeats: int = 0
+ self._is_solid: bool = False
+
+ # 时间追踪
+ self._start_time: datetime = None
+ self._time_spent: timedelta = timedelta()
+ self._time_remaining: timedelta = timedelta()
+
+ # ==================== 状态属性 ====================
+ # 这些属性会被Uni-Lab系统自动识别并定时对外广播
+
+ @property
+ def status(self) -> str:
+ return self._status
+
+ @property
+ def current_device(self) -> str:
+ """当前设备标识符"""
+ return self._current_device
+
+ @property
+ def pump_state(self) -> str:
+ return self._pump_state
+
+ @property
+ def flow_rate(self) -> float:
+ return self._flow_rate
+
+ @property
+ def target_flow_rate(self) -> float:
+ return self._target_flow_rate
+
+ @property
+ def pressure(self) -> float:
+ return self._pressure
+
+ @property
+ def total_volume(self) -> float:
+ return self._total_volume
+
+ @property
+ def max_flow_rate(self) -> float:
+ return self._max_flow_rate
+
+ @property
+ def max_pressure(self) -> float:
+ return self._max_pressure
+
+ # 添加新的属性访问器
+ @property
+ def from_vessel(self) -> str:
+ return self._from_vessel
+
+ @property
+ def to_vessel(self) -> str:
+ return self._to_vessel
+
+ @property
+ def transfer_volume(self) -> float:
+ return self._transfer_volume
+
+ @property
+ def amount(self) -> str:
+ return self._amount
+
+ @property
+ def transfer_time(self) -> float:
+ return self._transfer_time
+
+ @property
+ def is_viscous(self) -> bool:
+ return self._is_viscous
+
+ @property
+ def rinsing_solvent(self) -> str:
+ return self._rinsing_solvent
+
+ @property
+ def rinsing_volume(self) -> float:
+ return self._rinsing_volume
+
+ @property
+ def rinsing_repeats(self) -> int:
+ return self._rinsing_repeats
+
+ @property
+ def is_solid(self) -> bool:
+ return self._is_solid
+
+ # 修改这两个属性装饰器
+ @property
+ def time_spent(self) -> float:
+ """已用时间(秒)"""
+ if isinstance(self._time_spent, timedelta):
+ return self._time_spent.total_seconds()
+ return float(self._time_spent)
+
+ @property
+ def time_remaining(self) -> float:
+ """剩余时间(秒)"""
+ if isinstance(self._time_remaining, timedelta):
+ return self._time_remaining.total_seconds()
+ return float(self._time_remaining)
+
+ # ==================== 设备控制方法 ====================
+ # 这些方法需要在注册表中添加,会作为ActionServer接受控制指令
+ def pump_transfer(self, from_vessel: str, to_vessel: str, volume: float,
+ amount: str = "", time: float = 0.0, viscous: bool = False,
+ rinsing_solvent: str = "", rinsing_volume: float = 0.0,
+ rinsing_repeats: int = 0, solid: bool = False) -> dict:
+ """Execute pump transfer operation"""
+ # Stop any existing operation first
+ self._stop_pump_operation()
+
+ # Set transfer parameters
+ self._from_vessel = from_vessel
+ self._to_vessel = to_vessel
+ self._transfer_volume = float(volume)
+ self._amount = amount
+ self._transfer_time = float(time)
+ self._is_viscous = viscous
+ self._rinsing_solvent = rinsing_solvent
+ self._rinsing_volume = float(rinsing_volume)
+ self._rinsing_repeats = int(rinsing_repeats)
+ self._is_solid = solid
+
+ # Calculate flow rate
+ if self._transfer_time > 0 and self._transfer_volume > 0:
+ self._target_flow_rate = (self._transfer_volume / self._transfer_time) * 60.0
+ else:
+ self._target_flow_rate = 10.0 if not self._is_viscous else 5.0
+
+ # Reset timers and counters
+ self._start_time = datetime.now()
+ self._time_spent = timedelta()
+ self._time_remaining = timedelta(seconds=self._transfer_time)
+ self._total_volume = 0.0
+ self._flow_rate = 0.0
+
+ # Start pump operation
+ self._pump_state = "Running"
+ self._status = "Starting Transfer"
+ self._running = True
+
+ # Start pump operation thread
+ self._pump_thread = threading.Thread(target=self._pump_operation_loop)
+ self._pump_thread.daemon = True
+ self._pump_thread.start()
+
+ # Wait briefly to ensure thread starts
+ time.sleep(0.1)
+
+ return {
+ "success": True,
+ "status": self._status,
+ "current_device": self._current_device,
+ "time_spent": 0.0,
+ "time_remaining": float(self._transfer_time)
+ }
+
+ def pause_pump(self) -> str:
+
+ if self._pump_state != "Running":
+ self._status = "Error: Pump not running"
+ return "Error"
+
+ self._pump_state = "Paused"
+ self._status = "Pump Paused"
+ self._stop_pump_operation()
+
+ return "Success"
+
+ def resume_pump(self) -> str:
+
+ if self._pump_state != "Paused":
+ self._status = "Error: Pump not paused"
+ return "Error"
+
+ self._pump_state = "Running"
+ self._status = "Resuming Pump"
+ self._start_pump_operation()
+
+ return "Success"
+
+ def reset_volume_counter(self) -> str:
+ self._total_volume = 0.0
+ self._status = "Volume counter reset"
+ return "Success"
+
+ def emergency_stop(self) -> str:
+ self._status = "Emergency Stop"
+ self._pump_state = "Stopped"
+ self._stop_pump_operation()
+ self._flow_rate = 0.0
+ self._pressure = 0.0
+ self._target_flow_rate = 0.0
+
+ return "Success"
+
+ # ==================== 内部控制方法 ====================
+
+ def _start_pump_operation(self):
+ with self._thread_lock:
+ if not self._running:
+ self._running = True
+ self._pump_thread = threading.Thread(target=self._pump_operation_loop)
+ self._pump_thread.daemon = True
+ self._pump_thread.start()
+
+ def _stop_pump_operation(self):
+ with self._thread_lock:
+ self._running = False
+ if self._pump_thread and self._pump_thread.is_alive():
+ self._pump_thread.join(timeout=2.0)
+
+ def _pump_operation_loop(self):
+ """泵运行主循环"""
+ print("Pump operation loop started") # Debug print
+
+ while self._running and self._pump_state == "Running":
+ try:
+ # Calculate flow rate adjustment
+ flow_diff = self._target_flow_rate - self._flow_rate
+
+ # Adjust flow rate more aggressively (50% of difference)
+ adjustment = flow_diff * 0.5
+ self._flow_rate += adjustment
+
+ # Ensure flow rate is within bounds
+ self._flow_rate = max(0.1, min(self._max_flow_rate, self._flow_rate))
+
+ # Update status based on flow rate
+ if abs(flow_diff) < 0.1:
+ self._status = "Running at Target Flow Rate"
+ else:
+ self._status = "Adjusting Flow Rate"
+
+ # Calculate volume increment
+ volume_increment = (self._flow_rate / 60.0) # mL/s
+ self._total_volume += volume_increment
+
+ # Update time tracking
+ self._time_spent = datetime.now() - self._start_time
+ if self._transfer_time > 0:
+ remaining = self._transfer_time - self._time_spent.total_seconds()
+ self._time_remaining = timedelta(seconds=max(0, remaining))
+
+ # Check completion
+ if self._total_volume >= self._transfer_volume:
+ self._status = "Transfer Completed"
+ self._pump_state = "Stopped"
+ self._running = False
+ break
+
+ # Update pressure
+ self._pressure = (self._flow_rate / self._max_flow_rate) * self._max_pressure
+
+ print(f"Debug - Flow: {self._flow_rate:.1f}, Volume: {self._total_volume:.1f}") # Debug print
+
+ time.sleep(1.0)
+
+ except Exception as e:
+ print(f"Error in pump operation: {str(e)}")
+ self._status = "Error in pump operation"
+ self._pump_state = "Stopped"
+ self._running = False
+ break
+
+ def get_status_info(self) -> dict:
+ """
+ 获取完整的设备状态信息
+
+ Returns:
+ dict: 包含所有设备状态的字典
+ """
+ return {
+ "status": self._status,
+ "pump_state": self._pump_state,
+ "flow_rate": self._flow_rate,
+ "target_flow_rate": self._target_flow_rate,
+ "pressure": self._pressure,
+ "total_volume": self._total_volume,
+ "max_flow_rate": self._max_flow_rate,
+ "max_pressure": self._max_pressure,
+ "current_device": self._current_device,
+ "from_vessel": self._from_vessel,
+ "to_vessel": self._to_vessel,
+ "transfer_volume": self._transfer_volume,
+ "amount": self._amount,
+ "transfer_time": self._transfer_time,
+ "is_viscous": self._is_viscous,
+ "rinsing_solvent": self._rinsing_solvent,
+ "rinsing_volume": self._rinsing_volume,
+ "rinsing_repeats": self._rinsing_repeats,
+ "is_solid": self._is_solid,
+ "time_spent": self._time_spent.total_seconds(),
+ "time_remaining": self._time_remaining.total_seconds()
+ }
+
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ pump = MockPump()
+
+ # 测试基本功能
+ print("启动泵设备测试...")
+ print(f"初始状态: {pump.get_status_info()}")
+
+ # 设置流速并启动
+ pump.set_flow_rate(50.0)
+ pump.start_pump()
+
+ # 模拟运行10秒
+ for i in range(10):
+ time.sleep(1)
+ print(f"第{i+1}秒: 流速={pump.flow_rate:.1f}mL/min, 压力={pump.pressure:.2f}bar, 状态={pump.status}")
+
+ # 测试方向切换
+ print("切换泵方向...")
+
+
+ pump.emergency_stop()
+ print("测试完成")
diff --git a/unilabos/devices/mock/mock_rotavap.py b/unilabos/devices/mock/mock_rotavap.py
new file mode 100644
index 00000000..9b2ea914
--- /dev/null
+++ b/unilabos/devices/mock/mock_rotavap.py
@@ -0,0 +1,390 @@
+import time
+import threading
+import json
+
+
+class MockRotavap:
+ """
+ 模拟旋转蒸发器设备类
+
+ 这个类模拟了一个实验室旋转蒸发器的行为,包括旋转控制、
+ 真空泵控制、温度控制等功能。参考了现有的 RotavapOne 实现。
+ """
+
+ def __init__(self, port: str = "MOCK"):
+ """
+ 初始化MockRotavap实例
+
+ Args:
+ port (str): 设备端口,默认为"MOCK"表示模拟设备
+ """
+ self.port = port
+
+ # 设备基本状态属性
+ self._status: str = "Idle" # 设备状态:Idle, Running, Error, Stopped
+
+ # 旋转相关属性
+ self._rotate_state: str = "Stopped" # 旋转状态:Running, Stopped
+ self._rotate_time: float = 0.0 # 旋转剩余时间 (秒)
+ self._rotate_speed: float = 0.0 # 旋转速度 (rpm)
+ self._max_rotate_speed: float = 300.0 # 最大旋转速度 (rpm)
+
+ # 真空泵相关属性
+ self._pump_state: str = "Stopped" # 泵状态:Running, Stopped
+ self._pump_time: float = 0.0 # 泵剩余时间 (秒)
+ self._vacuum_level: float = 0.0 # 真空度 (mbar)
+ self._target_vacuum: float = 50.0 # 目标真空度 (mbar)
+
+ # 温度相关属性
+ self._temperature: float = 25.0 # 水浴温度 (°C)
+ self._target_temperature: float = 25.0 # 目标温度 (°C)
+ self._max_temperature: float = 180.0 # 最大温度 (°C)
+
+ # 运行控制线程
+ self._operation_thread = None
+ self._running = False
+ self._thread_lock = threading.Lock()
+
+ # 操作成功标志
+ self.success: str = "True" # 使用字符串而不是布尔值
+
+ # ==================== 状态属性 ====================
+ # 这些属性会被Uni-Lab系统自动识别并定时对外广播
+
+ @property
+ def status(self) -> str:
+ return self._status
+
+ @property
+ def rotate_state(self) -> str:
+ return self._rotate_state
+
+ @property
+ def rotate_time(self) -> float:
+ return self._rotate_time
+
+ @property
+ def rotate_speed(self) -> float:
+ return self._rotate_speed
+
+ @property
+ def pump_state(self) -> str:
+ return self._pump_state
+
+ @property
+ def pump_time(self) -> float:
+ return self._pump_time
+
+ @property
+ def vacuum_level(self) -> float:
+ return self._vacuum_level
+
+ @property
+ def temperature(self) -> float:
+ return self._temperature
+
+ @property
+ def target_temperature(self) -> float:
+ return self._target_temperature
+
+ # ==================== 设备控制方法 ====================
+ # 这些方法需要在注册表中添加,会作为ActionServer接受控制指令
+
+ def set_timer(self, command: str) -> str:
+ """
+ 设置定时器 - 兼容现有RotavapOne接口
+
+ Args:
+ command (str): JSON格式的命令字符串,包含rotate_time和pump_time
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+
+ try:
+ timer = json.loads(command)
+ rotate_time = timer.get("rotate_time", 0)
+ pump_time = timer.get("pump_time", 0)
+
+ self.success = "False"
+ self._rotate_time = float(rotate_time)
+ self._pump_time = float(pump_time)
+ self.success = "True"
+
+ self._status = "Timer Set"
+ return "Success"
+
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
+ self._status = f"Error: Invalid command format - {str(e)}"
+ self.success = "False"
+ return "Error"
+
+ def set_rotate_time(self, time_seconds: float) -> str:
+ """
+ 设置旋转时间
+
+ Args:
+ time_seconds (float): 旋转时间 (秒)
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+
+ self.success = "False"
+ self._rotate_time = max(0.0, float(time_seconds))
+ self.success = "True"
+ self._status = "Rotate time set"
+ return "Success"
+
+ def set_pump_time(self, time_seconds: float) -> str:
+ """
+ 设置泵时间
+
+ Args:
+ time_seconds (float): 泵时间 (秒)
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+
+ self.success = "False"
+ self._pump_time = max(0.0, float(time_seconds))
+ self.success = "True"
+ self._status = "Pump time set"
+ return "Success"
+
+ def set_rotate_speed(self, speed: float) -> str:
+ """
+ 设置旋转速度
+
+ Args:
+ speed (float): 旋转速度 (rpm)
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+
+ if speed < 0 or speed > self._max_rotate_speed:
+ self._status = f"Error: Speed out of range (0-{self._max_rotate_speed})"
+ return "Error"
+
+ self._rotate_speed = speed
+ self._status = "Rotate speed set"
+ return "Success"
+
+ def set_temperature(self, temperature: float) -> str:
+ """
+ 设置水浴温度
+
+ Args:
+ temperature (float): 目标温度 (°C)
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+
+ if temperature < 0 or temperature > self._max_temperature:
+ self._status = f"Error: Temperature out of range (0-{self._max_temperature})"
+ return "Error"
+
+ self._target_temperature = temperature
+ self._status = "Temperature set"
+
+ # 启动操作线程以开始温度控制
+ self._start_operation()
+
+ return "Success"
+
+ def start_rotation(self) -> str:
+ """
+ 启动旋转
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+
+ if self._rotate_time <= 0:
+ self._status = "Error: No rotate time set"
+ return "Error"
+
+ self._rotate_state = "Running"
+ self._status = "Rotation started"
+ return "Success"
+
+ def start_pump(self) -> str:
+ """
+ 启动真空泵
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+
+ if self._pump_time <= 0:
+ self._status = "Error: No pump time set"
+ return "Error"
+
+ self._pump_state = "Running"
+ self._status = "Pump started"
+ return "Success"
+
+ def stop_all_operations(self) -> str:
+ """
+ 停止所有操作
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ self._rotate_state = "Stopped"
+ self._pump_state = "Stopped"
+ self._stop_operation()
+ self._rotate_time = 0.0
+ self._pump_time = 0.0
+ self._vacuum_level = 0.0
+ self._status = "All operations stopped"
+ return "Success"
+
+ def emergency_stop(self) -> str:
+ """
+ 紧急停止
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ self._status = "Emergency Stop"
+ self.stop_all_operations()
+ return "Success"
+
+ # ==================== 内部控制方法 ====================
+
+ def _start_operation(self):
+ """
+ 启动操作线程
+
+ 这个方法启动一个后台线程来模拟旋蒸的实际运行过程。
+ """
+ with self._thread_lock:
+ if not self._running:
+ self._running = True
+ self._operation_thread = threading.Thread(target=self._operation_loop)
+ self._operation_thread.daemon = True
+ self._operation_thread.start()
+
+ def _stop_operation(self):
+ """
+ 停止操作线程
+
+ 安全地停止后台运行线程并等待其完成。
+ """
+ with self._thread_lock:
+ self._running = False
+ if self._operation_thread and self._operation_thread.is_alive():
+ self._operation_thread.join(timeout=2.0)
+
+ def _operation_loop(self):
+ """
+ 操作主循环
+
+ 这个方法在后台线程中运行,模拟真实旋蒸的工作过程:
+ 1. 时间倒计时
+ 2. 温度控制
+ 3. 真空度控制
+ 4. 状态更新
+ """
+ while self._running:
+ try:
+ # 处理旋转时间倒计时
+ if self._rotate_time > 0:
+ self._rotate_state = "Running"
+ self._rotate_time = max(0.0, self._rotate_time - 1.0)
+ else:
+ self._rotate_state = "Stopped"
+
+ # 处理泵时间倒计时
+ if self._pump_time > 0:
+ self._pump_state = "Running"
+ self._pump_time = max(0.0, self._pump_time - 1.0)
+ # 模拟真空度变化
+ if self._vacuum_level > self._target_vacuum:
+ self._vacuum_level = max(self._target_vacuum, self._vacuum_level - 5.0)
+ else:
+ self._pump_state = "Stopped"
+ # 真空度逐渐回升
+ self._vacuum_level = min(1013.25, self._vacuum_level + 2.0)
+
+ # 模拟温度控制
+ temp_diff = self._target_temperature - self._temperature
+ if abs(temp_diff) > 0.5:
+ if temp_diff > 0:
+ self._temperature += min(1.0, temp_diff * 0.1)
+ else:
+ self._temperature += max(-1.0, temp_diff * 0.1)
+
+ # 更新整体状态
+ if self._rotate_state == "Running" or self._pump_state == "Running":
+ self._status = "Operating"
+ elif self._rotate_time > 0 or self._pump_time > 0:
+ self._status = "Ready"
+ else:
+ self._status = "Idle"
+
+ # 等待1秒后继续下一次循环
+ time.sleep(1.0)
+
+ except Exception as e:
+ self._status = f"Error in operation: {str(e)}"
+ break
+
+ # 循环结束时的清理工作
+ self._status = "Idle"
+
+ def get_status_info(self) -> dict:
+ """
+ 获取完整的设备状态信息
+
+ Returns:
+ dict: 包含所有设备状态的字典
+ """
+ return {
+ "status": self._status,
+ "rotate_state": self._rotate_state,
+ "rotate_time": self._rotate_time,
+ "rotate_speed": self._rotate_speed,
+ "pump_state": self._pump_state,
+ "pump_time": self._pump_time,
+ "vacuum_level": self._vacuum_level,
+ "temperature": self._temperature,
+ "target_temperature": self._target_temperature,
+ "success": self.success,
+ }
+
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ rotavap = MockRotavap()
+
+ # 测试基本功能
+ print("启动旋转蒸发器测试...")
+ print(f"初始状态: {rotavap.get_status_info()}")
+
+ # 设置定时器
+ timer_command = '{"rotate_time": 300, "pump_time": 600}'
+ rotavap.set_timer(timer_command)
+
+ # 设置温度和转速
+ rotavap.set_temperature(60.0)
+ rotavap.set_rotate_speed(120.0)
+
+ # 启动操作
+ rotavap.start_rotation()
+ rotavap.start_pump()
+
+ # 模拟运行10秒
+ for i in range(10):
+ time.sleep(1)
+ print(
+ f"第{i+1}秒: 旋转={rotavap.rotate_time:.0f}s, 泵={rotavap.pump_time:.0f}s, "
+ f"温度={rotavap.temperature:.1f}°C, 真空={rotavap.vacuum_level:.1f}mbar"
+ )
+
+ rotavap.emergency_stop()
+ print("测试完成")
diff --git a/unilabos/devices/mock/mock_separator.py b/unilabos/devices/mock/mock_separator.py
new file mode 100644
index 00000000..222cb2ed
--- /dev/null
+++ b/unilabos/devices/mock/mock_separator.py
@@ -0,0 +1,399 @@
+import time
+import threading
+from datetime import datetime, timedelta
+
+class MockSeparator:
+ def __init__(self, port: str = "MOCK"):
+ self.port = port
+
+ # 基本状态属性
+ self._status: str = "Idle" # 当前总体状态
+ self._valve_state: str = "Closed" # 阀门状态:Open 或 Closed
+ self._settling_time: float = 0.0 # 静置时间(秒)
+
+ # 搅拌相关属性
+ self._shake_time: float = 0.0 # 剩余摇摆时间(秒)
+ self._shake_status: str = "Not Shaking" # 摇摆状态
+
+ # 用于后台模拟 shake 动作
+ self._operation_thread = None
+ self._thread_lock = threading.Lock()
+ self._running = False
+
+ # Separate action 相关属性
+ self._current_device: str = "MockSeparator1"
+ self._purpose: str = "" # wash or extract
+ self._product_phase: str = "" # top or bottom
+ self._from_vessel: str = ""
+ self._separation_vessel: str = ""
+ self._to_vessel: str = ""
+ self._waste_phase_to_vessel: str = ""
+ self._solvent: str = ""
+ self._solvent_volume: float = 0.0
+ self._through: str = ""
+ self._repeats: int = 1
+ self._stir_time: float = 0.0
+ self._stir_speed: float = 0.0
+ self._time_spent = timedelta()
+ self._time_remaining = timedelta()
+ self._start_time = datetime.now() # 添加这一行
+
+ @property
+ def current_device(self) -> str:
+ return self._current_device
+
+ @property
+ def purpose(self) -> str:
+ return self._purpose
+
+ @property
+ def valve_state(self) -> str:
+ return self._valve_state
+
+ @property
+ def settling_time(self) -> float:
+ return self._settling_time
+
+ @property
+ def status(self) -> str:
+ return self._status
+
+ @property
+ def shake_time(self) -> float:
+ with self._thread_lock:
+ return self._shake_time
+
+ @property
+ def shake_status(self) -> str:
+ with self._thread_lock:
+ return self._shake_status
+
+ @property
+ def product_phase(self) -> str:
+ return self._product_phase
+
+ @property
+ def from_vessel(self) -> str:
+ return self._from_vessel
+
+ @property
+ def separation_vessel(self) -> str:
+ return self._separation_vessel
+
+ @property
+ def to_vessel(self) -> str:
+ return self._to_vessel
+
+ @property
+ def waste_phase_to_vessel(self) -> str:
+ return self._waste_phase_to_vessel
+
+ @property
+ def solvent(self) -> str:
+ return self._solvent
+
+ @property
+ def solvent_volume(self) -> float:
+ return self._solvent_volume
+
+ @property
+ def through(self) -> str:
+ return self._through
+
+ @property
+ def repeats(self) -> int:
+ return self._repeats
+
+ @property
+ def stir_time(self) -> float:
+ return self._stir_time
+
+ @property
+ def stir_speed(self) -> float:
+ return self._stir_speed
+
+ @property
+ def time_spent(self) -> float:
+ if self._running:
+ self._time_spent = datetime.now() - self._start_time
+ return self._time_spent.total_seconds()
+
+ @property
+ def time_remaining(self) -> float:
+ if self._running:
+ elapsed = (datetime.now() - self._start_time).total_seconds()
+ total_time = (self._stir_time + self._settling_time + 10) * self._repeats
+ remain = max(0, total_time - elapsed)
+ self._time_remaining = timedelta(seconds=remain)
+ return self._time_remaining.total_seconds()
+
+ def separate(self, purpose: str, product_phase: str, from_vessel: str,
+ separation_vessel: str, to_vessel: str, waste_phase_to_vessel: str = "",
+ solvent: str = "", solvent_volume: float = 0.0, through: str = "",
+ repeats: int = 1, stir_time: float = 0.0, stir_speed: float = 0.0,
+ settling_time: float = 60.0) -> dict:
+ """
+ 执行分离操作
+ """
+ with self._thread_lock:
+ # 检查是否已经在运行
+ if self._running:
+ return {
+ "success": False,
+ "status": "Error: Operation already in progress"
+ }
+ # 必填参数验证
+ if not all([from_vessel, separation_vessel, to_vessel]):
+ self._status = "Error: Missing required vessel parameters"
+ return {"success": False}
+ # 验证参数
+ if purpose not in ["wash", "extract"]:
+ self._status = "Error: Invalid purpose"
+ return {"success": False}
+
+ if product_phase not in ["top", "bottom"]:
+ self._status = "Error: Invalid product phase"
+ return {"success": False}
+ # 数值参数验证
+ try:
+ solvent_volume = float(solvent_volume)
+ repeats = int(repeats)
+ stir_time = float(stir_time)
+ stir_speed = float(stir_speed)
+ settling_time = float(settling_time)
+ except ValueError:
+ self._status = "Error: Invalid numeric parameters"
+ return {"success": False}
+
+ # 设置参数
+ self._purpose = purpose
+ self._product_phase = product_phase
+ self._from_vessel = from_vessel
+ self._separation_vessel = separation_vessel
+ self._to_vessel = to_vessel
+ self._waste_phase_to_vessel = waste_phase_to_vessel
+ self._solvent = solvent
+ self._solvent_volume = float(solvent_volume)
+ self._through = through
+ self._repeats = int(repeats)
+ self._stir_time = float(stir_time)
+ self._stir_speed = float(stir_speed)
+ self._settling_time = float(settling_time)
+
+ # 重置计时器
+ self._start_time = datetime.now()
+ self._time_spent = timedelta()
+ total_time = (self._stir_time + self._settling_time + 10) * self._repeats
+ self._time_remaining = timedelta(seconds=total_time)
+
+ # 启动分离操作
+ self._status = "Starting Separation"
+ self._running = True
+
+ # 在锁内创建和启动线程
+ self._operation_thread = threading.Thread(target=self._operation_loop)
+ self._operation_thread.daemon = True
+ self._operation_thread.start()
+
+ # 等待确认操作已经开始
+ time.sleep(0.1) # 短暂等待确保操作线程已启动
+
+ return {
+ "success": True,
+ "status": self._status,
+ "current_device": self._current_device,
+ "time_spent": self._time_spent.total_seconds(),
+ "time_remaining": self._time_remaining.total_seconds()
+ }
+
+ def shake(self, shake_time: float) -> str:
+ """
+ 模拟 shake(搅拌)操作:
+ - 进入 "Shaking" 状态,倒计时 shake_time 秒
+ - shake 结束后,进入 "Settling" 状态,静置时间固定为 5 秒
+ - 最后恢复为 Idle
+ """
+ try:
+ shake_time = float(shake_time)
+ except ValueError:
+ self._status = "Error: Invalid shake time"
+ return "Error"
+
+ with self._thread_lock:
+ self._status = "Shaking"
+ self._settling_time = 0.0
+ self._shake_time = shake_time
+ self._shake_status = "Shaking"
+
+ def _run_shake():
+ remaining = shake_time
+ while remaining > 0:
+ time.sleep(1)
+ remaining -= 1
+ with self._thread_lock:
+ self._shake_time = remaining
+ with self._thread_lock:
+ self._status = "Settling"
+ self._settling_time = 60.0 # 固定静置时间为60秒
+ self._shake_status = "Settling"
+ while True:
+ with self._thread_lock:
+ if self._settling_time <= 0:
+ self._status = "Idle"
+ self._shake_status = "Idle"
+ break
+ time.sleep(1)
+ with self._thread_lock:
+ self._settling_time = max(0.0, self._settling_time - 1)
+
+ self._operation_thread = threading.Thread(target=_run_shake)
+ self._operation_thread.daemon = True
+ self._operation_thread.start()
+ return "Success"
+
+ def set_valve(self, command: str) -> str:
+ """
+ 阀门控制命令:传入 "open" 或 "close"
+ """
+
+ command = command.lower()
+ if command == "open":
+ self._valve_state = "Open"
+ self._status = "Valve Opened"
+ elif command == "close":
+ self._valve_state = "Closed"
+ self._status = "Valve Closed"
+ else:
+ self._status = "Error: Invalid valve command"
+ return "Error"
+ return "Success"
+
+ def _operation_loop(self):
+ """分离操作主循环"""
+ try:
+ current_repeat = 1
+
+ # 立即更新状态,确保不会停留在Starting Separation
+ with self._thread_lock:
+ self._status = f"Separation Cycle {current_repeat}/{self._repeats}"
+
+ while self._running and current_repeat <= self._repeats:
+ # 第一步:搅拌
+ if self._stir_time > 0:
+ with self._thread_lock:
+ self._status = f"Stirring (Repeat {current_repeat}/{self._repeats})"
+ remaining_stir = self._stir_time
+ while remaining_stir > 0 and self._running:
+ time.sleep(1)
+ remaining_stir -= 1
+
+ # 第二步:静置
+ if self._settling_time > 0:
+ with self._thread_lock:
+ self._status = f"Settling (Repeat {current_repeat}/{self._repeats})"
+ remaining_settle = self._settling_time
+ while remaining_settle > 0 and self._running:
+ time.sleep(1)
+ remaining_settle -= 1
+
+ # 第三步:打开阀门排出
+ with self._thread_lock:
+ self._valve_state = "Open"
+ self._status = f"Draining (Repeat {current_repeat}/{self._repeats})"
+
+ # 模拟排出时间(5秒)
+ time.sleep(10)
+
+ # 关闭阀门
+ with self._thread_lock:
+ self._valve_state = "Closed"
+
+ # 检查是否继续下一次重复
+ if current_repeat < self._repeats:
+ current_repeat += 1
+ else:
+ with self._thread_lock:
+ self._status = "Separation Complete"
+ break
+
+ except Exception as e:
+ with self._thread_lock:
+ self._status = f"Error in separation: {str(e)}"
+ finally:
+ with self._thread_lock:
+ self._running = False
+ self._valve_state = "Closed"
+ if self._status == "Starting Separation":
+ self._status = "Error: Operation failed to start"
+ elif self._status != "Separation Complete":
+ self._status = "Stopped"
+
+ def stop_operations(self) -> str:
+ """停止任何正在执行的操作"""
+ with self._thread_lock:
+ self._running = False
+ if self._operation_thread and self._operation_thread.is_alive():
+ self._operation_thread.join(timeout=1.0)
+ self._operation_thread = None
+ self._settling_time = 0.0
+ self._status = "Idle"
+ self._shake_status = "Idle"
+ self._shake_time = 0.0
+ self._time_remaining = timedelta()
+ return "Success"
+
+ def get_status_info(self) -> dict:
+ """获取当前设备状态信息"""
+ with self._thread_lock:
+ current_time = datetime.now()
+ if self._start_time:
+ self._time_spent = current_time - self._start_time
+
+ return {
+ "status": self._status,
+ "valve_state": self._valve_state,
+ "settling_time": self._settling_time,
+ "shake_time": self._shake_time,
+ "shake_status": self._shake_status,
+ "current_device": self._current_device,
+ "purpose": self._purpose,
+ "product_phase": self._product_phase,
+ "from_vessel": self._from_vessel,
+ "separation_vessel": self._separation_vessel,
+ "to_vessel": self._to_vessel,
+ "waste_phase_to_vessel": self._waste_phase_to_vessel,
+ "solvent": self._solvent,
+ "solvent_volume": self._solvent_volume,
+ "through": self._through,
+ "repeats": self._repeats,
+ "stir_time": self._stir_time,
+ "stir_speed": self._stir_speed,
+ "time_spent": self._time_spent.total_seconds(),
+ "time_remaining": self._time_remaining.total_seconds()
+ }
+
+
+# 主函数用于测试
+if __name__ == "__main__":
+ separator = MockSeparator()
+
+ print("启动简单版分离器测试...")
+ print("初始状态:", separator.get_status_info())
+
+ # 触发 shake 操作,模拟 10 秒的搅拌
+ print("执行 shake 操作...")
+ print(separator.shake(10.0))
+
+ # 循环显示状态变化
+ for i in range(20):
+ time.sleep(1)
+ info = separator.get_status_info()
+ print(
+ f"第{i+1}秒: 状态={info['status']}, 静置时间={info['settling_time']:.1f}秒, "
+ f"阀门状态={info['valve_state']}, shake_time={info['shake_time']:.1f}, "
+ f"shake_status={info['shake_status']}"
+ )
+
+ # 模拟打开阀门
+ print("打开阀门...", separator.set_valve("open"))
+ print("最终状态:", separator.get_status_info())
diff --git a/unilabos/devices/mock/mock_solenoid_valve.py b/unilabos/devices/mock/mock_solenoid_valve.py
new file mode 100644
index 00000000..0f0fbe55
--- /dev/null
+++ b/unilabos/devices/mock/mock_solenoid_valve.py
@@ -0,0 +1,89 @@
+import time
+
+
+class MockSolenoidValve:
+ """
+ 模拟电磁阀设备类 - 简化版本
+
+ 这个类提供了电磁阀的基本功能:开启、关闭和状态查询
+ """
+
+ def __init__(self, port: str = "MOCK"):
+ """
+ 初始化MockSolenoidValve实例
+
+ Args:
+ port (str): 设备端口,默认为"MOCK"表示模拟设备
+ """
+ self.port = port
+ self._status: str = "Idle"
+ self._valve_status: str = "Closed" # 阀门位置:Open, Closed
+
+ @property
+ def status(self) -> str:
+ """设备状态 - 会被自动识别的设备属性"""
+ return self._status
+
+ @property
+ def valve_status(self) -> str:
+ """阀门状态"""
+ return self._valve_status
+
+ def set_valve_status(self, status: str) -> str:
+ """
+ 设置阀门位置
+
+ Args:
+ position (str): 阀门位置,可选值:"Open", "Closed"
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ if status not in ["Open", "Closed"]:
+ self._status = "Error: Invalid position"
+ return "Error"
+
+ self._status = "Moving"
+ time.sleep(1) # 模拟阀门动作时间
+
+ self._valve_status = status
+ self._status = "Idle"
+ return "Success"
+
+ def open_valve(self) -> str:
+ """打开阀门"""
+ return self.set_valve_status("Open")
+
+ def close_valve(self) -> str:
+ """关闭阀门"""
+ return self.set_valve_status("Closed")
+
+ def get_valve_status(self) -> str:
+ """获取阀门位置"""
+ return self._valve_status
+
+ def is_open(self) -> bool:
+ """检查阀门是否打开"""
+ return self._valve_status == "Open"
+
+ def is_closed(self) -> bool:
+ """检查阀门是否关闭"""
+ return self._valve_status == "Closed"
+
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ valve = MockSolenoidValve()
+
+ print("启动电磁阀测试...")
+ print(f"初始状态: 位置={valve.valve_status}, 状态={valve.status}")
+
+ # 测试开启阀门
+ valve.open_valve()
+ print(f"开启后: 位置={valve.valve_status}, 状态={valve.status}")
+
+ # 测试关闭阀门
+ valve.close_valve()
+ print(f"关闭后: 位置={valve.valve_status}, 状态={valve.status}")
+
+ print("测试完成")
diff --git a/unilabos/devices/mock/mock_stirrer.py b/unilabos/devices/mock/mock_stirrer.py
new file mode 100644
index 00000000..a1f2c51d
--- /dev/null
+++ b/unilabos/devices/mock/mock_stirrer.py
@@ -0,0 +1,307 @@
+import time
+import threading
+
+
+class MockStirrer:
+ def __init__(self, port: str = "MOCK"):
+ self.port = port
+
+ # 设备基本状态属性
+ self._status: str = "Idle" # 设备状态:Idle, Running, Error, Stopped
+
+ # 搅拌相关属性
+ self._stir_speed: float = 0.0 # 当前搅拌速度 (rpm)
+ self._target_stir_speed: float = 0.0 # 目标搅拌速度 (rpm)
+ self._max_stir_speed: float = 2000.0 # 最大搅拌速度 (rpm)
+ self._stir_state: str = "Stopped" # 搅拌状态:Running, Stopped
+
+ # 温度相关属性
+ self._temperature: float = 25.0 # 当前温度 (°C)
+ self._target_temperature: float = 25.0 # 目标温度 (°C)
+ self._max_temperature: float = 300.0 # 最大温度 (°C)
+ self._heating_state: str = "Off" # 加热状态:On, Off
+ self._heating_power: float = 0.0 # 加热功率百分比 0-100
+
+ # 运行控制线程
+ self._operation_thread = None
+ self._running = False
+ self._thread_lock = threading.Lock()
+
+ # ==================== 状态属性 ====================
+ # 这些属性会被Uni-Lab系统自动识别并定时对外广播
+
+ @property
+ def status(self) -> str:
+ return self._status
+
+ @property
+ def stir_speed(self) -> float:
+ return self._stir_speed
+
+ @property
+ def target_stir_speed(self) -> float:
+ return self._target_stir_speed
+
+ @property
+ def stir_state(self) -> str:
+ return self._stir_state
+
+ @property
+ def temperature(self) -> float:
+ """
+ 当前温度
+
+ Returns:
+ float: 当前温度 (°C)
+ """
+ return self._temperature
+
+ @property
+ def target_temperature(self) -> float:
+ """
+ 目标温度
+
+ Returns:
+ float: 目标温度 (°C)
+ """
+ return self._target_temperature
+
+ @property
+ def heating_state(self) -> str:
+ return self._heating_state
+
+ @property
+ def heating_power(self) -> float:
+ return self._heating_power
+
+ @property
+ def max_stir_speed(self) -> float:
+ return self._max_stir_speed
+
+ @property
+ def max_temperature(self) -> float:
+ return self._max_temperature
+
+ # ==================== 设备控制方法 ====================
+ # 这些方法需要在注册表中添加,会作为ActionServer接受控制指令
+
+ def set_stir_speed(self, speed: float) -> str:
+
+ speed = float(speed) # 确保传入的速度是浮点数
+
+ if speed < 0 or speed > self._max_stir_speed:
+ self._status = f"Error: Speed out of range (0-{self._max_stir_speed})"
+ return "Error"
+
+ self._target_stir_speed = speed
+ self._status = "Setting Stir Speed"
+
+ # 如果设置了非零速度,启动搅拌
+ if speed > 0:
+ self._stir_state = "Running"
+ else:
+ self._stir_state = "Stopped"
+
+ return "Success"
+
+ def set_temperature(self, temperature: float) -> str:
+ temperature = float(temperature) # 确保传入的温度是浮点数
+
+ if temperature < 0 or temperature > self._max_temperature:
+ self._status = f"Error: Temperature out of range (0-{self._max_temperature})"
+ return "Error"
+
+ self._target_temperature = temperature
+ self._status = "Setting Temperature"
+
+ return "Success"
+
+ def start_stirring(self) -> str:
+
+ if self._target_stir_speed <= 0:
+ self._status = "Error: No target speed set"
+ return "Error"
+
+ self._stir_state = "Running"
+ self._status = "Stirring Started"
+ return "Success"
+
+ def stop_stirring(self) -> str:
+ self._stir_state = "Stopped"
+ self._target_stir_speed = 0.0
+ self._status = "Stirring Stopped"
+ return "Success"
+
+ def heating_control(self, heating_state: str = "On") -> str:
+
+ if heating_state not in ["On", "Off"]:
+ self._status = "Error: Invalid heating state"
+ return "Error"
+
+ self._heating_state = heating_state
+
+ if heating_state == "On":
+ self._status = "Heating On"
+ else:
+ self._status = "Heating Off"
+ self._heating_power = 0.0
+
+ return "Success"
+
+ def stop_all_operations(self) -> str:
+ self._stir_state = "Stopped"
+ self._heating_state = "Off"
+ self._stop_operation()
+ self._stir_speed = 0.0
+ self._target_stir_speed = 0.0
+ self._heating_power = 0.0
+ self._status = "All operations stopped"
+ return "Success"
+
+ def emergency_stop(self) -> str:
+ """
+ 紧急停止
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ self._status = "Emergency Stop"
+ self.stop_all_operations()
+ return "Success"
+
+ # ==================== 内部控制方法 ====================
+
+ def _start_operation(self):
+ with self._thread_lock:
+ if not self._running:
+ self._running = True
+ self._operation_thread = threading.Thread(target=self._operation_loop)
+ self._operation_thread.daemon = True
+ self._operation_thread.start()
+
+ def _stop_operation(self):
+ """
+ 停止操作线程
+
+ 安全地停止后台运行线程并等待其完成。
+ """
+ with self._thread_lock:
+ self._running = False
+ if self._operation_thread and self._operation_thread.is_alive():
+ self._operation_thread.join(timeout=2.0)
+
+ def _operation_loop(self):
+ while self._running:
+ try:
+ # 处理搅拌速度控制
+ if self._stir_state == "Running":
+ speed_diff = self._target_stir_speed - self._stir_speed
+
+ if abs(speed_diff) < 1.0: # 速度接近目标值
+ self._stir_speed = self._target_stir_speed
+ if self._stir_speed > 0:
+ self._status = "Stirring at Target Speed"
+ else:
+ # 模拟速度调节,每秒调整10%的差值
+ adjustment = speed_diff * 0.1
+ self._stir_speed += adjustment
+ self._status = "Adjusting Stir Speed"
+
+ # 确保速度在合理范围内
+ self._stir_speed = max(0.0, min(self._max_stir_speed, self._stir_speed))
+ else:
+ # 搅拌停止时,速度逐渐降为0
+ if self._stir_speed > 0:
+ self._stir_speed = max(0.0, self._stir_speed - 50.0) # 每秒减少50rpm
+
+ # 处理温度控制
+ if self._heating_state == "On":
+ temp_diff = self._target_temperature - self._temperature
+
+ if abs(temp_diff) < 0.5: # 温度接近目标值
+ self._heating_power = 20.0 # 维持温度的最小功率
+ elif temp_diff > 0: # 需要加热
+ # 根据温差调整加热功率
+ if temp_diff > 50:
+ self._heating_power = 100.0
+ elif temp_diff > 20:
+ self._heating_power = 80.0
+ elif temp_diff > 10:
+ self._heating_power = 60.0
+ else:
+ self._heating_power = 40.0
+
+ # 模拟加热过程
+ heating_rate = self._heating_power / 100.0 * 1.5 # 最大每秒升温1.5度
+ self._temperature += heating_rate
+ else: # 目标温度低于当前温度
+ self._heating_power = 0.0
+ # 自然冷却
+ self._temperature -= 0.1
+ else:
+ self._heating_power = 0.0
+ # 自然冷却到室温
+ if self._temperature > 25.0:
+ self._temperature -= 0.2
+
+ # 限制温度范围
+ self._temperature = max(20.0, min(self._max_temperature, self._temperature))
+
+ # 更新整体状态
+ if self._stir_state == "Running" and self._heating_state == "On":
+ self._status = "Stirring and Heating"
+ elif self._stir_state == "Running":
+ self._status = "Stirring Only"
+ elif self._heating_state == "On":
+ self._status = "Heating Only"
+ else:
+ self._status = "Idle"
+
+ # 等待1秒后继续下一次循环
+ time.sleep(1.0)
+
+ except Exception as e:
+ self._status = f"Error in operation: {str(e)}"
+ break
+
+ # 循环结束时的清理工作
+ self._status = "Idle"
+
+ def get_status_info(self) -> dict:
+ return {
+ "status": self._status,
+ "stir_speed": self._stir_speed,
+ "target_stir_speed": self._target_stir_speed,
+ "stir_state": self._stir_state,
+ "temperature": self._temperature,
+ "target_temperature": self._target_temperature,
+ "heating_state": self._heating_state,
+ "heating_power": self._heating_power,
+ "max_stir_speed": self._max_stir_speed,
+ "max_temperature": self._max_temperature,
+ }
+
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ stirrer = MockStirrer()
+
+ # 测试基本功能
+ print("启动搅拌器测试...")
+ print(f"初始状态: {stirrer.get_status_info()}")
+
+ # 设置搅拌速度和温度
+ stirrer.set_stir_speed(800.0)
+ stirrer.set_temperature(60.0)
+ stirrer.heating_control("On")
+
+ # 模拟运行15秒
+ for i in range(15):
+ time.sleep(1)
+ print(
+ f"第{i+1}秒: 速度={stirrer.stir_speed:.0f}rpm, 温度={stirrer.temperature:.1f}°C, "
+ f"功率={stirrer.heating_power:.1f}%, 状态={stirrer.status}"
+ )
+
+ stirrer.emergency_stop()
+ print("测试完成")
diff --git a/unilabos/devices/mock/mock_stirrer_new.py b/unilabos/devices/mock/mock_stirrer_new.py
new file mode 100644
index 00000000..ac429db5
--- /dev/null
+++ b/unilabos/devices/mock/mock_stirrer_new.py
@@ -0,0 +1,229 @@
+import time
+import threading
+from datetime import datetime, timedelta
+
+class MockStirrer_new:
+ def __init__(self, port: str = "MOCK"):
+ self.port = port
+
+ # 基本状态属性
+ self._status: str = "Idle"
+ self._vessel: str = ""
+ self._purpose: str = ""
+
+ # 搅拌相关属性
+ self._stir_speed: float = 0.0
+ self._target_stir_speed: float = 0.0
+ self._max_stir_speed: float = 2000.0
+ self._stir_state: str = "Stopped"
+
+ # 计时相关
+ self._stir_time: float = 0.0
+ self._settling_time: float = 0.0
+ self._start_time = datetime.now()
+ self._time_remaining = timedelta()
+
+ # 运行控制
+ self._operation_thread = None
+ self._running = False
+ self._thread_lock = threading.Lock()
+
+ # 创建操作线程
+ self._operation_thread = threading.Thread(target=self._operation_loop)
+ self._operation_thread.daemon = True
+ self._operation_thread.start()
+
+ # ==================== 状态属性 ====================
+ @property
+ def status(self) -> str:
+ return self._status
+
+ @property
+ def stir_speed(self) -> float:
+ return self._stir_speed
+
+ @property
+ def target_stir_speed(self) -> float:
+ return self._target_stir_speed
+
+ @property
+ def stir_state(self) -> str:
+ return self._stir_state
+
+ @property
+ def vessel(self) -> str:
+ return self._vessel
+
+ @property
+ def purpose(self) -> str:
+ return self._purpose
+
+ @property
+ def stir_time(self) -> float:
+ return self._stir_time
+
+ @property
+ def settling_time(self) -> float:
+ return self._settling_time
+
+ @property
+ def max_stir_speed(self) -> float:
+ return self._max_stir_speed
+
+ @property
+ def progress(self) -> float:
+ """返回当前操作的进度(0-100)"""
+ if not self._running:
+ return 0.0
+ elapsed = (datetime.now() - self._start_time).total_seconds()
+ total_time = self._stir_time + self._settling_time
+ if total_time <= 0:
+ return 100.0
+ return min(100.0, (elapsed / total_time) * 100)
+
+ # ==================== Action Server 方法 ====================
+ def start_stir(self, vessel: str, stir_speed: float = 0.0, purpose: str = "") -> dict:
+ """
+ StartStir.action 对应的方法
+ """
+ with self._thread_lock:
+ if self._running:
+ return {
+ "success": False,
+ "message": "Operation already in progress"
+ }
+
+ try:
+ # 重置所有参数
+ self._vessel = vessel
+ self._purpose = purpose
+ self._stir_time = 0.0 # 连续搅拌模式下不设置搅拌时间
+ self._settling_time = 0.0
+ self._start_time = datetime.now() # 重置开始时间
+
+ if stir_speed > 0:
+ self._target_stir_speed = min(stir_speed, self._max_stir_speed)
+
+ self._stir_state = "Running"
+ self._status = "Stirring Started"
+ self._running = True
+
+ return {
+ "success": True,
+ "message": "Stirring started successfully"
+ }
+
+ except Exception as e:
+ return {
+ "success": False,
+ "message": f"Error: {str(e)}"
+ }
+
+ def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> dict:
+ """
+ Stir.action 对应的方法
+ """
+ with self._thread_lock:
+ try:
+ # 如果已经在运行,先停止当前操作
+ if self._running:
+ self._running = False
+ self._stir_state = "Stopped"
+ self._target_stir_speed = 0.0
+ time.sleep(0.1) # 给一个短暂的停止时间
+
+
+ # 重置所有参数
+ self._stir_time = float(stir_time)
+ self._settling_time = float(settling_time)
+ self._target_stir_speed = min(float(stir_speed), self._max_stir_speed)
+ self._start_time = datetime.now() # 重置开始时间
+ self._stir_state = "Running"
+ self._status = "Stirring"
+ self._running = True
+
+ return {"success": True}
+
+ except ValueError:
+ self._status = "Error: Invalid parameters"
+ return {"success": False}
+
+ def stop_stir(self, vessel: str) -> dict:
+ """
+ StopStir.action 对应的方法
+ """
+ with self._thread_lock:
+ if vessel != self._vessel:
+ return {
+ "success": False,
+ "message": "Vessel mismatch"
+ }
+
+ self._running = False
+ self._stir_state = "Stopped"
+ self._target_stir_speed = 0.0
+ self._status = "Stirring Stopped"
+
+ return {
+ "success": True,
+ "message": "Stirring stopped successfully"
+ }
+
+ # ==================== 内部控制方法 ====================
+
+ def _operation_loop(self):
+ """操作主循环"""
+ while True:
+ try:
+ current_time = datetime.now()
+
+ with self._thread_lock: # 添加锁保护
+ if self._stir_state == "Running":
+ # 实际搅拌逻辑
+ speed_diff = self._target_stir_speed - self._stir_speed
+ if abs(speed_diff) > 0.1:
+ adjustment = speed_diff * 0.1
+ self._stir_speed += adjustment
+ else:
+ self._stir_speed = self._target_stir_speed
+
+ # 更新进度
+ if self._running:
+ if self._stir_time > 0: # 定时搅拌模式
+ elapsed = (current_time - self._start_time).total_seconds()
+ if elapsed >= self._stir_time + self._settling_time:
+ self._running = False
+ self._stir_state = "Stopped"
+ self._target_stir_speed = 0.0
+ self._stir_speed = 0.0
+ self._status = "Stirring Complete"
+ elif elapsed >= self._stir_time:
+ self._status = "Settling"
+ else: # 连续搅拌模式
+ self._status = "Stirring"
+ else:
+ # 停止状态下慢慢降低速度
+ if self._stir_speed > 0:
+ self._stir_speed = max(0, self._stir_speed - 20.0)
+
+ time.sleep(0.1)
+
+ except Exception as e:
+ print(f"Error in operation loop: {str(e)}") # 添加错误输出
+ self._status = f"Error: {str(e)}"
+ time.sleep(1.0) # 错误发生时等待较长时间
+
+ def get_status_info(self) -> dict:
+ """获取设备状态信息"""
+ return {
+ "status": self._status,
+ "vessel": self._vessel,
+ "purpose": self._purpose,
+ "stir_speed": self._stir_speed,
+ "target_stir_speed": self._target_stir_speed,
+ "stir_state": self._stir_state,
+ "stir_time": self._stir_time, # 添加
+ "settling_time": self._settling_time, # 添加
+ "progress": self.progress,
+ "max_stir_speed": self._max_stir_speed
+ }
\ No newline at end of file
diff --git a/unilabos/devices/mock/mock_vacuum.py b/unilabos/devices/mock/mock_vacuum.py
new file mode 100644
index 00000000..9e368a90
--- /dev/null
+++ b/unilabos/devices/mock/mock_vacuum.py
@@ -0,0 +1,410 @@
+import time
+import threading
+
+
+class MockVacuum:
+ """
+ 模拟真空泵设备类
+
+ 这个类模拟了一个实验室真空泵的行为,包括真空度控制、
+ 压力监测、运行状态管理等功能。参考了现有的 VacuumPumpMock 实现。
+ """
+
+ def __init__(self, port: str = "MOCK"):
+ """
+ 初始化MockVacuum实例
+
+ Args:
+ port (str): 设备端口,默认为"MOCK"表示模拟设备
+ """
+ self.port = port
+
+ # 设备基本状态属性
+ self._status: str = "Idle" # 设备状态:Idle, Running, Error, Stopped
+ self._power_state: str = "Off" # 电源状态:On, Off
+ self._pump_state: str = "Stopped" # 泵运行状态:Running, Stopped, Paused
+
+ # 真空相关属性
+ self._vacuum_level: float = 1013.25 # 当前真空度 (mbar) - 大气压开始
+ self._target_vacuum: float = 50.0 # 目标真空度 (mbar)
+ self._min_vacuum: float = 1.0 # 最小真空度 (mbar)
+ self._max_vacuum: float = 1013.25 # 最大真空度 (mbar) - 大气压
+
+ # 泵性能相关属性
+ self._pump_speed: float = 0.0 # 泵速 (L/s)
+ self._max_pump_speed: float = 100.0 # 最大泵速 (L/s)
+ self._pump_efficiency: float = 95.0 # 泵效率百分比
+
+ # 运行控制线程
+ self._vacuum_thread = None
+ self._running = False
+ self._thread_lock = threading.Lock()
+
+ # ==================== 状态属性 ====================
+ # 这些属性会被Uni-Lab系统自动识别并定时对外广播
+
+ @property
+ def status(self) -> str:
+ """
+ 设备状态 - 会被自动识别的设备属性
+
+ Returns:
+ str: 当前设备状态 (Idle, Running, Error, Stopped)
+ """
+ return self._status
+
+ @property
+ def power_state(self) -> str:
+ """
+ 电源状态
+
+ Returns:
+ str: 电源状态 (On, Off)
+ """
+ return self._power_state
+
+ @property
+ def pump_state(self) -> str:
+ """
+ 泵运行状态
+
+ Returns:
+ str: 泵状态 (Running, Stopped, Paused)
+ """
+ return self._pump_state
+
+ @property
+ def vacuum_level(self) -> float:
+ """
+ 当前真空度
+
+ Returns:
+ float: 当前真空度 (mbar)
+ """
+ return self._vacuum_level
+
+ @property
+ def target_vacuum(self) -> float:
+ """
+ 目标真空度
+
+ Returns:
+ float: 目标真空度 (mbar)
+ """
+ return self._target_vacuum
+
+ @property
+ def pump_speed(self) -> float:
+ """
+ 泵速
+
+ Returns:
+ float: 泵速 (L/s)
+ """
+ return self._pump_speed
+
+ @property
+ def pump_efficiency(self) -> float:
+ """
+ 泵效率
+
+ Returns:
+ float: 泵效率百分比
+ """
+ return self._pump_efficiency
+
+ @property
+ def max_pump_speed(self) -> float:
+ """
+ 最大泵速
+
+ Returns:
+ float: 最大泵速 (L/s)
+ """
+ return self._max_pump_speed
+
+ # ==================== 设备控制方法 ====================
+ # 这些方法需要在注册表中添加,会作为ActionServer接受控制指令
+
+ def power_control(self, power_state: str = "On") -> str:
+ """
+ 电源控制方法
+
+ Args:
+ power_state (str): 电源状态,可选值:"On", "Off"
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ if power_state not in ["On", "Off"]:
+ self._status = "Error: Invalid power state"
+ return "Error"
+
+ self._power_state = power_state
+
+ if power_state == "On":
+ self._status = "Power On"
+ self._start_vacuum_operation()
+ else:
+ self._status = "Power Off"
+ self.stop_vacuum()
+
+ return "Success"
+
+ def set_vacuum_level(self, vacuum_level: float) -> str:
+ """
+ 设置目标真空度
+
+ Args:
+ vacuum_level (float): 目标真空度 (mbar)
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ try:
+ vacuum_level = float(vacuum_level)
+ except ValueError:
+ self._status = "Error: Invalid vacuum level"
+ return "Error"
+ if self._power_state != "On":
+ self._status = "Error: Power Off"
+ return "Error"
+
+ if vacuum_level < self._min_vacuum or vacuum_level > self._max_vacuum:
+ self._status = f"Error: Vacuum level out of range ({self._min_vacuum}-{self._max_vacuum})"
+ return "Error"
+
+ self._target_vacuum = vacuum_level
+ self._status = "Setting Vacuum Level"
+
+ return "Success"
+
+ def start_vacuum(self) -> str:
+ """
+ 启动真空泵
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ if self._power_state != "On":
+ self._status = "Error: Power Off"
+ return "Error"
+
+ self._pump_state = "Running"
+ self._status = "Starting Vacuum Pump"
+ self._start_vacuum_operation()
+
+ return "Success"
+
+ def stop_vacuum(self) -> str:
+ """
+ 停止真空泵
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ self._pump_state = "Stopped"
+ self._status = "Stopping Vacuum Pump"
+ self._stop_vacuum_operation()
+ self._pump_speed = 0.0
+
+ return "Success"
+
+ def pause_vacuum(self) -> str:
+ """
+ 暂停真空泵
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ if self._pump_state != "Running":
+ self._status = "Error: Pump not running"
+ return "Error"
+
+ self._pump_state = "Paused"
+ self._status = "Vacuum Pump Paused"
+ self._stop_vacuum_operation()
+
+ return "Success"
+
+ def resume_vacuum(self) -> str:
+ """
+ 恢复真空泵运行
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ if self._pump_state != "Paused":
+ self._status = "Error: Pump not paused"
+ return "Error"
+
+ if self._power_state != "On":
+ self._status = "Error: Power Off"
+ return "Error"
+
+ self._pump_state = "Running"
+ self._status = "Resuming Vacuum Pump"
+ self._start_vacuum_operation()
+
+ return "Success"
+
+ def vent_to_atmosphere(self) -> str:
+ """
+ 通大气 - 将真空度恢复到大气压
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ self._target_vacuum = self._max_vacuum # 设置为大气压
+ self._status = "Venting to Atmosphere"
+ return "Success"
+
+ def emergency_stop(self) -> str:
+ """
+ 紧急停止
+
+ Returns:
+ str: 操作结果状态 ("Success", "Error")
+ """
+ self._status = "Emergency Stop"
+ self._pump_state = "Stopped"
+ self._stop_vacuum_operation()
+ self._pump_speed = 0.0
+
+ return "Success"
+
+ # ==================== 内部控制方法 ====================
+
+ def _start_vacuum_operation(self):
+ """
+ 启动真空操作线程
+
+ 这个方法启动一个后台线程来模拟真空泵的实际运行过程。
+ """
+ with self._thread_lock:
+ if not self._running and self._power_state == "On":
+ self._running = True
+ self._vacuum_thread = threading.Thread(target=self._vacuum_operation_loop)
+ self._vacuum_thread.daemon = True
+ self._vacuum_thread.start()
+
+ def _stop_vacuum_operation(self):
+ """
+ 停止真空操作线程
+
+ 安全地停止后台运行线程并等待其完成。
+ """
+ with self._thread_lock:
+ self._running = False
+ if self._vacuum_thread and self._vacuum_thread.is_alive():
+ self._vacuum_thread.join(timeout=2.0)
+
+ def _vacuum_operation_loop(self):
+ """
+ 真空操作主循环
+
+ 这个方法在后台线程中运行,模拟真空泵的工作过程:
+ 1. 检查电源状态和运行状态
+ 2. 如果泵状态为 "Running",根据目标真空调整泵速和真空度
+ 3. 否则等待
+ """
+ while self._running and self._power_state == "On":
+ try:
+ with self._thread_lock:
+ # 只有泵状态为 Running 时才进行更新
+ if self._pump_state == "Running":
+ vacuum_diff = self._vacuum_level - self._target_vacuum
+
+ if abs(vacuum_diff) < 1.0: # 真空度接近目标值
+ self._status = "At Target Vacuum"
+ self._pump_speed = self._max_pump_speed * 0.2 # 维持真空的最小泵速
+ elif vacuum_diff > 0: # 需要抽真空(降低压力)
+ self._status = "Pumping Down"
+ if vacuum_diff > 500:
+ self._pump_speed = self._max_pump_speed
+ elif vacuum_diff > 100:
+ self._pump_speed = self._max_pump_speed * 0.8
+ elif vacuum_diff > 50:
+ self._pump_speed = self._max_pump_speed * 0.6
+ else:
+ self._pump_speed = self._max_pump_speed * 0.4
+
+ # 根据泵速和效率计算真空降幅
+ pump_rate = (self._pump_speed / self._max_pump_speed) * self._pump_efficiency / 100.0
+ vacuum_reduction = pump_rate * 10.0 # 每秒最大降低10 mbar
+ self._vacuum_level = max(self._target_vacuum, self._vacuum_level - vacuum_reduction)
+ else: # 目标真空度高于当前值,需要通气
+ self._status = "Venting"
+ self._pump_speed = 0.0
+ self._vacuum_level = min(self._target_vacuum, self._vacuum_level + 5.0)
+
+ # 限制真空度范围
+ self._vacuum_level = max(self._min_vacuum, min(self._max_vacuum, self._vacuum_level))
+ else:
+ # 当泵状态不是 Running 时,可保持原状态
+ self._status = "Vacuum Pump Not Running"
+ # 释放锁后等待1秒钟
+ time.sleep(1.0)
+ except Exception as e:
+ with self._thread_lock:
+ self._status = f"Error in vacuum operation: {str(e)}"
+ break
+
+ # 循环结束后的清理工作
+ if self._pump_state == "Running":
+ self._status = "Idle"
+ # 停止泵后,真空度逐渐回升到大气压
+ while self._vacuum_level < self._max_vacuum * 0.9:
+ with self._thread_lock:
+ self._vacuum_level += 2.0
+ time.sleep(0.1)
+
+ def get_status_info(self) -> dict:
+ """
+ 获取完整的设备状态信息
+
+ Returns:
+ dict: 包含所有设备状态的字典
+ """
+ return {
+ "status": self._status,
+ "power_state": self._power_state,
+ "pump_state": self._pump_state,
+ "vacuum_level": self._vacuum_level,
+ "target_vacuum": self._target_vacuum,
+ "pump_speed": self._pump_speed,
+ "pump_efficiency": self._pump_efficiency,
+ "max_pump_speed": self._max_pump_speed,
+ }
+
+
+# 用于测试的主函数
+if __name__ == "__main__":
+ vacuum = MockVacuum()
+
+ # 测试基本功能
+ print("启动真空泵测试...")
+ vacuum.power_control("On")
+ print(f"初始状态: {vacuum.get_status_info()}")
+
+ # 设置目标真空度并启动
+ vacuum.set_vacuum_level(10.0) # 设置为10mbar
+ vacuum.start_vacuum()
+
+ # 模拟运行15秒
+ for i in range(15):
+ time.sleep(1)
+ print(
+ f"第{i+1}秒: 真空度={vacuum.vacuum_level:.1f}mbar, 泵速={vacuum.pump_speed:.1f}L/s, 状态={vacuum.status}"
+ )
+ # 测试通大气
+ print("测试通大气...")
+ vacuum.vent_to_atmosphere()
+
+ # 继续运行5秒观察通大气过程
+ for i in range(5):
+ time.sleep(1)
+ print(f"通大气第{i+1}秒: 真空度={vacuum.vacuum_level:.1f}mbar, 状态={vacuum.status}")
+
+ vacuum.emergency_stop()
+ print("测试完成")
diff --git a/unilabos/devices/pump_and_valve/solenoid_valve_mock.py b/unilabos/devices/pump_and_valve/solenoid_valve_mock.py
index 08820ca0..b6735a3f 100644
--- a/unilabos/devices/pump_and_valve/solenoid_valve_mock.py
+++ b/unilabos/devices/pump_and_valve/solenoid_valve_mock.py
@@ -5,22 +5,22 @@ class SolenoidValveMock:
def __init__(self, port: str = "COM6"):
self._status = "Idle"
self._valve_position = "OPEN"
-
+
@property
def status(self) -> str:
return self._status
-
+
@property
def valve_position(self) -> str:
return self._valve_position
def get_valve_position(self) -> str:
return self._valve_position
-
+
def set_valve_position(self, position):
self._status = "Busy"
time.sleep(5)
-
+
self._valve_position = position
time.sleep(5)
self._status = "Idle"
diff --git a/unilabos/devices/pump_and_valve/vacuum_pump_mock.py b/unilabos/devices/pump_and_valve/vacuum_pump_mock.py
index 3e330570..35655122 100644
--- a/unilabos/devices/pump_and_valve/vacuum_pump_mock.py
+++ b/unilabos/devices/pump_and_valve/vacuum_pump_mock.py
@@ -4,18 +4,16 @@ import time
class VacuumPumpMock:
def __init__(self, port: str = "COM6"):
self._status = "OPEN"
-
+
@property
def status(self) -> str:
return self._status
def get_status(self) -> str:
return self._status
-
- def set_status(self, position):
- time.sleep(5)
-
- self._status = position
+
+ def set_status(self, string):
+ self._status = string
time.sleep(5)
def open(self):
diff --git a/unilabos/devices/resource_container/__init__.py b/unilabos/devices/resource_container/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/unilabos/devices/resource_container/container.py b/unilabos/devices/resource_container/container.py
new file mode 100644
index 00000000..38b9a806
--- /dev/null
+++ b/unilabos/devices/resource_container/container.py
@@ -0,0 +1,9 @@
+
+class HotelContainer:
+ def __init__(self, rotation: dict, device_config: dict):
+ self.rotation = rotation
+ self.device_config = device_config
+ self.status = 'idle'
+
+ def get_rotation(self):
+ return self.rotation
diff --git a/unilabos/devices/ros_dev/lh_joint_config.json b/unilabos/devices/ros_dev/lh_joint_config.json
new file mode 100644
index 00000000..908cc545
--- /dev/null
+++ b/unilabos/devices/ros_dev/lh_joint_config.json
@@ -0,0 +1,38 @@
+{
+ "OTDeck":{
+ "joint_names":[
+ "first_joint",
+ "second_joint",
+ "third_joint",
+ "fourth_joint"
+ ],
+ "link_names":[
+ "first_link",
+ "second_link",
+ "third_link",
+ "fourth_link"
+ ],
+ "y":{
+ "first_joint":{
+ "factor":-0.001,
+ "offset":0.166
+ }
+ },
+ "x":{
+ "second_joint":{
+ "factor":-0.001,
+ "offset":0.1775
+ }
+ },
+ "z":{
+ "third_joint":{
+ "factor":0.001,
+ "offset":0.0
+ },
+ "fourth_joint":{
+ "factor":0.001,
+ "offset":0.0
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/unilabos/devices/ros_dev/liquid_handler_joint_publisher.py b/unilabos/devices/ros_dev/liquid_handler_joint_publisher.py
index e593f425..882b519d 100644
--- a/unilabos/devices/ros_dev/liquid_handler_joint_publisher.py
+++ b/unilabos/devices/ros_dev/liquid_handler_joint_publisher.py
@@ -1,9 +1,12 @@
+import asyncio
import copy
+from pathlib import Path
+import threading
import rclpy
import json
import time
from rclpy.executors import MultiThreadedExecutor
-from rclpy.action import ActionServer
+from rclpy.action import ActionServer,ActionClient
from sensor_msgs.msg import JointState
from unilabos_msgs.action import SendCmd
from rclpy.action.server import ServerGoalHandle
@@ -11,9 +14,11 @@ from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
from tf_transformations import quaternion_from_euler
from tf2_ros import TransformBroadcaster, Buffer, TransformListener
+from rclpy.node import Node
+import re
class LiquidHandlerJointPublisher(BaseROS2DeviceNode):
- def __init__(self,device_id:str, joint_config:dict, lh_id:str,resource_tracker, rate=50):
+ def __init__(self,resources_config:list, resource_tracker, rate=50, device_id:str = "lh_joint_publisher"):
super().__init__(
driver_instance=self,
device_id=device_id,
@@ -23,60 +28,118 @@ class LiquidHandlerJointPublisher(BaseROS2DeviceNode):
print_publish=False,
resource_tracker=resource_tracker,
)
-
- # joint_config_dict = {
- # "joint_names":[
- # "first_joint",
- # "second_joint",
- # "third_joint",
- # "fourth_joint"
- # ],
- # "y":{
- # "first_joint":{
- # "factor":-1,
- # "offset":0.0
- # }
- # },
- # "x":{
- # "second_joint":{
- # "factor":-1,
- # "offset":0.0
- # }
- # },
- # "z":{
- # "third_joint":{
- # "factor":1,
- # "offset":0.0
- # },
- # "fourth_joint":{
- # "factor":1,
- # "offset":0.0
- # }
- # }
- # }
-
+
+ # 初始化参数
self.j_msg = JointState()
- self.lh_id = lh_id
- # self.j_msg.name = joint_names
- self.joint_config = joint_config
- self.j_msg.position = [0.0 for i in range(len(joint_config['joint_names']))]
- self.j_msg.name = [f"{self.lh_id}_{x}" for x in joint_config['joint_names']]
- # self.joint_config = joint_config_dict
- # self.j_msg.position = [0.0 for i in range(len(joint_config_dict['joint_names']))]
- # self.j_msg.name = [f"{self.lh_id}_{x}" for x in joint_config_dict['joint_names']]
+ joint_config = json.load(open(f"{Path(__file__).parent.absolute()}/lh_joint_config.json", encoding="utf-8"))
+ self.resources_config = {x['id']:x for x in resources_config}
self.rate = rate
self.tf_buffer = Buffer()
self.tf_listener = TransformListener(self.tf_buffer, self)
self.j_pub = self.create_publisher(JointState,'/joint_states',10)
- self.create_timer(0.02,self.lh_joint_pub_callback)
+ self.create_timer(1,self.lh_joint_pub_callback)
+
+
+ self.resource_action = None
+
+ while self.resource_action is None:
+ self.resource_action = self.check_tf_update_actions()
+ time.sleep(1)
+
+ self.resource_action_client = ActionClient(self, SendCmd, self.resource_action)
+ while not self.resource_action_client.wait_for_server(timeout_sec=1.0):
+ self.get_logger().info('等待 TfUpdate 服务器...')
+
+ self.deck_list = []
+ self.lh_devices = {}
+ # 初始化设备ID与config信息
+ for resource in resources_config:
+ if resource['class'] == 'liquid_handler':
+ deck_id = resource['config']['data']['children'][0]['_resource_child_name']
+ deck_class = resource['config']['data']['children'][0]['_resource_type'].split(':')[-1]
+ key = f'{deck_id}'
+ # key = f'{resource["id"]}_{deck_id}'
+ self.lh_devices[key] = {
+ 'joint_msg':JointState(
+ name=[f'{key}_{x}' for x in joint_config[deck_class]['joint_names']],
+ position=[0.0 for _ in joint_config[deck_class]['joint_names']]
+ ),
+ 'joint_config':joint_config[deck_class]
+ }
+ self.deck_list.append(deck_id)
+
+ print('='*20)
+ print(self.lh_devices)
+ print('='*20)
self.j_action = ActionServer(
self,
SendCmd,
- "joint",
+ "hl_joint_action",
self.lh_joint_action_callback,
result_timeout=5000
)
+
+ def check_tf_update_actions(self):
+ topics = self.get_topic_names_and_types()
+
+
+ for topic_item in topics:
+
+ topic_name, topic_types = topic_item
+
+ if 'action_msgs/msg/GoalStatusArray' in topic_types:
+ # 删除 /_action/status 部分
+
+ base_name = topic_name.replace('/_action/status', '')
+ # 检查最后一个部分是否为 tf_update
+ parts = base_name.split('/')
+ if parts and parts[-1] == 'tf_update':
+ return base_name
+
+ return None
+
+
+ def find_resource_parent(self, resource_id:str):
+ # 遍历父辈,找到父辈的父辈,直到找到设备ID
+ parent_id = self.resources_config[resource_id]['parent']
+ try:
+ if parent_id in self.deck_list:
+ p_ = self.resources_config[parent_id]['parent']
+ str_ = f'{parent_id}'
+ return str(str_)
+ else:
+ return self.find_resource_parent(parent_id)
+ except Exception as e:
+ return None
+
+
+ def send_resource_action(self, resource_id_list:list[str], link_name:str):
+ goal_msg = SendCmd.Goal()
+ str_dict = {}
+ for resource in resource_id_list:
+ str_dict[resource] = link_name
+
+ goal_msg.command = json.dumps(str_dict)
+ self.resource_action_client.send_goal(goal_msg)
+
+ def resource_move(self, resource_id:str, link_name:str, channels:list[int]):
+ resource = resource_id.rsplit("_",1)
+
+ channel_list = ['A','B','C','D','E','F','G','H']
+
+ resource_list = []
+ match = re.match(r'([a-zA-Z_]+)(\d+)', resource[1])
+ if match:
+ number = match.group(2)
+ for channel in channels:
+ resource_list.append(f"{resource[0]}_{channel_list[channel]}{number}")
+
+ if len(resource_list) > 0:
+ self.send_resource_action(resource_list, link_name)
+
+
+
def lh_joint_action_callback(self,goal_handle: ServerGoalHandle):
"""Move a single joint
@@ -101,12 +164,13 @@ class LiquidHandlerJointPublisher(BaseROS2DeviceNode):
goal_handle.succeed()
except Exception as e:
- print(e)
+ print(f'Liquid handler action error: \n{e}')
goal_handle.abort()
result.success = False
-
+
return result
def inverse_kinematics(self, x, y, z,
+ parent_id,
x_joint:dict,
y_joint:dict,
z_joint:dict ):
@@ -117,77 +181,102 @@ class LiquidHandlerJointPublisher(BaseROS2DeviceNode):
x (float): x坐标
y (float): y坐标
z (float): z坐标
- x_joint (dict): x轴关节配置,包含plus和offset
- y_joint (dict): y轴关节配置,包含plus和offset
- z_joint (dict): z轴关节配置,包含plus和offset
+ x_joint (dict): x轴关节配置,包含factor和offset
+ y_joint (dict): y轴关节配置,包含factor和offset
+ z_joint (dict): z轴关节配置,包含factor和offset
Returns:
dict: 关节名称和对应位置的字典
"""
- joint_positions = copy.deepcopy(self.j_msg.position)
+ joint_positions = copy.deepcopy(self.lh_devices[parent_id]['joint_msg'].position)
+ z_index = 0
# 处理x轴关节
for joint_name, config in x_joint.items():
- index = self.j_msg.name.index(f"{self.lh_id}_{joint_name}")
+ index = self.lh_devices[parent_id]['joint_msg'].name.index(f"{parent_id}_{joint_name}")
joint_positions[index] = x * config["factor"] + config["offset"]
# 处理y轴关节
for joint_name, config in y_joint.items():
- index = self.j_msg.name.index(f"{self.lh_id}_{joint_name}")
+ index = self.lh_devices[parent_id]['joint_msg'].name.index(f"{parent_id}_{joint_name}")
joint_positions[index] = y * config["factor"] + config["offset"]
# 处理z轴关节
for joint_name, config in z_joint.items():
- index = self.j_msg.name.index(f"{self.lh_id}_{joint_name}")
+ index = self.lh_devices[parent_id]['joint_msg'].name.index(f"{parent_id}_{joint_name}")
joint_positions[index] = z * config["factor"] + config["offset"]
-
-
- return joint_positions
+ z_index = index
+
+ return joint_positions ,z_index
- def move_joints(self, resource_name, link_name, speed, x_joint=None, y_joint=None, z_joint=None):
+ def move_joints(self, resource_names, x, y, z, option, speed = 0.1 ,x_joint=None, y_joint=None, z_joint=None):
+ if isinstance(resource_names, list):
+ resource_name_ = resource_names[0]
+ else:
+ resource_name_ = resource_names
- transform = self.tf_buffer.lookup_transform(
- link_name,
- resource_name,
- rclpy.time.Time()
- )
- x,y,z = transform.transform.translation.x, transform.transform.translation.y, transform.transform.translation.z
+ parent_id = self.find_resource_parent(resource_name_)
+
+
+ print('!'*20)
+ print(parent_id)
+ print('!'*20)
if x_joint is None:
- x_joint_config = next(iter(self.joint_config['x'].items()))
- elif x_joint in self.joint_config['x']:
- x_joint_config = self.joint_config['x'][x_joint]
+ xa,xb = next(iter(self.lh_devices[parent_id]['joint_config']['x'].items()))
+ x_joint_config = {xa:xb}
+ elif x_joint in self.lh_devices[parent_id]['joint_config']['x']:
+ x_joint_config = self.lh_devices[parent_id]['joint_config']['x'][x_joint]
else:
raise ValueError(f"x_joint {x_joint} not in joint_config['x']")
if y_joint is None:
- y_joint_config = next(iter(self.joint_config['y'].items()))
- elif y_joint in self.joint_config['y']:
- y_joint_config = self.joint_config['y'][y_joint]
+ ya,yb = next(iter(self.lh_devices[parent_id]['joint_config']['y'].items()))
+ y_joint_config = {ya:yb}
+ elif y_joint in self.lh_devices[parent_id]['joint_config']['y']:
+ y_joint_config = self.lh_devices[parent_id]['joint_config']['y'][y_joint]
else:
raise ValueError(f"y_joint {y_joint} not in joint_config['y']")
if z_joint is None:
- z_joint_config = next(iter(self.joint_config['z'].items()))
- elif z_joint in self.joint_config['z']:
- z_joint_config = self.joint_config['z'][z_joint]
+ za, zb = next(iter(self.lh_devices[parent_id]['joint_config']['z'].items()))
+ z_joint_config = {za :zb}
+ elif z_joint in self.lh_devices[parent_id]['joint_config']['z']:
+ z_joint_config = self.lh_devices[parent_id]['joint_config']['z'][z_joint]
else:
raise ValueError(f"z_joint {z_joint} not in joint_config['z']")
- joint_positions_target = self.inverse_kinematics(x,y,z,x_joint_config,y_joint_config,z_joint_config)
+ joint_positions_target, z_index = self.inverse_kinematics(x,y,z,parent_id,x_joint_config,y_joint_config,z_joint_config)
+ joint_positions_target_zero = copy.deepcopy(joint_positions_target)
+ joint_positions_target_zero[z_index] = 0
+
+ self.move_to(joint_positions_target_zero, speed, parent_id)
+ self.move_to(joint_positions_target, speed, parent_id)
+ time.sleep(1)
+ if option == "pick":
+ link_name = self.lh_devices[parent_id]['joint_config']['link_names'][z_index]
+ link_name = f'{parent_id}_{link_name}'
+ self.resource_move(resource_name_, link_name, [0,1,2,3,4,5,6,7])
+ elif option == "drop_trash":
+ self.resource_move(resource_name_, "__trash", [0,1,2,3,4,5,6,7])
+ elif option == "drop":
+ self.resource_move(resource_name_, "world", [0,1,2,3,4,5,6,7])
+ self.move_to(joint_positions_target_zero, speed, parent_id)
+
+
+ def move_to(self, joint_positions ,speed, parent_id):
loop_flag = 0
-
- while loop_flag < len(self.joint_config['joint_names']):
+ while loop_flag < len(joint_positions):
loop_flag = 0
- for i in range(len(self.joint_config['joint_names'])):
- distance = joint_positions_target[i] - self.j_msg.position[i]
+ for i in range(len(joint_positions)):
+ distance = joint_positions[i] - self.lh_devices[parent_id]['joint_msg'].position[i]
if distance == 0:
loop_flag += 1
continue
minus_flag = distance/abs(distance)
if abs(distance) > speed/self.rate:
- self.j_msg.position[i] += minus_flag * speed/self.rate
+ self.lh_devices[parent_id]['joint_msg'].position[i] += minus_flag * speed/self.rate
else :
- self.j_msg.position[i] = joint_positions_target[i]
+ self.lh_devices[parent_id]['joint_msg'].position[i] = joint_positions[i]
loop_flag += 1
@@ -195,10 +284,103 @@ class LiquidHandlerJointPublisher(BaseROS2DeviceNode):
self.lh_joint_pub_callback()
time.sleep(1/self.rate)
-
def lh_joint_pub_callback(self):
- self.j_msg.header.stamp = self.get_clock().now().to_msg()
- self.j_pub.publish(self.j_msg)
+ for id, config in self.lh_devices.items():
+ config['joint_msg'].header.stamp = self.get_clock().now().to_msg()
+ self.j_pub.publish(config['joint_msg'])
+
+
+
+
+class JointStatePublisher(Node):
+ def __init__(self):
+ super().__init__('joint_state_publisher')
+
+ self.lh_action = None
+
+ while self.lh_action is None:
+ self.lh_action = self.check_hl_joint_actions()
+ time.sleep(1)
+
+ self.lh_action_client = ActionClient(self, SendCmd, self.lh_action)
+ while not self.lh_action_client.wait_for_server(timeout_sec=1.0):
+ self.get_logger().info('等待 TfUpdate 服务器...')
+
+
+
+ def check_hl_joint_actions(self):
+ topics = self.get_topic_names_and_types()
+
+
+ for topic_item in topics:
+
+ topic_name, topic_types = topic_item
+
+ if 'action_msgs/msg/GoalStatusArray' in topic_types:
+ # 删除 /_action/status 部分
+
+ base_name = topic_name.replace('/_action/status', '')
+ # 检查最后一个部分是否为 tf_update
+ parts = base_name.split('/')
+ if parts and parts[-1] == 'hl_joint_action':
+ return base_name
+
+ return None
+
+ def send_resource_action(self, resource_name, x,y,z,option, speed = 0.1,x_joint=None, y_joint=None, z_joint=None):
+ goal_msg = SendCmd.Goal()
+ str_dict = {
+ 'resource_names':resource_name,
+ 'x':x,
+ 'y':y,
+ 'z':z,
+ 'option':option,
+ 'speed':speed,
+ 'x_joint':x_joint,
+ 'y_joint':y_joint,
+ 'z_joint':z_joint
+ }
+
+
+ goal_msg.command = json.dumps(str_dict)
+
+ if not self.lh_action_client.wait_for_server(timeout_sec=5.0):
+ self.get_logger().error('Action server not available')
+ return None
+
+ try:
+ # 创建新的executor
+ executor = rclpy.executors.MultiThreadedExecutor()
+ executor.add_node(self)
+
+ # 发送目标
+ future = self.lh_action_client.send_goal_async(goal_msg)
+
+ # 使用executor等待结果
+ while not future.done():
+ executor.spin_once(timeout_sec=0.1)
+
+ handle = future.result()
+
+ if not handle.accepted:
+ self.get_logger().error('Goal was rejected')
+ return None
+
+ # 等待最终结果
+ result_future = handle.get_result_async()
+ while not result_future.done():
+ executor.spin_once(timeout_sec=0.1)
+
+ result = result_future.result()
+ return result
+
+ except Exception as e:
+ self.get_logger().error(f'Error during action execution: {str(e)}')
+ return None
+ finally:
+ # 清理executor
+ executor.remove_node(self)
+
def main():
diff --git a/unilabos/devices/ros_dev/moveit2.py b/unilabos/devices/ros_dev/moveit2.py
new file mode 100644
index 00000000..80bea9da
--- /dev/null
+++ b/unilabos/devices/ros_dev/moveit2.py
@@ -0,0 +1,2442 @@
+import copy
+import threading
+from enum import Enum
+from typing import Any, List, Optional, Tuple, Union
+
+import numpy as np
+from action_msgs.msg import GoalStatus
+from geometry_msgs.msg import Point, Pose, PoseStamped, Quaternion
+from moveit_msgs.action import ExecuteTrajectory, MoveGroup
+from moveit_msgs.msg import (
+ AllowedCollisionEntry,
+ AttachedCollisionObject,
+ CollisionObject,
+ Constraints,
+ JointConstraint,
+ MoveItErrorCodes,
+ OrientationConstraint,
+ PlanningScene,
+ PositionConstraint,
+)
+from moveit_msgs.srv import (
+ ApplyPlanningScene,
+ GetCartesianPath,
+ GetMotionPlan,
+ GetPlanningScene,
+ GetPositionFK,
+ GetPositionIK,
+)
+from rclpy.action import ActionClient
+from rclpy.callback_groups import CallbackGroup
+from rclpy.node import Node
+from rclpy.qos import (
+ QoSDurabilityPolicy,
+ QoSHistoryPolicy,
+ QoSProfile,
+ QoSReliabilityPolicy,
+)
+from rclpy.task import Future
+from sensor_msgs.msg import JointState
+from shape_msgs.msg import Mesh, MeshTriangle, SolidPrimitive
+from std_msgs.msg import Header, String
+from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
+
+
+class MoveIt2State(Enum):
+ """
+ An enum the represents the current execution state of the MoveIt2 interface.
+ - IDLE: No motion is being requested or executed
+ - REQUESTING: Execution has been requested, but the request has not yet been
+ accepted.
+ - EXECUTING: Execution has been requested and accepted, and has not yet been
+ completed.
+ """
+
+ IDLE = 0
+ REQUESTING = 1
+ EXECUTING = 2
+
+
+class MoveIt2:
+ """
+ Python interface for MoveIt 2 that enables planning and execution of trajectories.
+ For execution, this interface requires that robot utilises JointTrajectoryController.
+ """
+
+ def __init__(
+ self,
+ node: Node,
+ joint_names: List[str],
+ base_link_name: str,
+ end_effector_name: str,
+ group_name: str = "arm",
+ execute_via_moveit: bool = False,
+ ignore_new_calls_while_executing: bool = False,
+ callback_group: Optional[CallbackGroup] = None,
+ follow_joint_trajectory_action_name: str = "DEPRECATED",
+ use_move_group_action: bool = False,
+ ):
+ """
+ Construct an instance of `MoveIt2` interface.
+ - `node` - ROS 2 node that this interface is attached to
+ - `joint_names` - List of joint names of the robot (can be extracted from URDF)
+ - `base_link_name` - Name of the robot base link
+ - `end_effector_name` - Name of the robot end effector
+ - `group_name` - Name of the planning group for robot arm
+ - [DEPRECATED] `execute_via_moveit` - Flag that enables execution via MoveGroup action (MoveIt 2)
+ FollowJointTrajectory action (controller) is employed otherwise
+ together with a separate planning service client
+ - `ignore_new_calls_while_executing` - Flag to ignore requests to execute new trajectories
+ while previous is still being executed
+ - `callback_group` - Optional callback group to use for ROS 2 communication (topics/services/actions)
+ - [DEPRECATED] `follow_joint_trajectory_action_name` - Name of the action server for the controller
+ - `use_move_group_action` - Flag that enables execution via MoveGroup action (MoveIt 2)
+ ExecuteTrajectory action is employed otherwise
+ together with a separate planning service client
+ """
+
+ self._node = node
+ self._callback_group = callback_group
+
+ # Check for deprecated parameters
+ if execute_via_moveit:
+ self._node.get_logger().warn(
+ "Parameter `execute_via_moveit` is deprecated. Please use `use_move_group_action` instead."
+ )
+ use_move_group_action = True
+ if follow_joint_trajectory_action_name != "DEPRECATED":
+ self._node.get_logger().warn(
+ "Parameter `follow_joint_trajectory_action_name` is deprecated. `MoveIt2` uses the `execute_trajectory` action instead."
+ )
+
+ # Create subscriber for current joint states
+ self._node.create_subscription(
+ msg_type=JointState,
+ topic="/joint_states",
+ callback=self.__joint_state_callback,
+ qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.BEST_EFFORT,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ callback_group=self._callback_group,
+ )
+
+ # Create action client for move action
+ self.__move_action_client = ActionClient(
+ node=self._node,
+ action_type=MoveGroup,
+ action_name="/move_action",
+ goal_service_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ result_service_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=5,
+ ),
+ cancel_service_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=5,
+ ),
+ feedback_sub_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.BEST_EFFORT,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ status_sub_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.BEST_EFFORT,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ callback_group=self._callback_group,
+ )
+
+ # Also create a separate service client for planning
+ self._plan_kinematic_path_service = self._node.create_client(
+ srv_type=GetMotionPlan,
+ srv_name="/plan_kinematic_path",
+ qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ callback_group=callback_group,
+ )
+ self.__kinematic_path_request = GetMotionPlan.Request()
+
+ # Create a separate service client for Cartesian planning
+ self._plan_cartesian_path_service = self._node.create_client(
+ srv_type=GetCartesianPath,
+ srv_name="/compute_cartesian_path",
+ qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ callback_group=callback_group,
+ )
+ self.__cartesian_path_request = GetCartesianPath.Request()
+
+ # Create action client for trajectory execution
+ self._execute_trajectory_action_client = ActionClient(
+ node=self._node,
+ action_type=ExecuteTrajectory,
+ action_name="/execute_trajectory",
+ goal_service_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ result_service_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=5,
+ ),
+ cancel_service_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=5,
+ ),
+ feedback_sub_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.BEST_EFFORT,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ status_sub_qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.BEST_EFFORT,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ callback_group=self._callback_group,
+ )
+
+ # Create a service for getting the planning scene
+ self._get_planning_scene_service = self._node.create_client(
+ srv_type=GetPlanningScene,
+ srv_name="/get_planning_scene",
+ qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ callback_group=callback_group,
+ )
+ self.__planning_scene = None
+ self.__old_planning_scene = None
+ self.__old_allowed_collision_matrix = None
+
+ # Create a service for applying the planning scene
+ self._apply_planning_scene_service = self._node.create_client(
+ srv_type=ApplyPlanningScene,
+ srv_name="/apply_planning_scene",
+ qos_profile=QoSProfile(
+ durability=QoSDurabilityPolicy.VOLATILE,
+ reliability=QoSReliabilityPolicy.RELIABLE,
+ history=QoSHistoryPolicy.KEEP_LAST,
+ depth=1,
+ ),
+ callback_group=callback_group,
+ )
+
+ self.__collision_object_publisher = self._node.create_publisher(
+ CollisionObject, "/collision_object", 10
+ )
+ self.__attached_collision_object_publisher = self._node.create_publisher(
+ AttachedCollisionObject, "/attached_collision_object", 10
+ )
+
+ self.__cancellation_pub = self._node.create_publisher(
+ String, "/trajectory_execution_event", 1
+ )
+
+ self.__joint_state_mutex = threading.Lock()
+ self.__joint_state = None
+ self.__new_joint_state_available = False
+ self.__move_action_goal = self.__init_move_action_goal(
+ frame_id=base_link_name,
+ group_name=group_name,
+ end_effector=end_effector_name,
+ )
+
+ # Flag to determine whether to execute trajectories via Move Group Action, or rather by calling
+ # the separate ExecuteTrajectory action
+ # Applies to `move_to_pose()` and `move_to_configuration()`
+ self.__use_move_group_action = use_move_group_action
+
+ # Flag that determines whether a new goal can be sent while the previous one is being executed
+ self.__ignore_new_calls_while_executing = ignore_new_calls_while_executing
+
+ # Store additional variables for later use
+ self.__joint_names = joint_names
+ self.__base_link_name = base_link_name
+ self.__end_effector_name = end_effector_name
+ self.__group_name = group_name
+
+ # Internal states that monitor the current motion requests and execution
+ self.__is_motion_requested = False
+ self.__is_executing = False
+ self.motion_suceeded = False
+ self.__execution_goal_handle = None
+ self.__last_error_code = None
+ self.__wait_until_executed_rate = self._node.create_rate(1000.0)
+ self.__execution_mutex = threading.Lock()
+
+ # Event that enables waiting until async future is done
+ self.__future_done_event = threading.Event()
+
+ #### Execution Polling Functions
+ def query_state(self) -> MoveIt2State:
+ with self.__execution_mutex:
+ if self.__is_motion_requested:
+ return MoveIt2State.REQUESTING
+ elif self.__is_executing:
+ return MoveIt2State.EXECUTING
+ else:
+ return MoveIt2State.IDLE
+
+ def cancel_execution(self):
+ if self.query_state() != MoveIt2State.EXECUTING:
+ self._node.get_logger().warn("Attempted to cancel without active goal.")
+ return None
+
+ cancel_string = String()
+ cancel_string.data = "stop"
+ self.__cancellation_pub.publish(cancel_string)
+
+ def get_execution_future(self) -> Optional[Future]:
+ if self.query_state() != MoveIt2State.EXECUTING:
+ self._node.get_logger().warn("Need active goal for future.")
+ return None
+
+ return self.__execution_goal_handle.get_result_async()
+
+ def get_last_execution_error_code(self) -> Optional[MoveItErrorCodes]:
+ return self.__last_error_code
+
+ ####
+
+ def move_to_pose(
+ self,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ target_link: Optional[str] = None,
+ frame_id: Optional[str] = None,
+ tolerance_position: float = 0.001,
+ tolerance_orientation: float = 0.001,
+ weight_position: float = 1.0,
+ cartesian: bool = True,
+ weight_orientation: float = 1.0,
+ cartesian_max_step: float = 0.0025,
+ cartesian_fraction_threshold: float = 0.0,
+ ):
+ """
+ Plan and execute motion based on previously set goals. Optional arguments can be
+ passed in to internally use `set_pose_goal()` to define a goal during the call.
+ """
+
+ if isinstance(pose, PoseStamped):
+ pose_stamped = pose
+ elif isinstance(pose, Pose):
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=pose,
+ )
+ else:
+ if not isinstance(position, Point):
+ position = Point(
+ x=float(position[0]), y=float(position[1]), z=float(position[2])
+ )
+ if not isinstance(quat_xyzw, Quaternion):
+ quat_xyzw = Quaternion(
+ x=float(quat_xyzw[0]),
+ y=float(quat_xyzw[1]),
+ z=float(quat_xyzw[2]),
+ w=float(quat_xyzw[3]),
+ )
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=Pose(position=position, orientation=quat_xyzw),
+ )
+
+ if self.__use_move_group_action and not cartesian:
+ if self.__ignore_new_calls_while_executing and (
+ self.__is_motion_requested or self.__is_executing
+ ):
+ self._node.get_logger().warn(
+ "Controller is already following a trajectory. Skipping motion."
+ )
+ return
+
+ # Set goal
+ self.set_pose_goal(
+ position=pose_stamped.pose.position,
+ quat_xyzw=pose_stamped.pose.orientation,
+ frame_id=pose_stamped.header.frame_id,
+ target_link=target_link,
+ tolerance_position=tolerance_position,
+ tolerance_orientation=tolerance_orientation,
+ weight_position=weight_position,
+ weight_orientation=weight_orientation,
+ )
+ # Define starting state as the current state
+ if self.joint_state is not None:
+ self.__move_action_goal.request.start_state.joint_state = (
+ self.joint_state
+ )
+ # Send to goal to the server (async) - both planning and execution
+ self._send_goal_async_move_action()
+ # Clear all previous goal constrains
+ self.clear_goal_constraints()
+ self.clear_path_constraints()
+
+ else:
+ # Plan via MoveIt 2 and then execute directly with the controller
+ self.execute(
+ self.plan(
+ position=pose_stamped.pose.position,
+ quat_xyzw=pose_stamped.pose.orientation,
+ frame_id=pose_stamped.header.frame_id,
+ target_link=target_link,
+ tolerance_position=tolerance_position,
+ tolerance_orientation=tolerance_orientation,
+ weight_position=weight_position,
+ weight_orientation=weight_orientation,
+ cartesian=cartesian,
+ max_step=cartesian_max_step,
+ cartesian_fraction_threshold=cartesian_fraction_threshold,
+ )
+ )
+
+ def move_to_configuration(
+ self,
+ joint_positions: List[float],
+ joint_names: Optional[List[str]] = None,
+ tolerance: float = 0.001,
+ weight: float = 1.0,
+ cartesian: bool = False,
+ ):
+ """
+ Plan and execute motion based on previously set goals. Optional arguments can be
+ passed in to internally use `set_joint_goal()` to define a goal during the call.
+ """
+
+ if self.__use_move_group_action:
+ if self.__ignore_new_calls_while_executing and (
+ self.__is_motion_requested or self.__is_executing
+ ):
+ self._node.get_logger().warn(
+ "Controller is already following a trajectory. Skipping motion."
+ )
+ return
+
+ # Set goal
+ self.set_joint_goal(
+ joint_positions=joint_positions,
+ joint_names=joint_names,
+ tolerance=tolerance,
+ weight=weight,
+ )
+ # Define starting state as the current state
+ if self.joint_state is not None:
+ self.__move_action_goal.request.start_state.joint_state = (
+ self.joint_state
+ )
+ # Send to goal to the server (async) - both planning and execution
+ self._send_goal_async_move_action()
+ # Clear all previous goal constrains
+ self.clear_goal_constraints()
+ self.clear_path_constraints()
+
+ else:
+ # Plan via MoveIt 2 and then execute directly with the controller
+ self.execute(
+ self.plan(
+ joint_positions=joint_positions,
+ joint_names=joint_names,
+ tolerance_joint_position=tolerance,
+ weight_joint_position=weight,
+ cartesian=cartesian,
+ )
+ )
+
+ def plan(
+ self,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ joint_positions: Optional[List[float]] = None,
+ joint_names: Optional[List[str]] = None,
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance_position: float = 0.001,
+ tolerance_orientation: Union[float, Tuple[float, float, float]] = 0.001,
+ tolerance_joint_position: float = 0.001,
+ weight_position: float = 1.0,
+ weight_orientation: float = 1.0,
+ weight_joint_position: float = 1.0,
+ start_joint_state: Optional[Union[JointState, List[float]]] = None,
+ cartesian: bool = False,
+ max_step: float = 0.0025,
+ cartesian_fraction_threshold: float = 0.0,
+ ) -> Optional[JointTrajectory]:
+ """
+ Call plan_async and wait on future
+ """
+ future = self.plan_async(
+ **{
+ key: value
+ for key, value in locals().items()
+ if key not in ["self", "cartesian_fraction_threshold"]
+ }
+ )
+
+ if future is None:
+ return None
+
+ # 100ms sleep
+ rate = self._node.create_rate(10)
+ while not future.done():
+ rate.sleep()
+
+ return self.get_trajectory(
+ future,
+ cartesian=cartesian,
+ cartesian_fraction_threshold=cartesian_fraction_threshold,
+ )
+
+ def plan_async(
+ self,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ joint_positions: Optional[List[float]] = None,
+ joint_names: Optional[List[str]] = None,
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance_position: float = 0.001,
+ tolerance_orientation: Union[float, Tuple[float, float, float]] = 0.001,
+ tolerance_joint_position: float = 0.001,
+ weight_position: float = 1.0,
+ weight_orientation: float = 1.0,
+ weight_joint_position: float = 1.0,
+ start_joint_state: Optional[Union[JointState, List[float]]] = None,
+ cartesian: bool = False,
+ max_step: float = 0.0025,
+ ) -> Optional[Future]:
+ """
+ Plan motion based on previously set goals. Optional arguments can be passed in to
+ internally use `set_position_goal()`, `set_orientation_goal()` or `set_joint_goal()`
+ to define a goal during the call. If no trajectory is found within the timeout
+ duration, `None` is returned. To plan from the different position than the current
+ one, optional argument `start_` can be defined.
+ """
+
+ pose_stamped = None
+ if pose is not None:
+ if isinstance(pose, PoseStamped):
+ pose_stamped = pose
+ elif isinstance(pose, Pose):
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=pose,
+ )
+
+ self.set_position_goal(
+ position=pose_stamped.pose.position,
+ frame_id=pose_stamped.header.frame_id,
+ target_link=target_link,
+ tolerance=tolerance_position,
+ weight=weight_position,
+ )
+ self.set_orientation_goal(
+ quat_xyzw=pose_stamped.pose.orientation,
+ frame_id=pose_stamped.header.frame_id,
+ target_link=target_link,
+ tolerance=tolerance_orientation,
+ weight=weight_orientation,
+ )
+ else:
+ if position is not None:
+ if not isinstance(position, Point):
+ position = Point(
+ x=float(position[0]), y=float(position[1]), z=float(position[2])
+ )
+
+ self.set_position_goal(
+ position=position,
+ frame_id=frame_id,
+ target_link=target_link,
+ tolerance=tolerance_position,
+ weight=weight_position,
+ )
+
+ if quat_xyzw is not None:
+ if not isinstance(quat_xyzw, Quaternion):
+ quat_xyzw = Quaternion(
+ x=float(quat_xyzw[0]),
+ y=float(quat_xyzw[1]),
+ z=float(quat_xyzw[2]),
+ w=float(quat_xyzw[3]),
+ )
+
+ self.set_orientation_goal(
+ quat_xyzw=quat_xyzw,
+ frame_id=frame_id,
+ target_link=target_link,
+ tolerance=tolerance_orientation,
+ weight=weight_orientation,
+ )
+
+ if joint_positions is not None:
+ self.set_joint_goal(
+ joint_positions=joint_positions,
+ joint_names=joint_names,
+ tolerance=tolerance_joint_position,
+ weight=weight_joint_position,
+ )
+
+ # Define starting state for the plan (default to the current state)
+ if start_joint_state is not None:
+ if isinstance(start_joint_state, JointState):
+ self.__move_action_goal.request.start_state.joint_state = (
+ start_joint_state
+ )
+ else:
+ self.__move_action_goal.request.start_state.joint_state = (
+ init_joint_state(
+ joint_names=self.__joint_names,
+ joint_positions=start_joint_state,
+ )
+ )
+ elif self.joint_state is not None:
+ self.__move_action_goal.request.start_state.joint_state = self.joint_state
+
+ # Plan trajectory asynchronously by service call
+ if cartesian:
+ future = self._plan_cartesian_path(
+ max_step=max_step,
+ frame_id=(
+ pose_stamped.header.frame_id
+ if pose_stamped is not None
+ else frame_id
+ ),
+ )
+ else:
+ # Use service
+ future = self._plan_kinematic_path()
+
+
+ # Clear all previous goal constrains
+ self.clear_goal_constraints()
+ self.clear_path_constraints()
+
+ return future
+
+ def get_trajectory(
+ self,
+ future: Future,
+ cartesian: bool = False,
+ cartesian_fraction_threshold: float = 0.0,
+ ) -> Optional[JointTrajectory]:
+ """
+ Takes in a future returned by plan_async and returns the trajectory if the future is done
+ and planning was successful, else None.
+
+ For cartesian plans, the plan is rejected if the fraction of the path that was completed is
+ less than `cartesian_fraction_threshold`.
+ """
+ if not future.done():
+ self._node.get_logger().warn(
+ "Cannot get trajectory because future is not done."
+ )
+ return None
+
+ res = future.result()
+
+ # Cartesian
+ if cartesian:
+ if MoveItErrorCodes.SUCCESS == res.error_code.val:
+ if res.fraction >= cartesian_fraction_threshold:
+ return res.solution.joint_trajectory
+ else:
+ self._node.get_logger().warn(
+ f"Planning failed! Cartesian planner completed {res.fraction} "
+ f"of the trajectory, less than the threshold {cartesian_fraction_threshold}."
+ )
+ return None
+ else:
+ self._node.get_logger().warn(
+ f"Planning failed! Error code: {res.error_code.val}."
+ )
+ return None
+
+ # Else Kinematic
+ res = res.motion_plan_response
+ if MoveItErrorCodes.SUCCESS == res.error_code.val:
+ return res.trajectory.joint_trajectory
+ else:
+ self._node.get_logger().warn(
+ f"Planning failed! Error code: {res.error_code.val}."
+ )
+ return None
+
+ def execute(self, joint_trajectory: JointTrajectory):
+ """
+ Execute joint_trajectory by communicating directly with the controller.
+ """
+
+ if self.__ignore_new_calls_while_executing and (
+ self.__is_motion_requested or self.__is_executing
+ ):
+ self._node.get_logger().warn(
+ "Controller is already following a trajectory. Skipping motion."
+ )
+ return
+
+ execute_trajectory_goal = init_execute_trajectory_goal(
+ joint_trajectory=joint_trajectory
+ )
+
+ if execute_trajectory_goal is None:
+ self._node.get_logger().warn(
+ "Cannot execute motion because the provided/planned trajectory is invalid."
+ )
+ return
+
+ self._send_goal_async_execute_trajectory(goal=execute_trajectory_goal)
+
+ def wait_until_executed(self) -> bool:
+ """
+ Wait until the previously requested motion is finalised through either a success or failure.
+ """
+
+ if not self.__is_motion_requested:
+ self._node.get_logger().warn(
+ "Cannot wait until motion is executed (no motion is in progress)."
+ )
+ return False
+
+ while self.__is_motion_requested or self.__is_executing:
+ self.__wait_until_executed_rate.sleep()
+
+ return self.motion_suceeded
+
+ def reset_controller(
+ self, joint_state: Union[JointState, List[float]], sync: bool = True
+ ):
+ """
+ Reset controller to a given `joint_state` by sending a dummy joint trajectory.
+ This is useful for simulated robots that allow instantaneous reset of joints.
+ """
+
+ if not isinstance(joint_state, JointState):
+ joint_state = init_joint_state(
+ joint_names=self.__joint_names,
+ joint_positions=joint_state,
+ )
+ joint_trajectory = init_dummy_joint_trajectory_from_state(joint_state)
+ execute_trajectory_goal = init_execute_trajectory_goal(
+ joint_trajectory=joint_trajectory
+ )
+
+ self._send_goal_async_execute_trajectory(
+ goal=execute_trajectory_goal,
+ wait_until_response=sync,
+ )
+
+ def set_pose_goal(
+ self,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance_position: float = 0.001,
+ tolerance_orientation: Union[float, Tuple[float, float, float]] = 0.001,
+ weight_position: float = 1.0,
+ weight_orientation: float = 1.0,
+ ):
+ """
+ This is direct combination of `set_position_goal()` and `set_orientation_goal()`.
+ """
+
+ if (pose is None) and (position is None or quat_xyzw is None):
+ raise ValueError(
+ "Either `pose` or `position` and `quat_xyzw` must be specified!"
+ )
+
+ if isinstance(pose, PoseStamped):
+ pose_stamped = pose
+ elif isinstance(pose, Pose):
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=pose,
+ )
+ else:
+ if not isinstance(position, Point):
+ position = Point(
+ x=float(position[0]), y=float(position[1]), z=float(position[2])
+ )
+ if not isinstance(quat_xyzw, Quaternion):
+ quat_xyzw = Quaternion(
+ x=float(quat_xyzw[0]),
+ y=float(quat_xyzw[1]),
+ z=float(quat_xyzw[2]),
+ w=float(quat_xyzw[3]),
+ )
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=Pose(position=position, orientation=quat_xyzw),
+ )
+
+ self.set_position_goal(
+ position=pose_stamped.pose.position,
+ frame_id=pose_stamped.header.frame_id,
+ target_link=target_link,
+ tolerance=tolerance_position,
+ weight=weight_position,
+ )
+ self.set_orientation_goal(
+ quat_xyzw=pose_stamped.pose.orientation,
+ frame_id=pose_stamped.header.frame_id,
+ target_link=target_link,
+ tolerance=tolerance_orientation,
+ weight=weight_orientation,
+ )
+
+ def create_position_constraint(
+ self,
+ position: Union[Point, Tuple[float, float, float]],
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance: float = 0.001,
+ weight: float = 1.0,
+ ) -> PositionConstraint:
+ """
+ Create Cartesian position constraint of `target_link` with respect to `frame_id`.
+ - `frame_id` defaults to the base link
+ - `target_link` defaults to end effector
+ """
+
+ # Create new position constraint
+ constraint = PositionConstraint()
+
+ # Define reference frame and target link
+ constraint.header.frame_id = (
+ frame_id if frame_id is not None else self.__base_link_name
+ )
+ constraint.link_name = (
+ target_link if target_link is not None else self.__end_effector_name
+ )
+
+ # Define target position
+ constraint.constraint_region.primitive_poses.append(Pose())
+ if isinstance(position, Point):
+ constraint.constraint_region.primitive_poses[0].position = position
+ else:
+ constraint.constraint_region.primitive_poses[0].position.x = float(
+ position[0]
+ )
+ constraint.constraint_region.primitive_poses[0].position.y = float(
+ position[1]
+ )
+ constraint.constraint_region.primitive_poses[0].position.z = float(
+ position[2]
+ )
+
+ # Define goal region as a sphere with radius equal to the tolerance
+ constraint.constraint_region.primitives.append(SolidPrimitive())
+ constraint.constraint_region.primitives[0].type = 2 # Sphere
+ constraint.constraint_region.primitives[0].dimensions = [tolerance]
+
+ # Set weight of the constraint
+ constraint.weight = weight
+
+ return constraint
+
+ def set_position_goal(
+ self,
+ position: Union[Point, Tuple[float, float, float]],
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance: float = 0.001,
+ weight: float = 1.0,
+ ):
+ """
+ Set Cartesian position goal of `target_link` with respect to `frame_id`.
+ - `frame_id` defaults to the base link
+ - `target_link` defaults to end effector
+ """
+
+ constraint = self.create_position_constraint(
+ position=position,
+ frame_id=frame_id,
+ target_link=target_link,
+ tolerance=tolerance,
+ weight=weight,
+ )
+
+ # Append to other constraints
+ self.__move_action_goal.request.goal_constraints[
+ -1
+ ].position_constraints.append(constraint)
+
+ def create_orientation_constraint(
+ self,
+ quat_xyzw: Union[Quaternion, Tuple[float, float, float, float]],
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance: Union[float, Tuple[float, float, float]] = 0.001,
+ weight: float = 1.0,
+ parameterization: int = 0, # 0: Euler, 1: Rotation Vector
+ ) -> OrientationConstraint:
+ """
+ Create a Cartesian orientation constraint of `target_link` with respect to `frame_id`.
+ - `frame_id` defaults to the base link
+ - `target_link` defaults to end effector
+ """
+
+ # Create new position constraint
+ constraint = OrientationConstraint()
+
+ # Define reference frame and target link
+ constraint.header.frame_id = (
+ frame_id if frame_id is not None else self.__base_link_name
+ )
+ constraint.link_name = (
+ target_link if target_link is not None else self.__end_effector_name
+ )
+
+ # Define target orientation
+ if isinstance(quat_xyzw, Quaternion):
+ constraint.orientation = quat_xyzw
+ else:
+ constraint.orientation.x = float(quat_xyzw[0])
+ constraint.orientation.y = float(quat_xyzw[1])
+ constraint.orientation.z = float(quat_xyzw[2])
+ constraint.orientation.w = float(quat_xyzw[3])
+
+ # Define tolerances
+ if type(tolerance) == float:
+ tolerance_xyz = (tolerance, tolerance, tolerance)
+ else:
+ tolerance_xyz = tolerance
+ constraint.absolute_x_axis_tolerance = tolerance_xyz[0]
+ constraint.absolute_y_axis_tolerance = tolerance_xyz[1]
+ constraint.absolute_z_axis_tolerance = tolerance_xyz[2]
+
+ # Define parameterization (how to interpret the tolerance)
+ constraint.parameterization = parameterization
+
+ # Set weight of the constraint
+ constraint.weight = weight
+
+ return constraint
+
+ def set_orientation_goal(
+ self,
+ quat_xyzw: Union[Quaternion, Tuple[float, float, float, float]],
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance: Union[float, Tuple[float, float, float]] = 0.001,
+ weight: float = 1.0,
+ parameterization: int = 0, # 0: Euler, 1: Rotation Vector
+ ):
+ """
+ Set Cartesian orientation goal of `target_link` with respect to `frame_id`.
+ - `frame_id` defaults to the base link
+ - `target_link` defaults to end effector
+ """
+
+ constraint = self.create_orientation_constraint(
+ quat_xyzw=quat_xyzw,
+ frame_id=frame_id,
+ target_link=target_link,
+ tolerance=tolerance,
+ weight=weight,
+ parameterization=parameterization,
+ )
+
+ # Append to other constraints
+ self.__move_action_goal.request.goal_constraints[
+ -1
+ ].orientation_constraints.append(constraint)
+
+ def create_joint_constraints(
+ self,
+ joint_positions: List[float],
+ joint_names: Optional[List[str]] = None,
+ tolerance: float = 0.001,
+ weight: float = 1.0,
+ ) -> List[JointConstraint]:
+ """
+ Creates joint space constraints. With `joint_names` specified, `joint_positions` can be
+ defined for specific joints in an arbitrary order. Otherwise, first **n** joints
+ passed into the constructor is used, where **n** is the length of `joint_positions`.
+ """
+
+ constraints = []
+
+ # Use default joint names if not specified
+ if joint_names == None:
+ joint_names = self.__joint_names
+
+ for i in range(len(joint_positions)):
+ # Create a new constraint for each joint
+ constraint = JointConstraint()
+
+ # Define joint name
+ constraint.joint_name = joint_names[i]
+
+ # Define the target joint position
+ constraint.position = joint_positions[i]
+
+ # Define telerances
+ constraint.tolerance_above = tolerance
+ constraint.tolerance_below = tolerance
+
+ # Set weight of the constraint
+ constraint.weight = weight
+
+ constraints.append(constraint)
+
+ return constraints
+
+ def set_joint_goal(
+ self,
+ joint_positions: List[float],
+ joint_names: Optional[List[str]] = None,
+ tolerance: float = 0.001,
+ weight: float = 1.0,
+ ):
+ """
+ Set joint space goal. With `joint_names` specified, `joint_positions` can be
+ defined for specific joints in an arbitrary order. Otherwise, first **n** joints
+ passed into the constructor is used, where **n** is the length of `joint_positions`.
+ """
+
+ constraints = self.create_joint_constraints(
+ joint_positions=joint_positions,
+ joint_names=joint_names,
+ tolerance=tolerance,
+ weight=weight,
+ )
+
+ # Append to other constraints
+ self.__move_action_goal.request.goal_constraints[-1].joint_constraints.extend(
+ constraints
+ )
+
+ def clear_goal_constraints(self):
+ """
+ Clear all goal constraints that were previously set.
+ Note that this function is called automatically after each `plan_kinematic_path()`.
+ """
+
+ self.__move_action_goal.request.goal_constraints = [Constraints()]
+
+ def create_new_goal_constraint(self):
+ """
+ Create a new set of goal constraints that will be set together with the request. Each
+ subsequent setting of goals with `set_joint_goal()`, `set_pose_goal()` and others will be
+ added under this newly created set of constraints.
+ """
+
+ self.__move_action_goal.request.goal_constraints.append(Constraints())
+
+ def set_path_joint_constraint(
+ self,
+ joint_positions: List[float],
+ joint_names: Optional[List[str]] = None,
+ tolerance: float = 0.001,
+ weight: float = 1.0,
+ ):
+ """
+ Set joint space path constraints. With `joint_names` specified, `joint_positions` can be
+ defined for specific joints in an arbitrary order. Otherwise, first **n** joints
+ passed into the constructor is used, where **n** is the length of `joint_positions`.
+ """
+
+ constraints = self.create_joint_constraints(
+ joint_positions=joint_positions,
+ joint_names=joint_names,
+ tolerance=tolerance,
+ weight=weight,
+ )
+
+ # Append to other constraints
+ self.__move_action_goal.request.path_constraints.joint_constraints.extend(
+ constraints
+ )
+
+ def set_path_position_constraint(
+ self,
+ position: Union[Point, Tuple[float, float, float]],
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance: float = 0.001,
+ weight: float = 1.0,
+ ):
+ """
+ Set Cartesian position path constraint of `target_link` with respect to `frame_id`.
+ - `frame_id` defaults to the base link
+ - `target_link` defaults to end effector
+ """
+
+ constraint = self.create_position_constraint(
+ position=position,
+ frame_id=frame_id,
+ target_link=target_link,
+ tolerance=tolerance,
+ weight=weight,
+ )
+
+ # Append to other constraints
+ self.__move_action_goal.request.path_constraints.position_constraints.append(
+ constraint
+ )
+
+ def set_path_orientation_constraint(
+ self,
+ quat_xyzw: Union[Quaternion, Tuple[float, float, float, float]],
+ frame_id: Optional[str] = None,
+ target_link: Optional[str] = None,
+ tolerance: Union[float, Tuple[float, float, float]] = 0.001,
+ weight: float = 1.0,
+ parameterization: int = 0, # 0: Euler Angles, 1: Rotation Vector
+ ):
+ """
+ Set Cartesian orientation path constraint of `target_link` with respect to `frame_id`.
+ - `frame_id` defaults to the base link
+ - `target_link` defaults to end effector
+ """
+
+ constraint = self.create_orientation_constraint(
+ quat_xyzw=quat_xyzw,
+ frame_id=frame_id,
+ target_link=target_link,
+ tolerance=tolerance,
+ weight=weight,
+ parameterization=parameterization,
+ )
+
+ # Append to other constraints
+ self.__move_action_goal.request.path_constraints.orientation_constraints.append(
+ constraint
+ )
+
+ def clear_path_constraints(self):
+ """
+ Clear all path constraints that were previously set.
+ Note that this function is called automatically after each `plan_kinematic_path()`.
+ """
+
+ self.__move_action_goal.request.path_constraints = Constraints()
+
+ def compute_fk(
+ self,
+ joint_state: Optional[Union[JointState, List[float]]] = None,
+ fk_link_names: Optional[List[str]] = None,
+ ) -> Optional[Union[PoseStamped, List[PoseStamped]]]:
+ """
+ Call compute_fk_async and wait on future
+ """
+ future = self.compute_fk_async(
+ **{key: value for key, value in locals().items() if key != "self"}
+ )
+
+ if future is None:
+ return None
+
+ # 100ms sleep
+ rate = self._node.create_rate(10)
+ while not future.done():
+ rate.sleep()
+
+ return self.get_compute_fk_result(future, fk_link_names=fk_link_names)
+
+ def get_compute_fk_result(
+ self,
+ future: Future,
+ fk_link_names: Optional[List[str]] = None,
+ ) -> Optional[Union[PoseStamped, List[PoseStamped]]]:
+ """
+ Takes in a future returned by compute_fk_async and returns the poses
+ if the future is done and successful, else None.
+ """
+ if not future.done():
+ self._node.get_logger().warn(
+ "Cannot get FK result because future is not done."
+ )
+ return None
+
+ res = future.result()
+
+ if MoveItErrorCodes.SUCCESS == res.error_code.val:
+ if fk_link_names is None:
+ return res.pose_stamped[0]
+ else:
+ return res.pose_stamped
+ else:
+ self._node.get_logger().warn(
+ f"FK computation failed! Error code: {res.error_code.val}."
+ )
+ return None
+
+ def compute_fk_async(
+ self,
+ joint_state: Optional[Union[JointState, List[float]]] = None,
+ fk_link_names: Optional[List[str]] = None,
+ ) -> Optional[Future]:
+ """
+ Compute forward kinematics for all `fk_link_names` in a given `joint_state`.
+ - `fk_link_names` defaults to end-effector
+ - `joint_state` defaults to the current joint state
+ """
+
+ if not hasattr(self, "__compute_fk_client"):
+ self.__init_compute_fk()
+
+ if fk_link_names is None:
+ self.__compute_fk_req.fk_link_names = [self.__end_effector_name]
+ else:
+ self.__compute_fk_req.fk_link_names = fk_link_names
+
+ if joint_state is not None:
+ if isinstance(joint_state, JointState):
+ self.__compute_fk_req.robot_state.joint_state = joint_state
+ else:
+ self.__compute_fk_req.robot_state.joint_state = init_joint_state(
+ joint_names=self.__joint_names,
+ joint_positions=joint_state,
+ )
+ elif self.joint_state is not None:
+ self.__compute_fk_req.robot_state.joint_state = self.joint_state
+
+ stamp = self._node.get_clock().now().to_msg()
+ self.__compute_fk_req.header.stamp = stamp
+
+ if not self.__compute_fk_client.service_is_ready():
+ self._node.get_logger().warn(
+ f"Service '{self.__compute_fk_client.srv_name}' is not yet available. Better luck next time!"
+ )
+ return None
+
+ return self.__compute_fk_client.call_async(self.__compute_fk_req)
+
+ def compute_ik(
+ self,
+ position: Union[Point, Tuple[float, float, float]],
+ quat_xyzw: Union[Quaternion, Tuple[float, float, float, float]],
+ start_joint_state: Optional[Union[JointState, List[float]]] = None,
+ constraints: Optional[Constraints] = None,
+ wait_for_server_timeout_sec: Optional[float] = 1.0,
+ ) -> Optional[JointState]:
+ """
+ Call compute_ik_async and wait on future
+ """
+ future = self.compute_ik_async(
+ **{key: value for key, value in locals().items() if key != "self"}
+ )
+
+ if future is None:
+ return None
+
+ # 10ms sleep
+ rate = self._node.create_rate(10)
+ while not future.done():
+ rate.sleep()
+
+ return self.get_compute_ik_result(future)
+
+ def get_compute_ik_result(
+ self,
+ future: Future,
+ ) -> Optional[JointState]:
+ """
+ Takes in a future returned by compute_ik_async and returns the joint states
+ if the future is done and successful, else None.
+ """
+ if not future.done():
+ self._node.get_logger().warn(
+ "Cannot get IK result because future is not done."
+ )
+ return None
+
+ res = future.result()
+
+ if MoveItErrorCodes.SUCCESS == res.error_code.val:
+ return res.solution.joint_state
+ else:
+ self._node.get_logger().warn(
+ f"IK computation failed! Error code: {res.error_code.val}."
+ )
+ return None
+
+ def compute_ik_async(
+ self,
+ position: Union[Point, Tuple[float, float, float]],
+ quat_xyzw: Union[Quaternion, Tuple[float, float, float, float]],
+ start_joint_state: Optional[Union[JointState, List[float]]] = None,
+ constraints: Optional[Constraints] = None,
+ wait_for_server_timeout_sec: Optional[float] = 1.0,
+ ) -> Optional[Future]:
+ """
+ Compute inverse kinematics for the given pose. To indicate beginning of the search space,
+ `start_joint_state` can be specified. Furthermore, `constraints` can be imposed on the
+ computed IK.
+ - `start_joint_state` defaults to current joint state.
+ - `constraints` defaults to None.
+ """
+
+ if not hasattr(self, "__compute_ik_client"):
+ self.__init_compute_ik()
+
+ if isinstance(position, Point):
+ self.__compute_ik_req.ik_request.pose_stamped.pose.position = position
+ else:
+ self.__compute_ik_req.ik_request.pose_stamped.pose.position.x = float(
+ position[0]
+ )
+ self.__compute_ik_req.ik_request.pose_stamped.pose.position.y = float(
+ position[1]
+ )
+ self.__compute_ik_req.ik_request.pose_stamped.pose.position.z = float(
+ position[2]
+ )
+ if isinstance(quat_xyzw, Quaternion):
+ self.__compute_ik_req.ik_request.pose_stamped.pose.orientation = quat_xyzw
+ else:
+ self.__compute_ik_req.ik_request.pose_stamped.pose.orientation.x = float(
+ quat_xyzw[0]
+ )
+ self.__compute_ik_req.ik_request.pose_stamped.pose.orientation.y = float(
+ quat_xyzw[1]
+ )
+ self.__compute_ik_req.ik_request.pose_stamped.pose.orientation.z = float(
+ quat_xyzw[2]
+ )
+ self.__compute_ik_req.ik_request.pose_stamped.pose.orientation.w = float(
+ quat_xyzw[3]
+ )
+
+ if start_joint_state is not None:
+ if isinstance(start_joint_state, JointState):
+ self.__compute_ik_req.ik_request.robot_state.joint_state = (
+ start_joint_state
+ )
+ else:
+ self.__compute_ik_req.ik_request.robot_state.joint_state = (
+ init_joint_state(
+ joint_names=self.__joint_names,
+ joint_positions=start_joint_state,
+ )
+ )
+ elif self.joint_state is not None:
+ self.__compute_ik_req.ik_request.robot_state.joint_state = self.joint_state
+
+ if constraints is not None:
+ self.__compute_ik_req.ik_request.constraints = constraints
+
+ stamp = self._node.get_clock().now().to_msg()
+ self.__compute_ik_req.ik_request.pose_stamped.header.stamp = stamp
+
+ if not self.__compute_ik_client.wait_for_service(
+ timeout_sec=wait_for_server_timeout_sec
+ ):
+ self._node.get_logger().warn(
+ f"Service '{self.__compute_ik_client.srv_name}' is not yet available. Better luck next time!"
+ )
+ return None
+
+ return self.__compute_ik_client.call_async(self.__compute_ik_req)
+
+ def reset_new_joint_state_checker(self):
+ """
+ Reset checker of the new joint state.
+ """
+
+ self.__joint_state_mutex.acquire()
+ self.__new_joint_state_available = False
+ self.__joint_state_mutex.release()
+
+ def force_reset_executing_state(self):
+ """
+ Force reset of internal states that block execution while `ignore_new_calls_while_executing` is being
+ used. This function is applicable only in a very few edge-cases, so it should almost never be used.
+ """
+
+ self.__execution_mutex.acquire()
+ self.__is_motion_requested = False
+ self.__is_executing = False
+ self.__execution_mutex.release()
+
+ def add_collision_primitive(
+ self,
+ id: str,
+ primitive_type: int,
+ dimensions: Tuple[float, float, float],
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ frame_id: Optional[str] = None,
+ operation: int = CollisionObject.ADD,
+ ):
+ """
+ Add collision object with a primitive geometry specified by its dimensions.
+
+ `primitive_type` can be one of the following:
+ - `SolidPrimitive.BOX`
+ - `SolidPrimitive.SPHERE`
+ - `SolidPrimitive.CYLINDER`
+ - `SolidPrimitive.CONE`
+ """
+
+ if (pose is None) and (position is None or quat_xyzw is None):
+ raise ValueError(
+ "Either `pose` or `position` and `quat_xyzw` must be specified!"
+ )
+
+ if isinstance(pose, PoseStamped):
+ pose_stamped = pose
+ elif isinstance(pose, Pose):
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=pose,
+ )
+ else:
+ if not isinstance(position, Point):
+ position = Point(
+ x=float(position[0]), y=float(position[1]), z=float(position[2])
+ )
+ if not isinstance(quat_xyzw, Quaternion):
+ quat_xyzw = Quaternion(
+ x=float(quat_xyzw[0]),
+ y=float(quat_xyzw[1]),
+ z=float(quat_xyzw[2]),
+ w=float(quat_xyzw[3]),
+ )
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=Pose(position=position, orientation=quat_xyzw),
+ )
+
+ msg = CollisionObject(
+ header=pose_stamped.header,
+ id=id,
+ operation=operation,
+ pose=pose_stamped.pose,
+ )
+
+ msg.primitives.append(
+ SolidPrimitive(type=primitive_type, dimensions=dimensions)
+ )
+
+ self.__collision_object_publisher.publish(msg)
+
+ def add_collision_box(
+ self,
+ id: str,
+ size: Tuple[float, float, float],
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ frame_id: Optional[str] = None,
+ operation: int = CollisionObject.ADD,
+ ):
+ """
+ Add collision object with a box geometry specified by its size.
+ """
+
+ assert len(size) == 3, "Invalid size of the box!"
+
+ self.add_collision_primitive(
+ id=id,
+ primitive_type=SolidPrimitive.BOX,
+ dimensions=size,
+ pose=pose,
+ position=position,
+ quat_xyzw=quat_xyzw,
+ frame_id=frame_id,
+ operation=operation,
+ )
+
+ def add_collision_sphere(
+ self,
+ id: str,
+ radius: float,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ frame_id: Optional[str] = None,
+ operation: int = CollisionObject.ADD,
+ ):
+ """
+ Add collision object with a sphere geometry specified by its radius.
+ """
+
+ if quat_xyzw is None:
+ quat_xyzw = Quaternion(x=0.0, y=0.0, z=0.0, w=1.0)
+
+ self.add_collision_primitive(
+ id=id,
+ primitive_type=SolidPrimitive.SPHERE,
+ dimensions=[
+ radius,
+ ],
+ pose=pose,
+ position=position,
+ quat_xyzw=quat_xyzw,
+ frame_id=frame_id,
+ operation=operation,
+ )
+
+ def add_collision_cylinder(
+ self,
+ id: str,
+ height: float,
+ radius: float,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ frame_id: Optional[str] = None,
+ operation: int = CollisionObject.ADD,
+ ):
+ """
+ Add collision object with a cylinder geometry specified by its height and radius.
+ """
+
+ self.add_collision_primitive(
+ id=id,
+ primitive_type=SolidPrimitive.CYLINDER,
+ dimensions=[height, radius],
+ pose=pose,
+ position=position,
+ quat_xyzw=quat_xyzw,
+ frame_id=frame_id,
+ operation=operation,
+ )
+
+ def add_collision_cone(
+ self,
+ id: str,
+ height: float,
+ radius: float,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ frame_id: Optional[str] = None,
+ operation: int = CollisionObject.ADD,
+ ):
+ """
+ Add collision object with a cone geometry specified by its height and radius.
+ """
+
+ self.add_collision_primitive(
+ id=id,
+ primitive_type=SolidPrimitive.CONE,
+ dimensions=[height, radius],
+ pose=pose,
+ position=position,
+ quat_xyzw=quat_xyzw,
+ frame_id=frame_id,
+ operation=operation,
+ )
+
+ def add_collision_mesh(
+ self,
+ filepath: Optional[str],
+ id: str,
+ pose: Optional[Union[PoseStamped, Pose]] = None,
+ position: Optional[Union[Point, Tuple[float, float, float]]] = None,
+ quat_xyzw: Optional[
+ Union[Quaternion, Tuple[float, float, float, float]]
+ ] = None,
+ frame_id: Optional[str] = None,
+ operation: int = CollisionObject.ADD,
+ scale: Union[float, Tuple[float, float, float]] = 1.0,
+ mesh: Optional[Any] = None,
+ ):
+ """
+ Add collision object with a mesh geometry. Either `filepath` must be
+ specified or `mesh` must be provided.
+ Note: This function required 'trimesh' Python module to be installed.
+ """
+
+ # Load the mesh
+ try:
+ import trimesh
+ except ImportError as err:
+ raise ImportError(
+ "Python module 'trimesh' not found! Please install it manually in order "
+ "to add collision objects into the MoveIt 2 planning scene."
+ ) from err
+
+ # Check the parameters
+ if (pose is None) and (position is None or quat_xyzw is None):
+ raise ValueError(
+ "Either `pose` or `position` and `quat_xyzw` must be specified!"
+ )
+ if (filepath is None and mesh is None) or (
+ filepath is not None and mesh is not None
+ ):
+ raise ValueError("Exactly one of `filepath` or `mesh` must be specified!")
+ if mesh is not None and not isinstance(mesh, trimesh.Trimesh):
+ raise ValueError("`mesh` must be an instance of `trimesh.Trimesh`!")
+
+ if isinstance(pose, PoseStamped):
+ pose_stamped = pose
+ elif isinstance(pose, Pose):
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=pose,
+ )
+ else:
+ if not isinstance(position, Point):
+ position = Point(
+ x=float(position[0]), y=float(position[1]), z=float(position[2])
+ )
+ if not isinstance(quat_xyzw, Quaternion):
+ quat_xyzw = Quaternion(
+ x=float(quat_xyzw[0]),
+ y=float(quat_xyzw[1]),
+ z=float(quat_xyzw[2]),
+ w=float(quat_xyzw[3]),
+ )
+ pose_stamped = PoseStamped(
+ header=Header(
+ stamp=self._node.get_clock().now().to_msg(),
+ frame_id=(
+ frame_id if frame_id is not None else self.__base_link_name
+ ),
+ ),
+ pose=Pose(position=position, orientation=quat_xyzw),
+ )
+
+ msg = CollisionObject(
+ header=pose_stamped.header,
+ id=id,
+ operation=operation,
+ pose=pose_stamped.pose,
+ )
+
+ if filepath is not None:
+ mesh = trimesh.load(filepath)
+
+ # Scale the mesh
+ if isinstance(scale, float):
+ scale = (scale, scale, scale)
+ if not (scale[0] == scale[1] == scale[2] == 1.0):
+ # If the mesh was passed in as a parameter, make a copy of it to
+ # avoid transforming the original.
+ if filepath is not None:
+ mesh = mesh.copy()
+ # Transform the mesh
+ transform = np.eye(4)
+ np.fill_diagonal(transform, scale)
+ mesh.apply_transform(transform)
+
+ msg.meshes.append(
+ Mesh(
+ triangles=[MeshTriangle(vertex_indices=face) for face in mesh.faces],
+ vertices=[
+ Point(x=vert[0], y=vert[1], z=vert[2]) for vert in mesh.vertices
+ ],
+ )
+ )
+
+ self.__collision_object_publisher.publish(msg)
+
+ def remove_collision_object(self, id: str):
+ """
+ Remove collision object specified by its `id`.
+ """
+
+ msg = CollisionObject()
+ msg.id = id
+ msg.operation = CollisionObject.REMOVE
+ msg.header.stamp = self._node.get_clock().now().to_msg()
+ self.__collision_object_publisher.publish(msg)
+
+ def remove_collision_mesh(self, id: str):
+ """
+ Remove collision mesh specified by its `id`.
+ Identical to `remove_collision_object()`.
+ """
+
+ self.remove_collision_object(id)
+
+ def attach_collision_object(
+ self,
+ id: str,
+ link_name: Optional[str] = None,
+ touch_links: List[str] = [],
+ weight: float = 0.0,
+ ):
+ """
+ Attach collision object to the robot.
+ """
+
+ if link_name is None:
+ link_name = self.__end_effector_name
+
+ msg = AttachedCollisionObject(
+ object=CollisionObject(id=id, operation=CollisionObject.ADD)
+ )
+ msg.link_name = link_name
+ msg.touch_links = touch_links
+ msg.weight = weight
+
+ self.__attached_collision_object_publisher.publish(msg)
+
+ def detach_collision_object(self, id: int):
+ """
+ Detach collision object from the robot.
+ """
+
+ msg = AttachedCollisionObject(
+ object=CollisionObject(id=id, operation=CollisionObject.REMOVE)
+ )
+ self.__attached_collision_object_publisher.publish(msg)
+
+ def detach_all_collision_objects(self):
+ """
+ Detach collision object from the robot.
+ """
+
+ msg = AttachedCollisionObject(
+ object=CollisionObject(operation=CollisionObject.REMOVE)
+ )
+ self.__attached_collision_object_publisher.publish(msg)
+
+ def move_collision(
+ self,
+ id: str,
+ position: Union[Point, Tuple[float, float, float]],
+ quat_xyzw: Union[Quaternion, Tuple[float, float, float, float]],
+ frame_id: Optional[str] = None,
+ ):
+ """
+ Move collision object specified by its `id`.
+ """
+
+ msg = CollisionObject()
+
+ if not isinstance(position, Point):
+ position = Point(
+ x=float(position[0]), y=float(position[1]), z=float(position[2])
+ )
+ if not isinstance(quat_xyzw, Quaternion):
+ quat_xyzw = Quaternion(
+ x=float(quat_xyzw[0]),
+ y=float(quat_xyzw[1]),
+ z=float(quat_xyzw[2]),
+ w=float(quat_xyzw[3]),
+ )
+
+ pose = Pose()
+ pose.position = position
+ pose.orientation = quat_xyzw
+ msg.pose = pose
+ msg.id = id
+ msg.operation = CollisionObject.MOVE
+ msg.header.frame_id = (
+ frame_id if frame_id is not None else self.__base_link_name
+ )
+ msg.header.stamp = self._node.get_clock().now().to_msg()
+
+ self.__collision_object_publisher.publish(msg)
+
+ def update_planning_scene(self) -> bool:
+ """
+ Gets the current planning scene. Returns whether the service call was
+ successful.
+ """
+
+ if not self._get_planning_scene_service.service_is_ready():
+ self._node.get_logger().warn(
+ f"Service '{self._get_planning_scene_service.srv_name}' is not yet available. Better luck next time!"
+ )
+ return False
+ self.__planning_scene = self._get_planning_scene_service.call(
+ GetPlanningScene.Request()
+ ).scene
+ return True
+
+ def allow_collisions(self, id: str, allow: bool) -> Optional[Future]:
+ """
+ Takes in the ID of an element in the planning scene. Modifies the allowed
+ collision matrix to (dis)allow collisions between that object and all other
+ object.
+
+ If `allow` is True, a plan will succeed even if the robot collides with that object.
+ If `allow` is False, a plan will fail if the robot collides with that object.
+ Returns whether it successfully updated the allowed collision matrix.
+
+ Returns the future of the service call.
+ """
+ # Update the planning scene
+ if not self.update_planning_scene():
+ return None
+ allowed_collision_matrix = self.__planning_scene.allowed_collision_matrix
+ self.__old_allowed_collision_matrix = copy.deepcopy(allowed_collision_matrix)
+
+ # Get the location in the allowed collision matrix of the object
+ j = None
+ if id not in allowed_collision_matrix.entry_names:
+ allowed_collision_matrix.entry_names.append(id)
+ else:
+ j = allowed_collision_matrix.entry_names.index(id)
+ # For all other objects, (dis)allow collisions with the object with `id`
+ for i in range(len(allowed_collision_matrix.entry_values)):
+ if j is None:
+ allowed_collision_matrix.entry_values[i].enabled.append(allow)
+ elif i != j:
+ allowed_collision_matrix.entry_values[i].enabled[j] = allow
+ # For the object with `id`, (dis)allow collisions with all other objects
+ allowed_collision_entry = AllowedCollisionEntry(
+ enabled=[allow for _ in range(len(allowed_collision_matrix.entry_names))]
+ )
+ if j is None:
+ allowed_collision_matrix.entry_values.append(allowed_collision_entry)
+ else:
+ allowed_collision_matrix.entry_values[j] = allowed_collision_entry
+
+ # Apply the new planning scene
+ if not self._apply_planning_scene_service.service_is_ready():
+ self._node.get_logger().warn(
+ f"Service '{self._apply_planning_scene_service.srv_name}' is not yet available. Better luck next time!"
+ )
+ return None
+ return self._apply_planning_scene_service.call_async(
+ ApplyPlanningScene.Request(scene=self.__planning_scene)
+ )
+
+ def process_allow_collision_future(self, future: Future) -> bool:
+ """
+ Return whether the allow collision service call is done and has succeeded
+ or not. If it failed, reset the allowed collision matrix to the old one.
+ """
+ if not future.done():
+ return False
+
+ # Get response
+ resp = future.result()
+
+ # If it failed, restore the old planning scene
+ if not resp.success:
+ self.__planning_scene.allowed_collision_matrix = (
+ self.__old_allowed_collision_matrix
+ )
+
+ return resp.success
+
+ def clear_all_collision_objects(self) -> Optional[Future]:
+ """
+ Removes all attached and un-attached collision objects from the planning scene.
+
+ Returns a future for the ApplyPlanningScene service call.
+ """
+ # Update the planning scene
+ if not self.update_planning_scene():
+ return None
+ self.__old_planning_scene = copy.deepcopy(self.__planning_scene)
+
+ # Remove all collision objects from the planning scene
+ self.__planning_scene.world.collision_objects = []
+ self.__planning_scene.robot_state.attached_collision_objects = []
+
+ # Apply the new planning scene
+ if not self._apply_planning_scene_service.service_is_ready():
+ self._node.get_logger().warn(
+ f"Service '{self._apply_planning_scene_service.srv_name}' is not yet available. Better luck next time!"
+ )
+ return None
+ return self._apply_planning_scene_service.call_async(
+ ApplyPlanningScene.Request(scene=self.__planning_scene)
+ )
+
+ def cancel_clear_all_collision_objects_future(self, future: Future):
+ """
+ Cancel the clear all collision objects service call.
+ """
+ self._apply_planning_scene_service.remove_pending_request(future)
+
+ def process_clear_all_collision_objects_future(self, future: Future) -> bool:
+ """
+ Return whether the clear all collision objects service call is done and has succeeded
+ or not. If it failed, restore the old planning scene.
+ """
+ if not future.done():
+ return False
+
+ # Get response
+ resp = future.result()
+
+ # If it failed, restore the old planning scene
+ if not resp.success:
+ self.__planning_scene = self.__old_planning_scene
+
+ return resp.success
+
+ def __joint_state_callback(self, msg: JointState):
+ # Update only if all relevant joints are included in the message
+ for joint_name in self.joint_names:
+ if not joint_name in msg.name:
+ return
+
+ self.__joint_state_mutex.acquire()
+ self.__joint_state = msg
+ self.__new_joint_state_available = True
+ self.__joint_state_mutex.release()
+
+ def _plan_kinematic_path(self) -> Optional[Future]:
+ # Reuse request from move action goal
+ self.__kinematic_path_request.motion_plan_request = (
+ self.__move_action_goal.request
+ )
+
+ stamp = self._node.get_clock().now().to_msg()
+ self.__kinematic_path_request.motion_plan_request.workspace_parameters.header.stamp = (
+ stamp
+ )
+ for (
+ constraints
+ ) in self.__kinematic_path_request.motion_plan_request.goal_constraints:
+ for position_constraint in constraints.position_constraints:
+ position_constraint.header.stamp = stamp
+ for orientation_constraint in constraints.orientation_constraints:
+ orientation_constraint.header.stamp = stamp
+
+ if not self._plan_kinematic_path_service.service_is_ready():
+ self._node.get_logger().warn(
+ f"Service '{self._plan_kinematic_path_service.srv_name}' is not yet available. Better luck next time!"
+ )
+ return None
+
+ return self._plan_kinematic_path_service.call_async(
+ self.__kinematic_path_request
+ )
+
+ def _plan_cartesian_path(
+ self,
+ max_step: float = 0.0025,
+ frame_id: Optional[str] = None,
+ ) -> Optional[Future]:
+ # Reuse request from move action goal
+ self.__cartesian_path_request.start_state = (
+ self.__move_action_goal.request.start_state
+ )
+
+ # The below attributes were introduced in Iron and do not exist in Humble.
+ if hasattr(self.__cartesian_path_request, "max_velocity_scaling_factor"):
+ self.__cartesian_path_request.max_velocity_scaling_factor = (
+ self.__move_action_goal.request.max_velocity_scaling_factor
+ )
+ if hasattr(self.__cartesian_path_request, "max_acceleration_scaling_factor"):
+ self.__cartesian_path_request.max_acceleration_scaling_factor = (
+ self.__move_action_goal.request.max_acceleration_scaling_factor
+ )
+
+ self.__cartesian_path_request.group_name = (
+ self.__move_action_goal.request.group_name
+ )
+ self.__cartesian_path_request.link_name = self.__end_effector_name
+ self.__cartesian_path_request.max_step = max_step
+
+ self.__cartesian_path_request.header.frame_id = (
+ frame_id if frame_id is not None else self.__base_link_name
+ )
+
+ stamp = self._node.get_clock().now().to_msg()
+ self.__cartesian_path_request.header.stamp = stamp
+
+ self.__cartesian_path_request.path_constraints = (
+ self.__move_action_goal.request.path_constraints
+ )
+ for (
+ position_constraint
+ ) in self.__cartesian_path_request.path_constraints.position_constraints:
+ position_constraint.header.stamp = stamp
+ for (
+ orientation_constraint
+ ) in self.__cartesian_path_request.path_constraints.orientation_constraints:
+ orientation_constraint.header.stamp = stamp
+ # no header in joint_constraint message type
+
+ target_pose = Pose()
+ target_pose.position = (
+ self.__move_action_goal.request.goal_constraints[-1]
+ .position_constraints[-1]
+ .constraint_region.primitive_poses[0]
+ .position
+ )
+ target_pose.orientation = (
+ self.__move_action_goal.request.goal_constraints[-1]
+ .orientation_constraints[-1]
+ .orientation
+ )
+
+ self.__cartesian_path_request.waypoints = [target_pose]
+
+ if not self._plan_cartesian_path_service.service_is_ready():
+ self._node.get_logger().warn(
+ f"Service '{self._plan_cartesian_path_service.srv_name}' is not yet available. Better luck next time!"
+ )
+ return None
+
+ return self._plan_cartesian_path_service.call_async(
+ self.__cartesian_path_request
+ )
+
+ def _send_goal_async_move_action(self):
+ self.__execution_mutex.acquire()
+ stamp = self._node.get_clock().now().to_msg()
+ self.__move_action_goal.request.workspace_parameters.header.stamp = stamp
+ if not self.__move_action_client.server_is_ready():
+ self._node.get_logger().warn(
+ f"Action server '{self.__move_action_client._action_name}' is not yet available. Better luck next time!"
+ )
+ return
+
+ self.__last_error_code = None
+ self.__is_motion_requested = True
+ self.__send_goal_future_move_action = self.__move_action_client.send_goal_async(
+ goal=self.__move_action_goal,
+ feedback_callback=None,
+ )
+
+ self.__send_goal_future_move_action.add_done_callback(
+ self.__response_callback_move_action
+ )
+
+ self.__execution_mutex.release()
+
+ def __response_callback_move_action(self, response):
+ self.__execution_mutex.acquire()
+ goal_handle = response.result()
+ if not goal_handle.accepted:
+ self._node.get_logger().warn(
+ f"Action '{self.__move_action_client._action_name}' was rejected."
+ )
+ self.__is_motion_requested = False
+ return
+
+ self.__execution_goal_handle = goal_handle
+ self.__is_executing = True
+ self.__is_motion_requested = False
+
+ self.__get_result_future_move_action = goal_handle.get_result_async()
+ self.__get_result_future_move_action.add_done_callback(
+ self.__result_callback_move_action
+ )
+ self.__execution_mutex.release()
+
+ def __result_callback_move_action(self, res):
+ self.__execution_mutex.acquire()
+ if res.result().status != GoalStatus.STATUS_SUCCEEDED:
+ self._node.get_logger().warn(
+ f"Action '{self.__move_action_client._action_name}' was unsuccessful: {res.result().status}."
+ )
+ self.motion_suceeded = False
+ else:
+ self.motion_suceeded = True
+
+ self.__last_error_code = res.result().result.error_code
+
+ self.__execution_goal_handle = None
+ self.__is_executing = False
+ self.__execution_mutex.release()
+
+ def _send_goal_async_execute_trajectory(
+ self,
+ goal: ExecuteTrajectory,
+ wait_until_response: bool = False,
+ ):
+ self.__execution_mutex.acquire()
+
+ if not self._execute_trajectory_action_client.server_is_ready():
+ self._node.get_logger().warn(
+ f"Action server '{self._execute_trajectory_action_client._action_name}' is not yet available. Better luck next time!"
+ )
+ return
+
+ self.__last_error_code = None
+ self.__is_motion_requested = True
+ self.__send_goal_future_execute_trajectory = (
+ self._execute_trajectory_action_client.send_goal_async(
+ goal=goal,
+ feedback_callback=None,
+ )
+ )
+
+ self.__send_goal_future_execute_trajectory.add_done_callback(
+ self.__response_callback_execute_trajectory
+ )
+ self.__execution_mutex.release()
+
+ def __response_callback_execute_trajectory(self, response):
+ self.__execution_mutex.acquire()
+ goal_handle = response.result()
+ if not goal_handle.accepted:
+ self._node.get_logger().warn(
+ f"Action '{self._execute_trajectory_action_client._action_name}' was rejected."
+ )
+ self.__is_motion_requested = False
+ return
+
+ self.__execution_goal_handle = goal_handle
+ self.__is_executing = True
+ self.__is_motion_requested = False
+
+ self.__get_result_future_execute_trajectory = goal_handle.get_result_async()
+ self.__get_result_future_execute_trajectory.add_done_callback(
+ self.__result_callback_execute_trajectory
+ )
+ self.__execution_mutex.release()
+
+ def __response_callback_with_event_set_execute_trajectory(self, response):
+ self.__future_done_event.set()
+
+ def __result_callback_execute_trajectory(self, res):
+ self.__execution_mutex.acquire()
+ if res.result().status != GoalStatus.STATUS_SUCCEEDED:
+ self._node.get_logger().warn(
+ f"Action '{self._execute_trajectory_action_client._action_name}' was unsuccessful: {res.result().status}."
+ )
+ self.motion_suceeded = False
+ else:
+ self.motion_suceeded = True
+
+ self.__last_error_code = res.result().result.error_code
+
+ self.__execution_goal_handle = None
+ self.__is_executing = False
+ self.__execution_mutex.release()
+
+ @classmethod
+ def __init_move_action_goal(
+ cls, frame_id: str, group_name: str, end_effector: str
+ ) -> MoveGroup.Goal:
+ move_action_goal = MoveGroup.Goal()
+ move_action_goal.request.workspace_parameters.header.frame_id = frame_id
+ # move_action_goal.request.workspace_parameters.header.stamp = "Set during request"
+ move_action_goal.request.workspace_parameters.min_corner.x = -1.0
+ move_action_goal.request.workspace_parameters.min_corner.y = -1.0
+ move_action_goal.request.workspace_parameters.min_corner.z = -1.0
+ move_action_goal.request.workspace_parameters.max_corner.x = 1.0
+ move_action_goal.request.workspace_parameters.max_corner.y = 1.0
+ move_action_goal.request.workspace_parameters.max_corner.z = 1.0
+ # move_action_goal.request.start_state = "Set during request"
+ move_action_goal.request.goal_constraints = [Constraints()]
+ move_action_goal.request.path_constraints = Constraints()
+ # move_action_goal.request.trajectory_constraints = "Ignored"
+ # move_action_goal.request.reference_trajectories = "Ignored"
+ move_action_goal.request.pipeline_id = ""
+ move_action_goal.request.planner_id = ""
+ move_action_goal.request.group_name = group_name
+ move_action_goal.request.num_planning_attempts = 3
+ move_action_goal.request.allowed_planning_time = 0.5
+ move_action_goal.request.max_velocity_scaling_factor = 0.0
+ move_action_goal.request.max_acceleration_scaling_factor = 0.0
+ # Note: Attribute was renamed in Iron (https://github.com/ros-planning/moveit_msgs/pull/130)
+ if hasattr(move_action_goal.request, "cartesian_speed_limited_link"):
+ move_action_goal.request.cartesian_speed_limited_link = end_effector
+ else:
+ move_action_goal.request.cartesian_speed_end_effector_link = end_effector
+ move_action_goal.request.max_cartesian_speed = 0.0
+
+ # move_action_goal.planning_options.planning_scene_diff = "Ignored"
+ move_action_goal.planning_options.plan_only = False
+ # move_action_goal.planning_options.look_around = "Ignored"
+ # move_action_goal.planning_options.look_around_attempts = "Ignored"
+ # move_action_goal.planning_options.max_safe_execution_cost = "Ignored"
+ # move_action_goal.planning_options.replan = "Ignored"
+ # move_action_goal.planning_options.replan_attempts = "Ignored"
+ # move_action_goal.planning_options.replan_delay = "Ignored"
+
+ return move_action_goal
+
+ def __init_compute_fk(self):
+ self.__compute_fk_client = self._node.create_client(
+ srv_type=GetPositionFK,
+ srv_name="/compute_fk",
+ callback_group=self._callback_group,
+ )
+
+ self.__compute_fk_req = GetPositionFK.Request()
+ self.__compute_fk_req.header.frame_id = self.__base_link_name
+ # self.__compute_fk_req.header.stamp = "Set during request"
+ # self.__compute_fk_req.fk_link_names = "Set during request"
+ # self.__compute_fk_req.robot_state.joint_state = "Set during request"
+ # self.__compute_fk_req.robot_state.multi_dof_ = "Ignored"
+ # self.__compute_fk_req.robot_state.attached_collision_objects = "Ignored"
+ self.__compute_fk_req.robot_state.is_diff = False
+
+ def __init_compute_ik(self):
+ # Service client for IK
+ self.__compute_ik_client = self._node.create_client(
+ srv_type=GetPositionIK,
+ srv_name="/compute_ik",
+ callback_group=self._callback_group,
+ )
+
+ self.__compute_ik_req = GetPositionIK.Request()
+ self.__compute_ik_req.ik_request.group_name = self.__group_name
+ # self.__compute_ik_req.ik_request.robot_state.joint_state = "Set during request"
+ # self.__compute_ik_req.ik_request.robot_state.multi_dof_ = "Ignored"
+ # self.__compute_ik_req.ik_request.robot_state.attached_collision_objects = "Ignored"
+ self.__compute_ik_req.ik_request.robot_state.is_diff = False
+ # self.__compute_ik_req.ik_request.constraints = "Set during request OR Ignored"
+ self.__compute_ik_req.ik_request.avoid_collisions = True
+ # self.__compute_ik_req.ik_request.ik_link_name = "Ignored"
+ self.__compute_ik_req.ik_request.pose_stamped.header.frame_id = (
+ self.__base_link_name
+ )
+ # self.__compute_ik_req.ik_request.pose_stamped.header.stamp = "Set during request"
+ # self.__compute_ik_req.ik_request.pose_stamped.pose = "Set during request"
+ # self.__compute_ik_req.ik_request.ik_link_names = "Ignored"
+ # self.__compute_ik_req.ik_request.pose_stamped_vector = "Ignored"
+ # self.__compute_ik_req.ik_request.timeout.sec = "Ignored"
+ # self.__compute_ik_req.ik_request.timeout.nanosec = "Ignored"
+
+ @property
+ def planning_scene(self) -> Optional[PlanningScene]:
+ return self.__planning_scene
+
+ @property
+ def follow_joint_trajectory_action_client(self) -> str:
+ return self.__follow_joint_trajectory_action_client
+
+ @property
+ def end_effector_name(self) -> str:
+ return self.__end_effector_name
+
+ @property
+ def base_link_name(self) -> str:
+ return self.__base_link_name
+
+ @property
+ def joint_names(self) -> List[str]:
+ return self.__joint_names
+
+ @property
+ def joint_state(self) -> Optional[JointState]:
+ self.__joint_state_mutex.acquire()
+ joint_state = self.__joint_state
+ self.__joint_state_mutex.release()
+ return joint_state
+
+ @property
+ def new_joint_state_available(self):
+ return self.__new_joint_state_available
+
+ @property
+ def max_velocity(self) -> float:
+ return self.__move_action_goal.request.max_velocity_scaling_factor
+
+ @max_velocity.setter
+ def max_velocity(self, value: float):
+ self.__move_action_goal.request.max_velocity_scaling_factor = value
+
+ @property
+ def max_acceleration(self) -> float:
+ return self.__move_action_goal.request.max_acceleration_scaling_factor
+
+ @max_acceleration.setter
+ def max_acceleration(self, value: float):
+ self.__move_action_goal.request.max_acceleration_scaling_factor = value
+
+ @property
+ def num_planning_attempts(self) -> int:
+ return self.__move_action_goal.request.num_planning_attempts
+
+ @num_planning_attempts.setter
+ def num_planning_attempts(self, value: int):
+ self.__move_action_goal.request.num_planning_attempts = value
+
+ @property
+ def allowed_planning_time(self) -> float:
+ return self.__move_action_goal.request.allowed_planning_time
+
+ @allowed_planning_time.setter
+ def allowed_planning_time(self, value: float):
+ self.__move_action_goal.request.allowed_planning_time = value
+
+ @property
+ def cartesian_avoid_collisions(self) -> bool:
+ return self.__cartesian_path_request.request.avoid_collisions
+
+ @cartesian_avoid_collisions.setter
+ def cartesian_avoid_collisions(self, value: bool):
+ self.__cartesian_path_request.avoid_collisions = value
+
+ @property
+ def cartesian_jump_threshold(self) -> float:
+ return self.__cartesian_path_request.request.jump_threshold
+
+ @cartesian_jump_threshold.setter
+ def cartesian_jump_threshold(self, value: float):
+ self.__cartesian_path_request.jump_threshold = value
+
+ @property
+ def cartesian_prismatic_jump_threshold(self) -> float:
+ return self.__cartesian_path_request.request.prismatic_jump_threshold
+
+ @cartesian_prismatic_jump_threshold.setter
+ def cartesian_prismatic_jump_threshold(self, value: float):
+ self.__cartesian_path_request.prismatic_jump_threshold = value
+
+ @property
+ def cartesian_revolute_jump_threshold(self) -> float:
+ return self.__cartesian_path_request.request.revolute_jump_threshold
+
+ @cartesian_revolute_jump_threshold.setter
+ def cartesian_revolute_jump_threshold(self, value: float):
+ self.__cartesian_path_request.revolute_jump_threshold = value
+
+ @property
+ def pipeline_id(self) -> int:
+ return self.__move_action_goal.request.pipeline_id
+
+ @pipeline_id.setter
+ def pipeline_id(self, value: str):
+ self.__move_action_goal.request.pipeline_id = value
+
+ @property
+ def planner_id(self) -> int:
+ return self.__move_action_goal.request.planner_id
+
+ @planner_id.setter
+ def planner_id(self, value: str):
+ self.__move_action_goal.request.planner_id = value
+
+
+def init_joint_state(
+ joint_names: List[str],
+ joint_positions: Optional[List[str]] = None,
+ joint_velocities: Optional[List[str]] = None,
+ joint_effort: Optional[List[str]] = None,
+) -> JointState:
+ joint_state = JointState()
+
+ joint_state.name = joint_names
+ joint_state.position = (
+ joint_positions if joint_positions is not None else [0.0] * len(joint_names)
+ )
+ joint_state.velocity = (
+ joint_velocities if joint_velocities is not None else [0.0] * len(joint_names)
+ )
+ joint_state.effort = (
+ joint_effort if joint_effort is not None else [0.0] * len(joint_names)
+ )
+
+ return joint_state
+
+
+def init_execute_trajectory_goal(
+ joint_trajectory: JointTrajectory,
+) -> Optional[ExecuteTrajectory.Goal]:
+ if joint_trajectory is None:
+ return None
+
+ execute_trajectory_goal = ExecuteTrajectory.Goal()
+
+ execute_trajectory_goal.trajectory.joint_trajectory = joint_trajectory
+
+ return execute_trajectory_goal
+
+
+def init_dummy_joint_trajectory_from_state(
+ joint_state: JointState, duration_sec: int = 0, duration_nanosec: int = 0
+) -> JointTrajectory:
+ joint_trajectory = JointTrajectory()
+ joint_trajectory.joint_names = joint_state.name
+
+ point = JointTrajectoryPoint()
+ point.positions = joint_state.position
+ point.velocities = joint_state.velocity
+ point.accelerations = [0.0] * len(joint_trajectory.joint_names)
+ point.effort = joint_state.effort
+ point.time_from_start.sec = duration_sec
+ point.time_from_start.nanosec = duration_nanosec
+ joint_trajectory.points.append(point)
+
+ return joint_trajectory
diff --git a/unilabos/devices/ros_dev/moveit_interface.py b/unilabos/devices/ros_dev/moveit_interface.py
new file mode 100644
index 00000000..d0654035
--- /dev/null
+++ b/unilabos/devices/ros_dev/moveit_interface.py
@@ -0,0 +1,384 @@
+import json
+import time
+from copy import deepcopy
+from pathlib import Path
+
+from moveit_msgs.msg import JointConstraint, Constraints
+from rclpy.action import ActionClient
+from tf2_ros import Buffer, TransformListener
+from unilabos_msgs.action import SendCmd
+
+from unilabos.devices.ros_dev.moveit2 import MoveIt2
+from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
+
+
+class MoveitInterface:
+ _ros_node: BaseROS2DeviceNode
+ tf_buffer: Buffer
+ tf_listener: TransformListener
+
+ def __init__(self, moveit_type, joint_poses, rotation=None, device_config=None):
+ self.device_config = device_config
+ self.rotation = rotation
+ self.data_config = json.load(
+ open(
+ f"{Path(__file__).parent.parent.parent.absolute()}/device_mesh/devices/{moveit_type}/config/move_group.json",
+ encoding="utf-8",
+ )
+ )
+ self.arm_move_flag = False
+ self.move_option = ["pick", "place", "side_pick", "side_place"]
+ self.joint_poses = joint_poses
+ self.cartesian_flag = False
+ self.mesh_group = ["reactor", "sample", "beaker"]
+ self.moveit2 = {}
+ self.resource_action = None
+ self.resource_client = None
+ self.resource_action_ok = False
+
+
+ def post_init(self, ros_node: BaseROS2DeviceNode):
+ self._ros_node = ros_node
+ self.tf_buffer = Buffer()
+ self.tf_listener = TransformListener(self.tf_buffer, self._ros_node)
+
+ for move_group, config in self.data_config.items():
+
+ base_link_name = f"{self._ros_node.device_id}_{config['base_link_name']}"
+ end_effector_name = f"{self._ros_node.device_id}_{config['end_effector_name']}"
+ joint_names = [f"{self._ros_node.device_id}_{name}" for name in config["joint_names"]]
+
+ self.moveit2[f"{move_group}"] = MoveIt2(
+ node=self._ros_node,
+ joint_names=joint_names,
+ base_link_name=base_link_name,
+ end_effector_name=end_effector_name,
+ group_name=f"{self._ros_node.device_id}_{move_group}",
+ callback_group=self._ros_node.callback_group,
+ use_move_group_action=True,
+ ignore_new_calls_while_executing=True,
+ )
+ self.moveit2[f"{move_group}"].allowed_planning_time = 3.0
+
+ self._ros_node.create_timer(1, self.wait_for_resource_action, callback_group=self._ros_node.callback_group)
+
+
+ def wait_for_resource_action(self):
+ if not self.resource_action_ok:
+
+ while self.resource_action is None:
+ self.resource_action = self.check_tf_update_actions()
+ time.sleep(1)
+ self.resource_client = ActionClient(self._ros_node, SendCmd, self.resource_action)
+ self.resource_action_ok = True
+ while not self.resource_client.wait_for_server(timeout_sec=5.0):
+ self._ros_node.lab_logger().info("等待 TfUpdate 服务器...")
+
+ def check_tf_update_actions(self):
+ topics = self._ros_node.get_topic_names_and_types()
+ for topic_item in topics:
+ topic_name, topic_types = topic_item
+ if "action_msgs/msg/GoalStatusArray" in topic_types:
+ # 删除 /_action/status 部分
+
+ base_name = topic_name.replace("/_action/status", "")
+ # 检查最后一个部分是否为 tf_update
+ parts = base_name.split("/")
+ if parts and parts[-1] == "tf_update":
+ return base_name
+
+ return None
+
+ def set_position(self, command):
+ """使用moveit 移动到指定点
+ Args:
+ command: A JSON-formatted string that includes quaternion, speed, position
+
+ position (list) : 点的位置 [x,y,z]
+ quaternion (list) : 点的姿态(四元数) [x,y,z,w]
+ move_group (string) : The move group moveit will plan
+ speed (float) : The speed of the movement, speed > 0
+ retry (int) : Retry times when moveit plan fails
+
+ Returns:
+ None
+ """
+
+ result = SendCmd.Result()
+ cmd_str = command.replace("'", '"')
+ cmd_dict = json.loads(cmd_str)
+ self.moveit_task(**cmd_dict)
+ return result
+
+ def moveit_task(
+ self, move_group, position, quaternion, speed=1, retry=10, cartesian=False, target_link=None, offsets=[0, 0, 0]
+ ):
+
+ speed_ = float(max(0.1, min(speed, 1)))
+
+ self.moveit2[move_group].max_velocity = speed_
+ self.moveit2[move_group].max_acceleration = speed_
+
+ re_ = False
+
+ pose_result = [x + y for x, y in zip(position, offsets)]
+ # print(pose_result)
+
+ while retry > -1 and not re_:
+
+ self.moveit2[move_group].move_to_pose(
+ target_link=target_link,
+ position=pose_result,
+ quat_xyzw=quaternion,
+ cartesian=cartesian,
+ # cartesian_fraction_threshold=0.0,
+ cartesian_max_step=0.01,
+ weight_position=1.0,
+ )
+ re_ = self.moveit2[move_group].wait_until_executed()
+ retry += -1
+
+ return re_
+
+ def moveit_joint_task(self, move_group, joint_positions, joint_names=None, speed=1, retry=10):
+
+ re_ = False
+
+ joint_positions_ = [float(x) for x in joint_positions]
+
+ speed_ = float(max(0.1, min(speed, 1)))
+
+ self.moveit2[move_group].max_velocity = speed_
+ self.moveit2[move_group].max_acceleration = speed_
+
+ while retry > -1 and not re_:
+
+ self.moveit2[move_group].move_to_configuration(joint_positions=joint_positions_, joint_names=joint_names)
+ re_ = self.moveit2[move_group].wait_until_executed()
+
+ retry += -1
+ print(self.moveit2[move_group].compute_fk(joint_positions))
+ return re_
+
+ def resource_manager(self, resource, parent_link):
+ goal_msg = SendCmd.Goal()
+ str_dict = {}
+ str_dict[resource] = parent_link
+
+ goal_msg.command = json.dumps(str_dict)
+ assert self.resource_client is not None
+ self.resource_client.send_goal(goal_msg)
+
+ return True
+
+ def pick_and_place(self, command: str):
+ """
+ Using MoveIt to make the robotic arm pick or place materials to a target point.
+
+ Args:
+ command: A JSON-formatted string that includes option, target, speed, lift_height, mt_height
+
+ *option (string) : Action type: pick/place/side_pick/side_place
+ *move_group (string): The move group moveit will plan
+ *status(string) : Target pose
+ resource(string) : The target resource
+ x_distance (float) : The distance to the target in x direction(meters)
+ y_distance (float) : The distance to the target in y direction(meters)
+ lift_height (float) : The height at which the material should be lifted(meters)
+ retry (float) : Retry times when moveit plan fails
+ speed (float) : The speed of the movement, speed > 0
+ Returns:
+ None
+ """
+ result = SendCmd.Result()
+
+ try:
+ cmd_str = str(command).replace("'", '"')
+ cmd_dict = json.loads(cmd_str)
+
+ if cmd_dict["option"] in self.move_option:
+ option_index = self.move_option.index(cmd_dict["option"])
+ place_flag = option_index % 2
+
+ config = {}
+ function_list = []
+
+ status = cmd_dict["status"]
+ joint_positions_ = self.joint_poses[cmd_dict["move_group"]][status]
+
+ config.update({k: cmd_dict[k] for k in ["speed", "retry", "move_group"] if k in cmd_dict})
+
+ # 夹取
+ if not place_flag:
+ if "target" in cmd_dict.keys():
+ function_list.append(lambda: self.resource_manager(cmd_dict["resource"], cmd_dict["target"]))
+ else:
+ function_list.append(
+ lambda: self.resource_manager(
+ cmd_dict["resource"], self.moveit2[cmd_dict["move_group"]].end_effector_name
+ )
+ )
+ else:
+ function_list.append(lambda: self.resource_manager(cmd_dict["resource"], "world"))
+
+ constraints = []
+ if "constraints" in cmd_dict.keys():
+
+ for i in range(len(cmd_dict["constraints"])):
+ v = float(cmd_dict["constraints"][i])
+ if v > 0:
+ constraints.append(
+ JointConstraint(
+ joint_name=self.moveit2[cmd_dict["move_group"]].joint_names[i],
+ position=joint_positions_[i],
+ tolerance_above=v,
+ tolerance_below=v,
+ weight=1.0,
+ )
+ )
+
+ if "lift_height" in cmd_dict.keys():
+ retval = None
+ retry = config.get("retry", 10)
+ while retval is None and retry > 0:
+ retval = self.moveit2[cmd_dict["move_group"]].compute_fk(joint_positions_)
+ time.sleep(0.1)
+ retry -= 1
+ if retval is None:
+ result.success = False
+ return result
+ pose = [retval.pose.position.x, retval.pose.position.y, retval.pose.position.z]
+ quaternion = [
+ retval.pose.orientation.x,
+ retval.pose.orientation.y,
+ retval.pose.orientation.z,
+ retval.pose.orientation.w,
+ ]
+
+ function_list = [
+ lambda: self.moveit_task(
+ position=[retval.pose.position.x, retval.pose.position.y, retval.pose.position.z],
+ quaternion=quaternion,
+ **config,
+ cartesian=self.cartesian_flag,
+ )
+ ] + function_list
+
+ pose[2] += float(cmd_dict["lift_height"])
+ function_list.append(
+ lambda: self.moveit_task(
+ position=pose, quaternion=quaternion, **config, cartesian=self.cartesian_flag
+ )
+ )
+ end_pose = pose
+
+ if "x_distance" in cmd_dict.keys() or "y_distance" in cmd_dict.keys():
+ if "x_distance" in cmd_dict.keys():
+ deep_pose = deepcopy(pose)
+ deep_pose[0] += float(cmd_dict["x_distance"])
+ elif "y_distance" in cmd_dict.keys():
+ deep_pose = deepcopy(pose)
+ deep_pose[1] += float(cmd_dict["y_distance"])
+
+ function_list = [
+ lambda: self.moveit_task(
+ position=pose, quaternion=quaternion, **config, cartesian=self.cartesian_flag
+ )
+ ] + function_list
+ function_list.append(
+ lambda: self.moveit_task(
+ position=deep_pose, quaternion=quaternion, **config, cartesian=self.cartesian_flag
+ )
+ )
+ end_pose = deep_pose
+
+ retval_ik = None
+ retry = config.get("retry", 10)
+ while retval_ik is None and retry > 0:
+ retval_ik = self.moveit2[cmd_dict["move_group"]].compute_ik(
+ position=end_pose, quat_xyzw=quaternion, constraints=Constraints(joint_constraints=constraints)
+ )
+ time.sleep(0.1)
+ retry -= 1
+ if retval_ik is None:
+ result.success = False
+ return result
+ position_ = [
+ retval_ik.position[retval_ik.name.index(i)]
+ for i in self.moveit2[cmd_dict["move_group"]].joint_names
+ ]
+ function_list = [
+ lambda: self.moveit_joint_task(
+ joint_positions=position_,
+ joint_names=self.moveit2[cmd_dict["move_group"]].joint_names,
+ **config,
+ )
+ ] + function_list
+ else:
+ function_list = [
+ lambda: self.moveit_joint_task(**config, joint_positions=joint_positions_)
+ ] + function_list
+
+ for i in range(len(function_list)):
+ if i == 0:
+ self.cartesian_flag = False
+ else:
+ self.cartesian_flag = True
+
+ re = function_list[i]()
+ if not re:
+ print(i, re)
+ result.success = False
+ return result
+ result.success = True
+
+ except Exception as e:
+ print(e)
+ self.cartesian_flag = False
+ result.success = False
+
+ return result
+
+ def set_status(self, command: str):
+ """
+ Goto home position
+
+ Args:
+ command: A JSON-formatted string that includes speed
+ *status (string) : The joint status moveit will plan
+ *move_group (string): The move group moveit will plan
+ separate (list) : The joint index to be separated
+ lift_height (float) : The height at which the material should be lifted(meters)
+ x_distance (float) : The distance to the target in x direction(meters)
+ y_distance (float) : The distance to the target in y direction(meters)
+ speed (float) : The speed of the movement, speed > 0
+ retry (float) : Retry times when moveit plan fails
+
+ Returns:
+ None
+ """
+
+ result = SendCmd.Result()
+
+ try:
+ cmd_str = command.replace("'", '"')
+ cmd_dict = json.loads(cmd_str)
+ config = {}
+ config["move_group"] = cmd_dict["move_group"]
+ if "speed" in cmd_dict.keys():
+ config["speed"] = cmd_dict["speed"]
+ if "retry" in cmd_dict.keys():
+ config["retry"] = cmd_dict["retry"]
+
+ status = cmd_dict["status"]
+ joint_positions_ = self.joint_poses[cmd_dict["move_group"]][status]
+ re = self.moveit_joint_task(**config, joint_positions=joint_positions_)
+ if not re:
+ result.success = False
+ return result
+ result.success = True
+ except Exception as e:
+ print(e)
+ result.success = False
+
+ return result
diff --git a/unilabos/devices/virtual/__init__.py b/unilabos/devices/virtual/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/unilabos/devices/virtual/virtual_centrifuge.py b/unilabos/devices/virtual/virtual_centrifuge.py
new file mode 100644
index 00000000..20366ab1
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_centrifuge.py
@@ -0,0 +1,158 @@
+import asyncio
+import logging
+from typing import Dict, Any
+
+class VirtualCentrifuge:
+ """Virtual centrifuge device for CentrifugeProtocol testing"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_centrifuge"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
+ self.data = {}
+
+ # 添加调试信息
+ print(f"=== VirtualCentrifuge {self.device_id} is being created! ===")
+ print(f"=== Config: {self.config} ===")
+ print(f"=== Kwargs: {kwargs} ===")
+
+ # 从config或kwargs中获取配置参数
+ self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
+ self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 15000.0)
+ self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 40.0)
+ self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', 4.0)
+
+ # 处理其他kwargs参数,但跳过已知的配置参数
+ skip_keys = {'port', 'max_speed', 'max_temp', 'min_temp'}
+ for key, value in kwargs.items():
+ if key not in skip_keys and not hasattr(self, key):
+ setattr(self, key, value)
+
+ async def initialize(self) -> bool:
+ """Initialize virtual centrifuge"""
+ print(f"=== VirtualCentrifuge {self.device_id} initialize() called! ===")
+ self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
+ self.data.update({
+ "status": "Idle",
+ "current_speed": 0.0,
+ "target_speed": 0.0,
+ "current_temp": 25.0,
+ "target_temp": 25.0,
+ "max_speed": self._max_speed,
+ "max_temp": self._max_temp,
+ "min_temp": self._min_temp,
+ "centrifuge_state": "Stopped",
+ "time_remaining": 0.0,
+ "progress": 0.0,
+ "message": ""
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual centrifuge"""
+ self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}")
+ return True
+
+ async def centrifuge(self, vessel: str, speed: float, time: float, temp: float = 25.0) -> bool:
+ """Execute centrifuge action - matches Centrifuge action"""
+ self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} RPM, time={time}s, temp={temp}°C")
+
+ # 验证参数
+ if speed > self._max_speed:
+ self.logger.error(f"Speed {speed} exceeds maximum {self._max_speed}")
+ self.data["message"] = f"速度 {speed} 超过最大值 {self._max_speed}"
+ return False
+
+ if temp > self._max_temp or temp < self._min_temp:
+ self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
+ self.data["message"] = f"温度 {temp} 超出范围 {self._min_temp}-{self._max_temp}"
+ return False
+
+ # 开始离心
+ self.data.update({
+ "status": "Running",
+ "centrifuge_state": "Centrifuging",
+ "target_speed": speed,
+ "current_speed": speed,
+ "target_temp": temp,
+ "current_temp": temp,
+ "time_remaining": time,
+ "vessel": vessel,
+ "progress": 0.0,
+ "message": f"离心中: {vessel} at {speed} RPM"
+ })
+
+ # 模拟离心过程
+ simulation_time = min(time, 5.0) # 最多等待5秒用于测试
+ await asyncio.sleep(simulation_time)
+
+ # 离心完成
+ self.data.update({
+ "status": "Idle",
+ "centrifuge_state": "Stopped",
+ "current_speed": 0.0,
+ "target_speed": 0.0,
+ "time_remaining": 0.0,
+ "progress": 100.0,
+ "message": f"离心完成: {vessel}"
+ })
+
+ self.logger.info(f"Centrifuge completed for vessel {vessel}")
+ return True
+
+ # 状态属性
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Unknown")
+
+ @property
+ def current_speed(self) -> float:
+ return self.data.get("current_speed", 0.0)
+
+ @property
+ def target_speed(self) -> float:
+ return self.data.get("target_speed", 0.0)
+
+ @property
+ def current_temp(self) -> float:
+ return self.data.get("current_temp", 25.0)
+
+ @property
+ def target_temp(self) -> float:
+ return self.data.get("target_temp", 25.0)
+
+ @property
+ def max_speed(self) -> float:
+ return self.data.get("max_speed", self._max_speed)
+
+ @property
+ def max_temp(self) -> float:
+ return self.data.get("max_temp", self._max_temp)
+
+ @property
+ def min_temp(self) -> float:
+ return self.data.get("min_temp", self._min_temp)
+
+ @property
+ def centrifuge_state(self) -> str:
+ return self.data.get("centrifuge_state", "Unknown")
+
+ @property
+ def time_remaining(self) -> float:
+ return self.data.get("time_remaining", 0.0)
+
+ @property
+ def progress(self) -> float:
+ return self.data.get("progress", 0.0)
+
+ @property
+ def message(self) -> str:
+ return self.data.get("message", "")
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_column.py b/unilabos/devices/virtual/virtual_column.py
new file mode 100644
index 00000000..c83da1c2
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_column.py
@@ -0,0 +1,132 @@
+import asyncio
+import logging
+from typing import Dict, Any, Optional
+
+class VirtualColumn:
+ """Virtual column device for RunColumn protocol"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_column"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualColumn.{self.device_id}")
+ self.data = {}
+
+ # 从config或kwargs中获取配置参数
+ self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
+ self._max_flow_rate = self.config.get('max_flow_rate') or kwargs.get('max_flow_rate', 10.0)
+ self._column_length = self.config.get('column_length') or kwargs.get('column_length', 25.0)
+ self._column_diameter = self.config.get('column_diameter') or kwargs.get('column_diameter', 2.0)
+
+ print(f"=== VirtualColumn {self.device_id} created with max_flow_rate={self._max_flow_rate}, length={self._column_length}cm ===")
+
+ async def initialize(self) -> bool:
+ """Initialize virtual column"""
+ self.logger.info(f"Initializing virtual column {self.device_id}")
+ self.data.update({
+ "status": "Idle",
+ "column_state": "Ready",
+ "current_flow_rate": 0.0,
+ "max_flow_rate": self._max_flow_rate,
+ "column_length": self._column_length,
+ "column_diameter": self._column_diameter,
+ "processed_volume": 0.0,
+ "progress": 0.0,
+ "current_status": "Ready"
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual column"""
+ self.logger.info(f"Cleaning up virtual column {self.device_id}")
+ return True
+
+ async def run_column(self, from_vessel: str, to_vessel: str, column: str) -> bool:
+ """Execute column chromatography run - matches RunColumn action"""
+ self.logger.info(f"Running column separation: {from_vessel} -> {to_vessel} using {column}")
+
+ # 更新设备状态
+ self.data.update({
+ "status": "Running",
+ "column_state": "Separating",
+ "current_status": "Column separation in progress",
+ "progress": 0.0,
+ "processed_volume": 0.0
+ })
+
+ # 模拟柱层析分离过程
+ # 假设处理时间基于流速和柱子长度
+ separation_time = (self._column_length * 2) / self._max_flow_rate # 简化计算
+
+ steps = 20 # 分20个步骤模拟分离过程
+ step_time = separation_time / steps
+
+ for i in range(steps):
+ await asyncio.sleep(step_time)
+
+ progress = (i + 1) / steps * 100
+ volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
+
+ # 更新状态
+ self.data.update({
+ "progress": progress,
+ "processed_volume": volume_processed,
+ "current_status": f"Column separation: {progress:.1f}% - Processing {volume_processed:.1f}mL"
+ })
+
+ self.logger.info(f"Column separation progress: {progress:.1f}%")
+
+ # 分离完成
+ self.data.update({
+ "status": "Idle",
+ "column_state": "Ready",
+ "current_status": "Column separation completed",
+ "progress": 100.0
+ })
+
+ self.logger.info(f"Column separation completed: {from_vessel} -> {to_vessel}")
+ return True
+
+ # 状态属性
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Unknown")
+
+ @property
+ def column_state(self) -> str:
+ return self.data.get("column_state", "Unknown")
+
+ @property
+ def current_flow_rate(self) -> float:
+ return self.data.get("current_flow_rate", 0.0)
+
+ @property
+ def max_flow_rate(self) -> float:
+ return self.data.get("max_flow_rate", 0.0)
+
+ @property
+ def column_length(self) -> float:
+ return self.data.get("column_length", 0.0)
+
+ @property
+ def column_diameter(self) -> float:
+ return self.data.get("column_diameter", 0.0)
+
+ @property
+ def processed_volume(self) -> float:
+ return self.data.get("processed_volume", 0.0)
+
+ @property
+ def progress(self) -> float:
+ return self.data.get("progress", 0.0)
+
+ @property
+ def current_status(self) -> str:
+ return self.data.get("current_status", "Ready")
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_filter.py b/unilabos/devices/virtual/virtual_filter.py
new file mode 100644
index 00000000..71a984aa
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_filter.py
@@ -0,0 +1,151 @@
+import asyncio
+import logging
+from typing import Dict, Any
+
+class VirtualFilter:
+ """Virtual filter device for FilterProtocol testing"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_filter"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
+ self.data = {}
+
+ # 添加调试信息
+ print(f"=== VirtualFilter {self.device_id} is being created! ===")
+ print(f"=== Config: {self.config} ===")
+ print(f"=== Kwargs: {kwargs} ===")
+
+ # 从config或kwargs中获取配置参数
+ self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
+ self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
+ self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
+
+ # 处理其他kwargs参数,但跳过已知的配置参数
+ skip_keys = {'port', 'max_temp', 'max_stir_speed'}
+ for key, value in kwargs.items():
+ if key not in skip_keys and not hasattr(self, key):
+ setattr(self, key, value)
+
+ async def initialize(self) -> bool:
+ """Initialize virtual filter"""
+ print(f"=== VirtualFilter {self.device_id} initialize() called! ===")
+ self.logger.info(f"Initializing virtual filter {self.device_id}")
+ self.data.update({
+ "status": "Idle",
+ "filter_state": "Ready",
+ "current_temp": 25.0,
+ "target_temp": 25.0,
+ "max_temp": self._max_temp,
+ "stir_speed": 0.0,
+ "max_stir_speed": self._max_stir_speed,
+ "filtered_volume": 0.0,
+ "progress": 0.0,
+ "message": ""
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual filter"""
+ self.logger.info(f"Cleaning up virtual filter {self.device_id}")
+ return True
+
+ async def filter_sample(self, vessel: str, filtrate_vessel: str = "", stir: bool = False,
+ stir_speed: float = 300.0, temp: float = 25.0,
+ continue_heatchill: bool = False, volume: float = 0.0) -> bool:
+ """Execute filter action - matches Filter action"""
+ self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}, stir={stir}, volume={volume}")
+
+ # 验证参数
+ if temp > self._max_temp:
+ self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}")
+ self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}"
+ return False
+
+ if stir and stir_speed > self._max_stir_speed:
+ self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
+ self.data["message"] = f"搅拌速度 {stir_speed} 超过最大值 {self._max_stir_speed}"
+ return False
+
+ # 开始过滤
+ self.data.update({
+ "status": "Running",
+ "filter_state": "Filtering",
+ "target_temp": temp,
+ "current_temp": temp,
+ "stir_speed": stir_speed if stir else 0.0,
+ "vessel": vessel,
+ "filtrate_vessel": filtrate_vessel,
+ "target_volume": volume,
+ "progress": 0.0,
+ "message": f"过滤中: {vessel}"
+ })
+
+ # 模拟过滤过程
+ simulation_time = min(volume / 10.0 if volume > 0 else 5.0, 10.0)
+ await asyncio.sleep(simulation_time)
+
+ # 过滤完成
+ filtered_vol = volume if volume > 0 else 50.0 # 默认过滤量
+ self.data.update({
+ "status": "Idle",
+ "filter_state": "Ready",
+ "current_temp": 25.0 if not continue_heatchill else temp,
+ "target_temp": 25.0 if not continue_heatchill else temp,
+ "stir_speed": 0.0 if not stir else stir_speed,
+ "filtered_volume": filtered_vol,
+ "progress": 100.0,
+ "message": f"过滤完成: {filtered_vol}mL"
+ })
+
+ self.logger.info(f"Filter completed: {filtered_vol}mL from {vessel}")
+ return True
+
+ # 状态属性
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Unknown")
+
+ @property
+ def filter_state(self) -> str:
+ return self.data.get("filter_state", "Unknown")
+
+ @property
+ def current_temp(self) -> float:
+ return self.data.get("current_temp", 25.0)
+
+ @property
+ def target_temp(self) -> float:
+ return self.data.get("target_temp", 25.0)
+
+ @property
+ def max_temp(self) -> float:
+ return self.data.get("max_temp", self._max_temp)
+
+ @property
+ def stir_speed(self) -> float:
+ return self.data.get("stir_speed", 0.0)
+
+ @property
+ def max_stir_speed(self) -> float:
+ return self.data.get("max_stir_speed", self._max_stir_speed)
+
+ @property
+ def filtered_volume(self) -> float:
+ return self.data.get("filtered_volume", 0.0)
+
+ @property
+ def progress(self) -> float:
+ return self.data.get("progress", 0.0)
+
+ @property
+ def message(self) -> str:
+ return self.data.get("message", "")
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_heatchill.py b/unilabos/devices/virtual/virtual_heatchill.py
new file mode 100644
index 00000000..98a03ce0
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_heatchill.py
@@ -0,0 +1,107 @@
+import asyncio
+import logging
+from typing import Dict, Any
+
+class VirtualHeatChill:
+ """Virtual heat chill device for HeatChillProtocol testing"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_heatchill"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
+ self.data = {}
+
+ # 添加调试信息
+ print(f"=== VirtualHeatChill {self.device_id} is being created! ===")
+ print(f"=== Config: {self.config} ===")
+ print(f"=== Kwargs: {kwargs} ===")
+
+ # 从config或kwargs中获取配置参数
+ self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
+ self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 200.0)
+ self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', -80.0)
+ self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
+
+ # 处理其他kwargs参数,但跳过已知的配置参数
+ skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
+ for key, value in kwargs.items():
+ if key not in skip_keys and not hasattr(self, key):
+ setattr(self, key, value)
+
+ async def initialize(self) -> bool:
+ """Initialize virtual heat chill"""
+ print(f"=== VirtualHeatChill {self.device_id} initialize() called! ===")
+ self.logger.info(f"Initializing virtual heat chill {self.device_id}")
+ self.data.update({
+ "status": "Idle"
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual heat chill"""
+ self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
+ return True
+
+ async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
+ stir_speed: float, purpose: str) -> bool:
+ """Execute heat chill action - matches HeatChill action exactly"""
+ self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}, purpose={purpose}")
+
+ # 验证参数
+ if temp > self._max_temp or temp < self._min_temp:
+ self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
+ self.data["status"] = f"温度 {temp} 超出范围"
+ return False
+
+ if stir and stir_speed > self._max_stir_speed:
+ self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
+ self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
+ return False
+
+ # 开始加热/冷却
+ self.data.update({
+ "status": f"加热/冷却中: {vessel} 至 {temp}°C"
+ })
+
+ # 模拟加热/冷却时间
+ simulation_time = min(time, 10.0) # 最多等待10秒用于测试
+ await asyncio.sleep(simulation_time)
+
+ # 加热/冷却完成
+ self.data["status"] = f"完成: {vessel} 已达到 {temp}°C"
+
+ self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C")
+ return True
+
+ async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
+ """Start heat chill - matches HeatChillStart action exactly"""
+ self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C, purpose={purpose}")
+
+ # 验证参数
+ if temp > self._max_temp or temp < self._min_temp:
+ self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
+ self.data["status"] = f"温度 {temp} 超出范围"
+ return False
+
+ self.data["status"] = f"开始加热/冷却: {vessel} 至 {temp}°C"
+ return True
+
+ async def heat_chill_stop(self, vessel: str) -> bool:
+ """Stop heat chill - matches HeatChillStop action exactly"""
+ self.logger.info(f"HeatChillStop: vessel={vessel}")
+
+ self.data["status"] = f"停止加热/冷却: {vessel}"
+ return True
+
+ # 状态属性 - 只保留 action 中定义的 feedback
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Idle")
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_pump.py b/unilabos/devices/virtual/virtual_pump.py
new file mode 100644
index 00000000..d134319a
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_pump.py
@@ -0,0 +1,197 @@
+import asyncio
+import logging
+from typing import Dict, Any, Optional
+
+class VirtualPump:
+ """Virtual pump device for transfer and cleaning operations"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_pump"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualPump.{self.device_id}")
+ self.data = {}
+
+ # 从config或kwargs中获取配置参数
+ self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
+ self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 50.0)
+ self._transfer_rate = self.config.get('transfer_rate') or kwargs.get('transfer_rate', 10.0)
+
+ print(f"=== VirtualPump {self.device_id} created with max_volume={self._max_volume}, transfer_rate={self._transfer_rate} ===")
+
+ async def initialize(self) -> bool:
+ """Initialize virtual pump"""
+ self.logger.info(f"Initializing virtual pump {self.device_id}")
+ self.data.update({
+ "status": "Idle",
+ "valve_position": 0,
+ "current_volume": 0.0,
+ "max_volume": self._max_volume,
+ "transfer_rate": self._transfer_rate,
+ "from_vessel": "",
+ "to_vessel": "",
+ "progress": 0.0,
+ "transferred_volume": 0.0,
+ "current_status": "Ready"
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual pump"""
+ self.logger.info(f"Cleaning up virtual pump {self.device_id}")
+ return True
+
+ async def transfer(self, from_vessel: str, to_vessel: str, volume: float,
+ amount: str = "", time: float = 0.0, viscous: bool = False,
+ rinsing_solvent: str = "", rinsing_volume: float = 0.0,
+ rinsing_repeats: int = 0, solid: bool = False) -> bool:
+ """Execute transfer operation"""
+ self.logger.info(f"Transferring {volume}mL from {from_vessel} to {to_vessel}")
+
+ # 计算转移时间
+ transfer_time = volume / self._transfer_rate if time == 0 else time
+
+ self.data.update({
+ "status": "Running",
+ "from_vessel": from_vessel,
+ "to_vessel": to_vessel,
+ "current_status": "Transferring",
+ "progress": 0.0,
+ "transferred_volume": 0.0
+ })
+
+ # 模拟转移过程
+ steps = 10
+ step_time = transfer_time / steps
+ step_volume = volume / steps
+
+ for i in range(steps):
+ await asyncio.sleep(step_time)
+ progress = (i + 1) / steps * 100
+ current_volume = step_volume * (i + 1)
+
+ self.data.update({
+ "progress": progress,
+ "transferred_volume": current_volume,
+ "current_status": f"Transferring: {progress:.1f}%"
+ })
+
+ self.logger.info(f"Transfer progress: {progress:.1f}%")
+
+ self.data.update({
+ "status": "Idle",
+ "current_status": "Transfer completed",
+ "progress": 100.0,
+ "transferred_volume": volume
+ })
+
+ return True
+
+ async def clean_vessel(self, vessel: str, solvent: str, volume: float,
+ temp: float, repeats: int = 1) -> bool:
+ """Execute vessel cleaning operation - matches CleanVessel action"""
+ self.logger.info(f"Starting vessel cleaning: {vessel} with {solvent} ({volume}mL at {temp}°C, {repeats} repeats)")
+
+ # 更新设备状态
+ self.data.update({
+ "status": "Running",
+ "from_vessel": f"flask_{solvent}",
+ "to_vessel": vessel,
+ "current_status": "Cleaning in progress",
+ "progress": 0.0,
+ "transferred_volume": 0.0
+ })
+
+ # 计算清洗时间(基于体积和重复次数)
+ # 假设清洗速度为 transfer_rate 的一半(因为需要加载和排放)
+ cleaning_rate = self._transfer_rate / 2
+ cleaning_time_per_cycle = volume / cleaning_rate
+ total_cleaning_time = cleaning_time_per_cycle * repeats
+
+ # 模拟清洗过程
+ steps_per_repeat = 10 # 每次重复清洗分10个步骤
+ total_steps = steps_per_repeat * repeats
+ step_time = total_cleaning_time / total_steps
+
+ for repeat in range(repeats):
+ self.logger.info(f"Starting cleaning cycle {repeat + 1}/{repeats}")
+
+ for step in range(steps_per_repeat):
+ await asyncio.sleep(step_time)
+
+ # 计算当前进度
+ current_step = repeat * steps_per_repeat + step + 1
+ progress = (current_step / total_steps) * 100
+
+ # 计算已处理的体积
+ volume_processed = (current_step / total_steps) * volume * repeats
+
+ # 更新状态
+ self.data.update({
+ "progress": progress,
+ "transferred_volume": volume_processed,
+ "current_status": f"Cleaning cycle {repeat + 1}/{repeats} - Step {step + 1}/{steps_per_repeat} ({progress:.1f}%)"
+ })
+
+ self.logger.info(f"Cleaning progress: {progress:.1f}% (Cycle {repeat + 1}/{repeats})")
+
+ # 清洗完成
+ self.data.update({
+ "status": "Idle",
+ "current_status": "Cleaning completed successfully",
+ "progress": 100.0,
+ "transferred_volume": volume * repeats,
+ "from_vessel": "",
+ "to_vessel": ""
+ })
+
+ self.logger.info(f"Vessel cleaning completed: {vessel}")
+ return True
+
+ # 状态属性
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Unknown")
+
+ @property
+ def valve_position(self) -> int:
+ return self.data.get("valve_position", 0)
+
+ @property
+ def current_volume(self) -> float:
+ return self.data.get("current_volume", 0.0)
+
+ @property
+ def max_volume(self) -> float:
+ return self.data.get("max_volume", 0.0)
+
+ @property
+ def transfer_rate(self) -> float:
+ return self.data.get("transfer_rate", 0.0)
+
+ @property
+ def from_vessel(self) -> str:
+ return self.data.get("from_vessel", "")
+
+ @property
+ def to_vessel(self) -> str:
+ return self.data.get("to_vessel", "")
+
+ @property
+ def progress(self) -> float:
+ return self.data.get("progress", 0.0)
+
+ @property
+ def transferred_volume(self) -> float:
+ return self.data.get("transferred_volume", 0.0)
+
+ @property
+ def current_status(self) -> str:
+ return self.data.get("current_status", "Ready")
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_stirrer.py b/unilabos/devices/virtual/virtual_stirrer.py
new file mode 100644
index 00000000..b1a4098b
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_stirrer.py
@@ -0,0 +1,104 @@
+import asyncio
+import logging
+from typing import Dict, Any
+
+class VirtualStirrer:
+ """Virtual stirrer device for StirProtocol testing"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_stirrer"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
+ self.data = {}
+
+ # 添加调试信息
+ print(f"=== VirtualStirrer {self.device_id} is being created! ===")
+ print(f"=== Config: {self.config} ===")
+ print(f"=== Kwargs: {kwargs} ===")
+
+ # 从config或kwargs中获取配置参数
+ self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
+ self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
+ self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1000.0)
+
+ # 处理其他kwargs参数,但跳过已知的配置参数
+ skip_keys = {'port', 'max_temp', 'max_speed'}
+ for key, value in kwargs.items():
+ if key not in skip_keys and not hasattr(self, key):
+ setattr(self, key, value)
+
+ async def initialize(self) -> bool:
+ """Initialize virtual stirrer"""
+ print(f"=== VirtualStirrer {self.device_id} initialize() called! ===")
+ self.logger.info(f"Initializing virtual stirrer {self.device_id}")
+ self.data.update({
+ "status": "Idle"
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual stirrer"""
+ self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
+ return True
+
+ async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
+ """Execute stir action - matches Stir action exactly"""
+ self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
+
+ # 验证参数
+ if stir_speed > self._max_speed:
+ self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
+ self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
+ return False
+
+ # 开始搅拌
+ self.data["status"] = f"搅拌中: {stir_speed} RPM, {stir_time}s"
+
+ # 模拟搅拌时间
+ simulation_time = min(stir_time, 10.0) # 最多等待10秒用于测试
+ await asyncio.sleep(simulation_time)
+
+ # 搅拌完成,开始沉降
+ if settling_time > 0:
+ self.data["status"] = f"沉降中: {settling_time}s"
+ settling_simulation = min(settling_time, 5.0) # 最多等待5秒
+ await asyncio.sleep(settling_simulation)
+
+ # 操作完成
+ self.data["status"] = "搅拌完成"
+
+ self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s")
+ return True
+
+ async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
+ """Start stir action - matches StartStir action exactly"""
+ self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
+
+ # 验证参数
+ if stir_speed > self._max_speed:
+ self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
+ self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
+ return False
+
+ self.data["status"] = f"开始搅拌: {vessel} at {stir_speed} RPM"
+ return True
+
+ async def stop_stir(self, vessel: str) -> bool:
+ """Stop stir action - matches StopStir action exactly"""
+ self.logger.info(f"StopStir: vessel={vessel}")
+
+ self.data["status"] = f"停止搅拌: {vessel}"
+ return True
+
+ # 状态属性 - 只保留 action 中定义的 feedback
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Idle")
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_transferpump.py b/unilabos/devices/virtual/virtual_transferpump.py
new file mode 100644
index 00000000..87d4cffa
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_transferpump.py
@@ -0,0 +1,149 @@
+import asyncio
+import logging
+from typing import Dict, Any, Optional
+
+class VirtualTransferPump:
+ """Virtual pump device specifically for Transfer protocol"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_transfer_pump"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
+ self.data = {}
+
+ # 添加调试信息
+ print(f"=== VirtualTransferPump {self.device_id} is being created! ===")
+ print(f"=== Config: {self.config} ===")
+ print(f"=== Kwargs: {kwargs} ===")
+
+ # 从config或kwargs中获取配置参数
+ self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
+ self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 50.0)
+ self._transfer_rate = self.config.get('transfer_rate') or kwargs.get('transfer_rate', 5.0)
+ self._current_volume = 0.0
+ self.is_running = False
+
+ async def initialize(self) -> bool:
+ """Initialize virtual transfer pump"""
+ print(f"=== VirtualTransferPump {self.device_id} initialize() called! ===")
+ self.logger.info(f"Initializing virtual transfer pump {self.device_id}")
+ self.data.update({
+ "status": "Idle",
+ "current_volume": 0.0,
+ "max_volume": self._max_volume,
+ "transfer_rate": self._transfer_rate,
+ "from_vessel": "",
+ "to_vessel": "",
+ "progress": 0.0,
+ "transferred_volume": 0.0,
+ "current_status": "Ready"
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual transfer pump"""
+ self.logger.info(f"Cleaning up virtual transfer pump {self.device_id}")
+ return True
+
+ async def transfer(self, from_vessel: str, to_vessel: str, volume: float,
+ amount: str = "", time: float = 0, viscous: bool = False,
+ rinsing_solvent: str = "", rinsing_volume: float = 0.0,
+ rinsing_repeats: int = 0, solid: bool = False) -> bool:
+ """Execute liquid transfer - matches Transfer action"""
+ self.logger.info(f"Transfer: {volume}mL from {from_vessel} to {to_vessel}")
+
+ # 计算转移时间
+ if time > 0:
+ transfer_time = time
+ else:
+ # 如果是粘性液体,降低转移速率
+ rate = self._transfer_rate * 0.5 if viscous else self._transfer_rate
+ transfer_time = volume / rate
+
+ self.data.update({
+ "status": "Running",
+ "from_vessel": from_vessel,
+ "to_vessel": to_vessel,
+ "current_status": "Transferring",
+ "progress": 0.0,
+ "transferred_volume": 0.0
+ })
+
+ # 模拟转移过程
+ steps = 10
+ step_time = transfer_time / steps
+ step_volume = volume / steps
+
+ for i in range(steps):
+ await asyncio.sleep(step_time)
+ progress = (i + 1) / steps * 100
+ transferred = (i + 1) * step_volume
+
+ self.data.update({
+ "progress": progress,
+ "transferred_volume": transferred,
+ "current_status": f"Transferring {progress:.1f}%"
+ })
+
+ self.logger.info(f"Transfer progress: {progress:.1f}% ({transferred:.1f}/{volume}mL)")
+
+ # 如果需要冲洗
+ if rinsing_solvent and rinsing_volume > 0 and rinsing_repeats > 0:
+ self.data["current_status"] = "Rinsing"
+ for repeat in range(rinsing_repeats):
+ self.logger.info(f"Rinsing cycle {repeat + 1}/{rinsing_repeats} with {rinsing_solvent}")
+ await asyncio.sleep(1) # 模拟冲洗时间
+
+ self.data.update({
+ "status": "Idle",
+ "current_status": "Transfer completed",
+ "progress": 100.0,
+ "transferred_volume": volume
+ })
+
+ return True
+
+ # 添加所有在virtual_device.yaml中定义的状态属性
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Unknown")
+
+ @property
+ def current_volume(self) -> float:
+ return self.data.get("current_volume", 0.0)
+
+ @property
+ def max_volume(self) -> float:
+ return self.data.get("max_volume", self._max_volume)
+
+ @property
+ def transfer_rate(self) -> float:
+ return self.data.get("transfer_rate", self._transfer_rate)
+
+ @property
+ def from_vessel(self) -> str:
+ return self.data.get("from_vessel", "")
+
+ @property
+ def to_vessel(self) -> str:
+ return self.data.get("to_vessel", "")
+
+ @property
+ def progress(self) -> float:
+ return self.data.get("progress", 0.0)
+
+ @property
+ def transferred_volume(self) -> float:
+ return self.data.get("transferred_volume", 0.0)
+
+ @property
+ def current_status(self) -> str:
+ return self.data.get("current_status", "Ready")
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_valve.py b/unilabos/devices/virtual/virtual_valve.py
new file mode 100644
index 00000000..a665e005
--- /dev/null
+++ b/unilabos/devices/virtual/virtual_valve.py
@@ -0,0 +1,105 @@
+import asyncio
+import logging
+from typing import Dict, Any
+
+class VirtualValve:
+ """Virtual valve device for AddProtocol testing"""
+
+ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
+ # 处理可能的不同调用方式
+ if device_id is None and 'id' in kwargs:
+ device_id = kwargs.pop('id')
+ if config is None and 'config' in kwargs:
+ config = kwargs.pop('config')
+
+ # 设置默认值
+ self.device_id = device_id or "unknown_valve"
+ self.config = config or {}
+
+ self.logger = logging.getLogger(f"VirtualValve.{self.device_id}")
+ self.data = {}
+
+ print(f"=== VirtualValve {self.device_id} is being created! ===")
+ print(f"=== Config: {self.config} ===")
+ print(f"=== Kwargs: {kwargs} ===")
+
+ # 处理所有配置参数,包括port
+ self.port = self.config.get('port', 'VIRTUAL')
+ self.positions = self.config.get('positions', 6)
+ self.current_position = 0
+
+ # 忽略其他可能的kwargs参数
+ for key, value in kwargs.items():
+ if not hasattr(self, key):
+ setattr(self, key, value)
+
+ async def initialize(self) -> bool:
+ """Initialize virtual valve"""
+ print(f"=== VirtualValve {self.device_id} initialize() called! ===")
+ self.logger.info(f"Initializing virtual valve {self.device_id}")
+ self.data.update({
+ "status": "Idle",
+ "valve_state": "Closed",
+ "current_position": 0,
+ "target_position": 0,
+ "max_positions": self.positions
+ })
+ return True
+
+ async def cleanup(self) -> bool:
+ """Cleanup virtual valve"""
+ self.logger.info(f"Cleaning up virtual valve {self.device_id}")
+ return True
+
+ async def set_position(self, position: int) -> bool:
+ """Set valve position - matches SendCmd action"""
+ if 0 <= position <= self.positions:
+ self.logger.info(f"Setting valve position to {position}")
+ self.data.update({
+ "target_position": position,
+ "current_position": position,
+ "valve_state": "Open" if position > 0 else "Closed"
+ })
+ return True
+ else:
+ self.logger.error(f"Invalid position {position}. Must be 0-{self.positions}")
+ return False
+
+ async def open(self) -> bool:
+ """Open valve - matches EmptyIn action"""
+ self.logger.info("Opening valve")
+ self.data.update({
+ "valve_state": "Open",
+ "current_position": 1
+ })
+ return True
+
+ async def close(self) -> bool:
+ """Close valve - matches EmptyIn action"""
+ self.logger.info("Closing valve")
+ self.data.update({
+ "valve_state": "Closed",
+ "current_position": 0
+ })
+ return True
+
+ # 状态属性
+ @property
+ def status(self) -> str:
+ return self.data.get("status", "Unknown")
+
+ @property
+ def valve_state(self) -> str:
+ return self.data.get("valve_state", "Unknown")
+
+ @property
+ def current_position(self) -> int:
+ return self.data.get("current_position", 0)
+
+ @property
+ def target_position(self) -> int:
+ return self.data.get("target_position", 0)
+
+ @property
+ def max_positions(self) -> int:
+ return self.data.get("max_positions", 6)
\ No newline at end of file
diff --git a/unilabos/messages/__init__.py b/unilabos/messages/__init__.py
index 7bff6dc5..883b9ad8 100644
--- a/unilabos/messages/__init__.py
+++ b/unilabos/messages/__init__.py
@@ -67,7 +67,115 @@ class AGVTransferProtocol(BaseModel):
to_repo: dict
from_repo_position: str
to_repo_position: str
+#=============新添加的新的协议================
+class AddProtocol(BaseModel):
+ vessel: str
+ reagent: str
+ volume: float
+ mass: float
+ amount: str
+ time: float
+ stir: bool
+ stir_speed: float
+ viscous: bool
+ purpose: str
+class CentrifugeProtocol(BaseModel):
+ vessel: str
+ speed: float
+ time: float
+ temp: float # 移除默认值
-__all__ = ["Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol"]
+class FilterProtocol(BaseModel):
+ vessel: str
+ filtrate_vessel: str # 移除默认值
+ stir: bool # 移除默认值
+ stir_speed: float # 移除默认值
+ temp: float # 移除默认值
+ continue_heatchill: bool # 移除默认值
+ volume: float # 移除默认值
+
+class HeatChillProtocol(BaseModel):
+ vessel: str
+ temp: float
+ time: float
+ stir: bool
+ stir_speed: float
+ purpose: str
+
+class HeatChillStartProtocol(BaseModel):
+ vessel: str
+ temp: float
+ purpose: str
+
+class HeatChillStopProtocol(BaseModel):
+ vessel: str
+
+class StirProtocol(BaseModel):
+ stir_time: float
+ stir_speed: float
+ settling_time: float
+
+class StartStirProtocol(BaseModel):
+ vessel: str
+ stir_speed: float
+ purpose: str
+
+class StopStirProtocol(BaseModel):
+ vessel: str
+
+class TransferProtocol(BaseModel):
+ from_vessel: str
+ to_vessel: str
+ volume: float
+ amount: str = ""
+ time: float = 0
+ viscous: bool = False
+ rinsing_solvent: str = ""
+ rinsing_volume: float = 0.0
+ rinsing_repeats: int = 0
+ solid: bool = False
+
+class CleanVesselProtocol(BaseModel):
+ vessel: str # 要清洗的容器名称
+ solvent: str # 用于清洗容器的溶剂名称
+ volume: float # 清洗溶剂的体积,可选参数
+ temp: float # 清洗时的温度,可选参数
+ repeats: int = 1 # 清洗操作的重复次数,默认为 1
+
+class DissolveProtocol(BaseModel):
+ vessel: str # 装有要溶解物质的容器名称
+ solvent: str # 用于溶解物质的溶剂名称
+ volume: float # 溶剂的体积,可选参数
+ amount: str = "" # 要溶解物质的量,可选参数
+ temp: float = 25.0 # 溶解时的温度,可选参数
+ time: float = 0.0 # 溶解的时间,可选参数
+ stir_speed: float = 0.0 # 搅拌速度,可选参数
+
+class FilterThroughProtocol(BaseModel):
+ from_vessel: str # 源容器的名称,即物质起始所在的容器
+ to_vessel: str # 目标容器的名称,物质过滤后要到达的容器
+ filter_through: str # 过滤时所通过的介质,如滤纸、柱子等
+ eluting_solvent: str = "" # 洗脱溶剂的名称,可选参数
+ eluting_volume: float = 0.0 # 洗脱溶剂的体积,可选参数
+ eluting_repeats: int = 0 # 洗脱操作的重复次数,默认为 0
+ residence_time: float = 0.0 # 物质在过滤介质中的停留时间,可选参数
+
+class RunColumnProtocol(BaseModel):
+ from_vessel: str # 源容器的名称,即样品起始所在的容器
+ to_vessel: str # 目标容器的名称,分离后的样品要到达的容器
+ column: str # 所使用的柱子的名称
+
+class WashSolidProtocol(BaseModel):
+ vessel: str # 装有固体物质的容器名称
+ solvent: str # 用于清洗固体的溶剂名称
+ volume: float # 清洗溶剂的体积
+ filtrate_vessel: str = "" # 滤液要收集到的容器名称,可选参数
+ temp: float = 25.0 # 清洗时的温度,可选参数
+ stir: bool = False # 是否在清洗过程中搅拌,默认为 False
+ stir_speed: float = 0.0 # 搅拌速度,可选参数
+ time: float = 0.0 # 清洗的时间,可选参数
+ repeats: int = 1 # 清洗操作的重复次数,默认为 1
+
+__all__ = ["Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol", "CentrifugeProtocol", "AddProtocol", "FilterProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol", "TransferProtocol", "CleanVesselProtocol", "DissolveProtocol", "FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol"]
# End Protocols
diff --git a/unilabos/registry/device_comms/modbus_ioboard.yaml b/unilabos/registry/device_comms/modbus_ioboard.yaml
index 14732410..b1d04eec 100644
--- a/unilabos/registry/device_comms/modbus_ioboard.yaml
+++ b/unilabos/registry/device_comms/modbus_ioboard.yaml
@@ -5,6 +5,6 @@ io_snrd:
type: python
hardware_interface:
name: modbus_client
- extra_info: address
+ extra_info: []
read: read_io_coil
write: write_io_coil
\ No newline at end of file
diff --git a/unilabos/registry/devices/hotel.yaml b/unilabos/registry/devices/hotel.yaml
new file mode 100644
index 00000000..0f925118
--- /dev/null
+++ b/unilabos/registry/devices/hotel.yaml
@@ -0,0 +1,9 @@
+hotel.thermo_orbitor_rs2_hotel:
+ description: Thermo Orbitor RS2 Hotel
+ class:
+ module: unilabos.devices.resource_container.container:HotelContainer
+ type: python
+ model:
+ type: device
+ mesh: thermo_orbitor_rs2_hotel
+
diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml
index bcddae55..bb94b59f 100644
--- a/unilabos/registry/devices/liquid_handler.yaml
+++ b/unilabos/registry/devices/liquid_handler.yaml
@@ -1,5 +1,6 @@
liquid_handler:
description: Liquid handler device controlled by pylabrobot
+ icon: icon_yiyezhan.webp
class:
module: unilabos.devices.liquid_handling.liquid_handler_abstract:LiquidHandlerAbstract
type: python
@@ -22,8 +23,8 @@ liquid_handler:
is_96_well: is_96_well
top: top
none_keys: none_keys
- feedback: { }
- result: { }
+ feedback: {}
+ result: {}
add_liquid:
type: LiquidHandlerAdd
goal:
@@ -43,8 +44,8 @@ liquid_handler:
mix_rate: mix_rate
mix_liquid_height: mix_liquid_height
none_keys: none_keys
- feedback: { }
- result: { }
+ feedback: {}
+ result: {}
transfer_liquid:
type: LiquidHandlerTransfer
goal:
@@ -69,8 +70,8 @@ liquid_handler:
mix_liquid_height: mix_liquid_height
delays: delays
none_keys: none_keys
- feedback: { }
- result: { }
+ feedback: {}
+ result: {}
mix:
type: LiquidHandlerMix
goal:
@@ -81,16 +82,16 @@ liquid_handler:
offsets: offsets
mix_rate: mix_rate
none_keys: none_keys
- feedback: { }
- result: { }
+ feedback: {}
+ result: {}
move_to:
type: LiquidHandlerMoveTo
goal:
well: well
dis_to_top: dis_to_top
channel: channel
- feedback: { }
- result: { }
+ feedback: {}
+ result: {}
aspirate:
type: LiquidHandlerAspirate
goal:
@@ -245,6 +246,21 @@ liquid_handler:
target_vols: target_vols
aspiration_flow_rate: aspiration_flow_rate
dispense_flow_rates: dispense_flow_rates
+ handles:
+ input:
+ - handler_key: liquid-input
+ label: Liquid Input
+ data_type: resource
+ io_type: target
+ data_source: handle
+ data_key: liquid
+ output:
+ - handler_key: liquid-output
+ label: Liquid Output
+ data_type: resource
+ io_type: source
+ data_source: executor
+ data_key: liquid
schema:
type: object
properties:
@@ -272,3 +288,174 @@ liquid_handler.revvity:
status: status
result:
success: success
+
+liquid_handler.biomek:
+ description: Biomek液体处理器设备,基于pylabrobot控制
+ icon: icon_yiyezhan.webp
+ class:
+ module: unilabos.devices.liquid_handling.biomek:LiquidHandlerBiomek
+ type: python
+ status_types: {}
+ action_value_mappings:
+ create_protocol:
+ type: LiquidHandlerProtocolCreation
+ goal:
+ protocol_name: protocol_name
+ protocol_description: protocol_description
+ protocol_version: protocol_version
+ protocol_author: protocol_author
+ protocol_date: protocol_date
+ protocol_type: protocol_type
+ none_keys: none_keys
+ feedback: {}
+ result: {}
+ run_protocol:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result: {}
+ transfer_liquid:
+ type: LiquidHandlerTransfer
+ goal:
+ asp_vols: asp_vols
+ dis_vols: dis_vols
+ sources: sources
+ targets: targets
+ tip_racks: tip_racks
+ use_channels: use_channels
+ asp_flow_rates: asp_flow_rates
+ dis_flow_rates: dis_flow_rates
+ offsets: offsets
+ touch_tip: touch_tip
+ liquid_height: liquid_height
+ blow_out_air_volume: blow_out_air_volume
+ spread: spread
+ is_96_well: is_96_well
+ mix_stage: mix_stage
+ mix_times: mix_times
+ mix_vol: mix_vol
+ mix_rate: mix_rate
+ mix_liquid_height: mix_liquid_height
+ delays: delays
+ none_keys: none_keys
+ feedback: {}
+ result: {}
+ handles:
+ input:
+ - handler_key: liquid-input
+ label: Liquid Input
+ data_type: resource
+ io_type: target
+ data_source: handle
+ data_key: liquid
+ output:
+ - handler_key: liquid-output
+ label: Liquid Output
+ data_type: resource
+ io_type: source
+ data_source: executor
+ data_key: liquid
+ transfer_biomek:
+ type: LiquidHandlerTransferBiomek
+ goal:
+ sources: sources
+ targets: targets
+ tip_rack: tip_rack
+ volume: volume
+ aspirate_techniques: aspirate_techniques
+ dispense_techniques: dispense_techniques
+ feedback: {}
+ result: {}
+ handles:
+ input:
+ - handler_key: sources
+ label: sources
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ - handler_key: targets
+ label: targets
+ data_type: resource
+ data_source: executor
+ data_key: liquid
+ - handler_key: tip_rack
+ label: tip_rack
+ data_type: resource
+ data_source: executor
+ data_key: liquid
+ output:
+ - handler_key: sources_out
+ label: sources
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ - handler_key: targets_out
+ label: targets
+ data_type: resource
+ data_source: executor
+ data_key: liquid
+ oscillation_biomek:
+ type: LiquidHandlerOscillateBiomek
+ goal:
+ rpm: rpm
+ time: time
+ feedback: {}
+ result: {}
+ handles:
+ input:
+ - handler_key: plate
+ label: plate
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ output:
+ - handler_key: plate_out
+ label: plate
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ move_biomek:
+ type: LiquidHandlerMoveBiomek
+ goal:
+ source: sources
+ target: targets
+ feedback: {}
+ result:
+ name: name
+ handles:
+ input:
+ - handler_key: sources
+ label: sources
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ output:
+ - handler_key: targets
+ label: targets
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ incubation_biomek:
+ type: LiquidHandlerIncubateBiomek
+ goal:
+ time: time
+ feedback: {}
+ result: {}
+ handles:
+ input:
+ - handler_key: plate
+ label: plate
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ output:
+ - handler_key: plate_out
+ label: plate
+ data_type: resource
+ data_source: handle
+ data_key: liquid
+ schema:
+ type: object
+ properties: {}
+ required: []
+ additionalProperties: false
diff --git a/unilabos/registry/devices/mock_devices.yaml b/unilabos/registry/devices/mock_devices.yaml
new file mode 100644
index 00000000..59760469
--- /dev/null
+++ b/unilabos/registry/devices/mock_devices.yaml
@@ -0,0 +1,892 @@
+mock_chiller:
+ description: Mock Chiller Device
+ class:
+ module: unilabos.devices.mock.mock_chiller:MockChiller
+ type: python
+ status_types:
+ current_temperature: Float64
+ target_temperature: Float64
+ status: String
+ is_cooling: Bool
+ is_heating: Bool
+ vessel: String # 新增
+ purpose: String # 新增
+ action_value_mappings:
+ emergency_stop:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ heat_chill_start:
+ type: HeatChillStart
+ goal:
+ vessel: vessel
+ temp: temp
+ purpose: purpose
+ feedback: {}
+ result:
+ success: success
+ status: status
+ heat_chill_stop:
+ type: HeatChillStop
+ goal:
+ vessel: vessel
+ feedback: {}
+ result:
+ success: success
+ status: status
+ schema:
+ type: object
+ properties:
+ current_temperature:
+ type: number
+ description: Current temperature of the chiller in °C
+ target_temperature:
+ type: number
+ description: Target temperature setting in °C
+ status:
+ type: string
+ description: Current status of the device
+ is_cooling:
+ type: boolean
+ description: Whether the device is actively cooling
+ is_heating:
+ type: boolean
+ description: Whether the device is actively heating
+ vessel: # 新增
+ type: string
+ description: Current vessel being processed
+ purpose: # 新增
+ type: string
+ description: Purpose of the current operation
+ required:
+ - current_temperature
+ - target_temperature
+ - status
+ - vessel
+ - purpose
+ additionalProperties: false
+mock_filter:
+ description: Mock Filter Device
+ class:
+ module: unilabos.devices.mock.mock_filter:MockFilter
+ type: python
+ status_types:
+ status: String
+ is_filtering: Bool
+ flow_rate: Float64
+ pressure_drop: Float64
+ filter_life: Float64
+ vessel: String
+ filtrate_vessel: String
+ filtered_volume: Float64
+ progress: Float64
+ stir: Bool
+ stir_speed: Float64
+ temperature: Float64
+ continue_heatchill: Bool
+ target_volume: Float64
+ action_value_mappings:
+ filter:
+ type: ProtocolFilter
+ goal:
+ vessel: vessel
+ filtrate_vessel: filtrate_vessel
+ stir: stir
+ stir_speed: stir_speed
+ temp: temp
+ continue_heatchill: continue_heatchill
+ volume: volume
+ feedback:
+ progress: progress
+ current_temp: current_temp
+ filtered_volume: filtered_volume
+ current_status: current_status
+ result:
+ success: success
+ message: message
+ stop_filtering:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ replace_filter:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ description: Current status of the filter
+ is_filtering:
+ type: boolean
+ description: Whether the filter is actively filtering
+ flow_rate:
+ type: number
+ description: Current flow rate in L/min
+ pressure_drop:
+ type: number
+ description: Pressure drop across the filter in Pa
+ filter_life:
+ type: number
+ description: Remaining filter life percentage
+ power_on:
+ type: boolean
+ description: Power state of the device
+ required:
+ - status
+ - is_filtering
+ - flow_rate
+ - filter_life
+ - vessel
+ - filtrate_vessel
+ - filtered_volume
+ - progress
+ additionalProperties: false
+mock_heater:
+ description: Mock Heater Device
+ class:
+ module: unilabos.devices.mock.mock_heater:MockHeater
+ type: python
+ status_types:
+ current_temperature: Float64
+ target_temperature: Float64
+ status: String
+ is_heating: Bool
+ heating_power: Float64
+ max_temperature: Float64
+ vessel: String
+ purpose: String
+ stir: Bool
+ stir_speed: Float64
+ action_value_mappings:
+ heat_chill_start:
+ type: HeatChillStart
+ goal:
+ vessel: vessel
+ temp: temp
+ purpose: purpose
+ feedback:
+ status: status
+ result:
+ success: success
+ heat_chill_stop:
+ type: HeatChillStop
+ goal:
+ vessel: vessel
+ feedback:
+ status: status
+ result:
+ success: success
+ heat_chill:
+ type: HeatChill
+ goal:
+ vessel: vessel
+ temp: temp
+ time: time
+ stir: stir
+ stir_speed: stir_speed
+ purpose: purpose
+ feedback:
+ status: status
+ result:
+ success: success
+ emergency_stop:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ current_temperature:
+ type: number
+ description: Current temperature of the heater in °C
+ target_temperature:
+ type: number
+ description: Target temperature setting in °C
+ status:
+ type: string
+ description: Current status of the device
+ is_heating:
+ type: boolean
+ description: Whether the device is actively heating
+ heating_power:
+ type: number
+ description: Current heating power percentage
+ max_temperature:
+ type: number
+ description: Maximum temperature limit
+ vessel:
+ type: string
+ description: Current vessel being heated
+ purpose:
+ type: string
+ description: Purpose of the heating operation
+ stir:
+ type: boolean
+ description: Whether stirring is enabled
+ stir_speed:
+ type: number
+ description: Current stirring speed
+ required:
+ - current_temperature
+ - target_temperature
+ - status
+ - vessel
+ - purpose
+ additionalProperties: false
+mock_pump:
+ description: Mock Pump Device
+ class:
+ module: unilabos.devices.mock.mock_pump:MockPump
+ type: python
+ status_types:
+ status: String
+ pump_state: String
+ flow_rate: Float64
+ target_flow_rate: Float64
+ pressure: Float64
+ total_volume: Float64
+ max_flow_rate: Float64
+ max_pressure: Float64
+ from_vessel: String
+ to_vessel: String
+ transfer_volume: Float64
+ amount: String
+ transfer_time: Float64
+ is_viscous: Bool
+ rinsing_solvent: String
+ rinsing_volume: Float64
+ rinsing_repeats: Int32
+ is_solid: Bool
+ time_spent: Float64
+ time_remaining: Float64
+ current_device: String
+ action_value_mappings:
+ pump_transfer:
+ type: PumpTransfer
+ goal:
+ from_vessel: from_vessel
+ to_vessel: to_vessel
+ volume: volume
+ amount: amount
+ time: time
+ viscous: viscous
+ rinsing_solvent: rinsing_solvent
+ rinsing_volume: rinsing_volume
+ rinsing_repeats: rinsing_repeats
+ solid: solid
+ feedback:
+ status: status
+ current_device: current_device
+ time_spent: time_spent
+ time_remaining: time_remaining
+ result:
+ success: success
+ pause_pump:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ resume_pump:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ reset_volume_counter:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ description: Current status of the pump
+ pump_state:
+ type: string
+ description: Pump operation state (Running/Stopped/Paused)
+ flow_rate:
+ type: number
+ description: Current flow rate in mL/min
+ target_flow_rate:
+ type: number
+ description: Target flow rate in mL/min
+ pressure:
+ type: number
+ description: Current pressure in bar
+ total_volume:
+ type: number
+ description: Total accumulated volume in mL
+ max_flow_rate:
+ type: number
+ description: Maximum flow rate in mL/min
+ max_pressure:
+ type: number
+ description: Maximum pressure in bar
+ from_vessel:
+ type: string
+ description: Source vessel for transfer
+ to_vessel:
+ type: string
+ description: Target vessel for transfer
+ transfer_volume:
+ type: number
+ description: Volume to transfer in mL
+ amount:
+ type: string
+ description: Amount description
+ transfer_time:
+ type: number
+ description: Transfer time in seconds
+ is_viscous:
+ type: boolean
+ description: Whether the liquid is viscous
+ rinsing_solvent:
+ type: string
+ description: Solvent used for rinsing
+ rinsing_volume:
+ type: number
+ description: Volume used for rinsing
+ rinsing_repeats:
+ type: integer
+ description: Number of rinsing cycles
+ is_solid:
+ type: boolean
+ description: Whether transferring solid material
+ current_device:
+ type: string
+ description: Current device identifier
+ required:
+ - status
+ - pump_state
+ - flow_rate
+ - from_vessel
+ - to_vessel
+ additionalProperties: false
+mock_rotavap:
+ description: Mock Rotavap Device
+ class:
+ module: unilabos.devices.mock.mock_rotavap:MockRotavap
+ type: python
+ status_types:
+ status: String
+ rotate_state: String
+ rotate_time: Float64
+ rotate_speed: Float64
+ pump_state: String
+ pump_time: Float64
+ vacuum_level: Float64
+ temperature: Float64
+ target_temperature: Float64
+ success: String
+ action_value_mappings:
+ set_timer:
+ type: StrSingleInput
+ goal:
+ string: command
+ feedback: {}
+ result:
+ success: success
+ set_rotate_time:
+ type: FloatSingleInput
+ goal:
+ float_in: time_seconds
+ feedback: {}
+ result:
+ success: success
+ set_pump_time:
+ type: FloatSingleInput
+ goal:
+ float_in: time_seconds
+ feedback: {}
+ result:
+ success: success
+ set_rotate_speed:
+ type: FloatSingleInput
+ goal:
+ float_in: speed
+ feedback: {}
+ result:
+ success: success
+ set_temperature:
+ type: FloatSingleInput
+ goal:
+ float_in: temperature
+ feedback: {}
+ result:
+ success: success
+ start_rotation:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ start_pump:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ description: Current status of the rotavap
+ rotate_state:
+ type: string
+ description: Rotation state (Running/Stopped)
+ rotate_time:
+ type: number
+ description: Remaining rotation time in seconds
+ rotate_speed:
+ type: number
+ description: Rotation speed in rpm
+ pump_state:
+ type: string
+ description: Pump state (Running/Stopped)
+ pump_time:
+ type: number
+ description: Remaining pump time in seconds
+ vacuum_level:
+ type: number
+ description: Current vacuum level in mbar
+ temperature:
+ type: number
+ description: Current water bath temperature
+ target_temperature:
+ type: number
+ description: Target water bath temperature
+ success:
+ type: string
+ description: Operation success status
+ required:
+ - status
+ - rotate_time
+ - pump_time
+ - temperature
+ additionalProperties: false
+mock_separator:
+ description: Simplified Mock Separator Device
+ class:
+ module: unilabos.devices.mock.mock_separator:MockSeparator
+ type: python
+ status_types:
+ status: String
+ settling_time: Float64
+ valve_state: String
+ shake_time: Float64
+ shake_status: String
+ current_device: String
+ purpose: String
+ product_phase: String
+ from_vessel: String
+ separation_vessel: String
+ to_vessel: String
+ waste_phase_to_vessel: String
+ solvent: String
+ solvent_volume: Float64
+ through: String
+ repeats: Int32
+ stir_time: Float64
+ stir_speed: Float64
+ time_spent: Float64
+ time_remaining: Float64
+ action_value_mappings:
+ separate:
+ type: Separate
+ goal:
+ purpose: purpose
+ product_phase: product_phase
+ from_vessel: from_vessel
+ separation_vessel: separation_vessel
+ to_vessel: to_vessel
+ waste_phase_to_vessel: waste_phase_to_vessel
+ solvent: solvent
+ solvent_volume: solvent_volume
+ through: through
+ repeats: repeats
+ stir_time: stir_time
+ stir_speed: stir_speed
+ settling_time: settling_time
+ feedback:
+ status: status
+ current_device: current_device
+ time_spent: time_spent
+ time_remaining: time_remaining
+ result:
+ success: success
+ shake:
+ type: FloatSingleInput
+ goal:
+ float_in: shake_time
+ feedback:
+ status: status
+ result:
+ success: success
+ stop_operations:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ set_valve:
+ type: StrSingleInput
+ goal:
+ string: command
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ description: Current status of the separator
+ settling_time:
+ type: number
+ description: Settling time in seconds
+ valve_state:
+ type: string
+ description: Valve state (Open/Closed)
+ shake_time:
+ type: number
+ description: Remaining shake time in seconds
+ shake_status:
+ type: string
+ description: Current shake state
+ purpose:
+ type: string
+ description: Separation purpose (wash/extract)
+ product_phase:
+ type: string
+ description: Product phase (top/bottom)
+ from_vessel:
+ type: string
+ description: Source vessel
+ separation_vessel:
+ type: string
+ description: Vessel for separation
+ to_vessel:
+ type: string
+ description: Target vessel
+ required:
+ - status
+ - valve_state
+ - shake_status
+ - current_device
+ additionalProperties: false
+mock_solenoid_valve:
+ description: Mock Solenoid Valve Device
+ class:
+ module: unilabos.devices.mock.mock_solenoid_valve:MockSolenoidValve
+ type: python
+ status_types:
+ status: String
+ valve_status: String
+ action_value_mappings:
+ set_valve_status:
+ type: StrSingleInput
+ goal:
+ string: status
+ feedback: {}
+ result:
+ success: success
+ open_valve:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ close_valve:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ description: Current status of the valve
+ valve_status:
+ type: string
+ description: Valve status (Open/Closed)
+ required:
+ - status
+ - valve_status
+ additionalProperties: false
+mock_stirrer:
+ description: Mock Stirrer Device
+ class:
+ module: unilabos.devices.mock.mock_stirrer:MockStirrer
+ type: python
+ status_types:
+ status: String
+ stir_speed: Float64
+ target_stir_speed: Float64
+ stir_state: String
+ temperature: Float64
+ target_temperature: Float64
+ heating_state: String
+ heating_power: Float64
+ max_stir_speed: Float64
+ max_temperature: Float64
+ action_value_mappings:
+ set_stir_speed:
+ type: FloatSingleInput
+ goal:
+ float_in: speed
+ feedback: {}
+ result:
+ success: success
+ set_temperature:
+ type: FloatSingleInput
+ goal:
+ float_in: temperature
+ feedback: {}
+ result:
+ success: success
+ start_stirring:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ stop_stirring:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ heating_control:
+ type: StrSingleInput
+ goal:
+ string: heating_state
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ description: Current status of the stirrer
+ stir_speed:
+ type: number
+ description: Current stirring speed in rpm
+ target_stir_speed:
+ type: number
+ description: Target stirring speed in rpm
+ stir_state:
+ type: string
+ description: Stirring state (Running/Stopped)
+ temperature:
+ type: number
+ description: Current temperature in °C
+ target_temperature:
+ type: number
+ description: Target temperature in °C
+ heating_state:
+ type: string
+ description: Heating state (On/Off)
+ heating_power:
+ type: number
+ description: Current heating power percentage
+ max_stir_speed:
+ type: number
+ description: Maximum stirring speed in rpm
+ max_temperature:
+ type: number
+ description: Maximum temperature in °C
+ required:
+ - status
+ - stir_speed
+ - temperature
+ - power_state
+ additionalProperties: false
+mock_stirrer_new:
+ description: Mock Stirrer Device (Copy Version)
+ class:
+ module: unilabos.devices.mock.mock_stirrer_new:MockStirrer_new
+ type: python
+ status_types:
+ status: String
+ vessel: String
+ purpose: String
+ stir_speed: Float64
+ target_stir_speed: Float64
+ stir_state: String
+ stir_time: Float64
+ settling_time: Float64
+ progress: Float64
+ max_stir_speed: Float64
+ action_value_mappings:
+ start_stir:
+ type: ProtocolStartStir
+ goal:
+ vessel: vessel
+ stir_speed: stir_speed
+ purpose: purpose
+ feedback:
+ progress: progress
+ current_speed: stir_speed
+ current_status: status
+ result:
+ success: success
+ message: message
+ stir:
+ type: Stir
+ goal:
+ stir_time: stir_time
+ stir_speed: stir_speed
+ settling_time: settling_time
+ feedback:
+ status: status
+ result:
+ success: success
+ stop_stir:
+ type: ProtocolStopStir
+ goal:
+ vessel: vessel
+ feedback:
+ progress: progress
+ current_status: status
+ result:
+ success: success
+ message: message
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ vessel:
+ type: string
+ purpose:
+ type: string
+ stir_speed:
+ type: number
+ target_stir_speed:
+ type: number
+ stir_state:
+ type: string
+ stir_time:
+ type: number
+ settling_time:
+ type: number
+ progress:
+ type: number
+ max_stir_speed:
+ type: number
+ required:
+ - status
+ - stir_speed
+ - stir_state
+ - vessel
+ additionalProperties: false
+mock_vacuum:
+ description: Mock Vacuum Pump Device
+ class:
+ module: unilabos.devices.mock.mock_vacuum:MockVacuum
+ type: python
+ status_types:
+ status: String
+ power_state: String
+ pump_state: String
+ vacuum_level: Float64
+ target_vacuum: Float64
+ pump_speed: Float64
+ pump_efficiency: Float64
+ max_pump_speed: Float64
+ action_value_mappings:
+ power_control:
+ type: StrSingleInput
+ goal:
+ string: power_state
+ feedback: {}
+ result:
+ success: success
+ set_vacuum_level:
+ type: FloatSingleInput
+ goal:
+ float_in: vacuum_level
+ feedback: {}
+ result:
+ success: success
+ start_vacuum:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ stop_vacuum:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ pause_vacuum:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ resume_vacuum:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ vent_to_atmosphere:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ description: Current status of the vacuum pump
+ power_state:
+ type: string
+ description: Power state (On/Off)
+ pump_state:
+ type: string
+ description: Pump operation state (Running/Stopped/Paused)
+ vacuum_level:
+ type: number
+ description: Current vacuum level in mbar
+ target_vacuum:
+ type: number
+ description: Target vacuum level in mbar
+ pump_speed:
+ type: number
+ description: Current pump speed in L/s
+ pump_efficiency:
+ type: number
+ description: Pump efficiency percentage
+ max_pump_speed:
+ type: number
+ description: Maximum pump speed in L/s
+ required:
+ - status
+ - power_state
+ - pump_state
+ - vacuum_level
+ additionalProperties: false
diff --git a/unilabos/registry/devices/moveit_config.yaml b/unilabos/registry/devices/moveit_config.yaml
new file mode 100644
index 00000000..6236855d
--- /dev/null
+++ b/unilabos/registry/devices/moveit_config.yaml
@@ -0,0 +1,56 @@
+moveit.toyo_xyz:
+ description: Toyo XYZ
+ class:
+ module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
+ type: python
+ action_value_mappings:
+ set_position:
+ type: SendCmd
+ goal:
+ command: command
+ feedback: { }
+ result: { }
+ pick_and_place:
+ type: SendCmd
+ goal:
+ command: command
+ feedback: { }
+ result: { }
+ set_status:
+ type: SendCmd
+ goal:
+ command: command
+ feedback: { }
+ result: { }
+
+ model:
+ type: device
+ mesh: toyo_xyz
+
+moveit.arm_slider:
+ description: Arm with Slider
+ model:
+ type: device
+ mesh: arm_slider
+ class:
+ module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
+ type: python
+ action_value_mappings:
+ set_position:
+ type: SendCmd
+ goal:
+ command: command
+ feedback: {}
+ result: {}
+ pick_and_place:
+ type: SendCmd
+ goal:
+ command: command
+ feedback: {}
+ result: {}
+ set_status:
+ type: SendCmd
+ goal:
+ command: command
+ feedback: {}
+ result: {}
diff --git a/unilabos/registry/devices/pump_and_valve.yaml b/unilabos/registry/devices/pump_and_valve.yaml
index 5fdd8606..fd5dd98e 100644
--- a/unilabos/registry/devices/pump_and_valve.yaml
+++ b/unilabos/registry/devices/pump_and_valve.yaml
@@ -23,20 +23,51 @@ syringe_pump_with_valve.runze:
type: string
description: The position of the valve
required:
- - status
- - position
- - valve_position
+ - status
+ - position
+ - valve_position
additionalProperties: false
-
solenoid_valve.mock:
description: Mock solenoid valve
class:
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
type: python
+ status_types:
+ status: String
+ valve_position: String
+ action_value_mappings:
+ open:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result: {}
+ close:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result: {}
+ handles:
+ input:
+ - handler_key: fluid-input
+ label: Fluid Input
+ data_type: fluid
+ output:
+ - handler_key: fluid-output
+ label: Fluid Output
+ data_type: fluid
+ init_param_schema:
+ type: object
+ properties:
+ port:
+ type: string
+ description: "通信端口"
+ default: "COM6"
+ required:
+ - port
solenoid_valve:
description: Solenoid valve
class:
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
- type: python
\ No newline at end of file
+ type: python
diff --git a/unilabos/registry/devices/temperature.yaml b/unilabos/registry/devices/temperature.yaml
index 1c01b4e8..662ee016 100644
--- a/unilabos/registry/devices/temperature.yaml
+++ b/unilabos/registry/devices/temperature.yaml
@@ -62,4 +62,4 @@ tempsensor:
command: command
feedback: {}
result:
- success: success
\ No newline at end of file
+ success: success
diff --git a/unilabos/registry/devices/vacuum_and_purge.yaml b/unilabos/registry/devices/vacuum_and_purge.yaml
index 236ceddc..6bdff16b 100644
--- a/unilabos/registry/devices/vacuum_and_purge.yaml
+++ b/unilabos/registry/devices/vacuum_and_purge.yaml
@@ -22,9 +22,76 @@ vacuum_pump.mock:
string: string
feedback: {}
result: {}
+ handles:
+ input:
+ - handler_key: fluid-input
+ label: Fluid Input
+ data_type: fluid
+ io_type: target
+ data_source: handle
+ data_key: fluid_in
+ output:
+ - handler_key: fluid-output
+ label: Fluid Output
+ data_type: fluid
+ io_type: source
+ data_source: executor
+ data_key: fluid_out
+ init_param_schema:
+ type: object
+ properties:
+ port:
+ type: string
+ description: "通信端口"
+ default: "COM6"
+ required:
+ - port
gas_source.mock:
description: Mock gas source
class:
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
type: python
+ status_types:
+ status: String
+ action_value_mappings:
+ open:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result: {}
+ close:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result: {}
+ set_status:
+ type: StrSingleInput
+ goal:
+ string: string
+ feedback: {}
+ result: {}
+ handles:
+ input:
+ - handler_key: fluid-input
+ label: Fluid Input
+ data_type: fluid
+ io_type: target
+ data_source: handle
+ data_key: fluid_in
+ output:
+ - handler_key: fluid-output
+ label: Fluid Output
+ data_type: fluid
+ io_type: source
+ data_source: executor
+ data_key: fluid_out
+ init_param_schema:
+ type: object
+ properties:
+ port:
+ type: string
+ description: "通信端口"
+ default: "COM6"
+ required:
+ - port
\ No newline at end of file
diff --git a/unilabos/registry/devices/virtual_device.yaml b/unilabos/registry/devices/virtual_device.yaml
new file mode 100644
index 00000000..f92baf29
--- /dev/null
+++ b/unilabos/registry/devices/virtual_device.yaml
@@ -0,0 +1,388 @@
+virtual_pump:
+ description: Virtual Pump for PumpTransferProtocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_pump:VirtualPump
+ type: python
+ status_types:
+ status: String
+ position: Float64
+ valve_position: Int32 # 修复:使用 Int32 而不是 String
+ max_volume: Float64
+ current_volume: Float64
+ action_value_mappings:
+ transfer:
+ type: PumpTransfer
+ goal:
+ from_vessel: from_vessel
+ to_vessel: to_vessel
+ volume: volume
+ amount: amount
+ time: time
+ viscous: viscous
+ rinsing_solvent: rinsing_solvent
+ rinsing_volume: rinsing_volume
+ rinsing_repeats: rinsing_repeats
+ solid: solid
+ feedback:
+ status: status
+ result:
+ success: success
+ set_valve_position:
+ type: FloatSingleInput
+ goal:
+ Int32: Int32
+ feedback:
+ status: status
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ max_volume:
+ type: number
+ default: 25.0
+ additionalProperties: false
+
+virtual_stirrer:
+ description: Virtual Stirrer for StirProtocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_stirrer:VirtualStirrer
+ type: python
+ status_types:
+ status: String
+ action_value_mappings:
+ stir:
+ type: Stir
+ goal:
+ stir_time: stir_time
+ stir_speed: stir_speed
+ settling_time: settling_time
+ feedback:
+ status: status
+ result:
+ success: success
+ start_stir:
+ type: ProtocolStartStir
+ goal:
+ vessel: vessel
+ stir_speed: stir_speed
+ purpose: purpose
+ feedback:
+ status: status
+ result:
+ success: success
+ stop_stir:
+ type: ProtocolStopStir
+ goal:
+ vessel: vessel
+ feedback:
+ status: status
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ max_temp:
+ type: number
+ default: 100.0
+ max_speed:
+ type: number
+ default: 1000.0
+ additionalProperties: false
+
+virtual_valve:
+ description: Virtual Valve for AddProtocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_valve:VirtualValve
+ type: python
+ status_types:
+ status: String
+ valve_state: String
+ current_position: Int32
+ target_position: Int32
+ max_positions: Int32
+ action_value_mappings:
+ set_position:
+ type: SendCmd
+ goal:
+ command: position
+ feedback: {}
+ result:
+ success: success
+ open:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ close:
+ type: EmptyIn
+ goal: {}
+ feedback: {}
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ positions:
+ type: integer
+ default: 6
+ additionalProperties: false
+
+virtual_centrifuge:
+ description: Virtual Centrifuge for CentrifugeProtocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_centrifuge:VirtualCentrifuge
+ type: python
+ status_types:
+ status: String
+ current_speed: Float64
+ target_speed: Float64
+ current_temp: Float64
+ target_temp: Float64
+ max_speed: Float64
+ max_temp: Float64
+ min_temp: Float64
+ centrifuge_state: String
+ time_remaining: Float64
+ action_value_mappings:
+ centrifuge:
+ type: ProtocolCentrifuge
+ goal:
+ vessel: vessel
+ speed: speed
+ time: time
+ temp: temp
+ feedback:
+ progress: progress
+ current_speed: current_speed
+ current_temp: current_temp
+ current_status: status
+ result:
+ success: success
+ message: message
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ max_speed:
+ type: number
+ default: 15000.0
+ max_temp:
+ type: number
+ default: 40.0
+ min_temp:
+ type: number
+ default: 4.0
+ additionalProperties: false
+
+virtual_filter:
+ description: Virtual Filter for FilterProtocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_filter:VirtualFilter
+ type: python
+ status_types:
+ status: String
+ filter_state: String
+ current_temp: Float64
+ target_temp: Float64
+ max_temp: Float64
+ stir_speed: Float64
+ max_stir_speed: Float64
+ filtered_volume: Float64
+ progress: Float64
+ message: String
+ action_value_mappings:
+ filter_sample:
+ type: ProtocolFilter
+ goal:
+ vessel: vessel
+ filtrate_vessel: filtrate_vessel
+ stir: stir
+ stir_speed: stir_speed
+ temp: temp
+ continue_heatchill: continue_heatchill
+ volume: volume
+ feedback:
+ progress: progress
+ current_temp: current_temp
+ filtered_volume: filtered_volume
+ current_status: status
+ result:
+ success: success
+ message: message
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ max_temp:
+ type: number
+ default: 100.0
+ max_stir_speed:
+ type: number
+ default: 1000.0
+ additionalProperties: false
+
+virtual_heatchill:
+ description: Virtual HeatChill for HeatChillProtocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_heatchill:VirtualHeatChill
+ type: python
+ status_types:
+ status: String
+ action_value_mappings:
+ heat_chill:
+ type: HeatChill
+ goal:
+ vessel: vessel
+ temp: temp
+ time: time
+ stir: stir
+ stir_speed: stir_speed
+ purpose: purpose
+ feedback:
+ status: status
+ result:
+ success: success
+ heat_chill_start:
+ type: HeatChillStart
+ goal:
+ vessel: vessel
+ temp: temp
+ purpose: purpose
+ feedback:
+ status: status
+ result:
+ success: success
+ heat_chill_stop:
+ type: HeatChillStop
+ goal:
+ vessel: vessel
+ feedback:
+ status: status
+ result:
+ success: success
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ max_temp:
+ type: number
+ default: 200.0
+ min_temp:
+ type: number
+ default: -80.0
+ max_stir_speed:
+ type: number
+ default: 1000.0
+ additionalProperties: false
+
+virtual_transfer_pump:
+ description: Virtual Transfer Pump for TransferProtocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_transferpump:VirtualTransferPump
+ type: python
+ status_types:
+ status: String
+ current_volume: Float64
+ max_volume: Float64
+ transfer_rate: Float64
+ from_vessel: String
+ to_vessel: String
+ progress: Float64
+ transferred_volume: Float64
+ current_status: String
+ action_value_mappings:
+ transfer:
+ type: ProtocolTransfer
+ goal:
+ from_vessel: from_vessel
+ to_vessel: to_vessel
+ volume: volume
+ amount: amount
+ time: time
+ viscous: viscous
+ rinsing_solvent: rinsing_solvent
+ rinsing_volume: rinsing_volume
+ rinsing_repeats: rinsing_repeats
+ solid: solid
+ feedback:
+ progress: progress
+ transferred_volume: transferred_volume
+ current_status: current_status
+ result:
+ success: success
+ message: message
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ max_volume:
+ type: number
+ default: 50.0
+ transfer_rate:
+ type: number
+ default: 5.0
+ additionalProperties: false
+
+virtual_column:
+ description: Virtual Column for RunColumn Protocol Testing
+ class:
+ module: unilabos.devices.virtual.virtual_column:VirtualColumn
+ type: python
+ status_types:
+ status: String
+ column_state: String
+ current_flow_rate: Float64
+ max_flow_rate: Float64
+ column_length: Float64
+ column_diameter: Float64
+ processed_volume: Float64
+ progress: Float64
+ current_status: String
+ action_value_mappings:
+ run_column:
+ type: ProtocolRunColumn
+ goal:
+ from_vessel: from_vessel
+ to_vessel: to_vessel
+ column: column
+ feedback:
+ status: current_status
+ progress: progress
+ result:
+ success: success
+ message: message
+ schema:
+ type: object
+ properties:
+ port:
+ type: string
+ default: "VIRTUAL"
+ max_flow_rate:
+ type: number
+ default: 10.0
+ column_length:
+ type: number
+ default: 25.0
+ column_diameter:
+ type: number
+ default: 2.0
+ additionalProperties: false
\ No newline at end of file
diff --git a/unilabos/registry/devices/work_station.yaml b/unilabos/registry/devices/work_station.yaml
index fadfd5ec..d3a37338 100644
--- a/unilabos/registry/devices/work_station.yaml
+++ b/unilabos/registry/devices/work_station.yaml
@@ -4,4 +4,4 @@ workstation:
module: unilabos.ros.nodes.presets.protocol_node:ROS2ProtocolNode
type: ros2
schema:
- properties: {}
\ No newline at end of file
+ properties: {}
diff --git a/unilabos/registry/registry.py b/unilabos/registry/registry.py
index c68e0d8d..03d48997 100644
--- a/unilabos/registry/registry.py
+++ b/unilabos/registry/registry.py
@@ -25,9 +25,7 @@ class Registry:
self.ResourceCreateFromOuterEasy = self._replace_type_with_class(
"ResourceCreateFromOuterEasy", "host_node", f"动作 create_resource"
)
- self.EmptyIn = self._replace_type_with_class(
- "EmptyIn", "host_node", f""
- )
+ self.EmptyIn = self._replace_type_with_class("EmptyIn", "host_node", f"")
self.device_type_registry = {}
self.resource_type_registry = {}
self._setup_called = False # 跟踪setup是否已调用
@@ -66,6 +64,7 @@ class Registry:
"goal_default": yaml.safe_load(
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuter.Goal))
),
+ "handles": {},
},
"create_resource": {
"type": self.ResourceCreateFromOuterEasy,
@@ -86,6 +85,15 @@ class Registry:
"goal_default": yaml.safe_load(
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal))
),
+ "handles": {
+ "output": [{
+ "handler_key": "labware",
+ "label": "Labware",
+ "data_type": "resource",
+ "data_source": "handle",
+ "data_key": "liquid"
+ }]
+ },
},
"test_latency": {
"type": self.EmptyIn,
@@ -94,11 +102,14 @@ class Registry:
"result": {"latency_ms": "latency_ms", "time_diff_ms": "time_diff_ms"},
"schema": ros_action_to_json_schema(self.EmptyIn),
"goal_default": {},
+ "handles": {},
},
},
},
"icon": "icon_device.webp",
"registry_type": "device",
+ "handles": [],
+ "init_param_schema": {},
"schema": {"properties": {}, "additionalProperties": False, "type": "object"},
"file_path": "/",
}
@@ -132,6 +143,10 @@ class Registry:
resource_info["description"] = ""
if "icon" not in resource_info:
resource_info["icon"] = ""
+ if "handles" not in resource_info:
+ resource_info["handles"] = []
+ if "init_param_schema" not in resource_info:
+ resource_info["init_param_schema"] = {}
resource_info["registry_type"] = "resource"
self.resource_type_registry.update(data)
logger.debug(
@@ -194,6 +209,10 @@ class Registry:
device_config["description"] = ""
if "icon" not in device_config:
device_config["icon"] = ""
+ if "handles" not in device_config:
+ device_config["handles"] = []
+ if "init_param_schema" not in device_config:
+ device_config["init_param_schema"] = {}
device_config["registry_type"] = "device"
if "class" in device_config:
# 处理状态类型
@@ -206,6 +225,8 @@ class Registry:
# 处理动作值映射
if "action_value_mappings" in device_config["class"]:
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
+ if "handles" not in action_config:
+ action_config["handles"] = []
if "type" in action_config:
action_config["type"] = self._replace_type_with_class(
action_config["type"], device_id, f"动作 {action_name}"
diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py
index 9a31add5..cca7a35b 100644
--- a/unilabos/resources/graphio.py
+++ b/unilabos/resources/graphio.py
@@ -9,7 +9,7 @@ try:
from pylabrobot.resources.resource import Resource as ResourcePLR
except ImportError:
pass
-
+from typing import Union, get_origin, get_args
physical_setup_graph: nx.Graph = None
@@ -298,7 +298,6 @@ def nested_dict_to_list(nested_dict: dict) -> list[dict]: # FIXME 是tree?
return result
-
def convert_resources_to_type(
resources_list: list[dict], resource_type: type, *, plr_model: bool = False
) -> Union[list[dict], dict, None, "ResourcePLR"]:
@@ -320,9 +319,13 @@ def convert_resources_to_type(
return resource_ulab_to_plr(resources_list, plr_model)
resources_tree = dict_to_tree({r["id"]: r for r in resources_list})
return resource_ulab_to_plr(resources_tree[0], plr_model)
- elif isinstance(resource_type, list) and all(issubclass(t, ResourcePLR) for t in resource_type):
- resources_tree = dict_to_tree({r["id"]: r for r in resources_list})
- return [resource_ulab_to_plr(r, plr_model) for r in resources_tree]
+ elif isinstance(resource_type, list) :
+ if all((get_origin(t) is Union) for t in resource_type):
+ resources_tree = dict_to_tree({r["id"]: r for r in resources_list})
+ return [resource_ulab_to_plr(r, plr_model) for r in resources_tree]
+ elif all(issubclass(t, ResourcePLR) for t in resource_type):
+ resources_tree = dict_to_tree({r["id"]: r for r in resources_list})
+ return [resource_ulab_to_plr(r, plr_model) for r in resources_tree]
else:
return None
@@ -343,9 +346,13 @@ def convert_resources_from_type(resources_list, resource_type: type) -> Union[li
elif isinstance(resource_type, type) and issubclass(resource_type, ResourcePLR):
resources_tree = [resource_plr_to_ulab(resources_list)]
return tree_to_list(resources_tree)
- elif isinstance(resource_type, list) and all(issubclass(t, ResourcePLR) for t in resource_type):
- resources_tree = [resource_plr_to_ulab(r) for r in resources_list]
- return tree_to_list(resources_tree)
+ elif isinstance(resource_type, list) :
+ if all((get_origin(t) is Union) for t in resource_type):
+ resources_tree = [resource_plr_to_ulab(r) for r in resources_list]
+ return tree_to_list(resources_tree)
+ elif all(issubclass(t, ResourcePLR) for t in resource_type):
+ resources_tree = [resource_plr_to_ulab(r) for r in resources_list]
+ return tree_to_list(resources_tree)
else:
return None
diff --git a/unilabos/ros/main_slave_run.py b/unilabos/ros/main_slave_run.py
index c4c5a172..bbd6359e 100644
--- a/unilabos/ros/main_slave_run.py
+++ b/unilabos/ros/main_slave_run.py
@@ -6,9 +6,9 @@ import time
from typing import Optional, Dict, Any, List
import rclpy
-from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher
from unilabos.ros.nodes.presets.resource_mesh_manager import ResourceMeshManager
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
+from unilabos.devices.ros_dev.liquid_handler_joint_publisher import LiquidHandlerJointPublisher
from unilabos_msgs.msg import Resource # type: ignore
from unilabos_msgs.srv import ResourceAdd, SerialCommand # type: ignore
from rclpy.executors import MultiThreadedExecutor
@@ -69,19 +69,23 @@ def main(
)
if visual != "disable":
+ from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher
+
resource_mesh_manager = ResourceMeshManager(
resources_mesh_config,
resources_config,
- resource_tracker= DeviceNodeResourceTracker(),
+ resource_tracker = host_node.resource_tracker,
device_id = 'resource_mesh_manager',
)
joint_republisher = JointRepublisher(
'joint_republisher',
- DeviceNodeResourceTracker()
+ host_node.resource_tracker
)
-
+ lh_joint_pub = LiquidHandlerJointPublisher(resources_config=resources_config,
+ resource_tracker=host_node.resource_tracker)
executor.add_node(resource_mesh_manager)
executor.add_node(joint_republisher)
+ executor.add_node(lh_joint_pub)
thread = threading.Thread(target=executor.spin, daemon=True, name="host_executor_thread")
thread.start()
@@ -121,6 +125,7 @@ def slave(
executor.add_node(n)
if visual != "disable":
+ from unilabos.ros.nodes.presets.joint_republisher import JointRepublisher
resource_mesh_manager = ResourceMeshManager(
resources_mesh_config,
resources_config,
diff --git a/unilabos/ros/msgs/message_converter.py b/unilabos/ros/msgs/message_converter.py
index 11c7afd5..62ab606d 100644
--- a/unilabos/ros/msgs/message_converter.py
+++ b/unilabos/ros/msgs/message_converter.py
@@ -131,7 +131,7 @@ _msg_converter: Dict[Type, Any] = {
Bool: lambda x: Bool(data=bool(x)),
str: str,
String: lambda x: String(data=str(x)),
- Point: lambda x: Point(x=x.x, y=x.y, z=x.z),
+ Point: lambda x: Point(x=x.x, y=x.y, z=x.z) if not isinstance(x, dict) else Point(x=x.get("x", 0.0), y=x.get("y", 0.0), z=x.get("z", 0.0)),
Resource: lambda x: Resource(
id=x.get("id", ""),
name=x.get("name", ""),
@@ -141,7 +141,7 @@ _msg_converter: Dict[Type, Any] = {
type=x.get("type", ""),
category=x.get("class", "") or x.get("type", ""),
pose=(
- Pose(position=Point(x=float(x.get("position", {}).get("x", 0)), y=float(x.get("position", {}).get("y", 0)), z=float(x.get("position", {}).get("z", 0))))
+ Pose(position=Point(x=float(x.get("position", {}).get("x", 0.0)), y=float(x.get("position", {}).get("y", 0.0)), z=float(x.get("position", {}).get("z", 0.0))))
if x.get("position", None) is not None
else Pose()
),
diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py
index 28b67aa4..eafdd71c 100644
--- a/unilabos/ros/nodes/base_device_node.py
+++ b/unilabos/ros/nodes/base_device_node.py
@@ -1,5 +1,4 @@
import copy
-import functools
import json
import threading
import time
@@ -20,16 +19,29 @@ from rclpy.service import Service
from unilabos_msgs.action import SendCmd
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
-from unilabos.resources.graphio import convert_resources_to_type, convert_resources_from_type, resource_ulab_to_plr, \
- initialize_resources, list_to_nested_dict, dict_to_tree, resource_plr_to_ulab, tree_to_list
+from unilabos.resources.graphio import (
+ convert_resources_to_type,
+ convert_resources_from_type,
+ resource_ulab_to_plr,
+ initialize_resources,
+ dict_to_tree,
+ resource_plr_to_ulab,
+ tree_to_list,
+)
from unilabos.ros.msgs.message_converter import (
convert_to_ros_msg,
convert_from_ros_msg,
convert_from_ros_msg_with_mapping,
- convert_to_ros_msg_with_mapping, ros_action_to_json_schema,
+ convert_to_ros_msg_with_mapping,
)
-from unilabos_msgs.srv import ResourceAdd, ResourceGet, ResourceDelete, ResourceUpdate, ResourceList, \
- SerialCommand # type: ignore
+from unilabos_msgs.srv import (
+ ResourceAdd,
+ ResourceGet,
+ ResourceDelete,
+ ResourceUpdate,
+ ResourceList,
+ SerialCommand,
+) # type: ignore
from unilabos_msgs.msg import Resource # type: ignore
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
@@ -37,7 +49,7 @@ from unilabos.ros.x.rclpyx import get_event_loop
from unilabos.ros.utils.driver_creator import ProtocolNodeCreator, PyLabRobotCreator, DeviceClassCreator
from unilabos.utils.async_util import run_async_func
from unilabos.utils.log import info, debug, warning, error, critical, logger
-from unilabos.utils.type_check import get_type_class, TypeEncoder
+from unilabos.utils.type_check import get_type_class, TypeEncoder, serialize_result_info
T = TypeVar("T")
@@ -292,7 +304,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.create_ros_action_server(action_name, action_value_mapping)
# 创建线程池执行器
- self._executor = ThreadPoolExecutor(max_workers=max(len(action_value_mappings), 1), thread_name_prefix=f"ROSDevice{self.device_id}")
+ self._executor = ThreadPoolExecutor(
+ max_workers=max(len(action_value_mappings), 1), thread_name_prefix=f"ROSDevice{self.device_id}"
+ )
# 创建资源管理客户端
self._resource_clients: Dict[str, Client] = {
@@ -329,12 +343,14 @@ class BaseROS2DeviceNode(Node, Generic[T]):
ADD_LIQUID_TYPE = other_calling_param.pop("ADD_LIQUID_TYPE", [])
LIQUID_VOLUME = other_calling_param.pop("LIQUID_VOLUME", [])
LIQUID_INPUT_SLOT = other_calling_param.pop("LIQUID_INPUT_SLOT", [])
- slot = other_calling_param.pop("slot", -1)
- if slot >= 0: # slot为负数的时候采用assign方法
+ slot = other_calling_param.pop("slot", "-1")
+ if slot != "-1": # slot为负数的时候采用assign方法
other_calling_param["slot"] = slot
# 本地拿到这个物料,可能需要先做初始化?
if isinstance(resources, list):
- if len(resources) == 1 and isinstance(resources[0], list) and not initialize_full: # 取消,不存在的情况
+ if (
+ len(resources) == 1 and isinstance(resources[0], list) and not initialize_full
+ ): # 取消,不存在的情况
# 预先initialize过,以整组的形式传入
request.resources = [convert_to_ros_msg(Resource, resource_) for resource_ in resources[0]]
elif initialize_full:
@@ -349,6 +365,25 @@ class BaseROS2DeviceNode(Node, Generic[T]):
response = rclient.call(request)
# 应该先add_resource了
res.response = "OK"
+ # 如果driver自己就有assign的方法,那就使用driver自己的assign方法
+ if hasattr(self.driver_instance, "create_resource"):
+ create_resource_func = getattr(self.driver_instance, "create_resource")
+ try:
+ ret = create_resource_func(
+ resource_tracker=self.resource_tracker,
+ resources=request.resources,
+ bind_parent_id=bind_parent_id,
+ bind_location=location,
+ liquid_input_slot=LIQUID_INPUT_SLOT,
+ liquid_type=ADD_LIQUID_TYPE,
+ liquid_volume=LIQUID_VOLUME,
+ slot_on_deck=slot,
+ )
+ res.response = serialize_result_info("", True, ret)
+ except Exception as e:
+ traceback.print_exc()
+ res.response = serialize_result_info(traceback.format_exc(), False, {})
+ return res
# 接下来该根据bind_parent_id进行assign了,目前只有plr可以进行assign,不然没有办法输入到物料系统中
resource = self.resource_tracker.figure_resource({"name": bind_parent_id})
# request.resources = [convert_to_ros_msg(Resource, resources)]
@@ -359,6 +394,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
from pylabrobot.resources import Coordinate
from pylabrobot.resources import OTDeck
from pylabrobot.resources import Plate
+
contain_model = not isinstance(resource, Deck)
if isinstance(resource, ResourcePLR):
# resources.list()
@@ -366,25 +402,39 @@ class BaseROS2DeviceNode(Node, Generic[T]):
plr_instance = resource_ulab_to_plr(resources_tree[0], contain_model)
if isinstance(plr_instance, Plate):
empty_liquid_info_in = [(None, 0)] * plr_instance.num_items
- for liquid_type, liquid_volume, liquid_input_slot in zip(ADD_LIQUID_TYPE, LIQUID_VOLUME, LIQUID_INPUT_SLOT):
+ for liquid_type, liquid_volume, liquid_input_slot in zip(
+ ADD_LIQUID_TYPE, LIQUID_VOLUME, LIQUID_INPUT_SLOT
+ ):
empty_liquid_info_in[liquid_input_slot] = (liquid_type, liquid_volume)
plr_instance.set_well_liquids(empty_liquid_info_in)
if isinstance(resource, OTDeck) and "slot" in other_calling_param:
+ other_calling_param["slot"] = int(other_calling_param["slot"])
resource.assign_child_at_slot(plr_instance, **other_calling_param)
else:
- _discard_slot = other_calling_param.pop("slot", -1)
- resource.assign_child_resource(plr_instance, Coordinate(location["x"], location["y"], location["z"]), **other_calling_param)
- request2.resources = [convert_to_ros_msg(Resource, r) for r in tree_to_list([resource_plr_to_ulab(resource)])]
+ _discard_slot = other_calling_param.pop("slot", "-1")
+ resource.assign_child_resource(
+ plr_instance,
+ Coordinate(location["x"], location["y"], location["z"]),
+ **other_calling_param,
+ )
+ request2.resources = [
+ convert_to_ros_msg(Resource, r) for r in tree_to_list([resource_plr_to_ulab(resource)])
+ ]
rclient2.call(request2)
# 发送给ResourceMeshManager
action_client = ActionClient(
- self, SendCmd, "/devices/resource_mesh_manager/add_resource_mesh", callback_group=self.callback_group
+ self,
+ SendCmd,
+ "/devices/resource_mesh_manager/add_resource_mesh",
+ callback_group=self.callback_group,
)
goal = SendCmd.Goal()
- goal.command = json.dumps({
- "resources": resources,
- "bind_parent_id": bind_parent_id,
- })
+ goal.command = json.dumps(
+ {
+ "resources": resources,
+ "bind_parent_id": bind_parent_id,
+ }
+ )
future = action_client.send_goal_async(goal, goal_uuid=uuid.uuid4())
def done_cb(*args):
@@ -401,10 +451,16 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# noinspection PyTypeChecker
self._service_server: Dict[str, Service] = {
"query_host_name": self.create_service(
- SerialCommand, f"/srv{self.namespace}/query_host_name", query_host_name_cb, callback_group=self.callback_group
+ SerialCommand,
+ f"/srv{self.namespace}/query_host_name",
+ query_host_name_cb,
+ callback_group=self.callback_group,
),
"append_resource": self.create_service(
- SerialCommand, f"/srv{self.namespace}/append_resource", append_resource, callback_group=self.callback_group
+ SerialCommand,
+ f"/srv{self.namespace}/append_resource",
+ append_resource,
+ callback_group=self.callback_group,
),
}
@@ -433,6 +489,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
registered_devices[self.device_id] = device_info
from unilabos.config.config import BasicConfig
from unilabos.ros.nodes.presets.host_node import HostNode
+
if not BasicConfig.is_host_mode:
sclient = self.create_client(SerialCommand, "/node_info_update")
# 启动线程执行发送任务
@@ -440,7 +497,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
target=self.send_slave_node_info,
args=(sclient,),
daemon=True,
- name=f"ROSDevice{self.device_id}_send_slave_node_info"
+ name=f"ROSDevice{self.device_id}_send_slave_node_info",
).start()
else:
host_node = HostNode.get_instance(0)
@@ -451,12 +508,18 @@ class BaseROS2DeviceNode(Node, Generic[T]):
sclient.wait_for_service()
request = SerialCommand.Request()
from unilabos.config.config import BasicConfig
- request.command = json.dumps({
- "SYNC_SLAVE_NODE_INFO": {
- "machine_name": BasicConfig.machine_name,
- "type": "slave",
- "edge_device_id": self.device_id
- }}, ensure_ascii=False, cls=TypeEncoder)
+
+ request.command = json.dumps(
+ {
+ "SYNC_SLAVE_NODE_INFO": {
+ "machine_name": BasicConfig.machine_name,
+ "type": "slave",
+ "edge_device_id": self.device_id,
+ }
+ },
+ ensure_ascii=False,
+ cls=TypeEncoder,
+ )
# 发送异步请求并等待结果
future = sclient.call_async(request)
@@ -529,6 +592,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
"""创建动作执行回调函数"""
async def execute_callback(goal_handle: ServerGoalHandle):
+ # 初始化结果信息变量
+ execution_error = ""
+ execution_success = False
+ action_return_value = None
+
self.lab_logger().info(f"执行动作: {action_name}")
goal = goal_handle.request
@@ -568,7 +636,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
current_resources.extend(response.resources)
else:
r = ResourceGet.Request()
- r.id = action_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else action_kwargs[k][0]["id"]
+ r.id = (
+ action_kwargs[k]["id"]
+ if v == "unilabos_msgs/Resource"
+ else action_kwargs[k][0]["id"]
+ )
r.with_children = True
response = await self._resource_clients["resource_get"].call_async(r)
current_resources.extend(response.resources)
@@ -591,7 +663,19 @@ class BaseROS2DeviceNode(Node, Generic[T]):
if asyncio.iscoroutinefunction(ACTION):
try:
self.lab_logger().info(f"异步执行动作 {ACTION}")
- future = ROS2DeviceNode.run_async_func(ACTION, **action_kwargs)
+ future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
+
+ def _handle_future_exception(fut):
+ nonlocal execution_error, execution_success, action_return_value
+ try:
+ action_return_value = fut.result()
+ execution_success = True
+ except Exception as e:
+ execution_error = traceback.format_exc()
+ error(f"异步任务 {ACTION.__name__} 报错了")
+ error(traceback.format_exc())
+
+ future.add_done_callback(_handle_future_exception)
except Exception as e:
self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}")
raise e
@@ -600,9 +684,12 @@ class BaseROS2DeviceNode(Node, Generic[T]):
future = self._executor.submit(ACTION, **action_kwargs)
def _handle_future_exception(fut):
+ nonlocal execution_error, execution_success, action_return_value
try:
- fut.result()
+ action_return_value = fut.result()
+ execution_success = True
except Exception as e:
+ execution_error = traceback.format_exc()
error(f"同步任务 {ACTION.__name__} 报错了")
error(traceback.format_exc())
@@ -659,16 +746,23 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.lab_logger().info(f"更新资源状态: {k}")
r = ResourceUpdate.Request()
# 仅当action_kwargs[k]不为None时尝试转换
- akv = action_kwargs[k]
+ akv = action_kwargs[k] # 已经是完成转换的物料了,只需要转换成ros msg Resource了
apv = action_paramtypes[k]
final_type = get_type_class(apv)
if final_type is None:
continue
try:
- r.resources = [
- convert_to_ros_msg(Resource, self.resource_tracker.root_resource(rs))
- for rs in convert_resources_from_type(akv, final_type) # type: ignore # FIXME # 考虑反查到最大的
- ]
+ seen = set()
+ unique_resources = []
+ for rs in akv:
+ res = self.resource_tracker.parent_resource(rs) # 获取 resource 对象
+ if id(res) not in seen:
+ seen.add(id(res))
+ converted_list = convert_resources_from_type([res], final_type)
+ unique_resources.extend([convert_to_ros_msg(Resource, converted) for converted in converted_list])
+
+ r.resources = unique_resources
+
response = await self._resource_clients["resource_update"].call_async(r)
self.lab_logger().debug(f"资源更新结果: {response}")
except Exception as e:
@@ -693,6 +787,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
for attr_name in result_msg_types.keys():
if attr_name in ["success", "reached_goal"]:
setattr(result_msg, attr_name, True)
+ elif attr_name == "return_info":
+ setattr(result_msg, attr_name, serialize_result_info(execution_error, execution_success, action_return_value))
self.lab_logger().info(f"动作 {action_name} 完成并返回结果")
return result_msg
@@ -738,8 +834,8 @@ class ROS2DeviceNode:
return cls._loop
@classmethod
- def run_async_func(cls, func, **kwargs):
- return run_async_func(func, loop=cls._loop, **kwargs)
+ def run_async_func(cls, func, trace_error=True, **kwargs):
+ return run_async_func(func, loop=cls._loop, trace_error=trace_error, **kwargs)
@property
def driver_instance(self):
@@ -791,7 +887,11 @@ class ROS2DeviceNode:
self.resource_tracker = DeviceNodeResourceTracker()
# use_pylabrobot_creator 使用 cls的包路径检测
- use_pylabrobot_creator = driver_class.__module__.startswith("pylabrobot") or driver_class.__name__ == "LiquidHandlerAbstract"
+ use_pylabrobot_creator = (
+ driver_class.__module__.startswith("pylabrobot")
+ or driver_class.__name__ == "LiquidHandlerAbstract"
+ or driver_class.__name__ == "LiquidHandlerBiomek"
+ )
# TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建
# 创建设备类实例
@@ -830,6 +930,12 @@ class ROS2DeviceNode:
)
self._ros_node: BaseROS2DeviceNode
self._ros_node.lab_logger().info(f"初始化完成 {self._ros_node.uuid} {self.driver_is_ros}")
+ self.driver_instance._ros_node = self._ros_node # type: ignore
+ if hasattr(self.driver_instance, "post_init"):
+ try:
+ self.driver_instance.post_init(self._ros_node) # type: ignore
+ except Exception as e:
+ self._ros_node.lab_logger().error(f"设备后初始化失败: {e}")
def _start_loop(self):
def run_event_loop():
diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py
index 732e8bbd..712e9b3a 100644
--- a/unilabos/ros/nodes/presets/host_node.py
+++ b/unilabos/ros/nodes/presets/host_node.py
@@ -151,7 +151,7 @@ class HostNode(BaseROS2DeviceNode):
mqtt_client.publish_registry(device_info["id"], device_info)
for resource_info in lab_registry.obtain_registry_resource_info():
mqtt_client.publish_registry(resource_info["id"], resource_info)
-
+ time.sleep(1) # 等待MQTT连接稳定
# 首次发现网络中的设备
self._discover_devices()
@@ -203,8 +203,12 @@ class HostNode(BaseROS2DeviceNode):
try:
for bridge in self.bridges:
if hasattr(bridge, "resource_add"):
- self.lab_logger().info("[Host Node-Resource] Adding resources to bridge.")
- resource_add_res = bridge.resource_add(add_schema(resource_with_parent_name))
+ resource_start_time = time.time()
+ resource_add_res = bridge.resource_add(add_schema(resource_with_parent_name), True)
+ resource_end_time = time.time()
+ self.lab_logger().info(
+ f"[Host Node-Resource] 物料上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
+ )
except Exception as ex:
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
self.lab_logger().error(traceback.format_exc())
@@ -338,6 +342,7 @@ class HostNode(BaseROS2DeviceNode):
bind_locations: list[Point],
other_calling_params: list[str],
):
+ responses = []
for resource, device_id, bind_parent_id, bind_location, other_calling_param in zip(
resources, device_ids, bind_parent_ids, bind_locations, other_calling_params
):
@@ -363,8 +368,8 @@ class HostNode(BaseROS2DeviceNode):
ensure_ascii=False,
)
response = sclient.call(request)
- pass
- pass
+ responses.append(response)
+ return responses
def create_resource(
self,
@@ -376,7 +381,7 @@ class HostNode(BaseROS2DeviceNode):
liquid_input_slot: list[int],
liquid_type: list[str],
liquid_volume: list[int],
- slot_on_deck: int,
+ slot_on_deck: str,
):
init_new_res = initialize_resource(
{
@@ -610,13 +615,21 @@ class HostNode(BaseROS2DeviceNode):
"""获取结果回调"""
result_msg = future.result().result
result_data = convert_from_ros_msg(result_msg)
+ status = "success"
+ try:
+ ret = json.loads(result_data.get("return_info", "{}")) # 确保返回信息是有效的JSON
+ suc = ret.get("suc", False)
+ if not suc:
+ status = "failed"
+ except json.JSONDecodeError:
+ status = "failed"
self.lab_logger().info(f"[Host Node] Result for {action_id} ({uuid_str}): success")
self.lab_logger().debug(f"[Host Node] Result data: {result_data}")
if uuid_str:
for bridge in self.bridges:
if hasattr(bridge, "publish_job_status"):
- bridge.publish_job_status(result_data, uuid_str, "success")
+ bridge.publish_job_status(result_data, uuid_str, status, result_data.get("return_info", "{}"))
def cancel_goal(self, goal_uuid: str) -> None:
"""取消目标"""
@@ -856,7 +869,6 @@ class HostNode(BaseROS2DeviceNode):
测试网络延迟的action实现
通过5次ping-pong机制校对时间误差并计算实际延迟
"""
- import time
import uuid as uuid_module
self.lab_logger().info("=" * 60)
diff --git a/unilabos/ros/nodes/presets/protocol_node.py b/unilabos/ros/nodes/presets/protocol_node.py
index c4dfd083..23e08d0d 100644
--- a/unilabos/ros/nodes/presets/protocol_node.py
+++ b/unilabos/ros/nodes/presets/protocol_node.py
@@ -1,6 +1,7 @@
import time
import asyncio
import traceback
+from types import MethodType
from typing import Union
import rclpy
@@ -87,7 +88,10 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
# 如果硬件接口是字符串,通过通信设备提供
if isinstance(name, str) and name in self.sub_devices:
+ communicate_device = self.sub_devices[name]
+ communicate_hardware_info = communicate_device.ros_node_instance._hardware_interface
self._setup_hardware_proxy(d, self.sub_devices[name], read, write)
+ self.lab_logger().info(f"\n通信代理:为子设备{device_id}\n 添加了{read}方法(来源:{name} {communicate_hardware_info['write']}) \n 添加了{write}方法(来源:{name} {communicate_hardware_info['read']})")
def _setup_protocol_names(self, protocol_type):
# 处理协议类型
@@ -241,20 +245,23 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
def _setup_hardware_proxy(self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method):
"""为设备设置硬件接口代理"""
- extra_info = [getattr(device.driver_instance, info) for info in communication_device.ros_node_instance._hardware_interface.get("extra_info", [])]
- write_func = getattr(communication_device.ros_node_instance, communication_device.ros_node_instance._hardware_interface["write"])
- read_func = getattr(communication_device.ros_node_instance, communication_device.ros_node_instance._hardware_interface["read"])
+ # extra_info = [getattr(device.driver_instance, info) for info in communication_device.ros_node_instance._hardware_interface.get("extra_info", [])]
+ write_func = getattr(communication_device.driver_instance, communication_device.ros_node_instance._hardware_interface["write"])
+ read_func = getattr(communication_device.driver_instance, communication_device.ros_node_instance._hardware_interface["read"])
- def _read():
- return read_func(*extra_info)
+ def _read(*args, **kwargs):
+ return read_func(*args, **kwargs)
- def _write(command):
- return write_func(*extra_info, command)
+ def _write(*args, **kwargs):
+ return write_func(*args, **kwargs)
if read_method:
- setattr(device.driver_instance, read_method, _read)
+ bound_read = MethodType(_read, device.driver_instance)
+ setattr(device.driver_instance, read_method, bound_read)
+
if write_method:
- setattr(device.driver_instance, write_method, _write)
+ bound_write = MethodType(_write, device.driver_instance)
+ setattr(device.driver_instance, write_method, bound_write)
async def _update_resources(self, goal, protocol_kwargs):
diff --git a/unilabos/ros/nodes/presets/resource_mesh_manager.py b/unilabos/ros/nodes/presets/resource_mesh_manager.py
index c1495818..dadb49ab 100644
--- a/unilabos/ros/nodes/presets/resource_mesh_manager.py
+++ b/unilabos/ros/nodes/presets/resource_mesh_manager.py
@@ -91,8 +91,11 @@ class ResourceMeshManager(BaseROS2DeviceNode):
self.__collision_object_publisher = self.create_publisher(
CollisionObject, "/collision_object", 10
)
+ self.__planning_scene_publisher = self.create_publisher(
+ PlanningScene, "/planning_scene", 10
+ )
self.__attached_collision_object_publisher = self.create_publisher(
- AttachedCollisionObject, "/attached_collision_object", 10
+ AttachedCollisionObject, "/attached_collision_object", 0
)
# 创建一个Action Server用于修改resource_tf_dict
@@ -121,7 +124,8 @@ class ResourceMeshManager(BaseROS2DeviceNode):
"""检查move_group节点是否已初始化完成"""
# 获取当前可用的节点列表
-
+ if len(self.resource_tf_dict) == 0:
+ return
tf_ready = self.tf_buffer.can_transform("world", next(iter(self.resource_tf_dict.keys())), rclpy.time.Time(),rclpy.duration.Duration(seconds=2))
# if tf_ready:
@@ -129,8 +133,7 @@ class ResourceMeshManager(BaseROS2DeviceNode):
self.move_group_ready = True
self.publish_resource_tf()
self.add_resource_collision_meshes(self.resource_tf_dict)
-
- # time.sleep(1)
+
def add_resource_mesh_callback(self, goal_handle : ServerGoalHandle):
tf_update_msg = goal_handle.request
@@ -147,7 +150,7 @@ class ResourceMeshManager(BaseROS2DeviceNode):
"""刷新资源配置"""
registry = lab_registry
- resource_config = json.loads(resource_config_str)
+ resource_config = json.loads(resource_config_str.replace("'",'"'))
if resource_config['id'] in self.resource_config_dict:
self.get_logger().info(f'资源 {resource_config["id"]} 已存在')
@@ -158,7 +161,7 @@ class ResourceMeshManager(BaseROS2DeviceNode):
self.resource_model[resource_config['id']] = {
'mesh': f"{str(self.mesh_path)}/device_mesh/resources/{model_config['mesh']}",
'mesh_tf': model_config['mesh_tf']}
- if model_config['children_mesh'] is not None:
+ if 'children_mesh' in model_config.keys():
self.resource_model[f"{resource_config['id']}_"] = {
'mesh': f"{str(self.mesh_path)}/device_mesh/resources/{model_config['children_mesh']}",
'mesh_tf': model_config['children_mesh_tf']
@@ -187,7 +190,8 @@ class ResourceMeshManager(BaseROS2DeviceNode):
pass
elif parent is not None and resource_id in self.resource_model:
- parent_link = f"{self.resource_config_dict[parent]['parent']}_{parent}_device_link".replace("None","")
+ # parent_link = f"{self.resource_config_dict[parent]['parent']}_{parent}_device_link".replace("None_","")
+ parent_link = f"{parent}_device_link".replace("None_","")
else:
@@ -297,7 +301,7 @@ class ResourceMeshManager(BaseROS2DeviceNode):
"world",
resource_id,
rclpy.time.Time(seconds=0),
- rclpy.duration.Duration(seconds=5)
+ # rclpy.duration.Duration(seconds=5)
)
# 提取当前位姿信息
@@ -344,9 +348,7 @@ class ResourceMeshManager(BaseROS2DeviceNode):
self.resource_pose_publisher.publish(changed_poses_msg)
self.zero_count += 1
-
-
-
+
def _is_pose_equal(self, pose1, pose2, tolerance=1e-7):
"""
比较两个位姿是否相等(考虑浮点数精度)
@@ -386,14 +388,24 @@ class ResourceMeshManager(BaseROS2DeviceNode):
self.__planning_scene = self._get_planning_scene_service.call(
GetPlanningScene.Request()
).scene
-
- for resource_id, target_parent in cmd_dict.items():
+ self.__planning_scene.is_diff = True
+ planning_scene = PlanningScene()
+ planning_scene.is_diff = True
+ planning_scene.robot_state.is_diff = True
+ # time_start = self.get_clock().now()
+ time_start = rclpy.time.Time(seconds=0)
+ count = 0
+ for resource_id, target_parent in cmd_dict.items():
+ parent_id = target_parent
+ if target_parent == '__trash':
+ parent_id = 'world'
# 获取从resource_id到target_parent的转换
transform = self.tf_buffer.lookup_transform(
- target_parent,
+ parent_id,
resource_id,
- rclpy.time.Time(seconds=0)
+ time_start,
+ timeout=rclpy.duration.Duration(seconds=10)
)
# 提取转换中的位置和旋转信息
@@ -411,26 +423,62 @@ class ResourceMeshManager(BaseROS2DeviceNode):
}
self.resource_tf_dict[resource_id] = {
- "parent": target_parent,
+ "parent": parent_id,
"position": position,
"rotation": rotation
}
+
# self.attach_collision_object(id=resource_id,link_name=target_parent)
- collision_object = AttachedCollisionObject(
+ # time.sleep(0.02)
+ operation_attach = CollisionObject.ADD
+ operation_world = CollisionObject.REMOVE
+ if target_parent == 'world':
+ operation_attach = CollisionObject.REMOVE
+ operation_world = CollisionObject.ADD
+ elif target_parent == '__trash':
+ operation_attach = CollisionObject.REMOVE
+
+ world_object = CollisionObject(
id=resource_id,
- link_name=target_parent,
+ operation=operation_world
+ )
+ if target_parent != '__trash':
+ planning_scene.world.collision_objects.append(world_object)
+
+
+ collision_object = AttachedCollisionObject(
object=CollisionObject(
id=resource_id,
- operation=CollisionObject.ADD
+ operation=operation_attach
)
)
-
- self.__planning_scene.robot_state.attached_collision_objects.append(collision_object)
+ if target_parent != 'world' and target_parent != '__trash':
+ collision_object.link_name = target_parent
+ planning_scene.robot_state.attached_collision_objects.append(collision_object)
+
+ count += 1
+
+ if count > 30:
+ req = ApplyPlanningScene.Request()
+ req.scene = planning_scene
+ self.publish_resource_tf()
+ self._apply_planning_scene_service.call(req)
+ self.__planning_scene_publisher.publish(planning_scene)
+ count = 0
+
+ planning_scene = PlanningScene()
+ planning_scene.is_diff = True
+ planning_scene.robot_state.is_diff = True
+
req = ApplyPlanningScene.Request()
- req.scene = self.__planning_scene
- self._apply_planning_scene_service.call_async(req)
+ req.scene = planning_scene
self.publish_resource_tf()
+ self._apply_planning_scene_service.call(req)
+ self.__planning_scene_publisher.publish(planning_scene)
+
+ # self.__collision_object_publisher.publish(CollisionObject())
+
except Exception as e:
self.get_logger().error(f"更新资源TF字典失败: {e}")
@@ -440,18 +488,22 @@ class ResourceMeshManager(BaseROS2DeviceNode):
return SendCmd.Result(success=True)
+
def add_resource_collision_meshes(self,resource_tf_dict:dict):
"""
遍历资源配置字典,为每个在resource_model中有对应模型的资源添加碰撞网格
该方法检查每个资源ID是否在self.resource_model中有对应的3D模型文件路径,
- 如果有,则调用add_collision_mesh方法将其添加到碰撞环境中。
+
"""
self.get_logger().info('开始添加资源碰撞网格')
self.__planning_scene = self._get_planning_scene_service.call(
GetPlanningScene.Request()
).scene
+ planning_scene = PlanningScene()
+ planning_scene.is_diff = True
+ count = 0
for resource_id, tf_info in resource_tf_dict.items():
if resource_id in self.resource_model:
@@ -479,7 +531,8 @@ class ResourceMeshManager(BaseROS2DeviceNode):
quat_xyzw=q,
frame_id=resource_id
)
- self.__planning_scene.world.collision_objects.append(collision_object)
+ count += 1
+ planning_scene.world.collision_objects.append(collision_object)
elif f"{tf_info['parent']}_" in self.resource_model:
# 获取资源的父级框架ID
id_ = f"{tf_info['parent']}_"
@@ -507,14 +560,26 @@ class ResourceMeshManager(BaseROS2DeviceNode):
quat_xyzw=q,
frame_id=resource_id
)
+ count += 1
+ planning_scene.world.collision_objects.append(collision_object)
- self.__planning_scene.world.collision_objects.append(collision_object)
+ if count > 30:
+ req = ApplyPlanningScene.Request()
+ req.scene = planning_scene
+ self.publish_resource_tf()
+ self._apply_planning_scene_service.call(req)
+ self.__planning_scene_publisher.publish(planning_scene)
+ count = 0
+
+ planning_scene = PlanningScene()
+ planning_scene.is_diff = True
req = ApplyPlanningScene.Request()
- req.scene = self.__planning_scene
- self._apply_planning_scene_service.call_async(req)
-
-
+ req.scene = planning_scene
+ self.publish_resource_tf()
+ self._apply_planning_scene_service.call(req)
+ self.__planning_scene_publisher.publish(planning_scene)
+
self.get_logger().info('资源碰撞网格添加完成')
@@ -959,9 +1024,6 @@ class ResourceMeshManager(BaseROS2DeviceNode):
Attach collision object to the robot.
"""
- if link_name is None:
- link_name = self.__end_effector_name
-
msg = AttachedCollisionObject(
object=CollisionObject(id=id, operation=CollisionObject.ADD)
)
diff --git a/unilabos/ros/nodes/resource_tracker.py b/unilabos/ros/nodes/resource_tracker.py
index 04b54373..ff1a7797 100644
--- a/unilabos/ros/nodes/resource_tracker.py
+++ b/unilabos/ros/nodes/resource_tracker.py
@@ -1,3 +1,5 @@
+from typing import List, Tuple, Any
+
from unilabos.utils.log import logger
@@ -5,12 +7,12 @@ class DeviceNodeResourceTracker(object):
def __init__(self):
self.resources = []
- self.root_resource2resource = {}
+ self.resource2parent_resource = {}
pass
- def root_resource(self, resource):
- if id(resource) in self.root_resource2resource:
- return self.root_resource2resource[id(resource)]
+ def parent_resource(self, resource):
+ if id(resource) in self.resource2parent_resource:
+ return self.resource2parent_resource[id(resource)]
else:
return resource
@@ -44,20 +46,21 @@ class DeviceNodeResourceTracker(object):
self.loop_find_resource(r, resource_cls_type, identifier_key, getattr(query_resource, identifier_key))
)
assert len(res_list) == 1, f"{query_resource} 找到多个资源,请检查资源是否唯一: {res_list}"
- self.root_resource2resource[id(query_resource)] = res_list[0]
+ self.resource2parent_resource[id(query_resource)] = res_list[0][0]
+ self.resource2parent_resource[id(res_list[0][1])] = res_list[0][0]
# 后续加入其他对比方式
- return res_list[0]
+ return res_list[0][1]
- def loop_find_resource(self, resource, target_resource_cls_type, identifier_key, compare_value):
+ def loop_find_resource(self, resource, target_resource_cls_type, identifier_key, compare_value, parent_res=None) -> List[Tuple[Any, Any]]:
res_list = []
# print(resource, target_resource_cls_type, identifier_key, compare_value)
children = getattr(resource, "children", [])
for child in children:
- res_list.extend(self.loop_find_resource(child, target_resource_cls_type, identifier_key, compare_value))
+ res_list.extend(self.loop_find_resource(child, target_resource_cls_type, identifier_key, compare_value, resource))
if target_resource_cls_type == type(resource) or target_resource_cls_type == dict:
if hasattr(resource, identifier_key):
if getattr(resource, identifier_key) == compare_value:
- res_list.append(resource)
+ res_list.append((parent_res, resource))
return res_list
def filter_find_list(self, res_list, compare_std_dict):
diff --git a/unilabos/ros/utils/driver_creator.py b/unilabos/ros/utils/driver_creator.py
index 1218725e..9f223f9f 100644
--- a/unilabos/ros/utils/driver_creator.py
+++ b/unilabos/ros/utils/driver_creator.py
@@ -105,40 +105,41 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
return nested_dict_to_list(resource), Resource
return resource, source_type
- def _process_resource_references(self, data: Any, to_dict=False) -> Any:
+ def _process_resource_references(self, data: Any, to_dict=False, states=None, prefix_path="") -> Any:
"""
递归处理资源引用,替换_resource_child_name对应的资源
Args:
data: 需要处理的数据,可能是字典、列表或其他类型
- to_dict: 转换成对应的实例,还是转换成对应的字典
+ to_dict: 是否返回字典形式的资源
+ states: 用于保存所有资源状态
+ prefix_path: 当前递归路径
Returns:
处理后的数据
"""
from pylabrobot.resources import Deck, Resource
+ if states is None:
+ states = {}
if isinstance(data, dict):
- # 检查是否包含资源引用
if "_resource_child_name" in data:
child_name = data["_resource_child_name"]
if child_name in self.children:
- # 找到了对应的资源
resource = self.children[child_name]
-
- # 检查是否需要转换资源类型
if "_resource_type" in data:
type_path = data["_resource_type"]
try:
- # 尝试导入指定的类型
target_type = import_manager.get_class(type_path)
contain_model = not issubclass(target_type, Deck)
resource, target_type = self._process_resource_mapping(resource, target_type)
- # 在截图中格式,是deserialize,所以这里要转成plr resource可deserialize的字典
- # 这样后面执行deserialize的时候能够正确反序列化对应的物料
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model)
+
+ # 使用 prefix_path 作为 key 存储资源状态
if to_dict:
- return resource_instance.serialize()
+ serialized = resource_instance.serialize()
+ states[prefix_path] = resource_instance.serialize_all_state()
+ return serialized
else:
self.resource_tracker.add_resource(resource_instance)
return resource_instance
@@ -151,18 +152,21 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
else:
logger.warning(f"找不到资源引用 '{child_name}',保持原值不变")
- # 递归处理字典的每个值
+ # 递归处理每个键值
result = {}
for key, value in data.items():
- result[key] = self._process_resource_references(value, to_dict)
+ new_prefix = f"{prefix_path}.{key}" if prefix_path else key
+ result[key] = self._process_resource_references(value, to_dict, states, new_prefix)
return result
- # 处理列表类型
elif isinstance(data, list):
- return [self._process_resource_references(item, to_dict) for item in data]
+ return [
+ self._process_resource_references(item, to_dict, states, f"{prefix_path}[{i}]")
+ for i, item in enumerate(data)
+ ]
- # 其他类型直接返回
- return data
+ else:
+ return data
def create_instance(self, data: Dict[str, Any]) -> Optional[T]:
"""
@@ -187,10 +191,18 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
# 首先处理资源引用
- processed_data = self._process_resource_references(data, to_dict=True)
+ states = {}
+ processed_data = self._process_resource_references(data, to_dict=True, states=states)
try:
self.device_instance = deserialize_method(**processed_data)
+ all_states = self.device_instance.serialize_all_state()
+ for k, v in states.items():
+ logger.debug(f"PyLabRobot反序列化设置状态:{k}")
+ for kk, vv in all_states.items():
+ if kk not in v:
+ v[kk] = vv
+ self.device_instance.deck.load_all_state(v)
self.resource_tracker.add_resource(self.device_instance)
self.post_create()
return self.device_instance # type: ignore
@@ -225,6 +237,10 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
if hasattr(self.device_instance, "setup") and asyncio.iscoroutinefunction(getattr(self.device_instance, "setup")):
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
def done_cb(*args):
+ from pylabrobot.resources import set_volume_tracking
+ # from pylabrobot.resources import set_tip_tracking
+ set_volume_tracking(enabled=True)
+ # set_tip_tracking(enabled=True) # 序列化tip_spot has为False
logger.debug(f"PyLabRobot设备实例 {self.device_instance} 设置完成")
from unilabos.config.config import BasicConfig
if BasicConfig.vis_2d_enable:
diff --git a/unilabos/utils/async_util.py b/unilabos/utils/async_util.py
index ce97f5a1..0f50a730 100644
--- a/unilabos/utils/async_util.py
+++ b/unilabos/utils/async_util.py
@@ -5,7 +5,7 @@ from asyncio import get_event_loop
from unilabos.utils.log import error
-def run_async_func(func, *, loop=None, **kwargs):
+def run_async_func(func, *, loop=None, trace_error=True, **kwargs):
if loop is None:
loop = get_event_loop()
@@ -17,5 +17,6 @@ def run_async_func(func, *, loop=None, **kwargs):
error(traceback.format_exc())
future = asyncio.run_coroutine_threadsafe(func(**kwargs), loop)
- future.add_done_callback(_handle_future_exception)
- return future
\ No newline at end of file
+ if trace_error:
+ future.add_done_callback(_handle_future_exception)
+ return future
diff --git a/unilabos/utils/type_check.py b/unilabos/utils/type_check.py
index 7366652b..578eb93d 100644
--- a/unilabos/utils/type_check.py
+++ b/unilabos/utils/type_check.py
@@ -1,4 +1,4 @@
-import collections
+import collections.abc
import json
from typing import get_origin, get_args
@@ -21,3 +21,46 @@ class TypeEncoder(json.JSONEncoder):
return str(obj)[8:-2]
return super().default(obj)
+
+class ResultInfoEncoder(json.JSONEncoder):
+ """专门用于处理任务执行结果信息的JSON编码器"""
+
+ def default(self, obj):
+ # 优先处理类型对象
+ if isinstance(obj, type):
+ return str(obj)[8:-2]
+
+ # 对于无法序列化的对象,统一转换为字符串
+ try:
+ # 尝试调用 __dict__ 或者其他序列化方法
+ if hasattr(obj, "__dict__"):
+ return obj.__dict__
+ elif hasattr(obj, "_asdict"): # namedtuple
+ return obj._asdict()
+ elif hasattr(obj, "to_dict"):
+ return obj.to_dict()
+ elif hasattr(obj, "dict"):
+ return obj.dict()
+ else:
+ # 如果都不行,转换为字符串
+ return str(obj)
+ except Exception:
+ # 如果转换失败,直接返回字符串表示
+ return str(obj)
+
+
+def serialize_result_info(error: str, suc: bool, return_value=None) -> str:
+ """
+ 序列化任务执行结果信息
+
+ Args:
+ error: 错误信息字符串
+ suc: 是否成功的布尔值
+ return_value: 返回值,可以是任何类型
+
+ Returns:
+ JSON字符串格式的结果信息
+ """
+ result_info = {"error": error, "suc": suc, "return_value": return_value}
+
+ return json.dumps(result_info, ensure_ascii=False, cls=ResultInfoEncoder)
diff --git a/unilabos_msgs/CMakeLists.txt b/unilabos_msgs/CMakeLists.txt
index 0cd6a1e3..b818cf00 100644
--- a/unilabos_msgs/CMakeLists.txt
+++ b/unilabos_msgs/CMakeLists.txt
@@ -29,6 +29,25 @@ set(action_files
"action/HeatChillStart.action"
"action/HeatChillStop.action"
+ "action/ProtocolCleanVessel.action"
+ "action/ProtocolDissolve.action"
+ "action/ProtocolFilterThrough.action"
+ "action/ProtocolRunColumn.action"
+ "action/ProtocolWait.action"
+ "action/ProtocolWashSolid.action"
+ "action/ProtocolFilter.action"
+
+ "action/ProtocolCentrifuge.action"
+ "action/ProtocolCrystallize.action"
+ "action/ProtocolDry.action"
+ "action/ProtocolPurge.action"
+ "action/ProtocolStartPurge.action"
+ "action/ProtocolStartStir.action"
+ "action/ProtocolStopPurge.action"
+ "action/ProtocolStopStir.action"
+ "action/ProtocolTransfer.action"
+
+ "action/LiquidHandlerProtocolCreation.action"
"action/LiquidHandlerAspirate.action"
"action/LiquidHandlerDiscardTips.action"
"action/LiquidHandlerDispense.action"
@@ -44,6 +63,11 @@ set(action_files
"action/LiquidHandlerStamp.action"
"action/LiquidHandlerTransfer.action"
+ "action/LiquidHandlerTransferBiomek.action"
+ "action/LiquidHandlerIncubateBiomek.action"
+ "action/LiquidHandlerMoveBiomek.action"
+ "action/LiquidHandlerOscillateBiomek.action"
+
"action/LiquidHandlerAdd.action"
"action/LiquidHandlerMix.action"
"action/LiquidHandlerMoveTo.action"
diff --git a/unilabos_msgs/action/AGVTransfer.action b/unilabos_msgs/action/AGVTransfer.action
index 06c8c8ef..0c301f47 100644
--- a/unilabos_msgs/action/AGVTransfer.action
+++ b/unilabos_msgs/action/AGVTransfer.action
@@ -4,6 +4,7 @@ string from_repo_position
Resource to_repo
string to_repo_position
---
+string return_info
bool success
---
string status
diff --git a/unilabos_msgs/action/Clean.action b/unilabos_msgs/action/Clean.action
index 093a0dad..8fb9be1e 100644
--- a/unilabos_msgs/action/Clean.action
+++ b/unilabos_msgs/action/Clean.action
@@ -5,6 +5,7 @@ float64 volume # Optional. Volume of solvent to clean vessel with.
float64 temp # Optional. Temperature to heat vessel to while cleaning.
int32 repeats # Optional. Number of cleaning cycles to perform.
---
+string return_info
bool success
---
string status
diff --git a/unilabos_msgs/action/EmptyIn.action b/unilabos_msgs/action/EmptyIn.action
index c44b70c0..e7f59d07 100644
--- a/unilabos_msgs/action/EmptyIn.action
+++ b/unilabos_msgs/action/EmptyIn.action
@@ -1,4 +1,4 @@
---
-
+string return_info
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/EvacuateAndRefill.action b/unilabos_msgs/action/EvacuateAndRefill.action
index ed138dd5..22ffc659 100644
--- a/unilabos_msgs/action/EvacuateAndRefill.action
+++ b/unilabos_msgs/action/EvacuateAndRefill.action
@@ -3,6 +3,7 @@ string vessel
string gas
int32 repeats
---
+string return_info
bool success
---
string status
diff --git a/unilabos_msgs/action/Evaporate.action b/unilabos_msgs/action/Evaporate.action
index 9638a9a8..45887f27 100644
--- a/unilabos_msgs/action/Evaporate.action
+++ b/unilabos_msgs/action/Evaporate.action
@@ -5,6 +5,7 @@ float64 temp
float64 time
float64 stir_speed
---
+string return_info
bool success
---
string status
diff --git a/unilabos_msgs/action/FloatSingleInput.action b/unilabos_msgs/action/FloatSingleInput.action
index 2542d31f..52feed71 100644
--- a/unilabos_msgs/action/FloatSingleInput.action
+++ b/unilabos_msgs/action/FloatSingleInput.action
@@ -1,4 +1,5 @@
float64 float_in
---
+string return_info
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/HeatChill.action b/unilabos_msgs/action/HeatChill.action
index 1c7f8411..87ebf526 100644
--- a/unilabos_msgs/action/HeatChill.action
+++ b/unilabos_msgs/action/HeatChill.action
@@ -6,6 +6,7 @@ bool stir
float64 stir_speed
string purpose
---
+string return_info
bool success
---
string status
\ No newline at end of file
diff --git a/unilabos_msgs/action/HeatChillStart.action b/unilabos_msgs/action/HeatChillStart.action
index f9286937..565bad1e 100644
--- a/unilabos_msgs/action/HeatChillStart.action
+++ b/unilabos_msgs/action/HeatChillStart.action
@@ -3,6 +3,7 @@ string vessel
float64 temp
string purpose
---
+string return_info
bool success
---
string status
\ No newline at end of file
diff --git a/unilabos_msgs/action/HeatChillStop.action b/unilabos_msgs/action/HeatChillStop.action
index 88fc0293..280ca154 100644
--- a/unilabos_msgs/action/HeatChillStop.action
+++ b/unilabos_msgs/action/HeatChillStop.action
@@ -1,6 +1,7 @@
# Organic
string vessel
---
+string return_info
bool success
---
string status
\ No newline at end of file
diff --git a/unilabos_msgs/action/IntSingleInput.action b/unilabos_msgs/action/IntSingleInput.action
index 0f8b7aaa..23aeec6f 100644
--- a/unilabos_msgs/action/IntSingleInput.action
+++ b/unilabos_msgs/action/IntSingleInput.action
@@ -1,4 +1,5 @@
int32 int_input
---
+string return_info
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerAdd.action b/unilabos_msgs/action/LiquidHandlerAdd.action
index 0611b276..a17a61c4 100644
--- a/unilabos_msgs/action/LiquidHandlerAdd.action
+++ b/unilabos_msgs/action/LiquidHandlerAdd.action
@@ -15,6 +15,7 @@ int32 mix_rate
float64 mix_liquid_height
string[] none_keys
---
+string return_info
bool success
---
# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerAspirate.action b/unilabos_msgs/action/LiquidHandlerAspirate.action
index 9ba17068..b8558617 100644
--- a/unilabos_msgs/action/LiquidHandlerAspirate.action
+++ b/unilabos_msgs/action/LiquidHandlerAspirate.action
@@ -7,5 +7,6 @@ float64[] liquid_height
float64[] blow_out_air_volume
string spread
---
+string return_info
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerDiscardTips.action b/unilabos_msgs/action/LiquidHandlerDiscardTips.action
index a7c6f8ae..c2d290d0 100644
--- a/unilabos_msgs/action/LiquidHandlerDiscardTips.action
+++ b/unilabos_msgs/action/LiquidHandlerDiscardTips.action
@@ -3,6 +3,7 @@
int32[] use_channels
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerDispense.action b/unilabos_msgs/action/LiquidHandlerDispense.action
index 73c4d0f4..e06e3753 100644
--- a/unilabos_msgs/action/LiquidHandlerDispense.action
+++ b/unilabos_msgs/action/LiquidHandlerDispense.action
@@ -8,6 +8,7 @@ int32[] blow_out_air_volume
string spread
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerDropTips.action b/unilabos_msgs/action/LiquidHandlerDropTips.action
index 76a5625b..46f7e493 100644
--- a/unilabos_msgs/action/LiquidHandlerDropTips.action
+++ b/unilabos_msgs/action/LiquidHandlerDropTips.action
@@ -6,6 +6,7 @@ geometry_msgs/Point[] offsets
bool allow_nonzero_volume
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerDropTips96.action b/unilabos_msgs/action/LiquidHandlerDropTips96.action
index b4b7dfcf..a32891f5 100644
--- a/unilabos_msgs/action/LiquidHandlerDropTips96.action
+++ b/unilabos_msgs/action/LiquidHandlerDropTips96.action
@@ -5,6 +5,7 @@ geometry_msgs/Point offset
bool allow_nonzero_volume
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerIncubateBiomek.action b/unilabos_msgs/action/LiquidHandlerIncubateBiomek.action
new file mode 100644
index 00000000..da9d7035
--- /dev/null
+++ b/unilabos_msgs/action/LiquidHandlerIncubateBiomek.action
@@ -0,0 +1,6 @@
+int32 time
+
+---
+string return_info
+bool success
+---
diff --git a/unilabos_msgs/action/LiquidHandlerMix.action b/unilabos_msgs/action/LiquidHandlerMix.action
index 81d1b71c..99abe939 100644
--- a/unilabos_msgs/action/LiquidHandlerMix.action
+++ b/unilabos_msgs/action/LiquidHandlerMix.action
@@ -6,6 +6,7 @@ geometry_msgs/Point[] offsets
float64 mix_rate
string[] none_keys
---
+string return_info
bool success
---
# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerMoveBiomek.action b/unilabos_msgs/action/LiquidHandlerMoveBiomek.action
new file mode 100644
index 00000000..9c75206f
--- /dev/null
+++ b/unilabos_msgs/action/LiquidHandlerMoveBiomek.action
@@ -0,0 +1,7 @@
+string sources
+string targets
+
+---
+string return_info
+bool success
+---
diff --git a/unilabos_msgs/action/LiquidHandlerMoveLid.action b/unilabos_msgs/action/LiquidHandlerMoveLid.action
index 41a51e58..0c5d7477 100644
--- a/unilabos_msgs/action/LiquidHandlerMoveLid.action
+++ b/unilabos_msgs/action/LiquidHandlerMoveLid.action
@@ -12,6 +12,7 @@ string put_direction
float64 pickup_distance_from_top
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerMovePlate.action b/unilabos_msgs/action/LiquidHandlerMovePlate.action
index ea7503a1..6ad283db 100644
--- a/unilabos_msgs/action/LiquidHandlerMovePlate.action
+++ b/unilabos_msgs/action/LiquidHandlerMovePlate.action
@@ -13,6 +13,7 @@ string put_direction
float64 pickup_distance_from_top
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerMoveResource.action b/unilabos_msgs/action/LiquidHandlerMoveResource.action
index aaffa968..776d6cee 100644
--- a/unilabos_msgs/action/LiquidHandlerMoveResource.action
+++ b/unilabos_msgs/action/LiquidHandlerMoveResource.action
@@ -12,6 +12,7 @@ string get_direction
string put_direction
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerMoveTo.action b/unilabos_msgs/action/LiquidHandlerMoveTo.action
index 740d0fc6..0a9e1fe2 100644
--- a/unilabos_msgs/action/LiquidHandlerMoveTo.action
+++ b/unilabos_msgs/action/LiquidHandlerMoveTo.action
@@ -2,6 +2,7 @@ Resource well
float64 dis_to_top
int32 channel
---
+string return_info
bool success
---
# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerOscillateBiomek.action b/unilabos_msgs/action/LiquidHandlerOscillateBiomek.action
new file mode 100644
index 00000000..b07eb76d
--- /dev/null
+++ b/unilabos_msgs/action/LiquidHandlerOscillateBiomek.action
@@ -0,0 +1,7 @@
+int32 rpm
+int32 time
+
+---
+string return_info
+bool success
+---
diff --git a/unilabos_msgs/action/LiquidHandlerPickUpTips.action b/unilabos_msgs/action/LiquidHandlerPickUpTips.action
index 096bf17e..89287d91 100644
--- a/unilabos_msgs/action/LiquidHandlerPickUpTips.action
+++ b/unilabos_msgs/action/LiquidHandlerPickUpTips.action
@@ -5,6 +5,7 @@ int32[] use_channels
geometry_msgs/Point[] offsets
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerPickUpTips96.action b/unilabos_msgs/action/LiquidHandlerPickUpTips96.action
index 761349a1..63a60b4a 100644
--- a/unilabos_msgs/action/LiquidHandlerPickUpTips96.action
+++ b/unilabos_msgs/action/LiquidHandlerPickUpTips96.action
@@ -4,6 +4,7 @@ Resource tip_rack
geometry_msgs/Point offset
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerProtocolCreation.action b/unilabos_msgs/action/LiquidHandlerProtocolCreation.action
new file mode 100644
index 00000000..c63525a5
--- /dev/null
+++ b/unilabos_msgs/action/LiquidHandlerProtocolCreation.action
@@ -0,0 +1,10 @@
+string protocol_name
+string protocol_description
+string protocol_version
+string protocol_author
+string protocol_date
+string protocol_type
+string[] none_keys
+---
+string return_info
+---
diff --git a/unilabos_msgs/action/LiquidHandlerRemove.action b/unilabos_msgs/action/LiquidHandlerRemove.action
index e6b43c53..2b2656e5 100644
--- a/unilabos_msgs/action/LiquidHandlerRemove.action
+++ b/unilabos_msgs/action/LiquidHandlerRemove.action
@@ -12,6 +12,7 @@ bool is_96_well
float64[] top
string[] none_keys
---
+string return_info
bool success
---
# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerReturnTips.action b/unilabos_msgs/action/LiquidHandlerReturnTips.action
index 25d15965..0c3b366c 100644
--- a/unilabos_msgs/action/LiquidHandlerReturnTips.action
+++ b/unilabos_msgs/action/LiquidHandlerReturnTips.action
@@ -4,6 +4,7 @@ int32[] use_channels
bool allow_nonzero_volume
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerReturnTips96.action b/unilabos_msgs/action/LiquidHandlerReturnTips96.action
index fd20d712..da670e5f 100644
--- a/unilabos_msgs/action/LiquidHandlerReturnTips96.action
+++ b/unilabos_msgs/action/LiquidHandlerReturnTips96.action
@@ -3,6 +3,7 @@
bool allow_nonzero_volume
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerStamp.action b/unilabos_msgs/action/LiquidHandlerStamp.action
index a7db4bf2..383eee3e 100644
--- a/unilabos_msgs/action/LiquidHandlerStamp.action
+++ b/unilabos_msgs/action/LiquidHandlerStamp.action
@@ -7,6 +7,7 @@ float64 aspiration_flow_rate
float64 dispense_flow_rate
---
# 结果字段
+string return_info
bool success
---
# 反馈字段
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerTransfer.action b/unilabos_msgs/action/LiquidHandlerTransfer.action
index 39df59bb..6130f20c 100644
--- a/unilabos_msgs/action/LiquidHandlerTransfer.action
+++ b/unilabos_msgs/action/LiquidHandlerTransfer.action
@@ -20,6 +20,7 @@ float64 mix_liquid_height
int32[] delays
string[] none_keys
---
+string return_info
bool success
---
# 反馈
\ No newline at end of file
diff --git a/unilabos_msgs/action/LiquidHandlerTransferBiomek.action b/unilabos_msgs/action/LiquidHandlerTransferBiomek.action
new file mode 100644
index 00000000..65db2646
--- /dev/null
+++ b/unilabos_msgs/action/LiquidHandlerTransferBiomek.action
@@ -0,0 +1,11 @@
+string sources
+string targets
+string tip_rack
+float64 volume
+string aspirate_technique
+string dispense_technique
+
+---
+string return_info
+bool success
+---
diff --git a/unilabos_msgs/action/Point3DSeparateInput.action b/unilabos_msgs/action/Point3DSeparateInput.action
index 4e15e8f8..5d24125e 100644
--- a/unilabos_msgs/action/Point3DSeparateInput.action
+++ b/unilabos_msgs/action/Point3DSeparateInput.action
@@ -2,5 +2,6 @@ float64 x
float64 y
float64 z
---
+string return_info
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolAdd.action b/unilabos_msgs/action/ProtocolAdd.action
new file mode 100644
index 00000000..de06c6a0
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolAdd.action
@@ -0,0 +1,20 @@
+# Goal - 添加试剂的目标参数
+string vessel # 目标容器
+string reagent # 试剂名称
+float64 volume # 体积 (可选)
+float64 mass # 质量 (可选)
+string amount # 数量描述 (可选)
+float64 time # 添加时间 (可选)
+bool stir # 是否搅拌
+float64 stir_speed # 搅拌速度 (可选)
+bool viscous # 是否为粘性液体
+string purpose # 添加目的 (可选)
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolCentrifuge.action b/unilabos_msgs/action/ProtocolCentrifuge.action
new file mode 100644
index 00000000..356ccb9f
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolCentrifuge.action
@@ -0,0 +1,16 @@
+# Goal - 离心操作的目标参数
+string vessel # 离心容器
+float64 speed # 离心速度 (rpm)
+float64 time # 离心时间 (秒)
+float64 temp # 温度 (可选,摄氏度)
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 current_speed # 当前转速
+float64 current_temp # 当前温度
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolCleanVessel.action b/unilabos_msgs/action/ProtocolCleanVessel.action
new file mode 100644
index 00000000..cba232a7
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolCleanVessel.action
@@ -0,0 +1,12 @@
+string vessel # 要清洗的容器名称
+string solvent # 用于清洗容器的溶剂名称
+float64 volume # 清洗溶剂的体积,可选参数
+float64 temp # 清洗时的温度,可选参数
+int32 repeats # 清洗操作的重复次数,默认为 1
+---
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+string status # 当前状态描述
+float64 progress # 进度百分比 (0-100)
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolCrystallize.action b/unilabos_msgs/action/ProtocolCrystallize.action
new file mode 100644
index 00000000..50d26cf6
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolCrystallize.action
@@ -0,0 +1,14 @@
+# Goal - 结晶操作的目标参数
+string vessel # 结晶容器
+float64 ramp_time # 升温/降温时间 (可选,秒)
+float64 ramp_temp # 目标温度 (可选,摄氏度)
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 current_temp # 当前温度
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolDissolve.action b/unilabos_msgs/action/ProtocolDissolve.action
new file mode 100644
index 00000000..6b860d07
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolDissolve.action
@@ -0,0 +1,14 @@
+string vessel # 装有要溶解物质的容器名称
+string solvent # 用于溶解物质的溶剂名称
+float64 volume # 溶剂的体积,可选参数
+string amount # 要溶解物质的量,可选参数
+float64 temp # 溶解时的温度,可选参数
+float64 time # 溶解的时间,可选参数
+float64 stir_speed # 搅拌速度,可选参数
+---
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+string status # 当前状态描述
+float64 progress # 进度百分比 (0-100)
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolDry.action b/unilabos_msgs/action/ProtocolDry.action
new file mode 100644
index 00000000..5692ef28
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolDry.action
@@ -0,0 +1,17 @@
+# Goal - 干燥操作的目标参数
+string vessel # 干燥容器
+float64 time # 干燥时间 (可选,秒)
+float64 pressure # 压力 (可选,Pa)
+float64 temp # 温度 (可选,摄氏度)
+bool continue_heatchill # 是否继续加热冷却
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 current_temp # 当前温度
+float64 current_pressure # 当前压力
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolFilter.action b/unilabos_msgs/action/ProtocolFilter.action
new file mode 100644
index 00000000..564df1a5
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolFilter.action
@@ -0,0 +1,19 @@
+# Goal - 过滤操作的目标参数
+string vessel # 过滤容器
+string filtrate_vessel # 滤液容器 (可选)
+bool stir # 是否搅拌
+float64 stir_speed # 搅拌速度 (可选)
+float64 temp # 温度 (可选,摄氏度)
+bool continue_heatchill # 是否继续加热冷却
+float64 volume # 过滤体积 (可选)
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 current_temp # 当前温度
+float64 filtered_volume # 已过滤体积
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolFilterThrough.action b/unilabos_msgs/action/ProtocolFilterThrough.action
new file mode 100644
index 00000000..dbabd129
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolFilterThrough.action
@@ -0,0 +1,14 @@
+string from_vessel # 源容器的名称,即物质起始所在的容器
+string to_vessel # 目标容器的名称,物质过滤后要到达的容器
+string filter_through # 过滤时所通过的介质,如滤纸、柱子等
+string eluting_solvent # 洗脱溶剂的名称,可选参数
+float64 eluting_volume # 洗脱溶剂的体积,可选参数
+int32 eluting_repeats # 洗脱操作的重复次数,默认为 0
+float64 residence_time # 物质在过滤介质中的停留时间,可选参数
+---
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+string status # 当前状态描述
+float64 progress # 进度百分比 (0-100)
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolPurge.action b/unilabos_msgs/action/ProtocolPurge.action
new file mode 100644
index 00000000..00d76b31
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolPurge.action
@@ -0,0 +1,17 @@
+# Goal - 清洗/吹扫操作的目标参数
+string vessel # 清洗容器
+string gas # 清洗气体 (可选)
+float64 time # 清洗时间 (可选,秒)
+float64 pressure # 压力 (可选,Pa)
+float64 flow_rate # 流速 (可选,mL/min)
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 current_pressure # 当前压力
+float64 current_flow_rate # 当前流速
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolRunColumn.action b/unilabos_msgs/action/ProtocolRunColumn.action
new file mode 100644
index 00000000..3fba948a
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolRunColumn.action
@@ -0,0 +1,10 @@
+string from_vessel # 源容器的名称,即样品起始所在的容器
+string to_vessel # 目标容器的名称,分离后的样品要到达的容器
+string column # 所使用的柱子的名称
+---
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+string status # 当前状态描述
+float64 progress # 进度百分比 (0-100)
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolStartPurge.action b/unilabos_msgs/action/ProtocolStartPurge.action
new file mode 100644
index 00000000..f5500a61
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolStartPurge.action
@@ -0,0 +1,16 @@
+# Goal - 启动清洗/吹扫操作的目标参数
+string vessel # 清洗容器
+string gas # 清洗气体 (可选)
+float64 pressure # 压力 (可选,Pa)
+float64 flow_rate # 流速 (可选,mL/min)
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 current_pressure # 当前压力
+float64 current_flow_rate # 当前流速
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolStartStir.action b/unilabos_msgs/action/ProtocolStartStir.action
new file mode 100644
index 00000000..534c9f31
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolStartStir.action
@@ -0,0 +1,14 @@
+# Goal - 启动搅拌操作的目标参数
+string vessel # 搅拌容器
+float64 stir_speed # 搅拌速度 (可选,rpm)
+string purpose # 搅拌目的 (可选)
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 current_speed # 当前搅拌速度
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolStopPurge.action b/unilabos_msgs/action/ProtocolStopPurge.action
new file mode 100644
index 00000000..b7db8913
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolStopPurge.action
@@ -0,0 +1,11 @@
+# Goal - 停止清洗/吹扫操作的目标参数
+string vessel # 清洗容器
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolStopStir.action b/unilabos_msgs/action/ProtocolStopStir.action
new file mode 100644
index 00000000..a3205987
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolStopStir.action
@@ -0,0 +1,11 @@
+# Goal - 停止搅拌操作的目标参数
+string vessel # 搅拌容器
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolTransfer.action b/unilabos_msgs/action/ProtocolTransfer.action
new file mode 100644
index 00000000..f31f9dde
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolTransfer.action
@@ -0,0 +1,20 @@
+string from_vessel # 源容器
+string to_vessel # 目标容器
+float64 volume # 转移体积 (可选)
+string amount # 数量描述 (可选)
+float64 time # 转移时间 (可选,秒)
+bool viscous # 是否为粘性液体
+string rinsing_solvent # 冲洗溶剂 (可选)
+float64 rinsing_volume # 冲洗体积 (可选)
+int32 rinsing_repeats # 冲洗重复次数
+bool solid # 是否涉及固体
+---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+# Feedback - 实时反馈
+float64 progress # 进度百分比 (0-100)
+float64 transferred_volume # 已转移体积
+string current_status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolWait.action b/unilabos_msgs/action/ProtocolWait.action
new file mode 100644
index 00000000..d4c49429
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolWait.action
@@ -0,0 +1,9 @@
+int32 time # 等待时间(秒)
+---
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+string status # 当前状态描述
+float64 progress # 进度百分比 (0-100)
+int32 remaining_time # 剩余等待时间(秒)
\ No newline at end of file
diff --git a/unilabos_msgs/action/ProtocolWashSolid.action b/unilabos_msgs/action/ProtocolWashSolid.action
new file mode 100644
index 00000000..cb57e5cc
--- /dev/null
+++ b/unilabos_msgs/action/ProtocolWashSolid.action
@@ -0,0 +1,16 @@
+string vessel # 装有固体物质的容器名称
+string solvent # 用于清洗固体的溶剂名称
+float64 volume # 清洗溶剂的体积
+string filtrate_vessel # 滤液要收集到的容器名称,可选参数
+float64 temp # 清洗时的温度,可选参数
+bool stir # 是否在清洗过程中搅拌,默认为 False
+float64 stir_speed # 搅拌速度,可选参数
+float64 time # 清洗的时间,可选参数
+int32 repeats # 清洗操作的重复次数,默认为 1
+---
+bool success # 操作是否成功
+string message # 结果消息
+string return_info
+---
+string status # 当前状态描述
+float64 progress # 进度百分比 (0-100)
\ No newline at end of file
diff --git a/unilabos_msgs/action/PumpTransfer.action b/unilabos_msgs/action/PumpTransfer.action
index bbe6cb1e..69d22b6c 100644
--- a/unilabos_msgs/action/PumpTransfer.action
+++ b/unilabos_msgs/action/PumpTransfer.action
@@ -10,6 +10,7 @@ float64 rinsing_volume
int32 rinsing_repeats
bool solid
---
+string return_info
bool success
---
string status
diff --git a/unilabos_msgs/action/ResourceCreateFromOuter.action b/unilabos_msgs/action/ResourceCreateFromOuter.action
index e0eeb1c7..57330758 100644
--- a/unilabos_msgs/action/ResourceCreateFromOuter.action
+++ b/unilabos_msgs/action/ResourceCreateFromOuter.action
@@ -4,5 +4,6 @@ string[] bind_parent_ids
geometry_msgs/Point[] bind_locations
string[] other_calling_params
---
+string return_info
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/ResourceCreateFromOuterEasy.action b/unilabos_msgs/action/ResourceCreateFromOuterEasy.action
index cc832a71..420602eb 100644
--- a/unilabos_msgs/action/ResourceCreateFromOuterEasy.action
+++ b/unilabos_msgs/action/ResourceCreateFromOuterEasy.action
@@ -6,7 +6,8 @@ geometry_msgs/Point bind_locations
int32[] liquid_input_slot
string[] liquid_type
float32[] liquid_volume
-int32 slot_on_deck
+string slot_on_deck
---
+string return_info
bool success
---
diff --git a/unilabos_msgs/action/SendCmd.action b/unilabos_msgs/action/SendCmd.action
index cc883204..6f453f6e 100644
--- a/unilabos_msgs/action/SendCmd.action
+++ b/unilabos_msgs/action/SendCmd.action
@@ -1,6 +1,7 @@
# Simple
string command
---
+string return_info
bool success
---
string status
diff --git a/unilabos_msgs/action/Separate.action b/unilabos_msgs/action/Separate.action
index 502b420c..fe8976a7 100644
--- a/unilabos_msgs/action/Separate.action
+++ b/unilabos_msgs/action/Separate.action
@@ -13,6 +13,7 @@ float64 stir_time # Optional. Time stir for after adding solvent, before separat
float64 stir_speed # Optional. Speed to stir at after adding solvent, before separation of phases.
float64 settling_time # Optional. Time
---
+string return_info
bool success
---
string status
diff --git a/unilabos_msgs/action/SolidDispenseAddPowderTube.action b/unilabos_msgs/action/SolidDispenseAddPowderTube.action
index 674c4ffc..db0924e1 100644
--- a/unilabos_msgs/action/SolidDispenseAddPowderTube.action
+++ b/unilabos_msgs/action/SolidDispenseAddPowderTube.action
@@ -2,6 +2,7 @@ int32 powder_tube_number
string target_tube_position
float64 compound_mass
---
+string return_info
float64 actual_mass_mg
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/Stir.action b/unilabos_msgs/action/Stir.action
index defbed34..9542f9dc 100644
--- a/unilabos_msgs/action/Stir.action
+++ b/unilabos_msgs/action/Stir.action
@@ -3,6 +3,7 @@ float64 stir_time
float64 stir_speed
float64 settling_time
---
+string return_info
bool success
---
string status
\ No newline at end of file
diff --git a/unilabos_msgs/action/StrSingleInput.action b/unilabos_msgs/action/StrSingleInput.action
index bb762a58..bac365bc 100644
--- a/unilabos_msgs/action/StrSingleInput.action
+++ b/unilabos_msgs/action/StrSingleInput.action
@@ -1,4 +1,5 @@
string string
---
+string return_info
bool success
---
\ No newline at end of file
diff --git a/unilabos_msgs/action/WorkStationRun.action b/unilabos_msgs/action/WorkStationRun.action
index ea75668d..5ca9fd4e 100644
--- a/unilabos_msgs/action/WorkStationRun.action
+++ b/unilabos_msgs/action/WorkStationRun.action
@@ -3,6 +3,7 @@ string wf_name
string params
Resource resource
---
+string return_info
bool success
---
string status