From 70685aaa5d40f7a9910fbdfa33e07dd865cc69f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sun, 17 Aug 2025 18:29:31 +0200 Subject: [PATCH 1/5] FEM: Purge tool deletes all result objects. Fixes #23028 - Correctly deletes children of pipeline without error on delete - Ensures visualizations are also deleted as they are result objects too - Ensures purge tool uses a transaction, like all tools should --- src/Mod/Fem/App/FemPostGroupExtension.cpp | 7 ++----- src/Mod/Fem/femcommands/commands.py | 2 ++ src/Mod/Fem/femobjects/base_fempostvisualizations.py | 1 + src/Mod/Fem/femresult/resulttools.py | 6 ++++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Mod/Fem/App/FemPostGroupExtension.cpp b/src/Mod/Fem/App/FemPostGroupExtension.cpp index 5ecad291be..d7f8771c97 100644 --- a/src/Mod/Fem/App/FemPostGroupExtension.cpp +++ b/src/Mod/Fem/App/FemPostGroupExtension.cpp @@ -109,11 +109,8 @@ App::DocumentObject* FemPostGroupExtension::getGroupOfObject(const App::Document void FemPostGroupExtension::onExtendedUnsetupObject() { - // remove all children! - auto document = getExtendedObject()->getDocument(); - for (const auto& obj : Group.getValues()) { - document->removeObject(obj->getNameInDocument()); - } + // remove all children (if not already removed)! + removeObjectsFromDocument(); } bool FemPostGroupExtension::allowObject(App::DocumentObject* obj) diff --git a/src/Mod/Fem/femcommands/commands.py b/src/Mod/Fem/femcommands/commands.py index b167c65068..eb827ee6b1 100644 --- a/src/Mod/Fem/femcommands/commands.py +++ b/src/Mod/Fem/femcommands/commands.py @@ -926,7 +926,9 @@ class _ResultsPurge(CommandManager): def Activated(self): import femresult.resulttools as resulttools + FreeCAD.ActiveDocument.openTransaction("Purge FEM results") resulttools.purge_results(self.active_analysis) + FreeCAD.ActiveDocument.commitTransaction() class _SolverCalculixContextManager: diff --git a/src/Mod/Fem/femobjects/base_fempostvisualizations.py b/src/Mod/Fem/femobjects/base_fempostvisualizations.py index 5c7465d5bc..49797971d5 100644 --- a/src/Mod/Fem/femobjects/base_fempostvisualizations.py +++ b/src/Mod/Fem/femobjects/base_fempostvisualizations.py @@ -80,6 +80,7 @@ class PostVisualization(base_fempythonobject.BaseFemPythonObject): def __init__(self, obj): super().__init__(obj) + self.Type = "Fem::FemPostVisualization" obj.addExtension("App::GroupExtensionPython") self._setup_properties(obj) diff --git a/src/Mod/Fem/femresult/resulttools.py b/src/Mod/Fem/femresult/resulttools.py index 4b4bb2d39e..445143c5aa 100644 --- a/src/Mod/Fem/femresult/resulttools.py +++ b/src/Mod/Fem/femresult/resulttools.py @@ -75,6 +75,12 @@ def purge_results(analysis): analysis.Document.removeObject(m.Name) analysis.Document.recompute() + # remove visulizations + for m in analysis.Group: + if is_of_type(m, "Fem::FemPostVisualization"): + analysis.Document.removeObject(m.Name) + analysis.Document.recompute() + def reset_mesh_deformation(resultobj): """Resets result mesh deformation. From b20fe6039efaee12a8c4d399e6a826fe14777ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sun, 17 Aug 2025 18:45:15 +0200 Subject: [PATCH 2/5] FEM: Update color bar of children when switching postprocessing frames --- src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp index a78af62cd7..be8a7c369d 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp @@ -58,9 +58,12 @@ void ViewProviderFemPostPipeline::updateData(const App::Property* prop) FemGui::ViewProviderFemPostObject::updateData(prop); Fem::FemPostPipeline* pipeline = getObject(); - if ((prop == &pipeline->Data) || (prop == &pipeline->Group)) { + if ((prop == &pipeline->Data) || + (prop == &pipeline->Group)|| + (prop == &pipeline->Frame)) { updateFunctionSize(); + updateColorBars(); } } From c9ca852ad051d14e874eb00a6f5e72469ef064c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sun, 17 Aug 2025 19:52:09 +0200 Subject: [PATCH 3/5] FEM: Keep result pipeline and only reload data. Fixes #20542 --- src/App/GroupExtension.pyi | 7 +++++ src/App/GroupExtensionPyImp.cpp | 16 ++++++++++ src/Mod/Fem/feminout/importCcxFrdResults.py | 18 +++++++++-- src/Mod/Fem/femresult/resulttools.py | 35 +++++++++++++++++---- src/Mod/Fem/femtools/ccxtools.py | 6 +++- 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/App/GroupExtension.pyi b/src/App/GroupExtension.pyi index 8d50bc416b..d5d66a924b 100644 --- a/src/App/GroupExtension.pyi +++ b/src/App/GroupExtension.pyi @@ -61,6 +61,13 @@ class GroupExtension(DocumentObjectExtension): """ ... + def getObjectsOfType(self, typename: str) -> List[Any]: + """ + Returns all object in the group of given type + @param typename The Freecad type identifier + """ + ... + def hasObject(self, obj: Any, recursive: bool = False) -> bool: """ hasObject(obj, recursive=false) diff --git a/src/App/GroupExtensionPyImp.cpp b/src/App/GroupExtensionPyImp.cpp index fc281215d8..72e2eacc80 100644 --- a/src/App/GroupExtensionPyImp.cpp +++ b/src/App/GroupExtensionPyImp.cpp @@ -278,6 +278,22 @@ PyObject* GroupExtensionPy::getObject(PyObject* args) } } +PyObject* GroupExtensionPy::getObjectsOfType(PyObject* args) +{ + char* pcName; + if (!PyArg_ParseTuple(args, "s", &pcName)) { + return nullptr; + } + + std::vector objs = getGroupExtensionPtr()->getObjectsOfType(Base::Type::fromName(pcName)); + Py::List result; + for (App::DocumentObject* obj : objs) { + result.append(Py::asObject(obj->getPyObject())); + } + + return Py::new_reference_to(result); +} + PyObject* GroupExtensionPy::hasObject(PyObject* args) { PyObject* object; diff --git a/src/Mod/Fem/feminout/importCcxFrdResults.py b/src/Mod/Fem/feminout/importCcxFrdResults.py index 32735e28ff..a26a97c8ad 100644 --- a/src/Mod/Fem/feminout/importCcxFrdResults.py +++ b/src/Mod/Fem/feminout/importCcxFrdResults.py @@ -66,21 +66,33 @@ def setupPipeline(doc, analysis, results_name, result_data): if not "BUILD_FEM_VTK" in FreeCAD.__cmake__: return - # create a results pipeline if not already existing + # create a results pipeline (dependend on user settings) pipeline_name = "Pipeline_" + results_name - pipeline_obj = doc.getObject(pipeline_name) - if pipeline_obj is None: + pipelines = analysis.getObjectsOfType("Fem::FemPostPipeline") + fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General") + keep_results_on_rerun = fem_prefs.GetBool("KeepResultsOnReRun", False) + if not pipelines or keep_results_on_rerun: + # needs to create a new pipeline! pipeline_obj = ObjectsFem.makePostVtkResult(doc, result_data, results_name) pipeline_visibility = True if analysis: analysis.addObject(pipeline_obj) else: + # by default get the last one + pipeline_obj = pipelines[-1] + # maybe there is one with the correct name + named_pipeline = analysis.getObject(pipeline_name) + if named_pipeline: + pipeline_obj = named_pipeline + if FreeCAD.GuiUp: # store pipeline visibility because pipeline_obj.load makes the # pipeline always visible pipeline_visibility = pipeline_obj.ViewObject.Visibility + # relabel the pipeline and load the data into it + pipeline_obj.Label = pipeline_name pipeline_obj.load(*result_data) # update the pipeline diff --git a/src/Mod/Fem/femresult/resulttools.py b/src/Mod/Fem/femresult/resulttools.py index 445143c5aa..d434a64396 100644 --- a/src/Mod/Fem/femresult/resulttools.py +++ b/src/Mod/Fem/femresult/resulttools.py @@ -34,7 +34,7 @@ import FreeCAD from femtools.femutils import is_of_type -def purge_results(analysis): +def purge_result_objects(analysis): """Removes all result objects and result meshes from an analysis group. Parameters @@ -43,11 +43,6 @@ def purge_results(analysis): analysis group as a container for all objects needed for the analysis """ - # if analysis type check is used, result mesh - # without result obj is created in the analysis - # we could run into trouble in one loop because - # we will delete objects and try to access them later - # result object for m in analysis.Group: if m.isDerivedFrom("Fem::FemResultObject"): @@ -68,6 +63,16 @@ def purge_results(analysis): analysis.Document.removeObject(m.Name) analysis.Document.recompute() + +def purge_postprocessing_objects(analysis): + """Removes all postprocessing objects and visualizations form the analysis + + Parameters + ---------- + analysis : Fem::FemAnalysis + analysis group as a container for all objects needed for the analysis + """ + # result pipeline and filter for m in analysis.Group: if is_of_type(m, "Fem::FemPostPipeline"): @@ -82,6 +87,24 @@ def purge_results(analysis): analysis.Document.recompute() +def purge_results(analysis): + """Removes all result and postprocessing objects and result meshes from an analysis group. + + Parameters + ---------- + analysis : Fem::FemAnalysis + analysis group as a container for all objects needed for the analysis + """ + + # if analysis type check is used, result mesh + # without result obj is created in the analysis + # we could run into trouble in one loop because + # we will delete objects and try to access them later + + purge_result_objects(analysis) + purge_postprocessing_objects(analysis) + + def reset_mesh_deformation(resultobj): """Resets result mesh deformation. diff --git a/src/Mod/Fem/femtools/ccxtools.py b/src/Mod/Fem/femtools/ccxtools.py index 9589db4a6b..c718621fa5 100644 --- a/src/Mod/Fem/femtools/ccxtools.py +++ b/src/Mod/Fem/femtools/ccxtools.py @@ -162,7 +162,11 @@ class FemToolsCcx(QtCore.QRunnable, QtCore.QObject): self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General") keep_results_on_rerun = self.fem_prefs.GetBool("KeepResultsOnReRun", False) if not keep_results_on_rerun: - self.purge_results() + # we remove the result objects only, not the postprocessing ones. + # Reason: "Not keep results" means for the user override the data. For postprocessing + # this means keeping all filters, just change the data. + from femresult.resulttools import purge_result_objects as purge + purge(self.analysis) def reset_all(self): """Reset mesh color, deformation and removes all result objects""" From 7cd161f06616d6c5423320e9efeeda48ed7ee6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Tue, 19 Aug 2025 17:42:20 +0200 Subject: [PATCH 4/5] FEM: Update color bar when switching postprocessing frames. Fixes #22284 --- src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp index be8a7c369d..dacdb462a4 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp @@ -153,7 +153,7 @@ void ViewProviderFemPostPipeline::updateColorBars() } // if pipeline is visible, update it - if (this->isVisible()) { + if (this->Visibility.getValue()) { updateMaterial(); } } From 40ada2daf54a9b63d9d647b48ed041e6c773908b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:47:52 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp | 4 +--- src/Mod/Fem/feminout/importCcxFrdResults.py | 2 +- src/Mod/Fem/femtools/ccxtools.py | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp index dacdb462a4..2a14b7a47c 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp @@ -58,9 +58,7 @@ void ViewProviderFemPostPipeline::updateData(const App::Property* prop) FemGui::ViewProviderFemPostObject::updateData(prop); Fem::FemPostPipeline* pipeline = getObject(); - if ((prop == &pipeline->Data) || - (prop == &pipeline->Group)|| - (prop == &pipeline->Frame)) { + if ((prop == &pipeline->Data) || (prop == &pipeline->Group) || (prop == &pipeline->Frame)) { updateFunctionSize(); updateColorBars(); diff --git a/src/Mod/Fem/feminout/importCcxFrdResults.py b/src/Mod/Fem/feminout/importCcxFrdResults.py index a26a97c8ad..f833887f16 100644 --- a/src/Mod/Fem/feminout/importCcxFrdResults.py +++ b/src/Mod/Fem/feminout/importCcxFrdResults.py @@ -68,7 +68,7 @@ def setupPipeline(doc, analysis, results_name, result_data): # create a results pipeline (dependend on user settings) pipeline_name = "Pipeline_" + results_name - pipelines = analysis.getObjectsOfType("Fem::FemPostPipeline") + pipelines = analysis.getObjectsOfType("Fem::FemPostPipeline") fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General") keep_results_on_rerun = fem_prefs.GetBool("KeepResultsOnReRun", False) diff --git a/src/Mod/Fem/femtools/ccxtools.py b/src/Mod/Fem/femtools/ccxtools.py index c718621fa5..311ae865a6 100644 --- a/src/Mod/Fem/femtools/ccxtools.py +++ b/src/Mod/Fem/femtools/ccxtools.py @@ -165,7 +165,8 @@ class FemToolsCcx(QtCore.QRunnable, QtCore.QObject): # we remove the result objects only, not the postprocessing ones. # Reason: "Not keep results" means for the user override the data. For postprocessing # this means keeping all filters, just change the data. - from femresult.resulttools import purge_result_objects as purge + from femresult.resulttools import purge_result_objects as purge + purge(self.analysis) def reset_all(self):