dev: добавлена функция открытия, обработки и отображения трейсов клиента (вообще любых трейсов)

This commit is contained in:
Andrew 2025-01-30 16:08:42 +03:00
parent 3f7959e47c
commit 0afbfb9732
32 changed files with 265 additions and 50 deletions

View File

@ -11,6 +11,35 @@ from PyQt5.QtWidgets import QWidget, QTabWidget, QMainWindow, QVBoxLayout
from OptAlgorithm import OptAlgorithm
from utils.qt_settings import dark_style
@dataclass
class KukaTXT:
time: float = 0
endtime: float = 0
#module: str
func: str = ""
type_: str = ""
signal: str = ""
#line: int = 0
#point_name: str = ""
#point_coord: dict = field(default_factory=lambda: {})
#blending: str = ""
#blending_param: float = 0
#velocities: dict = field(default_factory=lambda: {})
#accelerarions: dict = field(default_factory=lambda: {})
#base: dict = field(default_factory=lambda: {})
#tool: dict = field(default_factory=lambda: {})
#ipo_mode: str = ""
#motion_mode: str = ""
#load: dict = field(default_factory=lambda: {})
#load_a3: dict = field(default_factory=lambda: {})
@dataclass
class KukaDataHead:
rob_ID: int
filename: str
channels: dict
@dataclass
class PlotItems:
@ -53,7 +82,8 @@ class BaseMediator:
passport_former: BasePointPassportFormer,
plot: BasePlotWidget,
controller: BaseController,
file_manager: BaseFileManager):
file_manager: BaseFileManager,
trace_processor: BaseRawTraceProcessor):
self._converter = converter
self._converter.mediator = self
self._passport_former = passport_former
@ -64,12 +94,16 @@ class BaseMediator:
self._controller.mediator = self
self._file_manager = file_manager
self._file_manager.mediator = self
self._trace_processor = trace_processor
self._trace_processor.mediator = self
def notify(self,
source: Union[BaseDirectoryMonitor, BaseDataConverter, BasePointPassportFormer, BasePlotWidget],
data: Union[list[str], list[pd.DataFrame], list[list], list[QWidget]]):
source: Union[BaseDirectoryMonitor, BaseDataConverter, BasePointPassportFormer, BasePlotWidget, BaseRawTraceProcessor],
data: Union[list[str], list[pd.DataFrame], list[list], list[QWidget], pd.DataFrame]):
...
def prerender(self, data:list[str]) -> None:
...
class BaseDirectoryMonitor:
@ -265,7 +299,7 @@ class BasePlotWidget:
font-weight: bold;
font-family: "Segoe UI", sans-serif;
}""")
@property
def controller(self) -> BaseController:
return self._controller
@ -281,6 +315,8 @@ class BasePlotWidget:
def build(self, data: list[pd.DataFrame]) -> list[QWidget]:
...
def build_raw_trace(self, data:pd.DataFrame) -> None:
...
class BaseController(QObject):
@ -303,6 +339,9 @@ class BaseController(QObject):
def open_file(self, filepath: str) -> None:
...
def open_dir(self, dirpath:str) -> None:
...
def update_plots(self) -> None:
...
@ -363,6 +402,9 @@ class BaseFileManager:
def open_custom_file(self, path:str) -> None:
...
def open_raw_traces_dir(self, path:str) -> None:
...
def set_mode(self, num:int) -> None:
...
@ -500,4 +542,47 @@ class BasePointPassportFormer:
@mediator.setter
def mediator(self, mediator: BaseMediator) -> None:
self._mediator = mediator
self._mediator = mediator
class BaseRawTraceProcessor:
def __init__(self,
dataparser:BaseKukaDataParser,
textparser:BaseKukaTextParser,
mediator:Optional[BaseMediator] = None):
self._mediator = mediator
self._dataparser = dataparser
self._textparser = textparser
self._trace_df = None
self._text_data = None
def prerender(self, data:list[str]) -> None:
...
@property
def mediator(self) -> BaseMediator:
return self._mediator
@mediator.setter
def mediator(self, mediator: BaseMediator) -> None:
self._mediator = mediator
class BaseKukaDataParser:
def __init__(self):
self._ch_name = None
def parse(self, head_path: str) -> pd.DataFrame:
...
class BaseKukaTextParser:
def __init__(self):
self._in_msg = None
self._datapacks = None
def parse(self, path:str ) -> list[KukaTXT]:
...

View File

@ -31,6 +31,9 @@ class Controller(BaseController):
def open_file(self, filepath: str) -> None:
self._file_manager.open_custom_file(filepath)
def open_dir(self, dirpath:str) -> None:
self._file_manager.open_raw_traces_dir(dirpath)
def save_file(self, data:list[str, QTabWidget]) -> None:
filepath, tab = data

View File

@ -21,12 +21,15 @@ class FileManager(BaseFileManager):
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()
case 3: # Режим работы с трейсами клиента
self._monitor.stop()
self._paths_library.clear()
def update_monitor_settings(self, settings:Settings) -> None:
directory_path = settings.system['trace_storage_path'][0]
@ -44,12 +47,32 @@ class FileManager(BaseFileManager):
self._monitor._update_time = update_time
if self._monitor.isActive: self._monitor.start()
def add_new_paths(self, paths):
def add_new_paths(self, paths:list) -> None:
paths_set = set(paths)
new = self._paths_library.difference(paths_set)
self._paths_library.update(new)
self._mediator.notify(list(new))
def open_raw_traces_dir(self, path:str) -> None:
dat_file, txt_file = None, None
for entry in os.listdir(path):
full_path = os.path.join(path, entry)
if os.path.isfile(full_path):
_, ext = os.path.splitext(entry)
ext = ext.lower()
if ext == '.dat' and dat_file is None:
dat_file = full_path
elif ext == '.txt' and txt_file is None:
txt_file = full_path
if dat_file and txt_file:
break
if dat_file and txt_file:
self._mediator.prerender([dat_file, txt_file])
class DirectoryMonitor(BaseDirectoryMonitor):

View File

@ -11,15 +11,16 @@ from base.base import (
BasePointPassportFormer,
BaseController,
GraphicPassport,
Settings
Settings,
BaseRawTraceProcessor
)
class Mediator(BaseMediator):
def notify(self,
source: Union[BaseFileManager, BaseDataConverter, BasePointPassportFormer, BasePlotWidget, BaseController],
data: Union[list[str], list[pd.DataFrame], list[GraphicPassport], list[QWidget], Settings]):
source: Union[BaseFileManager, BaseDataConverter, BasePointPassportFormer, BasePlotWidget, BaseController, BaseRawTraceProcessor],
data: Union[list[str], list[pd.DataFrame], list[GraphicPassport], list[QWidget], Settings, pd.DataFrame]):
if issubclass(source.__class__, BaseFileManager):
self._controller.update_status("CSV found! Calculating...")
@ -41,5 +42,11 @@ class Mediator(BaseMediator):
self._file_manager.update_monitor_settings(data)
self._passport_former.update_settings(data)
if issubclass(source.__class__, BaseRawTraceProcessor):
self._plot.build_raw_trace(data)
def prerender(self, data:list[str]) -> None:
self._trace_processor.prerender(data)

View File

@ -4,7 +4,8 @@ from PyQt5.QtCore import Qt, pyqtSignal
from base.base import BaseMainWindow, Settings
from gui.start_widget import (CustomMenuBar, CustomStatusBar,
StartWidget, CustomTabWidget,
RaportWidget, SeekingWidget)
RaportWidget, SeekingWidget,
ClientAnalyzerWidget)
from gui.settings_window import SystemSettings, OperatorSettings
from gui.report_gui import ReportSettings
@ -17,6 +18,7 @@ class MainWindow(BaseMainWindow):
signal_replot_all = pyqtSignal()
signal_open_file = pyqtSignal(str)
signal_save_file = pyqtSignal(list)
signal_open_dir = pyqtSignal(str)
def __init__(self) -> None:
super().__init__()
@ -53,6 +55,7 @@ class MainWindow(BaseMainWindow):
start_widget = StartWidget()
start_widget.seeking_mode_btn.clicked.connect(self._init_seekingUI)
start_widget.raport_mode_btn.clicked.connect(self._init_raportUI)
start_widget.client_mode_btn.clicked.connect(self._init_client_UI)
self.setCentralWidget(start_widget.get_widget())
def _init_settings(self) -> None:
@ -64,6 +67,7 @@ class MainWindow(BaseMainWindow):
self.menu = CustomMenuBar(self.sysSettings, self.repSettings, self.operSettings)
self.menu.seeking_action.triggered.connect(self._init_seekingUI)
self.menu.raport_action.triggered.connect(self._init_raportUI)
self.menu.client_action.triggered.connect(self._init_client_UI)
self.menu.view_settings.triggered.connect(lambda: self._on_tab_changed(0))
self.menu.setup(self)
@ -105,6 +109,17 @@ class MainWindow(BaseMainWindow):
self._set_mode(2)
seeking_widget = SeekingWidget(self._tab_widget)
self.setCentralWidget(seeking_widget.get_widget())
def _init_client_UI(self) -> None:
self._clear()
self._set_mode(3)
client_widget = ClientAnalyzerWidget(self._tab_widget)
#TODO: привязать действия к кнопкам
client_widget.open_folder_btn.clicked.connect(self._open_folder)
client_widget.save_screen_btn.clicked.connect(self._save_plots)
self.setCentralWidget(client_widget.get_widget())
#client_widget.build_TCW_btn.clicked.connect()
def _set_mode(self, num:int) -> None:
match num:
@ -114,6 +129,9 @@ class MainWindow(BaseMainWindow):
case 2:
self.status_widget.set_mode("online mode")
self.signal_mode.emit(2)
case 3:
self.status_widget.set_mode("client processor mode")
self.signal_mode.emit(3)
def _transfer_settings(self) -> None:
settings = Settings(self.operSettings.getParams(), self.sysSettings.getParams())
@ -129,6 +147,12 @@ class MainWindow(BaseMainWindow):
CSV_path, _ = QtWidgets.QFileDialog.getOpenFileName(self,"Select csv file", "", "CSV Files (*.csv)")
if CSV_path:
self.signal_open_file.emit(CSV_path)
def _open_folder(self) -> None:
dir_path = QtWidgets.QFileDialog.getExistingDirectory(self,"Select folder with traces","",
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks)
if dir_path:
self.signal_open_dir.emit(dir_path)
def _save_plots(self) -> None:
filepath, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Save file", "", "Image Files (*.png *.jpeg)")

View File

@ -19,6 +19,7 @@ import pyqtgraph as pg
import pandas as pd
from base.base import BasePlotWidget, GraphicPassport, PlotItems, PointPassport, UsefulGraphData
from utils import qt_settings as qts
@dataclass
@ -54,6 +55,31 @@ class PlotWidget(BasePlotWidget):
finally:
self._mediator.notify(self, widgets_datapack)
def build_raw_trace(self, data:pd.DataFrame) -> None:
"""
Создаёт один виджет с одним графиком, где представлены все данные
"""
container_widget, container_layout, pyqt_container = self._generate_widget_container()
plot = self._build_raw_plotitem(data, pyqt_container)
container_layout.addWidget(plot)
container_widget.setProperty("pyqt_container", pyqt_container)
self._mediator.notify(self, [container_widget])
def _build_raw_plotitem(self,
dataframe:pd.DataFrame,
pyqt_container:PlotItems) -> pg.GraphicsLayoutWidget:
plot_item, legend = PlotItemGenerator._init_plot_item("Customer data")
channels = dataframe.columns.to_list()
for i, channel in enumerate(channels):
plot = plot_item.plot(dataframe.index.values, dataframe[channel], pen = qts.colors[i], fast = True)
legend.addItem(plot, channel)
pyqt_container.curves["real"].setdefault(channel, {})
pyqt_container.curves["real"][channel] = plot
plot_layout = pg.GraphicsLayoutWidget()
plot_layout.addItem(plot_item)
return plot_layout
def _build_performance_label(self,
timings: ChannelTimings,
qt_items: dict) -> QWidget:
@ -132,7 +158,8 @@ class CustomPlotLayout(pg.GraphicsLayoutWidget):
def __init__(self,
graphic_passport: GraphicPassport,
widget_steps: int,
colors: dict, parent: PlotWidget = None) -> None:
colors: dict,
parent: PlotWidget = None) -> None:
super().__init__()
self._plotter = PlotItemGenerator(graphic_passport, widget_steps, colors, parent)
self.setProperty("performance", None)
@ -155,7 +182,6 @@ class CustomPlotLayout(pg.GraphicsLayoutWidget):
if navigator is not None:
self.addItem(navigator, widget_num+1, 0)
class PlotItemGenerator:
def __init__(self,

View File

@ -27,14 +27,17 @@ class StartWidget(QWidget):
def _build_main_layout(self) -> None:
self.resize(800,800)
self.seeking_mode_btn = QPushButton("Real time folder scanning")
self.seeking_mode_btn.setFixedWidth(300)
self.seeking_mode_btn.setFixedWidth(350)
self.raport_mode_btn = QPushButton("Raport editor")
self.raport_mode_btn.setFixedWidth(300)
self.raport_mode_btn.setFixedWidth(350)
self.client_mode_btn = QPushButton("Client trace processor")
self.client_mode_btn.setFixedWidth(350)
button_layout = QHBoxLayout()
button_layout.setSpacing(2)
button_layout.addWidget(self.seeking_mode_btn)
button_layout.addWidget(self.raport_mode_btn)
button_layout.addWidget(self.client_mode_btn)
button_widget = QWidget()
button_widget.setLayout(button_layout)
@ -201,6 +204,7 @@ class CustomMenuBar(QMenuBar):
# Создаем действия для меню
self.seeking_action = QAction("Real time folder scanning", self)
self.raport_action = QAction("Raport editor", self)
self.client_action = QAction("Client trace processor", self)
system_settings = QAction("System settings", self)
system_settings.setIcon(QIcon('resources/system_ico.png'))
@ -217,6 +221,7 @@ class CustomMenuBar(QMenuBar):
# Добавляем действия в меню "Режимы"
modes_menu.addAction(self.seeking_action)
modes_menu.addAction(self.raport_action)
modes_menu.addAction(self.client_action)
settings_menu.addAction(system_settings)
settings_menu.addAction(operator_settings)
@ -293,4 +298,35 @@ class SeekingWidget(QWidget):
main_layout.addWidget(button_widget)
self.setLayout(main_layout)
class ClientAnalyzerWidget(QWidget):
def __init__(self, tabWidget:CustomTabWidget):
super().__init__()
self._tabWidget = tabWidget
self._build_widget()
def get_widget(self) -> QWidget:
return self
def _build_widget(self):
main_layout = QVBoxLayout()
self.save_screen_btn = QPushButton("Save state")
self.save_screen_btn.setFixedWidth(185)
self.open_folder_btn = QPushButton("Open folder")
self.open_folder_btn.setFixedWidth(185)
self.build_TCW_btn = QPushButton("Calculate TCW")
self.build_TCW_btn.setFixedWidth(185)
button_layout = QHBoxLayout()
button_layout.setSpacing(2)
button_layout.addWidget(self.save_screen_btn)
button_layout.addWidget(self.open_folder_btn)
button_layout.addWidget(self.build_TCW_btn)
button_widget = QWidget()
button_widget.setLayout(button_layout)
main_layout.addWidget(self._tabWidget)
main_layout.addWidget(button_widget)
self.setLayout(main_layout)

View File

@ -10,6 +10,7 @@ from controller.converter import DataConverter
from gui.plotter import PlotWidget
from controller.controller import Controller
from controller.passport_former import PassportFormer
from performance.roboter import TraceProcessor
def main():
@ -22,13 +23,15 @@ def main():
plot_widget_builder = PlotWidget(controller=controller)
passport_former = PassportFormer()
window = MainWindow()
mediator = Mediator(data_converter, passport_former, plot_widget_builder, controller, file_manager)
trace_processor = TraceProcessor()
mediator = Mediator(data_converter, passport_former, plot_widget_builder, controller, file_manager, trace_processor)
window.show()
window.signal_mode.connect(controller.set_working_mode)
window.signal_settings.connect(controller.update_settings)
window.signal_replot_all.connect(controller.update_plots)
window.signal_open_file.connect(controller.open_file)
window.signal_open_dir.connect(controller.open_dir)
window.signal_save_file.connect(controller.save_file)
controller.signal_widgets.connect(window.show_plot_tabs)

View File

@ -1,43 +1,15 @@
from __future__ import annotations
import os
from typing import Tuple, Union
from typing import Tuple, Union, Optional
from dataclasses import dataclass, field
import numpy as np
import pandas as pd
@dataclass
class KukaDataHead:
rob_ID: int
filename: str
channels: dict
from base.base import BaseKukaDataParser, BaseKukaTextParser, BaseRawTraceProcessor, KukaDataHead, KukaTXT
@dataclass
class KukaTXT:
time: float = 0
endtime: float = 0
#module: str
func: str = ""
type_: str = ""
signal: str = ""
#line: int = 0
#point_name: str = ""
#point_coord: dict = field(default_factory=lambda: {})
#blending: str = ""
#blending_param: float = 0
#velocities: dict = field(default_factory=lambda: {})
#accelerarions: dict = field(default_factory=lambda: {})
#base: dict = field(default_factory=lambda: {})
#tool: dict = field(default_factory=lambda: {})
#ipo_mode: str = ""
#motion_mode: str = ""
#load: dict = field(default_factory=lambda: {})
#load_a3: dict = field(default_factory=lambda: {})
class KukaDataParser:
class KukaDataParser(BaseKukaDataParser):
def parse(self, head_path: str) -> pd.DataFrame:
head = self._parse_dat_file(head_path)
@ -120,8 +92,9 @@ class KukaDataParser:
return floats
class TextParser:
class KukaTextParser(BaseKukaTextParser):
def __init__(self):
super().__init__()
self._in_msg = False
self._datapacks = []
@ -290,6 +263,41 @@ class TextStageDetector:
return stages
class TraceProcessor(BaseRawTraceProcessor):
def __init__(self):
dataparser = KukaDataParser()
textparser = KukaTextParser()
super().__init__(dataparser, textparser)
def prerender(self, data:list[str]) -> None:
if data:
dat_filepath = data[0]
txt_filepath = None
elif len(data) == 2:
dat_filepath = data[0]
txt_filepath = data[1]
else:
dat_filepath = None
txt_filepath = None
self._trace_df = self._unpack_trace(dat_filepath)
self._text_data = self._unpack_text(txt_filepath)
self._mediator.notify(self, self._trace_df)
def _unpack_trace(self, dat_filepath:str = None) -> Optional[pd.DataFrame]:
if dat_filepath:
return self._dataparser.parse(dat_filepath)
return None
def _unpack_text(self, txt_filepath:str = None) -> Optional[list[KukaTXT]]:
if txt_filepath:
return self._textparser.parse(txt_filepath)
return None
"Перемещение"
@ -330,7 +338,7 @@ class TextStageDetector:
if __name__ == '__main__':
roboreader = KukaDataParser()
txt_reader = TextParser()
txt_reader = KukaTextParser()
detector_traces = TraceStageDetector(region_of_focus=[7.7, 42])
detector_weldings = TextStageDetector()