QtVCP ist eine Infrastruktur zum Erstellen von benutzerdefinierten CNC-Bildschirmen oder Bedienfeldern für LinuxCNC.
Es zeigt eine .ui
-Datei an, die mit dem Qt Designer-Bildschirmeditor erstellt wurde, und kombiniert diese mit Python-Programmierung, um einen GUI-Bildschirm für den Betrieb einer CNC-Maschine zu erstellen.
QtVCP is completely customizable: you can add different buttons and status LEDs etc. or add Python code for even finer grain customization.
1. Schaukasten
Einige Beispiele für mit QtVCP erstellte Bildschirme und virtuelle Bedienfelder:
2. Übersicht
Zwei Dateien werden, einzeln oder in Kombination, verwendet, um Anpassungen vorzunehmen:
-
Eine UI-Datei, bei der es sich um eine XML-Datei handelt, die mit dem grafischen Editor Qt Designer erstellt wurde.
-
Eine Handler-Datei, die eine Textdatei mit Python-Code ist.
Normalerweise verwendet QtVCP die standardmäßige UI- und Handler-Datei, aber Sie können QtVCP so einstellen, dass es "lokale" UI- und Handler-Dateien verwendet.
Eine lokale Datei ist eine Datei, die sich im Konfigurationsordner befindet, der den Rest der Anforderungen des Rechners definiert.
Man ist nicht darauf beschränkt, ein benutzerdefiniertes Panel auf der rechten Seite oder eine benutzerdefinierte Registerkarte hinzuzufügen, da QtVCP den Qt Designer (den Editor) und PyQT5 (das Widget-Toolkit) nutzt.
QtVCP hat einige spezielle LinuxCNC Widgets und Aktionen hinzugefügt.
Es gibt spezielle Widgets, um Widgets von Drittanbietern mit HAL-Pins zu verbinden.
Es ist möglich, Widget-Antworten zu erstellen, indem man Signale mit Python-Code in der Handler-Datei verbindet.
2.1. QtVCP Widgets
QtVCP nutzt die PyQt5-Toolkits für die Einbeziehung von LinuxCNC.
Widget is the general name for user interface objects such as buttons and labels in PyQT5.
You are free to use any available default widgets in the Qt Designer editor.
There are also special widgets made for LinuxCNC that make integration easier.
These are split in three heading on the left side of the editor:
-
One is for HAL only widgets;
-
One is for CNC control widgets;
-
One is for Dialog widgets.
You are free to mix them in any way on your panel.
A very important widget for CNC control is the ScreenOptions
widget: It does not add anything visually to the screen but, allows important details to be selected rather then be coded in the handler file.
2.2. INI-Einstellungen
If you are using QtVCP to make a CNC motion control screen (rather then a HAL based panel), in the INI file, in the [DISPLAY]
section, add a line with the following pattern:
DISPLAY = qtvcp <options> <screen_name>
Anmerkung
|
All <options> must appear before <screen_name> . |
-
-d
Debugging on. -
-i
Enable info output. -
-v
Enable verbose debug output. -
-q
Enable only error debug output. -
-a
Set window always on top. -
-c NAME
HAL component name. Default is to use the UI file name. -
-g GEOMETRY
Set geometry WIDTHxHEIGHT+XOFFSET+YOFFSET. Values are in pixel units, XOFFSET/YOFFSET is referenced from top left of screen. Use -g WIDTHxHEIGHT for just setting size or -g +XOFFSET+YOFFSET for just position. Example:-g 200x400+0+100
-
-H FILE
Execute hal statements from FILE with halcmd after the component is set up and ready. -
-m
Maximize window. -
-f
Fullscreen the window. -
-t THEME
Default is system theme -
-x XID
Embed into a X11 window that doesn’t support embedding. -
--push_xid
Sendet die X11-Fenster-Identifikationsnummer von QtVCP an die Standardausgabe; zum Einbetten. -
-u USERMOD
File path of a substitute handler file. -
-o USEROPTS
Pass a string to QtVCP’s handler file underself.w.USEROPTIONS_
list variable. Can be multiple -o.
<screen_name>
is the base name of the .ui and _handler.py files. If <screen_name>
is missing, the default screen will be loaded.
QtVCP assumes the UI file and the handler file use the same base name. QtVCP will first search the LinuxCNC configuration directory that was launched for the files, then in the system skin folder holding standard screens.
[DISPLAY] CYCLE_TIME = 100 GRAPHICS_CYCLE_TIME = 100 HALPIN_CYCLE = 100
Adjusts the response rate of the GUI updates in milliseconds. Defaults to 100, useable range 50 - 200.
Die Widgets, Grafiken und die HAL-Pin-Aktualisierung können separat eingestellt werden.
Wenn die Aktualisierungszeit nicht richtig eingestellt ist, kann der Bildschirm nicht mehr reagieren oder stark ruckeln.
2.3. Qt Designer UI Datei
Eine Qt Designer-Datei ist eine Textdatei, die im XML-Standard organisiert ist und das Layout und die Widgets des Bildschirms beschreibt.
PyQt5 verwendet diese Datei, um die Anzeige zu erstellen und auf diese Widgets zu reagieren.
Der Qt Designer Editor macht es relativ einfach, diese Datei zu erstellen und zu bearbeiten.
2.4. Handler-Dateien
Eine Handler-Datei ist eine Datei, die Python-Code enthält, der zu den QtVCP-Standardroutinen hinzugefügt wird.
A handler file allows one to modify defaults, or add logic to a QtVCP screen without having to modify QtVCP’s core code. In this way you can have custom behaviors.
If present a handler file will be loaded. Only one file is allowed.
2.5. Bibliotheken Module
QtVCP, as built, does little more than display the screen and react to widgets. For more prebuilt behaviors there are available libraries (found in lib/python/qtvcp/lib
in RIP LinuxCNC install).
Libraries are prebuilt Python modules that add features to QtVCP. In this way you can select what features you want - yet don’t have to build common ones yourself.
Such libraries include:
-
audio_player
-
aux_program_loader
-
keybindings
-
message
-
preferences
-
notify
-
virtual_keyboard
-
machine_log
2.6. Themen
Designs sind eine Möglichkeit, das look and feel der Widgets auf dem Bildschirm zu ändern.
Zum Beispiel kann die Farbe oder Größe von Schaltflächen und Schiebereglern mit Hilfe von Themen geändert werden.
Das Windows-Thema ist der Standard für Bildschirme.
Das Systemthema ist der Standard für Bedienfelder.
To see available themes, they can be loaded by running the following command in a terminal:
qtvcp -d -t <theme_name>
QtVCP kann auch mit Qt-Stylesheets (QSS) unter Verwendung von CSS angepasst werden.
2.7. Lokale Dateien
If present, local UI/QSS/Python files in the configuration folder will be loaded instead of the stock UI files.
Local UI/QSS/Python files allow you to use your customized designs rather than the default screens.
QtVCP sucht nach einem Ordner mit dem Namen <screen_name> (im Ordner für die Startkonfiguration, der die INI-Datei enthält).
In diesem Ordner lädt QtVCP jede der folgenden Dateien:
-
_<screen_name>_.ui
, -
<screen_name>_handler.py
, und -
_<screen_name>_.qss
.
2.8. Veränderung mitglieferter Bildschirmmasken
Es gibt drei Möglichkeiten, einen Bildschirm/Panel anzupassen.
2.8.1. Minor StyleSheet Changes
Stylesheets can be used to set Qt properties. If a widget uses properties then they usually can be modified by stylesheets.
State_LED #name_of_led{ qproperty-color: red; qproperty-diameter: 20; qproperty-flashRate: 150; }
2.8.2. Handler Patching - Subclassing Builtin Screens
We can have QtVCP load a subclassed version of the standard handler file. in that file we can manipulate the original functions or add new ones.
Subclassing just means our handler file first loads the original handler file and adds our new code on top of it - so a patch of changes.
This is useful for changing/adding behaviour while still retaining standard handler updates from LinuxCNC repositories.
You may still need to use the handler copy dialog to copy the original handler file to decide how to patch it. See custom handler file
There should be a folder in the config folder; for screens: named <CONFIG FOLDER>/qtvcp/screens/<SCREEN NAME>/
add the handle patch file there, named like so <ORIGINAL SCREEN NAME>_handler.py
ie for Qtdragon the file would be called qtdragon_handler.py
Here is a sample to add X axis jog pins to a screen like Qtdragon:
import sys import importlib from qtvcp.core import Path, Qhal, Action PATH = Path() QHAL = Qhal() ACTION = Action() # get reference to original handler file so we can subclass it sys.path.insert(0, PATH.SCREENDIR) module = "{}.{}_handler".format(PATH.BASEPATH,PATH.BASEPATH) mod = importlib.import_module(module, PATH.SCREENDIR) sys.path.remove(PATH.SCREENDIR) HandlerClass = mod.HandlerClass # return our subclassed handler object to QtVCP def get_handlers(halcomp, widgets, paths): return [UserHandlerClass(halcomp, widgets, paths)] # sub class HandlerClass which was imported above class UserHandlerClass(HandlerClass): # add a terminal message so we know this got loaded print('\nCustom subclassed handler patch loaded.\n') def init_pins(self): # call original handler init_pins function super().init_pins() # add jog pins X axis pin = QHAL.newpin("jog.axis.jog-x-plus", QHAL.HAL_BIT, QHAL.HAL_IN) pin.value_changed.connect(lambda s: self.kb_jog(s, 0, 1, fast = False, linear = True)) pin = QHAL.newpin("jog.axis.jog-x-minus", QHAL.HAL_BIT, QHAL.HAL_IN) pin.value_changed.connect(lambda s: self.kb_jog(s, 0, -1, fast = False, linear = True))
2.8.3. Minor Python Code Changes
Another Python file can be used to add commands to the screen, after the handler file is parsed. This can be useful for minor changes while still honouring standard handler updates from LinuxCNC repositories.
Anmerkung
|
Handler patching is a better way to add changes - instance patching is black magic voodoo - this is here for legacy reasons. |
In the INI file under the [DISPLAY]
heading add USER_COMMAND_FILE = _PATH_
PATH can be any valid path. It can use ~
for home directory or WORKINGFOLDER
or CONFIGFOLDER
to represent QtVCP’s idea of those directories:
[DISPLAY] USER_COMMAND_FILE = CONFIGFOLDER/<screen_name_added_commands>
If no entry is found in the INI, QtVCP will look in the default path. The default path is in the configuration directory as a hidden file using the screen basename and rc, e.g., CONFIGURATION DIRECTORY/.<screen_name>rc
.
Diese Datei wird als Python-Code im handler-Dateikontext gelesen und ausgeführt.
Only local functions and local attributes can be referenced.
Global libraries defined in the screen’s handler file can be referenced by importing the handler file.
These are usually seen as all capital words with no preceding self.
self references the window class functions
self.w typically references the widgets
Was verwendet werden kann, mag je nach Bildschirm und Entwicklungszyklus variieren.
Reference the main window to change the title (won’t show if using INI entries for title change).
self.w.setWindowTitle('Mein Titel-Test')
This could work with the Qtdragon screen’s handler file.
Here we show how to add new functions and override existing ones.
# Needed to instance patch. # reference: https://ruivieira.dev/python-monkey-patching-for-readability.html import types # import the handlerfile to get reference to its libraries. # use <screenname>_handler import qtdragon_handler as hdlr # This is actually an unbounded function with 'obj' as a parameter. # You call this function without the usual preceding 'self.'. # This is because will will not be patching it into the original handler class instance # It will only be called from code in this file. def test_function(obj): print(dir(obj)) # This is a new function we will added to the existing handler class instance. # Notice it calls the unbounded function with 'self' as an parameter 'self' is the only global reference available. # It references the window instance. def on_keycall_F10(self,event,state,shift,cntrl): if state: print ('F10') test_function(self) # This will be used to override an existing function in the existing handler class instance. # Note, we also call a copy of the original function too. # This shows how to extend an existing function to do extra functions. def on_keycall_F11(self,event,state,shift,cntrl): if state: self.on_keycall_F11_super(event,state,shift,cntrl) print ('Hello') # We are referencing the KEYBIND library that was instantiated in the original handler class instance # by adding 'hdlr.' to it (from the imp). # This function tells KEYBIND to call 'on_keycall_F10' when F10 is pressed. hdlr.KEYBIND.add_call('Key_F10','on_keycall_F10') # Here we are instance patching the original handler file to add a new function # that calls our new function (of the same name) defined in this file. self.on_keycall_F10 = types.MethodType(on_keycall_F10, self) # Here we are defining a copy of the original 'on_keycall_F11' function, # so we can call it later. We can use any valid, unused function name. # We need to do this before overriding the original function. self.on_keycall_F11_super = self.on_keycall_F11 # Here we are instance patching the original handler file to override an existing function # to point to our new function (of the same name) defined in this file. self.on_keycall_F11 = types.MethodType(on_keycall_F11, self) # add a new pin to the screen: # pin callback to print the state def new_pin_changed(data): print(data) # Special function that gets called before the HAL component is set ready. # Here we used the function to add a bit input pin with a callback. def after_override__(self): try: pin = hdlr.QHAL.newpin("new_pin", hdlr.QHAL.HAL_BIT, hdlr.QHAL.HAL_IN) pin.value_changed.connect(new_pin_changed) except Exception as e: print(e) # Here we are instance patching the original handler file to add a new function # that calls our new function (of the same name) defined in this file. self.after_override__ = types.MethodType(after_override__, self)
2.8.4. Full Creative Control with custom handler/ui files
If you wish to modify a stock screen with full control, copy its UI and handler file to your configuration folder.
Es gibt ein QtVCP-Panel, das dabei hilft:
-
Öffnen Sie ein Terminal und führen den folgenden Befehl aus:
qtvcp copy
-
Wählen Sie den Bildschirm und den Zielordner im Dialog
-
Wenn Sie Ihren Bildschirm anders benennen möchten als den Standardnamen des eingebauten Bildschirms, ändern Sie den Basisnamen im Bearbeitungsfeld.
-
There should be a folder in the config folder; for screens: named <CONFIG FOLDER>/qtvcp/screens/ for panels: named <CONFIG FOLDER>/qtvcp/panels/ add the folders if they are missing and copy your folder/files in it.
-
Bestätigen, um alle Dateien zu kopieren
-
Delete the files you don’t wish to modify so that the original files will be used.
3. VCP-Paneele
QtVCP kann verwendet werden, um Bedienfelder zu erstellen, die mit HAL verbunden sind.
3.1. Builtin Panels
Es sind mehrere integrierte HAL-Panels verfügbar.
Geben Sie in einem Terminal qtvcp <return>
ein, um eine Liste zu sehen:
-
test_panel
-
Sammlung nützlicher Widgets zum Testen von HAL-Komponenten, einschließlich der Anzeige des LED-Zustands.
-
cam_align
-
Ein Kameraanzeige-Widget für die Rotationsausrichtung.
-
sim_panel
-
A small control panel to simulate MPG jogging controls etc.
For simulated configurations. -
vismach_mill_xyz
-
3D OpenGL view of a 3-axis milling machine.
You can load these from the terminal or from a HAL file with this basic command:
loadusr qtvcp test_panel
But more typically like this:
loadusr -Wn test_panel qtvcp test_panel
In this way HAL will wait till the HAL pins are made before continuing on.
3.2. Benutzerdefinierte Bedienfelder
Sie können natürlich Ihr eigenes Panel erstellen und laden.
Wenn Sie eine UI-Datei mit dem Namen my_panel.ui
und eine HAL-Datei mit dem Namen my_panel.hal
erstellt haben, würden Sie diese dann von einem Terminal aus laden mit:
halrun -I -f my_panel.hal
# load realtime components loadrt threads loadrt classicladder_rt # load non-realtime programs loadusr classicladder loadusr -Wn my_panel qtvcp my_panel.ui # <1> # Komponenten zum Thread hinzufügen addf classicladder.0.refresh thread1 # Pins verbinden net bit-input1 test_panel.checkbox_1 classicladder.0.in-00 net bit-hide test_panel.checkbox_4 classicladder.0.hide_gui net bit-output1 test_panel.led_1 classicladder.0.out-00 net s32-in1 test_panel.doublescale_1-s classicladder.0.s32in-00 # start thread start
-
In diesem Fall laden wir
qtvcp
mit-Wn
, das wartet, bis das Panel das Laden beendet hat, bevor es mit der Ausführung des nächsten HAL-Befehls fortfährt.
Damit soll gewährleistet werden, dass die vom Panel erstellten HAL-Pins tatsächlich fertig sind, falls sie im Rest der Datei verwendet werden.
4. Build A Simple Clean-sheet Custom Screen
4.1. Übersicht
So erstellen Sie ein Bedienfeld oder einen Bildschirm:
-
Verwenden Sie Qt Designer, um ein Design zu erstellen, das Ihnen gefällt, und speichern Sie es in Ihrem Konfigurationsordner unter einem Namen Ihrer Wahl, der mit
.ui
endet -
Ändern Sie die Konfigurations-INI-Datei, um QtVCP mit Ihrer neuen
.ui
-Datei zu laden. -
Dann verbinden Sie alle erforderlichen HAL-Kenntnisse in einer HAL-Datei.
4.2. Holen Sie sich Qt Designer, um LinuxCNC-Widgets einzubinden
Zuerst müssen Sie den Qt Designer installieren.
Die folgenden Befehle sollten ihn zu Ihrem System hinzufügen, oder verwenden Sie Ihren Paketmanager, um dasselbe zu tun:
sudo apt-get install qttools5-dev-tools qttools5-dev libpython3-dev
qtvcp_plugin.py
zum Qt Designer SuchpfadDann müssen Sie einen Link zu qtvcp_plugin.py
in einem der Ordner hinzufügen, in denen Qt Designer suchen wird.
In einer RIP (engl. Abkürzung von "run in place", d.h. das Programm started dort wo es durch den Quellcode auch kompiliert wurde) Version von LinuxCNC wird qtvcp_plugin.py
sein:
'~/LINUXCNC_PROJECT_NAME/lib/python/qtvcp/plugins/qtvcp_plugin.py'
For a Package installed version it should be:
'usr/lib/python2.7/qtvcp/plugins/qtvcp_plugin.py' or
'usr/lib/python2.7/dist-packages/qtvcp/plugins/qtvcp_plugin.py'
Legen Sie einen symbolischen Link auf die obige Datei an und verschieben Sie sie an einen der Orte, an denen Qt Designer sucht.
Qt Designer sucht an diesen beiden Stellen nach Links (wählen Sie einen aus):
'/usr/lib/x86_64-linux-gnu/qt5/plugins/designer/python' or
'~/.designer/plugins/python'
Möglicherweise müssen Sie den Ordner plugins/python
erstellen.
-
Für eine RIP-Installation:
Öffnen Sie ein Terminal, setzen Sie die Umgebungsvariablen für LinuxCNC <1>, dann laden Sie Qt Designer <2> mit :. scripts/rip-environment <1> designer -qt=5 <2>
-
Für eine Paketinstallation:
Öffnen Sie ein Terminal und geben Sie ein:designer -qt=5
Wenn alles gut geht, wird Qt Designer gestartet und Sie werden die auswählbaren LinuxCNC Widgets auf der linken Seite sehen.
4.3. Build The Screen .ui
File
MainWindow
WidgetWenn Qt Designer zum ersten Mal gestartet wird, erscheint ein 'New Form' Dialog.
Wählen Sie 'Main Window' und drücken Sie die Schaltfläche 'Create'.
Ein MainWindow
-Widget wird angezeigt.
We are going to make this window a specific non resizeable size:
-
Fassen Sie die Ecke des Fensters an und ändern Sie die Größe auf eine geeignete Größe, z. B. 1000x600.
-
Klicken Sie mit der rechten Maustaste auf das Fenster und klicken Sie auf Mindestgröße einstellen.
-
Wiederholen Sie dies und stellen Sie maximale Größe ein.
Unser Beispiel-Widget ist nun nicht mehr größenveränderbar.
ScreenOptions
Ziehen Sie das ScreenOptions
-Widget per Drag-and-Drop an eine beliebige Stelle im Hauptfenster.
Dieses Widget fügt visuell nichts hinzu, richtet aber einige allgemeine Optionen ein.
Es wird empfohlen, dieses Widget immer vor allen anderen hinzuzufügen.
Klicken Sie mit der rechten Maustaste auf das Hauptfenster, nicht auf das "ScreenOptions"-Widget, und stellen Sie "Layout" auf "Vertikal", um "ScreenOptions" in voller Größe anzuzeigen.
Auf der rechten Seite befindet sich ein Panel mit Registerkarten für einen Eigenschaftseditor und einen Objektinspektor.
Klicken Sie im Objektinspektor auf ScreenOptions.
Wechseln Sie dann zum Eigenschaftseditor (engl. property editor) und schalten Sie unter der Überschrift ScreenOptions die filedialog_option
um.
Ziehen Sie ein GCodeGraphics
widget und ein GcodeEditor
widget per Drag and Drop.
Platzieren Sie sie und ändern Sie die Größe, wie Sie es für richtig halten, und lassen Sie etwas Platz für Schaltflächen.
Fügen Sie dem Hauptfenster 7 Aktionsschaltflächen hinzu.
Wenn Sie auf die Schaltfläche (engl. button) doppelklicken, können Sie Text hinzufügen.
Bearbeiten Sie die Schaltflächenbeschriftungen für "Notaus" (engl. "E-stop"), "Maschine ein", Referenzpunkt (engl. "Home"), "Laden" (engl. "Load"), "Ausführen" (engl. "Run"), "Pause" und "Stopp".
Aktionsschaltflächen sind standardmäßig auf keine Aktion eingestellt, daher müssen wir die Eigenschaften für definierte Funktionen ändern. Sie können die Eigenschaften bearbeiten:
-
direkt im Eigenschaften-Editor auf der rechten Seite des Qt-Designers, oder
-
praktischerweise lassen sich durch einen Doppelklick mit der linken Maustaste auf die Schaltfläche ein Dialogfeld "Eigenschaften" aufrufen, was die Auswahl von Aktionen ermöglicht, wobei nur die für die Aktion relevanten Daten angezeigt werden.
Wir werden zunächst den bequemen Weg beschreiben:
-
Klicken Sie mit der rechten Maustaste auf die Schaltfläche Maschine ein (engl.machine on) und wählen Sie Aktionen festlegen (engl. set actions).
-
Wenn das Dialogfeld angezeigt wird, verwenden Sie die Combobox, um zu
MASCHINENSTEUERUNGEN - Maschine ein
(engl.MACHINE CONTROLS - Machine On
) zu navigieren. -
In diesem Fall gibt es keine Option für diese Aktion, also wählen Sie "OK".
Now the button will turn the machine on when pressed.
Und nun der direkte Weg mit dem Eigenschaftseditor von Qt Designer:
-
Wählen Sie die Schaltfläche "Maschine ein".
-
Gehen Sie zum Eigenschaftseditor auf der rechten Seite von Qt Designer.
-
Blättern Sie nach unten, bis Sie die Überschrift ActionButton finden.
-
Klicken Sie auf das Kontrollkästchen für die Aktion "Machine_on", das Sie in der Liste der Eigenschaften und Werte sehen.
Die Taste steuert nun das Ein- und Ausschalten der Maschine.
Machen Sie das Gleiche für alle anderen Schaltflächen und fügen Sie noch einen hinzu:
-
Bei der Schaltfläche "Home" müssen wir auch die Eigenschaft
joint_number
auf1
ändern.
Dadurch wird der Controller angewiesen, alle Achsen und nicht nur eine bestimmte Achse zu referenzieren. -
Mit dem Button "Pause":
-
Unter der Überschrift
Indicated_PushButton
überprüfen Sie dieIndicator_option
. -
Unter der Überschrift
QAbstactButton
markieren Siecheckable
.
-
.ui
-Datei speichernDiesen Entwurf müssen wir dann als tester.ui
im Ordner sim/qtvcp
speichern.
We are saving it as tester as that is a file name that QtVCP recognizes and will use a built in handler file to display it.
4.4. Handler-Datei
A handler file is required.
Er ermöglicht das Schreiben von Anpassungen in Python.
Zum Beispiel werden Tastatursteuerungen normalerweise in die Handler-Datei geschrieben.
In this example, the built in file tester_handler.py
is automatically used: It does the minimum required to display the tester.ui
defined screen and do basic keyboard jogging.
4.5. INI Configuration
Wenn Sie QtVCP zur Erstellung eines CNC-Steuerungsbildschirms verwenden, setzen Sie unter der Überschrift INI-Datei [DISPLAY]
:
DISPLAY = qtvcp <Bildschirmname>
_<Bildschirmname>_
ist der Basisname der Dateien .ui
und _handler.py
.
In unserem Beispiel gibt es bereits eine Sim-Konfiguration namens tester, die wir zur Anzeige unseres Testbildschirms verwenden werden.
Wenn Ihr Bildschirm Widgets mit HAL-Pins verwendet, dann müssen Sie diese in einer HAL-Datei verbinden.
QtVCP sucht in der INI-Datei unter der Überschrift [HAL]
nach den folgenden Einträgen:
-
POSTGUI_HALFILE=<Dateiname>
-
Der Konvention nach wäre
<Dateiname>
als+<Bildschirm_name>_postgui.hal+
genannt, aber es kann jeder legale Dateiname sein.
Sie können mehrerePOSTGUI_HALFILE
-Zeilen in der INI haben: jede wird nacheinander in der Reihenfolge ausgeführt, in der sie erscheint.
Diese Befehle werden nach der Erstellung des Bildschirms ausgeführt, um sicherzustellen, dass die HAL-Pins des Widgets verfügbar sind. -
POSTGUI_HALCMD=<Befehl>
-
<Befehl>
wäre jeder gültige HAL-Befehl.
Sie können mehrerePOSTGUI_HALCMD
-Zeilen in der INI haben: jede wird nacheinander in der Reihenfolge ausgeführt, in der sie erscheint.
Um zu garantieren, dass die HAL-Pins des Widgets verfügbar sind, werden diese Befehle ausgeführt:-
after the screen is built,
-
nachdem alle POSTGUI_HALFILEs ausgeführt wurden.
-
In unserem Beispiel gibt es keine HAL-Pins zu verbinden.
5. Handler File In Detail
Handler files are used to create custom controls using Python.
5.1. Übersicht
Hier ist ein Beispiel für eine Handler-Datei.
Es ist in Abschnitte unterteilt, um die Diskussion zu erleichtern.
############################ # **** IMPORT SECTION **** # ############################ import sys import os import linuxcnc from PyQt5 import QtCore, QtWidgets from qtvcp.widgets.mdi_line import MDILine as MDI_WIDGET from qtvcp.widgets.gcode_editor import GcodeEditor as GCODE from qtvcp.lib.keybindings import Keylookup from qtvcp.core import Status, Action # Set up logging from qtvcp import logger LOG = logger.getLogger(__name__) # Set the log level for this module #LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL ########################################### # **** BIBLIOTHEKEN INSTANZIIEREN **** # ########################################### KEYBIND = Keylookup() STATUS = Status() ACTION = Action() ################################### # **** HANDLER CLASS SECTION **** # ################################### class HandlerClass: ######################## # **** 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 ########################################## # SPECIAL FUNCTIONS SECTION # ########################################## # at this point: # the widgets are instantiated. # the HAL pins are built but HAL is not set ready # This is where you make HAL pins or initialize state of widgets etc def initialized__(self): pass def processed_key_event__(self,receiver,event,is_pressed,key,code,shift,cntrl): # when typing in MDI, we don't want keybinding to call functions # so we catch and process the events directly. # We do want ESC, F1 and F2 to call keybinding functions though if code not in(QtCore.Qt.Key_Escape,QtCore.Qt.Key_F1 ,QtCore.Qt.Key_F2, QtCore.Qt.Key_F3,QtCore.Qt.Key_F5,QtCore.Qt.Key_F5): # search for the top widget of whatever widget received the event # then check if it is one we want the keypress events to go to flag = False receiver2 = receiver while receiver2 is not None and not flag: if isinstance(receiver2, QtWidgets.QDialog): flag = True break if isinstance(receiver2, MDI_WIDGET): flag = True break if isinstance(receiver2, GCODE): flag = True break receiver2 = receiver2.parent() if flag: if isinstance(receiver2, GCODE): # if in manual do our keybindings - otherwise # send events to G-code widget if STATUS.is_man_mode() == False: if is_pressed: receiver.keyPressEvent(event) event.accept() return True elif is_pressed: receiver.keyPressEvent(event) event.accept() return True else: event.accept() return True if event.isAutoRepeat():return True # ok if we got here then try keybindings try: return KEYBIND.call(self,event,is_pressed,shift,cntrl) except NameError as e: LOG.debug('Exception in KEYBINDING: {}'.format (e)) except Exception as e: LOG.debug('Exception in KEYBINDING:', exc_info=e) print('Error in, or no function for: %s in handler file for-%s'%(KEYBIND.convert(event),key)) return False ######################## # CALLBACKS FROM STATUS # ######################## ####################### # CALLBACKS FROM FORM # ####################### ##################### # GENERAL FUNCTIONS # ##################### # keyboard jogging from key binding calls # double the rate if fast is true def kb_jog(self, state, joint, direction, fast = False, linear = True): if not STATUS.is_man_mode() or not STATUS.machine_is_on(): return if linear: distance = STATUS.get_jog_increment() rate = STATUS.get_jograte()/60 else: distance = STATUS.get_jog_increment_angular() rate = STATUS.get_jograte_angular()/60 if state: if fast: rate = rate * 2 ACTION.JOG(joint, direction, rate, distance) else: ACTION.JOG(joint, 0, 0, 0) ##################### # KEY BINDING CALLS # ##################### # Machine control def on_keycall_ESTOP(self,event,state,shift,cntrl): if state: ACTION.SET_ESTOP_STATE(STATUS.estop_is_clear()) def on_keycall_POWER(self,event,state,shift,cntrl): if state: ACTION.SET_MACHINE_STATE(not STATUS.machine_is_on()) def on_keycall_HOME(self,event,state,shift,cntrl): if state: if STATUS.is_all_homed(): ACTION.SET_MACHINE_UNHOMED(-1) else: ACTION.SET_MACHINE_HOMING(-1) def on_keycall_ABORT(self,event,state,shift,cntrl): if state: if STATUS.stat.interp_state == linuxcnc.INTERP_IDLE: self.w.close() else: self.cmnd.abort() # Linear Jogging def on_keycall_XPOS(self,event,state,shift,cntrl): self.kb_jog(state, 0, 1, shift) def on_keycall_XNEG(self,event,state,shift,cntrl): self.kb_jog(state, 0, -1, shift) def on_keycall_YPOS(self,event,state,shift,cntrl): self.kb_jog(state, 1, 1, shift) def on_keycall_YNEG(self,event,state,shift,cntrl): self.kb_jog(state, 1, -1, shift) def on_keycall_ZPOS(self,event,state,shift,cntrl): self.kb_jog(state, 2, 1, shift) def on_keycall_ZNEG(self,event,state,shift,cntrl): self.kb_jog(state, 2, -1, shift) def on_keycall_APOS(self,event,state,shift,cntrl): pass #self.kb_jog(state, 3, 1, shift, False) def on_keycall_ANEG(self,event,state,shift,cntrl): pass #self.kb_jog(state, 3, -1, shift, linear=False) ########################### # **** closing event **** # ########################### ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) ################################ # required handler boiler code # ################################ def get_handlers(halcomp,widgets,paths): return [HandlerClass(halcomp,widgets,paths)]
5.2. IMPORT Section
Dieser Abschnitt ist für Import der erforderlichen Bibliotheksmodule für Ihren Bildschirm.
Es wäre typisch, die QtVCP-Bibliotheken keybinding, Status und Action zu importieren.
5.3. Abschnitt INSTANTIATE BIBRARIES
Indem wir die Bibliotheken hier instanziieren, erzeugen wir eine globale Referenz.
Sie können dies an den Befehlen erkennen, denen kein "self" vorangestellt ist.
By convention we capitalize the names of globally referenced libraries.
5.4. HANDLER CLASS Section
Der angepasste Code wird in einer Klasse platziert, damit QtVCP ihn verwenden kann.
Dies ist die Definition der Handler-Klasse.
5.5. INITIALIZE Section
Wie alle Python-Bibliotheken wird die +__init__+
-Funktion aufgerufen, wenn die Bibliothek erstmals instanziiert wird.
Hier können Sie Standardwerte, Referenzvariablen und globale Variablen einrichten.
Die Referenzen der Widgets sind zu diesem Zeitpunkt nicht verfügbar.
Die Variablen halcomp
, widgets
und paths
ermöglichen den Zugriff auf QtVCP’s HAL-Komponenten, Widgets bzw. Pfadinformationen.
5.6. Abschnitt zu BESONDEREN FUNKTIONEN
Es gibt mehrere special functions, nach denen QtVCP in der Handlerdatei sucht.
Wenn QtVCP diese findet, ruft es sie auf, wenn nicht, ignoriert es sie stillschweigend.
-
class_patch__(self):
-
Class patching, auch bekannt als monkey patching, erlaubt es, Funktionsaufrufe in einem importierten Modul zu überschreiben.
Klassen-Patching muss vor der Instanziierung des Moduls durchgeführt werden und verändert alle danach erstellten Instanzen.
Ein Beispiel wäre das Patchen von Schaltflächenaufrufen aus dem G-Code-Editor, um stattdessen Funktionen in der Handler-Datei aufzurufen.
-
initialized__(self):
-
Diese Funktion wird aufgerufen, nachdem die Widgets und HAL-Pins erstellt wurden.
Sie können hier die Widgets und HAL-Pins manipulieren oder weitere HAL-Pins hinzufügen.
In der Regel gibt es-
Einstellungen überprüft und eingestellt,
-
styles applied to widgets,
-
Status von LinuxCNC verbunden mit Funktionen.
-
Tastenbelegungen würden hinzugefügt.
-
-
after_override__(self):
-
This function is called after the optional override file is loaded but
before the optional HAL file is loaded or HAL component is set ready. -
processed_key_event__(self,receiver,event,is_pressed,key,code,shift,cntrl):
-
This function is called to facilitate keyboard jogging, etc.
By using thekeybinding
library this can be used to easily add functions bound to keypresses. -
keypress_event__(self,receiver, event):
-
Diese Funktion liefert rohe Tastendruckereignisse.
Sie hat _Vorrang vor demverarbeiteten_Tastenereignis
. -
keyrelease_event__(receiver, event):
-
Diese Funktion gibt raw key release events aus.
Es hat Vorrang vor demprocessed_key_event
. -
before_loop__(self):
-
Diese Funktion wird kurz vor dem Eintritt in die Qt-Ereignisschleife aufgerufen. Zu diesem Zeitpunkt sind alle Widgets/Bibliotheken/Initialisierungscodes abgeschlossen und der Bildschirm wird bereits angezeigt.
-
system_shutdown_request__(self):
-
If present, this function overrides the normal function called for total system shutdown.
It could be used to do pre-shutdown housekeeping.
-
+ The Linux system will not shutdown if using this function, you will have to do that yourself.
QtVCP/LinuxCNC will terminate without a prompt once this function returns.
-
-
closing_cleanup__(self):
-
Diese Funktion wird kurz vor dem Schließen des Bildschirms aufgerufen. Sie kann verwendet werden, um vor dem Schließen aufzuräumen.
5.7. STATUS CALLBACKS Abschnitt
Konventionell würden Sie hier Funktionen unterbringen, die Rückrufe von STATUS-Definitionen sind.
5.8. CALLBACKS FROM FORM Abschnitt
Konventionell würden Sie hier Funktionen ablegen, die Rückrufe von den Widgets sind, die mit dem MainWindow im Qt Designer-Editor verbunden sind.
5.9. GENERAL FUNCTIONS Section
Konventionell werden hier die allgemeinen Funktionen untergebracht.
5.10. KEY BINDING Section
Wenn Sie die Keybinding
-Bibliothek_ verwenden, platzieren Sie hier Ihre benutzerdefinierten Tastenaufrufroutinen.
The function signature is:
def on_keycall_KEY(self,event,state,shift,cntrl): if state: self.do_something_function()
KEY
ist der Code (aus der Keybindings-Bibliothek) für den gewünschten Schlüssel.
5.11. CLOSING EVENT Section
Wenn Sie die Funktion closeEvent
hier einfügen, werden Schließungsereignisse abgefangen.
Dies replaces eine vordefinierte 'closeEvent'-Funktion von QtVCP.
def closeEvent(self, event): self.do_something() event.accept()
Anmerkung
|
It is usually better to use the special closing_cleanup__ function. |
6. Connecting Widgets to Python Code
Es ist möglich, Widgets über Signale und Slots mit Python-Code zu verbinden.
Auf diese Weise können Sie:
-
LinuxCNC-Widgets neue Funktionen geben, oder
-
Standard Qt-Widgets zur Steuerung von LinuxCNC verwenden.
6.1. Übersicht
In the Qt Designer editor:
-
You create user function slots
-
Sie verbinden die Slots mit Widgets, indem Sie Signale verwenden.
In the handler file:
-
You create the slot’s functions defined in Qt Designer.
6.2. Using Qt Designer to add Slots
When you have loaded your screen into Qt Designer, add a plain PushButton
to the screen.
You could change the name of the button to something interesting like test_button.
There are two ways to edit connections - This is the graphical way.
-
There is a button in the top tool bar of Qt Designer for editing signals. After pushing it, if you click-and-hold on the button it will show an arrow (looks like a ground signal from electrical schematic).
-
Slide this arrow to a part of the main window that does not have widgets on it.
-
A Configure Connections dialog will pop up.
-
The list on the left are the available signals from the widget.
-
The list on the right are the available slots on the main window and you can add to it.
-
-
Pick the signal
clicked()
- this makes the slots side available. -
Click Edit on the slots list.
-
A Slots/Signals of MainWindow dialog will pop up.
-
On the slots list at the top there is a + icon - click it.
-
You can now edit a new slot name.
-
Erase the default name
slot()
and change it totest_button()
-
Drücken Sie die Taste OK.
-
You’ll be back to the Configure Connections dialog.
-
Now you can select your new slot in the slot list.
-
Then press OK and save the file.
6.3. Python Handler Changes
Nun müssen Sie die Funktion in die Handler-Datei einfügen.
The function signature is def slot_name(self):
.
Für unser Beispiel fügen wir etwas Code hinzu, um den Namen des Widgets zu auszugeben:
def test_button(self): name = self.w.sender().text() print(name)
Fügen Sie diesen Code unter dem Abschnitt namens:
####################### # Callbacks vom Formular #######################
Tatsächlich spielt es keine Rolle, wo in der Handler-Klasse Sie die Befehle ablegen, aber per Konvention ist dies der Ort, an dem Sie sie ablegen müssen.
Speichern der Handlerdatei.
Now when you load your screen and press the button it should print the name of the button in the terminal.