Refactor radar system

This commit is contained in:
Ayzen
2026-10-05 16:46:28 +03:00
parent 515049d2ed
commit 8f097dfa2e
179 changed files with 8212 additions and 6676 deletions
+133
View File
@@ -0,0 +1,133 @@
"""Table editor for antenna switch positions and coordinates in metres."""
from __future__ import annotations
from dataclasses import asdict, is_dataclass
import math
from PyQt6.QtCore import QLocale, QSignalBlocker, pyqtSignal
from PyQt6.QtGui import QDoubleValidator, QIntValidator
from PyQt6.QtWidgets import (
QAbstractItemView, QHeaderView, QHBoxLayout, QLineEdit, QMessageBox, QPushButton,
QStyledItemDelegate, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget,
)
class _CoordinateDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
validator = (
QIntValidator(0, self.parent().property("position_count") - 1, editor)
if index.column() == 0 else QDoubleValidator(editor)
)
validator.setLocale(QLocale.c())
editor.setValidator(validator)
return editor
class AntennaGeometryEditor(QWidget):
"""Keep geometry explicit and preserve all three coordinates on profile round trips."""
changed = pyqtSignal()
def __init__(self, position_field: str, entries, *, position_count: int, parent=None) -> None:
super().__init__(parent)
self.position_field = position_field
self._table = QTableWidget(0, 4, self)
self.set_position_count(position_count)
self._table.setHorizontalHeaderLabels([
"Output" if position_field == "output_pos" else "Input", "X (m)", "Y (m)", "Z (m)",
])
self._table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self._table.verticalHeader().hide()
self._table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self._table.setItemDelegate(_CoordinateDelegate(self._table))
self._table.setMinimumHeight(130)
self._table.setMaximumHeight(220)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self._table)
buttons = QHBoxLayout()
self._add_button = QPushButton("Add antenna", self)
self._remove_button = QPushButton("Remove selected", self)
buttons.addWidget(self._add_button)
buttons.addWidget(self._remove_button)
layout.addLayout(buttons)
self.set_records(entries)
self._table.itemChanged.connect(self._on_item_changed)
self._add_button.clicked.connect(self._add_row)
self._remove_button.clicked.connect(self._remove_rows)
def records(self) -> list[dict]:
records = []
for row in range(self._table.rowCount()):
records.append(dict(zip(
(self.position_field, "x_m", "y_m", "z_m"),
(self._table.item(row, column).text() for column in range(4)),
strict=True,
)))
return self._normalize(records)
def _normalize(self, entries) -> list[dict]:
records = []
positions = set()
for entry in entries:
entry = asdict(entry) if is_dataclass(entry) else entry
if any(isinstance(entry.get(key), bool) for key in (self.position_field, "x_m", "y_m", "z_m")):
raise ValueError("Antenna positions and coordinates must be numbers, not booleans")
position = int(entry[self.position_field])
if float(entry[self.position_field]) != position or not 0 <= position < self._table.property("position_count"):
raise ValueError("Antenna position is outside the configured switch positions")
if position in positions:
raise ValueError(f"Duplicate antenna position: {position}")
positions.add(position)
coordinates = {key: float(entry.get(key, 0.0)) for key in ("x_m", "y_m", "z_m")}
if any(not math.isfinite(value) or abs(value) > 3.4e38 for value in coordinates.values()):
raise ValueError("Antenna coordinates must be finite numbers")
records.append({self.position_field: position, **coordinates})
return records
def set_position_count(self, count: int) -> None:
self.position_count = int(count)
self._table.setProperty("position_count", int(count))
def set_records(self, entries) -> None:
records = self._normalize(entries)
with QSignalBlocker(self._table):
self._table.setRowCount(len(records))
for row, record in enumerate(records):
for column, value in enumerate(record.values()):
self._table.setItem(row, column, QTableWidgetItem(str(value)))
self._valid_records = records
self._add_button.setEnabled(len(records) < self._table.property("position_count"))
self.changed.emit()
def _on_item_changed(self, _item) -> None:
try:
self._valid_records = self.records()
except ValueError as exc:
with QSignalBlocker(self):
self.set_records(self._valid_records)
QMessageBox.warning(self, "Invalid antenna geometry", str(exc))
return
self.changed.emit()
def _add_row(self) -> None:
records = self.records()
if len(records) >= self._table.property("position_count"):
return
positions = {record[self.position_field] for record in records}
position = next(index for index in range(len(positions) + 1) if index not in positions)
self.set_records([*records, {self.position_field: position}])
self._table.setCurrentCell(len(records), 1)
self._table.editItem(self._table.item(len(records), 1))
def _remove_rows(self) -> None:
rows = {index.row() for index in self._table.selectionModel().selectedRows()}
if rows:
with QSignalBlocker(self._table):
for row in sorted(rows, reverse=True):
self._table.removeRow(row)
self._valid_records = self.records()
self._add_button.setEnabled(True)
self.changed.emit()