2024-12-09 16:05:34 +03:00
|
|
|
|
from typing import Callable, Optional, Any
|
2024-12-18 10:16:50 +03:00
|
|
|
|
from PyQt5.QtWidgets import (QWidget, QPushButton,
|
|
|
|
|
|
QLineEdit, QHBoxLayout,
|
|
|
|
|
|
QVBoxLayout, QLabel,
|
2024-12-24 16:25:50 +03:00
|
|
|
|
QTableWidget, QTableWidgetItem,
|
|
|
|
|
|
QStyledItemDelegate)
|
|
|
|
|
|
from PyQt5.QtGui import QIntValidator, QDoubleValidator
|
2024-12-04 20:01:30 +03:00
|
|
|
|
|
2024-12-05 13:18:53 +03:00
|
|
|
|
from utils.json_tools import read_json, write_json
|
2024-12-24 11:46:00 +03:00
|
|
|
|
from utils import qt_settings as qts
|
2024-11-11 12:11:37 +03:00
|
|
|
|
|
2024-12-04 20:01:30 +03:00
|
|
|
|
class settingsWindow(QWidget):
|
2024-12-24 17:24:54 +03:00
|
|
|
|
def __init__(self, path: str, name: str, upd_func: Callable[[], None], names: dict):
|
2024-12-09 16:05:34 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Окно настроек для редактирования параметров.
|
|
|
|
|
|
:param path: Путь к файлу настроек (JSON).
|
|
|
|
|
|
:param name: Название набора настроек.
|
|
|
|
|
|
:param upd_func: Функция обновления (коллбэк).
|
|
|
|
|
|
"""
|
|
|
|
|
|
super().__init__()
|
2024-12-04 20:01:30 +03:00
|
|
|
|
self._settingsPath = path
|
|
|
|
|
|
self._name = name
|
2024-12-09 16:05:34 +03:00
|
|
|
|
self._data: dict[str, list[Any]] = {}
|
2024-12-04 20:01:30 +03:00
|
|
|
|
self._upd_func = upd_func
|
|
|
|
|
|
|
2024-12-09 16:05:34 +03:00
|
|
|
|
self._num_points: Optional[QLineEdit] = None
|
|
|
|
|
|
self._param_table: Optional[QTableWidget] = None
|
2024-12-24 17:24:54 +03:00
|
|
|
|
self._assosiated_names = names
|
2024-12-09 16:05:34 +03:00
|
|
|
|
|
2024-11-11 12:11:37 +03:00
|
|
|
|
self.load_settings()
|
2024-11-11 13:05:18 +03:00
|
|
|
|
self._init_ui()
|
2024-11-11 12:11:37 +03:00
|
|
|
|
|
2024-11-25 17:20:00 +03:00
|
|
|
|
def load_settings(self) -> None:
|
2024-12-09 16:05:34 +03:00
|
|
|
|
"""Загружает настройки из JSON-файла."""
|
|
|
|
|
|
data = read_json(self._settingsPath)
|
|
|
|
|
|
if isinstance(data, dict):
|
|
|
|
|
|
self._data = data
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._data = {}
|
2024-11-11 12:11:37 +03:00
|
|
|
|
|
2024-11-25 17:20:00 +03:00
|
|
|
|
def write_settings(self) -> None:
|
2024-12-09 16:05:34 +03:00
|
|
|
|
"""Записывает текущие настройки в JSON-файл."""
|
2024-12-04 20:01:30 +03:00
|
|
|
|
write_json(self._settingsPath, self._data)
|
2024-12-09 16:05:34 +03:00
|
|
|
|
|
2024-12-04 20:01:30 +03:00
|
|
|
|
def getParams(self) -> dict:
|
2024-12-09 16:05:34 +03:00
|
|
|
|
"""Возвращает текущий словарь параметров."""
|
2024-12-04 20:01:30 +03:00
|
|
|
|
return self._data
|
|
|
|
|
|
|
2024-11-25 17:20:00 +03:00
|
|
|
|
def _init_ui(self) -> None:
|
2024-12-09 16:05:34 +03:00
|
|
|
|
"""Инициализирует UI: кнопки, поля ввода, таблицу."""
|
|
|
|
|
|
save_button = QPushButton("Save")
|
|
|
|
|
|
restore_button = QPushButton("Restore")
|
|
|
|
|
|
|
2024-12-04 20:01:30 +03:00
|
|
|
|
self._num_points = QLineEdit()
|
|
|
|
|
|
self._num_points.setPlaceholderText("Enter the number of welding points")
|
|
|
|
|
|
self._num_points.setValidator(QIntValidator())
|
2024-12-09 16:05:34 +03:00
|
|
|
|
|
2024-12-04 20:01:30 +03:00
|
|
|
|
control_layout = QHBoxLayout()
|
|
|
|
|
|
control_layout.addWidget(save_button)
|
|
|
|
|
|
control_layout.addWidget(restore_button)
|
|
|
|
|
|
control_layout.addWidget(self._num_points)
|
|
|
|
|
|
|
|
|
|
|
|
save_button.pressed.connect(self._save)
|
|
|
|
|
|
restore_button.pressed.connect(self._restore)
|
|
|
|
|
|
self._num_points.editingFinished.connect(self._expand)
|
2024-12-09 16:05:34 +03:00
|
|
|
|
|
2024-12-04 20:01:30 +03:00
|
|
|
|
self._param_table = QTableWidget()
|
2024-12-09 16:05:34 +03:00
|
|
|
|
self._populate_table()
|
2024-12-04 20:01:30 +03:00
|
|
|
|
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
header = QLabel(self._name)
|
|
|
|
|
|
layout.addWidget(header)
|
|
|
|
|
|
layout.addLayout(control_layout)
|
|
|
|
|
|
layout.addWidget(self._param_table)
|
2024-11-11 12:11:37 +03:00
|
|
|
|
self.setLayout(layout)
|
2024-12-24 11:46:00 +03:00
|
|
|
|
self.setStyleSheet(qts.dark_style)
|
2024-12-09 16:05:34 +03:00
|
|
|
|
|
|
|
|
|
|
def _populate_table(self) -> None:
|
|
|
|
|
|
"""Заполняет таблицу значениями из self._data."""
|
|
|
|
|
|
# Если нет данных для заполнения
|
|
|
|
|
|
if not self._data:
|
|
|
|
|
|
self._param_table.setRowCount(0)
|
|
|
|
|
|
self._param_table.setColumnCount(0)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Предполагаем, что у всех ключей одинаковая длина списков параметров.
|
|
|
|
|
|
first_key = next(iter(self._data), None)
|
|
|
|
|
|
if first_key is None:
|
|
|
|
|
|
self._param_table.setRowCount(0)
|
|
|
|
|
|
self._param_table.setColumnCount(0)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2024-12-24 16:25:50 +03:00
|
|
|
|
column_count = len(self._data[first_key])
|
2024-12-04 20:01:30 +03:00
|
|
|
|
self._param_table.setRowCount(len(self._data))
|
2024-12-09 16:05:34 +03:00
|
|
|
|
self._param_table.setColumnCount(column_count)
|
2024-12-24 17:24:54 +03:00
|
|
|
|
headers = [self._assosiated_names[key] for key in self._data.keys()]
|
|
|
|
|
|
self._param_table.setVerticalHeaderLabels(headers)
|
2024-12-09 16:05:34 +03:00
|
|
|
|
|
2024-12-24 16:25:50 +03:00
|
|
|
|
int_delegate = ValidatorDelegate(data_type='int', parent=self._param_table)
|
|
|
|
|
|
float_delegate = ValidatorDelegate(data_type='float', parent=self._param_table)
|
|
|
|
|
|
str_delegate = ValidatorDelegate(data_type='str', parent=self._param_table)
|
2024-12-09 16:05:34 +03:00
|
|
|
|
|
2024-12-24 16:25:50 +03:00
|
|
|
|
for i, (_, items) in enumerate(self._data.items()):
|
|
|
|
|
|
for j, item in enumerate(items):
|
|
|
|
|
|
self._param_table.setItem(i, j, QTableWidgetItem(str(item)))
|
|
|
|
|
|
|
|
|
|
|
|
if type(item) == int:
|
|
|
|
|
|
self._param_table.setItemDelegateForRow(i, int_delegate)
|
|
|
|
|
|
elif type(item) == float:
|
|
|
|
|
|
self._param_table.setItemDelegateForRow(i, float_delegate)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._param_table.setItemDelegateForRow(i, str_delegate)
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-12-09 16:05:34 +03:00
|
|
|
|
def _save(self) -> None:
|
|
|
|
|
|
"""Сохраняет текущие параметры из таблицы в self._data и вызывает _upd_func()."""
|
|
|
|
|
|
new_data = {}
|
|
|
|
|
|
col_count = self._param_table.columnCount()
|
2024-12-24 16:25:50 +03:00
|
|
|
|
for i, key in enumerate(self._data.keys()):
|
2024-12-09 16:05:34 +03:00
|
|
|
|
row_data = []
|
2024-12-24 16:25:50 +03:00
|
|
|
|
for j in range(col_count):
|
2024-12-09 16:05:34 +03:00
|
|
|
|
cell_item = self._param_table.item(i, j)
|
|
|
|
|
|
if cell_item is None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
param_str = cell_item.text()
|
|
|
|
|
|
# Если ключ не trace_storage_path, конвертируем в float
|
|
|
|
|
|
if key != "trace_storage_path":
|
|
|
|
|
|
try:
|
|
|
|
|
|
param = float(param_str)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
param = 0.0
|
|
|
|
|
|
else:
|
|
|
|
|
|
param = param_str
|
|
|
|
|
|
row_data.append(param)
|
|
|
|
|
|
|
|
|
|
|
|
new_data[key] = row_data
|
|
|
|
|
|
|
|
|
|
|
|
self._data = new_data
|
|
|
|
|
|
self.write_settings()
|
|
|
|
|
|
self._upd_func()
|
|
|
|
|
|
|
|
|
|
|
|
def _restore(self) -> None:
|
|
|
|
|
|
"""Перезагружает данные из файла и обновляет таблицу."""
|
|
|
|
|
|
self.load_settings()
|
|
|
|
|
|
self._populate_table()
|
|
|
|
|
|
|
2024-12-04 20:01:30 +03:00
|
|
|
|
def _expand(self) -> None:
|
2024-12-09 16:05:34 +03:00
|
|
|
|
"""Расширяет количество столбцов таблицы в зависимости от введённого значения."""
|
|
|
|
|
|
if not self._num_points:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
num_points_text = self._num_points.text()
|
|
|
|
|
|
if not num_points_text.isdigit():
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
num_points = int(num_points_text)
|
|
|
|
|
|
if num_points < 0:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2024-12-04 20:01:30 +03:00
|
|
|
|
prev_columns = self._param_table.columnCount()
|
2024-12-24 16:25:50 +03:00
|
|
|
|
desired_columns = num_points
|
2024-12-04 20:01:30 +03:00
|
|
|
|
|
2024-12-24 16:56:04 +03:00
|
|
|
|
if desired_columns <= 0:
|
2024-12-09 16:05:34 +03:00
|
|
|
|
return
|
2024-12-04 20:01:30 +03:00
|
|
|
|
|
2024-12-09 16:05:34 +03:00
|
|
|
|
self._param_table.setColumnCount(desired_columns)
|
|
|
|
|
|
|
|
|
|
|
|
# Новые столбцы заполняем последним известным параметром для каждого ключа
|
|
|
|
|
|
for i, (key, items) in enumerate(self._data.items()):
|
|
|
|
|
|
# Если нет данных, пропускаем
|
|
|
|
|
|
if not items:
|
|
|
|
|
|
continue
|
|
|
|
|
|
last_value = str(items[-1])
|
|
|
|
|
|
for col in range(prev_columns, desired_columns):
|
|
|
|
|
|
self._param_table.setItem(i, col, QTableWidgetItem(last_value))
|
|
|
|
|
|
# Добавляем новый элемент также в self._data для консистентности
|
|
|
|
|
|
# После этого можно будет сохранить при нажатии Save
|
|
|
|
|
|
# Дополним также и в self._data
|
|
|
|
|
|
additional_count = desired_columns - prev_columns
|
|
|
|
|
|
self._data[key].extend([float(last_value) if key != "trace_storage_path" else last_value] * additional_count)
|
2024-12-04 20:01:30 +03:00
|
|
|
|
|
|
|
|
|
|
|
2024-12-23 14:18:54 +03:00
|
|
|
|
class SystemSettings(settingsWindow):
|
|
|
|
|
|
def __init__(self, path, name, upd_func):
|
2024-12-24 17:24:54 +03:00
|
|
|
|
assosiated_names = {
|
2024-12-26 11:39:38 +03:00
|
|
|
|
"trace_storage_path": "Trace path",
|
2024-12-24 17:24:54 +03:00
|
|
|
|
"monitor_update_period": "Monitoring period",
|
|
|
|
|
|
"a_max_1": "Max lin accel FE, m/s^2",
|
|
|
|
|
|
"v_max_1": "Max lin speed FE, m/s",
|
|
|
|
|
|
"a_max_2":"Max lin accel ME, m/s^2",
|
|
|
|
|
|
"v_max_2": "Max lin speed FE, m/s",
|
|
|
|
|
|
"mass_1": "Mass FE, kg",
|
|
|
|
|
|
"mass_2": "Mass ME, kg",
|
|
|
|
|
|
"k_hardness_1": "Hardness coef FE, N/m",
|
|
|
|
|
|
"k_hardness_2": "Hardness coef ME, N/m",
|
|
|
|
|
|
"torque_max_1": "Max torque FE, N*m",
|
|
|
|
|
|
"torque_max_2": "Max torque ME, N*m",
|
|
|
|
|
|
"transmission_ratio_1": "Transmission ratio FE",
|
|
|
|
|
|
"transmission_ratio_2": "Transmission ratio ME",
|
|
|
|
|
|
"contact_distance_1": "Contact distance FE, m",
|
|
|
|
|
|
"contact_distance_2": "Contact distance ME, m",
|
|
|
|
|
|
"k_prop": "Proportionality factor",
|
|
|
|
|
|
"time_capture": "Calculated points per sec",
|
|
|
|
|
|
"UML_time_scaler": "UML_time_scaler",
|
|
|
|
|
|
"Range ME, mm": "Range ME, mm"
|
|
|
|
|
|
}
|
|
|
|
|
|
super().__init__(path, name, upd_func, assosiated_names)
|
2024-12-23 14:18:54 +03:00
|
|
|
|
self._num_points.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def _expand(self):
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class OperatorSettings(settingsWindow):
|
2024-12-24 17:24:54 +03:00
|
|
|
|
def __init__(self, path, name, upd_func):
|
|
|
|
|
|
assosiated_names = {
|
|
|
|
|
|
"distance_h_start_1": "Closing start dist FE, m" ,
|
|
|
|
|
|
"distance_h_start_2": "Closing start dist ME, m",
|
|
|
|
|
|
"distance_s_1": "Rob movement start dist FE, m",
|
|
|
|
|
|
"distance_s_2": "Rob movement start dist ME, m",
|
|
|
|
|
|
"distance_l_1": "Max oncoming dist FE, m",
|
|
|
|
|
|
"distance_l_2": "Max oncoming dist ME, m",
|
|
|
|
|
|
"distance_h_end1": "Oncoming end dist FE, m",
|
|
|
|
|
|
"distance_h_end2": "Oncoming end dist FE, m",
|
|
|
|
|
|
"time_wielding": "Time of welding, sec",
|
|
|
|
|
|
"time_command": "Communication time compensator, sec",
|
|
|
|
|
|
"time_robot_movement": "Rob movement time, sec",
|
|
|
|
|
|
"object_thickness": "Workpiece thickness, m",
|
|
|
|
|
|
"force_target": "Target force, N",
|
|
|
|
|
|
"force_capture": "Capture force, N",
|
|
|
|
|
|
"Tesla closing": "Client closing time, sec",
|
|
|
|
|
|
"Tesla squeeze": "Client squeeze time, sec",
|
|
|
|
|
|
"Tesla welding": "Client welding time, sec",
|
|
|
|
|
|
"Tesla oncomming_relief": "Client moving to next point time, sec",
|
|
|
|
|
|
"Tesla summary time": "Client summary time, sec"
|
|
|
|
|
|
}
|
|
|
|
|
|
super().__init__(path, name, upd_func, assosiated_names)
|
2024-12-23 14:18:54 +03:00
|
|
|
|
pass
|
2024-12-04 20:01:30 +03:00
|
|
|
|
|
2024-11-11 12:11:37 +03:00
|
|
|
|
|
2024-12-24 16:25:50 +03:00
|
|
|
|
class ValidatorDelegate(QStyledItemDelegate):
|
|
|
|
|
|
def __init__(self, data_type='str', parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.data_type = data_type
|
|
|
|
|
|
if self.data_type == 'int':
|
|
|
|
|
|
self.validator = QIntValidator()
|
|
|
|
|
|
elif self.data_type == 'float':
|
|
|
|
|
|
self.validator = QDoubleValidator()
|
|
|
|
|
|
self.validator.setNotation(QDoubleValidator.StandardNotation)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.validator = None
|
|
|
|
|
|
|
|
|
|
|
|
def createEditor(self, parent, option, index):
|
|
|
|
|
|
editor = QLineEdit(parent)
|
|
|
|
|
|
if self.validator:
|
|
|
|
|
|
editor.setValidator(self.validator)
|
|
|
|
|
|
return editor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-11-11 12:11:37 +03:00
|
|
|
|
if __name__ == '__main__':
|
2024-12-09 16:05:34 +03:00
|
|
|
|
import pyqtgraph as pg
|
2024-11-11 12:11:37 +03:00
|
|
|
|
app = pg.mkQApp("Parameter Tree Example")
|
|
|
|
|
|
window = settingsWindow('params\operator_params.json', 'operator')
|
|
|
|
|
|
app.exec()
|
|
|
|
|
|
|
|
|
|
|
|
|