FEM: Handle python vtk user installs that conflict with FreeCAD VTK
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
236
src/Mod/Fem/femguiutils/vtk_module_handling.py
Normal file
236
src/Mod/Fem/femguiutils/vtk_module_handling.py
Normal 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
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user