From 1ddfe20a8ded98c2b5a88a8cab48fc36ba9534f1 Mon Sep 17 00:00:00 2001 From: Artemiy Date: Tue, 26 May 2026 22:50:16 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BA=D0=BE=D0=B4,=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D1=8B=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=BE=D0=B2=20=D1=8D=D0=BA?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- calculations/doe.py | 16 ++--- gui.py | 141 +++++++++++++++++--------------------------- theme.py | 18 ++++-- 3 files changed, 75 insertions(+), 100 deletions(-) diff --git a/calculations/doe.py b/calculations/doe.py index 06305fe..15884c5 100644 --- a/calculations/doe.py +++ b/calculations/doe.py @@ -16,7 +16,7 @@ import numpy as np # Типы расчёта шага FACTOR_TYPES = { - 'absolute': 'абс', # абсолютный шаг + 'absolute': 'ед.', # абсолютный шаг 'relative': '%', # относительный шаг (процент от нулевого уровня) } @@ -33,7 +33,7 @@ def calculate_factor_levels( Параметры: center_value: нулевой уровень фактора (центральная точка) step_value: значение шага - step_type: тип шага ("абс" - абсолютный, "%" - относительный) + step_type: тип шага ("ед." - абсолютный, "%" - относительный) base_value: базовое значение для относительного шага (если None, используется center_value) Возвращает: @@ -42,14 +42,14 @@ def calculate_factor_levels( Пример: >>> calculate_factor_levels(100, 10, "%") (110.0, 90.0) - >>> calculate_factor_levels(100, 20, "абс") + >>> calculate_factor_levels(100, 20, "ед.") (120.0, 80.0) """ # Определяем абсолютное значение шага if step_type == "%": base = base_value if base_value is not None else center_value step_abs = center_value * step_value / 100 - else: # "абс" или "absolute" + else: # "ед." или "absolute" step_abs = step_value high_level = center_value + step_abs @@ -79,7 +79,7 @@ def generate_factorial_design( - center (float): нулевой уровень (0) - unit (str): единица измерения - step (float, опционально): шаг варьирования - - step_type (str, опционально): тип шага ("абс" или "%") + - step_type (str, опционально): тип шага ("ед." или "%") Минимально необходимые ключи: name, low, high, center, unit @@ -297,7 +297,7 @@ def create_factor_from_reagent( step_value = center_value * step_percent / 100 high_level, low_level = calculate_factor_levels( - center_value, step_value, "абс" + center_value, step_value, "ед." ) return { @@ -306,7 +306,7 @@ def create_factor_from_reagent( 'low': low_level, 'high': high_level, 'step': step_value, - 'step_type': 'абс', + 'step_type': 'ед.', 'unit': reagent.get('unit', volume_unit), 'percentage': reagent.get('percentage', 0), 'dilution_factor': reagent.get('dilution_factor', 1.0) @@ -336,7 +336,7 @@ def create_factor_from_reagent( step_value = center_value * step_percent / 100 high_level, low_level = calculate_factor_levels( - center_value, step_value, "абс" + center_value, step_value, "ед." ) return { diff --git a/gui.py b/gui.py index 5f336b4..a03134f 100644 --- a/gui.py +++ b/gui.py @@ -189,24 +189,22 @@ class MainWindow(QMainWindow): return tab - def _add_reagent_row(self): + def _add_reagent_row(self, name = "", percentage = "0", unit = "мл", dilution = "1", amount = ""): row = self.reagents_table.rowCount() self.reagents_table.insertRow(row) - self.reagents_table.setItem(row, 0, QTableWidgetItem(f"Реагент_{row+1}")) - self.reagents_table.setItem(row, 1, QTableWidgetItem("0")) + if name == "": + self.reagents_table.setItem(row, 0, QTableWidgetItem(f"Реагент_{row+1}")) + else: + self.reagents_table.setItem(row, 0, QTableWidgetItem(name)) + self.reagents_table.setItem(row, 1, QTableWidgetItem(percentage)) unit_combo = QComboBox() - unit_combo.setStyleSheet(""" - QComboBox { - padding: 1px; - } - """) - unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л", "нл"]) - unit_combo.setCurrentText("мл") + unit_combo.addItems(["мг", "г", "кг", "мкг", "нг", "мл", "мкл", "л"]) + unit_combo.setCurrentText(unit) self.reagents_table.setCellWidget(row, 2, unit_combo) - self.reagents_table.setItem(row, 3, QTableWidgetItem("1")) - self.reagents_table.setItem(row, 4, QTableWidgetItem("")) + self.reagents_table.setItem(row, 3, QTableWidgetItem(dilution)) + self.reagents_table.setItem(row, 4, QTableWidgetItem(amount)) def _remove_reagent_row(self): for row in sorted(set(i.row() for i in self.reagents_table.selectedItems()), reverse=True): @@ -280,13 +278,12 @@ class MainWindow(QMainWindow): # Сохраняем результаты для передачи в DoE self.last_medium_result = result - QMessageBox.information(self, "Успех", "Расчёт выполнен успешно!") +# QMessageBox.information(self, "Успех", "Расчёт выполнен успешно!") except Exception as e: QMessageBox.critical(self, "Ошибка", str(e)) # ========== ВКЛАДКА 2: ФАКТОРЫ ЭКСПЕРИМЕНТА ========== - def _create_factors_tab(self): tab = QWidget() layout = QVBoxLayout(tab) @@ -313,7 +310,7 @@ class MainWindow(QMainWindow): "Фактор", "%", "Разбавление", "Нулевой уровень", "Шаг", "Тип шага", "Верхний (+1)", "Нижний (-1)", "Ед. изм." ]) - self.factors_table.setAlternatingRowColors(True) + #self.factors_table.setAlternatingRowColors(True) factors_layout.addWidget(self.factors_table) @@ -361,30 +358,38 @@ class MainWindow(QMainWindow): return tab - def _add_factor_row(self): + def _add_factor_row(self, name = "", percentage = "0", dilution = "0", center = "0", step ="0", step_type = "%", high = "", low = "", unit= "г"): 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("0")) - self.factors_table.setItem(row, 4, QTableWidgetItem("1")) + if name == "": + self.factors_table.setItem(row, 0, QTableWidgetItem(f"Фактор_{row+1}")) + else: + self.factors_table.setItem(row, 0, QTableWidgetItem(name)) + self.factors_table.setItem(row, 1, QTableWidgetItem(percentage)) + self.factors_table.setItem(row, 2, QTableWidgetItem(dilution)) + self.factors_table.setItem(row, 3, QTableWidgetItem(center)) + self.factors_table.setItem(row, 4, QTableWidgetItem(step)) + step_type_combo = QComboBox() - step_type_combo.addItems(["абс", "%"]) + step_type_combo.addItems(["%", "ед."]) + step_type_combo.setCurrentText(step_type) self.factors_table.setCellWidget(row, 5, step_type_combo) - high_item = QTableWidgetItem("1") + high_item = QTableWidgetItem(high) high_item.setFlags(high_item.flags() & ~Qt.ItemIsEditable) high_item.setBackground(QColor(240, 240, 240)) self.factors_table.setItem(row, 6, high_item) - low_item = QTableWidgetItem("-1") + low_item = QTableWidgetItem(low) low_item.setFlags(low_item.flags() & ~Qt.ItemIsEditable) low_item.setBackground(QColor(240, 240, 240)) self.factors_table.setItem(row, 7, low_item) - - self.factors_table.setItem(row, 8, QTableWidgetItem("")) + unit_measure = QComboBox() + unit_measure.addItems(["мкл", "мл", "л", "мг", "г", "кг"]) + unit_measure.setCurrentText(unit) + self.factors_table.setCellWidget(row, 8, unit_measure) + return row def _remove_factor_row(self): for row in sorted(set(i.row() for i in self.factors_table.selectedItems()), reverse=True): @@ -404,37 +409,20 @@ class MainWindow(QMainWindow): self.factors_table.setRowCount(0) for reagent in result['reagents']: - row = self.factors_table.rowCount() - self.factors_table.insertRow(row) - name = reagent['name'] if reagent.get('dilution_factor', 1.0) != 1.0: - name += f" (разб. ×{reagent['dilution_factor']:.2f})" - - self.factors_table.setItem(row, 0, QTableWidgetItem(name)) - self.factors_table.setItem(row, 1, QTableWidgetItem(f"{reagent['percentage']:.2f}")) - self.factors_table.setItem(row, 2, QTableWidgetItem(f"{reagent.get('dilution_factor', 1.0):.3f}")) - - # Нулевой уровень - исходное количество (неразбавленное) - center = reagent.get('undiluted_amount', reagent['calculated_amount']) - self.factors_table.setItem(row, 3, QTableWidgetItem(self._format_number(center))) - - # Шаг - 10% от нулевого уровня - step = center * 0.1 - self.factors_table.setItem(row, 4, QTableWidgetItem(self._format_number(step))) - - # Тип шага - абсолютный - step_combo = self.factors_table.cellWidget(row, 5) - if step_combo: - step_combo.setCurrentText("абс") - - # Верхний и нижний уровни - self.factors_table.setItem(row, 6, QTableWidgetItem(self._format_number(center + step))) - self.factors_table.setItem(row, 7, QTableWidgetItem(self._format_number(center - step))) - self.factors_table.setItem(row, 8, QTableWidgetItem(result['total_unit'])) - - QMessageBox.information(self, "Успех", - f"Импортировано {len(result['reagents'])} факторов из калькулятора сред") + name += f" (разб. ×{reagent['dilution_factor']:.g})" + percentage = f"{reagent['percentage']:.g}" + dilution = f"{reagent.get('dilution_factor', 1.0):g}" + center = str(reagent.get('calculated_amount')) + step = "" + step_type = "%" + high = "" + low = "" + unit = reagent["unit"] + self._add_factor_row(name, percentage, dilution, center, step, step_type, high, low, unit) +# QMessageBox.information(self, "Успех", +# f"Импортировано {len(result['reagents'])} факторов из калькулятора сред") def _get_factors_from_table(self) -> List[Dict]: """Собирает данные факторов из таблицы""" @@ -466,7 +454,7 @@ class MainWindow(QMainWindow): 'low': float(low_text), 'unit': unit_item.text() if unit_item else "", 'step': float(step_item.text()) if step_item and step_item.text() else 0, - 'step_type': step_combo.currentText() if step_combo else "абс" + 'step_type': step_combo.currentText() if step_combo else "ед." } factors.append(factor) except (ValueError, AttributeError) as e: @@ -750,7 +738,7 @@ class MainWindow(QMainWindow): try: project = ProjectData.load_from_file(filename) self._apply_project_data(project) - QMessageBox.information(self, "Успех", f"Проект загружен из {filename}") +# QMessageBox.information(self, "Успех", f"Проект загружен из {filename}") except FileNotFoundError: QMessageBox.critical(self, "Ошибка", f"Файл не найден: {filename}") except Exception as e: @@ -788,7 +776,7 @@ class MainWindow(QMainWindow): 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) + unit_item = self.factors_table.cellWidget(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) @@ -801,8 +789,8 @@ class MainWindow(QMainWindow): 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 "", + step_type=step_combo.currentText() if step_combo else "ед.", + unit=unit_item.currentText() 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 ) @@ -874,32 +862,13 @@ class MainWindow(QMainWindow): # Применяем данные факторов эксперимента 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")) - + percentage = str(factor.percentage) 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)) - + dilution = str(factor.dilution_factor) + self._add_factor_row(factor.name, percentage, dilution, str(factor.center), + str(factor.step), factor.step_type, str(factor.high), + str(factor.low), factor.unit) # Применяем настройки эксперимента self.center_points_spin.setValue(project.experiment_center_points) self.randomize_check.setChecked(project.experiment_randomize) @@ -916,9 +885,9 @@ class MainWindow(QMainWindow): if i < self.results_table.rowCount() and row_results: self.results_table.setItem(i, 1, QTableWidgetItem(str(row_results[0]))) - # Переключаемся на вкладку с факторами + # Переключаемся на вкладку с экспериментом if self.tab_widget: - self.tab_widget.setCurrentIndex(1) + self.tab_widget.setCurrentIndex(0) def _refresh_design_matrix(self): """Обновляет отображение матрицы планирования""" diff --git a/theme.py b/theme.py index 5a1426c..cd35b02 100644 --- a/theme.py +++ b/theme.py @@ -12,8 +12,8 @@ class Colors: """Цветовая палитра приложения""" # Основные цвета (Primary) - PRIMARY = "#3498db" # Синий - основной акцент - PRIMARY_DARK = "#2980b9" # Тёмно-синий (наведение) + PRIMARY = "#999999" # Синий - основной акцент + PRIMARY_DARK = "#777777" # Тёмно-синий (наведение) PRIMARY_LIGHT = "#5dade2" # Светло-синий PRIMARY_BG = "#ebf5fb" # Фоновый для primary элементов @@ -216,10 +216,10 @@ class ButtonStyles: {ButtonStyles._base_style()} }} QPushButton:hover {{ - background-color: {Colors.PRIMARY_DARK}; + background-color: {Colors.PRIMARY_LIGHT}; }} QPushButton:pressed {{ - background-color: {Colors.PRIMARY_LIGHT}; + background-color: {Colors.PRIMARY_DARK}; }} QPushButton:disabled {{ background-color: {Colors.GRAY_400}; @@ -326,7 +326,14 @@ class InputStyles: @staticmethod def default(): return f""" - QLineEdit, QDoubleSpinBox, QSpinBox, QComboBox {{ + QComboBox {{ + border: 1px solid {Colors.BORDER_DEFAULT}; + border-radius: {Spacing.BORDER_RADIUS_SM}px; + padding: 1px; + background-color: {Colors.WHITE}; + min-height: {Spacing.INPUT_HEIGHT - 16}px; + }} + QLineEdit, QDoubleSpinBox, QSpinBox {{ border: 1px solid {Colors.BORDER_DEFAULT}; border-radius: {Spacing.BORDER_RADIUS_SM}px; padding: {Spacing.SM}px; @@ -342,7 +349,6 @@ class InputStyles: color: {Colors.GRAY_600}; }} """ - @staticmethod def error(): return f"""