dev: добавлена обработка ошибок записи трейсов

This commit is contained in:
Andrew 2025-02-13 14:10:04 +03:00
parent 78d34343d5
commit 47248ac6a7
7 changed files with 14152 additions and 198 deletions

View File

@ -222,85 +222,5 @@
500.0,
500.0,
500.0
],
"Tesla closing": [
0.216,
0.228,
0.252,
0.216,
0.228,
0.216,
0.228,
0.228,
0.228,
0.216,
0.228,
0.216,
0.216,
0.216
],
"Tesla squeeze": [
0.276,
0.288,
0.264,
0.264,
0.276,
0.276,
0.312,
0.276,
0.24,
0.24,
0.24,
0.24,
0.24,
0.24
],
"Tesla welding": [
1.332,
1.644,
1.644,
1.428,
1.284,
1.308,
1.272,
1.38,
1.416,
1.392,
1.38,
1.404,
1.452,
1.452
],
"Tesla oncomming_relief": [
0.516,
0.492,
0.636,
0.492,
0.42,
0.54,
0.444,
0.66,
0.521,
0.557,
0.51,
0.51,
0.534,
0.01
],
"Tesla summary time": [
2.748,
2.676,
2.652,
2.544,
2.28,
2.22,
2.352,
2.328,
2.676,
2.369,
2.405,
2.37,
2.418,
2.442
]
}

View File

@ -1,6 +1,6 @@
{
"trace_storage_path": [
"D:/downloads/a22"
"/home/andrew/weldingspotperformance/trace_samples"
],
"monitor_update_period": [
1000.0
@ -58,5 +58,17 @@
],
"Range ME, mm": [
115.0
],
"client_time": [
35.488
],
"performance_mode": [
"client"
],
"time_before_start": [
0.924
],
"time_after_end": [
1.14
]
}

View File

@ -63,6 +63,13 @@ class PointPassport:
useful_data: Dict = field(default_factory=dict)
@dataclass
class PerformanceData:
client_to_TWC: float = 0
client_to_ideal: float = 0
TWC_to_ideal: float = 0
@dataclass
class UsefulGraphData:
"""
@ -72,9 +79,10 @@ class UsefulGraphData:
:param range_ME: Диапазон для ME.
:param k_hardness: Коэффициент твердости.
"""
client_time: float = 0
performance: PerformanceData = PerformanceData()
range_ME: float = 0
k_hardness: float = 0
@dataclass
@ -471,6 +479,7 @@ class BaseIdealDataBuilder(OptAlgorithm):
def __init__(self, settings: Settings):
self.mul = settings.system['time_capture']
self.welding_time = settings.operator['time_wielding']
self._settings = settings
super().__init__(settings.system, settings.operator)
def get_closingDF(self) -> pd.DataFrame:

View File

@ -9,7 +9,8 @@ from base.base import (
PointPassport,
GraphicPassport,
Settings,
UsefulGraphData
UsefulGraphData,
PerformanceData
)
@ -115,12 +116,16 @@ class PassportFormer(BasePointPassportFormer):
:return: Кортеж из двух списков: (список времён начала, список времён окончания).
"""
start_idx, finish_idx = PassportFormer._find_indexes(signal, df)
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]))
start_list, end_list = [], []
if len(start_idx) > 0 and len(finish_idx) > 0:
if start_idx[0] > finish_idx[0]:
logger.debug(f"_find_events - не найдено начало события {signal} с окончанием в {times.iloc[finish_idx[0]]} секунд, принято равным 0")
start_idx = np.insert(start_idx, 0, 0)
if start_idx[-1] > finish_idx[-1]:
logger.debug(f"_find_events - не найден конец события {signal} с началом в {times.iloc[start_idx[-1]]} секунд, принято равным {times.iloc[-1]} секунд")
finish_idx = np.append(finish_idx, -1)
start_list = times.iloc[start_idx].tolist()
end_list = times.iloc[finish_idx].tolist()
return (start_list, end_list)
def _generate_events(self, times: pd.Series, df: pd.DataFrame) -> Tuple[Dict[str, List[List[float]]], int]:
@ -137,20 +142,25 @@ class PassportFormer(BasePointPassportFormer):
events = {}
point_quantity = 0
if self._clear_stage in self._stages:
start_list, end_list = self._find_events(self._clear_stage, times, df)
start_list, _ = self._find_events(self._clear_stage, times, df)
point_quantity = len(start_list)
if point_quantity == 0:
logger.error(f"_generate_events - Не найдены события для этапа '{self._clear_stage}'.")
return {}, 0
for stage in self._stages:
s_list, e_list = self._find_events(stage, times, df)
temp = min(len(s_list), len(e_list))
temp = max(len(s_list), len(e_list))
if temp < point_quantity:
logger.warning(f"_generate_events - Недостаточное количество событий для этапа '{stage}'. "
f"Ожидается {point_quantity}, получено {temp}. Заполнение нулями/единицами.")
s_list += [0] * (point_quantity - temp)
e_list += [1] * (point_quantity - temp)
f"Ожидается {point_quantity}, получено {temp}.")
events[stage] = [s_list, e_list]
if self._settings.system["performance_mode"][0] == 'client':
client_rob = self._settings.operator["time_robot_movement"]
move_start = events["Oncomming"][0]
if len(move_start) > len(client_rob):
move_start = move_start[1:]
events["Oncomming"] = [move_start, [client_rob[i]+ move_start[i] for i in range(len(move_start))]]
return (events, point_quantity)
def _build_ideal_data(self, idealDataBuilder: Optional[BaseIdealDataBuilder] = None,
@ -217,8 +227,7 @@ class PassportFormer(BasePointPassportFormer):
"""
try:
if df is not None and set(self._stages).issubset(set(df.columns.tolist())):
events, _ = self._generate_events(df["time"], df)
point_quantity = len(events["Welding"][0])
events, point_quantity = self._generate_events(df["time"], df)
if point_quantity == 0:
logger.error("_build_from_df_only - Не найдено ни одного события в DataFrame.")
return None
@ -250,11 +259,8 @@ class PassportFormer(BasePointPassportFormer):
"""
try:
system_settings = {key: value[0] for key, value in self._settings.system.items()}
graphic_passport = GraphicPassport(
df,
[],
self._form_graphic_useful_data(system_settings)
)
graphic_passport = GraphicPassport()
graphic_passport.dataframe = df
for i in range(point_quantity):
point_settings = Settings(self._get_operator_settings_part(i), system_settings)
@ -265,12 +271,13 @@ class PassportFormer(BasePointPassportFormer):
useful_data = self._form_point_useful_data(point_settings.operator)
point_passport = PointPassport(timeframe, po_events, ideal_data, useful_data)
graphic_passport.points_pocket.append(point_passport)
graphic_passport.useful_data = self._form_graphic_useful_data(system_settings, graphic_passport.points_pocket)
return graphic_passport
except Exception as e:
logger.error(f"_form_graphic_passport - Ошибка при формировании графического паспорта: {e}")
return None
def _form_graphic_useful_data(self, system_settings: Dict) -> UsefulGraphData:
def _form_graphic_useful_data(self, system_settings: Dict, points:List[PointPassport]) -> UsefulGraphData:
"""
Формирует словарь полезных данных для графического паспорта.
@ -278,9 +285,23 @@ class PassportFormer(BasePointPassportFormer):
:return: Объект UsefulGraphData.
"""
try:
tesla_time = sum(self._settings.operator.get("Tesla summary time", []))
performance_data = PerformanceData()
if points[0].timeframe is not None and points[0].events is not None:
client_time = system_settings["client_time"]
ideal_time = sum([sum(point.ideal_data["Ideal timings"])for point in points])
TWC_time = (
system_settings["time_before_start"] +
points[0].timeframe[1] - points[0].events["Closing"][0] +
sum([sum([item[1]-item[0] for key, item in point.events.items()]) for point in points[1:-1]]) +
points[-1].events["Relief"][1] - points[-1].timeframe[0] +
system_settings["time_after_end"]
)
performance_data.client_to_TWC = round((1 - TWC_time / client_time) * 100, 2) if client_time else 0.0
performance_data.client_to_ideal = round((1 - ideal_time / client_time) * 100, 2) if client_time else 0.0
performance_data.TWC_to_ideal = round((ideal_time / TWC_time) * 100, 2) if TWC_time else 0.0
useful_data = UsefulGraphData(
tesla_time,
performance_data,
system_settings["Range ME, mm"],
system_settings["k_hardness_1"]
)
@ -360,11 +381,27 @@ class PassportFormer(BasePointPassportFormer):
"""
try:
timeframe, point_events = None, None
start, end = 0, 0
if events is not None:
# Если первое событие основного этапа начинается с 0, сдвигаем индекс
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()}
start = events[self._stages[0]][0][idx]
end = 0
point_events = {}
for key, value in events.items():
if len(value[0]) > idx and value[0][idx] >= start:
point_events[key] = [value[0][idx], value[1][idx]]
if value[1][idx] > end: end = value[1][idx]
else:
logger.warning(f"_form_point_events - Обнаружен аномальный порядок событий для точки {idx}")
for i in range(len(value[0])):
if value[0][i] >= start:
logger.info(f"_form_point_events - Найдено событе (вхождение {i}), соответствующее временным рамкам точки")
point_events[key] = [value[0][i], value[1][i]]
if value[1][i] > end: end = value[1][i]
break
point_events[key] = [end, end+0.01]
end += 0.01
if end != 0: timeframe = [start, end]
return timeframe, point_events
except Exception as e:
logger.error(f"_form_point_events - Ошибка при формировании событий для точки {idx}: {e}")

View File

@ -16,27 +16,12 @@ import numpy as np
from base.base import (
BasePlotWidget, GraphicPassport, PlotItems, PointPassport,
UsefulGraphData, BaseController
UsefulGraphData, BaseController, PerformanceData
)
from utils.json_tools import read_json
from utils import qt_settings as qts
# =============================================================================
# Дата-класс для хранения временных характеристик канала
# =============================================================================
@dataclass
class ChannelTimings:
shift: float = 0
TWC_time: float = 0.0
ideal_time: float = 0.0
client_time: float = 0.0
TWC_start: float = 0.0
TWC_end: float = 0.0
worst_performance: float = 2
worst_timeframe: List[float] = field(default_factory=lambda: [0, 0])
# =============================================================================
# Класс PlotWidget построение графических виджетов на основе графических паспортов
# =============================================================================
@ -137,29 +122,25 @@ class PlotWidget(BasePlotWidget):
plot_layout.addItem(plot_item)
return plot_layout
def _build_performance_label(self, timings: ChannelTimings, qt_items: Dict) -> QWidget:
def _build_performance_label(self, performance:PerformanceData, qt_items: Dict) -> QWidget:
"""
Создает QLabel с информацией о производительности (сокращение длительности, идеальное значение, КДИП).
:param timings: Объект ChannelTimings с рассчитанными временными характеристиками.
:param qt_items: Словарь для сохранения ссылок на созданные виджеты.
:return: Виджет с меткой производительности.
"""
tesla_TWC = round((1 - timings.TWC_time / timings.client_time) * 100, 2) if timings.client_time else 0.0
tesla_ideal = round((1 - timings.ideal_time / timings.client_time) * 100, 2) if timings.client_time else 0.0
TWC_ideal = round((timings.ideal_time / timings.TWC_time) * 100, 2) if timings.TWC_time else 0.0
label_widget = QWidget()
label_layout = QHBoxLayout(label_widget)
start_label = QLabel("Сокращение длительности: ")
real_label = QLabel(f"фактическое = {tesla_TWC} % ")
if not tesla_TWC or not timings.TWC_time:
real_label = QLabel(f"фактическое = {performance.client_to_TWC} % ")
if performance.client_to_TWC == 0:
real_label.setVisible(False)
ideal_label = QLabel(f"идеальное = {tesla_ideal} % ")
if not tesla_ideal:
ideal_label = QLabel(f"идеальное = {performance.client_to_ideal} % ")
if performance.client_to_ideal == 0:
ideal_label.setVisible(False)
kdip_label = QLabel(f"КДИП = {TWC_ideal}% ")
if not TWC_ideal:
kdip_label = QLabel(f"КДИП = {performance.TWC_to_ideal}% ")
if performance.TWC_to_ideal == 0:
kdip_label.setVisible(False)
label_layout.addWidget(start_label, alignment=Qt.AlignLeft)
@ -190,9 +171,9 @@ class PlotWidget(BasePlotWidget):
plot_layout = CustomPlotLayout(graphic_passport, len(self._plt_channels), self._stage_colors, self)
plot_layout.build(pyqt_container, self._plt_channels)
if plot_layout.property("performance"):
perf_widget = self._build_performance_label(plot_layout.property("performance"), pyqt_container.qt_items)
container_layout.addWidget(perf_widget)
perf_widget = self._build_performance_label(graphic_passport.useful_data.performance, pyqt_container.qt_items)
container_layout.addWidget(perf_widget)
container_layout.addWidget(plot_layout)
container_widget.setProperty("pyqt_container", pyqt_container)
return container_widget
@ -258,19 +239,13 @@ class CustomPlotLayout(pg.GraphicsLayoutWidget):
"""
main_plot = None
for widget_num, (channel, description) in enumerate(plt_channels.items()):
plot_item, plot_timings = self._plotter.generate_plot_item(widget_num, channel, description, pyqt_container)
plot_item = self._plotter.generate_plot_item(widget_num, channel, description, pyqt_container)
if widget_num == 0:
main_plot = plot_item
else:
plot_item.setXLink(main_plot)
if description["Settings"].get("performance", False):
self.setProperty("performance", plot_timings)
self.addItem(plot_item, widget_num, 0)
# Если задана производительность, получаем объект ChannelTimings
timings = ChannelTimings()
if self.property('performance'):
timings = self.property('performance')
navigator = NavigatorPlot(timings.worst_timeframe, main_plot)
navigator = NavigatorPlot([0, 10], main_plot)
if navigator is not None:
self.addItem(navigator, widget_num + 1, 0)
@ -309,7 +284,7 @@ class PlotItemGenerator:
}
def generate_plot_item(self, widget_num: int, channel: str, description: Dict[str, Any],
pyqt_container: PlotItems) -> Tuple[pg.PlotItem, ChannelTimings]:
pyqt_container: PlotItems) -> pg.PlotItem:
"""
Генерирует PlotItem для заданного канала с добавлением регионов, компенсаций и идеальных данных.
@ -317,11 +292,11 @@ class PlotItemGenerator:
:param channel: Имя канала.
:param description: Словарь настроек для данного канала.
:param pyqt_container: Контейнер для хранения ссылок на объекты графиков.
:return: Кортеж из созданного PlotItem и объекта ChannelTimings с рассчитанными параметрами.
:return: PlotItem
"""
dp = self._datapack
dataframe = dp["dataframe"]
useful_data = dp["useful_data"]
useful_data:UsefulGraphData = dp["useful_data"]
points_pocket = dp["points_pocket"]
widget_steps = dp["widget_steps"]
point_steps = dp["point_steps"]
@ -329,8 +304,7 @@ class PlotItemGenerator:
# Инициализируем PlotItem и легенду
plot_item, legend = self._init_plot_item(title=channel)
settings: Dict = description["Settings"]
timings = ChannelTimings()
timings.client_time = useful_data.client_time
global_shift = 0
ideal_df = pd.DataFrame({})
# При необходимости зеркальное отражение данных для ME
@ -341,10 +315,8 @@ class PlotItemGenerator:
# Итерация по точкам паспорта
for cur_point, point_data in enumerate(points_pocket):
ideal_data = copy.deepcopy(point_data.ideal_data)
is_last = (cur_point == len(points_pocket) - 1)
is_first = (cur_point == 0)
if self._ideal_mode:
timings, point_data.events, point_data.timeframe = self._generate_synthetic_events(timings, ideal_data)
global_shift, point_data.events, point_data.timeframe = self._generate_synthetic_events(global_shift, ideal_data)
else:
if settings.get("force compensation FE", False):
force = point_data.useful_data["force"]
@ -366,8 +338,6 @@ class PlotItemGenerator:
self._add_ideal_stage_regions(self._stage_colors, plot_item, ideal_data, point_data.events,
pyqt_container.regions, 100)
ideal_df = self._modify_ideal_df(ideal_df, ideal_data, point_data.events)
if settings.get("performance", False):
timings = self._calc_performance(timings, point_data, ideal_data, is_first, is_last)
# Обновляем статус через родительский PlotWidget
self._parent._update_status(widget_steps, point_steps, widget_num, cur_point)
@ -377,7 +347,7 @@ class PlotItemGenerator:
# Добавляем реальные сигналы, если не включен режим идеала
if not self._ideal_mode:
self._add_signals(plot_item, dataframe, description["Real_signals"], legend, pyqt_container.curves["real"])
return plot_item, timings
return plot_item
@staticmethod
def _shift_data(valid_str: str, signals: List[Dict], dataframe: pd.DataFrame, func: Callable) -> pd.DataFrame:
@ -579,55 +549,25 @@ class PlotItemGenerator:
rect_item.setPen(pg.mkPen('black', width=3))
plot_item.addItem(rect_item)
@staticmethod
def _calc_performance(timings: ChannelTimings, point_data: PointPassport, ideal_data: Dict,
is_first: bool, is_last: bool) -> ChannelTimings:
"""
Рассчитывает показатели производительности для текущей точки.
:param timings: Текущие временные показатели (ChannelTimings).
:param point_data: Объект PointPassport для текущей точки.
:param ideal_data: Идеальные данные для текущей точки.
:param is_first: True, если точка первая.
:param is_last: True, если точка последняя.
:return: Обновленный объект ChannelTimings.
"""
if is_first:
if not PlotItemGenerator._parent_ideal_mode():
timings.TWC_start = point_data.events["Closing"][0]
ideal_delta = ideal_data["Ideal cycle"]
elif is_last:
if not PlotItemGenerator._parent_ideal_mode():
timings.TWC_end = point_data.events["Relief"][1]
timings.TWC_time = timings.TWC_end - timings.TWC_start
timings.worst_timeframe = [timings.TWC_start, timings.TWC_end]
ideal_delta = sum(ideal_data["Ideal timings"][0:3])
else:
ideal_delta = ideal_data["Ideal cycle"]
timings.ideal_time += ideal_delta
# При желании можно добавить сравнение с текущей производительностью
# Если (ideal_delta / TWC_delta) < timings.worst_performance, обновляем worst_performance и worst_timeframe
return timings
@staticmethod
def _generate_synthetic_events(timings: ChannelTimings, ideal_data: Dict) -> Tuple[ChannelTimings, Dict, List[float]]:
def _generate_synthetic_events(global_shift:float, ideal_data: Dict) -> Tuple[float, Dict, List[float]]:
"""
Генерирует синтетические события для случая, когда данные отсутствуют.
:param timings: Объект ChannelTimings.
:param global_shift: Смещение точки по линии времени относительно нуля.
:param ideal_data: Словарь с идеальными данными, содержащий "Ideal cycle" и "Ideal timings".
:return: Кортеж (обновленные timings, сгенерированные события, временной интервал точки).
:return: Кортеж (обновленные global_shift, сгенерированные события, временной интервал точки).
"""
point_timeframe = [timings.shift, timings.shift + ideal_data["Ideal cycle"]]
point_timeframe = [global_shift, global_shift + ideal_data["Ideal cycle"]]
point_events = {}
keys = list(ideal_data.keys())
shift = 0
for i, time in enumerate(ideal_data["Ideal timings"]):
point_events[keys[i]] = [timings.shift + shift, timings.shift + time + shift]
point_events[keys[i]] = [global_shift + shift, global_shift + time + shift]
shift += time
timings.shift += ideal_data["Ideal cycle"]
return timings, point_events, point_timeframe
global_shift += ideal_data["Ideal cycle"]
return global_shift, point_events, point_timeframe
@staticmethod
def _apply_force_compensation(force: float, k_hardness: float, dataframe: pd.DataFrame,

View File

@ -2,7 +2,7 @@ from typing import Callable, Optional, Any
from PyQt5.QtWidgets import (
QWidget, QPushButton, QLineEdit, QHBoxLayout, QVBoxLayout, QLabel,
QTableWidget, QTableWidgetItem, QStyledItemDelegate
QTableWidget, QTableWidgetItem, QStyledItemDelegate, QRadioButton, QGroupBox, QDoubleSpinBox
)
from PyQt5.QtGui import QIntValidator, QDoubleValidator
@ -33,8 +33,9 @@ class SettingsWindow(QWidget):
self._num_points: Optional[QLineEdit] = None
self._param_table: Optional[QTableWidget] = None
self.load_settings()
self._init_ui()
self.load_settings()
self._populate_table()
def load_settings(self) -> None:
"""Загружает настройки из JSON-файла."""
@ -75,7 +76,7 @@ class SettingsWindow(QWidget):
# Таблица для отображения параметров
self._param_table = QTableWidget()
self._populate_table()
# Основной вертикальный макет
layout = QVBoxLayout()
@ -128,7 +129,7 @@ class SettingsWindow(QWidget):
else:
self._param_table.setItemDelegateForRow(i, str_delegate)
def _save(self) -> None:
def _save_data(self) -> None:
"""
Сохраняет текущие параметры из таблицы в self._data,
записывает их в JSON-файл и вызывает функцию обновления.
@ -154,8 +155,14 @@ class SettingsWindow(QWidget):
new_data[key] = row_data
self._data = new_data
def _push_data (self) -> None:
self.write_settings()
self._upd_func()
def _save(self) -> None:
self._save_data()
self._push_data()
def _restore(self) -> None:
"""Перезагружает данные из файла и обновляет таблицу."""
@ -237,12 +244,99 @@ class SystemSettings(SettingsWindow):
}
super().__init__(path, name, upd_func, associated_names)
self._num_points.setVisible(False)
def _init_ui(self) -> None:
super()._init_ui()
performance_layout = QVBoxLayout()
self.btn_trace_movement = QRadioButton()
self.btn_trace_movement.setText("Use robot movement time from trace (auto set)")
self.btn_trace_movement.setChecked(True)
def _expand(self):
self.btn_client_movement = QRadioButton()
self.btn_client_movement.setText("Use robot movement time from client (manual set)")
label_time_before_start = QLabel("Additional time before first Squeeze, ms")
self.spin_time_before_start = QDoubleSpinBox()
self.spin_time_before_start.setRange(0, 100000.000)
self.spin_time_before_start.setValue(0)
label_time_after_end = QLabel("Additional time after last Relief, ms")
self.spin_time_after_end = QDoubleSpinBox()
self.spin_time_after_end.setRange(0, 100000.000)
self.spin_time_after_end.setValue(0)
label_client_time = QLabel("Client full time, ms")
self.spin_client_time = QDoubleSpinBox()
self.spin_client_time.setRange(0, 100000.000)
self.spin_client_time.setValue(0)
widget_time_before_start = QWidget()
layout_time_before_start = QHBoxLayout(widget_time_before_start)
layout_time_before_start.addWidget(label_time_before_start)
layout_time_before_start.addWidget(self.spin_time_before_start)
widget_time_after_end = QWidget()
layout_time_after_end = QHBoxLayout(widget_time_after_end)
layout_time_after_end.addWidget(label_time_after_end)
layout_time_after_end.addWidget(self.spin_time_after_end)
widget_client_time = QWidget()
layout_client_time = QHBoxLayout(widget_client_time)
layout_client_time.addWidget(label_client_time)
layout_client_time.addWidget(self.spin_client_time)
performance_layout.addWidget(self.btn_trace_movement)
performance_layout.addWidget(self.btn_client_movement)
performance_layout.addWidget(widget_time_before_start)
performance_layout.addWidget(widget_time_after_end)
performance_layout.addWidget(widget_client_time)
performance_box = QGroupBox()
performance_box.setTitle("Performance settings")
performance_box.setLayout(performance_layout)
self.layout().addWidget(performance_box)
def _add_performance_mode(self) -> None:
if self.btn_trace_movement.isChecked():
mode = 'trace'
elif self.btn_client_movement.isChecked():
mode = 'client'
else:
mode = None
self._data["performance_mode"] = [mode]
self._data["time_before_start"] = [self.spin_time_before_start.value()/1000]
self._data["time_after_end"] = [self.spin_time_after_end.value()/1000]
self._data["client_time"] = [self.spin_client_time.value()/1000]
def _save_data(self) -> None:
super()._save_data()
self._add_performance_mode()
def _load_performance_settings(self) -> None:
mode = self._data["performance_mode"][0]
if mode == 'trace':
self.btn_trace_movement.setChecked(True)
elif mode == 'client':
self.btn_client_movement.setChecked(True)
self.spin_time_before_start.setValue(self._data["time_before_start"][0]*1000)
self.spin_time_after_end.setValue(self._data["time_after_end"][0]*1000)
self.spin_client_time.setValue(self._data["client_time"][0]*1000)
def load_settings(self) -> None:
super().load_settings()
try:
self._load_performance_settings()
except KeyError:
self._add_performance_mode()
def _expand(self) -> None:
# Для системных настроек расширение столбцов не требуется.
pass
class OperatorSettings(SettingsWindow):
"""
Настройки оператора.
@ -272,8 +366,12 @@ class OperatorSettings(SettingsWindow):
"Tesla summary time": "Client summary time, sec"
}
super().__init__(path, name, upd_func, associated_names)
class FilterSettings(SettingsWindow):
"""
Настройки фильтра.

File diff suppressed because it is too large Load Diff