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