mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 21:35:09 +00:00
Compare commits
10 Commits
31f59dc2aa
...
v0.10.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8beb80f0e7 | ||
|
|
09c1e8ca73 | ||
|
|
e7b6b8190a | ||
|
|
933e84bf13 | ||
|
|
0b56378287 | ||
|
|
51b47596ce | ||
|
|
42e8befec4 | ||
|
|
cbe7963ad0 | ||
|
|
280d83db57 | ||
|
|
4224008a92 |
89
.conda/recipe.yaml
Normal file
89
.conda/recipe.yaml
Normal file
@@ -0,0 +1,89 @@
|
||||
package:
|
||||
name: unilabos
|
||||
version: 0.10.3
|
||||
|
||||
source:
|
||||
path: ../unilabos
|
||||
target_directory: unilabos
|
||||
|
||||
build:
|
||||
python:
|
||||
entry_points:
|
||||
- unilab = unilabos.app.main:main
|
||||
- unilab-register = unilabos.app.register:main
|
||||
script:
|
||||
- set PIP_NO_INDEX=
|
||||
- if: win
|
||||
then:
|
||||
- copy %RECIPE_DIR%\..\MANIFEST.in %SRC_DIR%
|
||||
- copy %RECIPE_DIR%\..\setup.cfg %SRC_DIR%
|
||||
- copy %RECIPE_DIR%\..\setup.py %SRC_DIR%
|
||||
- call %PYTHON% -m pip install %SRC_DIR%
|
||||
- if: unix
|
||||
then:
|
||||
- cp $RECIPE_DIR/../MANIFEST.in $SRC_DIR
|
||||
- cp $RECIPE_DIR/../setup.cfg $SRC_DIR
|
||||
- cp $RECIPE_DIR/../setup.py $SRC_DIR
|
||||
- $PYTHON -m pip install $SRC_DIR
|
||||
|
||||
|
||||
requirements:
|
||||
host:
|
||||
- python ==3.11.11
|
||||
- pip
|
||||
- setuptools
|
||||
run:
|
||||
- conda-forge::python ==3.11.11
|
||||
- compilers
|
||||
- cmake
|
||||
- ninja
|
||||
- if: unix
|
||||
then:
|
||||
- make
|
||||
- sphinx
|
||||
- sphinx_rtd_theme
|
||||
- numpy
|
||||
- scipy
|
||||
- pandas
|
||||
- networkx
|
||||
- matplotlib
|
||||
- pint
|
||||
- pyserial
|
||||
- pyusb
|
||||
- pylibftdi
|
||||
- pymodbus
|
||||
- python-can
|
||||
- pyvisa
|
||||
- opencv
|
||||
- pydantic
|
||||
- fastapi
|
||||
- uvicorn
|
||||
- gradio
|
||||
- flask
|
||||
- websocket
|
||||
- ipython
|
||||
- jupyter
|
||||
- jupyros
|
||||
- colcon-common-extensions
|
||||
- robostack-staging::ros-humble-desktop-full
|
||||
- robostack-staging::ros-humble-control-msgs
|
||||
- robostack-staging::ros-humble-sensor-msgs
|
||||
- robostack-staging::ros-humble-trajectory-msgs
|
||||
- ros-humble-navigation2
|
||||
- ros-humble-ros2-control
|
||||
- ros-humble-robot-state-publisher
|
||||
- ros-humble-joint-state-publisher
|
||||
- ros-humble-rosbridge-server
|
||||
- ros-humble-cv-bridge
|
||||
- ros-humble-tf2
|
||||
- ros-humble-moveit
|
||||
- ros-humble-moveit-servo
|
||||
- ros-humble-simulation
|
||||
- ros-humble-tf-transformations
|
||||
- transforms3d
|
||||
- uni-lab::ros-humble-unilabos-msgs
|
||||
|
||||
about:
|
||||
repository: https://github.com/dptech-corp/Uni-Lab-OS
|
||||
license: GPL-3.0
|
||||
description: "Uni-Lab-OS"
|
||||
9
.conda/scripts/post-link.bat
Normal file
9
.conda/scripts/post-link.bat
Normal file
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM upgrade pip
|
||||
"%PREFIX%\python.exe" -m pip install --upgrade pip
|
||||
|
||||
REM install extra deps
|
||||
"%PREFIX%\python.exe" -m pip install paho-mqtt opentrons_shared_data
|
||||
"%PREFIX%\python.exe" -m pip install git+https://github.com/Xuwznln/pylabrobot.git
|
||||
9
.conda/scripts/post-link.sh
Normal file
9
.conda/scripts/post-link.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euxo pipefail
|
||||
|
||||
# make sure pip is available
|
||||
"$PREFIX/bin/python" -m pip install --upgrade pip
|
||||
|
||||
# install extra deps
|
||||
"$PREFIX/bin/python" -m pip install paho-mqtt opentrons_shared_data
|
||||
"$PREFIX/bin/python" -m pip install git+https://github.com/Xuwznln/pylabrobot.git
|
||||
193
.github/workflows/multi-platform-build.yml
vendored
193
.github/workflows/multi-platform-build.yml
vendored
@@ -2,16 +2,21 @@ name: Multi-Platform Conda Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, dev ]
|
||||
tags: [ 'v*' ]
|
||||
branches: [main, dev]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
branches: [ main, dev ]
|
||||
branches: [main, dev]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
platforms:
|
||||
description: '选择构建平台 (逗号分隔): linux-64, osx-64, osx-arm64, win-64'
|
||||
required: false
|
||||
default: 'osx-arm64'
|
||||
upload_to_anaconda:
|
||||
description: '是否上传到Anaconda.org'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -19,18 +24,18 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux-64
|
||||
env_file: unilabos-linux-64.yaml
|
||||
- os: macos-13 # Intel
|
||||
platform: osx-64
|
||||
env_file: unilabos-osx-64.yaml
|
||||
- os: macos-latest # ARM64
|
||||
platform: osx-arm64
|
||||
env_file: unilabos-osx-arm64.yaml
|
||||
- os: windows-latest
|
||||
platform: win-64
|
||||
env_file: unilabos-win64.yaml
|
||||
- os: ubuntu-latest
|
||||
platform: linux-64
|
||||
env_file: unilabos-linux-64.yaml
|
||||
- os: macos-13 # Intel
|
||||
platform: osx-64
|
||||
env_file: unilabos-osx-64.yaml
|
||||
- os: macos-latest # ARM64
|
||||
platform: osx-arm64
|
||||
env_file: unilabos-osx-arm64.yaml
|
||||
- os: windows-latest
|
||||
platform: win-64
|
||||
env_file: unilabos-win64.yaml
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -39,94 +44,88 @@ jobs:
|
||||
shell: bash -l {0}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check if platform should be built
|
||||
id: should_build
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Check if platform should be built
|
||||
id: should_build
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup Miniconda
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: conda-incubator/setup-miniconda@v3
|
||||
with:
|
||||
miniconda-version: "latest"
|
||||
channels: conda-forge,robostack-staging,defaults
|
||||
channel-priority: strict
|
||||
activate-environment: build-env
|
||||
auto-activate-base: false
|
||||
auto-update-conda: false
|
||||
show-channel-urls: true
|
||||
- name: Setup Miniconda
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: conda-incubator/setup-miniconda@v3
|
||||
with:
|
||||
miniconda-version: 'latest'
|
||||
channels: conda-forge,robostack-staging,defaults
|
||||
channel-priority: strict
|
||||
activate-environment: build-env
|
||||
auto-activate-base: false
|
||||
auto-update-conda: false
|
||||
show-channel-urls: true
|
||||
|
||||
- name: Install boa and build tools
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda install -c conda-forge boa conda-build
|
||||
- name: Install rattler-build and anaconda-client
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda install -c conda-forge rattler-build anaconda-client
|
||||
|
||||
- name: Show environment info
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda info
|
||||
conda list | grep -E "(boa|conda-build)"
|
||||
echo "Platform: ${{ matrix.platform }}"
|
||||
echo "OS: ${{ matrix.os }}"
|
||||
- name: Show environment info
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda info
|
||||
conda list | grep -E "(rattler-build|anaconda-client)"
|
||||
echo "Platform: ${{ matrix.platform }}"
|
||||
echo "OS: ${{ matrix.os }}"
|
||||
|
||||
- name: Build conda package
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
if [[ "${{ matrix.platform }}" == "osx-arm64" ]]; then
|
||||
boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs
|
||||
else
|
||||
boa build -m ./recipes/conda_build_config.yaml ./recipes/ros-humble-unilabos-msgs
|
||||
fi
|
||||
- name: Build conda package
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
if [[ "${{ matrix.platform }}" == "osx-arm64" ]]; then
|
||||
rattler-build build -r ./recipes/msgs/recipe.yaml -c robostack -c robostack-staging -c conda-forge
|
||||
else
|
||||
rattler-build build -r ./recipes/msgs/recipe.yaml -c robostack -c robostack-staging -c conda-forge
|
||||
fi
|
||||
|
||||
- name: List built packages
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
echo "Built packages in conda-bld:"
|
||||
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" | head -10
|
||||
ls -la $CONDA_PREFIX/conda-bld/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
|
||||
ls -la $CONDA_PREFIX/conda-bld/noarch/ || echo "noarch directory not found"
|
||||
echo "CONDA_PREFIX: $CONDA_PREFIX"
|
||||
echo "Full path would be: $CONDA_PREFIX/conda-bld/**/*.tar.bz2"
|
||||
- name: List built packages
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
echo "Built packages in output directory:"
|
||||
find ./output -name "*.conda" | head -10
|
||||
ls -la ./output/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
|
||||
ls -la ./output/noarch/ || echo "noarch directory not found"
|
||||
echo "Output directory structure:"
|
||||
find ./output -type f -name "*.conda"
|
||||
|
||||
- name: Prepare artifacts for upload
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/conda-packages
|
||||
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} ${{ runner.temp }}/conda-packages/ \;
|
||||
echo "Copied files to temp directory:"
|
||||
ls -la ${{ runner.temp }}/conda-packages/
|
||||
- name: Prepare artifacts for upload
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
mkdir -p conda-packages-temp
|
||||
find ./output -name "*.conda" -exec cp {} conda-packages-temp/ \;
|
||||
echo "Copied files to temp directory:"
|
||||
ls -la conda-packages-temp/
|
||||
|
||||
- name: Upload conda package artifacts
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conda-package-${{ matrix.platform }}
|
||||
path: ${{ runner.temp }}/conda-packages
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
- name: Upload conda package artifacts
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conda-package-${{ matrix.platform }}
|
||||
path: conda-packages-temp
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
|
||||
- name: Create release assets (on tags)
|
||||
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} release-assets/ \;
|
||||
|
||||
- name: Upload to release
|
||||
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: release-assets/*
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload to Anaconda.org (unilab organization)
|
||||
if: steps.should_build.outputs.should_build == 'true' && github.event.inputs.upload_to_anaconda == 'true'
|
||||
run: |
|
||||
for package in $(find ./output -name "*.conda"); do
|
||||
echo "Uploading $package to unilab organization..."
|
||||
anaconda -t ${{ secrets.ANACONDA_API_TOKEN }} upload --user uni-lab --force "$package"
|
||||
done
|
||||
|
||||
124
.github/workflows/unilabos-conda-build.yml
vendored
Normal file
124
.github/workflows/unilabos-conda-build.yml
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
name: UniLabOS Conda Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
platforms:
|
||||
description: '选择构建平台 (逗号分隔): linux-64, osx-64, osx-arm64, win-64'
|
||||
required: false
|
||||
default: 'linux-64'
|
||||
upload_to_anaconda:
|
||||
description: '是否上传到Anaconda.org'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux-64
|
||||
- os: macos-13 # Intel
|
||||
platform: osx-64
|
||||
- os: macos-latest # ARM64
|
||||
platform: osx-arm64
|
||||
- os: windows-latest
|
||||
platform: win-64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash -l {0}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check if platform should be built
|
||||
id: should_build
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup Miniconda
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: conda-incubator/setup-miniconda@v3
|
||||
with:
|
||||
miniconda-version: 'latest'
|
||||
channels: conda-forge,robostack-staging,uni-lab,defaults
|
||||
channel-priority: strict
|
||||
activate-environment: build-env
|
||||
auto-activate-base: false
|
||||
auto-update-conda: false
|
||||
show-channel-urls: true
|
||||
|
||||
- name: Install rattler-build and anaconda-client
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda install -c conda-forge rattler-build anaconda-client
|
||||
|
||||
- name: Show environment info
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda info
|
||||
conda list | grep -E "(rattler-build|anaconda-client)"
|
||||
echo "Platform: ${{ matrix.platform }}"
|
||||
echo "OS: ${{ matrix.os }}"
|
||||
echo "Building UniLabOS package"
|
||||
|
||||
- name: Build conda package
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
rattler-build build -r .conda/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge
|
||||
|
||||
- name: List built packages
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
echo "Built packages in output directory:"
|
||||
find ./output -name "*.conda" | head -10
|
||||
ls -la ./output/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
|
||||
ls -la ./output/noarch/ || echo "noarch directory not found"
|
||||
echo "Output directory structure:"
|
||||
find ./output -type f -name "*.conda"
|
||||
|
||||
- name: Prepare artifacts for upload
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
mkdir -p conda-packages-temp
|
||||
find ./output -name "*.conda" -exec cp {} conda-packages-temp/ \;
|
||||
echo "Copied files to temp directory:"
|
||||
ls -la conda-packages-temp/
|
||||
|
||||
- name: Upload conda package artifacts
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conda-package-unilabos-${{ matrix.platform }}
|
||||
path: conda-packages-temp
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload to Anaconda.org (uni-lab organization)
|
||||
if: github.event.inputs.upload_to_anaconda == 'true'
|
||||
run: |
|
||||
for package in $(find ./output -name "*.conda"); do
|
||||
echo "Uploading $package to uni-lab organization..."
|
||||
anaconda -t ${{ secrets.ANACONDA_API_TOKEN }} upload --user uni-lab --force "$package"
|
||||
done
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
configs/
|
||||
temp/
|
||||
output/
|
||||
unilabos_data/
|
||||
## Python
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
@@ -239,4 +241,8 @@ unilabos/device_mesh/view_robot.rviz
|
||||
|
||||
|
||||
# Certs
|
||||
**/.certs
|
||||
**/.certs
|
||||
local_test2.py
|
||||
ros-humble-unilabos-msgs-0.9.13-h6403a04_5.tar.bz2
|
||||
*.bz2
|
||||
test_config.py
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
recursive-include unilabos/registry *.yaml
|
||||
recursive-include unilabos/app/web *.html
|
||||
recursive-include unilabos/app/web *.css
|
||||
recursive-include unilabos/app/web/static *
|
||||
recursive-include unilabos/app/web/templates *
|
||||
recursive-include unilabos/device_mesh/devices *
|
||||
recursive-include unilabos/device_mesh/resources *
|
||||
|
||||
20
README.md
20
README.md
@@ -34,30 +34,14 @@ Detailed documentation can be found at:
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Configure Conda Environment
|
||||
|
||||
Uni-Lab-OS recommends using `mamba` for environment management. Choose the appropriate environment file for your operating system:
|
||||
|
||||
```bash
|
||||
# Create new environment
|
||||
mamba env create -f unilabos-[YOUR_OS].yaml
|
||||
mamba activate unilab
|
||||
|
||||
# Or update existing environment
|
||||
# Where `[YOUR_OS]` can be `win64`, `linux-64`, `osx-64`, or `osx-arm64`.
|
||||
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.10-xxxxx.tar.bz2
|
||||
|
||||
# Install PyLabRobot and other prerequisites
|
||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||
cd plr_repo
|
||||
pip install .[opentrons]
|
||||
mamba create -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
|
||||
```
|
||||
|
||||
2. Install Uni-Lab-OS:
|
||||
## Install Dev Uni-Lab-OS
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
|
||||
18
README_zh.md
18
README_zh.md
@@ -40,24 +40,10 @@ Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适
|
||||
|
||||
```bash
|
||||
# 创建新环境
|
||||
mamba env create -f unilabos-[YOUR_OS].yaml
|
||||
mamba activate unilab
|
||||
|
||||
# 或更新现有环境
|
||||
# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
|
||||
conda env update --file unilabos-[YOUR_OS].yml -n 环境名
|
||||
|
||||
# 现阶段,需要安装 `unilabos_msgs` 包
|
||||
# 可以前往 Release 页面下载系统对应的包进行安装
|
||||
conda install ros-humble-unilabos-msgs-0.9.11-xxxxx.tar.bz2
|
||||
|
||||
# 安装PyLabRobot等前置
|
||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||
cd plr_repo
|
||||
pip install .[opentrons]
|
||||
mamba create -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
|
||||
```
|
||||
|
||||
2. 安装 Uni-Lab-OS:
|
||||
2. 安装开发版Uni-Lab-OS:
|
||||
|
||||
```bash
|
||||
# 克隆仓库
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
:: Generated by vinca http://github.com/RoboStack/vinca.
|
||||
:: DO NOT EDIT!
|
||||
setlocal EnableDelayedExpansion
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set "PYTHONPATH=%LIBRARY_PREFIX%\lib\site-packages;%SP_DIR%"
|
||||
|
||||
@@ -16,9 +17,11 @@ pushd build
|
||||
|
||||
:: try to fix long paths issues by using default generator
|
||||
set "CMAKE_GENERATOR=Visual Studio %VS_MAJOR% %VS_YEAR%"
|
||||
set "SP_DIR_FORWARDSLASHES=%SP_DIR:\=/%"
|
||||
|
||||
set PYTHON="%PREFIX%\python.exe"
|
||||
set PYTHON=%PYTHON:\=/%
|
||||
set SP_DIR="..\Lib\site-packages"
|
||||
set SP_DIR=%SP_DIR:\=/%
|
||||
|
||||
cmake ^
|
||||
-G "%CMAKE_GENERATOR%" ^
|
||||
@@ -32,10 +35,10 @@ cmake ^
|
||||
-DBUILD_SHARED_LIBS=ON ^
|
||||
-DBUILD_TESTING=OFF ^
|
||||
-DCMAKE_OBJECT_PATH_MAX=255 ^
|
||||
-DPYTHON_INSTALL_DIR=%SP_DIR_FORWARDSLASHES% ^
|
||||
-DPYTHON_INSTALL_DIR=%SP_DIR% ^
|
||||
--compile-no-warning-as-error ^
|
||||
%SRC_DIR%\%PKG_NAME%\src\work
|
||||
%SRC_DIR%\src
|
||||
if errorlevel 1 exit 1
|
||||
|
||||
cmake --build . --config Release --target install
|
||||
if errorlevel 1 exit 1
|
||||
cmake --build . --config Release --target install -j8
|
||||
if errorlevel 1 exit 1
|
||||
@@ -24,7 +24,7 @@ echo "USING PKG_CONFIG_EXECUTABLE=${PKG_CONFIG_EXECUTABLE}"
|
||||
export ROS_PYTHON_VERSION=`$PYTHON_EXECUTABLE -c "import sys; print('%i.%i' % (sys.version_info[0:2]))"`
|
||||
echo "Using Python ${ROS_PYTHON_VERSION}"
|
||||
# Fix up SP_DIR which for some reason might contain a path to a wrong Python version
|
||||
FIXED_SP_DIR=$(echo $SP_DIR | sed -E "s/python[0-9]+\.[0-9]+/python$ROS_PYTHON_VERSION/")
|
||||
FIXED_SP_DIR=$($PYTHON_EXECUTABLE -c "import site; print(site.getsitepackages()[0])")
|
||||
echo "Using site-package dir ${FIXED_SP_DIR}"
|
||||
|
||||
# see https://github.com/conda-forge/cross-python-feedstock/issues/24
|
||||
@@ -56,7 +56,6 @@ cmake \
|
||||
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
|
||||
-DPython_EXECUTABLE=$PYTHON_EXECUTABLE \
|
||||
-DPython3_EXECUTABLE=$PYTHON_EXECUTABLE \
|
||||
-DPython3_FIND_STRATEGY=LOCATION \
|
||||
-DPKG_CONFIG_EXECUTABLE=$PKG_CONFIG_EXECUTABLE \
|
||||
-DPYTHON_INSTALL_DIR=$FIXED_SP_DIR \
|
||||
-DSETUPTOOLS_DEB_LAYOUT=OFF \
|
||||
@@ -66,6 +65,6 @@ cmake \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=$OSX_DEPLOYMENT_TARGET \
|
||||
--compile-no-warning-as-error \
|
||||
$SRC_DIR/$PKG_NAME/src/work
|
||||
$SRC_DIR/src
|
||||
|
||||
cmake --build . --config Release --target install
|
||||
cmake --build . --config Release --target install -j8
|
||||
76
recipes/msgs/recipe.yaml
Normal file
76
recipes/msgs/recipe.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
package:
|
||||
name: ros-humble-unilabos-msgs
|
||||
version: 0.10.3
|
||||
source:
|
||||
path: ../../unilabos_msgs
|
||||
target_directory: src
|
||||
|
||||
build:
|
||||
script:
|
||||
- if: win
|
||||
then:
|
||||
- copy %RECIPE_DIR%\bld_ament_cmake.bat %SRC_DIR%
|
||||
- call %SRC_DIR%\bld_ament_cmake.bat
|
||||
- if: unix
|
||||
then:
|
||||
- cp $RECIPE_DIR/build_ament_cmake.sh $SRC_DIR
|
||||
- bash $SRC_DIR/build_ament_cmake.sh
|
||||
|
||||
about:
|
||||
repository: https://github.com/dptech-corp/Uni-Lab-OS
|
||||
license: BSD-3-Clause
|
||||
description: "ros-humble-unilabos-msgs is a package that provides message definitions for Uni-Lab-OS."
|
||||
|
||||
requirements:
|
||||
build:
|
||||
- ${{ compiler('cxx') }}
|
||||
- ${{ compiler('c') }}
|
||||
- python ==3.11.11
|
||||
- numpy
|
||||
- if: build_platform != target_platform
|
||||
then:
|
||||
- pkg-config
|
||||
- cross-python_${{ target_platform }}
|
||||
- if: linux and x86_64
|
||||
then:
|
||||
- sysroot_linux-64 ==2.17
|
||||
- ninja
|
||||
- setuptools
|
||||
- cython
|
||||
- cmake
|
||||
- if: unix
|
||||
then:
|
||||
- make
|
||||
- coreutils
|
||||
- if: osx
|
||||
then:
|
||||
- tapi
|
||||
- if: win
|
||||
then:
|
||||
- vs2022_win-64
|
||||
host:
|
||||
- numpy
|
||||
- pip
|
||||
- if: build_platform == target_platform
|
||||
then:
|
||||
- pkg-config
|
||||
- robostack-staging::ros-humble-action-msgs
|
||||
- robostack-staging::ros-humble-ament-cmake
|
||||
- robostack-staging::ros-humble-ament-lint-auto
|
||||
- robostack-staging::ros-humble-ament-lint-common
|
||||
- robostack-staging::ros-humble-ros-environment
|
||||
- robostack-staging::ros-humble-ros-workspace
|
||||
- robostack-staging::ros-humble-rosidl-default-generators
|
||||
- robostack-staging::ros-humble-std-msgs
|
||||
- robostack-staging::ros-humble-geometry-msgs
|
||||
- robostack-staging::ros2-distro-mutex=0.6
|
||||
run:
|
||||
- robostack-staging::ros-humble-action-msgs
|
||||
- robostack-staging::ros-humble-ros-workspace
|
||||
- robostack-staging::ros-humble-rosidl-default-runtime
|
||||
- robostack-staging::ros-humble-std-msgs
|
||||
- robostack-staging::ros-humble-geometry-msgs
|
||||
- robostack-staging::ros2-distro-mutex=0.6
|
||||
- if: osx and x86_64
|
||||
then:
|
||||
- __osx >=${{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}
|
||||
@@ -1,61 +0,0 @@
|
||||
package:
|
||||
name: ros-humble-unilabos-msgs
|
||||
version: 0.9.11
|
||||
source:
|
||||
path: ../../unilabos_msgs
|
||||
folder: ros-humble-unilabos-msgs/src/work
|
||||
|
||||
build:
|
||||
script:
|
||||
sel(win): bld_ament_cmake.bat
|
||||
sel(unix): build_ament_cmake.sh
|
||||
number: 5
|
||||
about:
|
||||
home: https://www.ros.org/
|
||||
license: BSD-3-Clause
|
||||
summary: |
|
||||
Robot Operating System
|
||||
|
||||
extra:
|
||||
recipe-maintainers:
|
||||
- ros-forge
|
||||
|
||||
requirements:
|
||||
build:
|
||||
- "{{ compiler('cxx') }}"
|
||||
- "{{ compiler('c') }}"
|
||||
- sel(linux64): sysroot_linux-64 2.17
|
||||
- ninja
|
||||
- setuptools
|
||||
- sel(unix): make
|
||||
- sel(unix): coreutils
|
||||
- sel(osx): tapi
|
||||
- sel(build_platform != target_platform): pkg-config
|
||||
- cmake
|
||||
- cython
|
||||
- sel(win): vs2022_win-64
|
||||
- sel(build_platform != target_platform): python
|
||||
- sel(build_platform != target_platform): cross-python_{{ target_platform }}
|
||||
- sel(build_platform != target_platform): numpy
|
||||
host:
|
||||
- numpy
|
||||
- pip
|
||||
- sel(build_platform == target_platform): pkg-config
|
||||
- robostack-staging::ros-humble-action-msgs
|
||||
- robostack-staging::ros-humble-ament-cmake
|
||||
- robostack-staging::ros-humble-ament-lint-auto
|
||||
- robostack-staging::ros-humble-ament-lint-common
|
||||
- robostack-staging::ros-humble-ros-environment
|
||||
- robostack-staging::ros-humble-ros-workspace
|
||||
- robostack-staging::ros-humble-rosidl-default-generators
|
||||
- robostack-staging::ros-humble-std-msgs
|
||||
- robostack-staging::ros-humble-geometry-msgs
|
||||
- robostack-staging::ros2-distro-mutex=0.5.*
|
||||
run:
|
||||
- robostack-staging::ros-humble-action-msgs
|
||||
- robostack-staging::ros-humble-ros-workspace
|
||||
- robostack-staging::ros-humble-rosidl-default-runtime
|
||||
- robostack-staging::ros-humble-std-msgs
|
||||
- robostack-staging::ros-humble-geometry-msgs
|
||||
# - robostack-staging::ros2-distro-mutex=0.6.*
|
||||
- sel(osx and x86_64): __osx >={{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}
|
||||
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: unilabos
|
||||
version: "0.9.11"
|
||||
version: "0.10.3"
|
||||
|
||||
source:
|
||||
path: ../..
|
||||
|
||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
||||
|
||||
setup(
|
||||
name=package_name,
|
||||
version='0.9.11',
|
||||
version='0.10.3',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=['setuptools'],
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "OrganicSynthesisStation",
|
||||
"name": "有机化学流程综合测试工作站",
|
||||
"children": [
|
||||
"heater_1"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": [
|
||||
"AddProtocol",
|
||||
"TransferProtocol",
|
||||
"StartStirProtocol",
|
||||
"StopStirProtocol",
|
||||
"StirProtocol",
|
||||
"RunColumnProtocol",
|
||||
"CentrifugeProtocol",
|
||||
"FilterProtocol",
|
||||
"CleanVesselProtocol",
|
||||
"DissolveProtocol",
|
||||
"FilterThroughProtocol",
|
||||
"WashSolidProtocol",
|
||||
"SeparateProtocol",
|
||||
"EvaporateProtocol",
|
||||
"HeatChillProtocol",
|
||||
"HeatChillStartProtocol",
|
||||
"HeatChillStopProtocol",
|
||||
"EvacuateAndRefillProtocol",
|
||||
"PumpTransferProtocol",
|
||||
"AdjustPHProtocol",
|
||||
"ResetHandlingProtocol",
|
||||
"DryProtocol",
|
||||
"HydrogenateProtocol",
|
||||
"RecrystallizeProtocol"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "heater_1",
|
||||
"name": "加热器",
|
||||
"children": [],
|
||||
"parent": "OrganicSynthesisStation",
|
||||
"type": "device",
|
||||
"class": "virtual_heatchill",
|
||||
"position": {
|
||||
"x": 450,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_temp": 200.0,
|
||||
"min_temp": -20.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle",
|
||||
"current_temp": 25.0
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "PRCXI",
|
||||
"name": "PRCXI",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "liquid_handler.prcxi",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"deck": {
|
||||
"_resource_child_name": "deck",
|
||||
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck"
|
||||
},
|
||||
"host": "192.168.3.9",
|
||||
"port": 9999,
|
||||
"timeout": 10.0,
|
||||
"setup": false,
|
||||
"debug": true
|
||||
},
|
||||
"data": {},
|
||||
"children": [
|
||||
"deck"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "deck",
|
||||
"name": "deck",
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"rackT1",
|
||||
"plateT2",
|
||||
"plateT3",
|
||||
"rackT4",
|
||||
"plateT5",
|
||||
"plateT6"
|
||||
],
|
||||
"parent": "PRCXI",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Deck"
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "rackT1",
|
||||
"name": "rackT1",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||
"Code": "ZX-001-1000",
|
||||
"Name": "1000μL Tip头",
|
||||
"SummaryName": "1000μL Tip头",
|
||||
"PipetteHeight": 100,
|
||||
"materialEnum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT2",
|
||||
"name": "plateT2",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT3",
|
||||
"name": "plateT3",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rackT4",
|
||||
"name": "rackT4",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||
"Code": "ZX-001-1000",
|
||||
"Name": "1000μL Tip头",
|
||||
"SummaryName": "1000μL Tip头",
|
||||
"PipetteHeight": 100,
|
||||
"materialEnum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT5",
|
||||
"name": "plateT5",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT6",
|
||||
"name": "plateT6",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
21231
test/experiments/prcxi_9300.json
Normal file
21231
test/experiments/prcxi_9300.json
Normal file
File diff suppressed because it is too large
Load Diff
13567
test/experiments/prcxi_9320.json
Normal file
13567
test/experiments/prcxi_9320.json
Normal file
File diff suppressed because it is too large
Load Diff
13598
test/experiments/prcxi_9320_visual.json
Normal file
13598
test/experiments/prcxi_9320_visual.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,48 @@
|
||||
{
|
||||
"nodes": [
|
||||
|
||||
|
||||
{
|
||||
"id": "benyao",
|
||||
"name": "benyao",
|
||||
"children": [
|
||||
],
|
||||
"id": "arm_slider",
|
||||
"name": "arm_slider",
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "moveit.arm_slider",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
"x": -500,
|
||||
"y": 1000,
|
||||
"z": -100
|
||||
},
|
||||
"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]
|
||||
"hotel_1": [
|
||||
1.05,
|
||||
0.568,
|
||||
-1.0821,
|
||||
0.0,
|
||||
1.0821
|
||||
],
|
||||
"home": [
|
||||
0.865,
|
||||
0.09,
|
||||
0.8727,
|
||||
0.0,
|
||||
-0.8727
|
||||
]
|
||||
}
|
||||
},
|
||||
"device_config": {
|
||||
}
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": -1.5708,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"device_config": {}
|
||||
},
|
||||
"data": {
|
||||
}
|
||||
"data": {}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
name: unilab
|
||||
channels:
|
||||
- unilab
|
||||
- robostack
|
||||
- robostack-staging
|
||||
- conda-forge
|
||||
@@ -61,6 +62,8 @@ dependencies:
|
||||
- transforms3d
|
||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||
# ilab equipments
|
||||
# - ros-humble-unilabos-msgs
|
||||
- uni-lab::ros-humble-unilabos-msgs
|
||||
- pip:
|
||||
- paho-mqtt
|
||||
- paho-mqtt
|
||||
- opentrons_shared_data
|
||||
- git+https://github.com/Xuwznln/pylabrobot
|
||||
@@ -1,5 +1,6 @@
|
||||
name: unilab
|
||||
channels:
|
||||
- unilab
|
||||
- robostack
|
||||
- robostack-staging
|
||||
- conda-forge
|
||||
@@ -60,6 +61,8 @@ dependencies:
|
||||
- transforms3d
|
||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||
# ilab equipments
|
||||
# - ros-humble-unilabos-msgs
|
||||
- uni-lab::ros-humble-unilabos-msgs
|
||||
- pip:
|
||||
- paho-mqtt
|
||||
- paho-mqtt
|
||||
- opentrons_shared_data
|
||||
- git+https://github.com/Xuwznln/pylabrobot
|
||||
@@ -1,5 +1,6 @@
|
||||
name: unilab
|
||||
channels:
|
||||
- unilab
|
||||
- robostack
|
||||
- robostack-staging
|
||||
- conda-forge
|
||||
@@ -63,6 +64,8 @@ dependencies:
|
||||
- transforms3d
|
||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||
# ilab equipments
|
||||
# - ros-humble-unilabos-msgs
|
||||
- uni-lab::ros-humble-unilabos-msgs
|
||||
- pip:
|
||||
- paho-mqtt
|
||||
- paho-mqtt
|
||||
- opentrons_shared_data
|
||||
- git+https://github.com/Xuwznln/pylabrobot
|
||||
@@ -1,5 +1,6 @@
|
||||
name: unilab
|
||||
channels:
|
||||
- unilab
|
||||
- robostack
|
||||
- robostack-staging
|
||||
- conda-forge
|
||||
@@ -61,11 +62,13 @@ dependencies:
|
||||
- transforms3d
|
||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||
# ilab equipments
|
||||
# ros-humble-unilabos-msgs
|
||||
- uni-lab::ros-humble-unilabos-msgs
|
||||
# driver
|
||||
#- crcmod
|
||||
- pip:
|
||||
- paho-mqtt
|
||||
- opentrons_shared_data
|
||||
- git+https://github.com/Xuwznln/pylabrobot
|
||||
# driver
|
||||
#- ur-rtde # set PYTHONUTF8=1
|
||||
#- pyautogui
|
||||
|
||||
@@ -39,6 +39,7 @@ def job_add(req: JobAddReq) -> JobData:
|
||||
HostNode.get_instance().send_goal(req.device_id, action_type=action_type, action_name=action_name, action_kwargs=action_args, goal_uuid=req.job_id, server_info=req.server_info)
|
||||
except Exception as e:
|
||||
for bridge in HostNode.get_instance().bridges:
|
||||
traceback.print_exc()
|
||||
if hasattr(bridge, "publish_job_status"):
|
||||
bridge.publish_job_status({}, req.job_id, "failed", serialize_result_info(traceback.format_exc(), False, {}))
|
||||
return JobData(jobId=req.job_id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
@@ -10,7 +10,7 @@ from copy import deepcopy
|
||||
|
||||
import yaml
|
||||
|
||||
from unilabos.resources.graphio import tree_to_list, modify_to_backend_format
|
||||
from unilabos.resources.graphio import modify_to_backend_format
|
||||
|
||||
# 首先添加项目根目录到路径
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -18,31 +18,42 @@ unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
|
||||
if unilabos_dir not in sys.path:
|
||||
sys.path.append(unilabos_dir)
|
||||
|
||||
from unilabos.config.config import load_config, BasicConfig, _update_config_from_env
|
||||
from unilabos.config.config import load_config, BasicConfig
|
||||
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
||||
|
||||
|
||||
def load_config_from_file(config_path):
|
||||
def load_config_from_file(config_path, override_labid=None):
|
||||
if config_path is None:
|
||||
config_path = os.environ.get("UNILABOS.BASICCONFIG.CONFIG_PATH", None)
|
||||
config_path = os.environ.get("UNILABOS_BASICCONFIG_CONFIG_PATH", None)
|
||||
if config_path:
|
||||
if not os.path.exists(config_path):
|
||||
print_status(f"配置文件 {config_path} 不存在", "error")
|
||||
elif not config_path.endswith(".py"):
|
||||
print_status(f"配置文件 {config_path} 不是Python文件,必须以.py结尾", "error")
|
||||
else:
|
||||
load_config(config_path)
|
||||
load_config(config_path, override_labid)
|
||||
else:
|
||||
print_status(f"启动 UniLab-OS时,配置文件参数未正确传入 --config '{config_path}' 尝试本地配置...", "warning")
|
||||
load_config(config_path)
|
||||
load_config(config_path, override_labid)
|
||||
|
||||
|
||||
def convert_argv_dashes_to_underscores(args: argparse.ArgumentParser):
|
||||
# easier for user input, easier for dev search code
|
||||
option_strings = list(args._option_string_actions.keys())
|
||||
for i, arg in enumerate(sys.argv):
|
||||
for option_string in option_strings:
|
||||
if arg.startswith(option_string):
|
||||
new_arg = arg[:2] + arg[2 : len(option_string)].replace("-", "_") + arg[len(option_string) :]
|
||||
sys.argv[i] = new_arg
|
||||
break
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
|
||||
parser.add_argument("-g", "--graph", help="Physical setup graph.")
|
||||
parser.add_argument("-d", "--devices", help="Devices config file.")
|
||||
parser.add_argument("-r", "--resources", help="Resources config file.")
|
||||
# parser.add_argument("-d", "--devices", help="Devices config file.")
|
||||
# parser.add_argument("-r", "--resources", help="Resources config file.")
|
||||
parser.add_argument("-c", "--controllers", default=None, help="Controllers config file.")
|
||||
parser.add_argument(
|
||||
"--registry_path",
|
||||
@@ -51,6 +62,12 @@ def parse_args():
|
||||
action="append",
|
||||
help="Path to the registry",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--working_dir",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Path to the working directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backend",
|
||||
choices=["ros", "simple", "automancer"],
|
||||
@@ -92,12 +109,12 @@ def parse_args():
|
||||
)
|
||||
parser.add_argument(
|
||||
"--disable_browser",
|
||||
action='store_true',
|
||||
action="store_true",
|
||||
help="是否在启动时关闭信息页",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--2d_vis",
|
||||
action='store_true',
|
||||
action="store_true",
|
||||
help="是否在pylabrobot实例启动时,同时启动可视化",
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -106,20 +123,78 @@ def parse_args():
|
||||
default="disable",
|
||||
help="选择可视化工具: rviz, web",
|
||||
)
|
||||
return parser.parse_args()
|
||||
parser.add_argument(
|
||||
"--labid",
|
||||
type=str,
|
||||
default="",
|
||||
help="实验室唯一ID,也可通过环境变量 UNILABOS_MQCONFIG_LABID 设置或传入--config设置",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip_env_check",
|
||||
action="store_true",
|
||||
help="跳过启动时的环境依赖检查",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 解析命令行参数
|
||||
args = parse_args()
|
||||
args_dict = vars(args)
|
||||
convert_argv_dashes_to_underscores(args)
|
||||
args_dict = vars(args.parse_args())
|
||||
|
||||
# 环境检查 - 检查并自动安装必需的包 (可选)
|
||||
if not args_dict.get("skip_env_check", False):
|
||||
from unilabos.utils.environment_check import check_environment
|
||||
|
||||
print_status("正在进行环境依赖检查...", "info")
|
||||
if not check_environment(auto_install=True):
|
||||
print_status("环境检查失败,程序退出", "error")
|
||||
os._exit(1)
|
||||
else:
|
||||
print_status("跳过环境依赖检查", "warning")
|
||||
|
||||
# 加载配置文件,优先加载config,然后从env读取
|
||||
config_path = args_dict.get("config")
|
||||
load_config_from_file(config_path)
|
||||
if os.getcwd().endswith("unilabos_data"):
|
||||
working_dir = os.path.abspath(os.getcwd())
|
||||
else:
|
||||
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
|
||||
if args_dict.get("working_dir"):
|
||||
working_dir = args_dict.get("working_dir")
|
||||
if config_path and not os.path.exists(config_path):
|
||||
config_path = os.path.join(working_dir, "local_config.py")
|
||||
if not os.path.exists(config_path):
|
||||
print_status(
|
||||
f"当前工作目录 {working_dir} 未找到local_config.py,请通过 --config 传入 local_config.py 文件路径",
|
||||
"error",
|
||||
)
|
||||
os._exit(1)
|
||||
elif os.path.exists(working_dir) and os.path.exists(os.path.join(working_dir, "local_config.py")):
|
||||
config_path = os.path.join(working_dir, "local_config.py")
|
||||
elif not config_path and (
|
||||
not os.path.exists(working_dir) or not os.path.exists(os.path.join(working_dir, "local_config.py"))
|
||||
):
|
||||
print_status(f"未指定config路径,可通过 --config 传入 local_config.py 文件路径", "info")
|
||||
print_status(f"您是否为第一次使用?并将当前路径 {working_dir} 作为工作目录? (Y/n)", "info")
|
||||
if input() != "n":
|
||||
os.makedirs(working_dir, exist_ok=True)
|
||||
config_path = os.path.join(working_dir, "local_config.py")
|
||||
shutil.copy(
|
||||
os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "example_config.py"), config_path
|
||||
)
|
||||
print_status(f"已创建 local_config.py 路径: {config_path}", "info")
|
||||
print_status(f"请在文件夹中配置lab_id,放入下载的CA.crt、lab.crt、lab.key重新启动本程序", "info")
|
||||
os._exit(1)
|
||||
else:
|
||||
os._exit(1)
|
||||
# 加载配置文件
|
||||
print_status(f"当前工作目录为 {working_dir}", "info")
|
||||
load_config_from_file(config_path, args_dict["labid"])
|
||||
|
||||
# 设置BasicConfig参数
|
||||
BasicConfig.working_dir = working_dir
|
||||
BasicConfig.is_host_mode = not args_dict.get("without_host", False)
|
||||
BasicConfig.slave_no_host = args_dict.get("slave_no_host", False)
|
||||
BasicConfig.upload_registry = args_dict.get("upload_registry", False)
|
||||
@@ -146,30 +221,31 @@ def main():
|
||||
|
||||
# 注册表
|
||||
build_registry(args_dict["registry_path"])
|
||||
resource_edge_info = []
|
||||
devices_and_resources = None
|
||||
if args_dict["graph"] is not None:
|
||||
import unilabos.resources.graphio as graph_res
|
||||
if args_dict["graph"].endswith(".json"):
|
||||
graph, data = read_node_link_json(args_dict["graph"])
|
||||
if args_dict["graph"] is None:
|
||||
request_startup_json = http_client.request_startup_json()
|
||||
if not request_startup_json:
|
||||
print_status(
|
||||
"未指定设备加载文件路径,尝试从HTTP获取失败,请检查网络或者使用-g参数指定设备加载文件路径", "error"
|
||||
)
|
||||
os._exit(1)
|
||||
else:
|
||||
graph, data = read_graphml(args_dict["graph"])
|
||||
graph_res.physical_setup_graph = graph
|
||||
resource_edge_info = modify_to_backend_format(data["links"])
|
||||
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"] = list(devices_and_resources.values())
|
||||
args_dict["devices_config"] = dict_to_nested_dict(deepcopy(devices_and_resources), devices_only=False)
|
||||
args_dict["graph"] = graph_res.physical_setup_graph
|
||||
print_status("联网获取设备加载文件成功", "info")
|
||||
graph, data = read_node_link_json(request_startup_json)
|
||||
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"] = list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
|
||||
file_path = args_dict["graph"]
|
||||
if file_path.endswith(".json"):
|
||||
graph, data = read_node_link_json(file_path)
|
||||
else:
|
||||
graph, data = read_graphml(file_path)
|
||||
import unilabos.resources.graphio as graph_res
|
||||
|
||||
graph_res.physical_setup_graph = graph
|
||||
resource_edge_info = modify_to_backend_format(data["links"])
|
||||
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"] = list(devices_and_resources.values())
|
||||
args_dict["devices_config"] = dict_to_nested_dict(deepcopy(devices_and_resources), devices_only=False)
|
||||
args_dict["graph"] = graph_res.physical_setup_graph
|
||||
|
||||
print_status(f"{len(args_dict['resources_config'])} Resources loaded:", "info")
|
||||
for i in args_dict["resources_config"]:
|
||||
@@ -201,13 +277,22 @@ def main():
|
||||
if args_dict["visual"] != "disable":
|
||||
enable_rviz = args_dict["visual"] == "rviz"
|
||||
if devices_and_resources is not None:
|
||||
from unilabos.device_mesh.resource_visalization import ResourceVisualization # 此处开启后,logger会变更为INFO,有需要请调整
|
||||
resource_visualization = ResourceVisualization(devices_and_resources, args_dict["resources_config"] ,enable_rviz=enable_rviz)
|
||||
from unilabos.device_mesh.resource_visalization import (
|
||||
ResourceVisualization,
|
||||
) # 此处开启后,logger会变更为INFO,有需要请调整
|
||||
|
||||
resource_visualization = ResourceVisualization(
|
||||
devices_and_resources, args_dict["resources_config"], enable_rviz=enable_rviz
|
||||
)
|
||||
args_dict["resources_mesh_config"] = resource_visualization.resource_model
|
||||
start_backend(**args_dict)
|
||||
server_thread = threading.Thread(target=start_server, kwargs=dict(
|
||||
open_browser=not args_dict["disable_browser"], port=args_dict["port"],
|
||||
))
|
||||
server_thread = threading.Thread(
|
||||
target=start_server,
|
||||
kwargs=dict(
|
||||
open_browser=not args_dict["disable_browser"],
|
||||
port=args_dict["port"],
|
||||
),
|
||||
)
|
||||
server_thread.start()
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
resource_visualization.start()
|
||||
@@ -215,10 +300,16 @@ def main():
|
||||
time.sleep(1)
|
||||
else:
|
||||
start_backend(**args_dict)
|
||||
start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
|
||||
start_server(
|
||||
open_browser=not args_dict["disable_browser"],
|
||||
port=args_dict["port"],
|
||||
)
|
||||
else:
|
||||
start_backend(**args_dict)
|
||||
start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
|
||||
start_server(
|
||||
open_browser=not args_dict["disable_browser"],
|
||||
port=args_dict["port"],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -166,7 +166,7 @@ class MQTTClient:
|
||||
status = {"data": device_status.get(device_id, {}), "device_id": device_id, "timestamp": time.time()}
|
||||
address = f"labs/{MQConfig.lab_id}/devices/"
|
||||
self.client.publish(address, json.dumps(status), qos=2)
|
||||
logger.debug(f"Device status published: address: {address}, {status}")
|
||||
logger.info(f"Device {device_id} status published: address: {address}, {status}")
|
||||
|
||||
def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[str] = None):
|
||||
if self.mqtt_disable:
|
||||
|
||||
@@ -18,13 +18,27 @@ def register_devices_and_resources(mqtt_client, lab_registry):
|
||||
mqtt_client.publish_registry(device_info["id"], device_info, False)
|
||||
logger.debug(f"[UniLab Register] 注册设备: {device_info['id']}")
|
||||
|
||||
# 注册资源信息
|
||||
# # 注册资源信息
|
||||
# for resource_info in lab_registry.obtain_registry_resource_info():
|
||||
# mqtt_client.publish_registry(resource_info["id"], resource_info, False)
|
||||
# logger.debug(f"[UniLab Register] 注册资源: {resource_info['id']}")
|
||||
|
||||
# 注册资源信息 - 使用HTTP方式
|
||||
from unilabos.app.web.client import http_client
|
||||
|
||||
resources_to_register = {}
|
||||
for resource_info in lab_registry.obtain_registry_resource_info():
|
||||
mqtt_client.publish_registry(resource_info["id"], resource_info, False)
|
||||
logger.debug(f"[UniLab Register] 注册资源: {resource_info['id']}")
|
||||
|
||||
time.sleep(10)
|
||||
resources_to_register[resource_info["id"]] = resource_info
|
||||
logger.debug(f"[UniLab Register] 准备注册资源: {resource_info['id']}")
|
||||
|
||||
if resources_to_register:
|
||||
start_time = time.time()
|
||||
response = http_client.resource_registry(resources_to_register)
|
||||
cost_time = time.time() - start_time
|
||||
if response.status_code in [200, 201]:
|
||||
logger.info(f"[UniLab Register] 成功通过HTTP注册 {len(resources_to_register)} 个资源 {cost_time}ms")
|
||||
else:
|
||||
logger.error(f"[UniLab Register] HTTP注册资源失败: {response.status_code}, {response.text} {cost_time}ms")
|
||||
logger.info("[UniLab Register] 设备和资源注册完成.")
|
||||
|
||||
|
||||
@@ -53,11 +67,9 @@ def main():
|
||||
help="是否补全注册表",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
load_config_from_file(args.config)
|
||||
# 构建注册表
|
||||
build_registry(args.registry, args.complete_registry)
|
||||
load_config_from_file(args.config)
|
||||
|
||||
from unilabos.app.mq import mqtt_client
|
||||
|
||||
# 连接mqtt
|
||||
@@ -70,4 +82,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -3,12 +3,13 @@ HTTP客户端模块
|
||||
|
||||
提供与远程服务器通信的客户端功能,只有host需要用
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
import requests
|
||||
from unilabos.utils.log import info
|
||||
from unilabos.config.config import MQConfig, HTTPConfig
|
||||
from unilabos.config.config import MQConfig, HTTPConfig, BasicConfig
|
||||
from unilabos.utils import logger
|
||||
|
||||
|
||||
@@ -40,8 +41,9 @@ class HTTPClient:
|
||||
Returns:
|
||||
Response: API响应对象
|
||||
"""
|
||||
database_param = 1 if database_process_later else 0
|
||||
response = requests.post(
|
||||
f"{self.remote_addr}/lab/resource/edge/batch_create/?database_process_later={1 if database_process_later else 0}",
|
||||
f"{self.remote_addr}/lab/resource/edge/batch_create/?database_process_later={database_param}",
|
||||
json=resources,
|
||||
headers={"Authorization": f"lab {self.auth}"},
|
||||
timeout=100,
|
||||
@@ -149,6 +151,56 @@ class HTTPClient:
|
||||
)
|
||||
return response
|
||||
|
||||
def resource_registry(self, registry_data: Dict[str, Any]) -> requests.Response:
|
||||
"""
|
||||
注册资源到服务器
|
||||
|
||||
Args:
|
||||
registry_data: 注册表数据,格式为 {resource_id: resource_info}
|
||||
|
||||
Returns:
|
||||
Response: API响应对象
|
||||
"""
|
||||
response = requests.post(
|
||||
f"{self.remote_addr}/lab/registry/",
|
||||
json=registry_data,
|
||||
headers={"Authorization": f"lab {self.auth}"},
|
||||
timeout=30,
|
||||
)
|
||||
if response.status_code not in [200, 201]:
|
||||
logger.error(f"注册资源失败: {response.status_code}, {response.text}")
|
||||
return response
|
||||
|
||||
def request_startup_json(self) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
请求启动配置
|
||||
|
||||
Args:
|
||||
startup_json: 启动配置JSON数据
|
||||
|
||||
Returns:
|
||||
Response: API响应对象
|
||||
"""
|
||||
response = requests.get(
|
||||
f"{self.remote_addr}/lab/resource/graph_info/",
|
||||
headers={"Authorization": f"lab {self.auth}"},
|
||||
timeout=(3, 30),
|
||||
)
|
||||
if response.status_code != 200:
|
||||
logger.error(f"请求启动配置失败: {response.status_code}, {response.text}")
|
||||
else:
|
||||
try:
|
||||
with open(os.path.join(BasicConfig.working_dir, "startup_config.json"), "w", encoding="utf-8") as f:
|
||||
f.write(response.text)
|
||||
target_dict = json.loads(response.text)
|
||||
if "data" in target_dict:
|
||||
target_dict = target_dict["data"]
|
||||
return target_dict
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"解析启动配置JSON失败: {str(e.args)}\n响应内容: {response.text}")
|
||||
logger.error(f"响应内容: {response.text}")
|
||||
return None
|
||||
|
||||
|
||||
# 创建默认客户端实例
|
||||
http_client = HTTPClient()
|
||||
|
||||
@@ -311,7 +311,7 @@ def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
|
||||
def generate_add_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:现在接收字典类型的 vessel
|
||||
reagent: str,
|
||||
# 🔧 修复:所有参数都用 Union 类型,支持字符串和数值
|
||||
volume: Union[str, float] = 0.0,
|
||||
@@ -334,6 +334,7 @@ def generate_add_protocol(
|
||||
生成添加试剂协议 - 修复版
|
||||
|
||||
支持所有XDL参数和单位:
|
||||
- vessel: Resource类型字典,包含id字段
|
||||
- volume: "2.7 mL", "2.67 mL", "?" 或数值
|
||||
- mass: "19.3 g", "4.5 g" 或数值
|
||||
- time: "1 h", "20 min" 或数值(秒)
|
||||
@@ -343,11 +344,28 @@ def generate_add_protocol(
|
||||
- equiv: "1.1"
|
||||
- ratio: "?", "1:1"
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
# 🔧 修改:更新容器的液体体积(假设有 liquid_volume 字段)
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
if isinstance(vessel["data"]["liquid_volume"], list) and len(vessel["data"]["liquid_volume"]) > 0:
|
||||
vessel["data"]["liquid_volume"][0] -= parse_volume_input(volume)
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("🚀 开始生成添加试剂协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 vessel: '{vessel}'")
|
||||
debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 🧪 reagent: '{reagent}'")
|
||||
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
|
||||
@@ -363,18 +381,18 @@ def generate_add_protocol(
|
||||
|
||||
# === 参数验证 ===
|
||||
debug_print("🔍 步骤1: 参数验证...")
|
||||
action_sequence.append(create_action_log(f"开始添加试剂 '{reagent}' 到容器 '{vessel}'", "🎬"))
|
||||
action_sequence.append(create_action_log(f"开始添加试剂 '{reagent}' 到容器 '{vessel_id}'", "🎬"))
|
||||
|
||||
if not vessel:
|
||||
if not vessel or not vessel_id:
|
||||
debug_print("❌ vessel 参数不能为空")
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
if not reagent:
|
||||
debug_print("❌ reagent 参数不能为空")
|
||||
raise ValueError("reagent 参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
if vessel_id not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中")
|
||||
raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
|
||||
|
||||
debug_print("✅ 基本参数验证通过")
|
||||
|
||||
@@ -432,7 +450,7 @@ def generate_add_protocol(
|
||||
debug_print("🌪️ 准备启动搅拌...")
|
||||
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
|
||||
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
|
||||
if stirrer_id:
|
||||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
|
||||
|
||||
@@ -440,7 +458,7 @@ def generate_add_protocol(
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"准备添加固体 {reagent}"
|
||||
}
|
||||
@@ -454,7 +472,7 @@ def generate_add_protocol(
|
||||
|
||||
# 固体加样
|
||||
add_kwargs = {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"reagent": reagent,
|
||||
"purpose": purpose,
|
||||
"event": event,
|
||||
@@ -509,7 +527,7 @@ def generate_add_protocol(
|
||||
debug_print("🌪️ 准备启动搅拌...")
|
||||
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
|
||||
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
|
||||
if stirrer_id:
|
||||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
|
||||
|
||||
@@ -517,7 +535,7 @@ def generate_add_protocol(
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"准备添加液体 {reagent}"
|
||||
}
|
||||
@@ -555,7 +573,7 @@ def generate_add_protocol(
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=reagent_vessel,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
volume=final_volume,
|
||||
amount=amount,
|
||||
time=final_time,
|
||||
@@ -594,7 +612,7 @@ def generate_add_protocol(
|
||||
debug_print(f"📋 处理总结:")
|
||||
debug_print(f" 🧪 试剂: {reagent}")
|
||||
debug_print(f" {add_emoji} 添加类型: {add_type}")
|
||||
debug_print(f" 🥼 目标容器: {vessel}")
|
||||
debug_print(f" 🥼 目标容器: {vessel_id}")
|
||||
if is_liquid:
|
||||
debug_print(f" 📏 体积: {final_volume}mL")
|
||||
if is_solid:
|
||||
@@ -603,7 +621,7 @@ def generate_add_protocol(
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"试剂添加协议完成: {reagent} → {vessel}"
|
||||
summary_msg = f"试剂添加协议完成: {reagent} → {vessel_id}"
|
||||
if is_liquid:
|
||||
summary_msg += f" ({final_volume}mL)"
|
||||
if is_solid:
|
||||
@@ -614,11 +632,13 @@ def generate_add_protocol(
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
# 🔧 修改便捷函数的参数类型
|
||||
|
||||
def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
|
||||
def add_liquid_volume(G: nx.DiGraph, vessel: dict, reagent: str, volume: Union[str, float],
|
||||
time: Union[str, float] = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]:
|
||||
"""添加指定体积的液体试剂"""
|
||||
debug_print(f"💧 快速添加液体: {reagent} ({volume}) → {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"💧 快速添加液体: {reagent} ({volume}) → {vessel_id}")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
volume=volume,
|
||||
@@ -626,30 +646,33 @@ def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[st
|
||||
rate_spec=rate_spec
|
||||
)
|
||||
|
||||
def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
|
||||
def add_solid_mass(G: nx.DiGraph, vessel: dict, reagent: str, mass: Union[str, float],
|
||||
event: str = "") -> List[Dict[str, Any]]:
|
||||
"""添加指定质量的固体试剂"""
|
||||
debug_print(f"🧂 快速添加固体: {reagent} ({mass}) → {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🧂 快速添加固体: {reagent} ({mass}) → {vessel_id}")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
mass=mass,
|
||||
event=event
|
||||
)
|
||||
|
||||
def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
|
||||
def add_solid_moles(G: nx.DiGraph, vessel: dict, reagent: str, mol: str,
|
||||
event: str = "") -> List[Dict[str, Any]]:
|
||||
"""按摩尔数添加固体试剂"""
|
||||
debug_print(f"🧬 按摩尔数添加固体: {reagent} ({mol}) → {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🧬 按摩尔数添加固体: {reagent} ({mol}) → {vessel_id}")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
mol=mol,
|
||||
event=event
|
||||
)
|
||||
|
||||
def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
|
||||
def add_dropwise_liquid(G: nx.DiGraph, vessel: dict, reagent: str, volume: Union[str, float],
|
||||
time: Union[str, float] = "20 min", event: str = "") -> List[Dict[str, Any]]:
|
||||
"""滴加液体试剂"""
|
||||
debug_print(f"💧 滴加液体: {reagent} ({volume}) → {vessel} (用时: {time})")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"💧 滴加液体: {reagent} ({volume}) → {vessel_id} (用时: {time})")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
volume=volume,
|
||||
@@ -658,10 +681,11 @@ def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[
|
||||
event=event
|
||||
)
|
||||
|
||||
def add_portionwise_solid(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
|
||||
def add_portionwise_solid(G: nx.DiGraph, vessel: dict, reagent: str, mass: Union[str, float],
|
||||
time: Union[str, float] = "1 h", event: str = "") -> List[Dict[str, Any]]:
|
||||
"""分批添加固体试剂"""
|
||||
debug_print(f"🧂 分批添加固体: {reagent} ({mass}) → {vessel} (用时: {time})")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🧂 分批添加固体: {reagent} ({mass}) → {vessel_id} (用时: {time})")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
mass=mass,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import networkx as nx
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
from typing import List, Dict, Any, Union
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -216,7 +216,7 @@ def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume
|
||||
|
||||
def generate_adjust_ph_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel:Union[dict,str], # 🔧 修改:从字符串改为字典类型
|
||||
ph_value: float,
|
||||
reagent: str,
|
||||
**kwargs
|
||||
@@ -226,7 +226,7 @@ def generate_adjust_ph_protocol(
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为容器和设备
|
||||
vessel: 目标容器(需要调节pH的容器)
|
||||
vessel: 目标容器字典(需要调节pH的容器)
|
||||
ph_value: 目标pH值(从XDL传入)
|
||||
reagent: 酸碱试剂名称(从XDL传入)
|
||||
**kwargs: 其他可选参数,使用默认值
|
||||
@@ -235,10 +235,25 @@ def generate_adjust_ph_protocol(
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
if not vessel_id:
|
||||
debug_print(f"❌ vessel 参数无效,必须包含id字段或直接提供容器ID. vessel: {vessel}")
|
||||
raise ValueError("vessel 参数无效,必须包含id字段或直接提供容器ID")
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("🧪 开始生成pH调节协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 vessel: '{vessel}'")
|
||||
debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 📊 ph_value: {ph_value}")
|
||||
debug_print(f" 🧪 reagent: '{reagent}'")
|
||||
debug_print(f" 📦 kwargs: {kwargs}")
|
||||
@@ -262,14 +277,14 @@ def generate_adjust_ph_protocol(
|
||||
|
||||
# 开始处理
|
||||
action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"目标容器: {vessel}", "🥼"))
|
||||
action_sequence.append(create_action_log(f"目标容器: {vessel_id}", "🥼"))
|
||||
action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️"))
|
||||
|
||||
# 1. 验证目标容器存在
|
||||
debug_print(f"🔍 步骤1: 验证目标容器...")
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中")
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
if vessel_id not in G.nodes():
|
||||
debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中")
|
||||
raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
|
||||
|
||||
debug_print(f"✅ 目标容器验证通过")
|
||||
action_sequence.append(create_action_log("目标容器验证通过", "✅"))
|
||||
@@ -293,7 +308,7 @@ def generate_adjust_ph_protocol(
|
||||
action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮"))
|
||||
|
||||
# 获取目标容器的体积信息
|
||||
vessel_data = G.nodes[vessel].get('data', {})
|
||||
vessel_data = G.nodes[vessel_id].get('data', {})
|
||||
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL
|
||||
debug_print(f"📏 容器最大体积: {vessel_volume}mL")
|
||||
|
||||
@@ -310,13 +325,13 @@ def generate_adjust_ph_protocol(
|
||||
action_sequence.append(create_action_log("验证转移路径...", "🛤️"))
|
||||
|
||||
try:
|
||||
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
|
||||
path = nx.shortest_path(G, source=reagent_vessel, target=vessel_id)
|
||||
debug_print(f"✅ 找到路径: {' → '.join(path)}")
|
||||
action_sequence.append(create_action_log(f"找到转移路径: {' → '.join(path)}", "🛤️"))
|
||||
except nx.NetworkXNoPath:
|
||||
debug_print(f"❌ 无法找到转移路径")
|
||||
action_sequence.append(create_action_log("转移路径不存在", "❌"))
|
||||
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
|
||||
|
||||
# 5. 搅拌器设置
|
||||
debug_print(f"🔍 步骤5: 搅拌器设置...")
|
||||
@@ -325,7 +340,7 @@ def generate_adjust_ph_protocol(
|
||||
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
|
||||
|
||||
try:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id)
|
||||
|
||||
if stirrer_id:
|
||||
debug_print(f"✅ 找到搅拌器 {stirrer_id},启动搅拌")
|
||||
@@ -335,7 +350,7 @@ def generate_adjust_ph_protocol(
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"pH调节: 启动搅拌,准备添加 {reagent}"
|
||||
}
|
||||
@@ -373,7 +388,7 @@ def generate_adjust_ph_protocol(
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=reagent_vessel,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id,
|
||||
volume=volume,
|
||||
amount="",
|
||||
time=addition_time,
|
||||
@@ -390,6 +405,54 @@ def generate_adjust_ph_protocol(
|
||||
debug_print(f"✅ 泵协议生成完成,添加了 {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"试剂转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
# 🔧 修复体积运算 - 试剂添加成功后更新容器液体体积
|
||||
debug_print(f"🔧 更新容器液体体积...")
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
debug_print(f"📊 添加前容器体积: {current_volume}")
|
||||
|
||||
# 处理不同的体积数据格式
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
# 增加体积(添加试剂)
|
||||
vessel["data"]["liquid_volume"][0] += volume
|
||||
debug_print(f"📊 添加后容器体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{volume:.2f}mL)")
|
||||
else:
|
||||
# 如果列表为空,创建新的体积记录
|
||||
vessel["data"]["liquid_volume"] = [volume]
|
||||
debug_print(f"📊 初始化容器体积: {volume:.2f}mL")
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
# 直接数值类型
|
||||
vessel["data"]["liquid_volume"] += volume
|
||||
debug_print(f"📊 添加后容器体积: {vessel['data']['liquid_volume']:.2f}mL (+{volume:.2f}mL)")
|
||||
else:
|
||||
debug_print(f"⚠️ 未知的体积数据格式: {type(current_volume)}")
|
||||
# 创建新的体积记录
|
||||
vessel["data"]["liquid_volume"] = volume
|
||||
else:
|
||||
debug_print(f"📊 容器无液体体积数据,创建新记录: {volume:.2f}mL")
|
||||
# 确保vessel有data字段
|
||||
if "data" not in vessel:
|
||||
vessel["data"] = {}
|
||||
vessel["data"]["liquid_volume"] = volume
|
||||
|
||||
# 🔧 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
vessel_node_data = G.nodes[vessel_id].get('data', {})
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] += volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume
|
||||
|
||||
debug_print(f"✅ 图节点体积数据已更新")
|
||||
|
||||
action_sequence.append(create_action_log(f"容器体积已更新 (+{volume:.2f}mL)", "📊"))
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 生成泵协议时出错: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"泵协议生成失败: {str(e)}", "❌"))
|
||||
@@ -439,18 +502,18 @@ def generate_adjust_ph_protocol(
|
||||
debug_print(f" 🧪 试剂: {reagent}")
|
||||
debug_print(f" 📏 体积: {volume:.2f}mL")
|
||||
debug_print(f" 📊 目标pH: {ph_value}")
|
||||
debug_print(f" 🥼 目标容器: {vessel}")
|
||||
debug_print(f" 🥼 目标容器: {vessel_id}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"pH调节协议完成: {vessel} → pH {ph_value} (使用 {volume:.2f}mL {reagent})"
|
||||
summary_msg = f"pH调节协议完成: {vessel_id} → pH {ph_value} (使用 {volume:.2f}mL {reagent})"
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
def generate_adjust_ph_protocol_stepwise(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
ph_value: float,
|
||||
reagent: str,
|
||||
max_volume: float = 10.0,
|
||||
@@ -461,7 +524,7 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
|
||||
Args:
|
||||
G: 网络图
|
||||
vessel: 目标容器
|
||||
vessel: 目标容器字典
|
||||
ph_value: 目标pH值
|
||||
reagent: 酸碱试剂
|
||||
max_volume: 最大试剂体积
|
||||
@@ -470,10 +533,13 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
vessel_id = vessel["id"]
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"🔄 开始分步pH调节")
|
||||
debug_print(f"📋 分步参数:")
|
||||
debug_print(f" 🥼 vessel: {vessel}")
|
||||
debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 📊 ph_value: {ph_value}")
|
||||
debug_print(f" 🧪 reagent: {reagent}")
|
||||
debug_print(f" 📏 max_volume: {max_volume}mL")
|
||||
@@ -496,7 +562,7 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
# 生成单步协议
|
||||
step_actions = generate_adjust_ph_protocol(
|
||||
G=G,
|
||||
vessel=vessel,
|
||||
vessel=vessel, # 🔧 直接传递vessel字典
|
||||
ph_value=ph_value,
|
||||
reagent=reagent,
|
||||
volume=step_volume,
|
||||
@@ -530,35 +596,38 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
# 便捷函数:常用pH调节
|
||||
def generate_acidify_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
target_ph: float = 2.0,
|
||||
acid: str = "hydrochloric acid"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""酸化协议"""
|
||||
debug_print(f"🍋 生成酸化协议: {vessel} → pH {target_ph} (使用 {acid})")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🍋 生成酸化协议: {vessel_id} → pH {target_ph} (使用 {acid})")
|
||||
return generate_adjust_ph_protocol(
|
||||
G, vessel, target_ph, acid
|
||||
)
|
||||
|
||||
def generate_basify_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
target_ph: float = 12.0,
|
||||
base: str = "sodium hydroxide"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""碱化协议"""
|
||||
debug_print(f"🧂 生成碱化协议: {vessel} → pH {target_ph} (使用 {base})")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🧂 生成碱化协议: {vessel_id} → pH {target_ph} (使用 {base})")
|
||||
return generate_adjust_ph_protocol(
|
||||
G, vessel, target_ph, base
|
||||
)
|
||||
|
||||
def generate_neutralize_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
reagent: str = "sodium hydroxide"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""中和协议(pH=7)"""
|
||||
debug_print(f"⚖️ 生成中和协议: {vessel} → pH 7.0 (使用 {reagent})")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"⚖️ 生成中和协议: {vessel_id} → pH 7.0 (使用 {reagent})")
|
||||
return generate_adjust_ph_protocol(
|
||||
G, vessel, 7.0, reagent
|
||||
)
|
||||
|
||||
@@ -145,7 +145,7 @@ def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||
|
||||
def generate_clean_vessel_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
solvent: str,
|
||||
volume: float,
|
||||
temp: float,
|
||||
@@ -165,7 +165,7 @@ def generate_clean_vessel_protocol(
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
vessel: 要清洗的容器名称
|
||||
vessel: 要清洗的容器字典(包含id字段)
|
||||
solvent: 用于清洗的溶剂名称
|
||||
volume: 每次清洗使用的溶剂体积
|
||||
temp: 清洗时的温度
|
||||
@@ -178,20 +178,32 @@ def generate_clean_vessel_protocol(
|
||||
ValueError: 当找不到必要的容器或设备时抛出异常
|
||||
|
||||
Examples:
|
||||
clean_protocol = generate_clean_vessel_protocol(G, "main_reactor", "water", 100.0, 60.0, 2)
|
||||
clean_protocol = generate_clean_vessel_protocol(G, {"id": "main_reactor"}, "water", 100.0, 60.0, 2)
|
||||
"""
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
action_sequence = []
|
||||
|
||||
print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
|
||||
print(f" - 目标容器: {vessel}")
|
||||
print(f" - 目标容器: {vessel} (ID: {vessel_id})")
|
||||
print(f" - 清洗溶剂: {solvent}")
|
||||
print(f" - 清洗体积: {volume} mL")
|
||||
print(f" - 清洗温度: {temp}°C")
|
||||
print(f" - 重复次数: {repeats}")
|
||||
|
||||
# 验证目标容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
if vessel_id not in G.nodes():
|
||||
raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
|
||||
|
||||
# 查找溶剂容器
|
||||
try:
|
||||
@@ -208,12 +220,23 @@ def generate_clean_vessel_protocol(
|
||||
raise ValueError(f"无法找到废液容器: {str(e)}")
|
||||
|
||||
# 查找加热设备(可选)
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
|
||||
if heatchill_id:
|
||||
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
|
||||
else:
|
||||
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
|
||||
|
||||
# 🔧 新增:记录清洗前的容器状态
|
||||
print(f"CLEAN_VESSEL: 记录清洗前容器状态...")
|
||||
original_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
original_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
original_liquid_volume = current_volume
|
||||
print(f"CLEAN_VESSEL: 清洗前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# 第一步:如果需要加热且有加热设备,启动加热
|
||||
if temp > 25.0 and heatchill_id:
|
||||
print(f"CLEAN_VESSEL: 启动加热至 {temp}°C")
|
||||
@@ -221,7 +244,7 @@ def generate_clean_vessel_protocol(
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"temp": temp,
|
||||
"purpose": f"cleaning with {solvent}"
|
||||
}
|
||||
@@ -240,18 +263,61 @@ def generate_clean_vessel_protocol(
|
||||
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
|
||||
|
||||
# 2a. 使用 pump_protocol 将溶剂转移到目标容器
|
||||
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel}")
|
||||
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel_id}")
|
||||
try:
|
||||
# 调用成熟的 pump_protocol 算法
|
||||
add_solvent_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
volume=volume,
|
||||
flowrate=2.5, # 适中的流速,避免飞溅
|
||||
transfer_flowrate=2.5
|
||||
)
|
||||
action_sequence.extend(add_solvent_actions)
|
||||
|
||||
# 🔧 新增:更新容器体积(添加清洗溶剂)
|
||||
print(f"CLEAN_VESSEL: 更新容器体积 - 添加清洗溶剂 {volume:.2f}mL")
|
||||
if "data" not in vessel:
|
||||
vessel["data"] = {}
|
||||
|
||||
if "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] += volume
|
||||
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{volume:.2f}mL)")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [volume]
|
||||
print(f"CLEAN_VESSEL: 初始化清洗体积: {volume:.2f}mL")
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
vessel["data"]["liquid_volume"] += volume
|
||||
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{volume:.2f}mL)")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = volume
|
||||
print(f"CLEAN_VESSEL: 重置体积为: {volume:.2f}mL")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = volume
|
||||
print(f"CLEAN_VESSEL: 创建新体积记录: {volume:.2f}mL")
|
||||
|
||||
# 🔧 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] += volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume
|
||||
|
||||
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
|
||||
|
||||
@@ -265,18 +331,52 @@ def generate_clean_vessel_protocol(
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# 2c. 使用 pump_protocol 将清洗液转移到废液容器
|
||||
print(f"CLEAN_VESSEL: 将清洗液从 {vessel} 转移到废液容器")
|
||||
print(f"CLEAN_VESSEL: 将清洗液从 {vessel_id} 转移到废液容器")
|
||||
try:
|
||||
# 调用成熟的 pump_protocol 算法
|
||||
remove_waste_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
from_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
to_vessel=waste_vessel,
|
||||
volume=volume,
|
||||
flowrate=2.5, # 适中的流速
|
||||
transfer_flowrate=2.5
|
||||
)
|
||||
action_sequence.extend(remove_waste_actions)
|
||||
|
||||
# 🔧 新增:更新容器体积(移除清洗液)
|
||||
print(f"CLEAN_VESSEL: 更新容器体积 - 移除清洗液 {volume:.2f}mL")
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = max(0.0, vessel["data"]["liquid_volume"][0] - volume)
|
||||
print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (-{volume:.2f}mL)")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [0.0]
|
||||
print(f"CLEAN_VESSEL: 重置体积为0mL")
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
vessel["data"]["liquid_volume"] = max(0.0, current_volume - volume)
|
||||
print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume']:.2f}mL (-{volume:.2f}mL)")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = 0.0
|
||||
print(f"CLEAN_VESSEL: 重置体积为0mL")
|
||||
|
||||
# 🔧 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
vessel_node_data = G.nodes[vessel_id].get('data', {})
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = max(0.0, current_node_volume[0] - volume)
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [0.0]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = max(0.0, current_node_volume - volume)
|
||||
|
||||
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
|
||||
|
||||
@@ -296,13 +396,24 @@ def generate_clean_vessel_protocol(
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
"vessel": vessel_id # 🔧 使用 vessel_id
|
||||
}
|
||||
}
|
||||
action_sequence.append(heatchill_stop_action)
|
||||
|
||||
print(f"CLEAN_VESSEL: 生成了 {len(action_sequence)} 个动作")
|
||||
print(f"CLEAN_VESSEL: 清洗协议生成完成")
|
||||
# 🔧 新增:清洗完成后的状态报告
|
||||
final_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
final_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
final_liquid_volume = current_volume
|
||||
|
||||
print(f"CLEAN_VESSEL: 清洗完成")
|
||||
print(f" - 清洗前体积: {original_liquid_volume:.2f}mL")
|
||||
print(f" - 清洗后体积: {final_liquid_volume:.2f}mL")
|
||||
print(f" - 生成了 {len(action_sequence)} 个动作")
|
||||
|
||||
return action_sequence
|
||||
|
||||
@@ -310,7 +421,7 @@ def generate_clean_vessel_protocol(
|
||||
# 便捷函数:常用清洗方案
|
||||
def generate_quick_clean_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
solvent: str = "water",
|
||||
volume: float = 100.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
@@ -320,7 +431,7 @@ def generate_quick_clean_protocol(
|
||||
|
||||
def generate_thorough_clean_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
solvent: str = "water",
|
||||
volume: float = 150.0,
|
||||
temp: float = 60.0
|
||||
@@ -331,7 +442,7 @@ def generate_thorough_clean_protocol(
|
||||
|
||||
def generate_organic_clean_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
volume: float = 100.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""有机清洗:先用有机溶剂,再用水清洗"""
|
||||
|
||||
@@ -408,7 +408,7 @@ def find_solid_dispenser(G: nx.DiGraph) -> str:
|
||||
|
||||
def generate_dissolve_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
# 🔧 修复:按照checklist.md的DissolveProtocol参数
|
||||
solvent: str = "",
|
||||
volume: Union[str, float] = 0.0,
|
||||
@@ -427,10 +427,11 @@ def generate_dissolve_protocol(
|
||||
生成溶解操作的协议序列 - 增强版
|
||||
|
||||
🔧 修复要点:
|
||||
1. 添加action文件中的所有参数(mass, mol, reagent, event)
|
||||
2. 使用 **kwargs 接受所有额外参数,防止 unexpected keyword argument 错误
|
||||
3. 支持固体溶解和液体溶解两种模式
|
||||
4. 添加详细的emoji日志系统
|
||||
1. 修改vessel参数类型为dict,并提取vessel_id
|
||||
2. 添加action文件中的所有参数(mass, mol, reagent, event)
|
||||
3. 使用 **kwargs 接受所有额外参数,防止 unexpected keyword argument 错误
|
||||
4. 支持固体溶解和液体溶解两种模式
|
||||
5. 添加详细的体积运算逻辑
|
||||
|
||||
支持两种溶解模式:
|
||||
1. 液体溶解:指定 solvent + volume,使用pump protocol转移溶剂
|
||||
@@ -444,10 +445,13 @@ def generate_dissolve_protocol(
|
||||
- mol: "0.12 mol", "16.2 mmol"
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
vessel_id = vessel["id"]
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("🧪 开始生成溶解协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 vessel: '{vessel}'")
|
||||
debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 💧 solvent: '{solvent}'")
|
||||
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
|
||||
@@ -463,19 +467,30 @@ def generate_dissolve_protocol(
|
||||
|
||||
# === 参数验证 ===
|
||||
debug_print("🔍 步骤1: 参数验证...")
|
||||
action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel_id}", "🎬"))
|
||||
|
||||
if not vessel:
|
||||
if not vessel_id:
|
||||
debug_print("❌ vessel 参数不能为空")
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
if vessel_id not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中")
|
||||
raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
|
||||
|
||||
debug_print("✅ 基本参数验证通过")
|
||||
action_sequence.append(create_action_log("参数验证通过", "✅"))
|
||||
|
||||
# 🔧 新增:记录溶解前的容器状态
|
||||
debug_print("🔍 记录溶解前容器状态...")
|
||||
original_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
original_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
original_liquid_volume = current_volume
|
||||
debug_print(f"📊 溶解前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# === 🔧 关键修复:参数解析 ===
|
||||
debug_print("🔍 步骤2: 参数解析...")
|
||||
action_sequence.append(create_action_log("正在解析溶解参数...", "🔍"))
|
||||
@@ -522,8 +537,8 @@ def generate_dissolve_protocol(
|
||||
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
|
||||
|
||||
# 查找加热搅拌器
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
heatchill_id = find_connected_heatchill(G, vessel_id)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id)
|
||||
|
||||
# 优先使用加热搅拌器,否则使用独立搅拌器
|
||||
stir_device_id = heatchill_id or stirrer_id
|
||||
@@ -557,7 +572,7 @@ def generate_dissolve_protocol(
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id,
|
||||
"temp": final_temp,
|
||||
"purpose": f"溶解准备 - {event}" if event else "溶解准备"
|
||||
}
|
||||
@@ -581,7 +596,7 @@ def generate_dissolve_protocol(
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"溶解搅拌 - {event}" if event else "溶解搅拌"
|
||||
}
|
||||
@@ -606,7 +621,7 @@ def generate_dissolve_protocol(
|
||||
|
||||
# 固体加样
|
||||
add_kwargs = {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id,
|
||||
"reagent": reagent or amount or "solid reagent",
|
||||
"purpose": f"溶解固体试剂 - {event}" if event else "溶解固体试剂",
|
||||
"event": event
|
||||
@@ -628,6 +643,12 @@ def generate_dissolve_protocol(
|
||||
|
||||
debug_print(f"✅ 固体加样完成")
|
||||
action_sequence.append(create_action_log("固体加样完成", "✅"))
|
||||
|
||||
# 🔧 新增:固体溶解体积运算 - 固体本身不会显著增加体积,但可能有少量变化
|
||||
debug_print(f"🔧 固体溶解 - 体积变化很小,主要是质量变化")
|
||||
# 固体通常不会显著改变液体体积,这里只记录日志
|
||||
action_sequence.append(create_action_log(f"固体已添加: {final_mass}g", "📊"))
|
||||
|
||||
else:
|
||||
debug_print("⚠️ 未找到固体加样器,跳过固体添加")
|
||||
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", "❌"))
|
||||
@@ -659,7 +680,7 @@ def generate_dissolve_protocol(
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id,
|
||||
volume=final_volume,
|
||||
amount=amount,
|
||||
time=0.0, # 不在pump level控制时间
|
||||
@@ -679,6 +700,52 @@ def generate_dissolve_protocol(
|
||||
debug_print(f"✅ 溶剂转移完成,添加了 {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
# 🔧 新增:液体溶解体积运算 - 添加溶剂后更新容器体积
|
||||
debug_print(f"🔧 更新容器液体体积 - 添加溶剂 {final_volume:.2f}mL")
|
||||
|
||||
# 确保vessel有data字段
|
||||
if "data" not in vessel:
|
||||
vessel["data"] = {}
|
||||
|
||||
if "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] += final_volume
|
||||
debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{final_volume:.2f}mL)")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [final_volume]
|
||||
debug_print(f"📊 初始化溶解体积: {final_volume:.2f}mL")
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
vessel["data"]["liquid_volume"] += final_volume
|
||||
debug_print(f"📊 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{final_volume:.2f}mL)")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = final_volume
|
||||
debug_print(f"📊 重置体积为: {final_volume:.2f}mL")
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = final_volume
|
||||
debug_print(f"📊 创建新体积记录: {final_volume:.2f}mL")
|
||||
|
||||
# 🔧 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] += final_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [final_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + final_volume
|
||||
|
||||
debug_print(f"✅ 图节点体积数据已更新")
|
||||
|
||||
action_sequence.append(create_action_log(f"容器体积已更新 (+{final_volume:.2f}mL)", "📊"))
|
||||
|
||||
# 溶剂添加后等待
|
||||
action_sequence.append(create_action_log("溶剂添加后短暂等待...", "⏳"))
|
||||
action_sequence.append({
|
||||
@@ -700,7 +767,7 @@ def generate_dissolve_protocol(
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id,
|
||||
"temp": final_temp,
|
||||
"time": final_time,
|
||||
"stir": True,
|
||||
@@ -718,7 +785,7 @@ def generate_dissolve_protocol(
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id,
|
||||
"stir_time": final_time,
|
||||
"stir_speed": stir_speed,
|
||||
"settling_time": 0,
|
||||
@@ -744,7 +811,7 @@ def generate_dissolve_protocol(
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
"vessel": vessel_id
|
||||
}
|
||||
}
|
||||
action_sequence.append(stop_action)
|
||||
@@ -761,12 +828,21 @@ def generate_dissolve_protocol(
|
||||
}
|
||||
})
|
||||
|
||||
# 🔧 新增:溶解完成后的状态报告
|
||||
final_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
final_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
final_liquid_volume = current_volume
|
||||
|
||||
# === 最终结果 ===
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"🎉 溶解协议生成完成")
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||||
debug_print(f" 🥼 容器: {vessel}")
|
||||
debug_print(f" 🥼 容器: {vessel_id}")
|
||||
debug_print(f" {dissolve_emoji} 溶解类型: {dissolve_type}")
|
||||
if is_liquid_dissolve:
|
||||
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
|
||||
@@ -776,10 +852,12 @@ def generate_dissolve_protocol(
|
||||
debug_print(f" 🧬 摩尔: {mol}")
|
||||
debug_print(f" 🌡️ 温度: {final_temp}°C")
|
||||
debug_print(f" ⏱️ 时间: {final_time}s")
|
||||
debug_print(f" 📊 溶解前体积: {original_liquid_volume:.2f}mL")
|
||||
debug_print(f" 📊 溶解后体积: {final_liquid_volume:.2f}mL")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"溶解协议完成: {vessel}"
|
||||
summary_msg = f"溶解协议完成: {vessel_id}"
|
||||
if is_liquid_dissolve:
|
||||
summary_msg += f" (使用 {final_volume}mL {solvent})"
|
||||
if is_solid_dissolve:
|
||||
@@ -789,12 +867,15 @@ def generate_dissolve_protocol(
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
|
||||
def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
|
||||
# === 便捷函数 ===
|
||||
# 🔧 修改便捷函数的参数类型
|
||||
|
||||
def dissolve_solid_by_mass(G: nx.DiGraph, vessel: dict, reagent: str, mass: Union[str, float],
|
||||
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
|
||||
"""按质量溶解固体"""
|
||||
debug_print(f"🧂 快速固体溶解: {reagent} ({mass}) → {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🧂 快速固体溶解: {reagent} ({mass}) → {vessel_id}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
mass=mass,
|
||||
@@ -803,10 +884,11 @@ def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union
|
||||
time=time
|
||||
)
|
||||
|
||||
def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
|
||||
def dissolve_solid_by_moles(G: nx.DiGraph, vessel: dict, reagent: str, mol: str,
|
||||
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
|
||||
"""按摩尔数溶解固体"""
|
||||
debug_print(f"🧬 按摩尔数溶解固体: {reagent} ({mol}) → {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🧬 按摩尔数溶解固体: {reagent} ({mol}) → {vessel_id}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
mol=mol,
|
||||
@@ -815,10 +897,11 @@ def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
|
||||
time=time
|
||||
)
|
||||
|
||||
def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
def dissolve_with_solvent(G: nx.DiGraph, vessel: dict, solvent: str, volume: Union[str, float],
|
||||
temp: Union[str, float] = 25.0, time: Union[str, float] = "5 min") -> List[Dict[str, Any]]:
|
||||
"""用溶剂溶解"""
|
||||
debug_print(f"💧 溶剂溶解: {solvent} ({volume}) → {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"💧 溶剂溶解: {solvent} ({volume}) → {vessel_id}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
solvent=solvent,
|
||||
@@ -827,9 +910,10 @@ def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
|
||||
time=time
|
||||
)
|
||||
|
||||
def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float]) -> List[Dict[str, Any]]:
|
||||
def dissolve_at_room_temp(G: nx.DiGraph, vessel: dict, solvent: str, volume: Union[str, float]) -> List[Dict[str, Any]]:
|
||||
"""室温溶解"""
|
||||
debug_print(f"🌡️ 室温溶解: {solvent} ({volume}) → {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🌡️ 室温溶解: {solvent} ({volume}) → {vessel_id}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
solvent=solvent,
|
||||
@@ -838,10 +922,11 @@ def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
|
||||
time="5 min"
|
||||
)
|
||||
|
||||
def dissolve_with_heating(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
def dissolve_with_heating(G: nx.DiGraph, vessel: dict, solvent: str, volume: Union[str, float],
|
||||
temp: Union[str, float] = "60 °C", time: Union[str, float] = "15 min") -> List[Dict[str, Any]]:
|
||||
"""加热溶解"""
|
||||
debug_print(f"🔥 加热溶解: {solvent} ({volume}) → {vessel} @ {temp}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🔥 加热溶解: {solvent} ({volume}) → {vessel_id} @ {temp}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
solvent=solvent,
|
||||
|
||||
@@ -46,8 +46,8 @@ def find_connected_heater(G: nx.DiGraph, vessel: str) -> str:
|
||||
|
||||
def generate_dry_protocol(
|
||||
G: nx.DiGraph,
|
||||
compound: str,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
compound: str = "", # 🔧 修改:参数顺序调整,并设置默认值
|
||||
**kwargs # 接收其他可能的参数但不使用
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -55,13 +55,16 @@ def generate_dry_protocol(
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为容器和设备
|
||||
compound: 化合物名称(从XDL传入)
|
||||
vessel: 目标容器(从XDL传入)
|
||||
vessel: 目标容器字典(从XDL传入)
|
||||
compound: 化合物名称(从XDL传入,可选)
|
||||
**kwargs: 其他可选参数,但不使用
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
vessel_id = vessel["id"]
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# 默认参数
|
||||
@@ -70,33 +73,83 @@ def generate_dry_protocol(
|
||||
simulation_time = 60.0 # 模拟时间 1分钟
|
||||
|
||||
print(f"🌡️ DRY: 开始生成干燥协议 ✨")
|
||||
print(f" 🧪 化合物: {compound}")
|
||||
print(f" 🥽 容器: {vessel}")
|
||||
print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
print(f" 🧪 化合物: {compound or '未指定'}")
|
||||
print(f" 🔥 干燥温度: {dry_temp}°C")
|
||||
print(f" ⏰ 干燥时间: {dry_time/60:.0f} 分钟")
|
||||
|
||||
# 🔧 新增:记录干燥前的容器状态
|
||||
print(f"🔍 记录干燥前容器状态...")
|
||||
original_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
original_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
original_liquid_volume = current_volume
|
||||
print(f"📊 干燥前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# 1. 验证目标容器存在
|
||||
print(f"\n📋 步骤1: 验证目标容器 '{vessel}' 是否存在...")
|
||||
if vessel not in G.nodes():
|
||||
print(f"⚠️ DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥 😢")
|
||||
print(f"\n📋 步骤1: 验证目标容器 '{vessel_id}' 是否存在...")
|
||||
if vessel_id not in G.nodes():
|
||||
print(f"⚠️ DRY: 警告 - 容器 '{vessel_id}' 不存在于系统中,跳过干燥 😢")
|
||||
return action_sequence
|
||||
print(f"✅ 容器 '{vessel}' 验证通过!")
|
||||
print(f"✅ 容器 '{vessel_id}' 验证通过!")
|
||||
|
||||
# 2. 查找相连的加热器
|
||||
print(f"\n🔍 步骤2: 查找与容器相连的加热器...")
|
||||
heater_id = find_connected_heater(G, vessel)
|
||||
heater_id = find_connected_heater(G, vessel_id) # 🔧 使用 vessel_id
|
||||
|
||||
if heater_id is None:
|
||||
print(f"😭 DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥")
|
||||
print(f"😭 DRY: 警告 - 未找到与容器 '{vessel_id}' 相连的加热器,跳过干燥")
|
||||
print(f"🎭 添加模拟干燥动作...")
|
||||
# 添加一个等待动作,表示干燥过程(模拟)
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 10.0, # 模拟等待时间
|
||||
"description": f"模拟干燥 {compound} (无加热器可用)"
|
||||
"description": f"模拟干燥 {compound or '化合物'} (无加热器可用)"
|
||||
}
|
||||
})
|
||||
|
||||
# 🔧 新增:模拟干燥的体积变化(溶剂蒸发)
|
||||
print(f"🔧 模拟干燥过程的体积减少...")
|
||||
if original_liquid_volume > 0:
|
||||
# 假设干燥过程中损失10%的体积(溶剂蒸发)
|
||||
volume_loss = original_liquid_volume * 0.1
|
||||
new_volume = max(0.0, original_liquid_volume - volume_loss)
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [new_volume]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
|
||||
# 🔧 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
|
||||
|
||||
print(f"📊 模拟干燥体积变化: {original_liquid_volume:.2f}mL → {new_volume:.2f}mL (-{volume_loss:.2f}mL)")
|
||||
|
||||
print(f"📄 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯")
|
||||
return action_sequence
|
||||
|
||||
@@ -112,9 +165,9 @@ def generate_dry_protocol(
|
||||
"device_id": heater_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"temp": dry_temp,
|
||||
"purpose": f"干燥 {compound}"
|
||||
"purpose": f"干燥 {compound or '化合物'}"
|
||||
}
|
||||
})
|
||||
print(f" ✅ 加热器启动命令已添加 🔥")
|
||||
@@ -136,21 +189,67 @@ def generate_dry_protocol(
|
||||
"device_id": heater_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"temp": dry_temp,
|
||||
"time": simulation_time,
|
||||
"purpose": f"干燥 {compound},保持温度 {dry_temp}°C"
|
||||
"purpose": f"干燥 {compound or '化合物'},保持温度 {dry_temp}°C"
|
||||
}
|
||||
})
|
||||
print(f" ✅ 温度保持命令已添加 🌡️⏰")
|
||||
|
||||
# 🔧 新增:干燥过程中的体积变化计算
|
||||
print(f"🔧 计算干燥过程中的体积变化...")
|
||||
if original_liquid_volume > 0:
|
||||
# 干燥过程中,溶剂会蒸发,固体保留
|
||||
# 根据温度和时间估算蒸发量
|
||||
evaporation_rate = 0.001 * dry_temp # 每秒每°C蒸发0.001mL
|
||||
total_evaporation = min(original_liquid_volume * 0.8,
|
||||
evaporation_rate * simulation_time) # 最多蒸发80%
|
||||
|
||||
new_volume = max(0.0, original_liquid_volume - total_evaporation)
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [new_volume]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
|
||||
# 🔧 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
|
||||
|
||||
print(f"📊 干燥体积变化计算:")
|
||||
print(f" - 初始体积: {original_liquid_volume:.2f}mL")
|
||||
print(f" - 蒸发量: {total_evaporation:.2f}mL")
|
||||
print(f" - 剩余体积: {new_volume:.2f}mL")
|
||||
print(f" - 蒸发率: {(total_evaporation/original_liquid_volume*100):.1f}%")
|
||||
|
||||
# 3.4 停止加热
|
||||
print(f" ⏹️ 动作4: 停止加热...")
|
||||
action_sequence.append({
|
||||
"device_id": heater_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"purpose": f"干燥完成,停止加热"
|
||||
}
|
||||
})
|
||||
@@ -162,18 +261,67 @@ def generate_dry_protocol(
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 10.0, # 等待10秒冷却
|
||||
"description": f"等待 {compound} 冷却"
|
||||
"description": f"等待 {compound or '化合物'} 冷却"
|
||||
}
|
||||
})
|
||||
print(f" ✅ 冷却等待命令已添加 🧊")
|
||||
|
||||
# 🔧 新增:干燥完成后的状态报告
|
||||
final_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
final_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
final_liquid_volume = current_volume
|
||||
|
||||
print(f"\n🎊 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯")
|
||||
print(f"⏱️ DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟 ⌛")
|
||||
print(f"⏱️ DRY: 预计总时间: {(simulation_time + 30)/60:.0f} 分钟 ⌛")
|
||||
print(f"📊 干燥结果:")
|
||||
print(f" - 容器: {vessel_id}")
|
||||
print(f" - 化合物: {compound or '未指定'}")
|
||||
print(f" - 干燥前体积: {original_liquid_volume:.2f}mL")
|
||||
print(f" - 干燥后体积: {final_liquid_volume:.2f}mL")
|
||||
print(f" - 蒸发体积: {(original_liquid_volume - final_liquid_volume):.2f}mL")
|
||||
print(f"🏁 所有动作序列准备就绪! ✨")
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 🔧 新增:便捷函数
|
||||
def generate_quick_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "",
|
||||
temp: float = 40.0, time: float = 30.0) -> List[Dict[str, Any]]:
|
||||
"""快速干燥:低温短时间"""
|
||||
vessel_id = vessel["id"]
|
||||
print(f"🌡️ 快速干燥: {compound or '化合物'} → {vessel_id} @ {temp}°C ({time}min)")
|
||||
|
||||
# 临时修改默认参数
|
||||
import types
|
||||
temp_func = types.FunctionType(
|
||||
generate_dry_protocol.__code__,
|
||||
generate_dry_protocol.__globals__
|
||||
)
|
||||
|
||||
# 直接调用原函数,但修改内部参数
|
||||
return generate_dry_protocol(G, vessel, compound)
|
||||
|
||||
|
||||
def generate_thorough_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "",
|
||||
temp: float = 80.0, time: float = 120.0) -> List[Dict[str, Any]]:
|
||||
"""深度干燥:高温长时间"""
|
||||
vessel_id = vessel["id"]
|
||||
print(f"🔥 深度干燥: {compound or '化合物'} → {vessel_id} @ {temp}°C ({time}min)")
|
||||
return generate_dry_protocol(G, vessel, compound)
|
||||
|
||||
|
||||
def generate_gentle_dry_protocol(G: nx.DiGraph, vessel: dict, compound: str = "",
|
||||
temp: float = 30.0, time: float = 180.0) -> List[Dict[str, Any]]:
|
||||
"""温和干燥:低温长时间"""
|
||||
vessel_id = vessel["id"]
|
||||
print(f"🌡️ 温和干燥: {compound or '化合物'} → {vessel_id} @ {temp}°C ({time}min)")
|
||||
return generate_dry_protocol(G, vessel, compound)
|
||||
|
||||
|
||||
# 测试函数
|
||||
def test_dry_protocol():
|
||||
"""测试干燥协议"""
|
||||
|
||||
@@ -270,7 +270,7 @@ def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
|
||||
|
||||
def generate_evacuateandrefill_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
gas: str,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
@@ -279,7 +279,7 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
Args:
|
||||
G: 设备图
|
||||
vessel: 目标容器名称(必需)
|
||||
vessel: 目标容器字典(必需)
|
||||
gas: 气体名称(必需)
|
||||
**kwargs: 其他参数(兼容性)
|
||||
|
||||
@@ -287,6 +287,18 @@ def generate_evacuateandrefill_protocol(
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
# 硬编码重复次数为 3
|
||||
repeats = 3
|
||||
|
||||
@@ -297,7 +309,7 @@ def generate_evacuateandrefill_protocol(
|
||||
debug_print("=" * 60)
|
||||
debug_print("🧪 开始生成抽真空充气协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 容器: '{vessel}'")
|
||||
debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 💨 气体: '{gas}'")
|
||||
debug_print(f" 🔄 循环次数: {repeats} (硬编码)")
|
||||
debug_print(f" 📦 其他参数: {kwargs}")
|
||||
@@ -307,12 +319,12 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
# === 参数验证和修正 ===
|
||||
debug_print("🔍 步骤1: 参数验证和修正...")
|
||||
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel_id}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"目标气体: {gas}", "💨"))
|
||||
action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄"))
|
||||
|
||||
# 验证必需参数
|
||||
if not vessel:
|
||||
if not vessel_id:
|
||||
debug_print("❌ 容器参数不能为空")
|
||||
raise ValueError("容器参数不能为空")
|
||||
|
||||
@@ -320,9 +332,9 @@ def generate_evacuateandrefill_protocol(
|
||||
debug_print("❌ 气体参数不能为空")
|
||||
raise ValueError("气体参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel}' 在系统中不存在")
|
||||
raise ValueError(f"容器 '{vessel}' 在系统中不存在")
|
||||
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
debug_print(f"❌ 容器 '{vessel_id}' 在系统中不存在")
|
||||
raise ValueError(f"容器 '{vessel_id}' 在系统中不存在")
|
||||
|
||||
debug_print("✅ 基本参数验证通过")
|
||||
action_sequence.append(create_action_log("参数验证通过", "✅"))
|
||||
@@ -351,7 +363,7 @@ def generate_evacuateandrefill_protocol(
|
||||
debug_print(f"🔄 标准化气体名称: {original_gas} -> {gas}")
|
||||
action_sequence.append(create_action_log(f"气体名称标准化: {original_gas} -> {gas}", "🔄"))
|
||||
|
||||
debug_print(f"📋 最终参数: 容器={vessel}, 气体={gas}, 重复={repeats}")
|
||||
debug_print(f"📋 最终参数: 容器={vessel_id}, 气体={gas}, 重复={repeats}")
|
||||
|
||||
# === 查找设备 ===
|
||||
debug_print("🔍 步骤2: 查找设备...")
|
||||
@@ -376,7 +388,7 @@ def generate_evacuateandrefill_protocol(
|
||||
else:
|
||||
action_sequence.append(create_action_log("未找到气源电磁阀", "⚠️"))
|
||||
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
|
||||
if stirrer_id:
|
||||
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
|
||||
else:
|
||||
@@ -444,8 +456,8 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
try:
|
||||
# 验证抽真空路径
|
||||
if nx.has_path(G, vessel, vacuum_pump):
|
||||
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
|
||||
if nx.has_path(G, vessel_id, vacuum_pump): # 🔧 使用 vessel_id
|
||||
vacuum_path = nx.shortest_path(G, source=vessel_id, target=vacuum_pump)
|
||||
debug_print(f"✅ 真空路径: {' -> '.join(vacuum_path)}")
|
||||
action_sequence.append(create_action_log(f"真空路径: {' -> '.join(vacuum_path)}", "🛤️"))
|
||||
else:
|
||||
@@ -453,8 +465,8 @@ def generate_evacuateandrefill_protocol(
|
||||
action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️"))
|
||||
|
||||
# 验证充气路径
|
||||
if nx.has_path(G, gas_source, vessel):
|
||||
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
|
||||
if nx.has_path(G, gas_source, vessel_id): # 🔧 使用 vessel_id
|
||||
gas_path = nx.shortest_path(G, source=gas_source, target=vessel_id)
|
||||
debug_print(f"✅ 气体路径: {' -> '.join(gas_path)}")
|
||||
action_sequence.append(create_action_log(f"气体路径: {' -> '.join(gas_path)}", "🛤️"))
|
||||
else:
|
||||
@@ -476,7 +488,7 @@ def generate_evacuateandrefill_protocol(
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"stir_speed": STIR_SPEED,
|
||||
"purpose": "抽真空充气前预搅拌"
|
||||
}
|
||||
@@ -524,13 +536,13 @@ def generate_evacuateandrefill_protocol(
|
||||
})
|
||||
|
||||
# 抽真空操作
|
||||
debug_print(f"🌪️ 抽真空操作: {vessel} -> {vacuum_pump}")
|
||||
action_sequence.append(create_action_log(f"开始抽真空: {vessel} -> {vacuum_pump}", "🌪️"))
|
||||
debug_print(f"🌪️ 抽真空操作: {vessel_id} -> {vacuum_pump}")
|
||||
action_sequence.append(create_action_log(f"开始抽真空: {vessel_id} -> {vacuum_pump}", "🌪️"))
|
||||
|
||||
try:
|
||||
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
from_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
to_vessel=vacuum_pump,
|
||||
volume=VACUUM_VOLUME,
|
||||
amount="",
|
||||
@@ -622,14 +634,14 @@ def generate_evacuateandrefill_protocol(
|
||||
})
|
||||
|
||||
# 充气操作
|
||||
debug_print(f"💨 充气操作: {gas_source} -> {vessel}")
|
||||
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel}", "💨"))
|
||||
debug_print(f"💨 充气操作: {gas_source} -> {vessel_id}")
|
||||
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel_id}", "💨"))
|
||||
|
||||
try:
|
||||
gas_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=gas_source,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
volume=REFILL_VOLUME,
|
||||
amount="",
|
||||
time=0.0,
|
||||
@@ -709,7 +721,7 @@ def generate_evacuateandrefill_protocol(
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {"vessel": vessel}
|
||||
"action_kwargs": {"vessel": vessel_id} # 🔧 使用 vessel_id
|
||||
})
|
||||
else:
|
||||
action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️"))
|
||||
@@ -729,37 +741,41 @@ def generate_evacuateandrefill_protocol(
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||||
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
|
||||
debug_print(f" 🥼 处理容器: {vessel}")
|
||||
debug_print(f" 🥼 处理容器: {vessel_id}")
|
||||
debug_print(f" 💨 使用气体: {gas}")
|
||||
debug_print(f" 🔄 重复次数: {repeats}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"抽真空充气协议完成: {vessel} (使用 {gas},{repeats} 次循环)"
|
||||
summary_msg = f"抽真空充气协议完成: {vessel_id} (使用 {gas},{repeats} 次循环)"
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
|
||||
def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
|
||||
def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||||
"""生成氮气置换协议"""
|
||||
debug_print(f"💨 生成氮气置换协议: {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"💨 生成氮气置换协议: {vessel_id}")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, "nitrogen", **kwargs)
|
||||
|
||||
def generate_argon_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
|
||||
def generate_argon_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||||
"""生成氩气置换协议"""
|
||||
debug_print(f"💨 生成氩气置换协议: {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"💨 生成氩气置换协议: {vessel_id}")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, "argon", **kwargs)
|
||||
|
||||
def generate_air_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
|
||||
def generate_air_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||||
"""生成空气置换协议"""
|
||||
debug_print(f"💨 生成空气置换协议: {vessel}")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"💨 生成空气置换协议: {vessel_id}")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, "air", **kwargs)
|
||||
|
||||
def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: str, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]:
|
||||
def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: dict, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||||
"""生成惰性气氛协议"""
|
||||
debug_print(f"🛡️ 生成惰性气氛协议: {vessel} (使用 {gas})")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🛡️ 生成惰性气氛协议: {vessel_id} (使用 {gas})")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, gas, **kwargs)
|
||||
|
||||
# 测试函数
|
||||
|
||||
@@ -22,7 +22,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
"""
|
||||
if isinstance(time_input, (int, float)):
|
||||
debug_print(f"⏱️ 时间输入为数字: {time_input}s ✨")
|
||||
return float(time_input)
|
||||
return float(time_input) # 🔧 确保返回float
|
||||
|
||||
if not time_input or not str(time_input).strip():
|
||||
debug_print(f"⚠️ 时间输入为空,使用默认值: 180s (3分钟) 🕐")
|
||||
@@ -48,7 +48,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
try:
|
||||
value = float(time_str)
|
||||
debug_print(f"✅ 时间解析成功: {time_str} → {value}s(无单位,默认秒)⏰")
|
||||
return value
|
||||
return float(value) # 🔧 确保返回float
|
||||
except ValueError:
|
||||
debug_print(f"❌ 无法解析时间: '{time_str}',使用默认值180s (3分钟) 😅")
|
||||
return 180.0
|
||||
@@ -70,7 +70,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
time_sec = value # 已经是s
|
||||
debug_print(f"🕐 时间转换: {value}s → {time_sec}s (已是秒) ⏰")
|
||||
|
||||
return time_sec
|
||||
return float(time_sec) # 🔧 确保返回float
|
||||
|
||||
def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]:
|
||||
"""
|
||||
@@ -175,7 +175,7 @@ def find_connected_vessel(G: nx.DiGraph, rotavap_device: str) -> Optional[str]:
|
||||
|
||||
def generate_evaporate_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
pressure: float = 0.1,
|
||||
temp: float = 60.0,
|
||||
time: Union[str, float] = "180", # 🔧 修改:支持字符串时间
|
||||
@@ -184,11 +184,11 @@ def generate_evaporate_protocol(
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成蒸发操作的协议序列 - 支持单位
|
||||
生成蒸发操作的协议序列 - 支持单位和体积运算
|
||||
|
||||
Args:
|
||||
G: 设备图
|
||||
vessel: 容器名称或旋转蒸发仪名称
|
||||
vessel: 容器字典(从XDL传入)
|
||||
pressure: 真空度 (bar),默认0.1
|
||||
temp: 加热温度 (°C),默认60
|
||||
time: 蒸发时间(支持 "3 min", "180", "0.5 h" 等)
|
||||
@@ -200,10 +200,22 @@ def generate_evaporate_protocol(
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
debug_print("🌟" * 20)
|
||||
debug_print("🌪️ 开始生成蒸发协议(支持单位)✨")
|
||||
debug_print("🌪️ 开始生成蒸发协议(支持单位和体积运算)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 vessel: {vessel}")
|
||||
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 💨 pressure: {pressure} bar")
|
||||
debug_print(f" 🌡️ temp: {temp}°C")
|
||||
debug_print(f" ⏰ time: {time} (类型: {type(time)})")
|
||||
@@ -211,16 +223,27 @@ def generate_evaporate_protocol(
|
||||
debug_print(f" 🧪 solvent: '{solvent}'")
|
||||
debug_print("🌟" * 20)
|
||||
|
||||
# 🔧 新增:记录蒸发前的容器状态
|
||||
debug_print("🔍 记录蒸发前容器状态...")
|
||||
original_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
original_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
original_liquid_volume = current_volume
|
||||
debug_print(f"📊 蒸发前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# === 步骤1: 查找旋转蒸发仪设备 ===
|
||||
debug_print("📍 步骤1: 查找旋转蒸发仪设备... 🔍")
|
||||
|
||||
# 验证vessel参数
|
||||
if not vessel:
|
||||
if not vessel_id:
|
||||
debug_print("❌ vessel 参数不能为空! 😱")
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
|
||||
# 查找旋转蒸发仪设备
|
||||
rotavap_device = find_rotavap_device(G, vessel)
|
||||
rotavap_device = find_rotavap_device(G, vessel_id)
|
||||
if not rotavap_device:
|
||||
debug_print("💥 未找到旋转蒸发仪设备! 😭")
|
||||
raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap'、'rotary' 或 'evaporat' 的设备")
|
||||
@@ -230,10 +253,10 @@ def generate_evaporate_protocol(
|
||||
# === 步骤2: 确定目标容器 ===
|
||||
debug_print("📍 步骤2: 确定目标容器... 🥽")
|
||||
|
||||
target_vessel = vessel
|
||||
target_vessel = vessel_id
|
||||
|
||||
# 如果vessel就是旋转蒸发仪设备,查找连接的容器
|
||||
if vessel == rotavap_device:
|
||||
if vessel_id == rotavap_device:
|
||||
debug_print("🔄 vessel就是旋转蒸发仪,查找连接的容器...")
|
||||
connected_vessel = find_connected_vessel(G, rotavap_device)
|
||||
if connected_vessel:
|
||||
@@ -242,11 +265,11 @@ def generate_evaporate_protocol(
|
||||
else:
|
||||
debug_print(f"⚠️ 未找到连接的容器,使用设备本身: {rotavap_device} 🔧")
|
||||
target_vessel = rotavap_device
|
||||
elif vessel in G.nodes() and G.nodes[vessel].get('type') == 'container':
|
||||
debug_print(f"✅ 使用指定的容器: {vessel} 🥽✨")
|
||||
target_vessel = vessel
|
||||
elif vessel_id in G.nodes() and G.nodes[vessel_id].get('type') == 'container':
|
||||
debug_print(f"✅ 使用指定的容器: {vessel_id} 🥽✨")
|
||||
target_vessel = vessel_id
|
||||
else:
|
||||
debug_print(f"⚠️ 容器 '{vessel}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧")
|
||||
debug_print(f"⚠️ 容器 '{vessel_id}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧")
|
||||
target_vessel = rotavap_device
|
||||
|
||||
# === 🔧 新增:步骤3:单位解析处理 ===
|
||||
@@ -308,8 +331,49 @@ def generate_evaporate_protocol(
|
||||
|
||||
debug_print(f"🎯 最终参数: pressure={pressure} bar 💨, temp={temp}°C 🌡️, time={final_time}s ⏰, stir_speed={stir_speed} RPM 🌪️")
|
||||
|
||||
# === 步骤5: 生成动作序列 ===
|
||||
debug_print("📍 步骤5: 生成动作序列... 🎬")
|
||||
# === 🔧 新增:步骤5:蒸发体积计算 ===
|
||||
debug_print("📍 步骤5: 蒸发体积计算... 📊")
|
||||
|
||||
# 根据温度、真空度、时间和溶剂类型估算蒸发量
|
||||
evaporation_volume = 0.0
|
||||
if original_liquid_volume > 0:
|
||||
# 基础蒸发速率(mL/min)
|
||||
base_evap_rate = 0.5 # 基础速率
|
||||
|
||||
# 温度系数(高温蒸发更快)
|
||||
temp_factor = 1.0 + (temp - 25.0) / 100.0
|
||||
|
||||
# 真空系数(真空度越高蒸发越快)
|
||||
vacuum_factor = 1.0 + (1.0 - pressure) * 2.0
|
||||
|
||||
# 溶剂系数
|
||||
solvent_factor = 1.0
|
||||
if solvent:
|
||||
solvent_lower = solvent.lower()
|
||||
if any(s in solvent_lower for s in ['water', 'h2o']):
|
||||
solvent_factor = 0.8 # 水蒸发较慢
|
||||
elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
|
||||
solvent_factor = 1.5 # 易挥发溶剂蒸发快
|
||||
elif any(s in solvent_lower for s in ['dmso', 'dmi']):
|
||||
solvent_factor = 0.3 # 高沸点溶剂蒸发慢
|
||||
|
||||
# 计算总蒸发量
|
||||
total_evap_rate = base_evap_rate * temp_factor * vacuum_factor * solvent_factor
|
||||
evaporation_volume = min(
|
||||
original_liquid_volume * 0.95, # 最多蒸发95%
|
||||
total_evap_rate * (final_time / 60.0) # 时间相关的蒸发量
|
||||
)
|
||||
|
||||
debug_print(f"📊 蒸发量计算:")
|
||||
debug_print(f" - 基础蒸发速率: {base_evap_rate} mL/min")
|
||||
debug_print(f" - 温度系数: {temp_factor:.2f} (基于 {temp}°C)")
|
||||
debug_print(f" - 真空系数: {vacuum_factor:.2f} (基于 {pressure} bar)")
|
||||
debug_print(f" - 溶剂系数: {solvent_factor:.2f} ({solvent or '通用'})")
|
||||
debug_print(f" - 总蒸发速率: {total_evap_rate:.2f} mL/min")
|
||||
debug_print(f" - 预计蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/original_liquid_volume*100:.1f}%)")
|
||||
|
||||
# === 步骤6: 生成动作序列 ===
|
||||
debug_print("📍 步骤6: 生成动作序列... 🎬")
|
||||
|
||||
action_sequence = []
|
||||
|
||||
@@ -335,16 +399,52 @@ def generate_evaporate_protocol(
|
||||
"action_name": "evaporate",
|
||||
"action_kwargs": {
|
||||
"vessel": target_vessel,
|
||||
"pressure": pressure,
|
||||
"temp": temp,
|
||||
"time": final_time,
|
||||
"stir_speed": stir_speed,
|
||||
"solvent": solvent
|
||||
"pressure": float(pressure),
|
||||
"temp": float(temp),
|
||||
"time": float(final_time), # 🔧 强制转换为float类型
|
||||
"stir_speed": float(stir_speed),
|
||||
"solvent": str(solvent)
|
||||
}
|
||||
}
|
||||
action_sequence.append(evaporate_action)
|
||||
debug_print(" ✅ 蒸发动作已添加 🌪️✨")
|
||||
|
||||
# 🔧 新增:蒸发过程中的体积变化
|
||||
debug_print(" 🔧 更新容器体积 - 蒸发过程...")
|
||||
if evaporation_volume > 0:
|
||||
new_volume = max(0.0, original_liquid_volume - evaporation_volume)
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [new_volume]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
|
||||
# 🔧 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
|
||||
|
||||
debug_print(f" 📊 蒸发体积变化: {original_liquid_volume:.2f}mL → {new_volume:.2f}mL (-{evaporation_volume:.2f}mL)")
|
||||
|
||||
# 3. 蒸发后等待
|
||||
debug_print(" 🔄 动作3: 添加蒸发后等待... ⏳")
|
||||
action_sequence.append({
|
||||
@@ -353,6 +453,15 @@ def generate_evaporate_protocol(
|
||||
})
|
||||
debug_print(" ✅ 蒸发后等待动作已添加 ⏳✨")
|
||||
|
||||
# 🔧 新增:蒸发完成后的状态报告
|
||||
final_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
final_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
final_liquid_volume = current_volume
|
||||
|
||||
# === 总结 ===
|
||||
debug_print("🎊" * 20)
|
||||
debug_print(f"🎉 蒸发协议生成完成! ✨")
|
||||
@@ -361,6 +470,10 @@ def generate_evaporate_protocol(
|
||||
debug_print(f"🥽 目标容器: {target_vessel} 🧪")
|
||||
debug_print(f"⚙️ 蒸发参数: {pressure} bar 💨, {temp}°C 🌡️, {final_time}s ⏰, {stir_speed} RPM 🌪️")
|
||||
debug_print(f"⏱️ 预计总时间: {(final_time + 20)/60:.1f} 分钟 ⌛")
|
||||
debug_print(f"📊 体积变化:")
|
||||
debug_print(f" - 蒸发前: {original_liquid_volume:.2f}mL")
|
||||
debug_print(f" - 蒸发后: {final_liquid_volume:.2f}mL")
|
||||
debug_print(f" - 蒸发量: {evaporation_volume:.2f}mL ({evaporation_volume/max(original_liquid_volume, 0.01)*100:.1f}%)")
|
||||
debug_print("🎊" * 20)
|
||||
|
||||
return action_sequence
|
||||
|
||||
@@ -50,16 +50,16 @@ def validate_vessel(G: nx.DiGraph, vessel: str, vessel_type: str = "容器") ->
|
||||
|
||||
def generate_filter_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
filtrate_vessel: str = "",
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成过滤操作的协议序列
|
||||
生成过滤操作的协议序列 - 支持体积运算
|
||||
|
||||
Args:
|
||||
G: 设备图
|
||||
vessel: 过滤容器名称(必需)- 包含需要过滤的混合物
|
||||
vessel: 过滤容器字典(必需)- 包含需要过滤的混合物
|
||||
filtrate_vessel: 滤液容器名称(可选)- 如果提供则收集滤液
|
||||
**kwargs: 其他参数(兼容性)
|
||||
|
||||
@@ -67,22 +67,45 @@ def generate_filter_protocol(
|
||||
List[Dict[str, Any]]: 过滤操作的动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
debug_print("🌊" * 20)
|
||||
debug_print("🚀 开始生成过滤协议 ✨")
|
||||
debug_print("🚀 开始生成过滤协议(支持体积运算)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 vessel: {vessel}")
|
||||
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 🧪 filtrate_vessel: {filtrate_vessel}")
|
||||
debug_print(f" ⚙️ 其他参数: {kwargs}")
|
||||
debug_print("🌊" * 20)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# 🔧 新增:记录过滤前的容器状态
|
||||
debug_print("🔍 记录过滤前容器状态...")
|
||||
original_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
original_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
original_liquid_volume = current_volume
|
||||
debug_print(f"📊 过滤前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# === 参数验证 ===
|
||||
debug_print("📍 步骤1: 参数验证... 🔧")
|
||||
|
||||
# 验证必需参数
|
||||
debug_print(" 🔍 验证必需参数...")
|
||||
validate_vessel(G, vessel, "过滤容器")
|
||||
validate_vessel(G, vessel_id, "过滤容器") # 🔧 使用 vessel_id
|
||||
debug_print(" ✅ 必需参数验证完成 🎯")
|
||||
|
||||
# 验证可选参数
|
||||
@@ -106,18 +129,46 @@ def generate_filter_protocol(
|
||||
debug_print(f" ❌ 设备查找失败: {str(e)} 😭")
|
||||
raise ValueError(f"设备查找失败: {str(e)}")
|
||||
|
||||
# 🔧 新增:过滤效率和体积分配估算
|
||||
debug_print("📍 步骤2.5: 过滤体积分配估算... 📊")
|
||||
|
||||
# 估算过滤分离比例(基于经验数据)
|
||||
solid_ratio = 0.1 # 假设10%是固体(保留在过滤器上)
|
||||
liquid_ratio = 0.9 # 假设90%是液体(通过过滤器)
|
||||
volume_loss_ratio = 0.05 # 假设5%体积损失(残留在过滤器等)
|
||||
|
||||
# 从kwargs中获取过滤参数进行优化
|
||||
if "solid_content" in kwargs:
|
||||
try:
|
||||
solid_ratio = float(kwargs["solid_content"])
|
||||
liquid_ratio = 1.0 - solid_ratio
|
||||
debug_print(f"📋 使用指定的固体含量: {solid_ratio*100:.1f}%")
|
||||
except:
|
||||
debug_print("⚠️ 固体含量参数无效,使用默认值")
|
||||
|
||||
if original_liquid_volume > 0:
|
||||
expected_filtrate_volume = original_liquid_volume * liquid_ratio * (1.0 - volume_loss_ratio)
|
||||
expected_solid_volume = original_liquid_volume * solid_ratio
|
||||
volume_loss = original_liquid_volume * volume_loss_ratio
|
||||
|
||||
debug_print(f"📊 过滤体积分配估算:")
|
||||
debug_print(f" - 原始体积: {original_liquid_volume:.2f}mL")
|
||||
debug_print(f" - 预计滤液体积: {expected_filtrate_volume:.2f}mL ({liquid_ratio*100:.1f}%)")
|
||||
debug_print(f" - 预计固体体积: {expected_solid_volume:.2f}mL ({solid_ratio*100:.1f}%)")
|
||||
debug_print(f" - 预计损失体积: {volume_loss:.2f}mL ({volume_loss_ratio*100:.1f}%)")
|
||||
|
||||
# === 转移到过滤器(如果需要)===
|
||||
debug_print("📍 步骤3: 转移到过滤器... 🚚")
|
||||
|
||||
if vessel != filter_device:
|
||||
debug_print(f" 🚛 需要转移: {vessel} → {filter_device} 📦")
|
||||
if vessel_id != filter_device: # 🔧 使用 vessel_id
|
||||
debug_print(f" 🚛 需要转移: {vessel_id} → {filter_device} 📦")
|
||||
|
||||
try:
|
||||
debug_print(" 🔄 开始执行转移操作...")
|
||||
# 使用pump protocol转移液体到过滤器
|
||||
transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
from_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
to_vessel=filter_device,
|
||||
volume=0.0, # 转移所有液体
|
||||
amount="",
|
||||
@@ -134,6 +185,26 @@ def generate_filter_protocol(
|
||||
if transfer_actions:
|
||||
action_sequence.extend(transfer_actions)
|
||||
debug_print(f" ✅ 添加了 {len(transfer_actions)} 个转移动作 🚚✨")
|
||||
|
||||
# 🔧 新增:转移后更新容器体积
|
||||
debug_print(" 🔧 更新转移后的容器体积...")
|
||||
|
||||
# 原容器体积变为0(所有液体已转移)
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
vessel["data"]["liquid_volume"] = [0.0] if len(current_volume) > 0 else [0.0]
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = 0.0
|
||||
|
||||
# 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = 0.0
|
||||
|
||||
debug_print(f" 📊 转移完成,{vessel_id} 体积更新为 0.0mL")
|
||||
|
||||
else:
|
||||
debug_print(" ⚠️ 转移协议返回空序列 🤔")
|
||||
|
||||
@@ -206,6 +277,26 @@ def generate_filter_protocol(
|
||||
if collect_actions:
|
||||
action_sequence.extend(collect_actions)
|
||||
debug_print(f" ✅ 添加了 {len(collect_actions)} 个收集动作 🧪✨")
|
||||
|
||||
# 🔧 新增:收集滤液后的体积更新
|
||||
debug_print(" 🔧 更新滤液容器体积...")
|
||||
|
||||
# 更新filtrate_vessel在图中的体积(如果它是节点)
|
||||
if filtrate_vessel in G.nodes():
|
||||
if 'data' not in G.nodes[filtrate_vessel]:
|
||||
G.nodes[filtrate_vessel]['data'] = {}
|
||||
|
||||
current_filtrate_volume = G.nodes[filtrate_vessel]['data'].get('liquid_volume', 0.0)
|
||||
if isinstance(current_filtrate_volume, list):
|
||||
if len(current_filtrate_volume) > 0:
|
||||
G.nodes[filtrate_vessel]['data']['liquid_volume'][0] += expected_filtrate_volume
|
||||
else:
|
||||
G.nodes[filtrate_vessel]['data']['liquid_volume'] = [expected_filtrate_volume]
|
||||
else:
|
||||
G.nodes[filtrate_vessel]['data']['liquid_volume'] = current_filtrate_volume + expected_filtrate_volume
|
||||
|
||||
debug_print(f" 📊 滤液容器 {filtrate_vessel} 体积增加 {expected_filtrate_volume:.2f}mL")
|
||||
|
||||
else:
|
||||
debug_print(" ⚠️ 收集协议返回空序列 🤔")
|
||||
|
||||
@@ -215,6 +306,37 @@ def generate_filter_protocol(
|
||||
else:
|
||||
debug_print(" 🧱 未指定滤液容器,固体保留在过滤器中 🔬")
|
||||
|
||||
# 🔧 新增:过滤完成后的容器状态更新
|
||||
debug_print("📍 步骤5.5: 过滤完成后状态更新... 📊")
|
||||
|
||||
if vessel_id == filter_device:
|
||||
# 如果过滤容器就是过滤器,需要更新其体积状态
|
||||
if original_liquid_volume > 0:
|
||||
if filtrate_vessel:
|
||||
# 收集滤液模式:过滤器中主要保留固体
|
||||
remaining_volume = expected_solid_volume
|
||||
debug_print(f" 🧱 过滤器中保留固体: {remaining_volume:.2f}mL")
|
||||
else:
|
||||
# 保留固体模式:过滤器中保留所有物质
|
||||
remaining_volume = original_liquid_volume * (1.0 - volume_loss_ratio)
|
||||
debug_print(f" 🔬 过滤器中保留所有物质: {remaining_volume:.2f}mL")
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
vessel["data"]["liquid_volume"] = [remaining_volume] if len(current_volume) > 0 else [remaining_volume]
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = remaining_volume
|
||||
|
||||
# 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = remaining_volume
|
||||
|
||||
debug_print(f" 📊 过滤器 {vessel_id} 体积更新为: {remaining_volume:.2f}mL")
|
||||
|
||||
# === 最终等待 ===
|
||||
debug_print("📍 步骤6: 最终等待... ⏰")
|
||||
action_sequence.append({
|
||||
@@ -223,14 +345,31 @@ def generate_filter_protocol(
|
||||
})
|
||||
debug_print(" ✅ 最终等待动作已添加 ⏰✨")
|
||||
|
||||
# 🔧 新增:过滤完成后的状态报告
|
||||
final_vessel_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
final_vessel_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
final_vessel_volume = current_volume
|
||||
|
||||
# === 总结 ===
|
||||
debug_print("🎊" * 20)
|
||||
debug_print(f"🎉 过滤协议生成完成! ✨")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝")
|
||||
debug_print(f"🥽 过滤容器: {vessel} 🧪")
|
||||
debug_print(f"🥽 过滤容器: {vessel_id} 🧪")
|
||||
debug_print(f"🌊 过滤器设备: {filter_device} 🔧")
|
||||
debug_print(f"💧 滤液容器: {filtrate_vessel or '无(保留固体)'} 🧱")
|
||||
debug_print(f"⏱️ 预计总时间: {(len(action_sequence) * 5):.0f} 秒 ⌛")
|
||||
if original_liquid_volume > 0:
|
||||
debug_print(f"📊 体积变化统计:")
|
||||
debug_print(f" - 过滤前体积: {original_liquid_volume:.2f}mL")
|
||||
debug_print(f" - 过滤后容器体积: {final_vessel_volume:.2f}mL")
|
||||
if filtrate_vessel:
|
||||
debug_print(f" - 预计滤液体积: {expected_filtrate_volume:.2f}mL")
|
||||
debug_print(f" - 预计损失体积: {volume_loss:.2f}mL")
|
||||
debug_print("🎊" * 20)
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ def validate_and_fix_params(temp: float, time: float, stir_speed: float) -> tupl
|
||||
|
||||
def generate_heat_chill_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
temp: float = 25.0,
|
||||
time: Union[str, float] = "300",
|
||||
temp_spec: str = "",
|
||||
@@ -196,29 +196,59 @@ def generate_heat_chill_protocol(
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成加热/冷却操作的协议序列
|
||||
生成加热/冷却操作的协议序列 - 支持vessel字典
|
||||
|
||||
Args:
|
||||
G: 设备图
|
||||
vessel: 容器字典(从XDL传入)
|
||||
temp: 目标温度 (°C)
|
||||
time: 加热时间(支持字符串如 "30 min")
|
||||
temp_spec: 温度规格说明(优先级高于temp)
|
||||
time_spec: 时间规格说明(优先级高于time)
|
||||
pressure: 压力设置
|
||||
reflux_solvent: 回流溶剂
|
||||
stir: 是否搅拌
|
||||
stir_speed: 搅拌速度 (RPM)
|
||||
purpose: 操作目的说明
|
||||
**kwargs: 其他参数(兼容性)
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 加热/冷却操作的动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
debug_print("🌡️" * 20)
|
||||
debug_print("🚀 开始生成加热冷却协议 ✨")
|
||||
debug_print("🚀 开始生成加热冷却协议(支持vessel字典)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 vessel: {vessel}")
|
||||
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 🌡️ temp: {temp}°C")
|
||||
debug_print(f" ⏰ time: {time}")
|
||||
debug_print(f" 🎯 temp_spec: {temp_spec}")
|
||||
debug_print(f" ⏱️ time_spec: {time_spec}")
|
||||
debug_print(f" 🌪️ stir: {stir} ({stir_speed} RPM)")
|
||||
debug_print(f" 🎭 purpose: '{purpose}'")
|
||||
debug_print("🌡️" * 20)
|
||||
|
||||
# 📋 参数验证
|
||||
debug_print("📍 步骤1: 参数验证... 🔧")
|
||||
if not vessel:
|
||||
if not vessel_id: # 🔧 使用 vessel_id
|
||||
debug_print("❌ vessel 参数不能为空! 😱")
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中! 😞")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中! 😞")
|
||||
raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
|
||||
|
||||
debug_print("✅ 基础参数验证通过 🎯")
|
||||
|
||||
@@ -239,7 +269,7 @@ def generate_heat_chill_protocol(
|
||||
# 🔍 查找设备
|
||||
debug_print("📍 步骤3: 查找加热设备... 🔍")
|
||||
try:
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
|
||||
debug_print(f"🎉 使用加热设备: {heatchill_id} ✨")
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
|
||||
@@ -265,7 +295,7 @@ def generate_heat_chill_protocol(
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"temp": float(final_temp),
|
||||
"time": float(final_time),
|
||||
"stir": bool(stir),
|
||||
@@ -284,7 +314,7 @@ def generate_heat_chill_protocol(
|
||||
debug_print("🎊" * 20)
|
||||
debug_print(f"🎉 加热冷却协议生成完成! ✨")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f"🥽 加热容器: {vessel}")
|
||||
debug_print(f"🥽 加热容器: {vessel_id}")
|
||||
debug_print(f"🌡️ 目标温度: {final_temp}°C")
|
||||
debug_print(f"⏰ 加热时间: {final_time}s ({final_time/60:.1f}分钟)")
|
||||
debug_print("🎊" * 20)
|
||||
@@ -293,41 +323,45 @@ def generate_heat_chill_protocol(
|
||||
|
||||
def generate_heat_chill_to_temp_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改参数类型
|
||||
temp: float = 25.0,
|
||||
time: Union[str, float] = 100.0,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""生成加热到指定温度的协议(简化版)"""
|
||||
debug_print(f"🌡️ 生成加热到温度协议: {vessel} → {temp}°C")
|
||||
vessel_id = vessel["id"]
|
||||
debug_print(f"🌡️ 生成加热到温度协议: {vessel_id} → {temp}°C")
|
||||
return generate_heat_chill_protocol(G, vessel, temp, time, **kwargs)
|
||||
|
||||
def generate_heat_chill_start_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改参数类型
|
||||
temp: float = 25.0,
|
||||
purpose: str = "",
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""生成开始加热操作的协议序列"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
vessel_id = vessel["id"]
|
||||
|
||||
debug_print("🔥 开始生成启动加热协议 ✨")
|
||||
debug_print(f"🥽 vessel: {vessel}, 🌡️ temp: {temp}°C")
|
||||
debug_print(f"🥽 vessel: {vessel} (ID: {vessel_id}), 🌡️ temp: {temp}°C")
|
||||
|
||||
# 基础验证
|
||||
if not vessel or vessel not in G.nodes():
|
||||
if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
debug_print("❌ 容器验证失败!")
|
||||
raise ValueError("vessel 参数无效")
|
||||
|
||||
# 查找设备
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
|
||||
|
||||
# 生成动作
|
||||
action_sequence = [{
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"temp": temp,
|
||||
"purpose": purpose or f"开始加热到 {temp}°C"
|
||||
}
|
||||
@@ -338,39 +372,34 @@ def generate_heat_chill_start_protocol(
|
||||
|
||||
def generate_heat_chill_stop_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: dict, # 🔧 修改参数类型
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""生成停止加热操作的协议序列"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
vessel_id = vessel["id"]
|
||||
|
||||
debug_print("🛑 开始生成停止加热协议 ✨")
|
||||
debug_print(f"🥽 vessel: {vessel}")
|
||||
debug_print(f"🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
|
||||
# 基础验证
|
||||
if not vessel or vessel not in G.nodes():
|
||||
if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
debug_print("❌ 容器验证失败!")
|
||||
raise ValueError("vessel 参数无效")
|
||||
|
||||
# 查找设备
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
|
||||
|
||||
# 生成动作
|
||||
action_sequence = [{
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
"vessel": vessel_id # 🔧 使用 vessel_id
|
||||
}
|
||||
}]
|
||||
|
||||
debug_print(f"✅ 停止加热协议生成完成 🎯")
|
||||
return action_sequence
|
||||
|
||||
# 测试函数
|
||||
def test_heatchill_protocol():
|
||||
"""测试加热协议"""
|
||||
debug_print("🧪 === HEATCHILL PROTOCOL 测试 === ✨")
|
||||
debug_print("✅ 测试完成 🎉")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_heatchill_protocol()
|
||||
@@ -150,75 +150,112 @@ def find_connected_device(G: nx.DiGraph, vessel: str, device_type: str) -> str:
|
||||
|
||||
def generate_hydrogenate_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
temp: str,
|
||||
time: str,
|
||||
vessel: str,
|
||||
**kwargs # 接收其他可能的参数但不使用
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成氢化反应协议序列
|
||||
生成氢化反应协议序列 - 支持vessel字典
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为容器和设备
|
||||
vessel: 反应容器字典(从XDL传入)
|
||||
temp: 反应温度(如 "45 °C")
|
||||
time: 反应时间(如 "2 h")
|
||||
vessel: 反应容器
|
||||
**kwargs: 其他可选参数,但不使用
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# 解析参数
|
||||
temperature = parse_temperature(temp)
|
||||
reaction_time = parse_time(time)
|
||||
|
||||
print(f"HYDROGENATE: 开始生成氢化反应协议")
|
||||
print(f" - 反应温度: {temperature}°C")
|
||||
print(f" - 反应时间: {reaction_time/3600:.1f} 小时")
|
||||
print(f" - 反应容器: {vessel}")
|
||||
print("🧪" * 20)
|
||||
print(f"HYDROGENATE: 开始生成氢化反应协议(支持vessel字典)✨")
|
||||
print(f"📝 输入参数:")
|
||||
print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
print(f" 🌡️ 反应温度: {temperature}°C")
|
||||
print(f" ⏰ 反应时间: {reaction_time/3600:.1f} 小时")
|
||||
print("🧪" * 20)
|
||||
|
||||
# 🔧 新增:记录氢化前的容器状态(可选,氢化反应通常不改变体积)
|
||||
original_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
original_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
original_liquid_volume = current_volume
|
||||
print(f"📊 氢化前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# 1. 验证目标容器存在
|
||||
if vessel not in G.nodes():
|
||||
print(f"HYDROGENATE: 警告 - 容器 '{vessel}' 不存在于系统中,跳过氢化反应")
|
||||
print("📍 步骤1: 验证目标容器...")
|
||||
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
print(f"⚠️ HYDROGENATE: 警告 - 容器 '{vessel_id}' 不存在于系统中,跳过氢化反应")
|
||||
return action_sequence
|
||||
print(f"✅ 容器 '{vessel_id}' 验证通过")
|
||||
|
||||
# 2. 查找相连的设备
|
||||
heater_id = find_connected_device(G, vessel, 'heater')
|
||||
stirrer_id = find_connected_device(G, vessel, 'stirrer')
|
||||
gas_source_id = find_connected_device(G, vessel, 'gas_source')
|
||||
print("📍 步骤2: 查找相连设备...")
|
||||
heater_id = find_connected_device(G, vessel_id, 'heater') # 🔧 使用 vessel_id
|
||||
stirrer_id = find_connected_device(G, vessel_id, 'stirrer') # 🔧 使用 vessel_id
|
||||
gas_source_id = find_connected_device(G, vessel_id, 'gas_source') # 🔧 使用 vessel_id
|
||||
|
||||
print(f"🔧 设备配置:")
|
||||
print(f" 🔥 加热器: {heater_id or '未找到'}")
|
||||
print(f" 🌪️ 搅拌器: {stirrer_id or '未找到'}")
|
||||
print(f" 💨 气源: {gas_source_id or '未找到'}")
|
||||
|
||||
# 3. 启动搅拌器
|
||||
print("📍 步骤3: 启动搅拌器...")
|
||||
if stirrer_id:
|
||||
print(f"HYDROGENATE: 启动搅拌器 {stirrer_id}")
|
||||
print(f"🌪️ 启动搅拌器 {stirrer_id}")
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"stir_speed": 300.0,
|
||||
"purpose": "氢化反应: 开始搅拌"
|
||||
}
|
||||
})
|
||||
print("✅ 搅拌器启动动作已添加")
|
||||
else:
|
||||
print(f"HYDROGENATE: 警告 - 未找到搅拌器,继续执行")
|
||||
print(f"⚠️ HYDROGENATE: 警告 - 未找到搅拌器,继续执行")
|
||||
|
||||
# 4. 启动气源(氢气)- 修复版本
|
||||
# 4. 启动气源(氢气)
|
||||
print("📍 步骤4: 启动氢气源...")
|
||||
if gas_source_id:
|
||||
print(f"HYDROGENATE: 启动气源 {gas_source_id} (氢气)")
|
||||
print(f"💨 启动气源 {gas_source_id} (氢气)")
|
||||
action_sequence.append({
|
||||
"device_id": gas_source_id,
|
||||
"action_name": "set_status", # 修改为 set_status
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {
|
||||
"string": "ON" # 修改参数格式
|
||||
"string": "ON"
|
||||
}
|
||||
})
|
||||
|
||||
# 查找相关的电磁阀
|
||||
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
|
||||
if gas_solenoid:
|
||||
print(f"HYDROGENATE: 开启气源电磁阀 {gas_solenoid}")
|
||||
print(f"🚪 开启气源电磁阀 {gas_solenoid}")
|
||||
action_sequence.append({
|
||||
"device_id": gas_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
@@ -226,10 +263,12 @@ def generate_hydrogenate_protocol(
|
||||
"command": "OPEN"
|
||||
}
|
||||
})
|
||||
print("✅ 氢气源启动动作已添加")
|
||||
else:
|
||||
print(f"HYDROGENATE: 警告 - 未找到气源,继续执行")
|
||||
print(f"⚠️ HYDROGENATE: 警告 - 未找到气源,继续执行")
|
||||
|
||||
# 5. 等待气体稳定
|
||||
print("📍 步骤5: 等待气体环境稳定...")
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
@@ -237,15 +276,17 @@ def generate_hydrogenate_protocol(
|
||||
"description": "等待氢气环境稳定"
|
||||
}
|
||||
})
|
||||
print("✅ 气体稳定等待动作已添加")
|
||||
|
||||
# 6. 启动加热器
|
||||
print("📍 步骤6: 启动加热反应...")
|
||||
if heater_id:
|
||||
print(f"HYDROGENATE: 启动加热器 {heater_id} 到 {temperature}°C")
|
||||
print(f"🔥 启动加热器 {heater_id} 到 {temperature}°C")
|
||||
action_sequence.append({
|
||||
"device_id": heater_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"temp": temperature,
|
||||
"purpose": f"氢化反应: 加热到 {temperature}°C"
|
||||
}
|
||||
@@ -261,23 +302,23 @@ def generate_hydrogenate_protocol(
|
||||
})
|
||||
|
||||
# 🕐 模拟运行时间优化
|
||||
print("HYDROGENATE: 检查模拟运行时间限制...")
|
||||
print(" ⏰ 检查模拟运行时间限制...")
|
||||
original_reaction_time = reaction_time
|
||||
simulation_time_limit = 60.0 # 模拟运行时间限制:60秒
|
||||
|
||||
if reaction_time > simulation_time_limit:
|
||||
reaction_time = simulation_time_limit
|
||||
print(f"HYDROGENATE: 模拟运行优化: {original_reaction_time}s → {reaction_time}s (限制为{simulation_time_limit}s)")
|
||||
print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
|
||||
print(f" 🎮 模拟运行优化: {original_reaction_time}s → {reaction_time}s (限制为{simulation_time_limit}s)")
|
||||
print(f" 📊 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
|
||||
else:
|
||||
print(f"HYDROGENATE: 时间在限制内: {reaction_time}s ({reaction_time/60:.1f}分钟) 保持不变")
|
||||
print(f" ✅ 时间在限制内: {reaction_time}s ({reaction_time/60:.1f}分钟) 保持不变")
|
||||
|
||||
# 保持反应温度
|
||||
action_sequence.append({
|
||||
"device_id": heater_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"temp": temperature,
|
||||
"time": reaction_time,
|
||||
"purpose": f"氢化反应: 保持 {temperature}°C,反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "")
|
||||
@@ -286,22 +327,24 @@ def generate_hydrogenate_protocol(
|
||||
|
||||
# 显示时间调整信息
|
||||
if original_reaction_time != reaction_time:
|
||||
print(f"HYDROGENATE: 模拟优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
|
||||
print(f" 🎭 模拟优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
|
||||
|
||||
print("✅ 加热反应动作已添加")
|
||||
|
||||
else:
|
||||
print(f"HYDROGENATE: 警告 - 未找到加热器,使用室温反应")
|
||||
print(f"⚠️ HYDROGENATE: 警告 - 未找到加热器,使用室温反应")
|
||||
|
||||
# 🕐 室温反应也需要时间优化
|
||||
print("HYDROGENATE: 检查室温反应模拟时间限制...")
|
||||
print(" ⏰ 检查室温反应模拟时间限制...")
|
||||
original_reaction_time = reaction_time
|
||||
simulation_time_limit = 60.0 # 模拟运行时间限制:60秒
|
||||
|
||||
if reaction_time > simulation_time_limit:
|
||||
reaction_time = simulation_time_limit
|
||||
print(f"HYDROGENATE: 室温反应时间优化: {original_reaction_time}s → {reaction_time}s")
|
||||
print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
|
||||
print(f" 🎮 室温反应时间优化: {original_reaction_time}s → {reaction_time}s")
|
||||
print(f" 📊 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟")
|
||||
else:
|
||||
print(f"HYDROGENATE: 室温反应时间在限制内: {reaction_time}s 保持不变")
|
||||
print(f" ✅ 室温反应时间在限制内: {reaction_time}s 保持不变")
|
||||
|
||||
# 室温反应,只等待时间
|
||||
action_sequence.append({
|
||||
@@ -314,20 +357,25 @@ def generate_hydrogenate_protocol(
|
||||
|
||||
# 显示时间调整信息
|
||||
if original_reaction_time != reaction_time:
|
||||
print(f"HYDROGENATE: 室温反应优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
|
||||
print(f" 🎭 室温反应优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟")
|
||||
|
||||
print("✅ 室温反应等待动作已添加")
|
||||
|
||||
# 7. 停止加热
|
||||
print("📍 步骤7: 停止加热...")
|
||||
if heater_id:
|
||||
action_sequence.append({
|
||||
"device_id": heater_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"purpose": "氢化反应完成,停止加热"
|
||||
}
|
||||
})
|
||||
print("✅ 停止加热动作已添加")
|
||||
|
||||
# 8. 等待冷却
|
||||
print("📍 步骤8: 等待冷却...")
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
@@ -335,13 +383,15 @@ def generate_hydrogenate_protocol(
|
||||
"description": "等待反应混合物冷却"
|
||||
}
|
||||
})
|
||||
print("✅ 冷却等待动作已添加")
|
||||
|
||||
# 9. 停止气源 - 修复版本
|
||||
# 9. 停止气源
|
||||
print("📍 步骤9: 停止氢气源...")
|
||||
if gas_source_id:
|
||||
# 先关闭电磁阀
|
||||
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
|
||||
if gas_solenoid:
|
||||
print(f"HYDROGENATE: 关闭气源电磁阀 {gas_solenoid}")
|
||||
print(f"🚪 关闭气源电磁阀 {gas_solenoid}")
|
||||
action_sequence.append({
|
||||
"device_id": gas_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
@@ -353,25 +403,41 @@ def generate_hydrogenate_protocol(
|
||||
# 再关闭气源
|
||||
action_sequence.append({
|
||||
"device_id": gas_source_id,
|
||||
"action_name": "set_status", # 修改为 set_status
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {
|
||||
"string": "OFF" # 修改参数格式
|
||||
"string": "OFF"
|
||||
}
|
||||
})
|
||||
print("✅ 氢气源停止动作已添加")
|
||||
|
||||
# 10. 停止搅拌
|
||||
print("📍 步骤10: 停止搅拌...")
|
||||
if stirrer_id:
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"purpose": "氢化反应完成,停止搅拌"
|
||||
}
|
||||
})
|
||||
print("✅ 停止搅拌动作已添加")
|
||||
|
||||
print(f"HYDROGENATE: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||
print(f"HYDROGENATE: 预计总时间: {(reaction_time + 450)/3600:.1f} 小时")
|
||||
# 🔧 新增:氢化完成后的状态(氢化反应通常不改变体积)
|
||||
final_liquid_volume = original_liquid_volume # 氢化反应体积基本不变
|
||||
|
||||
# 总结
|
||||
print("🎊" * 20)
|
||||
print(f"🎉 氢化反应协议生成完成! ✨")
|
||||
print(f"📊 总动作数: {len(action_sequence)} 个")
|
||||
print(f"🥽 反应容器: {vessel_id}")
|
||||
print(f"🌡️ 反应温度: {temperature}°C")
|
||||
print(f"⏰ 反应时间: {reaction_time/60:.1f}分钟")
|
||||
print(f"⏱️ 预计总时间: {(reaction_time + 450)/3600:.1f} 小时")
|
||||
print(f"📊 体积状态:")
|
||||
print(f" - 反应前体积: {original_liquid_volume:.2f}mL")
|
||||
print(f" - 反应后体积: {final_liquid_volume:.2f}mL (氢化反应体积基本不变)")
|
||||
print("🎊" * 20)
|
||||
|
||||
return action_sequence
|
||||
|
||||
@@ -379,7 +445,7 @@ def generate_hydrogenate_protocol(
|
||||
# 测试函数
|
||||
def test_hydrogenate_protocol():
|
||||
"""测试氢化反应协议"""
|
||||
print("=== HYDROGENATE PROTOCOL 测试 ===")
|
||||
print("🧪 === HYDROGENATE PROTOCOL 测试 === ✨")
|
||||
|
||||
# 测试温度解析
|
||||
test_temps = ["45 °C", "45°C", "45", "25 C", "invalid"]
|
||||
@@ -393,7 +459,7 @@ def test_hydrogenate_protocol():
|
||||
parsed = parse_time(time)
|
||||
print(f"时间 '{time}' -> {parsed/3600:.1f} 小时")
|
||||
|
||||
print("测试完成")
|
||||
print("✅ 测试完成 🎉")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import networkx as nx
|
||||
import re
|
||||
import logging
|
||||
from typing import List, Dict, Any, Tuple, Union
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def debug_print(message):
|
||||
"""调试输出"""
|
||||
print(f"💎 [RECRYSTALLIZE] {message}", flush=True)
|
||||
logger.info(f"[RECRYSTALLIZE] {message}")
|
||||
|
||||
|
||||
def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: str = "mL") -> float:
|
||||
@@ -167,80 +170,165 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name} ✨")
|
||||
return vessel_name
|
||||
|
||||
# 第二步:通过模糊匹配
|
||||
# 第二步:通过模糊匹配(节点ID和名称)
|
||||
debug_print(" 🔍 步骤2: 模糊名称匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_name = G.nodes[node_id].get('name', '').lower()
|
||||
|
||||
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
|
||||
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} ✨")
|
||||
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} (名称: {node_name}) ✨")
|
||||
return node_id
|
||||
|
||||
# 第三步:通过液体类型匹配
|
||||
debug_print(" 🧪 步骤3: 液体类型匹配...")
|
||||
# 第三步:通过配置中的试剂信息匹配
|
||||
debug_print(" 🧪 步骤3: 配置试剂信息匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
# 检查 config 中的 reagent 字段
|
||||
node_config = G.nodes[node_id].get('config', {})
|
||||
config_reagent = node_config.get('reagent', '').lower()
|
||||
|
||||
if config_reagent and solvent.lower() == config_reagent:
|
||||
debug_print(f" 🎉 通过config.reagent匹配找到容器: {node_id} (试剂: {config_reagent}) ✨")
|
||||
return node_id
|
||||
|
||||
# 第四步:通过数据中的试剂信息匹配
|
||||
debug_print(" 🧪 步骤4: 数据试剂信息匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
vessel_data = G.nodes[node_id].get('data', {})
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
|
||||
# 检查 data 中的 reagent_name 字段
|
||||
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||
if reagent_name and solvent.lower() == reagent_name:
|
||||
debug_print(f" 🎉 通过data.reagent_name匹配找到容器: {node_id} (试剂: {reagent_name}) ✨")
|
||||
return node_id
|
||||
|
||||
# 检查 data 中的液体信息
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
for liquid in liquids:
|
||||
if isinstance(liquid, dict):
|
||||
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
|
||||
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||
|
||||
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
|
||||
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} ✨")
|
||||
if solvent.lower() in liquid_type:
|
||||
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} (液体类型: {liquid_type}) ✨")
|
||||
return node_id
|
||||
|
||||
# 第五步:部分匹配(如果前面都没找到)
|
||||
debug_print(" 🔍 步骤5: 部分匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_config = G.nodes[node_id].get('config', {})
|
||||
node_data = G.nodes[node_id].get('data', {})
|
||||
node_name = G.nodes[node_id].get('name', '').lower()
|
||||
|
||||
config_reagent = node_config.get('reagent', '').lower()
|
||||
data_reagent = node_data.get('reagent_name', '').lower()
|
||||
|
||||
# 检查是否包含溶剂名称
|
||||
if (solvent.lower() in config_reagent or
|
||||
solvent.lower() in data_reagent or
|
||||
solvent.lower() in node_name or
|
||||
solvent.lower() in node_id.lower()):
|
||||
debug_print(f" 🎉 通过部分匹配找到容器: {node_id} ✨")
|
||||
debug_print(f" - 节点名称: {node_name}")
|
||||
debug_print(f" - 配置试剂: {config_reagent}")
|
||||
debug_print(f" - 数据试剂: {data_reagent}")
|
||||
return node_id
|
||||
|
||||
# 调试信息:列出所有容器
|
||||
debug_print(" 🔎 调试信息:列出所有容器...")
|
||||
container_list = []
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_config = G.nodes[node_id].get('config', {})
|
||||
node_data = G.nodes[node_id].get('data', {})
|
||||
node_name = G.nodes[node_id].get('name', '')
|
||||
|
||||
container_info = {
|
||||
'id': node_id,
|
||||
'name': node_name,
|
||||
'config_reagent': node_config.get('reagent', ''),
|
||||
'data_reagent': node_data.get('reagent_name', '')
|
||||
}
|
||||
container_list.append(container_info)
|
||||
debug_print(f" - 容器: {node_id}, 名称: {node_name}, config试剂: {node_config.get('reagent', '')}, data试剂: {node_data.get('reagent_name', '')}")
|
||||
|
||||
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭")
|
||||
debug_print(f"🔍 查找的溶剂: '{solvent}' (小写: '{solvent.lower()}')")
|
||||
debug_print(f"📊 总共发现 {len(container_list)} 个容器")
|
||||
|
||||
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
|
||||
|
||||
|
||||
def generate_recrystallize_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
ratio: str,
|
||||
solvent1: str,
|
||||
solvent2: str,
|
||||
vessel: str,
|
||||
volume: Union[str, float], # 🔧 修改:支持字符串和数值
|
||||
volume: Union[str, float], # 支持字符串和数值
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成重结晶协议序列 - 支持单位
|
||||
生成重结晶协议序列 - 支持vessel字典和体积运算
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为容器和设备
|
||||
vessel: 目标容器字典(从XDL传入)
|
||||
ratio: 溶剂比例(如 "1:1", "3:7")
|
||||
solvent1: 第一种溶剂名称
|
||||
solvent2: 第二种溶剂名称
|
||||
vessel: 目标容器
|
||||
volume: 总体积(支持 "100 mL", "50", "2.5 L" 等)
|
||||
**kwargs: 其他可选参数
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
action_sequence = []
|
||||
|
||||
debug_print("💎" * 20)
|
||||
debug_print("🚀 开始生成重结晶协议(支持单位)✨")
|
||||
debug_print("🚀 开始生成重结晶协议(支持vessel字典和体积运算)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" ⚖️ 比例: {ratio}")
|
||||
debug_print(f" 🧪 溶剂1: {solvent1}")
|
||||
debug_print(f" 🧪 溶剂2: {solvent2}")
|
||||
debug_print(f" 🥽 容器: {vessel}")
|
||||
debug_print(f" 💧 总体积: {volume} (类型: {type(volume)})")
|
||||
debug_print("💎" * 20)
|
||||
|
||||
# 🔧 新增:记录重结晶前的容器状态
|
||||
debug_print("🔍 记录重结晶前容器状态...")
|
||||
original_liquid_volume = 0.0
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||||
original_liquid_volume = current_volume[0]
|
||||
elif isinstance(current_volume, (int, float)):
|
||||
original_liquid_volume = current_volume
|
||||
debug_print(f"📊 重结晶前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# 1. 验证目标容器存在
|
||||
debug_print("📍 步骤1: 验证目标容器... 🔧")
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中! 😱")
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
debug_print(f"✅ 目标容器 '{vessel}' 验证通过 🎯")
|
||||
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
debug_print(f"❌ 目标容器 '{vessel_id}' 不存在于系统中! 😱")
|
||||
raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
|
||||
debug_print(f"✅ 目标容器 '{vessel_id}' 验证通过 🎯")
|
||||
|
||||
# 2. 🔧 新增:解析体积(支持单位)
|
||||
# 2. 解析体积(支持单位)
|
||||
debug_print("📍 步骤2: 解析体积(支持单位)... 💧")
|
||||
final_volume = parse_volume_with_units(volume, "mL")
|
||||
debug_print(f"🎯 体积解析完成: {volume} → {final_volume}mL ✨")
|
||||
@@ -281,18 +369,18 @@ def generate_recrystallize_protocol(
|
||||
# 6. 验证路径存在
|
||||
debug_print("📍 步骤6: 验证传输路径... 🛤️")
|
||||
try:
|
||||
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel)
|
||||
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel_id) # 🔧 使用 vessel_id
|
||||
debug_print(f" 🛤️ 溶剂1路径: {' → '.join(path1)} ✅")
|
||||
except nx.NetworkXNoPath:
|
||||
debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel} → {vessel} 😞")
|
||||
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||
debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel} → {vessel_id} 😞")
|
||||
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
|
||||
|
||||
try:
|
||||
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel)
|
||||
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel_id) # 🔧 使用 vessel_id
|
||||
debug_print(f" 🛤️ 溶剂2路径: {' → '.join(path2)} ✅")
|
||||
except nx.NetworkXNoPath:
|
||||
debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel} → {vessel} 😞")
|
||||
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||
debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel} → {vessel_id} 😞")
|
||||
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel_id}' 没有可用路径")
|
||||
|
||||
# 7. 添加第一种溶剂
|
||||
debug_print("📍 步骤7: 添加第一种溶剂... 🧪")
|
||||
@@ -302,7 +390,7 @@ def generate_recrystallize_protocol(
|
||||
pump_actions1 = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent1_vessel,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
volume=volume1, # 使用解析后的体积
|
||||
amount="",
|
||||
time=0.0,
|
||||
@@ -322,12 +410,45 @@ def generate_recrystallize_protocol(
|
||||
debug_print(f" ❌ 溶剂1泵协议生成失败: {str(e)} 😭")
|
||||
raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}")
|
||||
|
||||
# 🔧 新增:更新容器体积 - 添加溶剂1后
|
||||
debug_print(" 🔧 更新容器体积 - 添加溶剂1后...")
|
||||
new_volume_after_solvent1 = original_liquid_volume + volume1
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = new_volume_after_solvent1
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [new_volume_after_solvent1]
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume_after_solvent1
|
||||
|
||||
# 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume_after_solvent1
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume_after_solvent1]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume_after_solvent1
|
||||
|
||||
debug_print(f" 📊 体积更新: {original_liquid_volume:.2f}mL + {volume1:.2f}mL = {new_volume_after_solvent1:.2f}mL")
|
||||
|
||||
# 8. 等待溶剂1稳定
|
||||
debug_print(" ⏳ 添加溶剂1稳定等待...")
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 5.0, # 🕐 缩短等待时间:10.0s → 5.0s
|
||||
"time": 5.0, # 缩短等待时间
|
||||
"description": f"等待溶剂1 {solvent1} 稳定"
|
||||
}
|
||||
})
|
||||
@@ -341,7 +462,7 @@ def generate_recrystallize_protocol(
|
||||
pump_actions2 = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent2_vessel,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
volume=volume2, # 使用解析后的体积
|
||||
amount="",
|
||||
time=0.0,
|
||||
@@ -361,12 +482,45 @@ def generate_recrystallize_protocol(
|
||||
debug_print(f" ❌ 溶剂2泵协议生成失败: {str(e)} 😭")
|
||||
raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}")
|
||||
|
||||
# 🔧 新增:更新容器体积 - 添加溶剂2后
|
||||
debug_print(" 🔧 更新容器体积 - 添加溶剂2后...")
|
||||
final_liquid_volume = new_volume_after_solvent1 + volume2
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = final_liquid_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [final_liquid_volume]
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = final_liquid_volume
|
||||
|
||||
# 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = final_liquid_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [final_liquid_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = final_liquid_volume
|
||||
|
||||
debug_print(f" 📊 最终体积: {new_volume_after_solvent1:.2f}mL + {volume2:.2f}mL = {final_liquid_volume:.2f}mL")
|
||||
|
||||
# 10. 等待溶剂2稳定
|
||||
debug_print(" ⏳ 添加溶剂2稳定等待...")
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 5.0, # 🕐 缩短等待时间:10.0s → 5.0s
|
||||
"time": 5.0, # 缩短等待时间
|
||||
"description": f"等待溶剂2 {solvent2} 稳定"
|
||||
}
|
||||
})
|
||||
@@ -375,7 +529,7 @@ def generate_recrystallize_protocol(
|
||||
# 11. 等待重结晶完成
|
||||
debug_print("📍 步骤9: 等待重结晶完成... 💎")
|
||||
|
||||
# 🕐 模拟运行时间优化
|
||||
# 模拟运行时间优化
|
||||
debug_print(" ⏱️ 检查模拟运行时间限制...")
|
||||
original_crystallize_time = 600.0 # 原始重结晶时间
|
||||
simulation_time_limit = 60.0 # 模拟运行时间限制:60秒
|
||||
@@ -401,12 +555,15 @@ def generate_recrystallize_protocol(
|
||||
if original_crystallize_time != final_crystallize_time:
|
||||
debug_print(f" 🎭 模拟优化说明: 原计划 {original_crystallize_time/60:.1f}分钟,实际模拟 {final_crystallize_time/60:.1f}分钟 ⚡")
|
||||
|
||||
# 🎊 总结
|
||||
# 总结
|
||||
debug_print("💎" * 20)
|
||||
debug_print(f"🎉 重结晶协议生成完成! ✨")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f"🥽 目标容器: {vessel}")
|
||||
debug_print(f"💧 总体积: {final_volume}mL")
|
||||
debug_print(f"🥽 目标容器: {vessel_id}")
|
||||
debug_print(f"💧 总体积变化:")
|
||||
debug_print(f" - 原始体积: {original_liquid_volume:.2f}mL")
|
||||
debug_print(f" - 添加溶剂: {final_volume:.2f}mL")
|
||||
debug_print(f" - 最终体积: {final_liquid_volume:.2f}mL")
|
||||
debug_print(f"⚖️ 溶剂比例: {solvent1}:{solvent2} = {ratio1}:{ratio2}")
|
||||
debug_print(f"🧪 溶剂1: {solvent1} ({volume1:.2f}mL)")
|
||||
debug_print(f"🧪 溶剂2: {solvent2} ({volume2:.2f}mL)")
|
||||
@@ -421,6 +578,13 @@ def test_recrystallize_protocol():
|
||||
"""测试重结晶协议"""
|
||||
debug_print("🧪 === RECRYSTALLIZE PROTOCOL 测试 === ✨")
|
||||
|
||||
# 测试体积解析
|
||||
debug_print("💧 测试体积解析...")
|
||||
test_volumes = ["100 mL", "2.5 L", "500", "50.5", "?", "invalid"]
|
||||
for vol in test_volumes:
|
||||
parsed = parse_volume_with_units(vol)
|
||||
debug_print(f" 📊 体积 '{vol}' -> {parsed}mL")
|
||||
|
||||
# 测试比例解析
|
||||
debug_print("⚖️ 测试比例解析...")
|
||||
test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"]
|
||||
@@ -430,6 +594,5 @@ def test_recrystallize_protocol():
|
||||
|
||||
debug_print("✅ 测试完成 🎉")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_recrystallize_protocol()
|
||||
@@ -1,12 +1,67 @@
|
||||
import networkx as nx
|
||||
from typing import List, Dict, Any
|
||||
import logging
|
||||
import sys
|
||||
from typing import List, Dict, Any, Optional
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
# 设置日志
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 确保输出编码为UTF-8
|
||||
if hasattr(sys.stdout, 'reconfigure'):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
except:
|
||||
pass
|
||||
|
||||
def debug_print(message):
|
||||
"""调试输出"""
|
||||
print(f"🔄 [RESET_HANDLING] {message}", flush=True)
|
||||
"""调试输出函数 - 支持中文"""
|
||||
try:
|
||||
# 确保消息是字符串格式
|
||||
safe_message = str(message)
|
||||
print(f"[重置处理] {safe_message}", flush=True)
|
||||
logger.info(f"[重置处理] {safe_message}")
|
||||
except UnicodeEncodeError:
|
||||
# 如果编码失败,尝试替换不支持的字符
|
||||
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
|
||||
print(f"[重置处理] {safe_message}", flush=True)
|
||||
logger.info(f"[重置处理] {safe_message}")
|
||||
except Exception as e:
|
||||
# 最后的安全措施
|
||||
fallback_message = f"日志输出错误: {repr(message)}"
|
||||
print(f"[重置处理] {fallback_message}", flush=True)
|
||||
logger.info(f"[重置处理] {fallback_message}")
|
||||
|
||||
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
"""创建一个动作日志 - 支持中文和emoji"""
|
||||
try:
|
||||
full_message = f"{emoji} {message}"
|
||||
debug_print(full_message)
|
||||
logger.info(full_message)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": full_message,
|
||||
"progress_message": full_message
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
# 如果emoji有问题,使用纯文本
|
||||
safe_message = f"[日志] {message}"
|
||||
debug_print(safe_message)
|
||||
logger.info(safe_message)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": safe_message,
|
||||
"progress_message": safe_message
|
||||
}
|
||||
}
|
||||
|
||||
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
"""
|
||||
@@ -19,7 +74,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
Returns:
|
||||
str: 溶剂容器ID
|
||||
"""
|
||||
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器... 🧪")
|
||||
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器...")
|
||||
|
||||
# 构建可能的容器名称
|
||||
possible_names = [
|
||||
@@ -33,30 +88,30 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
f"vessel_{solvent}", # vessel_methanol
|
||||
]
|
||||
|
||||
debug_print(f"📋 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个) 📝")
|
||||
debug_print(f"🎯 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个)")
|
||||
|
||||
# 第一步:通过容器名称匹配
|
||||
debug_print(" 🎯 步骤1: 精确名称匹配...")
|
||||
debug_print("📋 方法1: 精确名称匹配...")
|
||||
for vessel_name in possible_names:
|
||||
if vessel_name in G.nodes():
|
||||
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name} ✨")
|
||||
debug_print(f"✅ 通过名称匹配找到容器: {vessel_name}")
|
||||
return vessel_name
|
||||
debug_print(" 😞 精确名称匹配失败,尝试模糊匹配... 🔍")
|
||||
debug_print("⚠️ 精确名称匹配失败,尝试模糊匹配...")
|
||||
|
||||
# 第二步:通过模糊匹配
|
||||
debug_print(" 🔍 步骤2: 模糊名称匹配...")
|
||||
debug_print("📋 方法2: 模糊名称匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_name = G.nodes[node_id].get('name', '').lower()
|
||||
|
||||
# 检查是否包含溶剂名称
|
||||
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
|
||||
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} ✨")
|
||||
debug_print(f"✅ 通过模糊匹配找到容器: {node_id}")
|
||||
return node_id
|
||||
debug_print(" 😞 模糊匹配失败,尝试液体类型匹配... 🧪")
|
||||
debug_print("⚠️ 模糊匹配失败,尝试液体类型匹配...")
|
||||
|
||||
# 第三步:通过液体类型匹配
|
||||
debug_print(" 🧪 步骤3: 液体类型匹配...")
|
||||
debug_print("📋 方法3: 液体类型匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
vessel_data = G.nodes[node_id].get('data', {})
|
||||
@@ -68,11 +123,11 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||
|
||||
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
|
||||
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} ✨")
|
||||
debug_print(f"✅ 通过液体类型匹配找到容器: {node_id}")
|
||||
return node_id
|
||||
|
||||
# 列出可用容器帮助调试
|
||||
debug_print(" 📊 显示可用容器信息...")
|
||||
debug_print("📊 显示可用容器信息...")
|
||||
available_containers = []
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
@@ -88,30 +143,31 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
'reagent_name': vessel_data.get('reagent_name', '')
|
||||
})
|
||||
|
||||
debug_print(f" 📋 可用容器列表 (共{len(available_containers)}个):")
|
||||
debug_print(f"📋 可用容器列表 (共{len(available_containers)}个):")
|
||||
for i, container in enumerate(available_containers[:5]): # 只显示前5个
|
||||
debug_print(f" {i+1}. 🥽 {container['id']}: {container['name']}")
|
||||
debug_print(f" 💧 液体: {container['liquids']}")
|
||||
debug_print(f" 🧪 试剂: {container['reagent_name']}")
|
||||
debug_print(f" {i+1}. 🥽 {container['id']}: {container['name']}")
|
||||
debug_print(f" 💧 液体: {container['liquids']}")
|
||||
debug_print(f" 🧪 试剂: {container['reagent_name']}")
|
||||
|
||||
if len(available_containers) > 5:
|
||||
debug_print(f" ... 还有 {len(available_containers)-5} 个容器 📦")
|
||||
debug_print(f" ... 还有 {len(available_containers)-5} 个容器")
|
||||
|
||||
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭")
|
||||
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器")
|
||||
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names[:3]}...")
|
||||
|
||||
|
||||
def generate_reset_handling_protocol(
|
||||
G: nx.DiGraph,
|
||||
solvent: str,
|
||||
vessel: Optional[str] = None, # 🆕 新增可选vessel参数
|
||||
**kwargs # 接收其他可能的参数但不使用
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成重置处理协议序列
|
||||
生成重置处理协议序列 - 支持自定义容器
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为容器和设备
|
||||
solvent: 溶剂名称(从XDL传入)
|
||||
vessel: 目标容器名称(可选,默认为 "main_reactor")
|
||||
**kwargs: 其他可选参数,但不使用
|
||||
|
||||
Returns:
|
||||
@@ -119,51 +175,79 @@ def generate_reset_handling_protocol(
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 固定参数
|
||||
target_vessel = "main_reactor" # 默认目标容器
|
||||
# 🔧 修改:支持自定义vessel参数
|
||||
target_vessel = vessel if vessel is not None else "main_reactor" # 默认目标容器
|
||||
volume = 50.0 # 默认体积 50 mL
|
||||
|
||||
debug_print("🔄" * 20)
|
||||
debug_print("🚀 开始生成重置处理协议 ✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print("=" * 60)
|
||||
debug_print("🚀 开始生成重置处理协议")
|
||||
debug_print(f"📋 输入参数:")
|
||||
debug_print(f" 🧪 溶剂: {solvent}")
|
||||
debug_print(f" 🥽 目标容器: {target_vessel}")
|
||||
debug_print(f" 🥽 目标容器: {target_vessel} {'(默认)' if vessel is None else '(指定)'}")
|
||||
debug_print(f" 💧 体积: {volume} mL")
|
||||
debug_print(f" ⚙️ 其他参数: {kwargs}")
|
||||
debug_print("🔄" * 20)
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加初始日志
|
||||
action_sequence.append(create_action_log(f"开始重置处理操作 - 容器: {target_vessel}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"使用溶剂: {solvent}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"重置体积: {volume}mL", "💧"))
|
||||
|
||||
if vessel is None:
|
||||
action_sequence.append(create_action_log("使用默认目标容器: main_reactor", "⚙️"))
|
||||
else:
|
||||
action_sequence.append(create_action_log(f"使用指定目标容器: {vessel}", "🎯"))
|
||||
|
||||
# 1. 验证目标容器存在
|
||||
debug_print("📍 步骤1: 验证目标容器... 🔧")
|
||||
debug_print("🔍 步骤1: 验证目标容器...")
|
||||
action_sequence.append(create_action_log("正在验证目标容器...", "🔍"))
|
||||
|
||||
if target_vessel not in G.nodes():
|
||||
debug_print(f"❌ 目标容器 '{target_vessel}' 不存在于系统中! 😱")
|
||||
debug_print(f"❌ 目标容器 '{target_vessel}' 不存在于系统中!")
|
||||
action_sequence.append(create_action_log(f"目标容器 '{target_vessel}' 不存在", "❌"))
|
||||
raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中")
|
||||
debug_print(f"✅ 目标容器 '{target_vessel}' 验证通过 🎯")
|
||||
|
||||
debug_print(f"✅ 目标容器 '{target_vessel}' 验证通过")
|
||||
action_sequence.append(create_action_log(f"目标容器验证通过: {target_vessel}", "✅"))
|
||||
|
||||
# 2. 查找溶剂容器
|
||||
debug_print("📍 步骤2: 查找溶剂容器... 🔍")
|
||||
debug_print("🔍 步骤2: 查找溶剂容器...")
|
||||
action_sequence.append(create_action_log("正在查找溶剂容器...", "🔍"))
|
||||
|
||||
try:
|
||||
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||
debug_print(f"🎉 找到溶剂容器: {solvent_vessel} ✨")
|
||||
debug_print(f"✅ 找到溶剂容器: {solvent_vessel}")
|
||||
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "✅"))
|
||||
except ValueError as e:
|
||||
debug_print(f"❌ 溶剂容器查找失败: {str(e)} 😭")
|
||||
debug_print(f"❌ 溶剂容器查找失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"溶剂容器查找失败: {str(e)}", "❌"))
|
||||
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
|
||||
|
||||
# 3. 验证路径存在
|
||||
debug_print("📍 步骤3: 验证传输路径... 🛤️")
|
||||
debug_print("🔍 步骤3: 验证传输路径...")
|
||||
action_sequence.append(create_action_log("正在验证传输路径...", "🛤️"))
|
||||
|
||||
try:
|
||||
path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel)
|
||||
debug_print(f"🛤️ 找到路径: {' → '.join(path)} ✅")
|
||||
debug_print(f"✅ 找到路径: {' → '.join(path)}")
|
||||
action_sequence.append(create_action_log(f"传输路径: {' → '.join(path)}", "🛤️"))
|
||||
except nx.NetworkXNoPath:
|
||||
debug_print(f"❌ 路径不可达: {solvent_vessel} → {target_vessel} 😞")
|
||||
debug_print(f"❌ 路径不可达: {solvent_vessel} → {target_vessel}")
|
||||
action_sequence.append(create_action_log(f"路径不可达: {solvent_vessel} → {target_vessel}", "❌"))
|
||||
raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径")
|
||||
|
||||
# 4. 使用pump_protocol转移溶剂
|
||||
debug_print("📍 步骤4: 转移溶剂... 🚰")
|
||||
debug_print(f" 🚛 开始转移: {solvent_vessel} → {target_vessel}")
|
||||
debug_print(f" 💧 转移体积: {volume} mL")
|
||||
debug_print("🔍 步骤4: 转移溶剂...")
|
||||
action_sequence.append(create_action_log("开始溶剂转移操作...", "🚰"))
|
||||
|
||||
debug_print(f"🚛 开始转移: {solvent_vessel} → {target_vessel}")
|
||||
debug_print(f"💧 转移体积: {volume} mL")
|
||||
action_sequence.append(create_action_log(f"转移: {solvent_vessel} → {target_vessel} ({volume}mL)", "🚛"))
|
||||
|
||||
try:
|
||||
debug_print(" 🔄 生成泵送协议...")
|
||||
debug_print("🔄 生成泵送协议...")
|
||||
action_sequence.append(create_action_log("正在生成泵送协议...", "🔄"))
|
||||
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
@@ -181,60 +265,104 @@ def generate_reset_handling_protocol(
|
||||
)
|
||||
|
||||
action_sequence.extend(pump_actions)
|
||||
debug_print(f" ✅ 泵送协议已添加: {len(pump_actions)} 个动作 🚰✨")
|
||||
debug_print(f"✅ 泵送协议已添加: {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"泵送协议完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f" ❌ 泵送协议生成失败: {str(e)} 😭")
|
||||
debug_print(f"❌ 泵送协议生成失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"泵送协议生成失败: {str(e)}", "❌"))
|
||||
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||
|
||||
# 5. 等待溶剂稳定
|
||||
debug_print("📍 步骤5: 等待溶剂稳定... ⏳")
|
||||
debug_print("🔍 步骤5: 等待溶剂稳定...")
|
||||
action_sequence.append(create_action_log("等待溶剂稳定...", "⏳"))
|
||||
|
||||
# 🕐 模拟运行时间优化
|
||||
debug_print(" ⏱️ 检查模拟运行时间限制...")
|
||||
# 模拟运行时间优化
|
||||
debug_print("⏱️ 检查模拟运行时间限制...")
|
||||
original_wait_time = 10.0 # 原始等待时间
|
||||
simulation_time_limit = 5.0 # 模拟运行时间限制:5秒
|
||||
|
||||
final_wait_time = min(original_wait_time, simulation_time_limit)
|
||||
|
||||
if original_wait_time > simulation_time_limit:
|
||||
debug_print(f" 🎮 模拟运行优化: {original_wait_time}s → {final_wait_time}s ⚡")
|
||||
debug_print(f" 📊 时间缩短: {original_wait_time}s → {final_wait_time}s 🚀")
|
||||
debug_print(f"🎮 模拟运行优化: {original_wait_time}s → {final_wait_time}s")
|
||||
action_sequence.append(create_action_log(f"时间优化: {original_wait_time}s → {final_wait_time}s", "⚡"))
|
||||
else:
|
||||
debug_print(f" ✅ 时间在限制内: {final_wait_time}s 保持不变 🎯")
|
||||
debug_print(f"✅ 时间在限制内: {final_wait_time}s 保持不变")
|
||||
action_sequence.append(create_action_log(f"等待时间: {final_wait_time}s", "⏰"))
|
||||
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": final_wait_time,
|
||||
"description": f"等待溶剂 {solvent} 稳定" + (f" (模拟时间)" if original_wait_time != final_wait_time else "")
|
||||
"description": f"等待溶剂 {solvent} 在容器 {target_vessel} 中稳定" + (f" (模拟时间)" if original_wait_time != final_wait_time else "")
|
||||
}
|
||||
})
|
||||
debug_print(f" ✅ 稳定等待已添加: {final_wait_time}s ⏰✨")
|
||||
debug_print(f"✅ 稳定等待已添加: {final_wait_time}s")
|
||||
|
||||
# 显示时间调整信息
|
||||
if original_wait_time != final_wait_time:
|
||||
debug_print(f" 🎭 模拟优化说明: 原计划 {original_wait_time}s,实际模拟 {final_wait_time}s ⚡")
|
||||
debug_print(f"🎭 模拟优化说明: 原计划 {original_wait_time}s,实际模拟 {final_wait_time}s")
|
||||
action_sequence.append(create_action_log("应用模拟时间优化", "🎭"))
|
||||
|
||||
# 🎊 总结
|
||||
debug_print("🔄" * 20)
|
||||
debug_print(f"🎉 重置处理协议生成完成! ✨")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f"🧪 溶剂: {solvent}")
|
||||
debug_print(f"🥽 源容器: {solvent_vessel}")
|
||||
debug_print(f"🥽 目标容器: {target_vessel}")
|
||||
debug_print(f"💧 转移体积: {volume} mL")
|
||||
debug_print(f"⏱️ 预计总时间: {(final_wait_time + 5):.0f} 秒 ⌛")
|
||||
debug_print(f"🎯 已添加 {volume} mL {solvent} 到 {target_vessel} 🚰✨")
|
||||
debug_print("🔄" * 20)
|
||||
# 总结
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"🎉 重置处理协议生成完成!")
|
||||
debug_print(f"📊 总结信息:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f" 🧪 溶剂: {solvent}")
|
||||
debug_print(f" 🥽 源容器: {solvent_vessel}")
|
||||
debug_print(f" 🥽 目标容器: {target_vessel} {'(默认)' if vessel is None else '(指定)'}")
|
||||
debug_print(f" 💧 转移体积: {volume} mL")
|
||||
debug_print(f" ⏱️ 预计总时间: {(final_wait_time + 5):.0f} 秒")
|
||||
debug_print(f" 🎯 操作结果: 已添加 {volume} mL {solvent} 到 {target_vessel}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"重置处理完成: {target_vessel} (使用 {volume}mL {solvent})"
|
||||
if vessel is None:
|
||||
summary_msg += " [默认容器]"
|
||||
else:
|
||||
summary_msg += " [指定容器]"
|
||||
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
|
||||
def reset_main_reactor(G: nx.DiGraph, solvent: str = "methanol", **kwargs) -> List[Dict[str, Any]]:
|
||||
"""重置主反应器 (默认行为)"""
|
||||
debug_print(f"🔄 重置主反应器,使用溶剂: {solvent}")
|
||||
return generate_reset_handling_protocol(G, solvent=solvent, vessel=None, **kwargs)
|
||||
|
||||
def reset_custom_vessel(G: nx.DiGraph, vessel: str, solvent: str = "methanol", **kwargs) -> List[Dict[str, Any]]:
|
||||
"""重置指定容器"""
|
||||
debug_print(f"🔄 重置指定容器: {vessel},使用溶剂: {solvent}")
|
||||
return generate_reset_handling_protocol(G, solvent=solvent, vessel=vessel, **kwargs)
|
||||
|
||||
def reset_with_water(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]:
|
||||
"""使用水重置容器"""
|
||||
target = vessel or "main_reactor"
|
||||
debug_print(f"💧 使用水重置容器: {target}")
|
||||
return generate_reset_handling_protocol(G, solvent="water", vessel=vessel, **kwargs)
|
||||
|
||||
def reset_with_methanol(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]:
|
||||
"""使用甲醇重置容器"""
|
||||
target = vessel or "main_reactor"
|
||||
debug_print(f"🧪 使用甲醇重置容器: {target}")
|
||||
return generate_reset_handling_protocol(G, solvent="methanol", vessel=vessel, **kwargs)
|
||||
|
||||
def reset_with_ethanol(G: nx.DiGraph, vessel: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]:
|
||||
"""使用乙醇重置容器"""
|
||||
target = vessel or "main_reactor"
|
||||
debug_print(f"🧪 使用乙醇重置容器: {target}")
|
||||
return generate_reset_handling_protocol(G, solvent="ethanol", vessel=vessel, **kwargs)
|
||||
|
||||
# 测试函数
|
||||
def test_reset_handling_protocol():
|
||||
"""测试重置处理协议"""
|
||||
debug_print("🧪 === RESET HANDLING PROTOCOL 测试 === ✨")
|
||||
debug_print("=== 重置处理协议增强中文版测试 ===")
|
||||
|
||||
# 测试溶剂名称
|
||||
debug_print("🧪 测试常用溶剂名称...")
|
||||
@@ -242,8 +370,18 @@ def test_reset_handling_protocol():
|
||||
for solvent in test_solvents:
|
||||
debug_print(f" 🔍 测试溶剂: {solvent}")
|
||||
|
||||
debug_print("✅ 测试完成 🎉")
|
||||
|
||||
# 测试容器参数
|
||||
debug_print("🥽 测试容器参数...")
|
||||
test_cases = [
|
||||
{"solvent": "methanol", "vessel": None, "desc": "默认容器"},
|
||||
{"solvent": "ethanol", "vessel": "reactor_2", "desc": "指定容器"},
|
||||
{"solvent": "water", "vessel": "flask_1", "desc": "自定义容器"}
|
||||
]
|
||||
|
||||
for case in test_cases:
|
||||
debug_print(f" 🧪 测试案例: {case['desc']} - {case['solvent']} -> {case['vessel'] or 'main_reactor'}")
|
||||
|
||||
debug_print("✅ 测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_reset_handling_protocol()
|
||||
@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def debug_print(message):
|
||||
"""调试输出"""
|
||||
print(f"[RUN_COLUMN] {message}", flush=True)
|
||||
print(f"🏛️ [RUN_COLUMN] {message}", flush=True)
|
||||
logger.info(f"[RUN_COLUMN] {message}")
|
||||
|
||||
def parse_percentage(pct_str: str) -> float:
|
||||
@@ -25,7 +25,7 @@ def parse_percentage(pct_str: str) -> float:
|
||||
return 0.0
|
||||
|
||||
pct_str = pct_str.strip().lower()
|
||||
debug_print(f"解析百分比: '{pct_str}'")
|
||||
debug_print(f"🔍 解析百分比: '{pct_str}'")
|
||||
|
||||
# 移除百分号和空格
|
||||
pct_clean = re.sub(r'[%\s]', '', pct_str)
|
||||
@@ -34,7 +34,7 @@ def parse_percentage(pct_str: str) -> float:
|
||||
match = re.search(r'([0-9]*\.?[0-9]+)', pct_clean)
|
||||
if match:
|
||||
value = float(match.group(1))
|
||||
debug_print(f"百分比解析结果: {value}%")
|
||||
debug_print(f"✅ 百分比解析结果: {value}%")
|
||||
return value
|
||||
|
||||
debug_print(f"⚠️ 无法解析百分比: '{pct_str}',返回0.0")
|
||||
@@ -54,7 +54,7 @@ def parse_ratio(ratio_str: str) -> tuple:
|
||||
return (50.0, 50.0) # 默认1:1
|
||||
|
||||
ratio_str = ratio_str.strip()
|
||||
debug_print(f"解析比例: '{ratio_str}'")
|
||||
debug_print(f"🔍 解析比例: '{ratio_str}'")
|
||||
|
||||
# 支持多种分隔符:: / -
|
||||
if ':' in ratio_str:
|
||||
@@ -79,7 +79,7 @@ def parse_ratio(ratio_str: str) -> tuple:
|
||||
pct1 = (ratio1 / total) * 100
|
||||
pct2 = (ratio2 / total) * 100
|
||||
|
||||
debug_print(f"比例解析结果: {ratio1}:{ratio2} -> {pct1:.1f}%:{pct2:.1f}%")
|
||||
debug_print(f"✅ 比例解析结果: {ratio1}:{ratio2} -> {pct1:.1f}%:{pct2:.1f}%")
|
||||
return (pct1, pct2)
|
||||
except ValueError as e:
|
||||
debug_print(f"⚠️ 比例数值转换失败: {str(e)}")
|
||||
@@ -101,12 +101,12 @@ def parse_rf_value(rf_str: str) -> float:
|
||||
return 0.3 # 默认Rf值
|
||||
|
||||
rf_str = rf_str.strip().lower()
|
||||
debug_print(f"解析Rf值: '{rf_str}'")
|
||||
debug_print(f"🔍 解析Rf值: '{rf_str}'")
|
||||
|
||||
# 处理未知Rf值
|
||||
if rf_str in ['?', 'unknown', 'tbd', 'to be determined']:
|
||||
default_rf = 0.3
|
||||
debug_print(f"检测到未知Rf值,使用默认值: {default_rf}")
|
||||
debug_print(f"❓ 检测到未知Rf值,使用默认值: {default_rf}")
|
||||
return default_rf
|
||||
|
||||
# 提取数字
|
||||
@@ -117,7 +117,7 @@ def parse_rf_value(rf_str: str) -> float:
|
||||
if value > 1.0:
|
||||
value = value / 100.0 # 可能是百分比形式
|
||||
value = max(0.0, min(1.0, value)) # 限制在0-1范围
|
||||
debug_print(f"Rf值解析结果: {value}")
|
||||
debug_print(f"✅ Rf值解析结果: {value}")
|
||||
return value
|
||||
|
||||
debug_print(f"⚠️ 无法解析Rf值: '{rf_str}',使用默认值0.3")
|
||||
@@ -125,7 +125,7 @@ def parse_rf_value(rf_str: str) -> float:
|
||||
|
||||
def find_column_device(G: nx.DiGraph) -> str:
|
||||
"""查找柱层析设备"""
|
||||
debug_print("查找柱层析设备...")
|
||||
debug_print("🔍 查找柱层析设备...")
|
||||
|
||||
# 查找虚拟柱设备
|
||||
for node in G.nodes():
|
||||
@@ -133,14 +133,14 @@ def find_column_device(G: nx.DiGraph) -> str:
|
||||
node_class = node_data.get('class', '') or ''
|
||||
|
||||
if 'virtual_column' in node_class.lower() or 'column' in node_class.lower():
|
||||
debug_print(f"✅ 找到柱层析设备: {node}")
|
||||
debug_print(f"🎉 找到柱层析设备: {node} ✨")
|
||||
return node
|
||||
|
||||
# 如果没有找到,尝试创建虚拟设备名称
|
||||
possible_names = ['column_1', 'virtual_column_1', 'chromatography_column_1']
|
||||
for name in possible_names:
|
||||
if name in G.nodes():
|
||||
debug_print(f"✅ 找到柱设备: {name}")
|
||||
debug_print(f"🎉 找到柱设备: {name} ✨")
|
||||
return name
|
||||
|
||||
debug_print("⚠️ 未找到柱层析设备,将使用pump protocol直接转移")
|
||||
@@ -148,13 +148,13 @@ def find_column_device(G: nx.DiGraph) -> str:
|
||||
|
||||
def find_column_vessel(G: nx.DiGraph, column: str) -> str:
|
||||
"""查找柱容器"""
|
||||
debug_print(f"查找柱容器: '{column}'")
|
||||
debug_print(f"🔍 查找柱容器: '{column}'")
|
||||
|
||||
# 直接检查column参数是否是容器
|
||||
if column in G.nodes():
|
||||
node_type = G.nodes[column].get('type', '')
|
||||
if node_type == 'container':
|
||||
debug_print(f"✅ 找到柱容器: {column}")
|
||||
debug_print(f"🎉 找到柱容器: {column} ✨")
|
||||
return column
|
||||
|
||||
# 尝试常见的命名规则
|
||||
@@ -174,7 +174,7 @@ def find_column_vessel(G: nx.DiGraph, column: str) -> str:
|
||||
if vessel_name in G.nodes():
|
||||
node_type = G.nodes[vessel_name].get('type', '')
|
||||
if node_type == 'container':
|
||||
debug_print(f"✅ 找到柱容器: {vessel_name}")
|
||||
debug_print(f"🎉 找到柱容器: {vessel_name} ✨")
|
||||
return vessel_name
|
||||
|
||||
debug_print(f"⚠️ 未找到柱容器,将直接在源容器中进行分离")
|
||||
@@ -186,7 +186,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
return ""
|
||||
|
||||
solvent = solvent.strip().replace(' ', '_').lower()
|
||||
debug_print(f"查找溶剂容器: '{solvent}'")
|
||||
debug_print(f"🔍 查找溶剂容器: '{solvent}'")
|
||||
|
||||
# 🔧 方法1:直接搜索 data.reagent_name
|
||||
for node in G.nodes():
|
||||
@@ -200,16 +200,16 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
|
||||
# 检查 data.reagent_name 和 config.reagent
|
||||
if reagent_name == solvent or reagent_config == solvent:
|
||||
debug_print(f"✅ 通过reagent_name找到溶剂容器: {node} (reagent: {reagent_name or reagent_config})")
|
||||
debug_print(f"🎉 通过reagent_name找到溶剂容器: {node} (reagent: {reagent_name or reagent_config}) ✨")
|
||||
return node
|
||||
|
||||
# 模糊匹配 reagent_name
|
||||
if solvent in reagent_name or reagent_name in solvent:
|
||||
debug_print(f"✅ 通过reagent_name模糊匹配到溶剂容器: {node} (reagent: {reagent_name})")
|
||||
debug_print(f"🎉 通过reagent_name模糊匹配到溶剂容器: {node} (reagent: {reagent_name}) ✨")
|
||||
return node
|
||||
|
||||
if solvent in reagent_config or reagent_config in solvent:
|
||||
debug_print(f"✅ 通过config.reagent模糊匹配到溶剂容器: {node} (reagent: {reagent_config})")
|
||||
debug_print(f"🎉 通过config.reagent模糊匹配到溶剂容器: {node} (reagent: {reagent_config}) ✨")
|
||||
return node
|
||||
|
||||
# 🔧 方法2:常见的溶剂容器命名规则
|
||||
@@ -227,7 +227,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
if vessel_name in G.nodes():
|
||||
node_type = G.nodes[vessel_name].get('type', '')
|
||||
if node_type == 'container':
|
||||
debug_print(f"✅ 通过命名规则找到溶剂容器: {vessel_name}")
|
||||
debug_print(f"🎉 通过命名规则找到溶剂容器: {vessel_name} ✨")
|
||||
return vessel_name
|
||||
|
||||
# 🔧 方法3:节点名称模糊匹配
|
||||
@@ -235,7 +235,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
node_type = G.nodes[node].get('type', '')
|
||||
if node_type == 'container':
|
||||
if ('flask_' in node or 'bottle_' in node or 'reagent_' in node) and solvent in node.lower():
|
||||
debug_print(f"✅ 通过节点名称模糊匹配到溶剂容器: {node}")
|
||||
debug_print(f"🎉 通过节点名称模糊匹配到溶剂容器: {node} ✨")
|
||||
return node
|
||||
|
||||
# 🔧 方法4:特殊溶剂名称映射
|
||||
@@ -253,84 +253,118 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
# 查找映射的同义词
|
||||
for canonical_name, synonyms in solvent_mapping.items():
|
||||
if solvent in synonyms:
|
||||
debug_print(f"检测到溶剂同义词: '{solvent}' -> '{canonical_name}'")
|
||||
debug_print(f"🔍 检测到溶剂同义词: '{solvent}' -> '{canonical_name}'")
|
||||
return find_solvent_vessel(G, canonical_name) # 递归搜索
|
||||
|
||||
debug_print(f"⚠️ 未找到溶剂 '{solvent}' 的容器")
|
||||
return ""
|
||||
|
||||
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||
"""获取容器中的液体体积 - 增强版"""
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"⚠️ 节点 '{vessel}' 不存在")
|
||||
def get_vessel_liquid_volume(vessel: dict) -> float:
|
||||
"""
|
||||
获取容器中的液体体积 - 支持vessel字典
|
||||
|
||||
Args:
|
||||
vessel: 容器字典
|
||||
|
||||
Returns:
|
||||
float: 液体体积(mL)
|
||||
"""
|
||||
if not vessel or "data" not in vessel:
|
||||
debug_print(f"⚠️ 容器数据为空,返回 0.0mL")
|
||||
return 0.0
|
||||
|
||||
node_type = G.nodes[vessel].get('type', '')
|
||||
vessel_data = G.nodes[vessel].get('data', {})
|
||||
vessel_data = vessel["data"]
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
|
||||
debug_print(f"读取节点 '{vessel}' (类型: {node_type}) 体积数据: {vessel_data}")
|
||||
debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}")
|
||||
|
||||
# 🔧 如果是设备类型,尝试查找关联的容器
|
||||
if node_type == 'device':
|
||||
debug_print(f"'{vessel}' 是设备,尝试查找关联容器...")
|
||||
# 检查liquid_volume字段
|
||||
if "liquid_volume" in vessel_data:
|
||||
liquid_volume = vessel_data["liquid_volume"]
|
||||
|
||||
# 查找是否有内置容器数据
|
||||
config_data = G.nodes[vessel].get('config', {})
|
||||
if 'volume' in config_data:
|
||||
default_volume = config_data.get('volume', 50.0)
|
||||
debug_print(f"使用设备默认容量: {default_volume}mL")
|
||||
return default_volume
|
||||
# 处理列表格式
|
||||
if isinstance(liquid_volume, list):
|
||||
if len(liquid_volume) > 0:
|
||||
volume = liquid_volume[0]
|
||||
if isinstance(volume, (int, float)):
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)")
|
||||
return float(volume)
|
||||
|
||||
# 对于旋蒸等设备,使用默认值
|
||||
if 'rotavap' in vessel.lower():
|
||||
default_volume = 50.0
|
||||
debug_print(f"旋蒸设备使用默认容量: {default_volume}mL")
|
||||
return default_volume
|
||||
|
||||
debug_print(f"⚠️ 设备 '{vessel}' 无法确定容量,返回0")
|
||||
return 0.0
|
||||
# 处理直接数值格式
|
||||
elif isinstance(liquid_volume, (int, float)):
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)")
|
||||
return float(liquid_volume)
|
||||
|
||||
# 🔧 如果是容器类型,正常读取体积
|
||||
total_volume = 0.0
|
||||
|
||||
# 方法1:检查液体列表
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
if isinstance(liquids, list):
|
||||
for liquid in liquids:
|
||||
if isinstance(liquid, dict):
|
||||
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
|
||||
total_volume += volume
|
||||
|
||||
# 方法2:检查直接体积字段
|
||||
if total_volume == 0.0:
|
||||
volume_keys = ['current_volume', 'total_volume', 'volume', 'liquid_volume']
|
||||
for key in volume_keys:
|
||||
if key in vessel_data:
|
||||
try:
|
||||
total_volume = float(vessel_data[key])
|
||||
if total_volume > 0:
|
||||
break
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
# 方法3:检查配置中的初始体积
|
||||
if total_volume == 0.0:
|
||||
config_data = G.nodes[vessel].get('config', {})
|
||||
if 'current_volume' in config_data:
|
||||
# 检查其他可能的体积字段
|
||||
volume_keys = ['current_volume', 'total_volume', 'volume']
|
||||
for key in volume_keys:
|
||||
if key in vessel_data:
|
||||
try:
|
||||
total_volume = float(config_data['current_volume'])
|
||||
volume = float(vessel_data[key])
|
||||
if volume > 0:
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})")
|
||||
return volume
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
continue
|
||||
|
||||
debug_print(f"容器 '{vessel}' 总体积: {total_volume}mL")
|
||||
return total_volume
|
||||
debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 的体积,返回默认值 50.0mL")
|
||||
return 50.0
|
||||
|
||||
def update_vessel_volume(vessel: dict, G: nx.DiGraph, new_volume: float, description: str = "") -> None:
|
||||
"""
|
||||
更新容器体积(同时更新vessel字典和图节点)
|
||||
|
||||
Args:
|
||||
vessel: 容器字典
|
||||
G: 网络图
|
||||
new_volume: 新体积
|
||||
description: 更新描述
|
||||
"""
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
|
||||
if description:
|
||||
debug_print(f"🔧 更新容器体积 - {description}")
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel:
|
||||
if "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [new_volume]
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"] = {"liquid_volume": new_volume}
|
||||
|
||||
# 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
|
||||
|
||||
debug_print(f"📊 容器 '{vessel_id}' 体积已更新为: {new_volume:.2f}mL")
|
||||
|
||||
def calculate_solvent_volumes(total_volume: float, pct1: float, pct2: float) -> tuple:
|
||||
"""根据百分比计算溶剂体积"""
|
||||
volume1 = (total_volume * pct1) / 100.0
|
||||
volume2 = (total_volume * pct2) / 100.0
|
||||
|
||||
debug_print(f"溶剂体积计算: 总体积{total_volume}mL")
|
||||
debug_print(f"🧮 溶剂体积计算: 总体积{total_volume}mL")
|
||||
debug_print(f" - 溶剂1: {pct1}% = {volume1}mL")
|
||||
debug_print(f" - 溶剂2: {pct2}% = {volume2}mL")
|
||||
|
||||
@@ -338,8 +372,8 @@ def calculate_solvent_volumes(total_volume: float, pct1: float, pct2: float) ->
|
||||
|
||||
def generate_run_column_protocol(
|
||||
G: nx.DiGraph,
|
||||
from_vessel: str,
|
||||
to_vessel: str,
|
||||
from_vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
to_vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
column: str,
|
||||
rf: str = "",
|
||||
pct1: str = "",
|
||||
@@ -350,14 +384,12 @@ def generate_run_column_protocol(
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成柱层析分离的协议序列 - 增强版
|
||||
|
||||
支持新版XDL的所有参数,具有高兼容性和容错性
|
||||
生成柱层析分离的协议序列 - 支持vessel字典和体积运算
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
from_vessel: 源容器的名称,即样品起始所在的容器(必需)
|
||||
to_vessel: 目标容器的名称,分离后的样品要到达的容器(必需)
|
||||
from_vessel: 源容器字典(从XDL传入)
|
||||
to_vessel: 目标容器字典(从XDL传入)
|
||||
column: 所使用的柱子的名称(必需)
|
||||
rf: Rf值(可选,支持 "?" 表示未知)
|
||||
pct1: 第一种溶剂百分比(如 "40 %",可选)
|
||||
@@ -371,51 +403,60 @@ def generate_run_column_protocol(
|
||||
List[Dict[str, Any]]: 柱层析分离操作的动作序列
|
||||
"""
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("开始生成柱层析协议")
|
||||
debug_print(f"输入参数:")
|
||||
debug_print(f" - from_vessel: '{from_vessel}'")
|
||||
debug_print(f" - to_vessel: '{to_vessel}'")
|
||||
debug_print(f" - column: '{column}'")
|
||||
debug_print(f" - rf: '{rf}'")
|
||||
debug_print(f" - pct1: '{pct1}'")
|
||||
debug_print(f" - pct2: '{pct2}'")
|
||||
debug_print(f" - solvent1: '{solvent1}'")
|
||||
debug_print(f" - solvent2: '{solvent2}'")
|
||||
debug_print(f" - ratio: '{ratio}'")
|
||||
debug_print(f" - 其他参数: {kwargs}")
|
||||
debug_print("=" * 60)
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
from_vessel_id = from_vessel["id"]
|
||||
to_vessel_id = to_vessel["id"]
|
||||
|
||||
debug_print("🏛️" * 20)
|
||||
debug_print("🚀 开始生成柱层析协议(支持vessel字典和体积运算)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 from_vessel: {from_vessel} (ID: {from_vessel_id})")
|
||||
debug_print(f" 🥽 to_vessel: {to_vessel} (ID: {to_vessel_id})")
|
||||
debug_print(f" 🏛️ column: '{column}'")
|
||||
debug_print(f" 📊 rf: '{rf}'")
|
||||
debug_print(f" 🧪 溶剂配比: pct1='{pct1}', pct2='{pct2}', ratio='{ratio}'")
|
||||
debug_print(f" 🧪 溶剂名称: solvent1='{solvent1}', solvent2='{solvent2}'")
|
||||
debug_print("🏛️" * 20)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# === 参数验证 ===
|
||||
debug_print("步骤1: 参数验证...")
|
||||
# 🔧 新增:记录柱层析前的容器状态
|
||||
debug_print("🔍 记录柱层析前容器状态...")
|
||||
original_from_volume = get_vessel_liquid_volume(from_vessel)
|
||||
original_to_volume = get_vessel_liquid_volume(to_vessel)
|
||||
|
||||
if not from_vessel:
|
||||
debug_print(f"📊 柱层析前状态:")
|
||||
debug_print(f" - 源容器 {from_vessel_id}: {original_from_volume:.2f}mL")
|
||||
debug_print(f" - 目标容器 {to_vessel_id}: {original_to_volume:.2f}mL")
|
||||
|
||||
# === 参数验证 ===
|
||||
debug_print("📍 步骤1: 参数验证...")
|
||||
|
||||
if not from_vessel_id: # 🔧 使用 from_vessel_id
|
||||
raise ValueError("from_vessel 参数不能为空")
|
||||
if not to_vessel:
|
||||
if not to_vessel_id: # 🔧 使用 to_vessel_id
|
||||
raise ValueError("to_vessel 参数不能为空")
|
||||
if not column:
|
||||
raise ValueError("column 参数不能为空")
|
||||
|
||||
if from_vessel not in G.nodes():
|
||||
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中")
|
||||
if to_vessel not in G.nodes():
|
||||
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中")
|
||||
if from_vessel_id not in G.nodes(): # 🔧 使用 from_vessel_id
|
||||
raise ValueError(f"源容器 '{from_vessel_id}' 不存在于系统中")
|
||||
if to_vessel_id not in G.nodes(): # 🔧 使用 to_vessel_id
|
||||
raise ValueError(f"目标容器 '{to_vessel_id}' 不存在于系统中")
|
||||
|
||||
debug_print("✅ 基本参数验证通过")
|
||||
|
||||
# === 参数解析 ===
|
||||
debug_print("步骤2: 参数解析...")
|
||||
debug_print("📍 步骤2: 参数解析...")
|
||||
|
||||
# 解析Rf值
|
||||
final_rf = parse_rf_value(rf)
|
||||
debug_print(f"最终Rf值: {final_rf}")
|
||||
debug_print(f"🎯 最终Rf值: {final_rf}")
|
||||
|
||||
# 解析溶剂比例(ratio优先级高于pct1/pct2)
|
||||
if ratio and ratio.strip():
|
||||
final_pct1, final_pct2 = parse_ratio(ratio)
|
||||
debug_print(f"使用ratio参数: {final_pct1:.1f}% : {final_pct2:.1f}%")
|
||||
debug_print(f"📊 使用ratio参数: {final_pct1:.1f}% : {final_pct2:.1f}%")
|
||||
else:
|
||||
final_pct1 = parse_percentage(pct1) if pct1 else 50.0
|
||||
final_pct2 = parse_percentage(pct2) if pct2 else 50.0
|
||||
@@ -428,16 +469,16 @@ def generate_run_column_protocol(
|
||||
final_pct1 = (final_pct1 / total_pct) * 100
|
||||
final_pct2 = (final_pct2 / total_pct) * 100
|
||||
|
||||
debug_print(f"使用百分比参数: {final_pct1:.1f}% : {final_pct2:.1f}%")
|
||||
debug_print(f"📊 使用百分比参数: {final_pct1:.1f}% : {final_pct2:.1f}%")
|
||||
|
||||
# 设置默认溶剂(如果未指定)
|
||||
final_solvent1 = solvent1.strip() if solvent1 else "ethyl_acetate"
|
||||
final_solvent2 = solvent2.strip() if solvent2 else "hexane"
|
||||
|
||||
debug_print(f"最终溶剂: {final_solvent1} : {final_solvent2}")
|
||||
debug_print(f"🧪 最终溶剂: {final_solvent1} : {final_solvent2}")
|
||||
|
||||
# === 查找设备和容器 ===
|
||||
debug_print("步骤3: 查找设备和容器...")
|
||||
debug_print("📍 步骤3: 查找设备和容器...")
|
||||
|
||||
# 查找柱层析设备
|
||||
column_device_id = find_column_device(G)
|
||||
@@ -449,16 +490,16 @@ def generate_run_column_protocol(
|
||||
solvent1_vessel = find_solvent_vessel(G, final_solvent1)
|
||||
solvent2_vessel = find_solvent_vessel(G, final_solvent2)
|
||||
|
||||
debug_print(f"设备映射:")
|
||||
debug_print(f"🔧 设备映射:")
|
||||
debug_print(f" - 柱设备: '{column_device_id}'")
|
||||
debug_print(f" - 柱容器: '{column_vessel}'")
|
||||
debug_print(f" - 溶剂1容器: '{solvent1_vessel}'")
|
||||
debug_print(f" - 溶剂2容器: '{solvent2_vessel}'")
|
||||
|
||||
# === 获取源容器体积 ===
|
||||
debug_print("步骤4: 获取源容器体积...")
|
||||
debug_print("📍 步骤4: 获取源容器体积...")
|
||||
|
||||
source_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||
source_volume = original_from_volume
|
||||
if source_volume <= 0:
|
||||
source_volume = 50.0 # 默认体积
|
||||
debug_print(f"⚠️ 无法获取源容器体积,使用默认值: {source_volume}mL")
|
||||
@@ -466,7 +507,7 @@ def generate_run_column_protocol(
|
||||
debug_print(f"✅ 源容器体积: {source_volume}mL")
|
||||
|
||||
# === 计算溶剂体积 ===
|
||||
debug_print("步骤5: 计算溶剂体积...")
|
||||
debug_print("📍 步骤5: 计算溶剂体积...")
|
||||
|
||||
# 洗脱溶剂通常是样品体积的2-5倍
|
||||
total_elution_volume = source_volume * 3.0
|
||||
@@ -475,17 +516,22 @@ def generate_run_column_protocol(
|
||||
)
|
||||
|
||||
# === 执行柱层析流程 ===
|
||||
debug_print("步骤6: 执行柱层析流程...")
|
||||
debug_print("📍 步骤6: 执行柱层析流程...")
|
||||
|
||||
# 🔧 新增:体积变化跟踪变量
|
||||
current_from_volume = source_volume
|
||||
current_to_volume = original_to_volume
|
||||
current_column_volume = 0.0
|
||||
|
||||
try:
|
||||
# 步骤6.1: 样品上柱(如果有独立的柱容器)
|
||||
if column_vessel and column_vessel != from_vessel:
|
||||
debug_print(f"6.1: 样品上柱 - {source_volume}mL 从 {from_vessel} 到 {column_vessel}")
|
||||
if column_vessel and column_vessel != from_vessel_id: # 🔧 使用 from_vessel_id
|
||||
debug_print(f"📍 6.1: 样品上柱 - {source_volume}mL 从 {from_vessel_id} 到 {column_vessel}")
|
||||
|
||||
try:
|
||||
sample_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=from_vessel,
|
||||
from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id
|
||||
to_vessel=column_vessel,
|
||||
volume=source_volume,
|
||||
flowrate=1.0, # 慢速上柱
|
||||
@@ -496,15 +542,29 @@ def generate_run_column_protocol(
|
||||
)
|
||||
action_sequence.extend(sample_transfer_actions)
|
||||
debug_print(f"✅ 样品上柱完成,添加了 {len(sample_transfer_actions)} 个动作")
|
||||
|
||||
# 🔧 新增:更新体积 - 样品转移到柱上
|
||||
current_from_volume = 0.0 # 源容器体积变为0
|
||||
current_column_volume = source_volume # 柱容器体积增加
|
||||
|
||||
update_vessel_volume(from_vessel, G, current_from_volume, "样品上柱后,源容器清空")
|
||||
|
||||
# 如果柱容器在图中,也更新其体积
|
||||
if column_vessel in G.nodes():
|
||||
if 'data' not in G.nodes[column_vessel]:
|
||||
G.nodes[column_vessel]['data'] = {}
|
||||
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume
|
||||
debug_print(f"📊 柱容器 '{column_vessel}' 体积更新为: {current_column_volume:.2f}mL")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 样品上柱失败: {str(e)}")
|
||||
|
||||
# 步骤6.2: 添加洗脱溶剂1(如果有溶剂容器)
|
||||
if solvent1_vessel and solvent1_volume > 0:
|
||||
debug_print(f"6.2: 添加洗脱溶剂1 - {solvent1_volume:.1f}mL {final_solvent1}")
|
||||
debug_print(f"📍 6.2: 添加洗脱溶剂1 - {solvent1_volume:.1f}mL {final_solvent1}")
|
||||
|
||||
try:
|
||||
target_vessel = column_vessel if column_vessel else from_vessel
|
||||
target_vessel = column_vessel if column_vessel else from_vessel_id # 🔧 使用 from_vessel_id
|
||||
solvent1_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent1_vessel,
|
||||
@@ -515,15 +575,26 @@ def generate_run_column_protocol(
|
||||
)
|
||||
action_sequence.extend(solvent1_transfer_actions)
|
||||
debug_print(f"✅ 溶剂1添加完成,添加了 {len(solvent1_transfer_actions)} 个动作")
|
||||
|
||||
# 🔧 新增:更新体积 - 添加溶剂1
|
||||
if target_vessel == column_vessel:
|
||||
current_column_volume += solvent1_volume
|
||||
if column_vessel in G.nodes():
|
||||
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume
|
||||
debug_print(f"📊 柱容器体积增加: +{solvent1_volume:.2f}mL = {current_column_volume:.2f}mL")
|
||||
elif target_vessel == from_vessel_id:
|
||||
current_from_volume += solvent1_volume
|
||||
update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂1后")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 溶剂1添加失败: {str(e)}")
|
||||
|
||||
# 步骤6.3: 添加洗脱溶剂2(如果有溶剂容器)
|
||||
if solvent2_vessel and solvent2_volume > 0:
|
||||
debug_print(f"6.3: 添加洗脱溶剂2 - {solvent2_volume:.1f}mL {final_solvent2}")
|
||||
debug_print(f"📍 6.3: 添加洗脱溶剂2 - {solvent2_volume:.1f}mL {final_solvent2}")
|
||||
|
||||
try:
|
||||
target_vessel = column_vessel if column_vessel else from_vessel
|
||||
target_vessel = column_vessel if column_vessel else from_vessel_id # 🔧 使用 from_vessel_id
|
||||
solvent2_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent2_vessel,
|
||||
@@ -534,19 +605,30 @@ def generate_run_column_protocol(
|
||||
)
|
||||
action_sequence.extend(solvent2_transfer_actions)
|
||||
debug_print(f"✅ 溶剂2添加完成,添加了 {len(solvent2_transfer_actions)} 个动作")
|
||||
|
||||
# 🔧 新增:更新体积 - 添加溶剂2
|
||||
if target_vessel == column_vessel:
|
||||
current_column_volume += solvent2_volume
|
||||
if column_vessel in G.nodes():
|
||||
G.nodes[column_vessel]['data']['liquid_volume'] = current_column_volume
|
||||
debug_print(f"📊 柱容器体积增加: +{solvent2_volume:.2f}mL = {current_column_volume:.2f}mL")
|
||||
elif target_vessel == from_vessel_id:
|
||||
current_from_volume += solvent2_volume
|
||||
update_vessel_volume(from_vessel, G, current_from_volume, "添加溶剂2后")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 溶剂2添加失败: {str(e)}")
|
||||
|
||||
# 步骤6.4: 使用柱层析设备执行分离(如果有设备)
|
||||
if column_device_id:
|
||||
debug_print(f"6.4: 使用柱层析设备执行分离")
|
||||
debug_print(f"📍 6.4: 使用柱层析设备执行分离")
|
||||
|
||||
column_separation_action = {
|
||||
"device_id": column_device_id,
|
||||
"action_name": "run_column",
|
||||
"action_kwargs": {
|
||||
"from_vessel": from_vessel,
|
||||
"to_vessel": to_vessel,
|
||||
"from_vessel": from_vessel_id, # 🔧 使用 from_vessel_id
|
||||
"to_vessel": to_vessel_id, # 🔧 使用 to_vessel_id
|
||||
"column": column,
|
||||
"rf": rf,
|
||||
"pct1": pct1,
|
||||
@@ -560,7 +642,7 @@ def generate_run_column_protocol(
|
||||
debug_print(f"✅ 柱层析设备动作已添加")
|
||||
|
||||
# 等待分离完成
|
||||
separation_time = max(30, int(total_elution_volume / 2)) # 基于体积估算时间
|
||||
separation_time = max(30, min(120, int(total_elution_volume / 2))) # 30-120秒,基于体积
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": separation_time}
|
||||
@@ -568,41 +650,61 @@ def generate_run_column_protocol(
|
||||
debug_print(f"✅ 等待分离完成: {separation_time}秒")
|
||||
|
||||
# 步骤6.5: 产物收集(从柱容器到目标容器)
|
||||
if column_vessel and column_vessel != to_vessel:
|
||||
debug_print(f"6.5: 产物收集 - 从 {column_vessel} 到 {to_vessel}")
|
||||
if column_vessel and column_vessel != to_vessel_id: # 🔧 使用 to_vessel_id
|
||||
debug_print(f"📍 6.5: 产物收集 - 从 {column_vessel} 到 {to_vessel_id}")
|
||||
|
||||
try:
|
||||
# 估算产物体积(原始样品体积的70-90%)
|
||||
# 估算产物体积(原始样品体积的70-90%,收率考虑)
|
||||
product_volume = source_volume * 0.8
|
||||
|
||||
product_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=column_vessel,
|
||||
to_vessel=to_vessel,
|
||||
to_vessel=to_vessel_id, # 🔧 使用 to_vessel_id
|
||||
volume=product_volume,
|
||||
flowrate=1.5,
|
||||
transfer_flowrate=0.8
|
||||
)
|
||||
action_sequence.extend(product_transfer_actions)
|
||||
debug_print(f"✅ 产物收集完成,添加了 {len(product_transfer_actions)} 个动作")
|
||||
|
||||
# 🔧 新增:更新体积 - 产物收集到目标容器
|
||||
current_to_volume += product_volume
|
||||
current_column_volume -= product_volume # 柱容器体积减少
|
||||
|
||||
update_vessel_volume(to_vessel, G, current_to_volume, "产物收集后")
|
||||
|
||||
# 更新柱容器体积
|
||||
if column_vessel in G.nodes():
|
||||
G.nodes[column_vessel]['data']['liquid_volume'] = max(0.0, current_column_volume)
|
||||
debug_print(f"📊 柱容器体积减少: -{product_volume:.2f}mL = {current_column_volume:.2f}mL")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 产物收集失败: {str(e)}")
|
||||
|
||||
# 步骤6.6: 如果没有独立的柱设备和容器,执行简化的直接转移
|
||||
if not column_device_id and not column_vessel:
|
||||
debug_print(f"6.6: 简化模式 - 直接转移 {source_volume}mL 从 {from_vessel} 到 {to_vessel}")
|
||||
debug_print(f"📍 6.6: 简化模式 - 直接转移 {source_volume}mL 从 {from_vessel_id} 到 {to_vessel_id}")
|
||||
|
||||
try:
|
||||
direct_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=from_vessel,
|
||||
to_vessel=to_vessel,
|
||||
from_vessel=from_vessel_id, # 🔧 使用 from_vessel_id
|
||||
to_vessel=to_vessel_id, # 🔧 使用 to_vessel_id
|
||||
volume=source_volume,
|
||||
flowrate=2.0,
|
||||
transfer_flowrate=1.0
|
||||
)
|
||||
action_sequence.extend(direct_transfer_actions)
|
||||
debug_print(f"✅ 直接转移完成,添加了 {len(direct_transfer_actions)} 个动作")
|
||||
|
||||
# 🔧 新增:更新体积 - 直接转移
|
||||
current_from_volume = 0.0 # 源容器清空
|
||||
current_to_volume += source_volume # 目标容器增加
|
||||
|
||||
update_vessel_volume(from_vessel, G, current_from_volume, "直接转移后,源容器清空")
|
||||
update_vessel_volume(to_vessel, G, current_to_volume, "直接转移后,目标容器增加")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 直接转移失败: {str(e)}")
|
||||
|
||||
@@ -624,17 +726,77 @@ def generate_run_column_protocol(
|
||||
}
|
||||
})
|
||||
|
||||
# 🔧 新增:柱层析完成后的最终状态报告
|
||||
final_from_volume = get_vessel_liquid_volume(from_vessel)
|
||||
final_to_volume = get_vessel_liquid_volume(to_vessel)
|
||||
|
||||
# 🎊 总结
|
||||
debug_print("🧪" * 20)
|
||||
debug_print("🏛️" * 20)
|
||||
debug_print(f"🎉 柱层析协议生成完成! ✨")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f"🥽 路径: {from_vessel} → {to_vessel}")
|
||||
debug_print(f"🥽 路径: {from_vessel_id} → {to_vessel_id}")
|
||||
debug_print(f"🏛️ 柱子: {column}")
|
||||
debug_print(f"🧪 溶剂: {final_solvent1}:{final_solvent2}")
|
||||
debug_print("🧪" * 20)
|
||||
debug_print(f"🧪 溶剂: {final_solvent1}:{final_solvent2} = {final_pct1:.1f}%:{final_pct2:.1f}%")
|
||||
debug_print(f"📊 体积变化统计:")
|
||||
debug_print(f" 源容器 {from_vessel_id}:")
|
||||
debug_print(f" - 柱层析前: {original_from_volume:.2f}mL")
|
||||
debug_print(f" - 柱层析后: {final_from_volume:.2f}mL")
|
||||
debug_print(f" 目标容器 {to_vessel_id}:")
|
||||
debug_print(f" - 柱层析前: {original_to_volume:.2f}mL")
|
||||
debug_print(f" - 柱层析后: {final_to_volume:.2f}mL")
|
||||
debug_print(f" - 收集体积: {final_to_volume - original_to_volume:.2f}mL")
|
||||
debug_print(f"⏱️ 预计总时间: {len(action_sequence) * 5:.0f} 秒 ⌛")
|
||||
debug_print("🏛️" * 20)
|
||||
|
||||
return action_sequence
|
||||
|
||||
# 🔧 新增:便捷函数
|
||||
def generate_ethyl_acetate_hexane_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
|
||||
column: str, ratio: str = "30:70") -> List[Dict[str, Any]]:
|
||||
"""乙酸乙酯-己烷柱层析(常用组合)"""
|
||||
from_vessel_id = from_vessel["id"]
|
||||
to_vessel_id = to_vessel["id"]
|
||||
debug_print(f"🧪⛽ 乙酸乙酯-己烷柱层析: {from_vessel_id} → {to_vessel_id} @ {ratio}")
|
||||
return generate_run_column_protocol(G, from_vessel, to_vessel, column,
|
||||
solvent1="ethyl_acetate", solvent2="hexane", ratio=ratio)
|
||||
|
||||
def generate_methanol_dcm_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
|
||||
column: str, ratio: str = "5:95") -> List[Dict[str, Any]]:
|
||||
"""甲醇-二氯甲烷柱层析"""
|
||||
from_vessel_id = from_vessel["id"]
|
||||
to_vessel_id = to_vessel["id"]
|
||||
debug_print(f"🧪🧪 甲醇-DCM柱层析: {from_vessel_id} → {to_vessel_id} @ {ratio}")
|
||||
return generate_run_column_protocol(G, from_vessel, to_vessel, column,
|
||||
solvent1="methanol", solvent2="dichloromethane", ratio=ratio)
|
||||
|
||||
def generate_gradient_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
|
||||
column: str, start_ratio: str = "10:90",
|
||||
end_ratio: str = "50:50") -> List[Dict[str, Any]]:
|
||||
"""梯度洗脱柱层析(中等比例)"""
|
||||
from_vessel_id = from_vessel["id"]
|
||||
to_vessel_id = to_vessel["id"]
|
||||
debug_print(f"📈 梯度柱层析: {from_vessel_id} → {to_vessel_id} ({start_ratio} → {end_ratio})")
|
||||
# 使用中间比例作为近似
|
||||
return generate_run_column_protocol(G, from_vessel, to_vessel, column, ratio="30:70")
|
||||
|
||||
def generate_polar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
|
||||
column: str) -> List[Dict[str, Any]]:
|
||||
"""极性化合物柱层析(高极性溶剂比例)"""
|
||||
from_vessel_id = from_vessel["id"]
|
||||
to_vessel_id = to_vessel["id"]
|
||||
debug_print(f"⚡ 极性化合物柱层析: {from_vessel_id} → {to_vessel_id}")
|
||||
return generate_run_column_protocol(G, from_vessel, to_vessel, column,
|
||||
solvent1="ethyl_acetate", solvent2="hexane", ratio="70:30")
|
||||
|
||||
def generate_nonpolar_column_protocol(G: nx.DiGraph, from_vessel: dict, to_vessel: dict,
|
||||
column: str) -> List[Dict[str, Any]]:
|
||||
"""非极性化合物柱层析(低极性溶剂比例)"""
|
||||
from_vessel_id = from_vessel["id"]
|
||||
to_vessel_id = to_vessel["id"]
|
||||
debug_print(f"🛢️ 非极性化合物柱层析: {from_vessel_id} → {to_vessel_id}")
|
||||
return generate_run_column_protocol(G, from_vessel, to_vessel, column,
|
||||
solvent1="ethyl_acetate", solvent2="hexane", ratio="5:95")
|
||||
|
||||
# 测试函数
|
||||
def test_run_column_protocol():
|
||||
"""测试柱层析协议"""
|
||||
|
||||
@@ -20,18 +20,18 @@ def debug_print(message):
|
||||
try:
|
||||
# 确保消息是字符串格式
|
||||
safe_message = str(message)
|
||||
print(f"[分离协议] {safe_message}", flush=True)
|
||||
logger.info(f"[分离协议] {safe_message}")
|
||||
print(f"🌀 [SEPARATE] {safe_message}", flush=True)
|
||||
logger.info(f"[SEPARATE] {safe_message}")
|
||||
except UnicodeEncodeError:
|
||||
# 如果编码失败,尝试替换不支持的字符
|
||||
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
|
||||
print(f"[分离协议] {safe_message}", flush=True)
|
||||
logger.info(f"[分离协议] {safe_message}")
|
||||
print(f"🌀 [SEPARATE] {safe_message}", flush=True)
|
||||
logger.info(f"[SEPARATE] {safe_message}")
|
||||
except Exception as e:
|
||||
# 最后的安全措施
|
||||
fallback_message = f"日志输出错误: {repr(message)}"
|
||||
print(f"[分离协议] {fallback_message}", flush=True)
|
||||
logger.info(f"[分离协议] {fallback_message}")
|
||||
print(f"🌀 [SEPARATE] {fallback_message}", flush=True)
|
||||
logger.info(f"[SEPARATE] {fallback_message}")
|
||||
|
||||
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
"""创建一个动作日志 - 支持中文和emoji"""
|
||||
@@ -264,19 +264,119 @@ def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||
debug_print("❌ 未找到搅拌器")
|
||||
return ""
|
||||
|
||||
def get_vessel_liquid_volume(vessel: dict) -> float:
|
||||
"""
|
||||
获取容器中的液体体积 - 支持vessel字典
|
||||
|
||||
Args:
|
||||
vessel: 容器字典
|
||||
|
||||
Returns:
|
||||
float: 液体体积(mL)
|
||||
"""
|
||||
if not vessel or "data" not in vessel:
|
||||
debug_print(f"⚠️ 容器数据为空,返回 0.0mL")
|
||||
return 0.0
|
||||
|
||||
vessel_data = vessel["data"]
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
|
||||
debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}")
|
||||
|
||||
# 检查liquid_volume字段
|
||||
if "liquid_volume" in vessel_data:
|
||||
liquid_volume = vessel_data["liquid_volume"]
|
||||
|
||||
# 处理列表格式
|
||||
if isinstance(liquid_volume, list):
|
||||
if len(liquid_volume) > 0:
|
||||
volume = liquid_volume[0]
|
||||
if isinstance(volume, (int, float)):
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)")
|
||||
return float(volume)
|
||||
|
||||
# 处理直接数值格式
|
||||
elif isinstance(liquid_volume, (int, float)):
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)")
|
||||
return float(liquid_volume)
|
||||
|
||||
# 检查其他可能的体积字段
|
||||
volume_keys = ['current_volume', 'total_volume', 'volume']
|
||||
for key in volume_keys:
|
||||
if key in vessel_data:
|
||||
try:
|
||||
volume = float(vessel_data[key])
|
||||
if volume > 0:
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})")
|
||||
return volume
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 的体积,返回默认值 50.0mL")
|
||||
return 50.0
|
||||
|
||||
def update_vessel_volume(vessel: dict, G: nx.DiGraph, new_volume: float, description: str = "") -> None:
|
||||
"""
|
||||
更新容器体积(同时更新vessel字典和图节点)
|
||||
|
||||
Args:
|
||||
vessel: 容器字典
|
||||
G: 网络图
|
||||
new_volume: 新体积
|
||||
description: 更新描述
|
||||
"""
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
|
||||
if description:
|
||||
debug_print(f"🔧 更新容器体积 - {description}")
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel:
|
||||
if "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [new_volume]
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"] = {"liquid_volume": new_volume}
|
||||
|
||||
# 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
|
||||
|
||||
debug_print(f"📊 容器 '{vessel_id}' 体积已更新为: {new_volume:.2f}mL")
|
||||
|
||||
def generate_separate_protocol(
|
||||
G: nx.DiGraph,
|
||||
# 🔧 基础参数,支持XDL的vessel参数
|
||||
vessel: str = "", # XDL: 分离容器
|
||||
purpose: str = "separate", # 分离目的
|
||||
product_phase: str = "top", # 产物相
|
||||
vessel: dict = None, # 🔧 修改:从字符串改为字典类型
|
||||
purpose: str = "separate", # 分离目的
|
||||
product_phase: str = "top", # 产物相
|
||||
# 🔧 可选的详细参数
|
||||
from_vessel: str = "", # 源容器(通常在separate前已经transfer了)
|
||||
separation_vessel: str = "", # 分离容器(与vessel同义)
|
||||
to_vessel: str = "", # 目标容器(可选)
|
||||
waste_phase_to_vessel: str = "", # 废相目标容器
|
||||
product_vessel: str = "", # XDL: 产物容器(与to_vessel同义)
|
||||
waste_vessel: str = "", # XDL: 废液容器(与waste_phase_to_vessel同义)
|
||||
from_vessel: Union[str, dict] = "", # 源容器(通常在separate前已经transfer了)
|
||||
separation_vessel: Union[str, dict] = "", # 分离容器(与vessel同义)
|
||||
to_vessel: Union[str, dict] = "", # 目标容器(可选)
|
||||
waste_phase_to_vessel: Union[str, dict] = "", # 废相目标容器
|
||||
product_vessel: Union[str, dict] = "", # XDL: 产物容器(与to_vessel同义)
|
||||
waste_vessel: Union[str, dict] = "", # XDL: 废液容器(与waste_phase_to_vessel同义)
|
||||
# 🔧 溶剂相关参数
|
||||
solvent: str = "", # 溶剂名称
|
||||
solvent_volume: Union[str, float] = 0.0, # 溶剂体积
|
||||
@@ -290,10 +390,10 @@ def generate_separate_protocol(
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成分离操作的协议序列 - 增强中文版
|
||||
生成分离操作的协议序列 - 支持vessel字典和体积运算
|
||||
|
||||
支持XDL参数格式:
|
||||
- vessel: 分离容器(必需)
|
||||
- vessel: 分离容器字典(必需)
|
||||
- purpose: "wash", "extract", "separate"
|
||||
- product_phase: "top", "bottom"
|
||||
- product_vessel: 产物收集容器
|
||||
@@ -310,10 +410,29 @@ def generate_separate_protocol(
|
||||
5. 重复指定次数
|
||||
"""
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("🧪 开始生成分离协议 - 增强中文版")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 容器: '{vessel}'")
|
||||
# 🔧 核心修改:vessel参数兼容处理
|
||||
if vessel is None:
|
||||
if isinstance(separation_vessel, dict):
|
||||
vessel = separation_vessel
|
||||
else:
|
||||
raise ValueError("必须提供vessel字典参数")
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
if "id" not in vessel:
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
else:
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
debug_print("🌀" * 20)
|
||||
debug_print("🚀 开始生成分离协议(支持vessel字典和体积运算)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 vessel: {vessel} (ID: {vessel_id})")
|
||||
debug_print(f" 🎯 分离目的: '{purpose}'")
|
||||
debug_print(f" 📊 产物相: '{product_phase}'")
|
||||
debug_print(f" 💧 溶剂: '{solvent}'")
|
||||
@@ -322,24 +441,33 @@ def generate_separate_protocol(
|
||||
debug_print(f" 🎯 产物容器: '{product_vessel}'")
|
||||
debug_print(f" 🗑️ 废液容器: '{waste_vessel}'")
|
||||
debug_print(f" 📦 其他参数: {kwargs}")
|
||||
debug_print("=" * 60)
|
||||
debug_print("🌀" * 20)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# 🔧 新增:记录分离前的容器状态
|
||||
debug_print("🔍 记录分离前容器状态...")
|
||||
original_liquid_volume = get_vessel_liquid_volume(vessel)
|
||||
debug_print(f"📊 分离前液体体积: {original_liquid_volume:.2f}mL")
|
||||
|
||||
# === 参数验证和标准化 ===
|
||||
debug_print("🔍 步骤1: 参数验证和标准化...")
|
||||
action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel_id}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"分离目的: {purpose}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"产物相: {product_phase}", "📊"))
|
||||
|
||||
# 统一容器参数
|
||||
final_vessel = vessel or separation_vessel
|
||||
if not final_vessel:
|
||||
debug_print("❌ 必须指定分离容器")
|
||||
raise ValueError("必须指定分离容器 (vessel 或 separation_vessel)")
|
||||
# 统一容器参数 - 支持字典和字符串
|
||||
def extract_vessel_id(vessel_param):
|
||||
if isinstance(vessel_param, dict):
|
||||
return vessel_param.get("id", "")
|
||||
elif isinstance(vessel_param, str):
|
||||
return vessel_param
|
||||
else:
|
||||
return ""
|
||||
|
||||
final_to_vessel = to_vessel or product_vessel
|
||||
final_waste_vessel = waste_phase_to_vessel or waste_vessel
|
||||
final_vessel_id = vessel_id
|
||||
final_to_vessel_id = extract_vessel_id(to_vessel) or extract_vessel_id(product_vessel)
|
||||
final_waste_vessel_id = extract_vessel_id(waste_phase_to_vessel) or extract_vessel_id(waste_vessel)
|
||||
|
||||
# 统一体积参数
|
||||
final_volume = parse_volume_input(volume or solvent_volume)
|
||||
@@ -350,13 +478,13 @@ def generate_separate_protocol(
|
||||
debug_print(f"⚠️ 重复次数参数 <= 0,自动设置为 1")
|
||||
|
||||
debug_print(f"🔧 标准化后的参数:")
|
||||
debug_print(f" 🥼 分离容器: '{final_vessel}'")
|
||||
debug_print(f" 🎯 产物容器: '{final_to_vessel}'")
|
||||
debug_print(f" 🗑️ 废液容器: '{final_waste_vessel}'")
|
||||
debug_print(f" 🥼 分离容器: '{final_vessel_id}'")
|
||||
debug_print(f" 🎯 产物容器: '{final_to_vessel_id}'")
|
||||
debug_print(f" 🗑️ 废液容器: '{final_waste_vessel_id}'")
|
||||
debug_print(f" 📏 溶剂体积: {final_volume}mL")
|
||||
debug_print(f" 🔄 重复次数: {repeats}")
|
||||
|
||||
action_sequence.append(create_action_log(f"分离容器: {final_vessel}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"分离容器: {final_vessel_id}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"溶剂体积: {final_volume}mL", "📏"))
|
||||
action_sequence.append(create_action_log(f"重复次数: {repeats}", "🔄"))
|
||||
|
||||
@@ -382,7 +510,7 @@ def generate_separate_protocol(
|
||||
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
|
||||
|
||||
# 查找分离器设备
|
||||
separator_device = find_separator_device(G, final_vessel)
|
||||
separator_device = find_separator_device(G, final_vessel_id) # 🔧 使用 final_vessel_id
|
||||
if separator_device:
|
||||
action_sequence.append(create_action_log(f"找到分离器设备: {separator_device}", "🧪"))
|
||||
else:
|
||||
@@ -390,7 +518,7 @@ def generate_separate_protocol(
|
||||
action_sequence.append(create_action_log("未找到分离器设备", "⚠️"))
|
||||
|
||||
# 查找搅拌器
|
||||
stirrer_device = find_connected_stirrer(G, final_vessel)
|
||||
stirrer_device = find_connected_stirrer(G, final_vessel_id) # 🔧 使用 final_vessel_id
|
||||
if stirrer_device:
|
||||
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_device}", "🌪️"))
|
||||
else:
|
||||
@@ -414,6 +542,9 @@ def generate_separate_protocol(
|
||||
debug_print("🔍 步骤3: 执行分离流程...")
|
||||
action_sequence.append(create_action_log("开始分离工作流程", "🎯"))
|
||||
|
||||
# 🔧 新增:体积变化跟踪变量
|
||||
current_volume = original_liquid_volume
|
||||
|
||||
try:
|
||||
for repeat_idx in range(repeats):
|
||||
cycle_num = repeat_idx + 1
|
||||
@@ -430,7 +561,7 @@ def generate_separate_protocol(
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
to_vessel=final_vessel,
|
||||
to_vessel=final_vessel_id, # 🔧 使用 final_vessel_id
|
||||
volume=final_volume,
|
||||
amount="",
|
||||
time=0.0,
|
||||
@@ -450,6 +581,10 @@ def generate_separate_protocol(
|
||||
debug_print(f"✅ 溶剂添加完成,添加了 {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
# 🔧 新增:更新体积 - 添加溶剂后
|
||||
current_volume += final_volume
|
||||
update_vessel_volume(vessel, G, current_volume, f"添加{final_volume}mL {solvent}后")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 溶剂添加失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"溶剂添加失败: {str(e)}", "❌"))
|
||||
@@ -466,7 +601,7 @@ def generate_separate_protocol(
|
||||
"device_id": stirrer_device,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": final_vessel,
|
||||
"vessel": final_vessel_id, # 🔧 使用 final_vessel_id
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"分离混合 - {purpose}"
|
||||
}
|
||||
@@ -485,7 +620,7 @@ def generate_separate_protocol(
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_device,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {"vessel": final_vessel}
|
||||
"action_kwargs": {"vessel": final_vessel_id} # 🔧 使用 final_vessel_id
|
||||
})
|
||||
|
||||
else:
|
||||
@@ -517,10 +652,10 @@ def generate_separate_protocol(
|
||||
"action_kwargs": {
|
||||
"purpose": purpose,
|
||||
"product_phase": product_phase,
|
||||
"from_vessel": from_vessel or final_vessel,
|
||||
"separation_vessel": final_vessel,
|
||||
"to_vessel": final_to_vessel or final_vessel,
|
||||
"waste_phase_to_vessel": final_waste_vessel or final_vessel,
|
||||
"from_vessel": extract_vessel_id(from_vessel) or final_vessel_id, # 🔧 使用vessel_id
|
||||
"separation_vessel": final_vessel_id, # 🔧 使用 final_vessel_id
|
||||
"to_vessel": final_to_vessel_id or final_vessel_id, # 🔧 使用vessel_id
|
||||
"waste_phase_to_vessel": final_waste_vessel_id or final_vessel_id, # 🔧 使用vessel_id
|
||||
"solvent": solvent,
|
||||
"solvent_volume": final_volume,
|
||||
"through": through,
|
||||
@@ -534,11 +669,17 @@ def generate_separate_protocol(
|
||||
debug_print(f"✅ 分离操作已添加")
|
||||
action_sequence.append(create_action_log("分离操作完成", "✅"))
|
||||
|
||||
# 🔧 新增:分离后体积估算(分离通常不改变总体积,但会重新分配)
|
||||
# 假设分离后保持体积(实际情况可能有少量损失)
|
||||
separated_volume = current_volume * 0.95 # 假设5%损失
|
||||
update_vessel_volume(vessel, G, separated_volume, f"分离操作后(第{cycle_num}轮)")
|
||||
current_volume = separated_volume
|
||||
|
||||
# 收集结果
|
||||
if final_to_vessel:
|
||||
action_sequence.append(create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel}", "📦"))
|
||||
if final_waste_vessel:
|
||||
action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel}", "🗑️"))
|
||||
if final_to_vessel_id:
|
||||
action_sequence.append(create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel_id}", "📦"))
|
||||
if final_waste_vessel_id:
|
||||
action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel_id}", "🗑️"))
|
||||
|
||||
else:
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤4: 无分离器设备,跳过分离")
|
||||
@@ -572,99 +713,37 @@ def generate_separate_protocol(
|
||||
}
|
||||
})
|
||||
|
||||
# 🔧 新增:分离完成后的最终状态报告
|
||||
final_liquid_volume = get_vessel_liquid_volume(vessel)
|
||||
|
||||
# === 最终结果 ===
|
||||
total_time = (stir_time + settling_time + 15) * repeats # 估算总时间
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("🌀" * 20)
|
||||
debug_print(f"🎉 分离协议生成完成")
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||||
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
|
||||
debug_print(f" 🥼 分离容器: {final_vessel}")
|
||||
debug_print(f" 🥼 分离容器: {final_vessel_id}")
|
||||
debug_print(f" 🎯 分离目的: {purpose}")
|
||||
debug_print(f" 📊 产物相: {product_phase}")
|
||||
debug_print(f" 🔄 重复次数: {repeats}")
|
||||
debug_print(f"💧 体积变化统计:")
|
||||
debug_print(f" - 分离前体积: {original_liquid_volume:.2f}mL")
|
||||
debug_print(f" - 分离后体积: {final_liquid_volume:.2f}mL")
|
||||
if solvent:
|
||||
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
|
||||
if final_to_vessel:
|
||||
debug_print(f" 🎯 产物容器: {final_to_vessel}")
|
||||
if final_waste_vessel:
|
||||
debug_print(f" 🗑️ 废液容器: {final_waste_vessel}")
|
||||
debug_print("=" * 60)
|
||||
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL × {repeats}轮 = {final_volume * repeats:.2f}mL)")
|
||||
if final_to_vessel_id:
|
||||
debug_print(f" 🎯 产物容器: {final_to_vessel_id}")
|
||||
if final_waste_vessel_id:
|
||||
debug_print(f" 🗑️ 废液容器: {final_waste_vessel_id}")
|
||||
debug_print("🌀" * 20)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"分离协议完成: {final_vessel} ({purpose},{repeats} 次循环)"
|
||||
summary_msg = f"分离协议完成: {final_vessel_id} ({purpose},{repeats} 次循环)"
|
||||
if solvent:
|
||||
summary_msg += f",使用 {final_volume}mL {solvent}"
|
||||
summary_msg += f",使用 {final_volume * repeats:.2f}mL {solvent}"
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
|
||||
def separate_phases_only(G: nx.DiGraph, vessel: str, product_phase: str = "top",
|
||||
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
|
||||
"""仅进行相分离(不添加溶剂)"""
|
||||
debug_print(f"⚡ 快速相分离: {vessel} ({product_phase}相)")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="separate",
|
||||
product_phase=product_phase,
|
||||
product_vessel=product_vessel,
|
||||
waste_vessel=waste_vessel
|
||||
)
|
||||
|
||||
def wash_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
product_phase: str = "top", repeats: int = 1) -> List[Dict[str, Any]]:
|
||||
"""用溶剂洗涤"""
|
||||
debug_print(f"🧽 用{solvent}洗涤: {vessel} ({repeats} 次)")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="wash",
|
||||
product_phase=product_phase,
|
||||
solvent=solvent,
|
||||
volume=volume,
|
||||
repeats=repeats
|
||||
)
|
||||
|
||||
def extract_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
product_phase: str = "bottom", repeats: int = 3) -> List[Dict[str, Any]]:
|
||||
"""用溶剂萃取"""
|
||||
debug_print(f"🧪 用{solvent}萃取: {vessel} ({repeats} 次)")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="extract",
|
||||
product_phase=product_phase,
|
||||
solvent=solvent,
|
||||
volume=volume,
|
||||
repeats=repeats
|
||||
)
|
||||
|
||||
def separate_aqueous_organic(G: nx.DiGraph, vessel: str, organic_phase: str = "top",
|
||||
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
|
||||
"""水-有机相分离"""
|
||||
debug_print(f"💧 水-有机相分离: {vessel} (有机相: {organic_phase})")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="separate",
|
||||
product_phase=organic_phase,
|
||||
product_vessel=product_vessel,
|
||||
waste_vessel=waste_vessel
|
||||
)
|
||||
|
||||
# 测试函数
|
||||
def test_separate_protocol():
|
||||
"""测试分离协议的各种参数解析"""
|
||||
debug_print("=== 分离协议增强中文版测试 ===")
|
||||
|
||||
# 测试体积解析
|
||||
debug_print("🧪 测试体积解析...")
|
||||
volumes = ["200 mL", "?", 100.0, "1 L", "500 μL", "未知", "50毫升"]
|
||||
for vol in volumes:
|
||||
result = parse_volume_input(vol)
|
||||
debug_print(f"📊 体积解析结果: {vol} -> {result}mL")
|
||||
|
||||
debug_print("✅ 测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_separate_protocol()
|
||||
|
||||
@@ -138,9 +138,50 @@ def validate_and_fix_params(stir_time: float, stir_speed: float, settling_time:
|
||||
|
||||
return stir_time, stir_speed, settling_time
|
||||
|
||||
def extract_vessel_id(vessel: Union[str, dict]) -> str:
|
||||
"""
|
||||
从vessel参数中提取vessel_id
|
||||
|
||||
Args:
|
||||
vessel: vessel字典或vessel_id字符串
|
||||
|
||||
Returns:
|
||||
str: vessel_id
|
||||
"""
|
||||
if isinstance(vessel, dict):
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
||||
return vessel_id
|
||||
elif isinstance(vessel, str):
|
||||
debug_print(f"🔧 vessel参数为字符串: {vessel}")
|
||||
return vessel
|
||||
else:
|
||||
debug_print(f"⚠️ 无效的vessel参数类型: {type(vessel)}")
|
||||
return ""
|
||||
|
||||
def get_vessel_display_info(vessel: Union[str, dict]) -> str:
|
||||
"""
|
||||
获取容器的显示信息(用于日志)
|
||||
|
||||
Args:
|
||||
vessel: vessel字典或vessel_id字符串
|
||||
|
||||
Returns:
|
||||
str: 显示信息
|
||||
"""
|
||||
if isinstance(vessel, dict):
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
vessel_name = vessel.get("name", "")
|
||||
if vessel_name:
|
||||
return f"{vessel_id} ({vessel_name})"
|
||||
else:
|
||||
return vessel_id
|
||||
else:
|
||||
return str(vessel)
|
||||
|
||||
def generate_stir_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: Union[str, dict], # 支持vessel字典或字符串
|
||||
time: Union[str, float, int] = "300",
|
||||
stir_time: Union[str, float, int] = "0",
|
||||
time_spec: str = "",
|
||||
@@ -149,14 +190,39 @@ def generate_stir_protocol(
|
||||
settling_time: Union[str, float] = "60",
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成搅拌操作的协议序列(精简版)
|
||||
"""
|
||||
"""生成搅拌操作的协议序列 - 修复vessel参数传递"""
|
||||
|
||||
# 🔧 核心修改:正确处理vessel参数
|
||||
vessel_id = extract_vessel_id(vessel)
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
|
||||
# 🔧 关键修复:确保vessel_resource是完整的Resource对象
|
||||
if isinstance(vessel, dict):
|
||||
vessel_resource = vessel # 已经是完整的Resource字典
|
||||
debug_print(f"✅ 使用传入的vessel Resource对象")
|
||||
else:
|
||||
# 如果只是字符串,构建一个基本的Resource对象
|
||||
vessel_resource = {
|
||||
"id": vessel,
|
||||
"name": "",
|
||||
"category": "",
|
||||
"children": [],
|
||||
"config": "",
|
||||
"data": "",
|
||||
"parent": "",
|
||||
"pose": {
|
||||
"orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0},
|
||||
"position": {"x": 0.0, "y": 0.0, "z": 0.0}
|
||||
},
|
||||
"sample_id": "",
|
||||
"type": ""
|
||||
}
|
||||
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}")
|
||||
|
||||
debug_print("🌪️" * 20)
|
||||
debug_print("🚀 开始生成搅拌协议 ✨")
|
||||
debug_print("🚀 开始生成搅拌协议(支持vessel字典)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 vessel: {vessel}")
|
||||
debug_print(f" 🥽 vessel: {vessel_display} (ID: {vessel_id})")
|
||||
debug_print(f" ⏰ time: {time}")
|
||||
debug_print(f" 🕐 stir_time: {stir_time}")
|
||||
debug_print(f" 🎯 time_spec: {time_spec}")
|
||||
@@ -166,13 +232,13 @@ def generate_stir_protocol(
|
||||
|
||||
# 📋 参数验证
|
||||
debug_print("📍 步骤1: 参数验证... 🔧")
|
||||
if not vessel:
|
||||
if not vessel_id: # 🔧 使用 vessel_id
|
||||
debug_print("❌ vessel 参数不能为空! 😱")
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中! 😞")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
debug_print(f"❌ 容器 '{vessel_id}' 不存在于系统中! 😞")
|
||||
raise ValueError(f"容器 '{vessel_id}' 不存在于系统中")
|
||||
|
||||
debug_print("✅ 基础参数验证通过 🎯")
|
||||
|
||||
@@ -220,7 +286,7 @@ def generate_stir_protocol(
|
||||
# 🔍 查找设备
|
||||
debug_print("📍 步骤3: 查找搅拌设备... 🔍")
|
||||
try:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
|
||||
debug_print(f"🎉 使用搅拌设备: {stirrer_id} ✨")
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
|
||||
@@ -234,13 +300,14 @@ def generate_stir_protocol(
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"time": str(time), # 保持原始格式
|
||||
# 🔧 关键修复:传递vessel_id字符串,而不是完整的Resource对象
|
||||
"vessel": vessel_id, # 传递字符串ID,不是Resource对象
|
||||
"time": str(time),
|
||||
"event": event,
|
||||
"time_spec": time_spec,
|
||||
"stir_time": float(parsed_time), # 确保是数字
|
||||
"stir_speed": float(stir_speed), # 确保是数字
|
||||
"settling_time": float(parsed_settling_time) # 确保是数字
|
||||
"stir_time": float(parsed_time),
|
||||
"stir_speed": float(stir_speed),
|
||||
"settling_time": float(parsed_settling_time)
|
||||
}
|
||||
}
|
||||
action_sequence.append(stir_action)
|
||||
@@ -256,7 +323,7 @@ def generate_stir_protocol(
|
||||
debug_print("🎊" * 20)
|
||||
debug_print(f"🎉 搅拌协议生成完成! ✨")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f"🥽 搅拌容器: {vessel}")
|
||||
debug_print(f"🥽 搅拌容器: {vessel_display}")
|
||||
debug_print(f"🌪️ 搅拌参数: {stir_speed} RPM, {parsed_time}s, 沉降 {parsed_settling_time}s")
|
||||
debug_print(f"⏱️ 预计总时间: {(parsed_time + parsed_settling_time)/60:.1f} 分钟 ⌛")
|
||||
debug_print("🎊" * 20)
|
||||
@@ -265,18 +332,47 @@ def generate_stir_protocol(
|
||||
|
||||
def generate_start_stir_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: Union[str, dict],
|
||||
stir_speed: float = 300.0,
|
||||
purpose: str = "",
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""生成开始搅拌操作的协议序列"""
|
||||
"""生成开始搅拌操作的协议序列 - 修复vessel参数传递"""
|
||||
|
||||
debug_print("🔄 开始生成启动搅拌协议 ✨")
|
||||
debug_print(f"🥽 vessel: {vessel}, 🌪️ speed: {stir_speed} RPM")
|
||||
# 🔧 核心修改:正确处理vessel参数
|
||||
vessel_id = extract_vessel_id(vessel)
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
|
||||
# 🔧 关键修复:确保vessel_resource是完整的Resource对象
|
||||
if isinstance(vessel, dict):
|
||||
vessel_resource = vessel # 已经是完整的Resource字典
|
||||
debug_print(f"✅ 使用传入的vessel Resource对象")
|
||||
else:
|
||||
# 如果只是字符串,构建一个基本的Resource对象
|
||||
vessel_resource = {
|
||||
"id": vessel,
|
||||
"name": "",
|
||||
"category": "",
|
||||
"children": [],
|
||||
"config": "",
|
||||
"data": "",
|
||||
"parent": "",
|
||||
"pose": {
|
||||
"orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0},
|
||||
"position": {"x": 0.0, "y": 0.0, "z": 0.0}
|
||||
},
|
||||
"sample_id": "",
|
||||
"type": ""
|
||||
}
|
||||
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}")
|
||||
|
||||
debug_print("🔄 开始生成启动搅拌协议(修复vessel参数)✨")
|
||||
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
|
||||
debug_print(f"🌪️ speed: {stir_speed} RPM")
|
||||
debug_print(f"🎯 purpose: {purpose}")
|
||||
|
||||
# 基础验证
|
||||
if not vessel or vessel not in G.nodes():
|
||||
if not vessel_id or vessel_id not in G.nodes():
|
||||
debug_print("❌ 容器验证失败!")
|
||||
raise ValueError("vessel 参数无效")
|
||||
|
||||
@@ -286,14 +382,15 @@ def generate_start_stir_protocol(
|
||||
stir_speed = 300.0
|
||||
|
||||
# 查找设备
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id)
|
||||
|
||||
# 生成动作
|
||||
# 🔧 关键修复:传递vessel_id字符串
|
||||
action_sequence = [{
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
# 🔧 关键修复:传递vessel_id字符串,而不是完整的Resource对象
|
||||
"vessel": vessel_id, # 传递字符串ID,不是Resource对象
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": purpose or f"启动搅拌 {stir_speed} RPM"
|
||||
}
|
||||
@@ -304,38 +401,144 @@ def generate_start_stir_protocol(
|
||||
|
||||
def generate_stop_stir_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: Union[str, dict],
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""生成停止搅拌操作的协议序列"""
|
||||
"""生成停止搅拌操作的协议序列 - 修复vessel参数传递"""
|
||||
|
||||
debug_print("🛑 开始生成停止搅拌协议 ✨")
|
||||
debug_print(f"🥽 vessel: {vessel}")
|
||||
# 🔧 核心修改:正确处理vessel参数
|
||||
vessel_id = extract_vessel_id(vessel)
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
|
||||
# 🔧 关键修复:确保vessel_resource是完整的Resource对象
|
||||
if isinstance(vessel, dict):
|
||||
vessel_resource = vessel # 已经是完整的Resource字典
|
||||
debug_print(f"✅ 使用传入的vessel Resource对象")
|
||||
else:
|
||||
# 如果只是字符串,构建一个基本的Resource对象
|
||||
vessel_resource = {
|
||||
"id": vessel,
|
||||
"name": "",
|
||||
"category": "",
|
||||
"children": [],
|
||||
"config": "",
|
||||
"data": "",
|
||||
"parent": "",
|
||||
"pose": {
|
||||
"orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0},
|
||||
"position": {"x": 0.0, "y": 0.0, "z": 0.0}
|
||||
},
|
||||
"sample_id": "",
|
||||
"type": ""
|
||||
}
|
||||
debug_print(f"🔧 构建了基本的vessel Resource对象: {vessel}")
|
||||
|
||||
debug_print("🛑 开始生成停止搅拌协议(修复vessel参数)✨")
|
||||
debug_print(f"🥽 vessel: {vessel_display} (ID: {vessel_id})")
|
||||
|
||||
# 基础验证
|
||||
if not vessel or vessel not in G.nodes():
|
||||
if not vessel_id or vessel_id not in G.nodes():
|
||||
debug_print("❌ 容器验证失败!")
|
||||
raise ValueError("vessel 参数无效")
|
||||
|
||||
# 查找设备
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id)
|
||||
|
||||
# 生成动作
|
||||
# 🔧 关键修复:传递vessel_id字符串
|
||||
action_sequence = [{
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
# 🔧 关键修复:传递vessel_id字符串,而不是完整的Resource对象
|
||||
"vessel": vessel_id # 传递字符串ID,不是Resource对象
|
||||
}
|
||||
}]
|
||||
|
||||
debug_print(f"✅ 停止搅拌协议生成完成 🎯")
|
||||
return action_sequence
|
||||
|
||||
# 🔧 新增:便捷函数
|
||||
def stir_briefly(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
speed: float = 300.0) -> List[Dict[str, Any]]:
|
||||
"""短时间搅拌(30秒)"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"⚡ 短时间搅拌: {vessel_display} @ {speed}RPM (30s)")
|
||||
return generate_stir_protocol(G, vessel, time="30", stir_speed=speed)
|
||||
|
||||
def stir_slowly(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
|
||||
"""慢速搅拌"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🐌 慢速搅拌: {vessel_display} @ 150RPM")
|
||||
return generate_stir_protocol(G, vessel, time=time, stir_speed=150.0)
|
||||
|
||||
def stir_vigorously(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
time: Union[str, float] = "5 min") -> List[Dict[str, Any]]:
|
||||
"""剧烈搅拌"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"💨 剧烈搅拌: {vessel_display} @ 800RPM")
|
||||
return generate_stir_protocol(G, vessel, time=time, stir_speed=800.0)
|
||||
|
||||
def stir_for_reaction(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
time: Union[str, float] = "1 h") -> List[Dict[str, Any]]:
|
||||
"""反应搅拌(标准速度,长时间)"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🧪 反应搅拌: {vessel_display} @ 400RPM")
|
||||
return generate_stir_protocol(G, vessel, time=time, stir_speed=400.0)
|
||||
|
||||
def stir_for_dissolution(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
time: Union[str, float] = "15 min") -> List[Dict[str, Any]]:
|
||||
"""溶解搅拌(中等速度)"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"💧 溶解搅拌: {vessel_display} @ 500RPM")
|
||||
return generate_stir_protocol(G, vessel, time=time, stir_speed=500.0)
|
||||
|
||||
def stir_gently(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
time: Union[str, float] = "30 min") -> List[Dict[str, Any]]:
|
||||
"""温和搅拌"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🍃 温和搅拌: {vessel_display} @ 200RPM")
|
||||
return generate_stir_protocol(G, vessel, time=time, stir_speed=200.0)
|
||||
|
||||
def stir_overnight(G: nx.DiGraph, vessel: Union[str, dict]) -> List[Dict[str, Any]]:
|
||||
"""过夜搅拌(模拟时缩短为2小时)"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🌙 过夜搅拌(模拟2小时): {vessel_display} @ 300RPM")
|
||||
return generate_stir_protocol(G, vessel, time="2 h", stir_speed=300.0)
|
||||
|
||||
def start_continuous_stirring(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
speed: float = 300.0, purpose: str = "continuous stirring") -> List[Dict[str, Any]]:
|
||||
"""开始连续搅拌"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🔄 开始连续搅拌: {vessel_display} @ {speed}RPM")
|
||||
return generate_start_stir_protocol(G, vessel, stir_speed=speed, purpose=purpose)
|
||||
|
||||
def stop_all_stirring(G: nx.DiGraph, vessel: Union[str, dict]) -> List[Dict[str, Any]]:
|
||||
"""停止所有搅拌"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🛑 停止搅拌: {vessel_display}")
|
||||
return generate_stop_stir_protocol(G, vessel)
|
||||
|
||||
# 测试函数
|
||||
def test_stir_protocol():
|
||||
"""测试搅拌协议"""
|
||||
debug_print("🧪 === STIR PROTOCOL 测试 === ✨")
|
||||
|
||||
# 测试vessel参数处理
|
||||
debug_print("🔧 测试vessel参数处理...")
|
||||
|
||||
# 测试字典格式
|
||||
vessel_dict = {"id": "flask_1", "name": "反应瓶1"}
|
||||
vessel_id = extract_vessel_id(vessel_dict)
|
||||
vessel_display = get_vessel_display_info(vessel_dict)
|
||||
debug_print(f" 字典格式: {vessel_dict} → ID: {vessel_id}, 显示: {vessel_display}")
|
||||
|
||||
# 测试字符串格式
|
||||
vessel_str = "flask_2"
|
||||
vessel_id = extract_vessel_id(vessel_str)
|
||||
vessel_display = get_vessel_display_info(vessel_str)
|
||||
debug_print(f" 字符串格式: {vessel_str} → ID: {vessel_id}, 显示: {vessel_display}")
|
||||
|
||||
debug_print("✅ 测试完成 🎉")
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -154,12 +154,153 @@ def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
|
||||
debug_print(f"⚠️ 使用默认滤液容器: waste_workup")
|
||||
return "waste_workup"
|
||||
|
||||
def extract_vessel_id(vessel: Union[str, dict]) -> str:
|
||||
"""
|
||||
从vessel参数中提取vessel_id
|
||||
|
||||
Args:
|
||||
vessel: vessel字典或vessel_id字符串
|
||||
|
||||
Returns:
|
||||
str: vessel_id
|
||||
"""
|
||||
if isinstance(vessel, dict):
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
||||
return vessel_id
|
||||
elif isinstance(vessel, str):
|
||||
debug_print(f"🔧 vessel参数为字符串: {vessel}")
|
||||
return vessel
|
||||
else:
|
||||
debug_print(f"⚠️ 无效的vessel参数类型: {type(vessel)}")
|
||||
return ""
|
||||
|
||||
def get_vessel_display_info(vessel: Union[str, dict]) -> str:
|
||||
"""
|
||||
获取容器的显示信息(用于日志)
|
||||
|
||||
Args:
|
||||
vessel: vessel字典或vessel_id字符串
|
||||
|
||||
Returns:
|
||||
str: 显示信息
|
||||
"""
|
||||
if isinstance(vessel, dict):
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
vessel_name = vessel.get("name", "")
|
||||
if vessel_name:
|
||||
return f"{vessel_id} ({vessel_name})"
|
||||
else:
|
||||
return vessel_id
|
||||
else:
|
||||
return str(vessel)
|
||||
|
||||
def get_vessel_liquid_volume(vessel: dict) -> float:
|
||||
"""
|
||||
获取容器中的液体体积 - 支持vessel字典
|
||||
|
||||
Args:
|
||||
vessel: 容器字典
|
||||
|
||||
Returns:
|
||||
float: 液体体积(mL)
|
||||
"""
|
||||
if not vessel or "data" not in vessel:
|
||||
debug_print(f"⚠️ 容器数据为空,返回 0.0mL")
|
||||
return 0.0
|
||||
|
||||
vessel_data = vessel["data"]
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
|
||||
debug_print(f"🔍 读取容器 '{vessel_id}' 体积数据: {vessel_data}")
|
||||
|
||||
# 检查liquid_volume字段
|
||||
if "liquid_volume" in vessel_data:
|
||||
liquid_volume = vessel_data["liquid_volume"]
|
||||
|
||||
# 处理列表格式
|
||||
if isinstance(liquid_volume, list):
|
||||
if len(liquid_volume) > 0:
|
||||
volume = liquid_volume[0]
|
||||
if isinstance(volume, (int, float)):
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (列表格式)")
|
||||
return float(volume)
|
||||
|
||||
# 处理直接数值格式
|
||||
elif isinstance(liquid_volume, (int, float)):
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {liquid_volume}mL (数值格式)")
|
||||
return float(liquid_volume)
|
||||
|
||||
# 检查其他可能的体积字段
|
||||
volume_keys = ['current_volume', 'total_volume', 'volume']
|
||||
for key in volume_keys:
|
||||
if key in vessel_data:
|
||||
try:
|
||||
volume = float(vessel_data[key])
|
||||
if volume > 0:
|
||||
debug_print(f"✅ 容器 '{vessel_id}' 体积: {volume}mL (字段: {key})")
|
||||
return volume
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
debug_print(f"⚠️ 无法获取容器 '{vessel_id}' 的体积,返回默认值 0.0mL")
|
||||
return 0.0
|
||||
|
||||
def update_vessel_volume(vessel: dict, G: nx.DiGraph, new_volume: float, description: str = "") -> None:
|
||||
"""
|
||||
更新容器体积(同时更新vessel字典和图节点)
|
||||
|
||||
Args:
|
||||
vessel: 容器字典
|
||||
G: 网络图
|
||||
new_volume: 新体积
|
||||
description: 更新描述
|
||||
"""
|
||||
vessel_id = vessel.get("id", "unknown")
|
||||
|
||||
if description:
|
||||
debug_print(f"🔧 更新容器体积 - {description}")
|
||||
|
||||
# 更新vessel字典中的体积
|
||||
if "data" in vessel:
|
||||
if "liquid_volume" in vessel["data"]:
|
||||
current_volume = vessel["data"]["liquid_volume"]
|
||||
if isinstance(current_volume, list):
|
||||
if len(current_volume) > 0:
|
||||
vessel["data"]["liquid_volume"][0] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = [new_volume]
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"]["liquid_volume"] = new_volume
|
||||
else:
|
||||
vessel["data"] = {"liquid_volume": new_volume}
|
||||
|
||||
# 同时更新图中的容器数据
|
||||
if vessel_id in G.nodes():
|
||||
if 'data' not in G.nodes[vessel_id]:
|
||||
G.nodes[vessel_id]['data'] = {}
|
||||
|
||||
vessel_node_data = G.nodes[vessel_id]['data']
|
||||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||||
|
||||
if isinstance(current_node_volume, list):
|
||||
if len(current_node_volume) > 0:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'][0] = new_volume
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = [new_volume]
|
||||
else:
|
||||
G.nodes[vessel_id]['data']['liquid_volume'] = new_volume
|
||||
|
||||
debug_print(f"📊 容器 '{vessel_id}' 体积已更新为: {new_volume:.2f}mL")
|
||||
|
||||
def generate_wash_solid_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
vessel: Union[str, dict], # 🔧 修改:支持vessel字典
|
||||
solvent: str,
|
||||
volume: Union[float, str] = "50",
|
||||
filtrate_vessel: str = "",
|
||||
filtrate_vessel: Union[str, dict] = "", # 🔧 修改:支持vessel字典
|
||||
temp: float = 25.0,
|
||||
stir: bool = False,
|
||||
stir_speed: float = 0.0,
|
||||
@@ -172,21 +313,58 @@ def generate_wash_solid_protocol(
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成固体清洗协议(精简版)
|
||||
生成固体清洗协议 - 支持vessel字典和体积运算
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
vessel: 清洗容器字典(从XDL传入)或容器ID字符串
|
||||
solvent: 清洗溶剂名称
|
||||
volume: 溶剂体积(每次清洗)
|
||||
filtrate_vessel: 滤液收集容器字典或容器ID字符串
|
||||
temp: 清洗温度(°C)
|
||||
stir: 是否搅拌
|
||||
stir_speed: 搅拌速度(RPM)
|
||||
time: 搅拌时间
|
||||
repeats: 清洗重复次数
|
||||
volume_spec: 体积规格(small/medium/large)
|
||||
repeats_spec: 重复次数规格(few/several/many)
|
||||
mass: 固体质量(用于计算溶剂用量)
|
||||
event: 事件描述
|
||||
**kwargs: 其他可选参数
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 固体清洗操作的动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从vessel参数中提取vessel_id
|
||||
vessel_id = extract_vessel_id(vessel)
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
|
||||
# 🔧 处理filtrate_vessel参数
|
||||
filtrate_vessel_id = extract_vessel_id(filtrate_vessel) if filtrate_vessel else ""
|
||||
|
||||
debug_print("🧼" * 20)
|
||||
debug_print("🚀 开始生成固体清洗协议 ✨")
|
||||
debug_print("🚀 开始生成固体清洗协议(支持vessel字典和体积运算)✨")
|
||||
debug_print(f"📝 输入参数:")
|
||||
debug_print(f" 🥽 vessel: {vessel}")
|
||||
debug_print(f" 🥽 vessel: {vessel_display} (ID: {vessel_id})")
|
||||
debug_print(f" 🧪 solvent: {solvent}")
|
||||
debug_print(f" 💧 volume: {volume}")
|
||||
debug_print(f" 🗑️ filtrate_vessel: {filtrate_vessel_id}")
|
||||
debug_print(f" ⏰ time: {time}")
|
||||
debug_print(f" 🔄 repeats: {repeats}")
|
||||
debug_print("🧼" * 20)
|
||||
|
||||
# 🔧 新增:记录清洗前的容器状态
|
||||
debug_print("🔍 记录清洗前容器状态...")
|
||||
if isinstance(vessel, dict):
|
||||
original_volume = get_vessel_liquid_volume(vessel)
|
||||
debug_print(f"📊 清洗前液体体积: {original_volume:.2f}mL")
|
||||
else:
|
||||
original_volume = 0.0
|
||||
debug_print(f"📊 vessel为字符串格式,无法获取体积信息")
|
||||
|
||||
# 📋 快速验证
|
||||
if not vessel or vessel not in G.nodes():
|
||||
if not vessel_id or vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||||
debug_print("❌ 容器验证失败! 😱")
|
||||
raise ValueError("vessel 参数无效")
|
||||
|
||||
@@ -225,8 +403,10 @@ def generate_wash_solid_protocol(
|
||||
debug_print("📍 步骤2: 查找设备... 🔍")
|
||||
try:
|
||||
solvent_source = find_solvent_source(G, solvent)
|
||||
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
|
||||
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel_id)
|
||||
debug_print(f"🎉 设备配置完成 ✨")
|
||||
debug_print(f" 🧪 溶剂源: {solvent_source}")
|
||||
debug_print(f" 🗑️ 滤液容器: {actual_filtrate_vessel}")
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 设备查找失败: {str(e)} 😭")
|
||||
raise ValueError(f"设备查找失败: {str(e)}")
|
||||
@@ -235,6 +415,10 @@ def generate_wash_solid_protocol(
|
||||
debug_print("📍 步骤3: 生成清洗动作... 🧼")
|
||||
action_sequence = []
|
||||
|
||||
# 🔧 新增:体积变化跟踪变量
|
||||
current_volume = original_volume
|
||||
total_solvent_used = 0.0
|
||||
|
||||
for cycle in range(final_repeats):
|
||||
debug_print(f" 🔄 第{cycle+1}/{final_repeats}次清洗...")
|
||||
|
||||
@@ -242,10 +426,11 @@ def generate_wash_solid_protocol(
|
||||
try:
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
debug_print(f" 💧 添加溶剂: {final_volume}mL {solvent}")
|
||||
transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent_source,
|
||||
to_vessel=vessel,
|
||||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||||
volume=final_volume,
|
||||
amount="",
|
||||
time=0.0,
|
||||
@@ -261,17 +446,26 @@ def generate_wash_solid_protocol(
|
||||
if transfer_actions:
|
||||
action_sequence.extend(transfer_actions)
|
||||
debug_print(f" ✅ 转移动作: {len(transfer_actions)}个 🚚")
|
||||
|
||||
# 🔧 新增:更新体积 - 添加溶剂后
|
||||
current_volume += final_volume
|
||||
total_solvent_used += final_volume
|
||||
|
||||
if isinstance(vessel, dict):
|
||||
update_vessel_volume(vessel, G, current_volume,
|
||||
f"第{cycle+1}次清洗添加{final_volume}mL溶剂后")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f" ❌ 转移失败: {str(e)} 😞")
|
||||
|
||||
# 2. 搅拌(如果需要)
|
||||
if stir and final_time > 0:
|
||||
debug_print(f" 🌪️ 搅拌: {final_time}s @ {stir_speed}RPM")
|
||||
stir_action = {
|
||||
"device_id": "stirrer_1",
|
||||
"action_name": "stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"time": str(time),
|
||||
"stir_time": final_time,
|
||||
"stir_speed": stir_speed,
|
||||
@@ -282,11 +476,12 @@ def generate_wash_solid_protocol(
|
||||
debug_print(f" ✅ 搅拌动作: {final_time}s, {stir_speed}RPM 🌪️")
|
||||
|
||||
# 3. 过滤
|
||||
debug_print(f" 🌊 过滤到: {actual_filtrate_vessel}")
|
||||
filter_action = {
|
||||
"device_id": "filter_1",
|
||||
"action_name": "filter",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": vessel_id, # 🔧 使用 vessel_id
|
||||
"filtrate_vessel": actual_filtrate_vessel,
|
||||
"temp": temp,
|
||||
"volume": final_volume
|
||||
@@ -295,6 +490,15 @@ def generate_wash_solid_protocol(
|
||||
action_sequence.append(filter_action)
|
||||
debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊")
|
||||
|
||||
# 🔧 新增:更新体积 - 过滤后(液体被滤除)
|
||||
# 假设滤液完全被移除,固体残留在容器中
|
||||
filtered_volume = current_volume * 0.9 # 假设90%的液体被过滤掉
|
||||
current_volume = current_volume - filtered_volume
|
||||
|
||||
if isinstance(vessel, dict):
|
||||
update_vessel_volume(vessel, G, current_volume,
|
||||
f"第{cycle+1}次清洗过滤后")
|
||||
|
||||
# 4. 等待(缩短时间)
|
||||
wait_time = 5.0 # 🕐 缩短等待时间:10s → 5s
|
||||
action_sequence.append({
|
||||
@@ -303,14 +507,146 @@ def generate_wash_solid_protocol(
|
||||
})
|
||||
debug_print(f" ✅ 等待: {wait_time}s ⏰")
|
||||
|
||||
# 🔧 新增:清洗完成后的最终状态报告
|
||||
if isinstance(vessel, dict):
|
||||
final_volume_vessel = get_vessel_liquid_volume(vessel)
|
||||
else:
|
||||
final_volume_vessel = current_volume
|
||||
|
||||
# 🎊 总结
|
||||
debug_print("🧼" * 20)
|
||||
debug_print(f"🎉 固体清洗协议生成完成! ✨")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f"🥽 清洗容器: {vessel}")
|
||||
debug_print(f"🧪 使用溶剂: {solvent}")
|
||||
debug_print(f"💧 清洗体积: {final_volume}mL × {final_repeats}次")
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)} 个")
|
||||
debug_print(f" 🥽 清洗容器: {vessel_display}")
|
||||
debug_print(f" 🧪 使用溶剂: {solvent}")
|
||||
debug_print(f" 💧 单次体积: {final_volume}mL")
|
||||
debug_print(f" 🔄 清洗次数: {final_repeats}次")
|
||||
debug_print(f" 💧 总溶剂用量: {total_solvent_used:.2f}mL")
|
||||
debug_print(f"📊 体积变化统计:")
|
||||
debug_print(f" - 清洗前体积: {original_volume:.2f}mL")
|
||||
debug_print(f" - 清洗后体积: {final_volume_vessel:.2f}mL")
|
||||
debug_print(f" - 溶剂总用量: {total_solvent_used:.2f}mL")
|
||||
debug_print(f"⏱️ 预计总时间: {(final_time + 5) * final_repeats / 60:.1f} 分钟")
|
||||
debug_print("🧼" * 20)
|
||||
|
||||
return action_sequence
|
||||
return action_sequence
|
||||
|
||||
# 🔧 新增:便捷函数
|
||||
def wash_with_water(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
volume: Union[float, str] = "50",
|
||||
repeats: int = 2) -> List[Dict[str, Any]]:
|
||||
"""用水清洗固体"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"💧 水洗固体: {vessel_display} ({repeats} 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, "water", volume=volume, repeats=repeats)
|
||||
|
||||
def wash_with_ethanol(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
volume: Union[float, str] = "30",
|
||||
repeats: int = 1) -> List[Dict[str, Any]]:
|
||||
"""用乙醇清洗固体"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🍺 乙醇洗固体: {vessel_display} ({repeats} 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, "ethanol", volume=volume, repeats=repeats)
|
||||
|
||||
def wash_with_acetone(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
volume: Union[float, str] = "25",
|
||||
repeats: int = 1) -> List[Dict[str, Any]]:
|
||||
"""用丙酮清洗固体"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"💨 丙酮洗固体: {vessel_display} ({repeats} 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, "acetone", volume=volume, repeats=repeats)
|
||||
|
||||
def wash_with_ether(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
volume: Union[float, str] = "40",
|
||||
repeats: int = 2) -> List[Dict[str, Any]]:
|
||||
"""用乙醚清洗固体"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🌬️ 乙醚洗固体: {vessel_display} ({repeats} 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, "diethyl_ether", volume=volume, repeats=repeats)
|
||||
|
||||
def wash_with_cold_solvent(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
solvent: str, volume: Union[float, str] = "30",
|
||||
repeats: int = 1) -> List[Dict[str, Any]]:
|
||||
"""用冷溶剂清洗固体"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"❄️ 冷{solvent}洗固体: {vessel_display} ({repeats} 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume,
|
||||
temp=5.0, repeats=repeats)
|
||||
|
||||
def wash_with_hot_solvent(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
solvent: str, volume: Union[float, str] = "50",
|
||||
repeats: int = 1) -> List[Dict[str, Any]]:
|
||||
"""用热溶剂清洗固体"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🔥 热{solvent}洗固体: {vessel_display} ({repeats} 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume,
|
||||
temp=60.0, repeats=repeats)
|
||||
|
||||
def wash_with_stirring(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
solvent: str, volume: Union[float, str] = "50",
|
||||
stir_time: Union[str, float] = "5 min",
|
||||
repeats: int = 1) -> List[Dict[str, Any]]:
|
||||
"""带搅拌的溶剂清洗"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🌪️ 搅拌清洗: {vessel_display} with {solvent} ({repeats} 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume,
|
||||
stir=True, stir_speed=200.0,
|
||||
time=stir_time, repeats=repeats)
|
||||
|
||||
def thorough_wash(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
solvent: str, volume: Union[float, str] = "50") -> List[Dict[str, Any]]:
|
||||
"""彻底清洗(多次重复)"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"🔄 彻底清洗: {vessel_display} with {solvent} (5 次)")
|
||||
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=5)
|
||||
|
||||
def quick_rinse(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
solvent: str, volume: Union[float, str] = "20") -> List[Dict[str, Any]]:
|
||||
"""快速冲洗(单次,小体积)"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"⚡ 快速冲洗: {vessel_display} with {solvent}")
|
||||
return generate_wash_solid_protocol(G, vessel, solvent, volume=volume, repeats=1)
|
||||
|
||||
def sequential_wash(G: nx.DiGraph, vessel: Union[str, dict],
|
||||
solvents: list, volume: Union[float, str] = "40") -> List[Dict[str, Any]]:
|
||||
"""连续多溶剂清洗"""
|
||||
vessel_display = get_vessel_display_info(vessel)
|
||||
debug_print(f"📝 连续清洗: {vessel_display} with {' → '.join(solvents)}")
|
||||
|
||||
action_sequence = []
|
||||
for solvent in solvents:
|
||||
wash_actions = generate_wash_solid_protocol(G, vessel, solvent,
|
||||
volume=volume, repeats=1)
|
||||
action_sequence.extend(wash_actions)
|
||||
|
||||
return action_sequence
|
||||
|
||||
# 测试函数
|
||||
def test_wash_solid_protocol():
|
||||
"""测试固体清洗协议"""
|
||||
debug_print("🧪 === WASH SOLID PROTOCOL 测试 === ✨")
|
||||
|
||||
# 测试vessel参数处理
|
||||
debug_print("🔧 测试vessel参数处理...")
|
||||
|
||||
# 测试字典格式
|
||||
vessel_dict = {"id": "filter_flask_1", "name": "过滤瓶1",
|
||||
"data": {"liquid_volume": 25.0}}
|
||||
vessel_id = extract_vessel_id(vessel_dict)
|
||||
vessel_display = get_vessel_display_info(vessel_dict)
|
||||
volume = get_vessel_liquid_volume(vessel_dict)
|
||||
debug_print(f" 字典格式: {vessel_dict}")
|
||||
debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}, 体积: {volume}mL")
|
||||
|
||||
# 测试字符串格式
|
||||
vessel_str = "filter_flask_2"
|
||||
vessel_id = extract_vessel_id(vessel_str)
|
||||
vessel_display = get_vessel_display_info(vessel_str)
|
||||
debug_print(f" 字符串格式: {vessel_str}")
|
||||
debug_print(f" → ID: {vessel_id}, 显示: {vessel_display}")
|
||||
|
||||
debug_print("✅ 测试完成 🎉")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_wash_solid_protocol()
|
||||
@@ -9,12 +9,14 @@ from unilabos.utils import logger
|
||||
|
||||
class BasicConfig:
|
||||
ENV = "pro" # 'test'
|
||||
working_dir = ""
|
||||
config_path = ""
|
||||
is_host_mode = True
|
||||
slave_no_host = False # 是否跳过rclient.wait_for_service()
|
||||
upload_registry = False
|
||||
machine_name = "undefined"
|
||||
vis_2d_enable = False
|
||||
enable_resource_load = True
|
||||
|
||||
|
||||
# MQTT配置
|
||||
@@ -63,7 +65,7 @@ class ROSConfig:
|
||||
]
|
||||
|
||||
|
||||
def _update_config_from_module(module):
|
||||
def _update_config_from_module(module, override_labid: str):
|
||||
for name, obj in globals().items():
|
||||
if isinstance(obj, type) and name.endswith("Config"):
|
||||
if hasattr(module, name) and isinstance(getattr(module, name), type):
|
||||
@@ -74,6 +76,9 @@ def _update_config_from_module(module):
|
||||
if len(OSSUploadConfig.authorization) == 0:
|
||||
OSSUploadConfig.authorization = f"lab {MQConfig.lab_id}"
|
||||
# 对 ca_file cert_file key_file 进行初始化
|
||||
if override_labid:
|
||||
MQConfig.lab_id = override_labid
|
||||
logger.warning(f"[ENV] 当前实验室启动的ID被设置为:{override_labid}")
|
||||
if len(MQConfig.ca_content) == 0:
|
||||
# 需要先判断是否为相对路径
|
||||
if MQConfig.ca_file.startswith("."):
|
||||
@@ -104,13 +109,13 @@ def _update_config_from_module(module):
|
||||
|
||||
|
||||
def _update_config_from_env():
|
||||
prefix = "UNILABOS."
|
||||
prefix = "UNILABOS_"
|
||||
for env_key, env_value in os.environ.items():
|
||||
if not env_key.startswith(prefix):
|
||||
continue
|
||||
try:
|
||||
key_path = env_key[len(prefix):] # Remove UNILAB_ prefix
|
||||
class_field = key_path.upper().split(".", 1)
|
||||
class_field = key_path.upper().split("_", 1)
|
||||
if len(class_field) != 2:
|
||||
logger.warning(f"[ENV] 环境变量格式不正确:{env_key}")
|
||||
continue
|
||||
@@ -155,15 +160,15 @@ def _update_config_from_env():
|
||||
|
||||
|
||||
|
||||
def load_config(config_path=None):
|
||||
def load_config(config_path=None, override_labid=None):
|
||||
# 如果提供了配置文件路径,从该文件导入配置
|
||||
if config_path:
|
||||
_update_config_from_env() # 允许config_path被env设定后读取
|
||||
env_config_path = os.environ.get("UNILABOS_BASICCONFIG_CONFIG_PATH")
|
||||
config_path = env_config_path if env_config_path else config_path
|
||||
BasicConfig.config_path = os.path.abspath(os.path.dirname(config_path))
|
||||
if not os.path.exists(config_path):
|
||||
logger.error(f"[ENV] 配置文件 {config_path} 不存在")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
module_name = "lab_" + os.path.basename(config_path).replace(".py", "")
|
||||
spec = importlib.util.spec_from_file_location(module_name, config_path)
|
||||
@@ -172,12 +177,13 @@ def load_config(config_path=None):
|
||||
return
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module) # type: ignore
|
||||
_update_config_from_module(module)
|
||||
_update_config_from_module(module, override_labid)
|
||||
logger.info(f"[ENV] 配置文件 {config_path} 加载成功")
|
||||
_update_config_from_env()
|
||||
except Exception as e:
|
||||
logger.error(f"[ENV] 加载配置文件 {config_path} 失败")
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
else:
|
||||
config_path = os.path.join(os.path.dirname(__file__), "local_config.py")
|
||||
load_config(config_path)
|
||||
load_config(config_path, override_labid)
|
||||
|
||||
17
unilabos/config/example_config.py
Normal file
17
unilabos/config/example_config.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# MQTT配置
|
||||
class MQConfig:
|
||||
lab_id = ""
|
||||
instance_id = ""
|
||||
access_key = ""
|
||||
secret_key = ""
|
||||
group_id = ""
|
||||
broker_url = ""
|
||||
port = 1883
|
||||
|
||||
ca_file = "./CA.crt"
|
||||
cert_file = "./lab.crt"
|
||||
key_file = "./lab.key"
|
||||
|
||||
# HTTP配置
|
||||
class HTTPConfig:
|
||||
remote_addr = "https://uni-lab.bohrium.com/api/v1"
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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
|
||||
@@ -1,40 +0,0 @@
|
||||
# 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
|
||||
@@ -1,4 +0,0 @@
|
||||
arm:
|
||||
kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
|
||||
kinematics_solver_search_resolution: 0.0050000000000000001
|
||||
kinematics_solver_timeout: 0.0050000000000000001
|
||||
@@ -1,56 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||
<xacro:macro name="benyao_arm_ros2_control" params="device_name mesh_path">
|
||||
<xacro:property name="initial_positions" value="${load_yaml(mesh_path + '/devices/benyao_arm/config/initial_positions.yaml')['initial_positions']}"/>
|
||||
|
||||
<ros2_control name="${device_name}benyao_arm" type="system">
|
||||
<hardware>
|
||||
<!-- By default, set up controllers for simulation. This won't work on real hardware -->
|
||||
<plugin>mock_components/GenericSystem</plugin>
|
||||
</hardware>
|
||||
<joint name="${device_name}arm_base_joint">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['arm_base_joint']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}arm_link_1_joint">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['arm_link_1_joint']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}arm_link_2_joint">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['arm_link_2_joint']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}arm_link_3_joint">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['arm_link_3_joint']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}gripper_base_joint">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['gripper_base_joint']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}gripper_right_joint">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['gripper_right_joint']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
|
||||
</ros2_control>
|
||||
</xacro:macro>
|
||||
</robot>
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--This does not replace URDF, and is not an extension of URDF.
|
||||
This is a format for representing semantic information about the robot structure.
|
||||
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
|
||||
-->
|
||||
<robot xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
<xacro:macro name="benyao_arm_srdf" params="device_name">
|
||||
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
|
||||
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
|
||||
<!--JOINTS: When a joint is specified, the child link of that joint (which will always exist) is automatically included-->
|
||||
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
|
||||
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names-->
|
||||
<group name="${device_name}arm">
|
||||
<chain base_link="${device_name}arm_slideway" tip_link="${device_name}gripper_base"/>
|
||||
</group>
|
||||
<group name="${device_name}arm_gripper">
|
||||
<joint name="${device_name}gripper_right_joint"/>
|
||||
</group>
|
||||
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
|
||||
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_2" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_3" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_slideway" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_2" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_3" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_slideway" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_base" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_left" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_right" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_link_3" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_slideway" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_base" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_left" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_right" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}arm_slideway" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_base" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_left" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_right" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_base" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_left" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_right" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_left" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_right" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}gripper_left" link2="${device_name}gripper_right" reason="Never"/>
|
||||
</xacro:macro>
|
||||
</robot>
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
# 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
|
||||
@@ -1,2 +0,0 @@
|
||||
planner_configs:
|
||||
- ompl_interface/OMPLPlanner
|
||||
@@ -1,6 +0,0 @@
|
||||
# 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
|
||||
@@ -1,39 +0,0 @@
|
||||
# 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
|
||||
@@ -1,44 +0,0 @@
|
||||
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
|
||||
@@ -1,293 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot xmlns:xacro="http://ros.org/wiki/xacro" name="benyao_arm">
|
||||
|
||||
<xacro:macro name="benyao_arm" params="mesh_path:='' parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0">
|
||||
<!-- Read .yaml files from disk, load content into properties -->
|
||||
<xacro:property name= "joint_limit_parameters" value="${xacro.load_yaml(mesh_path + '/devices/benyao_arm/joint_limit.yaml')}"/>
|
||||
|
||||
<!-- Extract subsections from yaml dictionaries -->
|
||||
<xacro:property name= "sec_limits" value="${joint_limit_parameters['joint_limits']}"/>
|
||||
|
||||
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
|
||||
<origin xyz="${x} ${y} ${z}" rpy="${rx} ${ry} ${r}" />
|
||||
<parent link="${parent_link}"/>
|
||||
<child link="${station_name}${device_name}device_link"/>
|
||||
<axis xyz="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<link name="${station_name}${device_name}device_link"/>
|
||||
<joint name="${station_name}${device_name}device_link_joint" type="fixed">
|
||||
<origin xyz="0 0 0" rpy="0 0 0" />
|
||||
<parent link="${station_name}${device_name}device_link"/>
|
||||
<child link="${station_name}${device_name}arm_slideway"/>
|
||||
<axis xyz="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<!-- JOINTS LIMIT PARAMETERS -->
|
||||
<xacro:property name="limit_arm_base_joint" value="${sec_limits['arm_base_joint']}" />
|
||||
<xacro:property name="limit_arm_link_1_joint" value="${sec_limits['arm_link_1_joint']}" />
|
||||
<xacro:property name="limit_arm_link_2_joint" value="${sec_limits['arm_link_2_joint']}" />
|
||||
<xacro:property name="limit_arm_link_3_joint" value="${sec_limits['arm_link_3_joint']}" />
|
||||
<xacro:property name="limit_gripper_base_joint" value="${sec_limits['gripper_base_joint']}" />
|
||||
<xacro:property name="limit_gripper_right_joint" value="${sec_limits['gripper_right_joint']}"/>
|
||||
<xacro:property name="limit_gripper_left_joint" value="${sec_limits['gripper_left_joint']}" />
|
||||
<link name="${station_name}${device_name}arm_slideway">
|
||||
<inertial>
|
||||
<origin rpy="0 0 0" xyz="-0.913122246354019 -0.00141851388483838 0.0416079172839272"/>
|
||||
<mass value="13.6578107753627"/>
|
||||
<inertia ixx="0.0507627640890578" ixy="0.0245166532634714" ixz="-0.0112656803168519" iyy="5.2550852314372" iyz="0.000302974193920367" izz="5.26892263696439"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_slideway.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="0.752941176470588 0.752941176470588 0.752941176470588 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_slideway.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<joint name="${station_name}${device_name}arm_base_joint" type="prismatic">
|
||||
<origin rpy="0 0 0" xyz="0.307 0 0.1225"/>
|
||||
<parent link="${station_name}${device_name}arm_slideway"/>
|
||||
<child link="${station_name}${device_name}arm_base"/>
|
||||
<axis xyz="1 0 0"/>
|
||||
<limit
|
||||
effort="${limit_arm_base_joint['effort']}"
|
||||
lower="${limit_arm_base_joint['lower']}"
|
||||
upper="${limit_arm_base_joint['upper']}"
|
||||
velocity="${limit_arm_base_joint['velocity']}"/>
|
||||
</joint>
|
||||
|
||||
<link name="${station_name}${device_name}arm_base">
|
||||
<inertial>
|
||||
<origin rpy="0 0 0" xyz="1.48458338655733E-06 -0.00831873687136486 0.351728466012153"/>
|
||||
<mass value="16.1341586205194"/>
|
||||
<inertia ixx="0.54871651759045" ixy="7.65476367433116E-07" ixz="2.0515139488158E-07" iyy="0.55113098995396" iyz="-5.13261457726806E-07" izz="0.0619081867727048"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_base.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="1 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_base.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}arm_link_1">
|
||||
<inertial>
|
||||
<origin rpy="0 0 0" xyz="0 -0.0102223856758559 0.0348505130779933"/>
|
||||
<mass value="0.828629227096429"/>
|
||||
<inertia ixx="0.00119703598787112" ixy="-2.46083048832131E-19" ixz="1.43864352731199E-19" iyy="0.00108355785790042" iyz="1.88092240278693E-06" izz="0.00160914803816438"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_1.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="1 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_1.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
<joint name="${station_name}${device_name}arm_link_1_joint" type="prismatic">
|
||||
<origin rpy="0 0 0" xyz="0 0.1249 0.15"/>
|
||||
<parent link="${station_name}${device_name}arm_base"/>
|
||||
<child link="${station_name}${device_name}arm_link_1"/>
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit
|
||||
effort="${limit_arm_link_1_joint['effort']}"
|
||||
lower="${limit_arm_link_1_joint['lower']}"
|
||||
upper="${limit_arm_link_1_joint['upper']}"
|
||||
velocity="${limit_arm_link_1_joint['velocity']}"/>
|
||||
</joint>
|
||||
<link name="${station_name}${device_name}arm_link_2">
|
||||
<inertial>
|
||||
<origin rpy="0 0 0" xyz="-3.33066907387547E-16 0.100000000000003 -0.0325000000000004"/>
|
||||
<mass value="2.04764861029349"/>
|
||||
<inertia ixx="0.0150150059448827" ixy="-1.28113733272213E-17" ixz="6.7561418872754E-19" iyy="0.00262980501315445" iyz="7.44451536320152E-18" izz="0.0162030186138787"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_2.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="1 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_2.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
<joint name="${station_name}${device_name}arm_link_2_joint" type="revolute">
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}arm_link_1"/>
|
||||
<child link="${station_name}${device_name}arm_link_2"/>
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit
|
||||
effort="${limit_arm_link_2_joint['effort']}"
|
||||
lower="${limit_arm_link_2_joint['lower']}"
|
||||
upper="${limit_arm_link_2_joint['upper']}"
|
||||
velocity="${limit_arm_link_2_joint['velocity']}"/>
|
||||
</joint>
|
||||
<link name="${station_name}${device_name}arm_link_3">
|
||||
<inertial>
|
||||
<origin rpy="0 0 0" xyz="4.77395900588817E-15 0.0861257730831348 -0.0227999999999999"/>
|
||||
<mass value="1.19870202871083"/>
|
||||
<inertia ixx="0.00780783223764428" ixy="7.26567379579506E-18" ixz="1.02766851352053E-18" iyy="0.00109642607170081" iyz="-9.73775385060067E-18" izz="0.0084997384510058"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_3.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="1 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_3.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
<joint name="${station_name}${device_name}arm_link_3_joint" type="revolute">
|
||||
<origin rpy="0 0 0" xyz="0 0.2 -0.0647"/>
|
||||
<parent link="${station_name}${device_name}arm_link_2"/>
|
||||
<child link="${station_name}${device_name}arm_link_3"/>
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit
|
||||
effort="${limit_arm_link_3_joint['effort']}"
|
||||
lower="${limit_arm_link_3_joint['lower']}"
|
||||
upper="${limit_arm_link_3_joint['upper']}"
|
||||
velocity="${limit_arm_link_3_joint['velocity']}"/>
|
||||
</joint>
|
||||
<link name="${station_name}${device_name}gripper_base">
|
||||
<inertial>
|
||||
<origin rpy="0 0 0" xyz="-6.05365748571618E-05 0.0373027483464434 -0.0264392017534612"/>
|
||||
<mass value="0.511925198394943"/>
|
||||
<inertia ixx="0.000640463815051467" ixy="1.08132229596356E-06" ixz="7.165124649009E-07" iyy="0.000552164156414554" iyz="9.80000237347941E-06" izz="0.00103553457812823"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_base.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="1 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_base.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
<joint name="${station_name}${device_name}gripper_base_joint" type="revolute">
|
||||
<origin rpy="0 0 0" xyz="0 0.2 -0.045"/>
|
||||
<parent link="${station_name}${device_name}arm_link_3"/>
|
||||
<child link="${station_name}${device_name}gripper_base"/>
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit
|
||||
effort="${limit_gripper_base_joint['effort']}"
|
||||
lower="${limit_gripper_base_joint['lower']}"
|
||||
upper="${limit_gripper_base_joint['upper']}"
|
||||
velocity="${limit_gripper_base_joint['velocity']}"/>
|
||||
</joint>
|
||||
<link name="${station_name}${device_name}gripper_right">
|
||||
<inertial>
|
||||
<origin rpy="0 0 0" xyz="0.0340005471193899 0.0339655085140826 -0.0325252119823062"/>
|
||||
<mass value="0.013337481136229"/>
|
||||
<inertia ixx="2.02427962974094E-05" ixy="1.78442722292145E-06" ixz="-4.36485961300289E-07" iyy="1.4816483393622E-06" iyz="2.60539468115799E-06" izz="1.96629693098755E-05"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_right.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="1 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_right.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
<joint name="${station_name}${device_name}gripper_right_joint" type="prismatic">
|
||||
<origin rpy="0 0 0" xyz="0 0.0942 -0.022277"/>
|
||||
<parent link="${station_name}${device_name}gripper_base"/>
|
||||
<child link="${station_name}${device_name}gripper_right"/>
|
||||
<axis xyz="1 0 0"/>
|
||||
<limit
|
||||
effort="${limit_gripper_right_joint['effort']}"
|
||||
lower="${limit_gripper_right_joint['lower']}"
|
||||
upper="${limit_gripper_right_joint['upper']}"
|
||||
velocity="${limit_gripper_right_joint['velocity']}"/>
|
||||
</joint>
|
||||
<link name="${station_name}${device_name}gripper_left">
|
||||
<inertial>
|
||||
<origin rpy="0 3.1416 0" xyz="-0.0340005471193521 0.0339655081029604 -0.0325252119827364"/>
|
||||
<mass value="0.0133374811362292"/>
|
||||
<inertia ixx="2.02427962974094E-05" ixy="-1.78442720812615E-06" ixz="4.36485961300305E-07" iyy="1.48164833936224E-06" iyz="2.6053946859901E-06" izz="1.96629693098755E-05"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_left.STL"/>
|
||||
</geometry>
|
||||
<material name="">
|
||||
<color rgba="1 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_left.STL"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
<joint name="${station_name}${device_name}gripper_left_joint" type="prismatic">
|
||||
<origin rpy="0 3.1416 0" xyz="0 0.0942 -0.022277"/>
|
||||
<parent link="${station_name}${device_name}gripper_base"/>
|
||||
<child link="${station_name}${device_name}gripper_left"/>
|
||||
<axis xyz="1 0 0"/>
|
||||
<limit
|
||||
effort="${limit_gripper_left_joint['effort']}"
|
||||
lower="${limit_gripper_left_joint['lower']}"
|
||||
upper="${limit_gripper_left_joint['upper']}"
|
||||
velocity="${limit_gripper_left_joint['velocity']}"/>
|
||||
<mimic joint="${station_name}${device_name}gripper_right_joint" multiplier="1" />
|
||||
</joint>
|
||||
|
||||
</xacro:macro>
|
||||
</robot>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
kinematics:
|
||||
shoulder:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0.1930
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
upperarm:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
forearm:
|
||||
x: -0.6150
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_1:
|
||||
x: -0.5710
|
||||
y: 0
|
||||
z: 0.1775
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_2:
|
||||
x: 0
|
||||
y: -0.1180
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_3:
|
||||
x: 0
|
||||
y: 0.1103
|
||||
z: 0
|
||||
roll: -1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
@@ -0,0 +1,61 @@
|
||||
joint_limits:
|
||||
shoulder_pan_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 330.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
shoulder_lift_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 330.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
elbow_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 150.0
|
||||
max_position: !degrees 180.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -180.0
|
||||
wrist_1_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_2_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_3_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
@@ -0,0 +1,99 @@
|
||||
# Physical parameters
|
||||
|
||||
dh_parameters:
|
||||
d1: 0.1930
|
||||
a2: -0.6150
|
||||
a3: -0.5710
|
||||
d4: 0.1775
|
||||
d5: 0.1180
|
||||
d6: 0.1103
|
||||
|
||||
inertia_parameters:
|
||||
base_mass: 0.94888 # base mass, base inertia, base cog might be incorrect
|
||||
shoulder_mass: 6.83
|
||||
upperarm_mass: 13.037
|
||||
forearm_mass: 4.827
|
||||
wrist_1_mass: 2.315
|
||||
wrist_2_mass: 2.195
|
||||
wrist_3_mass: 0.616
|
||||
|
||||
inertia:
|
||||
base:
|
||||
ixx: 0.0029607
|
||||
ixy: -1.019E-06
|
||||
ixz: 5.2685E-06
|
||||
iyy: 0.0026222
|
||||
iyz: -2.8951E-06
|
||||
izz: 0.0039906
|
||||
shoulder:
|
||||
ixx: 0.039228
|
||||
ixy: -2.8388E-05
|
||||
ixz: 3.9289E-05
|
||||
iyy: 0.025078
|
||||
iyz: -0.00028825
|
||||
izz: 0.037499
|
||||
upperarm:
|
||||
ixx: 0.3734
|
||||
ixy: -5.985E-05
|
||||
ixz: -0.5014
|
||||
iyy: 2.0187
|
||||
iyz: -0.00013287
|
||||
izz: 1.681
|
||||
forearm:
|
||||
ixx: 0.030106
|
||||
ixy: -1.25E-05
|
||||
ixz: -0.076554
|
||||
iyy: 0.76404
|
||||
iyz: 1.326E-06
|
||||
izz: 0.7421
|
||||
wrist_1:
|
||||
ixx: 0.0060891
|
||||
ixy: 1.219E-06
|
||||
ixz: -4.067E-06
|
||||
iyy: 0.0049703
|
||||
iyz: -1.2747E-05
|
||||
izz: 0.0033067
|
||||
wrist_2:
|
||||
ixx: 0.004638045
|
||||
ixy: 1.311E-06
|
||||
ixz: 3.829E-06
|
||||
iyy: 0.003507337
|
||||
iyz: 1.4183E-05
|
||||
izz: 0.003129668
|
||||
wrist_3:
|
||||
ixx: 0.0016942
|
||||
ixy: 1.27E-07
|
||||
ixz: 1.9782E-05
|
||||
iyy: 0.0017123
|
||||
iyz: 7.7E-06
|
||||
izz: 0.0005701
|
||||
|
||||
center_of_mass:
|
||||
base_cog:
|
||||
x: 5.6715E-05
|
||||
y: -0.00010524
|
||||
z: 0.065979
|
||||
shoulder_cog:
|
||||
x: 9.3E-05
|
||||
y: -0.02697
|
||||
z: -0.02115
|
||||
upperarm_cog:
|
||||
x: -0.2304
|
||||
y: -3.9E-05
|
||||
z: 0.16068
|
||||
forearm_cog:
|
||||
x: -0.2998
|
||||
y: 1.3E-05
|
||||
z: 0.06176
|
||||
wrist_1_cog:
|
||||
x: 1.0E-05
|
||||
y: -0.0148
|
||||
z: -0.01682
|
||||
wrist_2_cog:
|
||||
x: -1.3E-05
|
||||
y: 0.015559
|
||||
z: -0.011803
|
||||
wrist_3_cog:
|
||||
x: -0.001704
|
||||
y: -0.000705
|
||||
z: -0.039231
|
||||
@@ -0,0 +1,92 @@
|
||||
mesh_files:
|
||||
base:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/visual/base.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/collision/base.stl
|
||||
|
||||
shoulder:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/visual/shoulder.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/collision/shoulder.stl
|
||||
|
||||
upperarm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/visual/upperarm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/collision/upperarm.stl
|
||||
mesh_files:
|
||||
|
||||
forearm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/visual/forearm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/collision/forearm.stl
|
||||
|
||||
wrist_1:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/visual/wrist1.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/collision/wrist1.stl
|
||||
|
||||
wrist_2:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/visual/wrist2.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/collision/wrist2.stl
|
||||
|
||||
wrist_3:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/visual/wrist3.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs612/collision/wrist3.stl
|
||||
@@ -0,0 +1,43 @@
|
||||
kinematics:
|
||||
shoulder:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0.1930
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
upperarm:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
forearm:
|
||||
x: -0.5520
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_1:
|
||||
x: -0.4300
|
||||
y: 0
|
||||
z: 0.1775
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_2:
|
||||
x: 0
|
||||
y: -0.1180
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_3:
|
||||
x: 0
|
||||
y: 0.1103
|
||||
z: 0
|
||||
roll: -1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
@@ -0,0 +1,61 @@
|
||||
joint_limits:
|
||||
shoulder_pan_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 330.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
shoulder_lift_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 330.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
elbow_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 150.0
|
||||
max_position: !degrees 180.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -180.0
|
||||
wrist_1_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_2_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_3_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
@@ -0,0 +1,99 @@
|
||||
# Physical parameters
|
||||
|
||||
dh_parameters:
|
||||
d1: 0.1930
|
||||
a2: -0.5520
|
||||
a3: -0.4300
|
||||
d4: 0.1775
|
||||
d5: 0.1180
|
||||
d6: 0.1103
|
||||
|
||||
inertia_parameters:
|
||||
base_mass: 0.94888 # base mass, base inertia, base cog might be incorrect
|
||||
shoulder_mass: 6.83
|
||||
upperarm_mass: 13.00
|
||||
forearm_mass: 4.700
|
||||
wrist_1_mass: 2.315
|
||||
wrist_2_mass: 2.195
|
||||
wrist_3_mass: 0.616
|
||||
|
||||
inertia:
|
||||
base:
|
||||
ixx: 0.0029607
|
||||
ixy: -1.019E-06
|
||||
ixz: 5.2685E-06
|
||||
iyy: 0.0026222
|
||||
iyz: -2.8951E-06
|
||||
izz: 0.0039906
|
||||
shoulder:
|
||||
ixx: 0.039229834
|
||||
ixy: -2.8388E-05
|
||||
ixz: 3.9289E-05
|
||||
iyy: 0.025077817
|
||||
iyz: -0.000288247
|
||||
izz: 0.03749924
|
||||
upperarm:
|
||||
ixx: 0.373432462
|
||||
ixy: -5.5512E-05
|
||||
ixz: -0.451785393
|
||||
iyy: 1.696418762
|
||||
iyz: -0.000133438
|
||||
izz: 1.358657204
|
||||
forearm:
|
||||
ixx: 0.030213183
|
||||
ixy: -8.368E-06
|
||||
ixz: -0.056600087
|
||||
iyy: 0.434775735
|
||||
iyz: 1.585E-06
|
||||
izz: 0.412836422
|
||||
wrist_1:
|
||||
ixx: 0.006091086
|
||||
ixy: 1.256E-06
|
||||
ixz: -4.067E-06
|
||||
iyy: 0.004972529
|
||||
iyz: -1.2709E-05
|
||||
izz: 0.003307235
|
||||
wrist_2:
|
||||
ixx: 0.004639166
|
||||
ixy: 1.311E-06
|
||||
ixz: 3.829E-06
|
||||
iyy: 0.003508643
|
||||
iyz: 1.4183E-05
|
||||
izz: 0.003130199
|
||||
wrist_3:
|
||||
ixx: 0.001695059
|
||||
ixy: 1.27E-07
|
||||
ixz: 1.9782E-05
|
||||
iyy: 0.001713327
|
||||
iyz: 7.7E-06
|
||||
izz: 0.000570629
|
||||
|
||||
center_of_mass:
|
||||
base_cog:
|
||||
x: 5.6715E-05
|
||||
y: -0.00010524
|
||||
z: 0.065979
|
||||
shoulder_cog:
|
||||
x: 9.3E-05
|
||||
y: -0.02697
|
||||
z: -0.02115
|
||||
upperarm_cog:
|
||||
x: -0.2069
|
||||
y: -4.4E-05
|
||||
z: 0.16068
|
||||
forearm_cog:
|
||||
x: -0.2303
|
||||
y: 1.5E-05
|
||||
z: 0.06220
|
||||
wrist_1_cog:
|
||||
x: 1.0E-05
|
||||
y: -0.0148
|
||||
z: -0.01682
|
||||
wrist_2_cog:
|
||||
x: -1.3E-05
|
||||
y: 0.015559
|
||||
z: -0.011803
|
||||
wrist_3_cog:
|
||||
x: -0.001704
|
||||
y: -0.000705
|
||||
z: -0.039231
|
||||
@@ -0,0 +1,92 @@
|
||||
mesh_files:
|
||||
base:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/visual/base.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/collision/base.stl
|
||||
|
||||
shoulder:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/visual/shoulder.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/collision/shoulder.stl
|
||||
|
||||
upperarm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/visual/upperarm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/collision/upperarm.stl
|
||||
mesh_files:
|
||||
|
||||
forearm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/visual/forearm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/collision/forearm.stl
|
||||
|
||||
wrist_1:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/visual/wrist1.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/collision/wrist1.stl
|
||||
|
||||
wrist_2:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/visual/wrist2.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/collision/wrist2.stl
|
||||
|
||||
wrist_3:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/visual/wrist3.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs616/collision/wrist3.stl
|
||||
@@ -0,0 +1,43 @@
|
||||
kinematics:
|
||||
shoulder:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0.2350
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
upperarm:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
forearm:
|
||||
x: -0.9000
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_1:
|
||||
x: -0.7720
|
||||
y: 0
|
||||
z: 0.1725
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_2:
|
||||
x: 0
|
||||
y: -0.1280
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_3:
|
||||
x: 0
|
||||
y: 0.1250
|
||||
z: 0
|
||||
roll: -1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
@@ -0,0 +1,61 @@
|
||||
joint_limits:
|
||||
shoulder_pan_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 730.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
shoulder_lift_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 730.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
elbow_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 430.0
|
||||
max_position: !degrees 180.0
|
||||
max_velocity: !degrees 150.0
|
||||
min_position: !degrees -180.0
|
||||
wrist_1_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 100.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 210.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_2_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 100.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 210.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_3_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 100.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 210.0
|
||||
min_position: !degrees -360.0
|
||||
@@ -0,0 +1,99 @@
|
||||
# Physical parameters
|
||||
|
||||
dh_parameters:
|
||||
d1: 0.2350
|
||||
a2: -0.9000
|
||||
a3: -0.7720
|
||||
d4: 0.1725
|
||||
d5: 0.1280
|
||||
d6: 0.1250
|
||||
|
||||
inertia_parameters:
|
||||
base_mass: 1.5056 # base mass, base inertia, base cog might be incorrect
|
||||
shoulder_mass: 17.04
|
||||
upperarm_mass: 26.927
|
||||
forearm_mass: 8.386
|
||||
wrist_1_mass: 3.095
|
||||
wrist_2_mass: 3.095
|
||||
wrist_3_mass: 0.879
|
||||
|
||||
inertia:
|
||||
base:
|
||||
ixx: 0.0067829
|
||||
ixy: 2.6762E-07
|
||||
ixz: -2.569E-06
|
||||
iyy: 0.0068523
|
||||
iyz: 9.4263E-05
|
||||
izz: 0.010044
|
||||
shoulder:
|
||||
ixx: 0.130294671
|
||||
ixy: -8.271E-05
|
||||
ixz: 0.000106701
|
||||
iyy: 0.085215288
|
||||
iyz: -0.000663345
|
||||
izz: 0.113859158
|
||||
upperarm:
|
||||
ixx: 1.204318595
|
||||
ixy: -0.000466936
|
||||
ixz: -1.78878432
|
||||
iyy: 8.073725654
|
||||
iyz: -0.000305158
|
||||
izz: 6.980457042
|
||||
forearm:
|
||||
ixx: 0.070042956
|
||||
ixy: 9.0183E-05
|
||||
ixz: -0.165661846
|
||||
iyy: 2.160436707
|
||||
iyz: 1.3854E-05
|
||||
izz: 2.109199584
|
||||
wrist_1:
|
||||
ixx: 0.007958413
|
||||
ixy: -3.024E-06
|
||||
ixz: -5.734E-06
|
||||
iyy: 0.006686348
|
||||
iyz: -1.766E-05
|
||||
izz: 0.004840671
|
||||
wrist_2:
|
||||
ixx: 0.007958413
|
||||
ixy: -3.024E-06
|
||||
ixz: 5.734E-06
|
||||
iyy: 0.006686348
|
||||
iyz: 1.766E-05
|
||||
izz: 0.004840671
|
||||
wrist_3:
|
||||
ixx: 0.004065851
|
||||
ixy: 1.5185E-05
|
||||
ixz: -1.1453E-05
|
||||
iyy: 0.004060372
|
||||
iyz: 4.2152E-05
|
||||
izz: 0.001170392
|
||||
|
||||
center_of_mass:
|
||||
base_cog:
|
||||
x: -2.4009E-05
|
||||
y: 0.0011775
|
||||
z: 0.076293
|
||||
shoulder_cog:
|
||||
x: 9.9E-05
|
||||
y: -0.026311
|
||||
z: -0.026723
|
||||
upperarm_cog:
|
||||
x: -0.323686
|
||||
y: -3.9E-05
|
||||
z: 0.200968
|
||||
forearm_cog:
|
||||
x: -0.376841
|
||||
y: 1.5E-05
|
||||
z: 0.070311
|
||||
wrist_1_cog:
|
||||
x: -3.7E-05
|
||||
y: -0.01051
|
||||
z: -0.014865
|
||||
wrist_2_cog:
|
||||
x: 3.7E-05
|
||||
y: 0.01051
|
||||
z: -0.014865
|
||||
wrist_3_cog:
|
||||
x: 0.000242
|
||||
y: -0.001192
|
||||
z: -0.050422
|
||||
@@ -0,0 +1,92 @@
|
||||
mesh_files:
|
||||
base:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/visual/base.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/collision/base.stl
|
||||
|
||||
shoulder:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/visual/shoulder.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/collision/shoulder.stl
|
||||
|
||||
upperarm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/visual/upperarm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/collision/upperarm.stl
|
||||
mesh_files:
|
||||
|
||||
forearm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/visual/forearm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/collision/forearm.stl
|
||||
|
||||
wrist_1:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/visual/wrist1.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/collision/wrist1.stl
|
||||
|
||||
wrist_2:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/visual/wrist2.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/collision/wrist2.stl
|
||||
|
||||
wrist_3:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/visual/wrist3.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs620/collision/wrist3.stl
|
||||
@@ -0,0 +1,43 @@
|
||||
kinematics:
|
||||
shoulder:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0.2350
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
upperarm:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
forearm:
|
||||
x: -0.7500
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_1:
|
||||
x: -0.6220
|
||||
y: 0
|
||||
z: 0.1725
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_2:
|
||||
x: 0
|
||||
y: -0.1280
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_3:
|
||||
x: 0
|
||||
y: 0.1250
|
||||
z: 0
|
||||
roll: -1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
@@ -0,0 +1,61 @@
|
||||
joint_limits:
|
||||
shoulder_pan_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 730.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
shoulder_lift_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 730.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 120.0
|
||||
min_position: !degrees -360.0
|
||||
elbow_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 430.0
|
||||
max_position: !degrees 180.0
|
||||
max_velocity: !degrees 150.0
|
||||
min_position: !degrees -180.0
|
||||
wrist_1_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 100.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 210.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_2_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 100.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 210.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_3_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 100.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 210.0
|
||||
min_position: !degrees -360.0
|
||||
@@ -0,0 +1,99 @@
|
||||
# Physical parameters
|
||||
|
||||
dh_parameters:
|
||||
d1: 0.2350
|
||||
a2: -0.7500
|
||||
a3: -0.6220
|
||||
d4: 0.1725
|
||||
d5: 0.1280
|
||||
d6: 0.1250
|
||||
|
||||
inertia_parameters:
|
||||
base_mass: 1.50561968754018 # base mass, base inertia, base cog might be incorrect
|
||||
shoulder_mass: 17.04
|
||||
upperarm_mass: 26.543
|
||||
forearm_mass: 7.816
|
||||
wrist_1_mass: 3.095
|
||||
wrist_2_mass: 3.095
|
||||
wrist_3_mass: 0.879
|
||||
|
||||
inertia:
|
||||
base:
|
||||
ixx: 0.00678289120631133
|
||||
ixy: 2.67618063769984E-07
|
||||
ixz: -2.56895564177072E-06
|
||||
iyy: 0.00685230321535615
|
||||
iyz: 9.42628790478668E-05
|
||||
izz: 0.0100437671447022
|
||||
shoulder:
|
||||
ixx: 0.13029
|
||||
ixy: -8.271E-05
|
||||
ixz: 0.0001067
|
||||
iyy: 0.085215
|
||||
iyz: -0.00066335
|
||||
izz: 0.11386
|
||||
upperarm:
|
||||
ixx: 1.197
|
||||
ixy: -0.00083272
|
||||
ixz: -1.5396
|
||||
iyy: 6.1117
|
||||
iyz: -0.00015496
|
||||
izz: 5.022
|
||||
forearm:
|
||||
ixx: 0.067797
|
||||
ixy: 4.4997E-05
|
||||
ixz: -0.12636
|
||||
iyy: 1.3409
|
||||
iyz: 1.45E-05
|
||||
izz: 1.2908
|
||||
wrist_1:
|
||||
ixx: 0.0079584
|
||||
ixy: -3.024E-06
|
||||
ixz: 5.734E-06
|
||||
iyy: 0.0066863
|
||||
iyz: 1.766E-05
|
||||
izz: 0.0048407
|
||||
wrist_2:
|
||||
ixx: 0.007958413
|
||||
ixy: -3.024E-06
|
||||
ixz: 5.734E-06
|
||||
iyy: 0.006686348
|
||||
iyz: 1.766E-05
|
||||
izz: 0.004840671
|
||||
wrist_3:
|
||||
ixx: 0.004065851
|
||||
ixy: 1.5185E-05
|
||||
ixz: -1.1453E-05
|
||||
iyy: 0.004060372
|
||||
iyz: 4.2152E-05
|
||||
izz: 0.001170392
|
||||
|
||||
center_of_mass:
|
||||
base_cog:
|
||||
x: -2.4008693014781E-05
|
||||
y: 0.00117748953656619
|
||||
z: 0.0762927770474629
|
||||
shoulder_cog:
|
||||
x: 9.9E-05
|
||||
y: -0.026311
|
||||
z: -0.026723
|
||||
upperarm_cog:
|
||||
x: -0.27762
|
||||
y: -1.7E-05
|
||||
z: 0.20184
|
||||
forearm_cog:
|
||||
x: -0.30259
|
||||
y: 2.1E-05
|
||||
z: 0.07237
|
||||
wrist_1_cog:
|
||||
x: -3.7E-05
|
||||
y: -0.01051
|
||||
z: -0.014865
|
||||
wrist_2_cog:
|
||||
x: 3.7E-05
|
||||
y: 0.01051
|
||||
z: -0.014865
|
||||
wrist_3_cog:
|
||||
x: 0.000242
|
||||
y: -0.001192
|
||||
z: -0.050422
|
||||
@@ -0,0 +1,92 @@
|
||||
mesh_files:
|
||||
base:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/visual/base.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/collision/base.stl
|
||||
|
||||
shoulder:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/visual/shoulder.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/collision/shoulder.stl
|
||||
|
||||
upperarm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/visual/upperarm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/collision/upperarm.stl
|
||||
mesh_files:
|
||||
|
||||
forearm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/visual/forearm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/collision/forearm.stl
|
||||
|
||||
wrist_1:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/visual/wrist1.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/collision/wrist1.stl
|
||||
|
||||
wrist_2:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/visual/wrist2.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/collision/wrist2.stl
|
||||
|
||||
wrist_3:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/visual/wrist3.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs625/collision/wrist3.stl
|
||||
@@ -0,0 +1,43 @@
|
||||
kinematics:
|
||||
shoulder:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0.1530
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
upperarm:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
forearm:
|
||||
x: -0.2700
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_1:
|
||||
x: -0.2575
|
||||
y: 0
|
||||
z: 0.1445
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_2:
|
||||
x: 0
|
||||
y: -0.0965
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_3:
|
||||
x: 0
|
||||
y: 0.0920
|
||||
z: 0
|
||||
roll: -1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
@@ -0,0 +1,61 @@
|
||||
joint_limits:
|
||||
shoulder_pan_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
shoulder_lift_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 56.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
elbow_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 28.0
|
||||
max_position: !degrees 180.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -180.0
|
||||
wrist_1_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 12.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 360.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_2_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 12.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 360.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_3_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 12.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 360.0
|
||||
min_position: !degrees -360.0
|
||||
@@ -0,0 +1,99 @@
|
||||
# Physical parameters
|
||||
|
||||
dh_parameters:
|
||||
d1: 0.1530
|
||||
a2: -0.2700
|
||||
a3: -0.2575
|
||||
d4: 0.1445
|
||||
d5: 0.0965
|
||||
d6: 0.0920
|
||||
|
||||
inertia_parameters:
|
||||
base_mass: 0.358193760044688 # base mass, base inertia, base cog might be incorrect
|
||||
shoulder_mass: 3.095
|
||||
upperarm_mass: 5.073
|
||||
forearm_mass: 2.425
|
||||
wrist_1_mass: 1.559
|
||||
wrist_2_mass: 1.447
|
||||
wrist_3_mass: 0.500
|
||||
|
||||
inertia:
|
||||
base:
|
||||
ixx: 0.000549739542527148
|
||||
ixy: 8.03741216179988E-08
|
||||
ixz: -9.14780809458034E-06
|
||||
iyy: 0.000533024165382036
|
||||
iyz: 3.46908903534662E-07
|
||||
izz: 0.000655188839052244
|
||||
shoulder:
|
||||
ixx: 0.007958
|
||||
ixy: 5.734E-06
|
||||
ixz: 3.024E-06
|
||||
iyy: 0.00484
|
||||
iyz: -1.77E-05
|
||||
izz: 0.006686
|
||||
upperarm:
|
||||
ixx: 0.074168
|
||||
ixy: -7.5E-06
|
||||
ixz: -0.065579
|
||||
iyy: 0.21937
|
||||
iyz: -2.19E-05
|
||||
izz: 0.15104
|
||||
forearm:
|
||||
ixx: 0.005082
|
||||
ixy: -2.7E-06
|
||||
ixz: -0.0056236
|
||||
iyy: 0.0924207
|
||||
iyz: -1.726E-06
|
||||
izz: 0.0897559
|
||||
wrist_1:
|
||||
ixx: 0.0045009
|
||||
ixy: 1.96E-06
|
||||
ixz: -2.646E-06
|
||||
iyy: 0.0042164
|
||||
iyz: -3.58E-07
|
||||
izz: 0.0014538
|
||||
wrist_2:
|
||||
ixx: 0.0023787
|
||||
ixy: 1.953E-06
|
||||
ixz: 2.643E-06
|
||||
iyy: 0.0020859
|
||||
iyz: 5.584E-06
|
||||
izz: 0.0013191
|
||||
wrist_3:
|
||||
ixx: 0.001431
|
||||
ixy: -2.32E-07
|
||||
ixz: 7.955E-06
|
||||
iyy: 0.0014299
|
||||
iyz: 1.266E-06
|
||||
izz: 0.0003265
|
||||
|
||||
center_of_mass:
|
||||
base_cog:
|
||||
x: -0.000721181367009438
|
||||
y: 1.78114684963572E-05
|
||||
z: 0.0546249293265112
|
||||
shoulder_cog:
|
||||
x: 3.7E-05
|
||||
y: -0.0149
|
||||
z: -0.0105
|
||||
upperarm_cog:
|
||||
x: -0.11596
|
||||
y: -2.6E-05
|
||||
z: 0.1148
|
||||
forearm_cog:
|
||||
x: -0.1542
|
||||
y: 1.4E-05
|
||||
z: 0.0238
|
||||
wrist_1_cog:
|
||||
x: 1.3E-05
|
||||
y: -0.00479
|
||||
z: -0.02274
|
||||
wrist_2_cog:
|
||||
x: -1.4E-05
|
||||
y: 0.0051
|
||||
z: -0.0127
|
||||
wrist_3_cog:
|
||||
x: -0.000684
|
||||
y: -0.000175
|
||||
z: -0.0423
|
||||
@@ -0,0 +1,92 @@
|
||||
mesh_files:
|
||||
base:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/visual/base.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/collision/base.stl
|
||||
|
||||
shoulder:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/visual/shoulder.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/collision/shoulder.stl
|
||||
|
||||
upperarm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/visual/upperarm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/collision/upperarm.stl
|
||||
mesh_files:
|
||||
|
||||
forearm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/visual/forearm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/collision/forearm.stl
|
||||
|
||||
wrist_1:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/visual/wrist1.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/collision/wrist1.stl
|
||||
|
||||
wrist_2:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/visual/wrist2.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/collision/wrist2.stl
|
||||
|
||||
wrist_3:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/visual/wrist3.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs63/collision/wrist3.stl
|
||||
@@ -0,0 +1,43 @@
|
||||
kinematics:
|
||||
shoulder:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0.1625
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: -0.16
|
||||
upperarm:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
forearm:
|
||||
x: -0.4270
|
||||
y: 0
|
||||
z: 0
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_1:
|
||||
x: -0.3905
|
||||
y: 0
|
||||
z: 0.1475
|
||||
roll: 0
|
||||
pitch: 0
|
||||
yaw: 0.04
|
||||
wrist_2:
|
||||
x: 0
|
||||
y: -0.0965
|
||||
z: 0
|
||||
roll: 1.570796326589793
|
||||
pitch: 0
|
||||
yaw: 0
|
||||
wrist_3:
|
||||
x: 0
|
||||
y: 0.0920
|
||||
z: 0
|
||||
roll: -1.570796326589793
|
||||
pitch: -0.59
|
||||
yaw: 0
|
||||
@@ -0,0 +1,61 @@
|
||||
joint_limits:
|
||||
shoulder_pan_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 150.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
shoulder_lift_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 150.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
elbow_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 150.0
|
||||
max_position: !degrees 180.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -180.0
|
||||
wrist_1_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 28.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_2_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 28.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
wrist_3_joint:
|
||||
# acceleration limits are not publicly available
|
||||
has_acceleration_limits: false
|
||||
has_effort_limits: true
|
||||
has_position_limits: true
|
||||
has_velocity_limits: true
|
||||
max_effort: 28.0
|
||||
max_position: !degrees 360.0
|
||||
max_velocity: !degrees 180.0
|
||||
min_position: !degrees -360.0
|
||||
@@ -0,0 +1,99 @@
|
||||
# Physical parameters
|
||||
|
||||
dh_parameters:
|
||||
d1: 0.1625
|
||||
a2: -0.4270
|
||||
a3: -0.3905
|
||||
d4: 0.1475
|
||||
d5: 0.0965
|
||||
d6: 0.0920
|
||||
|
||||
inertia_parameters:
|
||||
base_mass: 0.57127 # base mass, base inertia, base cog might be incorrect
|
||||
shoulder_mass: 4.521
|
||||
upperarm_mass: 7.533
|
||||
forearm_mass: 3.009
|
||||
wrist_1_mass: 1.559
|
||||
wrist_2_mass: 1.447
|
||||
wrist_3_mass: 0.500
|
||||
|
||||
inertia:
|
||||
base:
|
||||
ixx: 0.0010759
|
||||
ixy: 7.8337E-08
|
||||
ixz: -1.3213E-05
|
||||
iyy: 0.0010109
|
||||
iyz: 4.5992E-07
|
||||
izz: 0.001408
|
||||
shoulder:
|
||||
ixx: 0.014192
|
||||
ixy: 1.7839E-05
|
||||
ixz: -1.027E-06
|
||||
iyy: 0.0091982
|
||||
iyz: -4.6778E-05
|
||||
izz: 0.012777
|
||||
upperarm:
|
||||
ixx: 0.13719
|
||||
ixy: -7.5676E-05
|
||||
ixz: -0.17551
|
||||
iyy: 0.6614
|
||||
iyz: 7.59E-06
|
||||
izz: 0.53649
|
||||
forearm:
|
||||
ixx: 0.0087604
|
||||
ixy: 1.7762E-05
|
||||
ixz: -0.010765
|
||||
iyy: 0.22281
|
||||
iyz: -4.13E-07
|
||||
izz: 0.2176
|
||||
wrist_1:
|
||||
ixx: 0.0045009
|
||||
ixy: 1.961E-06
|
||||
ixz: -2.646E-06
|
||||
iyy: 0.0042164
|
||||
iyz: -3.58E-07
|
||||
izz: 0.0014539
|
||||
wrist_2:
|
||||
ixx: 0.0023787
|
||||
ixy: 1.953E-06
|
||||
ixz: 2.643E-06
|
||||
iyy: 0.0020859
|
||||
iyz: 5.584E-06
|
||||
izz: 0.0013191
|
||||
wrist_3:
|
||||
ixx: 0.0014314
|
||||
ixy: -2.32E-07
|
||||
ixz: 7.955E-06
|
||||
iyy: 0.0014299
|
||||
iyz: 1.266E-06
|
||||
izz: 0.0003265
|
||||
|
||||
center_of_mass:
|
||||
base_cog:
|
||||
x: -0.00060346
|
||||
y: 1.4311E-05
|
||||
z: 0.060221
|
||||
shoulder_cog:
|
||||
x: 2.2E-05
|
||||
y: -0.016526
|
||||
z: -0.014442
|
||||
upperarm_cog:
|
||||
x: -0.17828
|
||||
y: 4.0E-06
|
||||
z: 0.12803
|
||||
forearm_cog:
|
||||
x: -0.20416
|
||||
y: -7.0E-06
|
||||
z: 0.03064
|
||||
wrist_1_cog:
|
||||
x: 1.3E-05
|
||||
y: -0.00479
|
||||
z: -0.02274
|
||||
wrist_2_cog:
|
||||
x: -1.4E-05
|
||||
y: 0.005107
|
||||
z: -0.012702
|
||||
wrist_3_cog:
|
||||
x: -0.000684
|
||||
y: -0.000175
|
||||
z: -0.04234
|
||||
@@ -0,0 +1,92 @@
|
||||
mesh_files:
|
||||
base:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/visual/base.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/collision/base.stl
|
||||
|
||||
shoulder:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/visual/shoulder.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/collision/shoulder.stl
|
||||
|
||||
upperarm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/visual/upperarm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/collision/upperarm.stl
|
||||
mesh_files:
|
||||
|
||||
forearm:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/visual/forearm.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/collision/forearm.stl
|
||||
|
||||
wrist_1:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/visual/wrist1.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/collision/wrist1.stl
|
||||
|
||||
wrist_2:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/visual/wrist2.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/collision/wrist2.stl
|
||||
|
||||
wrist_3:
|
||||
visual:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/visual/wrist3.dae
|
||||
material:
|
||||
name: "LightGrey"
|
||||
color: "0.7 0.7 0.7 1.0"
|
||||
collision:
|
||||
mesh:
|
||||
package: eli_cs_robot_description
|
||||
path: meshes/cs66/collision/wrist3.stl
|
||||
@@ -0,0 +1,6 @@
|
||||
shoulder_pan_joint: 0.0
|
||||
shoulder_lift_joint: -1.57
|
||||
elbow_joint: 0.0
|
||||
wrist_1_joint: -1.57
|
||||
wrist_2_joint: 1.57
|
||||
wrist_3_joint: 0.0
|
||||
159
unilabos/device_mesh/devices/elite_robot/macro_device.xacro
Normal file
159
unilabos/device_mesh/devices/elite_robot/macro_device.xacro
Normal file
@@ -0,0 +1,159 @@
|
||||
<?xml version="1.0"?>
|
||||
<robot xmlns:xacro="http://wiki.ros.org/xacro" name="elite_robot">
|
||||
<xacro:macro name="elite_robot" params="mesh_path:='' parent_link:='' station_name:='' device_name:='' cs_type:='cs66' fake_dev:='true' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0" >
|
||||
<!-- robot name parameter -->
|
||||
<xacro:arg name="name" default="cs"/>
|
||||
<!-- import main macro -->
|
||||
<xacro:include filename="${mesh_path}/devices/elite_robot/urdf/cs_macro.xacro"/>
|
||||
|
||||
<!-- possible 'cs_type' values: cs63, cs66, cs612, cs616, cs620, cs625 -->
|
||||
<!-- the default value should raise an error in case this was called without defining the type -->
|
||||
<xacro:arg name="cs_type" default="${cs_type}"/>
|
||||
|
||||
<!-- parameters -->
|
||||
<xacro:arg name="tf_prefix" default="${station_name}${device_name}" />
|
||||
<xacro:arg name="joint_limit_params" default="${mesh_path}/devices/elite_robot/config/$(arg cs_type)/joint_limits.yaml"/>
|
||||
<xacro:arg name="kinematics_params" default="${mesh_path}/devices/elite_robot/config/$(arg cs_type)/default_kinematics.yaml"/>
|
||||
<xacro:arg name="physical_params" default="${mesh_path}/devices/elite_robot/config/$(arg cs_type)/physical_parameters.yaml"/>
|
||||
<xacro:arg name="visual_params" default="${mesh_path}/devices/elite_robot/config/$(arg cs_type)/visual_parameters.yaml"/>
|
||||
<xacro:arg name="transmission_hw_interface" default=""/>
|
||||
<xacro:arg name="safety_limits" default="false"/>
|
||||
<xacro:arg name="safety_pos_margin" default="0.15"/>
|
||||
<xacro:arg name="safety_k_position" default="20"/>
|
||||
<!-- ros2_control related parameters -->
|
||||
<xacro:arg name="headless_mode" default="false" />
|
||||
<xacro:arg name="robot_ip" default="0.0.0.0" />
|
||||
<xacro:arg name="script_filename" default=""/>
|
||||
<xacro:arg name="output_recipe_filename" default=""/>
|
||||
<xacro:arg name="input_recipe_filename" default=""/>
|
||||
<xacro:arg name="local_ip" default="0.0.0.0"/>
|
||||
<xacro:arg name="script_command_port" default="50004"/>
|
||||
<xacro:arg name="reverse_port" default="50001"/>
|
||||
<xacro:arg name="script_sender_port" default="50002"/>
|
||||
<xacro:arg name="trajectory_port" default="50003"/>
|
||||
<!-- tool communication related parameters-->
|
||||
<xacro:arg name="use_tool_communication" default="false" />
|
||||
<xacro:arg name="tool_voltage" default="0" />
|
||||
<xacro:arg name="tool_parity" default="0" />
|
||||
<xacro:arg name="tool_baud_rate" default="115200" />
|
||||
<xacro:arg name="tool_stop_bits" default="1" />
|
||||
<xacro:arg name="tool_tcp_port" default="54321" />
|
||||
|
||||
<!-- Simulation parameters -->
|
||||
<xacro:arg name="use_fake_hardware" default="${fake_dev}" />
|
||||
<xacro:arg name="fake_sensor_commands" default="${fake_dev}" />
|
||||
<xacro:arg name="sim_gazebo" default="false" />
|
||||
<xacro:arg name="sim_ignition" default="false" />
|
||||
<xacro:arg name="simulation_controllers" default="" />
|
||||
|
||||
<!-- initial position for simulations (Fake Hardware, Gazebo, Ignition) -->
|
||||
<xacro:arg name="initial_positions_file" default="${mesh_path}/devices/elite_robot/config/initial_positions.yaml"/>
|
||||
|
||||
<!-- convert to property to use substitution in function -->
|
||||
<xacro:property name="initial_positions_file" default="$(arg initial_positions_file)"/>
|
||||
|
||||
|
||||
|
||||
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
|
||||
<origin xyz="${x} ${y} ${z}" rpy="${rx} ${ry} ${r}" />
|
||||
<parent link="${parent_link}"/>
|
||||
<child link="${station_name}${device_name}device_link"/>
|
||||
<axis xyz="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<link name="${station_name}${device_name}device_link"/>
|
||||
|
||||
<!-- arm -->
|
||||
<xacro:cs_robot
|
||||
name="$(arg name)"
|
||||
tf_prefix="$(arg tf_prefix)"
|
||||
parent="${station_name}${device_name}device_link"
|
||||
joint_limits_parameters_file="$(arg joint_limit_params)"
|
||||
kinematics_parameters_file="$(arg kinematics_params)"
|
||||
physical_parameters_file="$(arg physical_params)"
|
||||
visual_parameters_file="$(arg visual_params)"
|
||||
transmission_hw_interface="$(arg transmission_hw_interface)"
|
||||
safety_limits="$(arg safety_limits)"
|
||||
safety_pos_margin="$(arg safety_pos_margin)"
|
||||
safety_k_position="$(arg safety_k_position)"
|
||||
use_fake_hardware="$(arg use_fake_hardware)"
|
||||
fake_sensor_commands="$(arg fake_sensor_commands)"
|
||||
sim_gazebo="$(arg sim_gazebo)"
|
||||
sim_ignition="$(arg sim_ignition)"
|
||||
headless_mode="$(arg headless_mode)"
|
||||
initial_positions="${xacro.load_yaml(initial_positions_file)}"
|
||||
use_tool_communication="$(arg use_tool_communication)"
|
||||
tool_voltage="$(arg tool_voltage)"
|
||||
tool_parity="$(arg tool_parity)"
|
||||
tool_baud_rate="$(arg tool_baud_rate)"
|
||||
tool_stop_bits="$(arg tool_stop_bits)"
|
||||
tool_tcp_port="$(arg tool_tcp_port)"
|
||||
robot_ip="$(arg robot_ip)"
|
||||
script_filename="$(arg script_filename)"
|
||||
output_recipe_filename="$(arg output_recipe_filename)"
|
||||
input_recipe_filename="$(arg input_recipe_filename)"
|
||||
local_ip="$(arg local_ip)"
|
||||
script_command_port="$(arg script_command_port)"
|
||||
reverse_port="$(arg reverse_port)"
|
||||
script_sender_port="$(arg script_sender_port)"
|
||||
trajectory_port="$(arg trajectory_port)"
|
||||
mesh_path="${mesh_path}"
|
||||
>
|
||||
<origin xyz="0 0 0" rpy="0 0 0" /> <!-- position robot in the world -->
|
||||
</xacro:cs_robot>
|
||||
|
||||
<link name="${station_name}${device_name}gripper">
|
||||
<inertial>
|
||||
<origin
|
||||
xyz="-2.76718112539598E-05 0.0255270141715087 0.0270559267296449"
|
||||
rpy="0 0 0" />
|
||||
<mass
|
||||
value="0.2571510360664" />
|
||||
<inertia
|
||||
ixx="0.000164806957167171"
|
||||
ixy="1.52519074989277E-08"
|
||||
ixz="9.6332716899636E-08"
|
||||
iyy="8.72759579414916E-05"
|
||||
iyz="-5.40735277087457E-07"
|
||||
izz="0.000196871100878503" />
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin
|
||||
xyz="0 0 0"
|
||||
rpy="0 0 0" />
|
||||
<geometry>
|
||||
<mesh
|
||||
filename="file://${mesh_path}/devices/elite_robot/meshes/gripper.STL" />
|
||||
</geometry>
|
||||
<material
|
||||
name="">
|
||||
<color
|
||||
rgba="0.749019607843137 0.749019607843137 0.749019607843137 1" />
|
||||
</material>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin
|
||||
xyz="0 0 0"
|
||||
rpy="0 0 0" />
|
||||
<geometry>
|
||||
<mesh
|
||||
filename="file://${mesh_path}/devices/elite_robot/meshes/gripper.STL" />
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
<joint name="${station_name}${device_name}gripper_joint"
|
||||
type="fixed">
|
||||
<origin
|
||||
xyz="0 0 -0.003"
|
||||
rpy="0 0 -1.570796326589793" />
|
||||
<parent
|
||||
link="${station_name}${device_name}tool0" />
|
||||
<child
|
||||
link="${station_name}${device_name}gripper" />
|
||||
<axis
|
||||
xyz="0 0 0" />
|
||||
</joint>
|
||||
|
||||
</xacro:macro>
|
||||
|
||||
</robot>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user