QtVCP is an infrastructure to build custom CNC screens or control panels for LinuxCNC.
It displays a .ui
file built with Qt Designer screen editor and
combines this with Python programming to create a GUI screen for
running a CNC machine.
QtVCP is completely customizable: you can add different buttons and status LEDs etc. or add Python code for even finer grain customization.
1. Showcase
Few examples of QtVCP built screens and virtual control panels:
2. Overview
Two files are used, individually or in combination, to add customizations:
-
A UI file that is a XML file made with Qt Designer graphical editor.
-
A handler file which is a Python code text file.
Normally QtVCP uses the stock UI and handler file, but you can specify
QtVCP to use local UI and handler files.
A local file is one that is in the configuration folder that defines
the rest of the machine’s requirements.
One is not restricted to adding a custom panel on the right or a custom tab as QtVCP leverages Qt Designer (the editor) and PyQT5 (the widget toolkit).
QtVCP has some added special LinuxCNC widgets and actions.
There are special widgets to bridge third party widgets to HAL pins.
It’s possible to create widget responses by connecting signals to
Python code in the handler file.
2.1. QtVCP Widgets
QtVCP uses the PyQt5 toolkit’s widgets for LinuxCNC integration.
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 Settings
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>
Note
|
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
Send QtVCP’s X11 window id number to standard output; for embedding. -
-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.
The widgets, graphics and HAL pin update can be set separately.
If the update time is not set right the screen can become unresponsive or very jerky.
2.3. Qt Designer UI File
A Qt Designer file is a text file organized in the XML standard that describes the layout and widgets of the screen.
PyQt5 uses this file to build the display and react to those widgets.
The Qt Designer editor makes it relatively easy to build and edit this file.
2.4. Handler Files
A handler file is a file containing Python code, which adds to QtVCP default routines.
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. Libraries Modules
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. Themes
Themes are a way to modify the look and feel of the widgets on the screen.
For instance the color or size of buttons and sliders can be changed using themes.
The Windows theme is default for screens.
The System theme is default for panels.
To see available themes, they can be loaded by running the following command in a terminal:
qtvcp -d -t <theme_name>
QtVCP can also be customized with Qt stylesheets (QSS) using CSS.
2.7. Local Files
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 will look for a folder named <screen_name> (in the launched configuration folder that holds the INI file).
In that folder, QtVCP will load any of the available following files:
-
<screen_name>.ui
, -
<screen_name>_handler.py
, and -
<screen_name>.qss
.
2.8. Modifying Stock Screens
There are three ways to customize a screen/panel.
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.
Note
|
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
.
This file will be read and executed as Python code in the handler file context.
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
What can be used can vary by screen and development cycle.
Reference the main window to change the title (won’t show if using INI entries for title change).
self.w.setWindowTitle('My Title 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.
There is a QtVCP panel to help with this:
-
Open a terminal and run the following command:
qtvcp copy
-
Select the screen and destination folder in the dialog
-
If you wish to name your screen differently than the builtin screen’s default name, change the basename in the edit box.
-
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.
-
Validate to copy all the files
-
Delete the files you don’t wish to modify so that the original files will be used.
3. VCP Panels
QtVCP can be used to create control panels that interface with HAL.
3.1. Builtin Panels
There are several builtin HAL panels available.
In a terminal type qtvcp <return>
to see a list:
-
test_panel
-
Collection of useful widgets for testing HAL components, including speech of LED state.
-
cam_align
-
A camera display widget for rotational alignment.
-
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. Custom Panels
You can of course make your own panel and load it.
If you made a UI file named my_panel.ui
and a HAL file named my_panel.hal
, you would then load this from a terminal with:
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> # add components to thread addf classicladder.0.refresh thread1 # connect pins 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 this case we load
qtvcp
using-Wn
which waits for the panel to finish loading before continuing to run the next HAL command.
This is to ensure that the panel created HAL pins are actually done in case they are used in the rest of the file.
4. Build A Simple Clean-sheet Custom Screen
4.1. Overview
To build a panel or screen:
-
Use Qt Designer to build a design you like and save it to your configuration folder with a name of your choice, ending with
.ui
-
Modify the configuration INI file to load QtVCP using your new
.ui
file. -
Then connect any required HAL pins in a HAL file.
4.2. Get Qt Designer To Include LinuxCNC Widgets
First you must have the Qt Designer installed.
The following commands should add it to your system, or use your package manager to do the same:
sudo apt-get install qttools5-dev-tools qttools5-dev libpython3-dev
qtvcp_plugin.py
link to the Qt Designer Search PathThen you must add a link to the qtvcp_plugin.py
in one of the folders that Qt Designer will search into.
In a RIP version of LinuxCNC qtvcp_plugin.py
will be:
'~/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'
Make a symbolic link to the above file and move it to one of the places Qt Designer searches in.
Qt Designer searches in these two place for links (pick one):
'/usr/lib/x86_64-linux-gnu/qt5/plugins/designer/python' or
'~/.designer/plugins/python'
You may need to create the plugins/python
folder.
-
For a RIP install:
Open a terminal, set the environment for LinuxCNC <1>, then load Qt Designer <2> with :. scripts/rip-environment <1> designer -qt=5 <2>
-
For a package install:
Open a terminal and type:designer -qt=5
If all goes right, Qt Designer will launch and you will see the selectable LinuxCNC widgets on the left hand side.
4.3. Build The Screen .ui
File
MainWindow
WidgetWhen Qt Designer is first started there is a 'New Form' dialog displayed.
Pick 'Main Window' and press the 'Create' button.
A MainWindow
widget is displayed.
We are going to make this window a specific non resizeable size:
MainWindow
Minimum and Maximum Size-
Grab the corner of the window and resize to an appropriate size, say 1000x600.
-
Right click on the window and click set minimum size.
-
Do it again and set maximum size.
Our sample widget will now not be resizable.
ScreenOptions
WidgetDrag and drop the ScreenOptions
widget anywhere onto the main window.
This widget doesn’t add anything visually but sets up some common options.
It’s recommended to always add this widget before any other.
Right click on the main window, not the ScreenOptions
widget, and set the layout as vertical to make the ScreenOptions
fullsized.
On the right hand side there is a panel with tabs for a Property editor and an Object inspector.
On the Object inspector click on the ScreenOptions.
Then switch to the Property Editor and, under the ScreenOptions heading, toggle filedialog_option
.
Drag and drop a GCodeGraphics
widget and a GcodeEditor
widget.
Place and resize them as you see fit leaving some room for buttons.
Add 7 action buttons on to the main window.
If you double click the button, you can add text.
Edit the button labels for Estop, Machine On, Home, Load, Run, Pause and stop.
Action buttons default to no action so we must change the properties for defined functions. You can edit the properties:
-
directly in the property editor on the right side of Qt Designer, or
-
conveniently, left double clicking on the button to launch a properties dialog that allows selecting actions while only displaying relevant data to the action.
We will describe the convenient way first:
-
Right click the Machine On button and select Set Actions.
-
When the dialog displays, use the combobox to navigate to
MACHINE CONTROLS - Machine On
. -
In this case there is no option for this action so select OK.
Now the button will turn the machine on when pressed.
And now the direct way with Qt Designer’s property editor:
-
Select the Machine On button.
-
Go to the Property Editor on the right side of Qt Designer.
-
Scroll down until you find the ActionButton heading.
-
Click the
machine_on
action checkbox you will see in the list of properties and values.
The button will now control machine on/off.
Do the same for all the other button with the addition of:
-
With the Home button we must also change the
joint_number
property to-1
.
This tells the controller to home all the axes rather then a specific axis. -
With the Pause button:
-
Under the
Indicated_PushButton
heading check theindicator_option
. -
Under the
QAbstactButton
heading checkcheckable
.
-
.ui
FileWe then need to save this design as tester.ui
in the sim/qtvcp
folder.
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 file
A handler file is required.
It allows customizations to be written in Python.
For instance, keyboard controls are usually written in the handler file.
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
If you are using QtVCP to make a CNC control screen, under the INI file [DISPLAY]
heading, set:
DISPLAY = qtvcp <screen_name>
<screen_name>
is the base name of the .ui
and _handler.py
files.
In our example there is already a sim configuration called tester, that we will use to display our test screen.
If your screen used widgets with HAL pins, then you must connect them in a HAL file.
QtVCP looks in the INI file, under the [HAL]
heading for the entries below:
-
POSTGUI_HALFILE=<filename>
-
Typically
<filename>
would be+<screen_name>_postgui.hal+
, but can be any legal filename.
You can have multiplePOSTGUI_HALFILE
lines in the INI: each will be run one after the other in the order they appear.
These commands are executed after the screen is built, guaranteeing the widget HAL pins are available. -
POSTGUI_HALCMD=<command>
-
<command>
would be any valid HAL command.
You can have multiplePOSTGUI_HALCMD
lines in the INI: each will be run one after the other in the order they appear.
To guaranty the widget HAL pins are available, these commands are executed:-
after the screen is built,
-
after all the POSTGUI_HALFILEs are run.
-
In our example there are no HAL pins to connect.
5. Handler File In Detail
Handler files are used to create custom controls using Python.
5.1. Overview
Here is a sample handler file.
It’s broken up in sections for ease of discussion.
############################ # **** 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 ########################################### # **** INSTANTIATE LIBRARIES SECTION **** # ########################################### 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
This section is for importing required library modules for your screen.
It would be typical to import QtVCP’s keybinding, Status and Action libraries.
5.3. INSTANTIATE LIBRARIES Section
By instantiating the libraries here we create global reference.
You can note this by the commands that don’t have self.
in front of them.
By convention we capitalize the names of globally referenced libraries.
5.4. HANDLER CLASS Section
The custom code is placed in a class so QtVCP can utilize it.
This is the definitions of the handler class.
5.5. INITIALIZE Section
Like all Python libraries the +__init__+
function is called when the library is first instantiated.
This is where you would set up defaults, as well as reference variables and global variables.
The widget references are not available at this point.
The variables halcomp
, widgets
and paths
give access to QtVCP’s HAL component, widgets, and path info respectively.
5.6. SPECIAL FUNCTIONS Section
There are several special functions that QtVCP looks for in the handler file.
If QtVCP finds these it will call them, if not it will silently ignore them.
-
class_patch__(self):
-
Class patching, also known as monkey patching, allows to override function calls in an imported module.
Class patching must be done before the module is instantiated, and it modifies all instances made after that.
An example might be patching button calls from the G-code editor to call functions in the handler file instead.
-
initialized__(self):
-
This function is called after the widgets and HAL pins are built.
You can manipulate the widgets and HAL pins or add more HAL pins here.
Typically there can be-
preferences checked and set,
-
styles applied to widgets,
-
status of LinuxCNC connected to functions.
-
keybindings would be added.
-
-
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):
-
This function gives raw key press events.
It takes precedence over theprocessed_key_event
. -
keyrelease_event__(receiver, event):
-
This function gives raw key release events.
It takes precedence over theprocessed_key_event
. -
before_loop__(self):
-
This function is called just before the Qt event loop is entered. At that point, all widgets/libraries/initialization code has completed and the screen is already displayed.
-
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):
-
This function is called just before the screen closes. It can be used to do cleanup before closing.
5.7. STATUS CALLBACKS Section
By convention this is where you would put functions that are callbacks from STATUS definitions.
5.8. CALLBACKS FROM FORM Section
By convention this is where you would put functions that are callbacks from the widgets connected to the MainWindow in the Qt Designer editor.
5.9. GENERAL FUNCTIONS Section
By convention this is where you put your general functions.
5.10. KEY BINDING Section
If you are using the keybinding
library this is where you place your custom key call routines.
The function signature is:
def on_keycall_KEY(self,event,state,shift,cntrl): if state: self.do_something_function()
KEY
being the code (from the keybindings library) for the desired key.
5.11. CLOSING EVENT Section
Putting the closeEvent
function here will catch closing events.
This replaces any predefined closeEvent
function from QtVCP.
def closeEvent(self, event): self.do_something() event.accept()
Note
|
It is usually better to use the special closing_cleanup__ function. |
6. Connecting Widgets to Python Code
It is possible to connect widgets to Python code using signals and slots.
In this way you can:
-
Give new functions to LinuxCNC widgets, or
-
Utilize standard Qt widgets to control LinuxCNC.
6.1. Overview
In the Qt Designer editor:
-
You create user function slots
-
You connect the slots to widgets using signals.
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()
-
Press the OK button.
-
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
Now you must add the function to the handler file.
The function signature is def slot_name(self):
.
For our example, we will add some code to print the widget name:
def test_button(self): name = self.w.sender().text() print(name)
Add this code under the section named:
####################### # callbacks from form # #######################
In fact it doesn’t matter where in the handler class you put the commands but by convention this is where to put it.
Save the handler file.
Now when you load your screen and press the button it should print the name of the button in the terminal.