chore: создан класс данных Settings, скорректирован обмен данными в медиаторе

This commit is contained in:
Andrew 2025-01-23 18:13:44 +03:00
parent 57be163907
commit 4dfd9a5a8f
8 changed files with 315 additions and 309 deletions

View File

@ -27,6 +27,12 @@ class GraphicPassport:
useful_data: dict useful_data: dict
@dataclass
class Settings:
operator: dict
system: dict
class BaseMediator: class BaseMediator:
def __init__(self, def __init__(self,
converter: BaseDataConverter, converter: BaseDataConverter,
@ -118,9 +124,11 @@ class BaseDataConverter:
class BasePlotWidget: class BasePlotWidget:
def __init__(self, def __init__(self,
mediator: Optional[BaseMediator] = None): mediator: Optional[BaseMediator] = None,
controller: BaseController = None):
super().__init__() super().__init__()
self._mediator = mediator self._mediator = mediator
self._controller = controller
self._stage_colors = { self._stage_colors = {
"Closing": [220, 20, 60, 100], # Crimson "Closing": [220, 20, 60, 100], # Crimson
@ -242,6 +250,10 @@ class BasePlotWidget:
font-family: "Segoe UI", sans-serif; font-family: "Segoe UI", sans-serif;
}""") }""")
@property
def controller(self) -> BaseController:
return self._controller
@property @property
def mediator(self) -> BaseMediator: def mediator(self) -> BaseMediator:
return self._mediator return self._mediator
@ -348,11 +360,10 @@ class BaseFileManager:
class BaseIdealDataBuilder(OptAlgorithm): class BaseIdealDataBuilder(OptAlgorithm):
def __init__(self, params: list[dict]): def __init__(self, settings: Settings):
operator_params, system_params = params self.mul = settings.system['time_capture']
self.mul = system_params['time_capture'] self.welding_time = settings.operator['time_wielding']
self.welding_time = operator_params['time_wielding'] super().__init__(settings.system, settings.operator)
super().__init__(operator_params, system_params)
def get_closingDF(self) -> pd.DataFrame: def get_closingDF(self) -> pd.DataFrame:
... ...
@ -405,6 +416,7 @@ class BasePointPassportFormer:
mediator: Optional[BaseMediator] = None): mediator: Optional[BaseMediator] = None):
self._mediator = mediator self._mediator = mediator
self._clear_stage = "Welding" self._clear_stage = "Welding"
self._settings = Settings
self._stages = [ self._stages = [
"Closing", "Closing",
"Squeeze", "Squeeze",
@ -418,7 +430,6 @@ class BasePointPassportFormer:
"Tesla welding", "Tesla welding",
"Tesla oncomming_relief" "Tesla oncomming_relief"
] ]
self._params = []
self._ideal_data_cashe = LRUCache(maxsize=1000) self._ideal_data_cashe = LRUCache(maxsize=1000)
self._OptAlgorithm_operator_params = [ self._OptAlgorithm_operator_params = [
"dist_open_start_1", "dist_open_start_1",

View File

@ -2,7 +2,7 @@ from PyQt5.QtWidgets import QWidget, QTabWidget
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSignal
from base.base import BaseController from base.base import BaseController, Settings
class Controller(BaseController): class Controller(BaseController):
@ -20,8 +20,8 @@ class Controller(BaseController):
def send_widgets(self, widgets: list[QWidget]) -> None: def send_widgets(self, widgets: list[QWidget]) -> None:
self.signal_widgets.emit(widgets) self.signal_widgets.emit(widgets)
def update_settings(self, settings: list[dict]) -> None: def update_settings(self, settings: Settings) -> None:
self.mediator.notify(self, settings) self._mediator.notify(self, settings)
def update_status(self, msg:str) -> None: def update_status(self, msg:str) -> None:
self.signal_status_text.emit(msg) self.signal_status_text.emit(msg)

View File

@ -2,7 +2,53 @@ import os
from loguru import logger from loguru import logger
from base.base import BaseDirectoryMonitor, BaseFileManager from base.base import BaseDirectoryMonitor, BaseFileManager, Settings
class FileManager(BaseFileManager):
def replot_all(self) -> None:
if self._paths_library is not None:
self._mediator.notify(self, list(self._paths_library))
def open_custom_file(self, path:str) -> None:
self._paths_library.add(path)
self._mediator.notify(self, [path if path else ''])
def set_mode(self, num:int) -> None:
match num:
case 1: # Режим создания отчета
self._monitor.stop()
self._paths_library.clear()
self._paths_library.add('')
print(self._paths_library)
self._mediator.notify(self, list(self._paths_library))
case 2: # Режим онлайн-мониторинга папки
self._monitor.init_state()
self._monitor.start()
def update_monitor_settings(self, settings:Settings) -> None:
directory_path = settings.system['trace_storage_path'][0]
update_time = settings.system['monitor_update_period'][0]
if not os.path.exists(directory_path):
logger.error(f"Путь {directory_path} не существует.")
#raise FileNotFoundError(f"Путь {directory_path} не существует.")
if update_time <= 0.01:
logger.error(f"Путь {directory_path} не существует.")
if self._monitor.isActive: self._monitor.pause()
self._monitor._directory_path = directory_path
self._monitor._update_time = update_time
if self._monitor.isActive: self._monitor.start()
def add_new_paths(self, paths):
paths_set = set(paths)
new = self._paths_library.difference(paths_set)
self._paths_library.update(new)
self._mediator.notify(list(new))
class DirectoryMonitor(BaseDirectoryMonitor): class DirectoryMonitor(BaseDirectoryMonitor):
@ -33,53 +79,4 @@ class DirectoryMonitor(BaseDirectoryMonitor):
self._file_manager.add_new_paths(new_files) self._file_manager.add_new_paths(new_files)
self._files = current_files self._files = current_files
if not current_files: if not current_files:
self._files = [] self._files = []
class FileManager(BaseFileManager):
def replot_all(self) -> None:
if self._paths_library is not None:
self._mediator.notify(self, list(self._paths_library))
def open_custom_file(self, path:str) -> None:
self._paths_library.add(path)
self._mediator.notify(self, [path if path else ''])
def set_mode(self, num:int) -> None:
match num:
case 1: # Режим создания отчета
self._monitor.stop()
self._paths_library.clear()
self._paths_library.add('')
print(self._paths_library)
self._mediator.notify(self, list(self._paths_library))
case 2: # Режим онлайн-мониторинга папки
self._monitor.init_state()
self._monitor.start()
def update_monitor_settings(self, settings:list[dict]) -> None:
_, system_params = settings
directory_path = system_params['trace_storage_path'][0]
update_time = system_params['monitor_update_period'][0]
if not os.path.exists(directory_path):
logger.error(f"Путь {directory_path} не существует.")
#raise FileNotFoundError(f"Путь {directory_path} не существует.")
if update_time <= 0.01:
logger.error(f"Путь {directory_path} не существует.")
if self._monitor.isActive: self._monitor.pause()
self._monitor._directory_path = directory_path
self._monitor._update_time = update_time
if self._monitor.isActive: self._monitor.start()
def add_new_paths(self, paths):
paths_set = set(paths)
new = self._paths_library.difference(paths_set)
self._paths_library.update(new)
self._mediator.notify(list(new))

View File

@ -3,17 +3,23 @@ from typing import Union
import pandas as pd import pandas as pd
from PyQt5.QtWidgets import QWidget from PyQt5.QtWidgets import QWidget
from base.base import (BaseMediator, BaseFileManager, from base.base import (
BaseDataConverter, BasePlotWidget, BaseMediator,
BasePointPassportFormer, BaseFileManager,
BaseController) BaseDataConverter,
BasePlotWidget,
BasePointPassportFormer,
BaseController,
GraphicPassport,
Settings
)
class Mediator(BaseMediator): class Mediator(BaseMediator):
def notify(self, def notify(self,
source: Union[BaseFileManager, BaseDataConverter, BasePointPassportFormer, BasePlotWidget, BaseController], source: Union[BaseFileManager, BaseDataConverter, BasePointPassportFormer, BasePlotWidget, BaseController],
data: Union[list[str], list[pd.DataFrame], list[list], list[QWidget], list[dict]]): data: Union[list[str], list[pd.DataFrame], list[GraphicPassport], list[QWidget], Settings]):
if issubclass(source.__class__, BaseFileManager): if issubclass(source.__class__, BaseFileManager):
self._controller.update_status("CSV found! Calculating...") self._controller.update_status("CSV found! Calculating...")

View File

@ -6,8 +6,170 @@ import numpy as np
import pandas as pd import pandas as pd
from loguru import logger from loguru import logger
from base.base import BasePointPassportFormer, BaseIdealDataBuilder, PointPassport, GraphicPassport from base.base import BasePointPassportFormer, BaseIdealDataBuilder, PointPassport, GraphicPassport, Settings
class PassportFormer(BasePointPassportFormer):
def form_passports(self, data: list[pd.DataFrame]) -> list[GraphicPassport]:
try:
return_data = [self._build_graphic_passport(dataframe) for dataframe in data]
except:
# TODO: обработка исключений!!!
tb = sys.exc_info()[2]
# TODO: Нормальные сообщения в лог!
tbinfo = traceback.format_tb(tb)[0]
pymsg = "Traceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
logger.error(pymsg)
return_data = []
finally:
self._mediator.notify(self, return_data)
def update_settings(self, settings: Settings):
self._settings = settings
@staticmethod
def _find_indexes(signal: str,
dataframe: pd.DataFrame) -> tuple[np.ndarray, np.ndarray]:
stage_diff = np.diff(dataframe[signal])
start_idx = np.where(stage_diff == 1)
finish_idx = np.where(stage_diff == -1)
return start_idx[0], finish_idx[0]
def _find_events(self,
signal: str,
times:pd.Series,
dataframe: pd.DataFrame) -> tuple[list[float], list[float]]:
start_idx, finish_idx = self._find_indexes(signal, dataframe)
if len(start_idx) > 0 and len(finish_idx) > 0 and start_idx[0] > finish_idx[0]:
start_idx = np.insert(start_idx, 0, 0)
start_list = times.iloc[start_idx].tolist() if len(start_idx) > 0 else []
end_list = times.iloc[finish_idx].tolist() if len(finish_idx) > 0 else []
if len(start_list) - len(end_list) == 1:
end_list.append(float(times.iloc[-1]))
return start_list, end_list
def _filter_events(self,
times: pd.Series,
dataframe: pd.DataFrame) -> tuple[dict[str, list[list[float]]], int]:
events = {}
point_quantity = 0
if self._clear_stage in self._stages:
start_list, end_list = self._find_events(self._clear_stage, times, dataframe)
point_quantity = len(start_list)
if point_quantity == 0:
#TODO: добавить обработку исключения
return []
for stage in self._stages:
start_list, end_list = self._find_events(stage, times, dataframe)
temp = min([len(start_list), len(end_list)])
if temp < point_quantity:
print ("cant find enough", stage)
start_list += [0]*(point_quantity - temp)
end_list += [1]*(point_quantity - temp)
events[stage] = [start_list, end_list]
return events, point_quantity
def _build_ideal_data(self,
idealDataBuilder: Optional[BaseIdealDataBuilder] = None,
point_settings: Settings = None) -> dict:
self.opt_algorithm = idealDataBuilder(point_settings)
stage_ideals = {
"Closing": self._opt_algorithm.get_closingDF(),
"Squeeze": self._opt_algorithm.get_compressionDF(),
"Welding": self._opt_algorithm.get_weldingDF(),
"Relief": self._opt_algorithm.get_openingDF(),
"Oncomming": self._opt_algorithm.get_oncomingDF(),
"Ideal cycle": self._opt_algorithm.get_cycle_time(),
"Ideal timings": self._opt_algorithm.get_ideal_timings()
}
return stage_ideals
def _generate_cache_key(self,
point_settings:Settings) -> tuple[tuple[tuple[str, Any], ...], tuple[tuple[str, Any], ...]]:
"""
Преобразует point_settings в хешируемый ключ для кэша.
"""
# Преобразуем словари в отсортированные кортежи пар ключ-значение
operator_tuple = frozenset((key, value)
for key, value in point_settings.operator.items()
if str(key) in self._OptAlgorithm_operator_params)
system_tuple = frozenset((key, value)
for key, value in point_settings.system.items()
if str(key) in self._OptAlgorithm_system_params)
return (operator_tuple, system_tuple)
def _build_graphic_passport(self, dataframe: pd.DataFrame) -> GraphicPassport:
if dataframe is not None:
events, point_quantity = self._filter_events(dataframe["time"], dataframe)
if point_quantity == 0:
return []
else:
events = None
key = list(self._settings.operator.keys())[0]
point_quantity = len(self._settings.operator[key])
graphic_passport = GraphicPassport()
graphic_passport.dataframe = dataframe
graphic_passport.points_pocket = []
system_settings = {key: value[0] for key, value in self._settings.system.items()}
graphic_passport.useful_data = self._form_graphic_useful_data(system_settings)
for i in range(point_quantity):
point_settings = Settings()
point_settings.operator = self._get_operator_settings_part(i)
point_settings.system = system_settings
point_passport = PointPassport()
point_passport.ideal_data = self._form_point_ideal_data(point_settings)
point_passport.useful_data = self._form_point_useful_data(point_settings.operator)
point_passport.timeframe, point_passport.events = self._form_point_events(events, i)
graphic_passport.points_pocket.append(point_passport)
return graphic_passport
def _form_graphic_useful_data(self, system_settings:dict) -> dict:
tesla_time = sum(self._settings.operator.get("Tesla summary time", []))
useful_data = {"tesla_time": tesla_time,
"range_ME": system_settings["Range ME, mm"],
"k_hardness": system_settings["k_hardness_1"]
}
return useful_data
def _form_point_useful_data(self, operator_settings:dict) -> dict:
useful_data = {"thickness": operator_settings["object_thickness"],
"L2": operator_settings["distance_l_2"],
"force": operator_settings["force_target"]}
return useful_data
def _form_point_ideal_data(self, point_settings:Settings) -> dict:
cache_key = self._generate_cache_key(point_settings)
ideal_data = self._ideal_data_cashe.get(cache_key,
self._build_ideal_data(idealDataBuilder=IdealDataBuilder, params=point_settings))
self._ideal_data_cashe[cache_key] = ideal_data
return ideal_data
def _get_operator_settings_part(self, idx:int) -> dict:
operator_settings = {
key: (value[idx] if idx < len(value) else value[0])
for key, value in self._settings.operator.items()
}
return operator_settings
def _form_point_events(self, events:dict, idx) -> list:
timeframe, point_events = None, None
if events is not None:
idx_shift = idx+1 if events[self._stages[-1]][0][0] == 0 else idx
timeframe = [events[self._stages[0]][0][idx], events[self._stages[-1]][1][idx_shift]]
point_events = {key: [value[0][idx], value[1][idx]] for key, value in events.items()}
return timeframe, point_events
class IdealDataBuilder(BaseIdealDataBuilder): class IdealDataBuilder(BaseIdealDataBuilder):
@ -78,167 +240,4 @@ class IdealDataBuilder(BaseIdealDataBuilder):
"Rotor Speed ME":V2*1000, "Rotor Speed ME":V2*1000,
"Force":F "Force":F
}) })
return pd.DataFrame(data) return pd.DataFrame(data)
class PassportFormer(BasePointPassportFormer):
def form_passports(self, data: list[pd.DataFrame]) -> list[GraphicPassport]:
try:
return_data = [self._build_graphic_passport(dataframe) for dataframe in data]
except:
# TODO: обработка исключений!!!
tb = sys.exc_info()[2]
# TODO: Нормальные сообщения в лог!
tbinfo = traceback.format_tb(tb)[0]
pymsg = "Traceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
logger.error(pymsg)
return_data = []
finally:
self._mediator.notify(self, return_data)
def update_settings(self, params: list[dict, dict]):
self._params = params
@staticmethod
def _find_indexes(signal: str,
dataframe: pd.DataFrame) -> tuple[np.ndarray, np.ndarray]:
stage_diff = np.diff(dataframe[signal])
start_idx = np.where(stage_diff == 1)
finish_idx = np.where(stage_diff == -1)
return start_idx[0], finish_idx[0]
def _find_events(self,
signal: str,
times:pd.Series,
dataframe: pd.DataFrame) -> tuple[list[float], list[float]]:
start_idx, finish_idx = self._find_indexes(signal, dataframe)
if len(start_idx) > 0 and len(finish_idx) > 0 and start_idx[0] > finish_idx[0]:
start_idx = np.insert(start_idx, 0, 0)
start_list = times.iloc[start_idx].tolist() if len(start_idx) > 0 else []
end_list = times.iloc[finish_idx].tolist() if len(finish_idx) > 0 else []
if len(start_list) - len(end_list) == 1:
end_list.append(float(times.iloc[-1]))
return start_list, end_list
def _filter_events(self,
times: pd.Series,
dataframe: pd.DataFrame) -> tuple[dict[str, list[list[float]]], int]:
events = {}
point_quantity = 0
if self._clear_stage in self._stages:
start_list, end_list = self._find_events(self._clear_stage, times, dataframe)
point_quantity = len(start_list)
if point_quantity == 0:
#TODO: добавить обработку исключения
return []
for stage in self._stages:
start_list, end_list = self._find_events(stage, times, dataframe)
temp = min([len(start_list), len(end_list)])
if temp < point_quantity:
print ("cant find enough", stage)
start_list += [0]*(point_quantity - temp)
end_list += [1]*(point_quantity - temp)
events[stage] = [start_list, end_list]
return events, point_quantity
def _build_ideal_data(self,
idealDataBuilder: Optional[BaseIdealDataBuilder] = None,
params: list[dict] = None) -> dict:
self.opt_algorithm = idealDataBuilder(params)
stage_ideals = {
"Closing": self._opt_algorithm.get_closingDF(),
"Squeeze": self._opt_algorithm.get_compressionDF(),
"Welding": self._opt_algorithm.get_weldingDF(),
"Relief": self._opt_algorithm.get_openingDF(),
"Oncomming": self._opt_algorithm.get_oncomingDF(),
"Ideal cycle": self._opt_algorithm.get_cycle_time(),
"Ideal timings": self._opt_algorithm.get_ideal_timings()
}
return stage_ideals
def _generate_cache_key(self,
params_list: list[dict[str, Any]]) -> tuple[tuple[tuple[str, Any], ...], tuple[tuple[str, Any], ...]]:
"""
Преобразует params_list в хешируемый ключ для кэша.
"""
operator_settings, system_settings = params_list
# Преобразуем словари в отсортированные кортежи пар ключ-значение
operator_tuple = frozenset((key, value)
for key, value in operator_settings.items()
if str(key) in self._OptAlgorithm_operator_params)
system_tuple = frozenset((key, value)
for key, value in system_settings.items()
if str(key) in self._OptAlgorithm_system_params)
return (operator_tuple, system_tuple)
def _build_graphic_passport(self, dataframe: pd.DataFrame) -> GraphicPassport:
if dataframe is not None:
events, point_quantity = self._filter_events(dataframe["time"], dataframe)
if point_quantity == 0:
return []
else:
events = None
key = list(self._params[0].keys())[0]
point_quantity = len(self._params[0][key])
graphic_passport = GraphicPassport()
graphic_passport.dataframe = dataframe
graphic_passport.points_pocket = []
system_settings = {key: value[0] for key, value in self._params[1].items()}
graphic_passport.useful_data = self._form_graphic_useful_data(system_settings)
for i in range(point_quantity):
operator_settings = self._get_operator_settings_part(i)
params_list = [operator_settings, system_settings]
point_passport = PointPassport()
point_passport.ideal_data = self._form_point_ideal_data(params_list)
point_passport.useful_data = self._form_point_useful_data(operator_settings)
point_passport.timeframe, point_passport.events = self._form_point_events(events, i)
graphic_passport.points_pocket.append(point_passport)
return graphic_passport
def _form_graphic_useful_data(self, system_settings:dict) -> dict:
tesla_time = sum(self._params[0].get("Tesla summary time", []))
useful_data = {"tesla_time": tesla_time,
"range_ME": system_settings["Range ME, mm"],
"k_hardness": system_settings["k_hardness_1"]
}
return useful_data
def _form_point_useful_data(self, operator_settings:dict) -> dict:
useful_data = {"thickness": operator_settings["object_thickness"],
"L2": operator_settings["distance_l_2"],
"force": operator_settings["force_target"]}
return useful_data
def _form_point_ideal_data(self, params_list:list) -> dict:
cache_key = self._generate_cache_key(params_list)
ideal_data = self._ideal_data_cashe.get(cache_key,
self._build_ideal_data(idealDataBuilder=IdealDataBuilder, params=params_list))
self._ideal_data_cashe[cache_key] = ideal_data
return ideal_data
def _get_operator_settings_part(self, idx:int) -> dict:
operator_settings = {
key: (value[idx] if idx < len(value) else value[0])
for key, value in self._params[0].items()
}
return operator_settings
def _form_point_events(self, events:dict, idx) -> list:
timeframe, point_events = None, None
if events is not None:
idx_shift = idx+1 if events[self._stages[-1]][0][0] == 0 else idx
timeframe = [events[self._stages[0]][0][idx], events[self._stages[-1]][1][idx_shift]]
point_events = {key: [value[0][idx], value[1][idx]] for key, value in events.items()}
return timeframe, point_events

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtCore import Qt, pyqtSignal
from base.base import BaseMainWindow from base.base import BaseMainWindow, Settings
from gui.start_widget import (CustomMenuBar, CustomStatusBar, from gui.start_widget import (CustomMenuBar, CustomStatusBar,
StartWidget, CustomTabWidget, StartWidget, CustomTabWidget,
RaportWidget, SeekingWidget) RaportWidget, SeekingWidget)
@ -13,7 +13,7 @@ from gui.report_gui import ReportSettings
class MainWindow(BaseMainWindow): class MainWindow(BaseMainWindow):
signal_mode = pyqtSignal(int) signal_mode = pyqtSignal(int)
signal_settings = pyqtSignal(list) signal_settings = pyqtSignal(Settings)
signal_replot_all = pyqtSignal() signal_replot_all = pyqtSignal()
signal_open_file = pyqtSignal(str) signal_open_file = pyqtSignal(str)
signal_save_file = pyqtSignal(list) signal_save_file = pyqtSignal(list)
@ -22,6 +22,30 @@ class MainWindow(BaseMainWindow):
super().__init__() super().__init__()
self._init_startUI() self._init_startUI()
def show_plot_tabs(self, plot_widgets: list[QtWidgets.QWidget]) -> None:
for plot_widget in plot_widgets:
self._tab_widget.create_tab(plot_widget)
tab_count = self._tab_widget.count()
if tab_count > 10:
for i in range(0, tab_count-10):
self._tab_widget.close_tab(i)
self.status_widget.set_note("Done!")
self.status_widget.set_progress(100)
def keyPressEvent(self, a0) -> None:
if a0.key() == Qt.Key_F5:
tab_count = self._tab_widget.count()
for i in range(0, tab_count):
self._tab_widget.close_tab(i)
def closeEvent(self, a0):
self.operSettings.close()
self.sysSettings.close()
self.repSettings.close()
super().closeEvent(a0)
def _init_startUI(self) -> None: def _init_startUI(self) -> None:
self._init_settings() self._init_settings()
self._init_menu() self._init_menu()
@ -91,34 +115,11 @@ class MainWindow(BaseMainWindow):
self.status_widget.set_mode("online mode") self.status_widget.set_mode("online mode")
self.signal_mode.emit(2) self.signal_mode.emit(2)
def show_plot_tabs(self, plot_widgets: list[QtWidgets.QWidget]) -> None:
for plot_widget in plot_widgets:
self._tab_widget.create_tab(plot_widget)
tab_count = self._tab_widget.count()
if tab_count > 10:
for i in range(0, tab_count-10):
self._tab_widget.close_tab(i)
self.status_widget.set_note("Done!")
self.status_widget.set_progress(100)
def keyPressEvent(self, a0) -> None:
if a0.key() == Qt.Key_F5:
tab_count = self._tab_widget.count()
for i in range(0, tab_count):
self._tab_widget.close_tab(i)
def closeEvent(self, a0):
self.operSettings.close()
self.sysSettings.close()
self.repSettings.close()
super().closeEvent(a0)
def _transfer_settings(self) -> None: def _transfer_settings(self) -> None:
operator_params = self.operSettings.getParams() settings = Settings()
system_params = self.sysSettings.getParams() settings.operator = self.operSettings.getParams()
self.signal_settings.emit([operator_params, system_params]) settings.system = self.sysSettings.getParams()
self.signal_settings.emit(Settings)
def _upd_settings(self) -> None: def _upd_settings(self) -> None:
self._transfer_settings() self._transfer_settings()

View File

@ -3,20 +3,46 @@ import traceback
import sys import sys
from typing import Optional, Any from typing import Optional, Any
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, from PyQt5.QtWidgets import (
QHBoxLayout, QLabel, QWidget,
QGraphicsRectItem, QSpacerItem, QVBoxLayout,
QSizePolicy) QHBoxLayout,
QLabel,
QGraphicsRectItem,
QSpacerItem,
QSizePolicy
)
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from loguru import logger from loguru import logger
import pyqtgraph as pg import pyqtgraph as pg
import pandas as pd import pandas as pd
from base.base import BasePlotWidget from base.base import BasePlotWidget, GraphicPassport
class PlotWidget(BasePlotWidget): class PlotWidget(BasePlotWidget):
def build(self, data: list[GraphicPassport]) -> None:
"""
Создает набор виджетов по предоставленному списку данных.
"""
try:
# TODO: Про инициализацию атрибутов класса где-то уже писал. На всякий случай: она делается в конструкторе.
self._datalen = len(data)
widgets_datapack = [self._build_widget(data_sample) for self._datastep, data_sample in enumerate(data)]
except:
# TODO: Добавить конкретные исключения.
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "Traceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
# TODO: Логи должны быть понятны обычному пользователю. Traceback что-то там - это не информативно и может
# быть непонятно, поэтому пишем осознанные сообщения. Если надо, то создаем собственные
# классы исключений. (Касается всего проекта - уже не первый раз натыкаюсь на это.)
logger.error(pymsg)
widgets_datapack = [QLabel(pymsg)]
finally:
self._mediator.notify(self, widgets_datapack)
def _downsample_data(self, x, y, max_points=5000): def _downsample_data(self, x, y, max_points=5000):
# TODO: Данный метод статичный. Для обозначения подобных методов используется декоратор @staticmethod. # TODO: Данный метод статичный. Для обозначения подобных методов используется декоратор @staticmethod.
# TODO: Какой тип данных у 'x'? Какой тип данных у 'y'? Надо добавить аннотации типов. # TODO: Какой тип данных у 'x'? Какой тип данных у 'y'? Надо добавить аннотации типов.
@ -273,16 +299,13 @@ class PlotWidget(BasePlotWidget):
qt_items["real to ideal performance"] = kdip_label qt_items["real to ideal performance"] = kdip_label
def _build_widget(self, data: list[Any]) -> QWidget: def _build_widget(self, graphic_passport: GraphicPassport) -> QWidget:
# TODO: Либо передавать в метод 3 аргумента с аннотацией типа каждого, либо передавать
# NamedTuple / dataclass.
# TODO: Исходя из сигнатуры метода, он создает и возвращает виджет с графиками. # TODO: Исходя из сигнатуры метода, он создает и возвращает виджет с графиками.
# Простыня "result_widget, reg_items, curve_items, qt_items" не похожа на виджет с графиком. # Простыня "result_widget, reg_items, curve_items, qt_items" не похожа на виджет с графиком.
# TODO: Данный метод должен содержать только ту логику, которая относится непосредственно к графикам. # TODO: Данный метод должен содержать только ту логику, которая относится непосредственно к графикам.
# Все остальное - вынести. Оставшийся метод декомпозировать - очень трудно вообще понять, что тут происходит. # Все остальное - вынести. Оставшийся метод декомпозировать - очень трудно вообще понять, что тут происходит.
""" """
Собирает графический виджет для одного набора данных. Собирает графический виджет для одного набора данных.
Параметр `data` предполагается списком: [dataframe, points_pocket, useful_data].
""" """
result_widget = QWidget() result_widget = QWidget()
result_layout = QVBoxLayout(result_widget) result_layout = QVBoxLayout(result_widget)
@ -290,19 +313,17 @@ class PlotWidget(BasePlotWidget):
reg_items = {"real":{}, "ideal":{}} reg_items = {"real":{}, "ideal":{}}
curve_items = {"real":{}, "ideal":{}} curve_items = {"real":{}, "ideal":{}}
qt_items = {} qt_items = {}
dataframe, points_pocket, useful_data = data
tesla_time = useful_data["tesla_time"]
range_ME = useful_data["range_ME"]
k_hardness = useful_data["k_hardness"]
dataframe = graphic_passport.dataframe
tesla_time = graphic_passport.useful_data["tesla_time"]
range_ME = graphic_passport.useful_data["range_ME"]
k_hardness = graphic_passport.useful_data["k_hardness"]
dat_is_none = dataframe is None dat_is_none = dataframe is None
widget_steps = len(self._plt_channels) widget_steps = len(self._plt_channels)
if not dat_is_none: if not dat_is_none:
dataframe_headers = dataframe.columns.tolist() dataframe_headers = dataframe.columns.tolist()
point_steps = len(points_pocket) point_steps = len(graphic_passport.points_pocket)
else: point_steps = 1 else: point_steps = 1
for widget_num, (channel, description) in enumerate(self._plt_channels.items()): for widget_num, (channel, description) in enumerate(self._plt_channels.items()):
@ -323,7 +344,7 @@ class PlotWidget(BasePlotWidget):
) )
# Итерация по точкам # Итерация по точкам
for cur_point, point_data in enumerate(points_pocket): for cur_point, point_data in enumerate(graphic_passport.points_pocket):
# point_data структура: [point_timeframe, ideal_data, point_events, useful_p_data] # point_data структура: [point_timeframe, ideal_data, point_events, useful_p_data]
point_timeframe, ideal_dat, point_events, useful_p_data = point_data point_timeframe, ideal_dat, point_events, useful_p_data = point_data
ideal_data = copy.deepcopy(ideal_dat) ideal_data = copy.deepcopy(ideal_dat)
@ -384,13 +405,13 @@ class PlotWidget(BasePlotWidget):
# Добавляем идеальные стадии и идеальные сигналы # Добавляем идеальные стадии и идеальные сигналы
if settings["ideals"]: if settings["ideals"]:
is_last_point = (cur_point == len(points_pocket) - 1) is_last_point = (cur_point == len(graphic_passport.points_pocket) - 1)
self._add_ideal_stage_regions(plot_item, ideal_data, point_events, reg_items, 100) self._add_ideal_stage_regions(plot_item, ideal_data, point_events, reg_items, 100)
self._add_ideal_signals(plot_item, legend, ideal_data, point_events, description["Ideal_signals"], curve_items, is_last_point) self._add_ideal_signals(plot_item, legend, ideal_data, point_events, description["Ideal_signals"], curve_items, is_last_point)
# Подсчёт производительности # Подсчёт производительности
if settings["performance"]: if settings["performance"]:
is_last_point = (cur_point == len(points_pocket) - 1) is_last_point = (cur_point == len(graphic_passport.points_pocket) - 1)
if is_last_point: if is_last_point:
if not dat_is_none: TWC_delta = sum([point_events[stage][1] - point_events[stage][0] if not dat_is_none: TWC_delta = sum([point_events[stage][1] - point_events[stage][0]
for stage in ["Closing", "Squeeze", "Welding"]]) for stage in ["Closing", "Squeeze", "Welding"]])
@ -436,34 +457,6 @@ class PlotWidget(BasePlotWidget):
result_layout.addWidget(plot_layout) result_layout.addWidget(plot_layout)
return result_widget, reg_items, curve_items, qt_items return result_widget, reg_items, curve_items, qt_items
def build(self, data: list[list[Any]]) -> None:
"""
Создает набор виджетов по предоставленному списку данных.
Предполагается, что data это список элементов вида:
[
[dataframe, points_pocket, useful_data],
[dataframe, points_pocket, useful_data],
...
]
"""
# TODO: Про входные аргументы см. выше.
try:
# TODO: Про инициализацию атрибутов класса где-то уже писал. На всякий случай: она делается в конструкторе.
self._datalen = len(data)
widgets_datapack = [self._build_widget(data_sample) for self._datastep, data_sample in enumerate(data)]
except:
# TODO: Добавить конкретные исключения.
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "Traceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
# TODO: Логи должны быть понятны обычному пользователю. Traceback что-то там - это не информативно и может
# быть непонятно, поэтому пишем осознанные сообщения. Если надо, то создаем собственные
# классы исключений. (Касается всего проекта - уже не первый раз натыкаюсь на это.)
logger.error(pymsg)
widgets_datapack = [QLabel(pymsg)]
finally:
self._mediator.notify(self, widgets_datapack)
def _update_status(self, widgsteps:int, pointsteps:int, cur_widg:int, cur_point:int): def _update_status(self, widgsteps:int, pointsteps:int, cur_widg:int, cur_point:int):
if self._datalen: if self._datalen:
sycle_start = self._datastep/self._datalen*100 + 1 sycle_start = self._datastep/self._datalen*100 + 1
@ -477,7 +470,7 @@ class PlotWidget(BasePlotWidget):
progress = sycle_start + period2*cur_widg + period3*cur_point progress = sycle_start + period2*cur_widg + period3*cur_point
# TODO: см. модуль mediator.py # TODO: см. модуль mediator.py
self._mediator.update_status(progress) self._controller.update_progress(progress)

View File

@ -11,7 +11,6 @@ from gui.plotter import PlotWidget
from controller.controller import Controller from controller.controller import Controller
from src.controller.passport_former import PassportFormer from src.controller.passport_former import PassportFormer
# TODO: Именование модулей: lowercase / snake_case.
def main(): def main():
pg.setConfigOptions(useOpenGL=False, antialias=False) pg.setConfigOptions(useOpenGL=False, antialias=False)