kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
656 wiersze
25 KiB
Python
656 wiersze
25 KiB
Python
# NanoVNASaver
|
|
#
|
|
# A python program to view and export Touchstone data from a NanoVNA
|
|
# Copyright (C) 2019, 2020 Rune B. Broberg
|
|
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
import logging
|
|
import sys
|
|
import threading
|
|
from collections import OrderedDict
|
|
from time import strftime, localtime
|
|
|
|
from PyQt5 import QtWidgets, QtCore, QtGui
|
|
|
|
from .Windows import (
|
|
AboutWindow, AnalysisWindow, CalibrationWindow,
|
|
DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow,
|
|
TDRWindow, FilesWindow
|
|
)
|
|
from .Controls import MarkerControl, SweepControl, SerialControl
|
|
from .Formatting import format_frequency, format_vswr, format_gain
|
|
from .Hardware.Hardware import Interface
|
|
from .Hardware.VNA import VNA
|
|
from .RFTools import corr_att_data
|
|
from .Charts.Chart import Chart
|
|
from .Charts import (
|
|
CapacitanceChart,
|
|
CombinedLogMagChart, GroupDelayChart, InductanceChart,
|
|
LogMagChart, PhaseChart,
|
|
MagnitudeChart, MagnitudeZChart, MagnitudeZShuntChart, MagnitudeZSeriesChart,
|
|
QualityFactorChart, VSWRChart, PermeabilityChart, PolarChart,
|
|
RealImaginaryChart, RealImaginaryShuntChart, RealImaginarySeriesChart,
|
|
SmithChart, SParameterChart, TDRChart,
|
|
)
|
|
from .Calibration import Calibration
|
|
from .Marker import Marker, DeltaMarker
|
|
from .SweepWorker import SweepWorker
|
|
from .Settings import BandsModel, Sweep
|
|
from .Touchstone import Touchstone
|
|
from .About import VERSION
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class NanoVNASaver(QtWidgets.QWidget):
|
|
version = VERSION
|
|
dataAvailable = QtCore.pyqtSignal()
|
|
scaleFactor = 1
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.s21att = 0.0
|
|
if getattr(sys, 'frozen', False):
|
|
logger.debug("Running from pyinstaller bundle")
|
|
self.icon = QtGui.QIcon(f"{sys._MEIPASS}/icon_48x48.png") # pylint: disable=no-member
|
|
else:
|
|
self.icon = QtGui.QIcon("icon_48x48.png")
|
|
self.setWindowIcon(self.icon)
|
|
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
|
|
QtCore.QSettings.UserScope,
|
|
"NanoVNASaver", "NanoVNASaver")
|
|
logger.info("Settings from: %s", self.settings.fileName())
|
|
self.threadpool = QtCore.QThreadPool()
|
|
self.sweep = Sweep()
|
|
self.worker = SweepWorker(self)
|
|
|
|
self.worker.signals.updated.connect(self.dataUpdated)
|
|
self.worker.signals.finished.connect(self.sweepFinished)
|
|
self.worker.signals.sweepError.connect(self.showSweepError)
|
|
|
|
self.markers = []
|
|
|
|
self.marker_column = QtWidgets.QVBoxLayout()
|
|
self.marker_frame = QtWidgets.QFrame()
|
|
self.marker_column.setContentsMargins(0, 0, 0, 0)
|
|
self.marker_frame.setLayout(self.marker_column)
|
|
|
|
self.sweep_control = SweepControl(self)
|
|
self.marker_control = MarkerControl(self)
|
|
self.serial_control = SerialControl(self)
|
|
|
|
self.bands = BandsModel()
|
|
|
|
self.interface = Interface("serial", "None")
|
|
try:
|
|
self.vna = VNA(self.interface)
|
|
except IOError as exc:
|
|
self.showError(f"{exc}\n\nPlease try reconnect")
|
|
|
|
self.dataLock = threading.Lock()
|
|
self.data = Touchstone()
|
|
self.ref_data = Touchstone()
|
|
|
|
self.sweepSource = ""
|
|
self.referenceSource = ""
|
|
|
|
self.calibration = Calibration()
|
|
|
|
logger.debug("Building user interface")
|
|
|
|
self.baseTitle = f"NanoVNA Saver {NanoVNASaver.version}"
|
|
self.updateTitle()
|
|
layout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight)
|
|
|
|
scrollarea = QtWidgets.QScrollArea()
|
|
outer = QtWidgets.QVBoxLayout()
|
|
outer.addWidget(scrollarea)
|
|
self.setLayout(outer)
|
|
scrollarea.setWidgetResizable(True)
|
|
window_width = self.settings.value("WindowWidth", 1350, type=int)
|
|
window_height = self.settings.value("WindowHeight", 950, type=int)
|
|
self.resize(window_width, window_height)
|
|
scrollarea.setSizePolicy(
|
|
QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
|
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
|
|
QtWidgets.QSizePolicy.MinimumExpanding)
|
|
widget = QtWidgets.QWidget()
|
|
widget.setLayout(layout)
|
|
scrollarea.setWidget(widget)
|
|
|
|
self.charts = {
|
|
"s11": OrderedDict((
|
|
("capacitance", CapacitanceChart("S11 Serial C")),
|
|
("group_delay", GroupDelayChart("S11 Group Delay")),
|
|
("inductance", InductanceChart("S11 Serial L")),
|
|
("log_mag", LogMagChart("S11 Return Loss")),
|
|
("magnitude", MagnitudeChart("|S11|")),
|
|
("magnitude_z", MagnitudeZChart("S11 |Z|")),
|
|
("permeability", PermeabilityChart(
|
|
"S11 R/\N{GREEK SMALL LETTER OMEGA} &"
|
|
" X/\N{GREEK SMALL LETTER OMEGA}")),
|
|
("phase", PhaseChart("S11 Phase")),
|
|
("q_factor", QualityFactorChart("S11 Quality Factor")),
|
|
("real_imag", RealImaginaryChart("S11 R+jX")),
|
|
("smith", SmithChart("S11 Smith Chart")),
|
|
("s_parameter", SParameterChart("S11 Real/Imaginary")),
|
|
("vswr", VSWRChart("S11 VSWR")),
|
|
)),
|
|
"s21": OrderedDict((
|
|
("group_delay", GroupDelayChart("S21 Group Delay",
|
|
reflective=False)),
|
|
("log_mag", LogMagChart("S21 Gain")),
|
|
("magnitude", MagnitudeChart("|S21|")),
|
|
("magnitude_z_shunt", MagnitudeZShuntChart("S21 |Z| shunt")),
|
|
("magnitude_z_series", MagnitudeZSeriesChart("S21 |Z| series")),
|
|
("real_imag_shunt", RealImaginaryShuntChart("S21 R+jX shunt")),
|
|
("real_imag_series", RealImaginarySeriesChart("S21 R+jX series")),
|
|
("phase", PhaseChart("S21 Phase")),
|
|
("polar", PolarChart("S21 Polar Plot")),
|
|
("s_parameter", SParameterChart("S21 Real/Imaginary")),
|
|
)),
|
|
"combined": OrderedDict((
|
|
("log_mag", CombinedLogMagChart("S11 & S21 LogMag")),
|
|
)),
|
|
}
|
|
self.tdr_chart = TDRChart("TDR")
|
|
self.tdr_mainwindow_chart = TDRChart("TDR")
|
|
|
|
# List of all the S11 charts, for selecting
|
|
self.s11charts = list(self.charts["s11"].values())
|
|
|
|
# List of all the S21 charts, for selecting
|
|
self.s21charts = list(self.charts["s21"].values())
|
|
|
|
# List of all charts that use both S11 and S21
|
|
self.combinedCharts = list(self.charts["combined"].values())
|
|
|
|
# List of all charts that can be selected for display
|
|
self.selectable_charts = (
|
|
self.s11charts + self.s21charts +
|
|
self.combinedCharts + [self.tdr_mainwindow_chart, ])
|
|
|
|
# List of all charts that subscribe to updates (including duplicates!)
|
|
self.subscribing_charts = []
|
|
self.subscribing_charts.extend(self.selectable_charts)
|
|
self.subscribing_charts.append(self.tdr_chart)
|
|
|
|
for c in self.subscribing_charts:
|
|
c.popoutRequested.connect(self.popoutChart)
|
|
|
|
self.charts_layout = QtWidgets.QGridLayout()
|
|
|
|
QtWidgets.QShortcut(QtGui.QKeySequence("Ctrl+Q"), self, self.close)
|
|
|
|
###############################################################
|
|
# Create main layout
|
|
###############################################################
|
|
|
|
left_column = QtWidgets.QVBoxLayout()
|
|
right_column = QtWidgets.QVBoxLayout()
|
|
right_column.addLayout(self.charts_layout)
|
|
self.marker_frame.setHidden(
|
|
not self.settings.value("MarkersVisible", True, bool))
|
|
chart_widget = QtWidgets.QWidget()
|
|
chart_widget.setLayout(right_column)
|
|
self.splitter = QtWidgets.QSplitter()
|
|
self.splitter.addWidget(self.marker_frame)
|
|
self.splitter.addWidget(chart_widget)
|
|
|
|
try:
|
|
self.splitter.restoreState(self.settings.value("SplitterSizes"))
|
|
except TypeError:
|
|
pass
|
|
|
|
layout.addLayout(left_column)
|
|
layout.addWidget(self.splitter, 2)
|
|
|
|
###############################################################
|
|
# Windows
|
|
###############################################################
|
|
|
|
self.windows = {
|
|
"about": AboutWindow(self),
|
|
# "analysis": AnalysisWindow(self),
|
|
"calibration": CalibrationWindow(self),
|
|
"device_settings": DeviceSettingsWindow(self),
|
|
"file": FilesWindow(self),
|
|
"sweep_settings": SweepSettingsWindow(self),
|
|
"setup": DisplaySettingsWindow(self),
|
|
"tdr": TDRWindow(self),
|
|
}
|
|
|
|
###############################################################
|
|
# Sweep control
|
|
###############################################################
|
|
|
|
left_column.addWidget(self.sweep_control)
|
|
|
|
# ###############################################################
|
|
# Marker control
|
|
###############################################################
|
|
|
|
left_column.addWidget(self.marker_control)
|
|
|
|
for c in self.subscribing_charts:
|
|
c.setMarkers(self.markers)
|
|
c.setBands(self.bands)
|
|
|
|
self.marker_data_layout = QtWidgets.QVBoxLayout()
|
|
self.marker_data_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
for m in self.markers:
|
|
self.marker_data_layout.addWidget(m.get_data_layout())
|
|
|
|
scroll2 = QtWidgets.QScrollArea()
|
|
#scroll2.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
|
scroll2.setWidgetResizable(True)
|
|
scroll2.setVisible(True)
|
|
|
|
widget2 = QtWidgets.QWidget()
|
|
widget2.setLayout(self.marker_data_layout)
|
|
scroll2.setWidget(widget2)
|
|
self.marker_column.addWidget(scroll2)
|
|
|
|
# init delta marker (but assume only one marker exists)
|
|
self.delta_marker = DeltaMarker("Delta Marker 2 - Marker 1")
|
|
self.delta_marker_layout = self.delta_marker.get_data_layout()
|
|
self.delta_marker_layout.hide()
|
|
self.marker_column.addWidget(self.delta_marker_layout)
|
|
|
|
###############################################################
|
|
# Statistics/analysis
|
|
###############################################################
|
|
|
|
s11_control_box = QtWidgets.QGroupBox()
|
|
s11_control_box.setTitle("S11")
|
|
s11_control_layout = QtWidgets.QFormLayout()
|
|
s11_control_layout.setVerticalSpacing(0)
|
|
s11_control_box.setLayout(s11_control_layout)
|
|
|
|
self.s11_min_swr_label = QtWidgets.QLabel()
|
|
s11_control_layout.addRow("Min VSWR:", self.s11_min_swr_label)
|
|
self.s11_min_rl_label = QtWidgets.QLabel()
|
|
s11_control_layout.addRow("Return loss:", self.s11_min_rl_label)
|
|
|
|
self.marker_column.addWidget(s11_control_box)
|
|
|
|
s21_control_box = QtWidgets.QGroupBox()
|
|
s21_control_box.setTitle("S21")
|
|
s21_control_layout = QtWidgets.QFormLayout()
|
|
s21_control_layout.setVerticalSpacing(0)
|
|
s21_control_box.setLayout(s21_control_layout)
|
|
|
|
self.s21_min_gain_label = QtWidgets.QLabel()
|
|
s21_control_layout.addRow("Min gain:", self.s21_min_gain_label)
|
|
|
|
self.s21_max_gain_label = QtWidgets.QLabel()
|
|
s21_control_layout.addRow("Max gain:", self.s21_max_gain_label)
|
|
|
|
self.marker_column.addWidget(s21_control_box)
|
|
|
|
# self.marker_column.addStretch(1)
|
|
|
|
self.windows["analysis"] = AnalysisWindow(self)
|
|
btn_show_analysis = QtWidgets.QPushButton("Analysis ...")
|
|
btn_show_analysis.setMinimumHeight(20)
|
|
btn_show_analysis.clicked.connect(
|
|
lambda: self.display_window("analysis"))
|
|
self.marker_column.addWidget(btn_show_analysis)
|
|
|
|
###############################################################
|
|
# TDR
|
|
###############################################################
|
|
|
|
self.tdr_chart.tdrWindow = self.windows["tdr"]
|
|
self.tdr_mainwindow_chart.tdrWindow = self.windows["tdr"]
|
|
self.windows["tdr"].updated.connect(self.tdr_chart.update)
|
|
self.windows["tdr"].updated.connect(self.tdr_mainwindow_chart.update)
|
|
|
|
tdr_control_box = QtWidgets.QGroupBox()
|
|
tdr_control_box.setTitle("TDR")
|
|
tdr_control_layout = QtWidgets.QFormLayout()
|
|
tdr_control_box.setLayout(tdr_control_layout)
|
|
tdr_control_box.setMaximumWidth(240)
|
|
|
|
self.tdr_result_label = QtWidgets.QLabel()
|
|
self.tdr_result_label.setMinimumHeight(20)
|
|
tdr_control_layout.addRow(
|
|
"Estimated cable length:", self.tdr_result_label)
|
|
|
|
self.tdr_button = QtWidgets.QPushButton(
|
|
"Time Domain Reflectometry ...")
|
|
self.tdr_button.setMinimumHeight(20)
|
|
self.tdr_button.clicked.connect(lambda: self.display_window("tdr"))
|
|
|
|
tdr_control_layout.addRow(self.tdr_button)
|
|
|
|
left_column.addWidget(tdr_control_box)
|
|
|
|
###############################################################
|
|
# Spacer
|
|
###############################################################
|
|
|
|
left_column.addSpacerItem(
|
|
QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed,
|
|
QtWidgets.QSizePolicy.Expanding))
|
|
|
|
###############################################################
|
|
# Reference control
|
|
###############################################################
|
|
|
|
reference_control_box = QtWidgets.QGroupBox()
|
|
reference_control_box.setMaximumWidth(240)
|
|
reference_control_box.setTitle("Reference sweep")
|
|
reference_control_layout = QtWidgets.QFormLayout(reference_control_box)
|
|
|
|
btn_set_reference = QtWidgets.QPushButton("Set current as reference")
|
|
btn_set_reference.setMinimumHeight(20)
|
|
btn_set_reference.clicked.connect(self.setReference)
|
|
self.btnResetReference = QtWidgets.QPushButton("Reset reference")
|
|
self.btnResetReference.setMinimumHeight(20)
|
|
self.btnResetReference.clicked.connect(self.resetReference)
|
|
self.btnResetReference.setDisabled(True)
|
|
|
|
reference_control_layout.addRow(btn_set_reference)
|
|
reference_control_layout.addRow(self.btnResetReference)
|
|
|
|
left_column.addWidget(reference_control_box)
|
|
|
|
###############################################################
|
|
# Serial control
|
|
###############################################################
|
|
|
|
left_column.addWidget(self.serial_control)
|
|
|
|
###############################################################
|
|
# Calibration
|
|
###############################################################
|
|
|
|
btnOpenCalibrationWindow = QtWidgets.QPushButton("Calibration ...")
|
|
btnOpenCalibrationWindow.setMinimumHeight(20)
|
|
self.calibrationWindow = CalibrationWindow(self)
|
|
btnOpenCalibrationWindow.clicked.connect(
|
|
lambda: self.display_window("calibration"))
|
|
|
|
###############################################################
|
|
# Display setup
|
|
###############################################################
|
|
|
|
btn_display_setup = QtWidgets.QPushButton("Display setup ...")
|
|
btn_display_setup.setMinimumHeight(20)
|
|
btn_display_setup.setMaximumWidth(240)
|
|
btn_display_setup.clicked.connect(
|
|
lambda: self.display_window("setup"))
|
|
|
|
btn_about = QtWidgets.QPushButton("About ...")
|
|
btn_about.setMinimumHeight(20)
|
|
btn_about.setMaximumWidth(240)
|
|
|
|
btn_about.clicked.connect(
|
|
lambda: self.display_window("about"))
|
|
|
|
|
|
btn_open_file_window = QtWidgets.QPushButton("Files")
|
|
btn_open_file_window.setMinimumHeight(20)
|
|
btn_open_file_window.setMaximumWidth(240)
|
|
|
|
btn_open_file_window.clicked.connect(
|
|
lambda: self.display_window("file"))
|
|
|
|
button_grid = QtWidgets.QGridLayout()
|
|
button_grid.addWidget(btn_open_file_window, 0, 0)
|
|
button_grid.addWidget(btnOpenCalibrationWindow, 0, 1)
|
|
button_grid.addWidget(btn_display_setup, 1, 0)
|
|
button_grid.addWidget(btn_about, 1, 1)
|
|
left_column.addLayout(button_grid)
|
|
|
|
logger.debug("Finished building interface")
|
|
|
|
def sweep_start(self):
|
|
# Run the device data update
|
|
if not self.vna.connected():
|
|
return
|
|
self.worker.stopped = False
|
|
|
|
self.sweep_control.progress_bar.setValue(0)
|
|
self.sweep_control.btn_start.setDisabled(True)
|
|
self.sweep_control.btn_stop.setDisabled(False)
|
|
self.sweep_control.toggle_settings(True)
|
|
|
|
for m in self.markers:
|
|
m.resetLabels()
|
|
self.s11_min_rl_label.setText("")
|
|
self.s11_min_swr_label.setText("")
|
|
self.s21_min_gain_label.setText("")
|
|
self.s21_max_gain_label.setText("")
|
|
self.tdr_result_label.setText("")
|
|
|
|
self.settings.setValue("Segments", self.sweep_control.get_segments())
|
|
|
|
logger.debug("Starting worker thread")
|
|
self.threadpool.start(self.worker)
|
|
|
|
def sweep_stop(self):
|
|
self.worker.stopped = True
|
|
|
|
def saveData(self, data, data21, source=None):
|
|
with self.dataLock:
|
|
self.data.s11 = data
|
|
self.data.s21 = data21
|
|
if self.s21att > 0:
|
|
self.data.s21 = corr_att_data(self.data.s21, self.s21att)
|
|
if source is not None:
|
|
self.sweepSource = source
|
|
else:
|
|
self.sweepSource = (
|
|
f"{self.sweep.properties.name}"
|
|
f" {strftime('%Y-%m-%d %H:%M:%S', localtime())}"
|
|
).lstrip()
|
|
|
|
def markerUpdated(self, marker: Marker):
|
|
with self.dataLock:
|
|
marker.findLocation(self.data.s11)
|
|
marker.resetLabels()
|
|
marker.updateLabels(self.data.s11, self.data.s21)
|
|
for c in self.subscribing_charts:
|
|
c.update()
|
|
if Marker.count() >= 2 and not self.delta_marker_layout.isHidden():
|
|
self.delta_marker.set_markers(self.markers[0], self.markers[1])
|
|
self.delta_marker.resetLabels()
|
|
try:
|
|
self.delta_marker.updateLabels()
|
|
except IndexError:
|
|
pass
|
|
|
|
def dataUpdated(self):
|
|
with self.dataLock:
|
|
s11 = self.data.s11[:]
|
|
s21 = self.data.s21[:]
|
|
|
|
for m in self.markers:
|
|
m.resetLabels()
|
|
m.updateLabels(s11, s21)
|
|
|
|
for c in self.s11charts:
|
|
c.setData(s11)
|
|
|
|
for c in self.s21charts:
|
|
c.setData(s21)
|
|
|
|
for c in self.combinedCharts:
|
|
c.setCombinedData(s11, s21)
|
|
|
|
self.sweep_control.progress_bar.setValue(self.worker.percentage)
|
|
self.windows["tdr"].updateTDR()
|
|
|
|
if s11:
|
|
min_vswr = min(s11, key=lambda data: data.vswr)
|
|
self.s11_min_swr_label.setText(
|
|
f"{format_vswr(min_vswr.vswr)} @ {format_frequency(min_vswr.freq)}")
|
|
self.s11_min_rl_label.setText(format_gain(min_vswr.gain))
|
|
else:
|
|
self.s11_min_swr_label.setText("")
|
|
self.s11_min_rl_label.setText("")
|
|
|
|
if s21:
|
|
min_gain = min(s21, key=lambda data: data.gain)
|
|
max_gain = max(s21, key=lambda data: data.gain)
|
|
self.s21_min_gain_label.setText(
|
|
f"{format_gain(min_gain.gain)}"
|
|
f" @ {format_frequency(min_gain.freq)}")
|
|
self.s21_max_gain_label.setText(
|
|
f"{format_gain(max_gain.gain)}"
|
|
f" @ {format_frequency(max_gain.freq)}")
|
|
else:
|
|
self.s21_min_gain_label.setText("")
|
|
self.s21_max_gain_label.setText("")
|
|
|
|
self.updateTitle()
|
|
self.dataAvailable.emit()
|
|
|
|
def sweepFinished(self):
|
|
self.sweep_control.progress_bar.setValue(100)
|
|
self.sweep_control.btn_start.setDisabled(False)
|
|
self.sweep_control.btn_stop.setDisabled(True)
|
|
self.sweep_control.toggle_settings(False)
|
|
|
|
for marker in self.markers:
|
|
marker.frequencyInput.textEdited.emit(
|
|
marker.frequencyInput.text())
|
|
|
|
def setReference(self, s11=None, s21=None, source=None):
|
|
if not s11:
|
|
with self.dataLock:
|
|
s11 = self.data.s11[:]
|
|
s21 = self.data.s21[:]
|
|
|
|
self.ref_data.s11 = s11
|
|
for c in self.s11charts:
|
|
c.setReference(s11)
|
|
|
|
self.ref_data.s21 = s21
|
|
for c in self.s21charts:
|
|
c.setReference(s21)
|
|
|
|
for c in self.combinedCharts:
|
|
c.setCombinedReference(s11, s21)
|
|
|
|
self.btnResetReference.setDisabled(False)
|
|
|
|
if source is not None:
|
|
# Save the reference source info
|
|
self.referenceSource = source
|
|
else:
|
|
self.referenceSource = self.sweepSource
|
|
self.updateTitle()
|
|
|
|
def updateTitle(self):
|
|
insert = "("
|
|
if self.sweepSource != "":
|
|
insert += (
|
|
f"Sweep: {self.sweepSource} @ {len(self.data.s11)} points"
|
|
f"{', ' if self.referenceSource else ''}")
|
|
if self.referenceSource != "":
|
|
insert += (
|
|
f"Reference: {self.referenceSource} @"
|
|
f" {len(self.ref_data.s11)} points")
|
|
insert += ")"
|
|
title = f"{self.baseTitle} {insert if insert else ''}"
|
|
self.setWindowTitle(title)
|
|
|
|
def resetReference(self):
|
|
self.ref_data = Touchstone()
|
|
self.referenceSource = ""
|
|
self.updateTitle()
|
|
for c in self.subscribing_charts:
|
|
c.resetReference()
|
|
self.btnResetReference.setDisabled(True)
|
|
|
|
def sizeHint(self) -> QtCore.QSize:
|
|
return QtCore.QSize(1100, 950)
|
|
|
|
def display_window(self, name):
|
|
self.windows[name].show()
|
|
QtWidgets.QApplication.setActiveWindow(self.windows[name])
|
|
|
|
def showError(self, text):
|
|
QtWidgets.QMessageBox.warning(self, "Error", text)
|
|
|
|
def showSweepError(self):
|
|
self.showError(self.worker.error_message)
|
|
try:
|
|
self.vna.flushSerialBuffers() # Remove any left-over data
|
|
self.vna.reconnect() # try reconnection
|
|
except IOError:
|
|
pass
|
|
self.sweepFinished()
|
|
|
|
def popoutChart(self, chart: Chart):
|
|
logger.debug("Requested popout for chart: %s", chart.name)
|
|
new_chart = self.copyChart(chart)
|
|
new_chart.isPopout = True
|
|
new_chart.show()
|
|
new_chart.setWindowTitle(new_chart.name)
|
|
|
|
def copyChart(self, chart: Chart):
|
|
new_chart = chart.copy()
|
|
self.subscribing_charts.append(new_chart)
|
|
if chart in self.s11charts:
|
|
self.s11charts.append(new_chart)
|
|
if chart in self.s21charts:
|
|
self.s21charts.append(new_chart)
|
|
if chart in self.combinedCharts:
|
|
self.combinedCharts.append(new_chart)
|
|
new_chart.popoutRequested.connect(self.popoutChart)
|
|
return new_chart
|
|
|
|
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
|
self.worker.stopped = True
|
|
self.settings.setValue("MarkerCount", Marker.count())
|
|
for marker in self.markers:
|
|
marker.update_settings()
|
|
|
|
self.settings.setValue("WindowHeight", self.height())
|
|
self.settings.setValue("WindowWidth", self.width())
|
|
self.settings.setValue("SplitterSizes", self.splitter.saveState())
|
|
|
|
self.settings.sync()
|
|
self.bands.saveSettings()
|
|
self.threadpool.waitForDone(2500)
|
|
a0.accept()
|
|
sys.exit()
|
|
|
|
def changeFont(self, font: QtGui.QFont) -> None:
|
|
qf_new = QtGui.QFontMetricsF(font)
|
|
normal_font = QtGui.QFont(font)
|
|
normal_font.setPointSize(8)
|
|
qf_normal = QtGui.QFontMetricsF(normal_font)
|
|
# Characters we would normally display
|
|
standard_string = "0.123456789 0.123456789 MHz \N{OHM SIGN}"
|
|
new_width = qf_new.horizontalAdvance(standard_string)
|
|
old_width = qf_normal.horizontalAdvance(standard_string)
|
|
self.scaleFactor = new_width / old_width
|
|
logger.debug("New font width: %f, normal font: %f, factor: %f",
|
|
new_width, old_width, self.scaleFactor)
|
|
# TODO: Update all the fixed widths to account for the scaling
|
|
for m in self.markers:
|
|
m.get_data_layout().setFont(font)
|
|
m.setScale(self.scaleFactor)
|
|
|
|
def update_sweep_title(self):
|
|
for c in self.subscribing_charts:
|
|
c.setSweepTitle(self.sweep.properties.name)
|