Files
help_lab/patch.sh
T

905 lines
38 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "================================================================"