From 75b710bf597b21c7930fc244c91d4379868d1a1a Mon Sep 17 00:00:00 2001 From: Artemiy Date: Mon, 18 May 2026 23:23:53 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=87=D0=B8=D1=89=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patch.sh | 904 ------------------------------------------------------- 1 file changed, 904 deletions(-) delete mode 100644 patch.sh diff --git a/patch.sh b/patch.sh deleted file mode 100644 index d99727a..0000000 --- a/patch.sh +++ /dev/null @@ -1,904 +0,0 @@ -#!/bin/bash - -# Скрипт для добавления функционала сохранения/загрузки данных в JSON -# Запускать из корневой директории проекта - -set -e - -echo "📁 Добавление функционала сохранения/загрузки данных в JSON..." -echo "================================================================" - -# Создаём директорию для моделей, если её нет -mkdir -p calculations/models - -# 1. СОЗДАЁМ МОДЕЛЬ ДАННЫХ ДЛЯ СОХРАНЕНИЯ -cat > calculations/models/project_data.py << 'EOF' -""" -Модель данных проекта для сохранения/загрузки в JSON -""" - -from typing import List, Dict, Optional -from dataclasses import dataclass, asdict -from datetime import datetime -import json - - -@dataclass -class ReagentData: - """Данные реагента""" - name: str - percentage: float - unit: str - conversion_factor: float - dilution_factor: float - - def to_dict(self) -> Dict: - return asdict(self) - - @classmethod - def from_dict(cls, data: Dict) -> 'ReagentData': - return cls( - name=data['name'], - percentage=data['percentage'], - unit=data['unit'], - conversion_factor=data.get('conversion_factor', 1.0), - dilution_factor=data.get('dilution_factor', 1.0) - ) - - -@dataclass -class FactorData: - """Данные фактора эксперимента""" - name: str - center: float - low: float - high: float - step: float - step_type: str - unit: str - percentage: Optional[float] = None - dilution_factor: Optional[float] = None - - def to_dict(self) -> Dict: - d = asdict(self) - # Удаляем None значения - return {k: v for k, v in d.items() if v is not None} - - @classmethod - def from_dict(cls, data: Dict) -> 'FactorData': - return cls( - name=data['name'], - center=data['center'], - low=data['low'], - high=data['high'], - step=data.get('step', 0), - step_type=data.get('step_type', 'абс'), - unit=data.get('unit', ''), - percentage=data.get('percentage'), - dilution_factor=data.get('dilution_factor') - ) - - -@dataclass -class ResponseData: - """Данные отклика эксперимента""" - name: str - unit: str - - def to_dict(self) -> Dict: - return asdict(self) - - @classmethod - def from_dict(cls, data: Dict) -> 'ResponseData': - return cls( - name=data['name'], - unit=data.get('unit', '') - ) - - -@dataclass -class ExperimentResultsData: - """Результаты эксперимента""" - design: List[Dict] # План эксперимента - results: List[List[float]] # Результаты измерений - responses: List[ResponseData] # Отклики - - def to_dict(self) -> Dict: - return { - 'design': self.design, - 'results': self.results, - 'responses': [r.to_dict() for r in self.responses] - } - - @classmethod - def from_dict(cls, data: Dict) -> 'ExperimentResultsData': - return cls( - design=data['design'], - results=data['results'], - responses=[ResponseData.from_dict(r) for r in data['responses']] - ) - - -@dataclass -class ProjectData: - """Полные данные проекта""" - # Информация о проекте - project_name: str - created_at: str - modified_at: str - version: str = "1.0" - - # Данные калькулятора сред - medium_total_volume: float = 1000.0 - medium_volume_unit: str = "мл" - medium_solvent: str = "Вода" - medium_reagents: List[ReagentData] = None - - # Данные эксперимента - experiment_factors: List[FactorData] = None - experiment_responses: List[ResponseData] = None - experiment_center_points: int = 3 - experiment_randomize: bool = True - experiment_results: Optional[ExperimentResultsData] = None - - def __post_init__(self): - if self.medium_reagents is None: - self.medium_reagents = [] - if self.experiment_factors is None: - self.experiment_factors = [] - if self.experiment_responses is None: - self.experiment_responses = [] - - def to_dict(self) -> Dict: - """Конвертирует в словарь для JSON""" - return { - 'project_info': { - 'name': self.project_name, - 'created_at': self.created_at, - 'modified_at': self.modified_at, - 'version': self.version - }, - 'medium_calculator': { - 'total_volume': self.medium_total_volume, - 'volume_unit': self.medium_volume_unit, - 'solvent': self.medium_solvent, - 'reagents': [r.to_dict() for r in self.medium_reagents] - }, - 'experiment': { - 'factors': [f.to_dict() for f in self.experiment_factors], - 'responses': [r.to_dict() for r in self.experiment_responses], - 'center_points': self.experiment_center_points, - 'randomize': self.experiment_randomize, - 'results': self.experiment_results.to_dict() if self.experiment_results else None - } - } - - @classmethod - def from_dict(cls, data: Dict) -> 'ProjectData': - """Создаёт объект из словаря""" - project_info = data.get('project_info', {}) - medium = data.get('medium_calculator', {}) - experiment = data.get('experiment', {}) - - # Создаём объект - obj = cls( - project_name=project_info.get('name', 'Новый проект'), - created_at=project_info.get('created_at', datetime.now().isoformat()), - modified_at=project_info.get('modified_at', datetime.now().isoformat()), - version=project_info.get('version', '1.0'), - medium_total_volume=medium.get('total_volume', 1000.0), - medium_volume_unit=medium.get('volume_unit', 'мл'), - medium_solvent=medium.get('solvent', 'Вода'), - medium_reagents=[ReagentData.from_dict(r) for r in medium.get('reagents', [])], - experiment_factors=[FactorData.from_dict(f) for f in experiment.get('factors', [])], - experiment_responses=[ResponseData.from_dict(r) for r in experiment.get('responses', [])], - experiment_center_points=experiment.get('center_points', 3), - experiment_randomize=experiment.get('randomize', True), - experiment_results=ExperimentResultsData.from_dict(experiment['results']) - if experiment.get('results') else None - ) - return obj - - def save_to_file(self, filename: str): - """Сохраняет проект в JSON файл""" - self.modified_at = datetime.now().isoformat() - with open(filename, 'w', encoding='utf-8') as f: - json.dump(self.to_dict(), f, ensure_ascii=False, indent=2) - - @classmethod - def load_from_file(cls, filename: str) -> 'ProjectData': - """Загружает проект из JSON файла""" - with open(filename, 'r', encoding='utf-8') as f: - data = json.load(f) - return cls.from_dict(data) - - -def create_new_project(name: str = "Новый проект") -> ProjectData: - """Создаёт новый проект с текущей датой""" - now = datetime.now().isoformat() - return ProjectData( - project_name=name, - created_at=now, - modified_at=now - ) -EOF - -echo "✅ Создан calculations/models/project_data.py" - -# 2. ДОБАВЛЯЕМ ФУНКЦИИ В GUI ДЛЯ РАБОТЫ С JSON -# Создаём патч для gui.py -cat > gui_patch.py << 'EOF' -# ДОБАВИТЬ В gui.py ПОСЛЕ ИМПОРТОВ: - -from calculations.models.project_data import ( - ProjectData, ReagentData, FactorData, - ResponseData, ExperimentResultsData, create_new_project -) - -# ДОБАВИТЬ В КЛАСС MainWindow НОВЫЕ МЕТОДЫ: - - def save_project_to_json(self): - """Сохраняет весь проект в JSON файл""" - filename, _ = QFileDialog.getSaveFileName( - self, "Сохранить проект", "", "JSON Files (*.json);;All Files (*)" - ) - if not filename: - return - - if not filename.endswith('.json'): - filename += '.json' - - try: - # Собираем данные из GUI - project = self._collect_current_data() - project.save_to_file(filename) - QMessageBox.information(self, "Успех", f"Проект сохранён в {filename}") - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить проект: {str(e)}") - - def load_project_from_json(self): - """Загружает проект из JSON файла""" - filename, _ = QFileDialog.getOpenFileName( - self, "Загрузить проект", "", "JSON Files (*.json);;All Files (*)" - ) - if not filename: - return - - try: - project = ProjectData.load_from_file(filename) - self._apply_project_data(project) - QMessageBox.information(self, "Успех", f"Проект загружен из {filename}") - except FileNotFoundError: - QMessageBox.critical(self, "Ошибка", f"Файл не найден: {filename}") - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить проект: {str(e)}") - - def _collect_current_data(self) -> ProjectData: - """Собирает текущие данные из всех виджетов""" - # Данные калькулятора сред - reagents = [] - for row in range(self.reagents_table.rowCount()): - name_item = self.reagents_table.item(row, 0) - percent_item = self.reagents_table.item(row, 1) - unit_widget = self.reagents_table.cellWidget(row, 2) - coeff_item = self.reagents_table.item(row, 3) - dilution_item = self.reagents_table.item(row, 4) - - if name_item and percent_item: - try: - reagent = ReagentData( - name=name_item.text(), - percentage=float(percent_item.text()) if percent_item.text() else 0, - unit=unit_widget.currentText() if unit_widget else "мг", - conversion_factor=float(coeff_item.text()) if coeff_item and coeff_item.text() else 1.0, - dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else 1.0 - ) - reagents.append(reagent) - except ValueError: - pass - - # Данные факторов эксперимента - factors = [] - for row in range(self.factors_table.rowCount()): - name_item = self.factors_table.item(row, 0) - center_item = self.factors_table.item(row, 3) - low_item = self.factors_table.item(row, 7) - high_item = self.factors_table.item(row, 6) - step_item = self.factors_table.item(row, 4) - unit_item = self.factors_table.item(row, 8) - step_combo = self.factors_table.cellWidget(row, 5) - percent_item = self.factors_table.item(row, 1) - dilution_item = self.factors_table.item(row, 2) - - if name_item and center_item: - try: - factor = FactorData( - name=name_item.text(), - center=float(center_item.text()) if center_item.text() else 0, - low=float(low_item.text()) if low_item and low_item.text() else 0, - high=float(high_item.text()) if high_item and high_item.text() else 0, - step=float(step_item.text()) if step_item and step_item.text() else 0, - step_type=step_combo.currentText() if step_combo else "абс", - unit=unit_item.text() if unit_item else "", - percentage=float(percent_item.text()) if percent_item and percent_item.text() else None, - dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else None - ) - factors.append(factor) - except ValueError: - pass - - # Данные результатов (если есть) - results_data = None - if hasattr(self, 'generated_design') and self.generated_design: - results = [] - for i in range(self.results_table.rowCount()): - item = self.results_table.item(i, 1) - if item and item.text(): - try: - results.append([float(item.text())]) - except ValueError: - pass - - if results: - responses = [ResponseData(name="Отклик", unit="")] - results_data = ExperimentResultsData( - design=self.generated_design, - results=results, - responses=responses - ) - - # Создаём проект - project = create_new_project("Мой проект") - project.medium_total_volume = self.total_volume_spin.value() - project.medium_volume_unit = self.volume_unit_combo.currentText() - project.medium_solvent = self.solvent_input.text() - project.medium_reagents = reagents - project.experiment_factors = factors - project.experiment_center_points = self.center_points_spin.value() - project.experiment_randomize = self.randomize_check.isChecked() - project.experiment_results = results_data - - return project - - def _apply_project_data(self, project: ProjectData): - """Применяет загруженные данные к GUI""" - # Применяем данные калькулятора сред - self.total_volume_spin.setValue(project.medium_total_volume) - - index = self.volume_unit_combo.findText(project.medium_volume_unit) - if index >= 0: - self.volume_unit_combo.setCurrentIndex(index) - - self.solvent_input.setText(project.medium_solvent) - - # Очищаем и заполняем таблицу реагентов - self.reagents_table.setRowCount(0) - for reagent in project.medium_reagents: - row = self.reagents_table.rowCount() - self.reagents_table.insertRow(row) - self.reagents_table.setItem(row, 0, QTableWidgetItem(reagent.name)) - self.reagents_table.setItem(row, 1, QTableWidgetItem(str(reagent.percentage))) - - unit_combo = QComboBox() - unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"]) - unit_combo.setCurrentText(reagent.unit) - self.reagents_table.setCellWidget(row, 2, unit_combo) - - self.reagents_table.setItem(row, 3, QTableWidgetItem(str(reagent.conversion_factor))) - self.reagents_table.setItem(row, 4, QTableWidgetItem(str(reagent.dilution_factor))) - self.reagents_table.setItem(row, 5, QTableWidgetItem("")) - - # Применяем данные факторов эксперимента - self.factors_table.setRowCount(0) - for factor in project.experiment_factors: - row = self.factors_table.rowCount() - self.factors_table.insertRow(row) - self.factors_table.setItem(row, 0, QTableWidgetItem(factor.name)) - - if factor.percentage is not None: - self.factors_table.setItem(row, 1, QTableWidgetItem(str(factor.percentage))) - else: - self.factors_table.setItem(row, 1, QTableWidgetItem("0")) - - if factor.dilution_factor is not None: - self.factors_table.setItem(row, 2, QTableWidgetItem(str(factor.dilution_factor))) - else: - self.factors_table.setItem(row, 2, QTableWidgetItem("1")) - - self.factors_table.setItem(row, 3, QTableWidgetItem(str(factor.center))) - self.factors_table.setItem(row, 4, QTableWidgetItem(str(factor.step))) - - step_combo = QComboBox() - step_combo.addItems(["абс", "%"]) - step_combo.setCurrentText(factor.step_type) - self.factors_table.setCellWidget(row, 5, step_combo) - - self.factors_table.setItem(row, 6, QTableWidgetItem(str(factor.high))) - self.factors_table.setItem(row, 7, QTableWidgetItem(str(factor.low))) - self.factors_table.setItem(row, 8, QTableWidgetItem(factor.unit)) - - # Применяем настройки эксперимента - self.center_points_spin.setValue(project.experiment_center_points) - self.randomize_check.setChecked(project.experiment_randomize) - - # Если есть результаты, загружаем их - if project.experiment_results and project.experiment_results.design: - self.generated_design = project.experiment_results.design - self._display_design_matrix() - - # Загружаем результаты в таблицу - if project.experiment_results.results: - for i, row_results in enumerate(project.experiment_results.results): - if i < self.results_table.rowCount() and row_results: - self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0]))) - - # Переключаемся на вкладку с факторами - self.tab_widget.setCurrentIndex(1) - - def _display_design_matrix(self): - """Отображает матрицу планирования (добавить в _generate_design или отдельно)""" - if not self.generated_design: - return - - n_exp = len(self.generated_design) - n_factors = len(self._get_factors_from_table()) - - self.design_matrix.setRowCount(n_exp) - self.design_matrix.setColumnCount(n_factors + 2) - headers = ["№"] + [f['name'] for f in self._get_factors_from_table()] + ["Тип"] - self.design_matrix.setHorizontalHeaderLabels(headers) - - for exp_idx, exp in enumerate(self.generated_design): - self.design_matrix.setItem(exp_idx, 0, QTableWidgetItem(str(exp_idx + 1))) - - for f_idx in range(n_factors): - key = f"Фактор_{f_idx + 1}" - if key not in exp: - continue - value = exp[key]['natural'] - unit = self._get_factors_from_table()[f_idx]['unit'] - - display = self._format_number(value) - if unit: - display += f" {unit}" - - item = QTableWidgetItem(display) - if exp.get('is_center', False): - item.setBackground(QColor(255, 255, 200)) - self.design_matrix.setItem(exp_idx, f_idx + 1, item) - - if exp.get('is_center', False): - type_item = QTableWidgetItem(f"Центр #{exp['center_num']}") - type_item.setBackground(QColor(255, 255, 200)) - else: - type_item = QTableWidgetItem("Факторная") - self.design_matrix.setItem(exp_idx, n_factors + 1, type_item) - - self.design_matrix.resizeColumnsToContents() - self.export_csv_btn.setEnabled(True) - self._setup_results_table(n_exp) - -# ДОБАВИТЬ В МЕТОД _setup_ui ПОСЛЕ СОЗДАНИЯ ВКЛАДОК: - - # Добавляем кнопки сохранения/загрузки в тулбар - toolbar = self.addToolBar("Файл") - toolbar.setMovable(False) - - save_action = toolbar.addAction("💾 Сохранить проект") - save_action.triggered.connect(self.save_project_to_json) - - load_action = toolbar.addAction("📂 Загрузить проект") - load_action.triggered.connect(self.load_project_from_json) - - toolbar.addSeparator() - - # Сохраняем ссылку на tab_widget для переключения вкладок - self.tab_widget = tabs -EOF - -echo "✅ Создан патч gui_patch.py" - -# 3. ПРИМЕНЯЕМ ПАТЧ К GUI.PY -# Создаём резервную копию -cp gui.py gui.py.backup -echo "✅ Создана резервная копия gui.py.backup" - -# Вставляем импорты после существующих импортов -sed -i '/from calculations.doe import/a\ -from calculations.models.project_data import (\ - ProjectData, ReagentData, FactorData, \ - ResponseData, ExperimentResultsData, create_new_project\ -)' gui.py - -# Находим строку с self._setup_ui() и добавляем после неё toolbar -sed -i '/self._setup_ui()/a\ self._add_file_toolbar()' gui.py - -# Добавляем новый метод _add_file_toolbar после _setup_ui -cat >> gui.py.new_method << 'EOF' - - def _add_file_toolbar(self): - """Добавляет панель инструментов с кнопками сохранения/загрузки""" - toolbar = self.addToolBar("Файл") - toolbar.setMovable(False) - - save_action = toolbar.addAction("💾 Сохранить проект") - save_action.triggered.connect(self.save_project_to_json) - - load_action = toolbar.addAction("📂 Загрузить проект") - load_action.triggered.connect(self.load_project_from_json) - - toolbar.addSeparator() -EOF - -# Вставляем новый метод после _setup_ui -sed -i '/def _setup_ui(self):/,/def _get_stylesheet(self):/ { - /def _get_stylesheet(self):/ { - r gui.py.new_method - } -}' gui.py - -rm gui.py.new_method - -# Добавляем методы сохранения/загрузки в конец файла перед if __name__ -cat >> gui.py << 'EOF' - - # ========== МЕТОДЫ РАБОТЫ С JSON ========== - - def save_project_to_json(self): - """Сохраняет весь проект в JSON файл""" - from PyQt5.QtWidgets import QFileDialog, QMessageBox - - filename, _ = QFileDialog.getSaveFileName( - self, "Сохранить проект", "", "JSON Files (*.json);;All Files (*)" - ) - if not filename: - return - - if not filename.endswith('.json'): - filename += '.json' - - try: - project = self._collect_current_data() - project.save_to_file(filename) - QMessageBox.information(self, "Успех", f"Проект сохранён в {filename}") - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить проект: {str(e)}") - - def load_project_from_json(self): - """Загружает проект из JSON файла""" - from PyQt5.QtWidgets import QFileDialog, QMessageBox, QComboBox, QTableWidgetItem - - filename, _ = QFileDialog.getOpenFileName( - self, "Загрузить проект", "", "JSON Files (*.json);;All Files (*)" - ) - if not filename: - return - - try: - project = ProjectData.load_from_file(filename) - self._apply_project_data(project) - QMessageBox.information(self, "Успех", f"Проект загружен из {filename}") - except FileNotFoundError: - QMessageBox.critical(self, "Ошибка", f"Файл не найден: {filename}") - except Exception as e: - QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить проект: {str(e)}") - - def _collect_current_data(self) -> ProjectData: - """Собирает текущие данные из всех виджетов""" - from PyQt5.QtWidgets import QComboBox - - # Данные калькулятора сред - reagents = [] - for row in range(self.reagents_table.rowCount()): - name_item = self.reagents_table.item(row, 0) - percent_item = self.reagents_table.item(row, 1) - unit_widget = self.reagents_table.cellWidget(row, 2) - coeff_item = self.reagents_table.item(row, 3) - dilution_item = self.reagents_table.item(row, 4) - - if name_item and percent_item and percent_item.text(): - try: - reagent = ReagentData( - name=name_item.text(), - percentage=float(percent_item.text()), - unit=unit_widget.currentText() if unit_widget else "мг", - conversion_factor=float(coeff_item.text()) if coeff_item and coeff_item.text() else 1.0, - dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else 1.0 - ) - reagents.append(reagent) - except ValueError: - pass - - # Данные факторов эксперимента - factors = [] - for row in range(self.factors_table.rowCount()): - name_item = self.factors_table.item(row, 0) - center_item = self.factors_table.item(row, 3) - low_item = self.factors_table.item(row, 7) - high_item = self.factors_table.item(row, 6) - step_item = self.factors_table.item(row, 4) - unit_item = self.factors_table.item(row, 8) - step_combo = self.factors_table.cellWidget(row, 5) - percent_item = self.factors_table.item(row, 1) - dilution_item = self.factors_table.item(row, 2) - - if name_item and center_item and center_item.text(): - try: - factor = FactorData( - name=name_item.text(), - center=float(center_item.text()), - low=float(low_item.text()) if low_item and low_item.text() else 0, - high=float(high_item.text()) if high_item and high_item.text() else 0, - step=float(step_item.text()) if step_item and step_item.text() else 0, - step_type=step_combo.currentText() if step_combo else "абс", - unit=unit_item.text() if unit_item else "", - percentage=float(percent_item.text()) if percent_item and percent_item.text() else None, - dilution_factor=float(dilution_item.text()) if dilution_item and dilution_item.text() else None - ) - factors.append(factor) - except ValueError: - pass - - # Данные результатов (если есть) - results_data = None - if hasattr(self, 'generated_design') and self.generated_design: - results = [] - for i in range(self.results_table.rowCount()): - item = self.results_table.item(i, 1) - if item and item.text(): - try: - results.append([float(item.text())]) - except ValueError: - pass - - if results: - responses = [ResponseData(name="Отклик", unit="")] - results_data = ExperimentResultsData( - design=self.generated_design, - results=results, - responses=responses - ) - - # Создаём проект - project = create_new_project("Мой проект") - project.medium_total_volume = self.total_volume_spin.value() - project.medium_volume_unit = self.volume_unit_combo.currentText() - project.medium_solvent = self.solvent_input.text() - project.medium_reagents = reagents - project.experiment_factors = factors - project.experiment_center_points = self.center_points_spin.value() - project.experiment_randomize = self.randomize_check.isChecked() - project.experiment_results = results_data - - return project - - def _apply_project_data(self, project: ProjectData): - """Применяет загруженные данные к GUI""" - from PyQt5.QtWidgets import QComboBox, QTableWidgetItem - from PyQt5.QtCore import Qt - - # Применяем данные калькулятора сред - self.total_volume_spin.setValue(project.medium_total_volume) - - index = self.volume_unit_combo.findText(project.medium_volume_unit) - if index >= 0: - self.volume_unit_combo.setCurrentIndex(index) - - self.solvent_input.setText(project.medium_solvent) - - # Очищаем и заполняем таблицу реагентов - self.reagents_table.setRowCount(0) - for reagent in project.medium_reagents: - row = self.reagents_table.rowCount() - self.reagents_table.insertRow(row) - self.reagents_table.setItem(row, 0, QTableWidgetItem(reagent.name)) - self.reagents_table.setItem(row, 1, QTableWidgetItem(str(reagent.percentage))) - - unit_combo = QComboBox() - unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"]) - unit_combo.setCurrentText(reagent.unit) - self.reagents_table.setCellWidget(row, 2, unit_combo) - - self.reagents_table.setItem(row, 3, QTableWidgetItem(str(reagent.conversion_factor))) - self.reagents_table.setItem(row, 4, QTableWidgetItem(str(reagent.dilution_factor))) - self.reagents_table.setItem(row, 5, QTableWidgetItem("")) - - # Применяем данные факторов эксперимента - self.factors_table.setRowCount(0) - for factor in project.experiment_factors: - row = self.factors_table.rowCount() - self.factors_table.insertRow(row) - self.factors_table.setItem(row, 0, QTableWidgetItem(factor.name)) - - if factor.percentage is not None: - self.factors_table.setItem(row, 1, QTableWidgetItem(str(factor.percentage))) - else: - self.factors_table.setItem(row, 1, QTableWidgetItem("0")) - - if factor.dilution_factor is not None: - self.factors_table.setItem(row, 2, QTableWidgetItem(str(factor.dilution_factor))) - else: - self.factors_table.setItem(row, 2, QTableWidgetItem("1")) - - self.factors_table.setItem(row, 3, QTableWidgetItem(str(factor.center))) - self.factors_table.setItem(row, 4, QTableWidgetItem(str(factor.step))) - - step_combo = QComboBox() - step_combo.addItems(["абс", "%"]) - step_combo.setCurrentText(factor.step_type) - self.factors_table.setCellWidget(row, 5, step_combo) - - self.factors_table.setItem(row, 6, QTableWidgetItem(str(factor.high))) - self.factors_table.setItem(row, 7, QTableWidgetItem(str(factor.low))) - self.factors_table.setItem(row, 8, QTableWidgetItem(factor.unit)) - - # Применяем настройки эксперимента - self.center_points_spin.setValue(project.experiment_center_points) - self.randomize_check.setChecked(project.experiment_randomize) - - # Если есть результаты, загружаем их - if project.experiment_results and project.experiment_results.design: - self.generated_design = project.experiment_results.design - # Обновляем отображение матрицы - self._refresh_design_matrix() - - # Загружаем результаты в таблицу - if project.experiment_results.results: - for i, row_results in enumerate(project.experiment_results.results): - if i < self.results_table.rowCount() and row_results: - self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0]))) - - # Переключаемся на вкладку с факторами - if hasattr(self, 'tab_widget'): - self.tab_widget.setCurrentIndex(1) - - def _refresh_design_matrix(self): - """Обновляет отображение матрицы планирования""" - if not self.generated_design: - return - - factors = self._get_factors_from_table() - n_exp = len(self.generated_design) - n_factors = len(factors) - - self.design_matrix.setRowCount(n_exp) - self.design_matrix.setColumnCount(n_factors + 2) - headers = ["№"] + [f['name'] for f in factors] + ["Тип"] - self.design_matrix.setHorizontalHeaderLabels(headers) - - for exp_idx, exp in enumerate(self.generated_design): - self.design_matrix.setItem(exp_idx, 0, QTableWidgetItem(str(exp_idx + 1))) - - for f_idx in range(n_factors): - key = f"Фактор_{f_idx + 1}" - if key not in exp: - continue - value = exp[key]['natural'] - unit = factors[f_idx]['unit'] - - display = self._format_number(value) - if unit: - display += f" {unit}" - - item = QTableWidgetItem(display) - if exp.get('is_center', False): - item.setBackground(QColor(255, 255, 200)) - self.design_matrix.setItem(exp_idx, f_idx + 1, item) - - if exp.get('is_center', False): - type_item = QTableWidgetItem(f"Центр #{exp['center_num']}") - type_item.setBackground(QColor(255, 255, 200)) - else: - type_item = QTableWidgetItem("Факторная") - self.design_matrix.setItem(exp_idx, n_factors + 1, type_item) - - self.design_matrix.resizeColumnsToContents() - - if hasattr(self, 'export_csv_btn'): - self.export_csv_btn.setEnabled(True) - - n_factorial = 2 ** n_factors - n_center = self.center_points_spin.value() - self.design_info.setText( - f"📊 Факторных точек: {n_factorial}, Центральных: {n_center}, Всего: {n_exp}" - ) - self._setup_results_table(n_exp) -EOF - -echo "✅ Добавлены методы работы с JSON в gui.py" - -# 4. СОЗДАЁМ ПРИМЕР JSON ФАЙЛА -cat > example_project.json << 'EOF' -{ - "project_info": { - "name": "Пример проекта", - "created_at": "2026-05-14T16:00:00", - "modified_at": "2026-05-14T16:00:00", - "version": "1.0" - }, - "medium_calculator": { - "total_volume": 1000, - "volume_unit": "мл", - "solvent": "Вода", - "reagents": [ - { - "name": "Глюкоза", - "percentage": 2.0, - "unit": "г", - "conversion_factor": 1.0, - "dilution_factor": 1.0 - }, - { - "name": "Пептон", - "percentage": 1.0, - "unit": "г", - "conversion_factor": 1.0, - "dilution_factor": 1.0 - } - ] - }, - "experiment": { - "factors": [ - { - "name": "Глюкоза", - "center": 20.0, - "low": 18.0, - "high": 22.0, - "step": 2.0, - "step_type": "абс", - "unit": "г", - "percentage": 2.0, - "dilution_factor": 1.0 - }, - { - "name": "Пептон", - "center": 10.0, - "low": 9.0, - "high": 11.0, - "step": 1.0, - "step_type": "абс", - "unit": "г", - "percentage": 1.0, - "dilution_factor": 1.0 - } - ], - "responses": [ - {"name": "Отклик", "unit": ""} - ], - "center_points": 3, - "randomize": true, - "results": null - } -} -EOF - -echo "✅ Создан example_project.json" - -# 5. СОЗДАЁМ requirements.txt ЕСЛИ ЕГО НЕТ -if [ ! -f requirements.txt ]; then - cat > requirements.txt << 'EOF' -PyQt5>=5.15.0 -numpy>=1.21.0 -EOF - echo "✅ Создан requirements.txt" -fi - -echo "" -echo "================================================================" -echo "✅ ГОТОВО! Функционал сохранения/загрузки JSON добавлен!" -echo "================================================================" -echo "" -echo "Что было добавлено:" -echo " 1. Модель данных в calculations/models/project_data.py" -echo " 2. Кнопки 'Сохранить проект' и 'Загрузить проект' в toolbar" -echo " 3. Методы сохранения/загрузки в gui.py" -echo " 4. Пример JSON файла example_project.json" -echo "" -echo "Для запуска программы:" -echo " python main.py" -echo "" -echo "Для восстановления оригинального gui.py (если нужно):" -echo " cp gui.py.backup gui.py" -echo "" -echo "Чтобы применить изменения, перезапустите программу" -echo "================================================================"