FEM: Handle python vtk user installs that conflict with FreeCAD VTK

This commit is contained in:
Stefan Tröger
2025-04-30 19:08:22 +02:00
parent 96e9e7a17a
commit 0304faa2bc
6 changed files with 293 additions and 0 deletions

View File

@@ -40,6 +40,11 @@
#ifdef FC_USE_VTK
#include "FemPostPipeline.h"
#include "FemVTKTools.h"
#include <LibraryVersions.h>
#endif
#ifdef FC_USE_VTK_PYTHON
#include <vtkPythonUtil.h>
#endif
@@ -75,6 +80,14 @@ public:
&Module::writeResult,
"write a CFD or FEM result (auto detect) to a file (file format "
"detected from file suffix)");
add_varargs_method("getVtkVersion",
&Module::getVtkVersion,
"Returns the VTK version freeCAD is linked against");
#ifdef FC_USE_VTK_PYTHON
add_varargs_method("isVtkCompatible",
&Module::isVtkCompatible,
"Checks if the passed vtkObject is compatible with the c++ VTK version freecad uses");
#endif
#endif
add_varargs_method("show",
&Module::show,
@@ -318,6 +331,34 @@ private:
return Py::None();
}
Py::Object getVtkVersion(const Py::Tuple& args)
{
if (!PyArg_ParseTuple(args.ptr(),"")) {
throw Py::Exception();
}
return Py::String(fcVtkVersion);
}
#ifdef FC_USE_VTK_PYTHON
Py::Object isVtkCompatible(const Py::Tuple& args)
{
PyObject* pcObj = nullptr;
if (!PyArg_ParseTuple(args.ptr(),"O", &pcObj)) {
throw Py::Exception();
}
// if non is returned the vtk object was created by annother vtk library, and the
// python api used to create it cannot be used with FreeCAD
vtkObjectBase *obj = vtkPythonUtil::GetPointerFromObject(pcObj, "vtkObject");
if (!obj) {
PyErr_Clear();
return Py::False();
}
return Py::True();
}
#endif
#endif
Py::Object show(const Py::Tuple& args)

View File

@@ -639,6 +639,7 @@ SET(FemGuiUtils_SRCS
femguiutils/disambiguate_solid_selection.py
femguiutils/migrate_gui.py
femguiutils/selection_widgets.py
femguiutils/vtk_module_handling.py
)
SET(FemGuiViewProvider_SRCS

View File

@@ -80,6 +80,10 @@ class FemWorkbench(Workbench):
False if FemGui.__name__ else True
False if femcommands.commands.__name__ else True
# check vtk version to potentially find missmatchs
from femguiutils.vtk_module_handling import vtk_module_handling
vtk_module_handling()
def GetClassName(self):
# see https://forum.freecad.org/viewtopic.php?f=10&t=43300
return "FemGui::Workbench"

View File

@@ -34,6 +34,7 @@ import FreeCAD
from femtools.femutils import expandParentObject
from femtools.femutils import is_of_type
from femguiutils.vtk_module_handling import vtk_compatibility_abort
if FreeCAD.GuiUp:
from PySide import QtCore
@@ -381,6 +382,10 @@ class CommandManager:
# like add_obj_on_gui_selobj_noset_edit but the selection is kept
# and the selobj is expanded in the tree to see the added obj
# check if we should use python fitler
if vtk_compatibility_abort(True):
return
# Note: we know selobj is a FemPostObject as otherwise the command should not have been active
# We also assume the all filters are in PostGroups and not astray
group = None

View File

@@ -0,0 +1,236 @@
# ***************************************************************************
# * Copyright (c) 2025 Stefan Tröger <stefantroeger@gmx.net> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Methods to verify if the python VTK module is the correct one
FreeCAD is linked with VTK libraries during its build process. To use the VTK
python module and pass objects between python and c++ the compiled module library
needs to be linked to the exact same vtk library as FreeCAD is. This is ensured by
installing VTK via linux app managers: All known distros install the python side
packages together with vtk libs. Libpack and other OS systems ensure this too.
However, if a vtk python package is installed manually, e.g. by "pip install vtk",
it could be found instead of the system module. This python API brings its own
set of VTK libs, and hence object passing in FreeCAD fails. (Note: import and
pure vtk python code still works, only passing to c++ fails)
This file provides functions that detect this situation and inform the user.
Additionally we try to find the correct module in the path and offer to use
it instead.
Note that this problem occurs with all "compiled binary" python APIs, also
with PySide. It is the users responsibility to handle his python path and keep
it clean/working. The functions provided here are a workaround only.
"""
__title__ = "FEM GUI vtk python module check"
__author__ = "Stefan Tröger"
__url__ = "https://www.freecad.org"
__user_input_received = False
def vtk_module_compatible():
# checks if the VTK library FreeCAD is build against is the one used by
# the python module
# make sure we do not contaminate the modules with vtk to not trick
# the check later
unload = not _vtk_is_loaded()
import Fem
from vtkmodules.vtkCommonCore import vtkVersion, vtkBitArray
# simple version check
if Fem.getVtkVersion() != vtkVersion.GetVTKVersion():
return False
# check binary compatibility
result = Fem.isVtkCompatible(vtkBitArray())
if unload:
# cleanup our own import
_unload_vtk_modules()
return result
def _vtk_is_loaded():
import sys
return any("vtkmodules" in module for module in sys.modules)
def _unload_vtk_modules():
# unloads all loaded vtk modules
# NOTE: does not remove any stored references in objects
import sys
for module in sys.modules.copy():
if "vtkmodules" in module:
del sys.modules[module]
def _find_compatible_module():
# Check all python path folders if they contain a vtk module
import Fem
import sys
# remove module from runtime
_unload_vtk_modules()
path = sys.path.copy()
for folder in reversed(path):
try:
# use a single folder as path and try to load vtk
sys.path = [folder]
if vtk_module_compatible():
# we do still unload, to let the user descide if he wants to use it
_unload_vtk_modules()
sys.path = path
return folder
except:
continue
# reset the correct path and indicate that we failed
sys.path = path
return None
def _load_vtk_from(folder):
import sys
path = sys.path
try:
sys.path = [folder]
import vtkmodules
finally:
sys.path = path
# If FreeCAD is build with VTK python support this function checks if the
# used python module is compatible with the c++ lib. Does inform the user
# if not so and offers the correct module, if available
#
# Note: Call this also from Python feature module, as on document load
# this can be loaded before initializing FEM workbench.
def vtk_module_handling():
import sys
import FreeCAD
if not "BUILD_FEM_VTK_PYTHON" in FreeCAD.__cmake__:
# no VTK python api support in FreeCAD
return
# only ask user once per session
global __user_input_received
if __user_input_received:
return
__user_input_received = True
loaded = _vtk_is_loaded()
# check if we are compatible
if not vtk_module_compatible():
if not FreeCAD.GuiUp:
FreeCAD.Console.PrintError("FEM: vtk python module is not compatible with internal vtk library")
return
import FreeCAD, Fem
from vtkmodules.vtkCommonCore import vtkVersion
import inspect
from PySide import QtGui
translate = FreeCAD.Qt.translate
path = inspect.getfile(vtkVersion)
path = path[:path.find("vtkmodules")]
message = translate("FEM", ("FreeCAD is linked to a different VTK library then the imported "
"VTK python module. This is incompatible and will lead to errors."
"\n\nWrong python module is imported from: \n{}")).format(path)
buttons = QtGui.QMessageBox.Discard
# check if there is any compatible vtk module
compatible_module = _find_compatible_module()
if compatible_module:
# there is a compatible module of VTK available.
message += translate("FEM", "\n\nCorrect module found in: \n{}").format(compatible_module)
if not loaded:
# vtk was not loaded beforehand, therefore we can realistically reload
message += translate("FEM", "\n\nShould this module be loaded instead?")
buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
else:
message += translate("FEM", ("\n\nAs the wrong module was already loaded, a reload is not possible. "
"Restart FreeCAD to get the option for loading this module."))
else:
message += translate("FEM", "\n\nNo matching module was found in the current python path.")
# raise a dialog to the user
import FreeCADGui
button = QtGui.QMessageBox.critical(
FreeCADGui.getMainWindow(),
translate("FEM", "VTK module conflict"),
message,
buttons=buttons,
)
if button == QtGui.QMessageBox.Yes:
# try to reload the correct vtk module
_load_vtk_from(compatible_module)
# Returns if vtk python is incompatible and hence operations need to be aborted.
# If inform=True the user gets informed by dialog about incompatibilities
def vtk_compatibility_abort(inform=True):
if not vtk_module_compatible():
if inform:
# raise a dialog to the user that this functionality is not available
import FreeCAD
import FreeCADGui
from PySide import QtGui
translate = FreeCAD.Qt.translate
button = QtGui.QMessageBox.critical(
FreeCADGui.getMainWindow(),
translate("FEM", "VTK module conflict"),
translate("FEM", "This functionality is not available due to VTK python module conflict"),
buttons=QtGui.QMessageBox.Discard,
)
return True
return False

View File

@@ -29,6 +29,12 @@ __url__ = "https://www.freecad.org"
# \ingroup FEM
# \brief Post processing filter creating glyphs for vector fields
import FreeCAD
# check vtk version to potentially find missmatchs
from femguiutils.vtk_module_handling import vtk_module_handling
vtk_module_handling()
# IMPORTANT: Never import vtk directly. Often vtk is compiled with different QT
# version than FreeCAD, and "import vtk" crashes by importing qt components.
# Always import the filter and data modules only.