1. Einstellungsdatei Laden/Speichern

Hier erfahren Sie, wie Sie die Einstellungen beim Start und beim Beenden laden und speichern.

Voraussetzungen
  • Die Option Preference file muss im Widget ScreenOptions gesetzt werden.

  • Der Pfad der Einstellungsdatei muss in der INI-Konfiguration festgelegt werden.

Leseeinstellungen zum Zeitpunkt des Starts

Under the Funktion def initialized__(self): hinzufügen:

if self.w.PREFS_:
    # variable name (entry name, default value, type, section name)
    self.int_value = self.w.PREFS_.getpref('Integer_value', 75, int, 'CUSTOM_FORM_ENTRIES')
    self.string_value = self.w.PREFS_.getpref('String_value', 'on', str, 'CUSTOM_FORM_ENTRIES')
Schreibeinstellungen beim Schließen

In der Funktion closing_cleanup__() hinzufügen:

if self.w.PREFS_:
    # variable name (entry name, variable name, type, section name)
    self.w.PREFS_.putpref('Integer_value', self.integer_value, int, 'CUSTOM_FORM_ENTRIES')
    self.w.PREFS_.putpref('String_value', self.string_value, str, 'CUSTOM_FORM_ENTRIES')

2. QSettings zum Lesen/Speichern von Variablen verwenden

Hier wird beschrieben, wie man Variablen mit den QSettings-Funktionen von PyQt lädt und speichert:

Gute Praktiken
  • Benutzen Sie Group, um Namen zu organisieren und eindeutig zu halten.

  • Berücksichtigung des Wertes none der beim Lesen einer Einstellung zurückgegeben wird, wenn diese keinen Eintrag hat.

  • Mit der Syntax or _<default_value>_ werden die Standardwerte für die erste Ausführung festgelegt.

Anmerkung
Die Datei wird tatsächlich in ~/.config/QtVcp gespeichert
Beispiel

In diesem Beispiel:

  • Wir fügen or 20 und or 2.5 als Standardwerte hinzu.

  • Die Namen MyGroupName, int_value, float_value, myInteger, und myFloat sind benutzerdefiniert.

  • Under the Funktion def initialized__(self): hinzufügen:

    # Sortiereinstellungen für aufgezeichnete Spalten festlegen
    self.SETTINGS_.beginGroup("MeinGruppenname")
    self.int_value = self.SETTINGS_.value('myInteger', Typ = int) or 20
    self.float_value = self.SETTINGS_.value('myFloat', type = float) or 2.5
    self.SETTINGS_.endGroup()
  • Unter der Funktion def closing_cleanup__(self): hinzufügen:

    # Werte mit QSettings speichern
    self.SETTINGS_.beginGroup("MeinGruppenname")
    self.SETTINGS_.setValue('myInteger', self.int_value)
    self.SETTINGS_.setValue('myFloat', self.float_value)
    self.SETTINGS_.endGroup()

3. Hinzufügen eines grundlegenden Stileditors

Die Möglichkeit, einen Stil auf einem laufenden Bildschirm zu bearbeiten, ist sehr praktisch.

Importieren Sie das Modul StyleSheetEditor in der IMPORT SECTION:
from qtvcp.widgets.stylesheeteditor import StyleSheetEditor as SSE
Instanziieren Sie das Modul StyleSheetEditor in der INSTANTIATE SECTION:
STYLEEDITOR = SSE()
Erstellen Sie eine Tastenbelegung (engl. keybinding) in der INITIALIZE SECTION:

Unter dem init. (self, halcomp, widgets, paths): Funktion hinzufügen:

KEYBIND.add_call('Key_F12','on_keycall_F12')
Erstellen Sie die tastengebundene Funktion im KEYBINDING SECTION:
def on_keycall_F12(self,event,state,shift,cntrl):
    if state:
        STYLEEDITOR.load_dialog()

4. Dialog-Eintrag anfordern

QtVCP verwendet ‚STATUS‘-Nachrichten, um Informationen aus Dialogen aufzurufen und zurückzugeben.

Vorgefertigte Dialoge behalten ihre letzte Position im Auge und enthalten Optionen für Fokusschattierung und Ton.

Um Informationen aus dem Dialog zurückzubekommen, muss man eine allgemeine Nachricht STATUS verwenden.

Importieren und Instanziieren des Status Moduls im IMPORT ABSCHNITT
from qtvcp.core import Status
STATUS = Status()

Dies lädt und initialisiert die Status-Bibliothek.

Registrierfunktion für STATUS general Nachrichten im INITIALIZE ABSCHNITT

Unter der +__init__. (self, halcomp, widgets, paths)+ Funktion:

STATUS.connect('general',self.return_value)

Damit wird STATUS registriert, um die Funktion self.return_value aufzurufen, wenn eine allgemeine Nachricht gesendet wird.

Hinzufügen der Funktion zur Abfrage des Eingabedialogs im Abschnitt GENERAL FUNCTIONS (engl. für allgemeine Funktionen)
def request_number(self):
    mess = {'NAME':'ENTRY','ID':'FORM__NUMBER', 'TITLE':'Set Tool Offset'}
    STATUS.emit('dialog-request', mess)

Die Funktion

  • erstellt ein Python-dict mit:

    • NAME - needs to be set to the dialogs unique launch name.
      NAME sets which dialog to request.
      ENTRY or CALCULATOR allows entering numbers.

    • ID - needs to be set to a unique name that the function supplies. ID should be a unique key.

    • TITLE sets the dialog title.

    • Arbitrary data can be added to the dict. The dialog will ignore them but send them back to the return code.

  • Sends the dict as a dialog-request STATUS message

Hinzufügung der Funktion zur Verarbeitung von Nachrichtendaten im Abschnitt "CALLBACKS FROM STATUS".
# Verarbeitung der STATUS-Rückmeldung von set-tool-offset
def return_value(self, w, message):
    num = message.get('RETURN')
    id_code = bool(message.get('ID') == 'FORM__NUMBER')
    name = bool(message.get('NAME') == 'ENTRY')
    if id_code and name and num is not None:
        print('The {} number from {} was: {}'.format(name, id_code, num))

Dies fängt alle allgemeinen Nachrichten ab, so dass der Dialogtyp und der ID-Code überprüft werden müssen, um zu bestätigen, dass es sich um unseren Dialog handelt. In diesem Fall hatten wir einen ENTRY-Dialog angefordert und unsere eindeutige ID war FORM_NUMBER, also wissen wir jetzt, dass die Nachricht für uns ist. Die Dialoge ENTRY oder CALCULATOR geben eine Fließkommazahl zurück.

5. Sprechen Sie eine Startup-Begrüßung

Dazu muss die Bibliothek espeak auf dem System installiert sein.

Importieren und Instanziieren des Status im Abschnitt IMPORT
from qtvcp.core import Status
STATUS = Status()
Ausgabe gesprochener Nachricht in der INITIALIZE SECTION

Unter der init. (self, halcomp, widgets, paths) Funktion:

STATUS.emit('play-alert','SPEAK Bitte denken Sie daran, die Wege zu ölen.')

SPEAK is a keyword: everything after it will be pronounced.

6. ToolBar-Funktionen

Symbolleisten-Schaltflächen und Untermenüs werden in Qt Designer hinzugefügt, aber der Code, um sie zu aktivieren, wird in der Handler-Datei hinzugefügt. Um ein Untermenü in Qt Designer hinzuzufügen:

  • Fügen Sie eine "Qaction" hinzu, indem Sie sie in die Symbolleistenspalte eingeben und dann auf das Symbol "+" auf der rechten Seite klicken.

  • Dies wird eine Unterspalte hinzufügen, in die Sie einen Namen eingeben müssen.

  • Jetzt wird die ursprüngliche Qaction stattdessen ein Qmenu sein.

  • Löschen Sie nun die Qaction, die Sie diesem Qmenu hinzugefügt haben, das Menü bleibt als Menü erhalten.

In diesem Beispiel gehen wir davon aus, dass Sie eine Symbolleiste mit einem Untermenü und drei Aktionen hinzugefügt haben. Diese Aktionen werden konfiguriert, um Folgendes zu erstellen:

  • ein Menü zur Auswahl zuletzt verwendeter Dateien,

  • eine Pop-up-Dialog-Aktion,

  • eine Aktion zum Beenden des Programms, und

  • eine benutzerdefinierte Funktionsaktion.

Der objectName der Symbolleistenschaltfläche wird verwendet, um die Schaltfläche bei der Konfiguration zu identifizieren - deskriptive Namen helfen.

Klicken Sie mit der rechten Maustaste auf das Menü des Aktionseditors und wählen Sie Bearbeiten.
Editieren Sie den Objektnamen, den Text und den Schaltflächentyp für eine geeignete Aktion.

In diesem Beispiel müssen:

  • Der Name des Untermenüs (engl. submenu) menuRecent lauten,

  • Aktionsnamen actionAbout, actionQuit, actionMyFunction sein

Lädt die Bibliothek toolbar_actions in die IMPORT SECTION
from qtvcp.lib.toolbar_actions import ToolBarActions
Instanziieren Sie das Modul ToolBarActions im Abschnitt INSTANTIATE LIBRARY SECTION
TOOLBAR = ToolBarActions()
Konfigurieren Sie Untermenüs und Aktionen im Abschnitt "BESONDERE FUNKTIONEN"

Unter der Funktion def initialized__(self) hinzufügen:

TOOLBAR.configure_submenu(self.w.menuRecent, 'recent_submenu')
TOOLBAR.configure_action(self.w.actionAbout, 'about')
TOOLBAR.configure_action(self.w.actionQuit, 'Quit', lambda d:self.w.close())
TOOLBAR.configure_action(self.w.actionMyFunction, 'My Function', self.my_function)
Definieren Sie die Benutzerfunktion in der GENERAL FUNCTIONS SECTION
def my_function(self, widget, state):
    print('My function State = ()'.format(state))

Die Funktion, die aufgerufen werden soll, wenn die Aktionsschaltfläche "Meine Funktion" gedrückt wird.

7. HAL Pins hinzufügen, die Funktionen aufrufen

Auf diese Weise müssen Sie den Zustand der Eingangsstifte nicht abfragen.

Lädt die Bibliothek Qhal in die IMPORT SECTION
from qtvcp.core import Qhal

Dadurch wird der Zugriff auf die HAL-Komponente von QtVCP ermöglicht.

Instanziieren von Qhal in der INSTANTIATE LIBRARY SECTION
QHAL = Qhal()
Hinzufügen einer Funktion, die aufgerufen wird, wenn sich der Zustand des Pins ändert

Vergewissern Sie sich, dass unter der Funktion initialised__ ein Eintrag ähnlich dem folgenden vorhanden ist:

##########################################
# Spezielle Funktionen, die von QtVCP aufgerufen werden
##########################################

# zu diesem Zeitpunkt:
# sind die Widgets instanziiert.
# die HAL-Pins sind gebaut, aber HAL ist nicht bereit
def initialized__(self):
    self.pin_cycle_start_in = QHAL.newpin('cycle-start-in',QHAL.HAL_BIT, QHAL.HAL_IN)
    self.pin_cycle_start_in.value_changed.connect(lambda s: self.cycleStart(s))
Definieren der Funktion, die durch Pin-Statusänderung im GENERAL FUNCTIONS SECTION aufgerufen wird
#######################################
# allgemeine (engl. general) functions #
#######################################

def cycleStart(self, state):
    if state:
        tab = self.w.mainTab.currentWidget()
        if  tab in( self.w.tab_auto,  self.w.tab_graphics):
            ACTION.RUN(line=0)
        elif tab == self.w.tab_files:
                self.w.filemanager.load()
        elif tab == self.w.tab_mdi:
            self.w.mditouchy.run_command()

Diese Funktion geht davon aus, dass es ein Tab-Widget mit dem Namen mainTab gibt, das Tabs mit den Namen tab_auto, tab_graphics, tab_filemanager und tab_mdi hat.

Auf diese Weise funktioniert der Zyklusstart-Button je nach angezeigter Registerkarte unterschiedlich.

Dies ist vereinfacht - Zustandskontrolle und Fehlerverfolgung könnten hilfreich sein.

8. Hinzufügen eines speziellen prozentualen Max Velocity Sliders

Manchmal möchten Sie ein Widget erstellen, um etwas zu tun, das nicht eingebaut ist. Der eingebaute Schieberegler für die maximale Geschwindigkeit wirkt auf Einheiten pro Minute, hier zeigen wir, wie man mit Prozent arbeitet.

Der STATUS Befehl stellt sicher, dass der Schieberegler sich anpasst, wenn LinuxCNC die aktuelle maximale Geschwindigkeit ändert.

valueChanged.connect() calls a function when the slider is moved.

Fügen Sie im Qt Designer ein QSlider-Widget mit dem Namen mvPercent hinzu und fügen Sie dann den folgenden Code in die Handler-Datei ein:

#############################
# SPECIAL FUNCTIONS SECTION #
#############################

def initialized__(self):
    self.w.mvPercent.setMaximum(100)
    STATUS.connect('max-velocity-override-changed', \
        lambda w, data: self.w.mvPercent.setValue( \
            (data / INFO.MAX_TRAJ_VELOCITY)*100 \
            )
        )
    self.w.mvPercent.valueChanged.connect(self.setMVPercentValue)

#####################
# GENERAL FUNCTIONS #
#####################

def setMVPercentValue(self, value):
    ACTION.SET_MAX_VELOCITY_RATE(INFO.MAX_TRAJ_VELOCITY * (value/100.0))

9. Kontinuierlichen Jog ein- und ausschalten

Im Allgemeinen ist die Auswahl des kontinuierlichen Joggens eine Momenttaste, nach der Sie die vorherige Jogging-Stufe auswählen müssen.

Wir werden eine Schaltfläche erstellen, die zwischen kontinuierlichem Joggen und der bereits ausgewählten Schrittweite umschaltet.

Im Qt-Designer:

  • Hinzufügen eines ActionButton ohne Aktion

  • Nennen Sie ihn "btn_toggle_continuous".

  • Setzen Sie die Eigenschaft AbstractButton checkable auf True.

  • Setzen Sie die Eigenschaften ActionButton incr_imperial_number und incr_mm_number auf 0.

  • Verwenden Sie den Slot-Editor von Qt Designer, um das Button-Signal clicked(bool) zu verwenden, um die Formular-Handler-Funktion toggle_continuous_clicked() aufzurufen.
    Siehe Using Qt Designer To Add Slots Abschnitt für weitere Informationen.

Dann fügen Sie diesen Code-Schnipsel in die Handler-Datei unter der Funktion initialized__ ein:

# zu diesem Zeitpunkt:
# sind die Widgets instanziiert.
# die HAL-Pins sind gebaut, aber HAL ist nicht bereit
def initialized__(self):
    STATUS.connect('jogincrement-changed', \
        lambda w, d, t: self.record_jog_incr(d,t) \
        )
    # ein Standardinkrement festlegen, zu dem zurückgeschaltet werden soll
    self.L_incr = 0,01
    self.L_text = "0.01in"

Im Abschnitt ALLGEMEINE FUNKTIONEN (engl. general functions) hinzufügen:

#####################
# GENERAL FUNCTIONS #
#####################

# Wenn es nicht kontinuierlich ist, zeichnen Sie das letzte Jog-Inkrement auf.
# und deaktiviere die Schaltfläche "kontinuierlich".
def record_jog_incr(self,d, t):
    if d != 0:
        self.L_incr = d
        self.L_text = t
        self.w.btn_toggle_continuous.safecheck(False)

Im Abschnitt CALLBACKS FROM STATUS SECTION hinzufügen:

###########################
# CALLBACKS VOM FORMULAR #
###########################

def toggle_continuous_clicked(self, state):
    if state:
        # set continuous (call the actionbutton's function)
        self.w.btn_toggle_continuous.incr_action()
    else:
        # reset previously recorded increment
        ACTION.SET_JOG_INCR(self.L_incr, self.L_text)

10. Klassen-Patch Dateimanager-Widget

Anmerkung
Class Patching (Monkey Patching) ist ein wenig wie schwarze Magie - also verwenden Sie es nur bei Bedarf.

Das Dateimanager-Widget ist dazu gedacht, ein ausgewähltes Programm in LinuxCNC zu laden. Aber vielleicht möchten Sie zuerst den Dateinamen drucken.

Wir können die Bibliothek mit einem "Klassenpatch" versehen, um den Funktionsaufruf umzuleiten. In der IMPORT SECTION hinzufügen:

from qtvcp.widgets.file_manager import FileManager as FM

Hier werden wir:

  1. Behalten Sie einen Verweis auf die ursprüngliche Funktion (1), damit wir sie weiterhin aufrufen können

  2. Leiten Sie die Klasse um, damit sie stattdessen unsere benutzerdefinierte Funktion (2) in der Handler-Datei aufruft.

    ##########################################
    # Spezielle Funktionen, die von QtVCP aufgerufen werden
    ##########################################
    
    # Um Funktionen in Widgets zu ändern, können wir 'class patch' verwenden.
    # Klassenpatching muss vor der Instanziierung der Klasse erfolgen.
    def class_patch__(self):
        self.old_load = FM.load # behalte eine Referenz auf die alte Funktion <1>
        FM.load = self.our_load # Funktion auf unsere Handle-Datei-Funktion umleiten <2>
  3. Schreiben Sie eine benutzerdefinierte Funktion, um das Original zu ersetzen:
    Diese Funktion muss die gleiche Signatur wie die Originalfunktion haben.
    In diesem Beispiel werden wir immer noch die ursprüngliche Funktion aufrufen, indem wir den Verweis auf sie verwenden, den wir zuvor aufgezeichnet haben.
    Sie erfordert, dass das erste Argument die Widget-Instanz ist, was in diesem Fall self.w.filemanager ist (der Name, der im Qt Designer-Editor angegeben wurde).

    #####################
    # GENERAL FUNCTIONS #
    #####################
    
    def our_load(self,fname):
        print(fname)
        self.old_load(self.w.filemanager,fname)

Jetzt wird unsere benutzerdefinierte Funktion den Dateipfad in das Terminal ausgeben, bevor die Datei geladen wird. Offensichtlich langweilig, aber es zeigt das Prinzip.

Anmerkung

Es gibt noch eine andere, etwas andere Methode, die Vorteile haben kann: Sie können den Verweis auf die ursprüngliche Funktion in der ursprünglichen Klasse speichern.
Der Trick dabei ist, sicherzustellen, dass der Funktionsname, den Sie zum Speichern verwenden, nicht bereits in der Klasse verwendet wird.
super__ als Zusatz zum Funktionsnamen wäre eine gute Wahl.
Wir werden das in den eingebauten QtVCP-Widgets nicht verwenden.

##########################################
# Spezielle Funktionen, die von QtVCP aufgerufen werden
##########################################

# Um Funktionen in Widgets zu ändern, können wir 'class patch' verwenden.
# Klassenpatching muss vor der Instanziierung der Klasse erfolgen.
def class_patch__(self):
    FM.super__load = FM.load # eine Referenz auf die alte Funktion in der ursprünglichen Klasse behalten
    FM.load = self.our_load # Funktion auf unsere Handle-File-Funktion umlenken

#####################
# GENERAL FUNCTIONS #
#####################

def our_load(self,fname):
    print(fname)
    self.w.filemanager.super__load(fname)

11. Widgets programmatisch hinzufügen

In manchen Situationen ist es nur möglich, Widgets mit Python-Code hinzuzufügen, anstatt den Qt Designer-Editor zu verwenden.

Wenn QtVCP-Widgets programmatisch hinzugefügt werden, müssen manchmal zusätzliche Schritte unternommen werden.

Hier werden wir eine Spindeldrehzahl-Anzeigeleiste und eine LED für die aktuelle Geschwindigkeit in die Ecke eines Tab-Widgets einfügen. Qt Designer unterstützt das Hinzufügen von Eck-Widgets zu Tabs nicht, PyQt hingegen schon.

Dies ist ein gekürztes Beispiel aus der Handler-Datei von QtAxis screen.

Importieren erforderlicher Bibliotheken

Zunächst müssen wir die benötigten Bibliotheken importieren, sofern sie nicht bereits in der Handler-Datei enthalten sind:

  • QtWidgets gibt uns Zugriff auf die QProgressBar,

  • QColor ist für die LED-Farbe,

  • StateLED ist die QtVCP-Bibliothek, die zum Erstellen der Spindel-bei-Geschwindigkeit-LED verwendet wird,

  • Status wird verwendet, um LinuxCNC-Statusinformationen abzufangen,

  • Info gibt uns Informationen über die Maschinenkonfiguration.

############################
# **** IMPORT SECTION **** #
############################

from PyQt5 import QtWidgets
from PyQt5.QtGui import QColor
from qtvcp.widgets.state_led import StateLED as LED
from qtvcp.core import Status, Info
Instanziierung von Status- und Info-Kanälen

STATUS und INFO werden außerhalb der Handler-Klasse initialisiert, so dass es sich um globale Referenzen handelt (kein self. vorangestellt):

##########################################
**** Bibliotheken instanziieren Abschnitt **** #
###########################################

STATUS = Status()
INFO = Info()
Register STATUS Überwachungsfunktion

Für die Spindeldrehzahlanzeige müssen wir die aktuelle Spindeldrehzahl kennen. Dazu registrieren wir uns mit STATUS, um:

  • Erfassen des actual-spindle-speed-changed Signals

  • Aufruf der _Funktion self.update_spindle()

########################
# **** INITIALIZE **** #
########################
# Ermöglicht den Zugriff auf Widgets aus den QtVCP-Dateien.
# Zu diesem Zeitpunkt sind die Widgets und Hal-Pins noch nicht instanziiert
def __init__(self,halcomp,widgets,paths):
    self.hal = halcomp
    self.w = widgets
    self.PATHS = paths

    STATUS.connect('actual-spindle-speed-changed', \
        lambda w,speed: self.update_spindle(speed))
Hinzufügen der Widgets zur Registerkarte

Wir müssen sicherstellen, dass die Qt Designer Widgets bereits gebaut sind, bevor wir versuchen, sie zu ergänzen. Zu diesem Zweck fügen wir einen Aufruf der Funktion self.make_corner_widgets() hinzu, um unsere zusätzlichen Widgets zum richtigen Zeitpunkt zu erstellen, d.h. unter der Funktion initialized__():

##########################################
# Spezielle Funktionen, die von Qt Screen aufgerufen werden
##########################################

# at this point:
# the widgets are instantiated.
# the HAL pins are built but HAL is not set ready
def initialized__(self):
    self.make_corner_widgets()
Erstellen der Funktionen zum Erstellen von Widgets

Ok, lassen Sie uns die Funktion zum Erstellen der Widgets codieren und sie im Tab-Widget hinzufügen. Wir gehen davon aus, dass es ein mit Designer erstelltes Tab-Widget namens rightTab gibt.

Wir gehen davon aus, dass es ein Tab-Widget gibt, das mit Qt Designer gebaut wurde und rightTab heißt.

#######################################
# allgemeine (engl. general) functions #
#######################################

def make_corner_widgets(self):
    # make a spindle-at-speed green LED
    self.w.led = LED()                                        # <1>
    self.w.led.setProperty('is_spindle_at_speed_status',True) # <2>
    self.w.led.setProperty('color',QColor(0,255,0,255))       # <3>
    self.w.led.hal_init(HAL_NAME = 'spindle_is_at_speed')     # <4>

    # make a spindle speed bar
    self.w.rpm_bar = QtWidgets.QProgressBar()                 # <5>
    self.w.rpm_bar.setRange(0, INFO.MAX_SPINDLE_SPEED)        # <6>

    # container
    w = QtWidgets.QWidget()                                   # <7>
    w.setContentsMargins(0,0,0,6)
    w.setMinimumHeight(40)

    # layout
    hbox = QtWidgets.QHBoxLayout()                            # <8>
    hbox.addWidget(self.w.rpm_bar)                            # <9>
    hbox.addWidget(self.w.led)                                # <9>
    w.setLayout(hbox)

    # den Container zur Ecke des rechten Tab-Widgets hinzufügen
    self.w.rightTab.setCornerWidget(w) # <10>
  1. Dies initialisiert das grundlegende StateLed-Widget und verwendet von da an self.w.led als Referenz.

  2. Da die Zustands-LED für viele Anzeigen verwendet werden kann, müssen wir die Eigenschaft einstellen, die sie als LED für die Spindeldrehzahl kennzeichnet.

  3. Dadurch wird sie im eingeschalteten Zustand als grün angezeigt.

  4. Dies ist der zusätzliche Funktionsaufruf, der bei einigen QtVCP-Widgets erforderlich ist.
    Wenn HAL_NAME weggelassen wird, so wird der Objektname des Widgets verwendet, sofern es einen gibt.
    Es gibt den speziellen Widgets eine Referenz zu:

    self.HAL_GCOMP

    Die HAL-Komponenten-Instanz

    self.HAL_NAME

    Der Name dieses Widgets als String

    self.QT_OBJECT_

    dieses Widgets PyQt-Objekt als Instanz

    self.QTVCP_INSTANCE_

    Die übergeordnete Ebene des Bildschirms

    self.PATHS_

    Die _Instanz der Pfadbibliothek von QtVCP

    self.PREFS_

    die Instanz einer optionalen Präferenzdatei

    self.SETTINGS_

    das Qsettings Objekt

  5. Initialisiert einen PyQt5 QProgressBar.

  6. Setzt den maximalen Bereich des Fortschrittsbalkens auf den in der INI angegebenen Maximalwert.

  7. Wir erstellen ein QWidget
    Da man nur ein Widget in die Tab-Ecke einfügen kann und wir dort zwei haben wollen, müssen wir beide in einen Container einfügen.

  8. ein QHBoxLayout zum QWidget hinzufügen.

  9. Dann fügen wir unseren QProgress-Balken und die LED in das Layout ein.

  10. Schließlich fügen wir das QWidget (mit unserem QProgress-Balken und der LED darin) in die Ecke des Tab-Widgets ein.

Erstellen Sie die Überwachungsfunktion STATUS

Jetzt erstellen wir die Funktion, die den QProgressBar aktualisiert, wenn STATUS die Spindeldrehzahl aktualisiert:

#####################
# callbacks von STATUS #
#####################
def update_spindle(self, data):
    self.w.rpm_bar.setInvertedAppearance(bool(data<0))       # <1>
    self.w.rpm_bar.setFormat('{0:d} RPM'.format(int(data)))  # <2>
    self.w.rpm_bar.setValue(abs(data))                       # <3>
  1. In diesem Fall haben wir uns für die Darstellung von links nach rechts oder von rechts nach links entschieden, je nachdem, ob wir uns im oder gegen den Uhrzeigersinn drehen.

  2. Dadurch wird die Schrift in der Leiste formatiert.

  3. Damit legen Sie die Länge des farbigen Balkens fest.

12. Objekte periodisch aktualisieren/auslesen

Manchmal muss man ein Widget aktualisieren oder regelmäßig einen Wert auslesen, der von den normalen Bibliotheken nicht abgedeckt wird.

Hier aktualisieren wir eine LED auf der Grundlage eines überwachten HAL-Pins alle 100 ms.

Wir nehmen an, dass in der Qt Designer UI Datei eine LED mit dem Namen led vorhanden ist.

Laden Sie die Qhal-Bibliothek für den Zugriff auf die HAL-Komponente von QtVCP

In der IMPORT SECTION hinzufügen:

from qtvcp.core import Qhal
Instanziiere Qhal

Im Abschnitt INSTANTIATE LIBRARY hinzufügen:

QHAL = Qhal()

Fügen Sie nun diese Abschnitte hinzu bzw. ändern Sie sie so, dass sie einen ähnlichen Code enthalten wie dieser:

Registrierung einer Funktion, die im Zeitraum CYCLE_TIME aufgerufen wird

Dies geschieht normalerweise alle 100 ms.

########################
# **** INITIALIZE **** #
########################
# Ermöglicht den Zugriff auf Widgets aus den QtVCP-Dateien.
# Zu diesem Zeitpunkt sind die Widgets und Hal-Pins noch nicht instanziiert
def __init__(self,halcomp,widgets,paths):
    self.hal = halcomp
    self.w = widgets
    self.PATHS = paths

    # eine Funktion registrieren, die in einer CYCLE_TIME-Periode aufgerufen wird (normalerweise alle 100 ms)
    STATUS.connect('periodic', lambda w: self.update_periodic())
Erstellen Sie die benutzerdefinierte Funktion, die periodisch aufgerufen werden soll
#####################
# general functions #
#####################
def update_periodic(self):
    data = QHAL.getvalue('spindle.0.is-oriented')
    self.w.led.setState(data)

13. Externe Steuerung mit ZMQ

QtVCP kann automatisch ein ZMQ-Messaging einrichten, um Remote-Nachrichten von externen Programmen zu senden und/oder zu empfangen.

Es verwendet das ZMQ-Meldungsmuster Veröffentlichen/Abonnieren.

Wie immer sollte man die Sicherheit im Auge behalten, bevor man Programmen eine Schnittstelle für Nachrichtenübermittlung einräumt.

13.1. Lesen von ZMQ-Nachrichten

Manchmal möchte man den Bildschirm mit einem separaten Programm steuern.

Aktivieren des Empfangs von ZMQ-Nachrichten

Im Widget ScreenOptions können Sie die Eigenschaft use_receive_zmq_option auswählen.
Sie können diese Eigenschaft auch direkt in der Handler-Datei einstellen, wie in diesem Beispiel.

Wir nehmen an, dass das ScreenOptions-Widget in Qt Designer screen_options genannt wird:

########################
# **** INITIALIZE **** #
########################
# widgets erlaubt den Zugriff auf Widgets aus den QtVCP-Dateien.
# zu diesem Zeitpunkt sind die Widgets und Hal-Pins noch nicht instanziiert
def __init__(self, halcomp,widgets,paths):
    # directly select ZMQ message receiving
    self.w.screen_options.setProperty('use_receive_zmq_option',True)

Dies erlaubt einem externen Programm, Funktionen in der Handler-Datei aufzurufen.

Hinzufügen einer Funktion, die beim Empfang einer ZMQ-Nachricht aufgerufen wird

Fügen wir eine spezielle Funktion zum Testen hinzu. Sie müssen LinuxCNC von einem Terminal aus starten, um den gedruckten Text zu sehen.

#####################
# general functions #
#####################
def test_zmq_function(self, arg1, arg2):
    print('zmq_test_function called: ', arg1, arg2)
Erstellen eines externen Programms, das ZMQ-Nachrichten sendet, die einen Funktionsaufruf auslösen

Hier ist ein Beispiel für ein externes Programm zum Aufruf einer Funktion. Es wechselt jede Sekunde zwischen zwei Datensätzen. Führen Sie dies in einem separaten Terminal von LinuxCNC aus, um die gesendeten Nachrichten zu sehen.

#!/usr/bin/env python3
from time import sleep

import zmq
import json

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://127.0.0.1:5690")
topic = b'QtVCP'

# prebuilt message 1
# Erstellt ein dict  der aufzurufenden Funktion plus Argumenten
x = {                               # <1>
  "FUNCTION": "test_zmq_function",
  "ARGS": [True,200]
}
# Konvertiert zu JSON Objekt
m1 = json.dumps(x)

# prebuild message 2
x = {                               # <1>
  "FUNCTION": "test_zmq_function",
  "ARGS": [False,0],
}
# Konvertiert zu JSON Objekt
m2 = json.dumps(x)

if __name__ == '__main__':
    while True:
        print('send message 1')
        socket.send_multipart([topic, bytes((m1).encode('utf-8'))])
        sleep(ms(1000))

        print('send message 2')
        socket.send_multipart([topic, bytes((m2).encode('utf-8'))])
        sleep(ms(1000))
  1. Legen Sie die aufzurufende Funktion und die zu sendenden Argumente auf diese Funktion fest.

Sie müssen die Signatur der Funktion kennen, die Sie aufrufen möchten. Beachten Sie auch, dass die Nachricht in ein JSON-Objekt umgewandelt wird. Das liegt daran, dass ZMQ Byte-Nachrichten und keine Python-Objekte sendet. json konvertiert Python-Objekte in Bytes und wird beim Empfang wieder zurück konvertiert.

13.2. Schreiben von ZMQ-Nachrichten

Sie können auch mit einem externen Programm vom Bildschirm aus kommunizieren.

Im Widget ScreenOptions können Sie die Eigenschaft use_send_zmq_message auswählen. Sie können diese Eigenschaft auch direkt in der Handler-Datei einstellen, wie in diesem Beispiel.

Wir nehmen an, dass das ScreenOptions-Widget in Qt Designer screen_options genannt wird:

Senden von ZMQ-Nachrichten aktivieren
########################
# **** INITIALIZE **** #
########################
# widgets erlaubt den Zugriff auf Widgets aus den QtVCP-Dateien.
# zu diesem Zeitpunkt sind die Widgets und Hal-Pins noch nicht instanziiert
def __init__(self, halcomp,widgets,paths):
    # directly select ZMQ message sending
    self.w.screen_options.setProperty('use_send_zmq_option',True)

Dies ermöglicht das Senden von Nachrichten an ein separates Programm.
Welche Nachricht gesendet wird, hängt davon ab, was das externe Programm erwartet.

Erstellen einer Funktion zum Senden von ZMQ-Nachrichten

Lassen Sie uns eine spezielle Funktion zum Testen hinzufügen.
Sie müssen LinuxCNC von einem Terminal aus starten, um den gedruckten Text zu sehen.
Außerdem muss etwas hinzugefügt werden, um diese Funktion aufzurufen, wie z.B. ein Tastenklick.

######################
# allgemeine Funktionen #
######################
def send_zmq_message(self):
    # This could be any Python object JSON can convert
    message = {"name": "John", "age": 30}
    self.w.screen_options.send_zmq_message(message)
Verwenden oder erstellen Sie ein Programm, das ZMQ-Nachrichten empfangen kann

Hier ist ein Beispielprogramm, das die Nachricht empfängt und auf dem Terminal ausgibt:

import zmq
import json

# ZeroMQ Context
context = zmq.Context()

# Definition des Sockets mit Hilfe des "Context".
sock = context.socket(zmq.SUB)

# Definieren des Abonnements und der zu akzeptierenden Nachrichten ohne Einschränkung der Themen.
topic = "" # alle Themen
sock.setsockopt(zmq.SUBSCRIBE, topic)
sock.connect("tcp://127.0.0.1:5690")

while True:
    topic, message = sock.recv_multipart()
    print('{} sent message:{}'.format(topic,json.loads(message)))

14. Senden von Nachrichten an die Statusleiste oder Desktop-Benachrichtigungsdialoge

Es gibt mehrere Möglichkeiten, dem Benutzer Informationen zu übermitteln.

Eine Statusleiste wird für short information verwendet, um den Benutzer anzuzeigen.

Anmerkung
Nicht alle Bildschirme haben eine Statusleiste.
Beispiel für die Nutzung der Statusleiste
self.w.statusbar.showMessage(message, timeout * 1000)

timeout ist in Sekunden und wir nehmen an, dass "statusbar" der Name des Qt Designer Widgets ist.

Sie können auch die ‚Status‘-Bibliothek verwenden, um eine Nachricht an die ‚notify‘-Bibliothek zu senden, wenn diese aktiviert ist (normalerweise im ‚ScreenOptions‘-Widget eingestellt): Dadurch wird die Nachricht an die Statusleiste und den Desktop-Benachrichtigungsdialog (engl. notify dialog) gesendet.

Die Nachrichten werden auch aufgezeichnet, bis der Benutzer sie mit Hilfe der Bedienelemente löscht. Die Benutzer können alle aufgezeichneten Nachrichten abrufen.

Es gibt mehrere Optionen:

STATUS.TEMPORARY_MESSAGE

Anzeigen der Nachricht nur für eine kurze Zeit.

STATUS.OPERATOR_ERROR
STATUS.OPERATOR_TEXT
STATUS.NML_ERROR
STATUS.NML_TEXT

Beispiel für das Versenden einer Bedienermeldung:
STATUS.emit('error', STATUS.OPERATOR_ERROR, 'message')

Sie können Nachrichten über die Bedienernachrichtenfunktionen (engl. operator message functions) von LinuxCNC senden. Diese werden normalerweise vom Benachrichtigungssystem abgefangen, sind also gleich oben. Sie würden auch auf das Terminal gedruckt werden.

ACTION.SET_DISPLAY_MESSAGE('MESSAGE')
ACTION.SET_ERROR_MESSAGE('MESSAGE')

15. Fokusänderungen abfangen

Der Fokus wird verwendet, um Benutzeraktionen wie Tastatureingaben auf das richtige Widget zu lenken.

Aktuell fokussiertes Widget abrufen
fwidget = QtWidgets.QApplication.focusWidget()
if fwidget is not None:
    print("focus widget class: {} name: {} ".format(fwidget, fwidget.objectName()))
Fokussiertes Widget erhalten, wenn sich der Fokus ändert
# zu diesem Zeitpunkt:
# sind die Widgets instanziiert.
# die HAL-Pins sind gebaut, aber HAL ist nicht bereit
def initialized__(self):
    QtWidgets.QApplication.instance().event_filter.focusIn.connect(self.focusInChanged)

#######################################
# allgemeine (engl. general) functions #
#######################################

def focusInChanged(self, widget):
    if isinstance(widget.parent(),type(self.w.gcode_editor.editor)):
        print('G-code Editor')
    elif isinstance(widget,type(self.w.gcodegraphics)):
        print('G-code Display')
    elif isinstance(widget.parent(),type(self.w.mdihistory) ):
        print('MDI History')

Beachten Sie, dass wir manchmal mit widget, manchmal mit widget.parent() vergleichen.

Das liegt daran, dass einige QtVCP-Widgets aus mehreren Sub-Widgets aufgebaut sind und letztere tatsächlich den Fokus erhalten; daher müssen wir den Elternteil dieser Sub-Widgets überprüfen.

In anderen Fällen ist das Hauptwidget das, was den Fokus erhält, z. B. das G-Code-Anzeige-Widget kann so eingestellt werden, dass es den Fokus annimmt. In diesem Fall gibt es keine Unter-Widgets darin, so dass ein Vergleich mit widget.parent() den Container ergeben würde, der das G-Code-Widget enthält.

16. Lesen Sie die Optionen für die Ladezeit der Befehlszeile

Einige Panels benötigen zum Zeitpunkt des Ladens Informationen zur Einrichtung/Optionen. QtVCP deckt diese Anforderung mit -o-Optionen ab.
Das -o-Argument eignet sich für einige wenige, relativ kurze Optionen, die der Ladebefehlszeile hinzugefügt werden können.
Für umfangreichere Informationen ist das Lesen einer INI- oder Einstellungsdatei wahrscheinlich eine bessere Idee.

In der Befehlszeile können mehrere -o-Optionen verwendet werden, so dass Sie sie entschlüsseln müssen.
self.w.USEROPTIONS_' enthält alle gefundenen -o-Optionen als eine Liste von Strings. Sie müssen parsen und definieren, was akzeptiert wird und was damit zu tun ist.

Beispielcode zum Abrufen der -o Optionen für Kameranummer und Fenstergröße
    def initialized__(self):

        # setzt eine Standard-Kameranummer
        number = 0

        # prüfen, ob es überhaupt -o Optionen gibt
        if self.w.USEROPTIONS_ is not None:

            # wenn im Debug-Modus die Optionen auf dem Terminal ausgegeben werden
            LOG.debug('cam_align user options: {}'.format(self.w.USEROPTIONS_))

            # Gehe die gefundenen Optionen eine nach der anderen durch
            for num, i in enumerate(self.w.USEROPTIONS_):

                # wenn die Option -o 'size=' enthält, wird angenommen, dass es die Breite und Höhe des Fensters ist
                # Überschreibe die Standardbreite und -höhe des Fensters
                if 'size=' in self.w.USEROPTIONS_[num]:
                    try:
                        strg = self.w.USEROPTIONS_[num].strip('size=')
                        arg = strg.split(',')
                        self.w.resize(int(arg[0]),int(arg[1]))
                    except Exception as e:
                        print('Error with cam_align size setting:',self.w.USEROPTIONS_[num])

                #  # wenn die Option -o 'camnumber=' enthält, wird angenommen, dass dies die zu verwendende Kameranummer ist
                elif 'camnumber=' in self.w.USEROPTIONS_[num]:
                    try:
                        number = int(self.w.USEROPTIONS_[num].strip('camnumber='))
                    except Exception as e:
                        print('Fehler mit cam_align Kamera Auswahl - das ist keine Nummer - verwende 0')

        # setzt die Kameranummer entweder als Standard oder wenn die Option -o die Variable 'number' geändert hat, auf diese Nummer.
        self.w.camview._camNum = number