Расширен функционал, начата разработка функционала по оптимизации

This commit is contained in:
2026-05-06 00:30:44 +05:00
parent e8282a72a1
commit 361b934e8a
5 changed files with 522 additions and 192 deletions
+53 -179
View File
@@ -1,194 +1,55 @@
# Калькулятор питательных сред markdown
# 🧬 Цифровой помощник биохимика
Программа для расчёта состава питательных сред с поддержкой разбавленных реагентов. **Биотехнологические инструменты для лаборатории**
## 📋 Функционал ## 📋 О проекте
- **Расчёт состава среды** по процентному содержанию компонентов Цифровой помощник биохимика — это программный комплекс для автоматизации лабораторных расчётов и планирования экспериментов в области биотехнологии и биохимии.
- **Поддержка разных единиц измерения** (нг, мкг, мг, г, кг, нл, мкл, мл, л)
- **Коэффициент пересчёта** для каждого реагента (например, для перевода объёма в массу)
- **Разбавление реагентов** — возможность указать, во сколько раз разбавлен исходный реактив
- **Автоматический расчёт растворителя** с учётом объёма вносимых разбавленных реагентов
- **Сохранение и загрузка** состава в JSON-файл
- **Интерактивная таблица** с возможностью добавления/удаления реагентов
## 🚀 Установка и запуск ## 🚀 Возможности
### Требования ### 1. 🥼 Калькулятор питательных сред
- Python 3.6 или выше - Расчёт состава питательных сред по процентному содержанию компонентов
- PyQt5 - Поддержка различных единиц измерения (нг, мкг, мг, г, кг, нл, мкл, мл, л)
- Учёт разбавления исходных реактивов
- Автоматический расчёт количества растворителя
- Сохранение и загрузка рецептов в JSON
### Установка зависимостей ### 2. 📊 Планирование эксперимента (DoE)
```bash - Полнофакторный дизайн эксперимента (2^k факторный план)
pip install PyQt5 - Интерактивное создание матрицы планирования
``` - Регрессионный анализ (в разработке)
- Визуализация результатов (в разработке)
### Запуск программы ## 🛠 Технологии
```bash
python main.py
```
## 📖 Использование - Python 3.6+
- PyQt5 — графический интерфейс
### Основные параметры - JSON — хранение данных
1. **Общее количество** — конечный объём/масса готовой среды
2. **Единица измерения** — нл, мкл, мл, л
3. **Растворитель** — название растворителя (отображается в первой строке таблицы)
### Таблица реагентов
| Колонка | Описание |
|---------|----------|
| Название | Имя реагента |
| % | Процентное содержание в конечной среде |
| Единица | В каких единицах измеряется реагент |
| Коэфф. | Коэффициент пересчёта (например, для перевода объёма в массу) |
| Разбавление (x) | Во сколько раз разбавлен исходный реактив (1 = не разбавлен) |
| Нужно взять (разб.) | Рассчитанное количество разбавленного реагента |
### Пример расчёта
**Задача:** Приготовить 100 мл питательной среды с 10% соли.
**Вариант 1 — соль не разбавлена:**
- Разбавление = 1
- Нужно взять: 10 г соли
- Растворителя: 100 мл
**Вариант 2 — есть 10-кратно разбавленная соль:**
- Разбавление = 10
- Нужно взять: 1 мл разбавленной соли (10 г / 10)
- Растворителя: 99 мл (100 мл - 1 мл)
## 🧠 Особенности реализации
### Расчёт растворителя
**Важное примечание:** При расчёте объёма растворителя учитываются только те реагенты, которые заданы в **объёмных единицах** (нл, мкл, мл, л). Реагенты в **единицах массы** (нг, мкг, мг, г, кг) считаются не имеющими объёма и не влияют на расчёт растворителя.
Это сделано потому, что:
1. Массовые реагенты обычно добавляются в твёрдом виде и их объём пренебрежимо мал
2. Для корректного учёта объёма твёрдых веществ потребовалась бы плотность
```python
# Пример из model.py
if is_volume:
# Реагент в объёмных единицах — учитываем его объём
reagent_volume_base = diluted_amount * VOLUME_UNITS[reagent.unit]
else:
# Реагент в массовых единицах — не учитываем (объём = 0)
reagent_volume_base = 0
```
### Алгоритм расчёта
1. **Расчёт неразбавленного состава**
- Количество чистого вещества = (процент / 100) × общий объём
2. **Пересчёт с учётом разбавления**
- Количество разбавленного реагента = чистое вещество / фактор разбавления
3. **Расчёт объёма вносимых реагентов**
- Для объёмных реагентов: объём = количество разбавленного реагента
- Для массовых реагентов: объём = 0
4. **Расчёт растворителя**
- Объём растворителя = общий объём − сумма объёмов всех реагентов
## 📁 Структура проекта ## 📁 Структура проекта
digital_biochemist_assistant/
├── main.py # Точка входа
├── main_window.py # Главное окно с выбором инструментов
├── controller.py # Контроллер калькулятора сред
├── model.py # Модель расчётов
├── view.py # Интерфейс калькулятора
├── reagent.py # Класс реагента
├── experiment_design.py # Инструмент планирования эксперимента
└── README.md # Документация
``` text
nutrient_medium_pyqt/
├── main.py # Точка входа, инициализация приложения
├── controller.py # Контроллер — связь между моделью и представлением
├── model.py # Модель — бизнес-логика и расчёты
├── view.py # Представление — GUI на PyQt5
├── reagent.py # Класс Reagent для хранения данных
└── README.md # Документация
```
## 🔧 Описание кода ## 🎯 Планы развития
### main.py - [ ] Расширенный статистический анализ
Инициализирует QApplication, создаёт экземпляр Controller и запускает главный цикл обработки событий. - [ ] Визуализация поверхностей отклика
- [ ] Экспорт в Excel и PDF
### reagent.py - [ ] База данных реагентов и рецептов
```python - [ ] Графический редактор плана эксперимента
class Reagent: - [ ] Модуль концентраций и разбавлений
def __init__(self, name: str, percentage: float, unit: str, conversion_factor: float = 1.0) - [ ] Калькулятор растворов
```
Хранит данные о реагенте:
- `name` — название
- `percentage` — процентное содержание
- `unit` — единица измерения
- `conversion_factor` — коэффициент пересчёта
- `dilution_factor` — коэффициент разбавления (добавляется динамически)
### model.py
Содержит бизнес-логику программы:
**Константы:**
```python
VOLUME_UNITS = {'нл': 0.001, 'мкл': 1.0, 'мл': 1000.0, 'л': 1000000.0}
MASS_UNITS = {'нг': 0.000001, 'мкг': 0.001, 'мг': 1.0, 'г': 1000.0, 'кг': 1000000.0}
```
**Основные методы:**
- `convert_amount()` — пересчёт между единицами измерения через базовые (мкл для объёма, мкг для массы)
- `calculate_amounts()` — главный расчётный метод
- `save_to_file()` / `load_from_file()` — сериализация в JSON
### view.py
Реализует графический интерфейс на PyQt5:
**Основные компоненты:**
- `QDoubleSpinBox` для общего количества
- `QComboBox` для выбора единиц измерения
- `QTableWidget` для таблицы реагентов (6 колонок)
- Кнопки: Добавить, Удалить, Рассчитать, Сохранить, Загрузить
**Ключевые методы:**
- `add_solvent_row()` — создаёт нередактируемую строку растворителя
- `add_new_row()` — добавляет строку реагента
- `update_results()` — обновляет результаты расчёта
- `update_solvent_result()` — обновляет количество растворителя
### controller.py
Координирует взаимодействие Model и View:
**Сигналы (слоты):**
- `add_row_btn.clicked``add_reagent_row()`
- `calculate_btn.clicked``_perform_calculation()`
- `save_btn.clicked``save_composition()`
- `load_btn.clicked``load_composition()`
**Основные методы:**
- `_update_model_from_view()` — переносит данные из таблицы в модель
- `_update_view_from_model()` — заполняет таблицу из модели
- `_perform_calculation()` — вызывает расчёт и обновляет интерфейс
## 📝 Формат JSON-файла
```json
{
"total_amount": 1000.0,
"amount_unit": "мл",
"solvent": "Вода",
"reagents": [
{
"name": "NaCl",
"percentage": 0.9,
"unit": "г",
"conversion_factor": 1.0,
"dilution_factor": 1.0
}
]
}
```
## 🤝 Вклад в проект
При обнаружении ошибок или наличии предложений создавайте Issue или Pull Request.
## 📄 Лицензия ## 📄 Лицензия
@@ -196,4 +57,17 @@ MIT License
## 👨‍💻 Автор ## 👨‍💻 Автор
Kolobov Artem Kolobov artem
Основные изменения:
1. Новое название - "Цифровой помощник биохимика" (Digital Biotechnologist Assistant)
2. Современный дизайн - градиентный фон, иконки, улучшенная стилизация
3. Расширенный DoE модуль - уже умеет генерировать матрицу планирования для 2^k факторного плана
4. Улучшенная навигация - вкладки, понятные описания
5. Профессиональный вид - современный интерфейс, подходящий для лаборатории
+5 -4
View File
@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QMessageBox, QFileDialog, QTableWidgetItem, QComboBox, QDoubleSpinBox, QLineEdit from PyQt5.QtWidgets import QMessageBox, QFileDialog, QTableWidgetItem, QComboBox, QLineEdit
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from model import Model from model import Model
from view import MainWindow from view import MainWindow as MediumCalculatorView
import json import json
from reagent import Reagent from reagent import Reagent
@@ -9,8 +9,10 @@ from reagent import Reagent
class Controller: class Controller:
def __init__(self): def __init__(self):
self.model = Model() self.model = Model()
self.view = MainWindow() self.view = MediumCalculatorView()
self._connect_signals() self._connect_signals()
# Убираем автоматический показ окна - теперь он вызывается из главного меню
# self.view.show()
def _connect_signals(self): def _connect_signals(self):
"""Подключает обработчики событий интерфейса""" """Подключает обработчики событий интерфейса"""
@@ -19,7 +21,6 @@ class Controller:
self.view.calculate_btn.clicked.connect(self._perform_calculation) self.view.calculate_btn.clicked.connect(self._perform_calculation)
self.view.save_btn.clicked.connect(self.save_composition) self.view.save_btn.clicked.connect(self.save_composition)
self.view.load_btn.clicked.connect(self.load_composition) self.view.load_btn.clicked.connect(self.load_composition)
# Подключаем обновление названия растворителя в таблице при изменении поля
self.view.solvent_input.textChanged.connect(self.view.update_solvent_name) self.view.solvent_input.textChanged.connect(self.view.update_solvent_name)
def add_reagent_row(self): def add_reagent_row(self):
+312
View File
@@ -0,0 +1,312 @@
from PyQt5.QtWidgets import (QMainWindow, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QWidget, QMessageBox,
QTableWidget, QTableWidgetItem, QGroupBox,
QSpinBox, QDoubleSpinBox, QComboBox, QLineEdit,
QTextEdit, QTabWidget, QFormLayout)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
class ExperimentDesignWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Планирование эксперимента - Цифровой помощник биохимика")
self.setGeometry(200, 100, 1000, 750)
self.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QGroupBox {
font-weight: bold;
border: 2px solid #ccc;
border-radius: 5px;
margin-top: 10px;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
QPushButton {
background-color: #4CAF50;
color: white;
border: none;
padding: 8px;
border-radius: 4px;
font-weight: bold;
}
QPushButton:hover {
background-color: #45a049;
}
QTableWidget {
gridline-color: #ddd;
}
""")
self._init_ui()
def _init_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Заголовок
title_label = QLabel("📈 Планирование полнофакторного эксперимента (DoE)")
title_font = QFont()
title_font.setPointSize(18)
title_font.setBold(True)
title_label.setFont(title_font)
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("color: #2E7D32;")
layout.addWidget(title_label)
# Вкладки
tabs = QTabWidget()
# Вкладка 1: Параметры эксперимента
params_tab = QWidget()
params_layout = QVBoxLayout(params_tab)
# Группа факторов
factors_group = QGroupBox("Факторы эксперимента (независимые переменные)")
factors_layout = QVBoxLayout()
# Информация
info_label = QLabel("Определите факторы, которые влияют на ваш эксперимент:")
info_label.setStyleSheet("color: #555; font-weight: normal;")
factors_layout.addWidget(info_label)
self.factors_table = QTableWidget()
self.factors_table.setColumnCount(4)
self.factors_table.setHorizontalHeaderLabels(["Фактор", "Нижний уровень (-1)", "Верхний уровень (+1)", "Единица измерения"])
self.factors_table.setRowCount(3)
# Пример данных
sample_factors = [
["Температура", "25", "37", "°C"],
["pH", "6.5", "7.5", ""],
["Концентрация глюкозы", "5", "20", "г"]
]
for i, factor in enumerate(sample_factors):
for j, value in enumerate(factor):
self.factors_table.setItem(i, j, QTableWidgetItem(value))
factors_layout.addWidget(self.factors_table)
# Кнопки для управления факторами
factor_buttons = QHBoxLayout()
add_factor_btn = QPushButton("+ Добавить фактор")
add_factor_btn.clicked.connect(self.add_factor_row)
remove_factor_btn = QPushButton("- Удалить последний")
remove_factor_btn.clicked.connect(self.remove_factor_row)
factor_buttons.addWidget(add_factor_btn)
factor_buttons.addWidget(remove_factor_btn)
factor_buttons.addStretch()
factors_layout.addLayout(factor_buttons)
factors_group.setLayout(factors_layout)
params_layout.addWidget(factors_group)
# Группа откликов
responses_group = QGroupBox("Отклики (зависимые переменные)")
responses_layout = QVBoxLayout()
self.responses_table = QTableWidget()
self.responses_table.setColumnCount(2)
self.responses_table.setHorizontalHeaderLabels(["Отклик", "Единица измерения"])
self.responses_table.setRowCount(2)
sample_responses = [
["Оптическая плотность (OD600)", ""],
["Концентрация целевого продукта", "мг/мл"]
]
for i, response in enumerate(sample_responses):
for j, value in enumerate(response):
self.responses_table.setItem(i, j, QTableWidgetItem(value))
responses_layout.addWidget(self.responses_table)
# Кнопки для управления откликами
response_buttons = QHBoxLayout()
add_response_btn = QPushButton("+ Добавить отклик")
add_response_btn.clicked.connect(self.add_response_row)
remove_response_btn = QPushButton("- Удалить последний")
remove_response_btn.clicked.connect(self.remove_response_row)
response_buttons.addWidget(add_response_btn)
response_buttons.addWidget(remove_response_btn)
response_buttons.addStretch()
responses_layout.addLayout(response_buttons)
responses_group.setLayout(responses_layout)
params_layout.addWidget(responses_group)
tabs.addTab(params_tab, "📝 Параметры эксперимента")
# Вкладка 2: Матрица планирования
plan_tab = QWidget()
plan_layout = QVBoxLayout(plan_tab)
plan_info = QLabel("Полнофакторный план эксперимента (Full Factorial Design)")
plan_info.setAlignment(Qt.AlignCenter)
plan_info.setStyleSheet("font-weight: bold; font-size: 14px; padding: 10px;")
plan_layout.addWidget(plan_info)
self.design_matrix = QTableWidget()
plan_layout.addWidget(self.design_matrix)
generate_btn = QPushButton("🔄 Сгенерировать план эксперимента")
generate_btn.clicked.connect(self.generate_design_matrix)
plan_layout.addWidget(generate_btn)
tabs.addTab(plan_tab, "📊 Матрица планирования")
# Вкладка 3: Анализ результатов
analysis_tab = QWidget()
analysis_layout = QVBoxLayout(analysis_tab)
analysis_info = QLabel("Регрессионный анализ и визуализация результатов")
analysis_info.setAlignment(Qt.AlignCenter)
analysis_info.setStyleSheet("font-weight: bold; font-size: 14px; padding: 10px;")
analysis_layout.addWidget(analysis_info)
# Заглушка для анализа
placeholder = QLabel("Здесь будет:\n\n"
"• Множественная линейная регрессия\n"
"• Анализ взаимодействий факторов\n"
"• ANOVA (дисперсионный анализ)\n"
"• Построение поверхностей отклика\n"
"• Графики главных эффектов\n"
"• Оптимизация параметров")
placeholder.setAlignment(Qt.AlignCenter)
placeholder.setStyleSheet("color: #666; font-size: 12px; border: 1px dashed #ccc; padding: 20px;")
analysis_layout.addWidget(placeholder)
analyze_btn = QPushButton("📈 Провести анализ (в разработке)")
analyze_btn.clicked.connect(self.show_placeholder_message)
analysis_layout.addWidget(analyze_btn)
tabs.addTab(analysis_tab, "📐 Анализ результатов")
# Вкладка 4: Визуализация
viz_tab = QWidget()
viz_layout = QVBoxLayout(viz_tab)
viz_info = QLabel("Интерактивная визуализация данных")
viz_info.setAlignment(Qt.AlignCenter)
viz_info.setStyleSheet("font-weight: bold; font-size: 14px; padding: 10px;")
viz_layout.addWidget(viz_info)
viz_placeholder = QLabel("Здесь будут:\n\n"
"• 2D и 3D графики поверхностей отклика\n"
"• Контурные графики\n"
"• Диаграммы Парето\n"
"• Графики нормальной вероятности")
viz_placeholder.setAlignment(Qt.AlignCenter)
viz_placeholder.setStyleSheet("color: #666; font-size: 12px; border: 1px dashed #ccc; padding: 20px;")
viz_layout.addWidget(viz_placeholder)
tabs.addTab(viz_tab, "📈 Визуализация")
layout.addWidget(tabs)
# Кнопки управления
btn_layout = QHBoxLayout()
export_btn = QPushButton("💾 Экспорт отчёта (PDF/Excel)")
export_btn.clicked.connect(self.show_placeholder_message)
btn_layout.addWidget(export_btn)
save_btn = QPushButton("💿 Сохранить проект")
save_btn.clicked.connect(self.show_placeholder_message)
btn_layout.addWidget(save_btn)
load_btn = QPushButton("📂 Загрузить проект")
load_btn.clicked.connect(self.show_placeholder_message)
btn_layout.addWidget(load_btn)
btn_layout.addStretch()
close_btn = QPushButton("❌ Закрыть")
close_btn.clicked.connect(self.close)
btn_layout.addWidget(close_btn)
layout.addLayout(btn_layout)
def add_factor_row(self):
"""Добавляет строку для нового фактора"""
row = self.factors_table.rowCount()
self.factors_table.insertRow(row)
self.factors_table.setItem(row, 0, QTableWidgetItem(f"Фактор_{row+1}"))
self.factors_table.setItem(row, 1, QTableWidgetItem("0"))
self.factors_table.setItem(row, 2, QTableWidgetItem("1"))
self.factors_table.setItem(row, 3, QTableWidgetItem(""))
def remove_factor_row(self):
"""Удаляет последнюю строку факторов"""
if self.factors_table.rowCount() > 1:
self.factors_table.removeRow(self.factors_table.rowCount() - 1)
def add_response_row(self):
"""Добавляет строку для нового отклика"""
row = self.responses_table.rowCount()
self.responses_table.insertRow(row)
self.responses_table.setItem(row, 0, QTableWidgetItem(f"Отклик_{row+1}"))
self.responses_table.setItem(row, 1, QTableWidgetItem(""))
def remove_response_row(self):
"""Удаляет последнюю строку откликов"""
if self.responses_table.rowCount() > 1:
self.responses_table.removeRow(self.responses_table.rowCount() - 1)
def generate_design_matrix(self):
"""Генерирует матрицу планирования (заглушка)"""
n_factors = self.factors_table.rowCount()
if n_factors == 0:
QMessageBox.warning(self, "Предупреждение", "Добавьте хотя бы один фактор!")
return
# Полнофакторный план: 2^k опытов
n_experiments = 2 ** n_factors
self.design_matrix.setRowCount(n_experiments)
self.design_matrix.setColumnCount(n_factors + 1)
# Заголовки
headers = ["Опыт №"] + [self.factors_table.item(i, 0).text() if self.factors_table.item(i, 0) else f"Фактор_{i+1}"
for i in range(n_factors)]
self.design_matrix.setHorizontalHeaderLabels(headers)
# Заполняем матрицу (простой 2^k план)
for exp in range(n_experiments):
# Номер опыта
self.design_matrix.setItem(exp, 0, QTableWidgetItem(str(exp + 1)))
# Кодированные уровни факторов (-1 или +1)
for factor in range(n_factors):
level = -1 if (exp // (2 ** factor)) % 2 == 0 else 1
self.design_matrix.setItem(exp, factor + 1, QTableWidgetItem(str(level)))
self.design_matrix.resizeColumnsToContents()
QMessageBox.information(self, "Успех",
f"Сгенерирован план для {n_factors} факторов\n"
f"Общее количество опытов: {n_experiments}")
def show_placeholder_message(self):
"""Показывает сообщение о том, что функция в разработке"""
QMessageBox.information(
self,
"В разработке",
"🧪 Биотехнологические инструменты в стадии активной разработки!\n\n"
"В ближайшее время здесь появится:\n\n"
"✅ Полнофакторный план (2^k факторный дизайн)\n"
"✅ Регрессионный анализ и ANOVA\n"
"✅ Построение поверхностей отклика\n"
"✅ Оптимизация параметров\n"
"✅ Экспорт в Excel и PDF\n"
"✅ Визуализация 2D/3D графиков\n\n"
"Следите за обновлениями!"
)
+4 -6
View File
@@ -1,16 +1,14 @@
# main.py # main.py
import sys import sys
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from controller import Controller from main_window import MainWindow
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)
# Создаём контроллер - он сам создаст модель и представление # Создаём главное окно с выбором режима
controller = Controller() main_window = MainWindow()
main_window.show()
# Показываем главное окно
controller.view.show()
# Запускаем цикл обработки событий # Запускаем цикл обработки событий
sys.exit(app.exec_()) sys.exit(app.exec_())
+145
View File
@@ -0,0 +1,145 @@
from PyQt5.QtWidgets import (QMainWindow, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QWidget, QFrame)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QIcon
from controller import Controller
from experiment_design import ExperimentDesignWindow
class DigitalBiochemistAssistant(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Цифровой помощник биохимика - Главное меню")
self.setGeometry(300, 200, 700, 500)
self.setStyleSheet("""
QMainWindow {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:1,
stop:0 #e8f4f8, stop:1 #f0f0f0);
}
QPushButton {
background-color: #2196F3;
color: white;
border: none;
padding: 15px;
font-size: 16px;
font-weight: bold;
border-radius: 8px;
font-family: 'Segoe UI', Arial;
}
QPushButton:hover {
background-color: #1976D2;
}
QPushButton:pressed {
background-color: #0D47A1;
}
QLabel {
color: #333;
font-size: 14px;
font-family: 'Segoe UI', Arial;
}
""")
self._init_ui()
def _init_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
layout.setSpacing(20)
layout.setContentsMargins(50, 50, 50, 50)
# Заголовок
title_label = QLabel("🧬 Цифровой помощник биохимика 🧪")
title_font = QFont()
title_font.setPointSize(20)
title_font.setBold(True)
title_label.setFont(title_font)
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("color: #1565C0;")
layout.addWidget(title_label)
# Подзаголовок
subtitle_label = QLabel("Биотехнологические инструменты для лаборатории")
subtitle_font = QFont()
subtitle_font.setPointSize(12)
subtitle_label.setFont(subtitle_font)
subtitle_label.setAlignment(Qt.AlignCenter)
subtitle_label.setStyleSheet("color: #666;")
layout.addWidget(subtitle_label)
layout.addSpacing(20)
# Кнопка 1: Калькулятор питательных сред
btn_medium = QPushButton("🥼 Калькулятор питательных сред")
btn_medium.setMinimumHeight(80)
btn_medium.clicked.connect(self.open_medium_calculator)
layout.addWidget(btn_medium)
# Описание кнопки 1
desc1_label = QLabel("Расчёт состава питательной среды с учётом процентного содержания,\n"
"разбавления реагентов и автоматическим расчётом растворителя")
desc1_label.setAlignment(Qt.AlignCenter)
desc1_label.setWordWrap(True)
desc1_label.setStyleSheet("color: #555; font-size: 11px;")
layout.addWidget(desc1_label)
layout.addSpacing(15)
# Кнопка 2: Планирование эксперимента
btn_experiment = QPushButton("📊 Планирование эксперимента (DoE)")
btn_experiment.setMinimumHeight(80)
btn_experiment.clicked.connect(self.open_experiment_designer)
layout.addWidget(btn_experiment)
# Описание кнопки 2
desc2_label = QLabel("Дизайн эксперимента, оптимизация процессов,\n"
"многомерный анализ и визуализация")
desc2_label.setAlignment(Qt.AlignCenter)
desc2_label.setWordWrap(True)
desc2_label.setStyleSheet("color: #555; font-size: 11px;")
layout.addWidget(desc2_label)
layout.addSpacing(15)
# Линия-разделитель
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
layout.addWidget(line)
# Нижняя панель
bottom_layout = QHBoxLayout()
# Информация о версии
version_label = QLabel("Версия 2.0 | © 2024 Цифровой помощник биохимика")
version_label.setStyleSheet("color: #999; font-size: 10px;")
bottom_layout.addWidget(version_label)
bottom_layout.addStretch()
# Кнопка выхода
btn_exit = QPushButton("Выход")
btn_exit.setMaximumWidth(150)
btn_exit.setStyleSheet("""
QPushButton {
background-color: #f44336;
padding: 8px;
font-size: 14px;
}
QPushButton:hover {
background-color: #da190b;
}
""")
btn_exit.clicked.connect(self.close)
bottom_layout.addWidget(btn_exit)
layout.addLayout(bottom_layout)
def open_medium_calculator(self):
"""Открывает калькулятор питательной среды"""
self.medium_calculator = Controller()
self.medium_calculator.view.show()
def open_experiment_designer(self):
"""Открывает окно планирования эксперимента"""
self.experiment_window = ExperimentDesignWindow()
self.experiment_window.show()