1. Preference File Loading/Saving

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

Prerequisites
  • Preference file option must be set in the ScreenOptions widget.

  • Preference file path must be set in the INI configuration.

Reading preferences at launch time

Under the def initialized__(self): function add:

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')
Writing preferences at close time

In the closing_cleanup__() function, add:

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. Use QSettings To Read/Save Variables

Here is how to load and save variables using PyQt’s QSettings functions:

Good practices
  • Use Group to keep names organized and unique.

  • Account for none value returned when reading a setting which has no entry.

  • Set defaults to cover the first time it is run using the or _<default_value>_ syntax.

Anmerkung
The file is actually saved in ~/.config/QtVcp
Beispiel

In diesem Beispiel:

  • We add or 20 and or 2.5 as defaults.

  • The names MyGroupName, int_value, float_value, myInteger, and myFloat are user defined.

  • Under the def initialized__(self): function add:

    # 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()
  • Under the def closing_cleanup__(self): function add:

    # 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. Add A Basic Style Editor

Being able to edit a style on a running screen is convenient.

Import StyleSheetEditor module in the IMPORT SECTION:
from qtvcp.widgets.stylesheeteditor import StyleSheetEditor as SSE
Instantiate StyleSheetEditor module in the INSTANTIATE SECTION:
STYLEEDITOR = SSE()
Create a keybinding in the INITIALIZE SECTION:

Under the +__init__.(self, halcomp, widgets, paths):+ function add:

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 uses STATUS messages to pop up and return information from dialogs.

Prebuilt dialogs keep track of their last position and include options for focus shading and sound.

To get information back from the dialog requires using a STATUS general message.

Import and Instantiate the Status module in the IMPORT SECTION
from qtvcp.core import Status
STATUS = Status()

This loads and initializes the Status library.

Register function for STATUS general messages in the INITIALIZE SECTION

Under the +__init__.(self, halcomp, widgets, paths)+ function:

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

This registers STATUS to call the function self.return_value when a general message is sent.

Add entry dialog request function in the GENERAL FUNCTIONS section
def request_number(self):
    mess = {'NAME':'ENTRY','ID':'FORM__NUMBER', 'TITLE':'Set Tool Offset'}
    STATUS.emit('dialog-request', mess)

The function

  • creates a Python dict with:

    • 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

Add message data processing function in the CALLBACKS FROM STATUS section.
# Process the STATUS return message from 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))

This catches all general messages so it must check the dialog type and id code to confirm it’s our dialog. In this case we had requested an ENTRY dialog and our unique id was FORM_NUMBER, so now we know the message is for us. ENTRY or CALCULATOR dialogs return a float number.

5. Sprechen Sie eine Startup-Begrüßung

This requires the espeak library installed on the system.

Import and instantiate the Status in the IMPORT section
from qtvcp.core import Status
STATUS = Status()
Emit spoken message in the 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

Toolbar buttons and submenus are added in Qt Designer but the code to make them do something is added in the handler file. To add a submenus in Qt Designer:

  • Add a Qaction by typing in the toolbar column then clicking the + icon on the right.

  • This will add a sub column that you need to type a name into.

  • Now the original Qaction will be a Qmenu instead.

  • Now erase the Qaction you added to that Qmenu, the menu will stay as a menu.

In this example we assume you added a toolbar with one submenu and three actions. These actions will be configured to create:

  • a recent file selection menu,

  • an about pop up dialog action,

  • a quit program action, and

  • 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 this example the:

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

  • Aktionsnamen müssen 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"

Under the def initialized__(self) function add:

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)
Define the user function in the GENERAL FUNCTIONS SECTION
def my_function(self, widget, state):
    print('My function State = ()'.format(state))

The function to be called if the action "My Function" button is pressed.

7. Add HAL Pins That Call Functions

In this way you don’t need to poll the state of input pins.

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

This is to allow access to QtVCP’s HAL component.

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

Under the initialised__ function, make sure there is an entry similar to this:

##########################################
# 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. Add A Special Max Velocity Slider Based On Percent

Some times you want to build a widget to do something not built in. The built in Max velocity slider acts on units per minute, here we show how to do on percent.

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

Generally selecting continuous jogging is a momentary button, that requires you to select the previous jog increment after.

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.

  • Use Qt Designer’s slot editor to use the button signal clicked(bool) to call form’s handler function toggle_continuous_clicked().
    See Using Qt Designer To Add Slots section for more information.

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"

In the GENERAL FUNCTIONS SECTION add:

#####################
# 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. Class Patch The File Manager Widget

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

The File manager widget is designed to load a selected program in LinuxCNC. But maybe you want to print the file name first.

We can "class patch" the library to redirect the function call. In the IMPORT SECTION add:

from qtvcp.widgets.file_manager import FileManager as FM

Here we are going to:

  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.

    ##########################################
    # Special Functions called from QtVCP    #
    ##########################################
    
    # For changing functions in widgets we can 'class patch'.
    # class patching must be done before the class is instantiated.
    def class_patch__(self):
        self.old_load = FM.load # keep a reference of the old function <1>
        FM.load = self.our_load # redirect function to our handle file function <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)

Now our custom function will print the file path to the terminal before loading the file. Obviously boring but shows the principle.

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. Adding Widgets Programmatically

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.

Here we are going to add a spindle speed indicator bar and up-to-speed LED to a tab widget corner. Qt Designer does not support adding corner widgets to tabs but PyQt does.

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 gives us information about the machine configuration.

############################
# **** 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

For the spindle speed indicator we need to know the current spindle speed. For this we register with STATUS to:

  • Catch the actual-spindle-speed-changed signal

  • Aufruf der _Funktion self.update_spindle()

########################
# **** INITIALIZE **** #
########################
# Widgets allow access to widgets from the QtVCP files.
# At this point the widgets and HAL pins are not instantiated.
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

We need to make sure the Qt Designer widgets are already built before we try to add to them. For this, we add a call to self.make_corner_widgets() function to build our extra widgets at the right time, i.e. under the initialized__() function:

##########################################
# Special Functions called from QtScreen #
##########################################

# 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()
Create the widgets building functions

Ok let’s code the function to build the widgets and add them in the tab widget. We are assuming there is a tab widget built with Designer called rightTab.

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. This is the extra function call required with some QtVCP widgets.
    If HAL_NAME is omitted it will use the widget’s objectName if there is one.
    It gives the special widgets reference to:

    self.HAL_GCOMP

    the HAL component instance

    self.HAL_NAME

    This widget’s name as a 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. This sets the length of the colored bar.

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.

Here we update an LED based on a watched HAL pin every 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 the IMPORT SECTION add:

from qtvcp.core import Qhal
Instanziiere Qhal

In the INSTANTIATE LIBRARY SECTION add:

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

This is usually every 100 ms.

########################
# **** INITIALIZE **** #
########################
# widgets allows access to widgets from the QtVCP files
# at this point the widgets and hal pins are not instantiated
def __init__(self,halcomp,widgets,paths):
    self.hal = halcomp
    self.w = widgets
    self.PATHS = paths

    # register a function to be called at CYCLE_TIME period (usually every 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. External Control With ZMQ

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

It uses ZMQ’s publish/subscribe messaging pattern.

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

13.1. ZMQ Messages Reading

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 allows access to widgets from the QtVCP files
# at this point the widgets and hal pins are not instantiated
def __init__(self,halcomp,widgets,paths):
    # directly select ZMQ message receiving
    self.w.screen_options.setProperty('use_receive_zmq_option',True)

This allows an external program to call functions in the handler file.

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

Let’s add a specific function for testing. You will need to run LinuxCNC from a terminal to see the printed text.

#####################
# 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

Here is a sample external program to call a function. It alternates between two data sets every second. Run this in a separate terminal from LinuxCNC to see the sent messages.

#!/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
# makes a dict of function to call plus any arguments
x = {                               # <1>
  "FUNCTION": "test_zmq_function",
  "ARGS": [True,200]
}
# convert to JSON object
m1 = json.dumps(x)

# prebuild message 2
x = {                               # <1>
  "FUNCTION": "test_zmq_function",
  "ARGS": [False,0],
}
# convert to JSON object
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.

You will need to know the signature of the function you wish to call. Also note that the message is converted to a JSON object. This is because ZMQ sends byte messages not Python objects. json converts Python objects to bytes and will be converted back when received.

13.2. ZMQ Messages Writing

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

In the ScreenOptions widget, you can select the property use_send_zmq_message. You can also set this property directly in the handler file, as in this sample.

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.

#####################
# general functions #
#####################
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. Sending Messages To Status Bar Or Desktop Notify Dialogs

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

A status bar is used for short information to show the user.

Anmerkung
Nicht alle Bildschirme haben eine Statusleiste.
Status bar usage example
self.w.statusbar.showMessage(message, timeout * 1000)

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

You can also use the Status library to send a message to the notify library if it is enabled (usually set in ScreenOptions widget): This will send the message to the statusbar and the desktop notify dialog.

The messages are also recorded until the user erases them using controls. The users can recall any recorded messages.

There are several options:

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')

You can send messages thru LinuxCNC’s operator message functions. These are usually caught by the notify system, so are equal to above. They would be printed to the terminal as well.

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.

Other times the main widget is what gets the focus, e.g., the G-code display widget can be set to accept the focus. In that case there are no sub-widgets in it, so comparing to the widget.parent() would get you the container that holds the G-code widget.

16. Read Command Line Load Time Options

Some panels need information at load time for setup/options. QtVCP covers this requirement with -o options.
The -o argument is good for a few, relatively short options, that can be added to the loading command line.
For more involved information, reading an INI or preference file is probably a better idea.

Multiple -o options can be used on the command line so you must decode them.
self.w.USEROPTIONS_ will hold any found -o options as a list of strings. You must parse and define what is accepted and what to do with it.

Example code to get -o options for camera number and window size
    def initialized__(self):

        # set a default camera number
        number = 0

        # check if there are any -o options at all
        if self.w.USEROPTIONS_ is not None:

            # if in debug mode print the options to the terminal
            LOG.debug('cam_align user options: {}'.format(self.w.USEROPTIONS_))

            # go through the found options one by one
            for num, i in enumerate(self.w.USEROPTIONS_):

                # if the -o option has 'size=' in it, assume it's width and height of window
                # override the default width and height of the window
                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])

                #  # if the -o option has 'camnumber=' in it, assume it's the camera number to use
                elif 'camnumber=' in self.w.USEROPTIONS_[num]:
                    try:
                        number = int(self.w.USEROPTIONS_[num].strip('camnumber='))
                    except Exception as e:
                        print('Error with cam_align camera selection - not a number - using 0')

        # set the camera number either as default or if -o option changed the 'number' variable, to that number.
        self.w.camview._camNum = number