Write python bindings

Hi,

I have some Qt derived classes (widgets) written in C++ and used in SlicerCAT and I would like to bind them in python.

I have some experience of working with pybind11 and I do use it in my libraries that are not connected to Qt.
There is also automatic code generator rosetta-binder based on pybind11.

I also know that Qt also provides its method to convert C++ to python using Shiboken tool. Though I have not tested it yet.

So I would appreciate if someone could give an advice wich way is better and what should I use to convert C++ Qt based widget to python and use it in SlicerCAT

Is there a reason you choose to create your Qt based widget in C++ rather than creating the widget in python? Are you using it in other C++ code?

You can create a custom QWidget from the python interface for use in other python code.

1 Like

That is the question that I ask myself.
I think there are two reasons:

  1. I already implemented them in C++
  2. I think that in the future I encounter a situation when I will need to bind C++ class to python and thus it means that I will simply postpone this task if I decide not to use bindings now.

@keri did you look at how other Qt classes are wrapped in Slicer using PythonQt? This would be more consistent, and hopefully easier, than introducing another python wrapping method. I haven’t tried it in SlicerCAT, but I believe all the same functionality is available.

1 Like

Hi,

I have made an attempt but didn’t find a file (or class) where it is done. If you know an example in Slicer please say where to look for it.

Also as I know PyQt, PySide2 (official) and PythonQt they are all different projects am I right? Which ones Slicer is using?

Slicer uses PythonQt very extensively so you can look at any QObject or QWidget subclass in Slicer and the API will be exposed to Python through PythonQt. Basically, the public interface is wrapped, so Signals, Slots, and Properties are all available. In addition methods marked with the Q_INVOKABLE macro will also be wrapped. All the python-related developer tutorials make use of this.

1 Like

thank you! I will focus on this and try to sort it out

Hi,

I can’t actually find a comprehensive example how Slicer uses PythonQt to wrap C++ classes to python.
I’ve seen examples on PythonQt github but can’t apply it to my class.

For example I can do:

import PythonQt

dir(PythonQt)

the output would be:

['BoolResult', 'CTKCore', 'CTKScriptingPythonWidgets', 'CTKVisualizationVTKCore', 
'CTKVisualizationVTKWidgets', 'CTKWidgets', 'Debug', 'Qt', 'QtCore', 'QtGui', 'QtNetwork', 'QtUiTools', 
'__doc__', '__loader__', '__name__', '__package__', '__spec__', 'private', 'qMRMLWidgets', 
'qSlicerAnnotationsModuleWidgets', 'qSlicerBaseQTApp', 'qSlicerBaseQTCLI', 'qSlicerBaseQTCore', 
'qSlicerBaseQTGUI', 'qSlicerMarkupsModuleWidgets', 'qSlicerMarkupsSubjectHierarchyPlugins', 
'qSlicerModelsModuleWidgets', 'qSlicerModelsSubjectHierarchyPlugins', 'qSlicerPlotsModuleWidgets', 
'qSlicerPlotsSubjectHierarchyPlugins', 'qSlicerSegmentationsEditorEffects', 
'qSlicerSegmentationsModuleWidgets', 'qSlicerSegmentationsSubjectHierarchyPlugins', 
'qSlicerSequencesModuleWidgets', 'qSlicerSubjectHierarchyModuleWidgets', 
'qSlicerTablesModuleWidgets', 'qSlicerTablesSubjectHierarchyPlugins', 
'qSlicerTerminologiesModuleWidgets', 'qSlicerTextsModuleWidgets', 
'qSlicerTextsSubjectHierarchyPlugins', 'qSlicerTransformsModuleWidgets', 
'qSlicerTransformsSubjectHierarchyPlugins', 'qSlicerUnitsModuleWidgets', 
'qSlicerVolumeRenderingModuleWidgets', 'qSlicerVolumeRenderingSubjectHierarchyPlugins', 
'qSlicerVolumesModuleWidgets', 'qSlicerVolumesSubjectHierarchyPlugins']

then I look to GUI elements:

dir(PythonQt.qSlicerBaseQTGUI)

and among many output classes I can see qSlicerDirectoryListView for example. Assuming that this class was written in C++ and binded to python I search for qSlicerDirectoryListView.h and qSlicerDirectoryListView.cxx. I have found them in Base/QTGUI/ folder. To my surprise these files doesn’t contain any PythonQt mentionning…

From my point of view to bind C++ class to python I need to add somwhere #include <PythonQt.h> and PythonQt::self()->registerClass(&qMyClass::staticMetaObject);. And to check if it works I should run my application and in python interpreter do something like:

import PythonQt
dir(PythonQt)

and among the output I should be able to find qMyClass. Am I right? Though suspiciously simple :slight_smile:

Python wrapping is done fully automatically, by CMake macros (at the lowest level, it is implemented in ctkMacroWrapPythonQt in CTK library). As long as you build the library using Slicer macros, specify WRAP_PYTHONQT flag, and the Python wrapper can parse your header file, all your Qt classes will be automatically available in Python.

1 Like

Thank you! I just got first positive results by using WRAP_PYTHONQT inside slicerMacroBuildAppLibrary and I’m able to use my Qt based custom widgets

What maybe the reason why not all my widgets were binded to python?
For example I have:

slicerMacroBuildAppLibrary(
  NAME ${APPLIB_NAME}    # qColadaApp
  DESCRIPTION_SUMMARY ${${PROJECT_NAME}_DESCRIPTION_SUMMARY}
  DESCRIPTION_FILE ${${PROJECT_NAME}_DESCRIPTION_FILE}
  APPLICATION_NAME ${${PROJECT_NAME}_APPLICATION_NAME}
  EXPORT_DIRECTIVE "Q_COLADA_APP_EXPORT"
  FOLDER ${${PROJECT_NAME}_FOLDER}
  SRCS ${APPLIB_SRCS}
  MOC_SRCS ${APPLIB_MOC_SRCS}
  UI_SRCS ${APPLIB_UI_SRCS}
  RESOURCES ${APPLIB_RESOURCES}
  WRAP_PYTHONQT
  TARGET_LIBRARIES ${APPLIB_TARGET_LIBRARIES}
  INCLUDE_DIRECTORIES ${APPLIB_DIRS}
  )

CMake variable APPLIB_SRCS contains a list of .cxx and .h files that implement both Qt derived widgets and non Qt classes.
APPLIB_MOC_SRCS contains a list of .h files that declare all Qt derived objects with Q_OBJECT macro.

Generated module is called qColadaAppPythonQt. Then I can check binded classes:

import qColadaAppPythonQt

dir(qColadaAppPythonQt)

There I can see that only half of my classes were binded (half of APPLIB_MOC_SRCS var-list classes)

Also I tried it on both Windows and Ubuntu and seems that on Ubuntu a little more classes were binded than on Windows.

If a class is not wrapped then most likely it contains something that the wrapper cannot parse. You can determine what it is by commenting out everything in the .h file and gradually adding back lines and see which line is troublesome. If it is not clear why that specific line caused the wrapping to fail then you can ask us here, we may have suggestions for how to change it.

1 Like

I’m afraidn that the very simple exmple doesn’t work.

In my project I have qColadaH5TreeView base class that is inherited by some other classes. But it is difficult to comment something directly in this class as it would lead to many code changes in project.

Thus I created qColadaH5TreeView_TEST class wich is the same but with postfix _TEST. I have cut almost everything from it. Here it is:
qColadaH5TreeView_TEST.h

#ifndef __qColadaH5TreeView_TEST_h
#define __qColadaH5TreeView_TEST_h

// Qt includes
#include <QTreeView>

// Colada includes
#include "qColadaAppExport.h"

class qColadaH5TreeView_TESTPrivate;

class Q_COLADA_APP_EXPORT qColadaH5TreeView_TEST : public QTreeView {
  Q_OBJECT

public:
  explicit qColadaH5TreeView_TEST(QWidget *parent = nullptr);
  virtual ~qColadaH5TreeView_TEST() override;

protected:
  QScopedPointer<qColadaH5TreeView_TESTPrivate> d_ptr;

  explicit qColadaH5TreeView_TEST(qColadaH5TreeView_TESTPrivate *pimpl,
                             QWidget *parent);

private:
  Q_DECLARE_PRIVATE(qColadaH5TreeView_TEST);
  Q_DISABLE_COPY(qColadaH5TreeView_TEST);
};

#endif

qColadaH5TreeView_TEST.cxx

// Colada includes
#include "qColadaH5TreeView_TEST.h"
#include "qColadaH5TreeView_TEST_p.h"

qColadaH5TreeView_TESTPrivate::qColadaH5TreeView_TESTPrivate(qColadaH5TreeView_TEST &q)
    : q_ptr(&q) {}

qColadaH5TreeView_TESTPrivate::~qColadaH5TreeView_TESTPrivate() {}

void qColadaH5TreeView_TESTPrivate::init() {
  Q_Q(qColadaH5TreeView_TEST);
  // do some initialization
}

qColadaH5TreeView_TEST::qColadaH5TreeView_TEST(QWidget *parent)
    : QTreeView(parent), d_ptr(new qColadaH5TreeView_TESTPrivate(*this)) {
  Q_D(qColadaH5TreeView_TEST);
  d->init();
}

qColadaH5TreeView_TEST::qColadaH5TreeView_TEST(qColadaH5TreeView_TESTPrivate *pimpl, QWidget *parent)
    : QTreeView(parent), d_ptr(pimpl) {
  // init() is called by derived class.
}

qColadaH5TreeView_TEST::~qColadaH5TreeView_TEST() {}

qColadaH5TreeView_TEST_p.h

#ifndef __qColadaH5TreeView_TEST_p_h
#define __qColadaH5TreeView_TEST_p_h

// Colada includes
#include "qColadaH5TreeView_TEST.h"

class Q_COLADA_APP_EXPORT qColadaH5TreeView_TESTPrivate
{
	Q_DECLARE_PUBLIC(qColadaH5TreeView_TEST);

protected:
	qColadaH5TreeView_TEST* const q_ptr;

public:
	typedef qColadaH5TreeView_TESTPrivate Superclass;
	qColadaH5TreeView_TESTPrivate(qColadaH5TreeView_TEST& q);
	virtual ~qColadaH5TreeView_TESTPrivate();

	virtual void init();
};

#endif

For this class I still can’t get bindings. These files are included to my project but I don’t use this qColadaH5TreeView_TEST anywhere (I don’t know should I use it to make bindings?) but qColadaH5TreeView is in use of course.

The build output is:

Build started...
1>------ Build started: Project: SlicerConfigureVersionHeader, Configuration: Debug x64 ------
2>------ Build started: Project: vtkITKHierarchy, Configuration: Debug x64 ------
3>------ Build started: Project: vtkAddonHierarchy, Configuration: Debug x64 ------
4>------ Build started: Project: vtkSegmentationCoreHierarchy, Configuration: Debug x64 ------
1>Configuring vtkSlicerVersionConfigure.h
4>For vtkSegmentationCore - updating vtkSegmentationCoreHierarchy.txt
1>-- Configuring Colada release type [Experimental]
1>-- Found Git: C:/Program Files/Git/cmd/git.exe
1>-- Configuring Slicer version [4.13.0-2021-05-10]
1>-- Configuring Slicer revision [29890]
1>-- Configuring Colada version [0.1.0-2021-03-30]
1>-- Configuring Colada revision [2]
5>------ Build started: Project: vtkTeemHierarchy, Configuration: Debug x64 ------
6>------ Build started: Project: MRMLCoreHierarchy, Configuration: Debug x64 ------
7>------ Build started: Project: MRMLLogicHierarchy, Configuration: Debug x64 ------
8>------ Build started: Project: MRMLCLIHierarchy, Configuration: Debug x64 ------
9>------ Build started: Project: SlicerBaseLogicHierarchy, Configuration: Debug x64 ------
10>------ Build started: Project: qColadaApp, Configuration: Debug x64 ------
10>Generating moc_qColadaH5TreeView_TEST.cpp
10>qColadaH5TreeView_TEST.cxx
10>moc_qColadaH5TreeView_TEST.cpp
10>Generating Code...
10>qColadaApp.vcxproj -> C:\C\d\Slicer-build\bin\Debug\qColadaApp.dll
========== Build: 10 succeeded, 0 failed, 18 up-to-date, 0 skipped ==========

By the way more complex classes that uses third party libraries inside the logic and are inherited from QDialog or QTableView are binded without problems.

Thank you, this will be useful. Could you attach your CMakeLists.txt file as well?

sure, here is the CMakeLists.txt where I use Slicer’s macro to build libs:

#============================================================================
#
# Copyright (c) Kitware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#============================================================================

project(ColadaApp)

include(SlicerMacroBuildApplication)

# --------------------------------------------------------------------------
# Properties
# --------------------------------------------------------------------------
SlicerReadApplicationProperties()

# --------------------------------------------------------------------------
# Folder
# --------------------------------------------------------------------------
set(${PROJECT_NAME}_FOLDER "App-${PROJECT_NAME}")

find_package(OpenMP REQUIRED) # Colada needs OpenMP
find_package(ZLIB REQUIRED)
find_package(HDF5 REQUIRED CONFIG)  # based on HDF5_DIR
find_package(h5geo REQUIRED CONFIG)  # based on h5geo_DIR
find_package(GDAL REQUIRED)

# GDAL::GDAL depends on many libs (TIFF, GEOS etc). Here we are linking them
if(DEFINED GDAL_LIBS)
  target_link_libraries(GDAL::GDAL INTERFACE ${GDAL_LIBS})
endif()

# --------------------------------------------------------------------------
# Application library
# --------------------------------------------------------------------------
set(APPLIB_NAME "q${PROJECT_NAME}")

set(APPLIB_SRCS
  qColadaH5TreeView_TEST.cxx
  qColadaH5TreeView_TEST.h


  qColadaAppMainWindow.cxx
  qColadaAppMainWindow.h

  ColadaDBCore.cxx
  ColadaDBCore.h
  ColadaUtil.cxx
  ColadaUtil.h
  
  qAppStyle.cxx
  qAppStyle.h
  qColadaNewProject.cxx
  qColadaNewProject.h
  qCRSDropTableView.h
  qCRSDropTableView.cxx
  qCRSWidget.cxx
  qCRSWidget.h
  qREValidator.cxx
  qREValidator.h
  qColadaH5TreeView.cxx
  qColadaH5TreeView.h
  qColadaH5SeisTreeView.cxx
  qColadaH5SeisTreeView.h
  qColadaH5SurfTreeView.cxx
  qColadaH5SurfTreeView.h
  qColadaH5WellTreeView.cxx
  qColadaH5WellTreeView.h
  qColadaH5Item.cxx
  qColadaH5Item.h
  qColadaH5ItemDelegate.cxx
  qColadaH5ItemDelegate.h
  qColadaH5Model.cxx
  qColadaH5Model.h
  qColadaH5SeisModel.cxx
  qColadaH5SeisModel.h
  qColadaH5SurfModel.cxx
  qColadaH5SurfModel.h
  qColadaH5WellModel.cxx
  qColadaH5WellModel.h
  qColadaH5ProxyModel.cxx
  qColadaH5ProxyModel.h
  qColadaReader.h
  qColadaReader.cxx
  qCopyPasteTableView.h
  qCopyPasteTableView.cxx
  qScienceSpinBox.h
  qScienceSpinBox.cxx
  qColadaSegyReader.h
  qColadaSegyReader.cxx
  qComboBoxDelegate.h
  qComboBoxDelegate.cxx
  qSpinBoxDelegate.h
  qSpinBoxDelegate.cxx
  qScienceSpinBoxDelegate.h
  qScienceSpinBoxDelegate.cxx
  qLineEditDelegate.h
  qLineEditDelegate.cxx
  qPathEditDelegate.h
  qPathEditDelegate.cxx
  SegyRead.h
  SegyRead.cxx
  )

set(APPLIB_MOC_SRCS
  qColadaH5TreeView_TEST.h


  qColadaAppMainWindow.h
  qAppStyle.h
  qColadaNewProject.h
  qCRSDropTableView.h
  qCRSWidget.h
  qREValidator.h
  qColadaH5TreeView.h
  qColadaH5SurfTreeView.h
  qColadaH5SeisTreeView.h
  qColadaH5WellTreeView.h
  qColadaH5ItemDelegate.h
  qColadaH5Model.h
  qColadaH5SurfModel.h
  qColadaH5SeisModel.h
  qColadaH5WellModel.h
  qColadaH5ProxyModel.h
  qColadaReader.h
  qCopyPasteTableView.h
  qScienceSpinBox.h
  qColadaSegyReader.h
  qComboBoxDelegate.h
  qSpinBoxDelegate.h
  qScienceSpinBoxDelegate.h
  qLineEditDelegate.h
  qPathEditDelegate.h
  )

set(APPLIB_UI_SRCS
  )

set(APPLIB_RESOURCES
  Resources/App.qrc
  )

set(APPLIB_TARGET_LIBRARIES 
  # ColadaCore  # this may be useful when splitting qColadaApp by few subprojects
  OpenMP::OpenMP_CXX
  hdf5::hdf5-shared
  h5geo
  GDAL::GDAL
  ${julia_LIBS}
  )

# Sanity checks
set(include_dirs
  ${Eigen3_INCLUDE_DIR}
  ${h5gt_INCLUDE_DIR}
  ${magic_enum_INCLUDE_DIR}
  ${julia_INCLUDE_DIR}
  )

foreach(var ${include_dirs})
  if(NOT EXISTS "${var}")
    message(FATAL_ERROR "Path to include dir: ${var} doesn't exist!")
  endif()
endforeach()

set(APPLIB_DIRS 
  ${Eigen3_INCLUDE_DIR}
  ${h5gt_INCLUDE_DIR}
  ${magic_enum_INCLUDE_DIR}
  ${julia_INCLUDE_DIR}
  )

slicerMacroBuildAppLibrary(
  NAME ${APPLIB_NAME}
  DESCRIPTION_SUMMARY ${${PROJECT_NAME}_DESCRIPTION_SUMMARY}
  DESCRIPTION_FILE ${${PROJECT_NAME}_DESCRIPTION_FILE}
  APPLICATION_NAME ${${PROJECT_NAME}_APPLICATION_NAME}
  EXPORT_DIRECTIVE "Q_COLADA_APP_EXPORT"
  FOLDER ${${PROJECT_NAME}_FOLDER}
  SRCS ${APPLIB_SRCS}
  MOC_SRCS ${APPLIB_MOC_SRCS}
  UI_SRCS ${APPLIB_UI_SRCS}
  RESOURCES ${APPLIB_RESOURCES}
  WRAP_PYTHONQT
  TARGET_LIBRARIES ${APPLIB_TARGET_LIBRARIES}
  INCLUDE_DIRECTORIES ${APPLIB_DIRS}
  )

# --------------------------------------------------------------------------
# Application executable
# --------------------------------------------------------------------------

# Configure launcher only for the main application
set(extra_args)
if(${PROJECT_NAME} STREQUAL ${Slicer_MAIN_PROJECT})
  set(extra_args CONFIGURE_LAUNCHER)
endif()

set(APP_SRCS
  Main.cxx
  )

set(APP_TARGET_LIBRARIES ${APPLIB_NAME})

slicerMacroBuildApplication(
  NAME ${PROJECT_NAME}
  APPLICATION_NAME ${${PROJECT_NAME}_APPLICATION_NAME}
  LAUNCHER_SPLASHSCREEN_FILE ${${PROJECT_NAME}_LAUNCHER_SPLASHSCREEN_FILE}
  APPLE_ICON_FILE ${${PROJECT_NAME}_APPLE_ICON_FILE}
  WIN_ICON_FILE ${${PROJECT_NAME}_WIN_ICON_FILE}
  LICENSE_FILE ${${PROJECT_NAME}_LICENSE_FILE}
  FOLDER ${${PROJECT_NAME}_FOLDER}
  SRCS ${APP_SRCS}
  TARGET_LIBRARIES ${APP_TARGET_LIBRARIES}
  TARGET_NAME_VAR "APP_TARGET_NAME"
  DEFAULT_SETTINGS_FILE Resources/Settings/DefaultSettings.ini
  ${extra_args}
  )

#-----------------------------------------------------------------------------
message("Slicer_INSTALL_ROOT: ${Slicer_INSTALL_ROOT}")
message("Slicer_INSTALL_BIN_DIR: ${Slicer_INSTALL_BIN_DIR}")
message("julia_ROOT: ${julia_ROOT}")
# Install extension deps packages
# install(CODE "message(\"CPack: - Install directory: ${julia_ROOT}\")")
install(
  DIRECTORY ${julia_ROOT}/  # '/' in the end is necessary
  DESTINATION ${Slicer_INSTALL_ROOT}/julia
  COMPONENT RuntimeLibraries  # 'ALL' doesn't work
  )

# Install libraries
include(${Slicer_SOURCE_DIR}/CMake/SlicerFunctionInstallLibrary.cmake)
slicerInstallLibrary(
  FILE ${HDF5_ROOT}/lib/hdf5
  DESTINATION ${Slicer_INSTALL_BIN_DIR}
  COMPONENT RuntimeLibraries
  )
slicerInstallLibrary(
  FILE ${h5geo_ROOT}/lib/h5geo
  DESTINATION ${Slicer_INSTALL_BIN_DIR}
  COMPONENT RuntimeLibraries
  )

set(EXTENSION_NAME Colada)
set(CPACK_INSTALL_CMAKE_PROJECTS "${CPACK_INSTALL_CMAKE_PROJECTS};${HDF5_ROOT};HDF5;ALL;/")
set(CPACK_INSTALL_CMAKE_PROJECTS "${CPACK_INSTALL_CMAKE_PROJECTS};${h5geo_ROOT};h5geo;ALL;/")
set(CPACK_INSTALL_CMAKE_PROJECTS "${CPACK_INSTALL_CMAKE_PROJECTS};${julia_ROOT};julia;ALL;/")

#-----------------------------------------------------------------------------
# list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR};${EXTENSION_NAME};ALL;/")
# list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS}")
# include(${Slicer_EXTENSION_GENERATE_CONFIG})
# include(${Slicer_SOURCE_DIR}/CMake/SlicerCPack.cmake)
include(${Slicer_SOURCE_DIR}/CMake/SlicerExtensionGenerateConfig.cmake)
include(${Slicer_SOURCE_DIR}/CMake/SlicerExtensionCPack.cmake)

message("HDF5_ROOT: ${HDF5_ROOT}")
message("CPACK_INSTALL_CMAKE_PROJECTS: ${CPACK_INSTALL_CMAKE_PROJECTS}")

I’m sorry, that was my carelessness…
I simply had to rebuild project qColadaAppPythonQt in visual studio (previously I only used to rebuild qColadaApp project)

Now I can see that all my widgets are binded in python

1 Like

It’s great that you’ve figured it out. I reviewed the code and did not find anything wrong and wanted to build these files - but now I don’t need to!

1 Like

Is it possible to bind C++ functions in namespace using PythonQt?

For example I have:

namespace dbcore {
///
/// \brief createDB creates new database as a DEFAULT connection
/// \param fullName
/// \return NOT opened DB
///
QSqlDatabase Q_COLADA_APP_EXPORT createDB(const QString &prjPath, QString &prjName){
  if (QFileInfo(prjName).suffix().compare("db", Qt::CaseInsensitive) != 0) {
    prjName += ".db";
  }
  QDir dir(prjPath + "/" + prjName);
  QString DBFullName = dir.absolutePath();
  QSqlDatabase DB =
      QSqlDatabase::addDatabase("QSQLITE"); // it will be default connection
  DB.setDatabaseName(DBFullName);
  return DB;
}

} // dbcore

and I’m thinking to make python bindings for dbcore::createDB(const QString &prjPath, QString &prjName) function.
Using WRAP_PYTHONQT flag inside cmake script doesn’t work of course.
I guess it should be done in similar way as it is done in PythonQt tests but here I bind function and not a class…

Maybe somebody has experience of doing that?

If you use PythonQt for Python wrapping then you can use decorators to make static or global functions available. See fore example here:

1 Like

One more question about PythonQt wrapper:

I’m trying to wrap C++ class that has a method that accept third party-library’s type argument and that third party library is C++ library that also has python bindings using pybind11. So PythonQt doesn’t know how that third party class looks in python and thus I cannot pass object of that class as an argument in python.

For examle:
I have a simple class:

#ifndef __qColadaTreeTEST_h
#define __qColadaTreeTEST_h

// Qt includes
#include <QTreeView>

// Colada includes
#include "qColadaAppExport.h"

// h5gt includes
#include <h5gt/H5File.hpp>  // has python bindings using pybind11 

class Q_COLADA_APP_EXPORT qColadaTreeTEST : public QTreeView {
  Q_OBJECT

public:
  explicit qColadaTreeTEST(QWidget *parent = nullptr);
  ~qColadaTreeTEST() = default;

public slots:
  bool addH5File(h5gt::File file);  // file -  is of type `h5gt::File` -> it has python bindings
};

#endif

Here bool addH5File(h5gt::File file) accepts h5gt::File-type argument. h5gt is a C++ library and I wrote python bindings for it using pybind11. h5gt library is available in my SlicerCAT.

After PythonQt has successfully created bindings for qColadaTreeTEST I do in SlicerCAT’s python:

import qColadaAppPythonQt
q = qColadaAppPythonQt.qColadaTreeTEST()

from h5gtpy import h5gt
file_name = 'D:/tmp/test.h5'
file = h5gt.File(file_name, h5gt.OpenFlag(h5gt.ReadWrite | h5gt.Create | h5gt.Truncate))

q.addH5File(file) // here I get the following error:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
ValueError: Called addH5File(h5gt::File file) -> bool with wrong arguments: (<h5gtpy._h5gt.File object at 0x0000022035169730>,)

thus python (and PythonQt) can’t match C++ class h5gt::File with python h5gtpy._h5gt.File python type. How to inform PythonQt that h5gt::File C++ class is the same as h5gtpy._h5gt.File in python?

P.S.
I did exactly the same using pybind when I was writing python wrapper for my h5geo C++ library that depends on h5gt and I solved this by preliminary importing h5gt module inside wrapping code:

PYBIND11_MODULE(_h5geo, m) {
  py::module_::import("h5gtpy._h5gt"); // this code informs pybind about h5gt types

...
}

Probably I should act in similar way? Can’t understand where should I import h5gt module as it needs to be compiled (it must be already known) before qColadaTreeTEST class is compiled.

Now the wrapper for qColadaTreeTEST is done automatically using WRAP_PYTHONQT key word inside Slicer’s macro in CMakeLists.txt