[[cha:qtvcp]]

= Qtvcp

Qtvcp is an infrastructure to display a custom CNC screen or control panel in LinuxCNC. +
It displays a UI file built with the QTDesigner screen editor or 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. +

.qtdragon - 3 or 4 Axis Sample
image::images/silverdragon.png["QTDragon Router",align="left"]
.qtdefault - 3 Axis Sample
image::images/qt_cnc.png["QTscreen Mill",align="left"]
.Qtaxis - Self Adjusting Axis Sample
image::images/qtaxis.png["QTscreen Qtaxis",align="left"]
.Blender - 4 Axis Sample
image::images/blender.png["QTscreen Blender",align="left"]
.X1mill - 4 Axis Sample
image::images/x1mill.png["QTscreen x1mill",align="left"]
.cam_align - Camera alignment VCP
image::images/qtvcp-cam-align.png["QTscreen x1mill",align="left"]

[[sec:qtvcp-overview]](((QtVcp Overview)))

== Overview

There are two files that can be used, individually or in combination to add +
customization. +
A UI file that is made with QT's Designer graphical editor. +
A handler file which is a text file with python code. +
Normally qtvcp uses the stock UI and handler file. +
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. +
qtvcp leverages 'QT Designer' (the editor) and 'PyQT5' (the widget toolkit). +
QTvcp has some special widgets and actions added just for LinuxCNC. +
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. + 

=== QTvcp Widgets

Qtvcp uses the PyQt5 toolkit's widgets for linuxcnc integration. +
Widget is the general name for objects such as buttons and labels in PyQT5. +
You are free to use any available widgets in the QTDesigner editor. +
There are also special widgets made for linuxcnc that make integration easier. +
This 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. +

=== INI Settings

If you are using this to make a CNC control screen: +
Under the [DISPLAY] heading:

----
DISPLAY = qtvcp <screen_name>
  options:
    -d debugging on
    -a set window always on top
    -c HAL component name. Default is to use the UI file name.
    -g geometry: WIDTHxHEIGHT+XOFFSET+YOFFSET
    -m maximise window
    -f fullscreen the window
    -t theme. Default is system theme
    -x embed into a X11 window that doesn't supoort embedding.
    --push_xid send qtvcp's X11 window id number to standard output; for embedding
    <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 this same base name. +
Qtvcp will search the LinuxCNC configuration file that was launched first for the files,  +
then in the system skin folder. the skin folders holds standard screens. +

=== QTDesigner UI File

A designer file is a text file organized in the XML standard that describes the +
layout and the widgets of the screen. Pyqt5 uses this file to build the display +
and react to those widgets. The QTDesigner editor makes it relatively easy to build +
and edit this file. +

=== Handler Files

A handler file is a file containing python code, which qtvcp adds to it's +
default routines. A handler file allows one to modify defaults, or add logic +
to a qtvcp skin without having to modify qtvcp's core code. +
In this way you can have custom behaviour. +
If present a handler file will be loaded. +
Only one file is allowed. +

=== Libraries modules
Qtvcp as built does little more then display the screen and react to widgets. +
For more prebuilt behaviours there are available libraries. +
(found in lib/python/qtvcp/lib in RIP linuxcnc install) +
libraries are prebuilt python modules that give added 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 +


=== 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. System theme is default for panels. +
to see available themes load qtvcp with -d -t SHOWTHEMES +

qtvcp can also be customized with Qt stylesheets using css. +

=== Local Files

If present, local UI files in the configuration folder will be loaded instead +
of the stock UI files. Local UI files allow you to use your customized +
designs rather then the default screens. +
qtvcp will look for MYNAME.ui, MYNAME_handler.py and MYNAME.qss in the launched configuration folder. +
You can put these files in a arbitrary named folder - qtvcp will search all folders in your config folder. +

=== Modifying Stock Screens

If you wish to modify a stock screen, copy it's UI and handler file to your configuration folder. +


== Build a simple clean-sheet custom screen

.Ugly custom screen
image::images/qtvcp_tester.png["QTscreen Mill",align="left"]

=== Overview

To build a panel or screen use QTDesigner to build a design you like. +
Save this design to your configuration folder with a name of your choice, ending with .ui +
modify the configurations INI file to load qtvcp with your new .ui file. +
Then connect any required HAL pins in a HAL file +

=== Get Designer to include linuxcnc widgets

You must have designer installed; These commands should add it: +
Or use your package manager to install the same: +
'sudo apt-get install qttools5-dev-tools' +
'sudo apt-get install qttools5.dev' +

Then you need the python-module loading library added. +
Qtvcp uses QT5 with python2 - this combination is not normally available from +
repositories. You can compile it your self or there are precompiled versions +
available for common systems. +
in 'lib/python/qtvcp/designer' there are folders based on system architectures +
and then QT version. +
You must pick the cpu architecture folder then pick the series; 5.5, 5.7, or 5.9 of Qt. +
currently Debian stretch uses 5.7, Mint 12 uses 5.5, Mint 19 uses 5.9 +
if in doubt check the version of QT5 on the system. +

You must decompress the file and then copy that proper version of +
'libpyqt5_py2.so' to this folder: +
'/usr/lib/x86_64-linux-gnu/qt5/plugins/designer' +
(x86_64-linux-gnu might be called something slightly different +
on different systems) +

You will require super user privileges to copy the file to the folder. +

then you must add a link to the qtvcp_plugin.py to the folder that designer will search. +

In a RIP version of linuxcnc qtvcp_plugin.py will be in: +
'~/LINUXCNC_PROJECT_NAME/lib/python/qtvcp/plugins/qtvcp_plugin.py' +

installed version 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 link file to the above file and move it to one of the places Designer searches: +

Designer searches in these two place for links (pick one): +
This can be: +
'/usr/lib/x86_64-linux-gnu/qt5/plugins/designer/python' +
or +
'~/.designer/plugins/python' +
You may need to add the plugins/python folders +

To start Designer: +

for a RIP installed: +
open a terminal, set the environment for linuxcnc with the command: '. scripts/rip-environment' +
then load designer with : 'designer -qt=5' +

otherwise for an installed version, open a terminal and type 'designer -qt=5' +

If all goes right you will see the selectable linuxcnc widgets on the left hand side +

=== build the screen .ui file

When Designer is first started there is a 'New Form' dialog displayed. +
Pick 'Main Window' and press the 'create' button. +
Do not rename this window - Qtvcp requires the name to be 'MainWindow' +
 +
A MainWindow widget is Displayed. 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. +
 +
Drag and drop the screenoption widget onto the main window (anywhere). +
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. The screenoption widget will now be 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 screenoption. then +
switch to the property Editor. Under the heading 'ScreenOptions' 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. +

Now we will add action 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 designer. +
A convenient alternating is left double clicking on the button This will launch a Dialog +
that allows selecting actions while only display 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 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 Designer's property editor +

 - Select the 'Machine On' button. Now go to the 'Property Editor' on the right +
side of Designer. Scroll down until you find the 'ActionButton' heading. +
You will see a list of properties and values. find the 'machine on action' and +
click the checkbox. 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, +
Which tells the controller to home all the axes rather then a specific axis. +

 - With the 'Pause' button under the heading 'Indicated_PushButton' check the +
'indicator_option' and under the 'QAbstactButton' heading check 'checkable'

.Qt Designer - Selecting Pause button's properties
image::images/designer_button_property.png["designer button property",align="left"]

We 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. +

=== 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. +

=== INI

If you are using qtvcp to make a CNC control screen: +
Under the '[DISPLAY]' heading: +
 +
'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.

=== HAL

If your screen used widgets with HAL pins the you must connect then in a HAL file. +
qtvcp looks under the heading '[HAL]' for the entry 'POSTGUI_HALFILE=<filename>' +
Typically '<filename>' would be the screens base name + '_postgui' + '.hal' +
eg. 'qtvcp_postgui.hal' +
These commands are executed after the screen is built, guaranteeing the widget HAL +
pins are available. +
 +
In our example there are no HAl pins to connect. +

== Handler file in detail
handler files are used to create custom controls using python. +

=== Overview
Here is a sample handler file. +
It's broken up in sections for ease of discussion. +

[source,python]
----
############################
# **** 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's 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 gcode 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)]

----

=== IMPORT SECTION
This section is for importing library modules required for your screen. +
It would be typical to import qtvcp's keybinding, Status and action +
libraries. +

=== 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 global referenced libraries. +

=== HANDLER CLASS section 
The custom code is placed in a class so qtvcp can utilize it. +
This is the definitions on the handler class. +

=== INITIALIZE section
Like all python libraies the __init__ function is called when the library +
is first instantiated. You can set defaults and reference variables here. +
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 respectably. +
This is where you would set up global variables. +
Widgets are not actually accessible at this point. +

=== 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. +

==== 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 preferences can be checked and set, styles applied to  +
widgets or status of linuxcnc be connected to functions. +
This is also where keybindings would be added. +

==== class_patch__(self):
Class patching allow you 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 gcode editor to call functions +
in the handler file instead. +
Class patching is also known as monkey patching.

==== processed_key_event__(self, receiver,event,is_pressed,key,code,shift,cntrl):
This function is called to facilitate keyboard jogging etc. +
By using the keybindings 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 presidence over +
the processed_key_event. +
 
==== keyrelease_event__(receiver, event):
This function gives raw key release events. It takes presidence over +
the processed_key_event. +

==== before_loop__(self):
This function is called just before the Qt event loop is entered. +
At the 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 when a user selects a total system shutdown. +
It could be used to do pre-shutdown housekeeping. The system will not shutdown if using this function, you will +
have to do that yourself. qtvcp/linuxcnc will shutdown wihtout a prompt after this function returns +
 
==== closing_cleanup__(self):
This function is called just before the screen closes. It can be used +
to do cleanup before closing. +

=== STATUS CALLBACKS section
By convention this is where you would put functions that are callbacks +
from STATUS definitions. +

=== CALLBACKS FROM FORM section
By convention this is where you would put functions that are callbacks +
from the widgets that you have connected to the MainWindow with the +
designer editor. +

=== GENERAL FUNCTIONS section
By convention this is where you put your general functions +

=== KEY BINDING section
If you are using the keybinding library this is where you place your +
custom key call routines. +
The function signature is: +
[source,python]
----
    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. +

=== CLOSING EVENT section
Putting the close event function here will catch closing events. +
This replaces any predefined closeEvent function from qtvcp +
It's usally better to use the special closing_cleanup__ function. +
[source,python]
----
    def closeEvent(self, event):
        self.do_something()
        event.accept()
----

== Connecting widgets to python code
It's 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 widgets to control linuxcnc. +

=== Overview
In the Designer editor you would create user function slots and connect +
them to widgets using signals. +
In the handler file you would create the slot's functions defined in Designer. +

=== Using Designer to add slots
When you have loaded your screen into 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 designer for editing signals. +
After pushing it, if you click-and-hold on the button it will show a 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 is 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 plus icon - click it. +
you can now edit a new slot name. +
Erase the default name 'slot()' and change it to test_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. +

.Designer signal/slot selection
image::images/designer_slots.png["QTvcp",align="left"]

=== Handler file changes
Now you must add the function to the handler file. +
The function signature is 'def slotname(self):' +
We will add some code to print the widget name. +

So for our example:
[source,python]
----
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. +

=== More Information

<<cha:qtvcp-widgets,QtVCP Widgets>>

<<cha:qtvcp-libraries,QtVCP Libraries>>

<<cha:qtvcp-code,QtVCP Handler File Code Snippets>>

<<cha:qtvcp-development,QtVCP Development>>

<<cha:qtvcp-custom-widgets,QtVCP Custom Designer Widgets>>

