Running .ui -> Python during CMake

Here's the place for discussion related to coding in FreeCAD, C++ or Python. Design, interfaces and structures.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
chennes
Veteran
Posts: 3907
Joined: Fri Dec 23, 2016 3:38 pm
Location: Norman, OK, USA
Contact:

Running .ui -> Python during CMake

Post by chennes »

I'd like to translate a *.ui file into a Python class during the compilation process (via CMake), the way we do for C++. That is, to run either pyside2-uic or uic -g python (depending on the Qt version) automatically, so that when the .ui changes the generated file gets recreated. I don't see any examples for how to do this, all the other Python I see is generating entire windows in a ui file so just using loadUi(). In my case it's a widget that's getting reused hundreds of times, I don't want to reload the ui file each time. Is there a Python equivalent to QT5_WRAP_UI() or AUTOUIC()?
Chris Hennes
Pioneer Library System
GitHub profile, LinkedIn profile, chrishennes.com
User avatar
onekk
Veteran
Posts: 6205
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Running .ui -> Python during CMake

Post by onekk »

Maybe designing it directly using python?

Or maybe coding directly it in C++.

Sometimes is not so difficult as it seems at first glance.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
wmayer
Founder
Posts: 20307
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Running .ui -> Python during CMake

Post by wmayer »

It seems you must write your own CMake macro for this.

On my system Ubuntu 18.04 there are no official PySide2 packages but they are available on Launchpad. But what's odd with is package is that from a terminal window I cannot run:

Code: Select all

/usr/bin/pyside2-uic 
because it raises an ImportError that QtCore cannot be loaded. Having a look at the file shows why. The shebang is:

Code: Select all

#! /usr/bin/python2
So, it uses python2 and because I don't have installed PySide2 packages for that version the QtCore module cannot be loaded. A workaround for me is to explicitly invoke python3 with:

Code: Select all

python3 /usr/bin/pyside2-uic file.ui -o ui_file.py
and this is a use case the CMake macro should cover, too.
wmayer
Founder
Posts: 20307
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Running .ui -> Python during CMake

Post by wmayer »

This CMake function works with pyide2-uic:

Code: Select all

# must be set
set (PYSIDEUICBINARY "/usr/bin/pyside2-uic")

include(CMakeParseArguments)

function(PY3_WRAP_UI outfiles )
    set(options)
    set(oneValueArgs)
    set(multiValueArgs OPTIONS)

    cmake_parse_arguments(_WRAP_UI "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    set(ui_files ${_WRAP_UI_UNPARSED_ARGUMENTS})
    set(ui_options ${_WRAP_UI_OPTIONS})

    foreach(it ${ui_files})
        get_filename_component(outfile ${it} NAME_WE)
        get_filename_component(infile ${it} ABSOLUTE)
        set(outfile ${CMAKE_CURRENT_BINARY_DIR}/ui_${outfile}.py)
        add_custom_command(OUTPUT ${outfile}
          COMMAND ${PYTHON_EXECUTABLE} ${PYSIDEUICBINARY}
          ARGS ${ui_options} -o ${outfile} ${infile}
          MAIN_DEPENDENCY ${infile} VERBATIM)
        set_source_files_properties(${infile} PROPERTIES SKIP_AUTOUIC ON)
        set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOMOC ON)
        set_source_files_properties(${outfile} PROPERTIES SKIP_AUTOUIC ON)
        list(APPEND ${outfiles} ${outfile})
    endforeach()
    set(${outfiles} ${${outfiles}} PARENT_SCOPE)
endfunction()
For newer Qt versions you probably can use QT5_WRAP_UI like this I guess:

Code: Select all

qt5_wrap_ui(MY_UIC_PY_SRCS ${MY_UIC_SRCS} OPTIONS "-g python")
Two things you still have to consider:
  1. The ui files must not contain any custom widgets. Only Qt widgets are allowed
  2. Inside the ui files namespaces are not allowed as otherwise an invalid class name will be generated, e.g. Ui_PartGui::MyUIFile
User avatar
chennes
Veteran
Posts: 3907
Joined: Fri Dec 23, 2016 3:38 pm
Location: Norman, OK, USA
Contact:

Re: Running .ui -> Python during CMake

Post by chennes »

wmayer wrote: Tue Nov 23, 2021 11:02 am The ui files must not contain any custom widgets. Only Qt widgets are allowed
I was able to use a custom widget with uic -g python -- is this a limitation of pyside2-uic? With uic it's a little weird because you still have to provide a C++-style header file for the widget, but it strips the .h off the end of the filename and imports it instead.
Chris Hennes
Pioneer Library System
GitHub profile, LinkedIn profile, chrishennes.com
User avatar
chennes
Veteran
Posts: 3907
Joined: Fri Dec 23, 2016 3:38 pm
Location: Norman, OK, USA
Contact:

Re: Running .ui -> Python during CMake

Post by chennes »

onekk wrote: Tue Nov 23, 2021 8:04 am Maybe designing it directly using python?
The strategy I ended up using was to manually run uic on the ui file and include the resulting Python file manually in the git repo. I may not even commit the ui at all, I ended up hand-editing the resulting Python to deal with an unrelated issue, so it might not be worth keeping the ui around at all at this point. Anyone who wants to edit the widget needs to edit the Python directly now anyway.
Chris Hennes
Pioneer Library System
GitHub profile, LinkedIn profile, chrishennes.com
wmayer
Founder
Posts: 20307
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Running .ui -> Python during CMake

Post by wmayer »

I was able to use a custom widget with uic -g python -- is this a limitation of pyside2-uic?
Then I wonder how does the code generator know to create an instance of an unknown class. As a simple example take the ImageOrientationDialog.ui file of the image module. It has one custom widget, a Gui::QuantitySpinBox.

The ui file contains this information about the class:

Code: Select all

  <customwidget>
   <class>Gui::QuantitySpinBox</class>
   <extends>QWidget</extends>
   <header>Gui/QuantitySpinBox.h</header>
  </customwidget>
Maybe you could try it with a more recent Qt version then we can see the output. When I tried earlier today I got an error but now it prints a warning that Gui.QuantitySpinBox is unknown and skips it. So, the dialog class is not complete.
wmayer
Founder
Posts: 20307
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Running .ui -> Python during CMake

Post by wmayer »

I once wrote this summary about the differences of QUiLoader and pyide-uic: https://forum.freecadweb.org/viewtopic.php?f=10&t=5374

Btw, when I use the built-in function Gui.PySideUic.loadUiType then I again get the error message with ImageOrientationDialog.ui:
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<string>", line 16, in <module>
File "/usr/lib/python3/dist-packages/pyside2uic/__init__.py", line 143, in compileUi
winfo = compiler.UICompiler().compileUi(uifile, pyfile, from_imports)
File "/usr/lib/python3/dist-packages/pyside2uic/Compiler/compiler.py", line 91, in compileUi
w = self.parse(input_stream)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 874, in parse
actor(elem)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 717, in createUserInterface
self.traverseWidgetTree(elem)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 695, in traverseWidgetTree
handler(self, child)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 430, in createLayout
self.traverseWidgetTree(elem)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 695, in traverseWidgetTree
handler(self, child)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 467, in handleItem
self.traverseWidgetTree(elem)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 695, in traverseWidgetTree
handler(self, child)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 430, in createLayout
self.traverseWidgetTree(elem)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 695, in traverseWidgetTree
handler(self, child)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 467, in handleItem
self.traverseWidgetTree(elem)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 695, in traverseWidgetTree
handler(self, child)
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 191, in createWidget
self.stack.push(self.setupObject(widget_class, parent, elem))
File "/usr/lib/python3/dist-packages/pyside2uic/uiparser.py", line 154, in setupObject
obj = self.factory.createQObject(clsname, name, args, is_attribute)
File "/usr/lib/python3/dist-packages/pyside2uic/objcreator.py", line 91, in createQObject
raise NoSuchWidgetError(classname)
pyside2uic.exceptions.NoSuchWidgetError: Unknown Qt widget: Gui.QuantitySpinBox
User avatar
chennes
Veteran
Posts: 3907
Joined: Fri Dec 23, 2016 3:38 pm
Location: Norman, OK, USA
Contact:

Re: Running .ui -> Python during CMake

Post by chennes »

Oh, I see, I wasn't trying to use one of FreeCAD's custom widgets -- I had written a new one in Python that I was using, and that worked fine.
Chris Hennes
Pioneer Library System
GitHub profile, LinkedIn profile, chrishennes.com
wmayer
Founder
Posts: 20307
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: Running .ui -> Python during CMake

Post by wmayer »

chennes wrote: Tue Nov 23, 2021 3:16 pm Oh, I see, I wasn't trying to use one of FreeCAD's custom widgets -- I had written a new one in Python that I was using, and that worked fine.
Ah, OK.
Post Reply