chore: создан класс данных Settings, скорректирован обмен данными в медиаторе
This commit is contained in:
parent
57be163907
commit
4dfd9a5a8f
@ -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",
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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):
|
||||||
@ -34,52 +80,3 @@ class DirectoryMonitor(BaseDirectoryMonitor):
|
|||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
BaseFileManager,
|
||||||
|
BaseDataConverter,
|
||||||
|
BasePlotWidget,
|
||||||
BasePointPassportFormer,
|
BasePointPassportFormer,
|
||||||
BaseController)
|
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...")
|
||||||
|
|||||||
@ -6,7 +6,169 @@ 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):
|
||||||
@ -79,166 +241,3 @@ class IdealDataBuilder(BaseIdealDataBuilder):
|
|||||||
"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
|
|
||||||
@ -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()
|
||||||
|
|||||||
@ -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)
|
||||||
@ -291,18 +314,16 @@ class PlotWidget(BasePlotWidget):
|
|||||||
curve_items = {"real":{}, "ideal":{}}
|
curve_items = {"real":{}, "ideal":{}}
|
||||||
qt_items = {}
|
qt_items = {}
|
||||||
|
|
||||||
dataframe, points_pocket, useful_data = data
|
dataframe = graphic_passport.dataframe
|
||||||
tesla_time = useful_data["tesla_time"]
|
tesla_time = graphic_passport.useful_data["tesla_time"]
|
||||||
range_ME = useful_data["range_ME"]
|
range_ME = graphic_passport.useful_data["range_ME"]
|
||||||
k_hardness = useful_data["k_hardness"]
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user