From ea6c95a87065d97c79a1e68b5a0c806fca6f1330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Fri, 25 Apr 2025 18:47:06 +0200 Subject: [PATCH 1/5] FEM: Fix post object load of unsupported VTK data type --- src/Mod/Fem/App/PropertyPostDataObject.cpp | 49 ++++++++++++---------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/Mod/Fem/App/PropertyPostDataObject.cpp b/src/Mod/Fem/App/PropertyPostDataObject.cpp index e075a9cbeb..37c28243a2 100644 --- a/src/Mod/Fem/App/PropertyPostDataObject.cpp +++ b/src/Mod/Fem/App/PropertyPostDataObject.cpp @@ -211,7 +211,7 @@ void PropertyPostDataObject::createDataObjectByExternalType(vtkSmartPointer::New(); break; default: - break; + throw Base::TypeError("Unsupported VTK data type"); }; } @@ -432,7 +432,7 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) // TODO: read in of composite data structures need to be coded, // including replace of "GetOutputAsDataSet()" - vtkSmartPointer xmlReader; + vtkSmartPointer xmlReader = nullptr; if (extension == "vtp") { xmlReader = vtkSmartPointer::New(); } @@ -489,31 +489,38 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) xmlReader = vtkSmartPointer::New(); } - xmlReader->SetFileName(fi.filePath().c_str()); - xmlReader->Update(); + if (xmlReader) { + xmlReader->SetFileName(fi.filePath().c_str()); + xmlReader->Update(); - if (!xmlReader->GetOutputDataObject(0)) { - // Note: Do NOT throw an exception here because if the tmp. created file could - // not be read it's NOT an indication for an invalid input stream 'reader'. - // We only print an error message but continue reading the next files from the - // stream... - App::PropertyContainer* father = this->getContainer(); - if (father && father->isDerivedFrom()) { - App::DocumentObject* obj = static_cast(father); - Base::Console().Error("Dataset file '%s' with data of '%s' seems to be empty\n", - fi.filePath().c_str(), - obj->Label.getValue()); + if (!xmlReader->GetOutputDataObject(0)) { + // Note: Do NOT throw an exception here because if the tmp. created file could + // not be read it's NOT an indication for an invalid input stream 'reader'. + // We only print an error message but continue reading the next files from the + // stream... + App::PropertyContainer* father = this->getContainer(); + if (father && father->isDerivedFrom()) { + App::DocumentObject* obj = static_cast(father); + Base::Console().Error("Dataset file '%s' with data of '%s' seems to be empty\n", + fi.filePath().c_str(), + obj->Label.getValue()); + } + else { + Base::Console().Warning("Loaded Dataset file '%s' seems to be empty\n", + fi.filePath().c_str()); + } } else { - Base::Console().Warning("Loaded Dataset file '%s' seems to be empty\n", - fi.filePath().c_str()); + aboutToSetValue(); + createDataObjectByExternalType(xmlReader->GetOutputDataObject(0)); + m_dataObject->DeepCopy(xmlReader->GetOutputDataObject(0)); + hasSetValue(); } } else { - aboutToSetValue(); - createDataObjectByExternalType(xmlReader->GetOutputDataObject(0)); - m_dataObject->DeepCopy(xmlReader->GetOutputDataObject(0)); - hasSetValue(); + Base::Console().Error("Dataset file '%s' is of unsupportet type: %s. Data not loaded.\n", + fi.filePath().c_str(), + extension); } } From 642eff16632db62f01a54ed1deec78e911ad9bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Fri, 25 Apr 2025 19:41:00 +0200 Subject: [PATCH 2/5] FEM: Ensure finite frame values only. Fixes #20933 --- src/Mod/Fem/App/FemPostPipeline.cpp | 37 +++++++++++++++++---- src/Mod/Fem/App/FemPostPipelinePyImp.cpp | 24 ++++++++----- src/Mod/Fem/feminout/importCcxFrdResults.py | 3 ++ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/Mod/Fem/App/FemPostPipeline.cpp b/src/Mod/Fem/App/FemPostPipeline.cpp index 85e3b988bb..e76954d4a9 100644 --- a/src/Mod/Fem/App/FemPostPipeline.cpp +++ b/src/Mod/Fem/App/FemPostPipeline.cpp @@ -23,6 +23,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include #include #include #include @@ -313,10 +314,23 @@ void FemPostPipeline::read(std::vector& files, std::string& frame_type) { if (files.size() != values.size()) { - Base::Console().Error("Result files and frame values have different length.\n"); - return; + throw Base::ValueError("Result files and frame values have different length"); } + // make sure we do not have invalid values + for (auto& value : values) { + if (!std::isfinite(value)) { + throw Base::ValueError("Values need to be finite"); + } + } + + // ensure no double values for frames + std::set value_set(values.begin(), values.end()); + if (value_set.size() != values.size()) { + throw Base::ValueError("Values need to be unique"); + } + + // setup the time information for the multiblock vtkStringArray* TimeInfo = vtkStringArray::New(); TimeInfo->SetName("TimeInfo"); @@ -650,8 +664,20 @@ void FemPostPipeline::load(std::vector& res, { if (res.size() != values.size()) { - Base::Console().Error("Result values and frame values have different length.\n"); - return; + throw Base::ValueError("Result values and frame values have different length"); + } + + // make sure we do not have invalid values + for (auto& value : values) { + if (!std::isfinite(value)) { + throw Base::ValueError("Values need to be finite"); + } + } + + // ensure no double values for frames + std::set value_set(values.begin(), values.end()); + if (value_set.size() != values.size()) { + throw Base::ValueError("Values need to be unique"); } // setup the time information for the multiblock @@ -664,8 +690,7 @@ void FemPostPipeline::load(std::vector& res, for (ulong i = 0; i < res.size(); i++) { if (!res[i]->Mesh.getValue()->isDerivedFrom()) { - Base::Console().Error("Result mesh object is not derived from Fem::FemMeshObject.\n"); - return; + throw Base::ValueError("Result mesh object is not derived from Fem::FemMeshObject"); } // first copy the mesh over diff --git a/src/Mod/Fem/App/FemPostPipelinePyImp.cpp b/src/Mod/Fem/App/FemPostPipelinePyImp.cpp index 9de89e34cb..971924c675 100644 --- a/src/Mod/Fem/App/FemPostPipelinePyImp.cpp +++ b/src/Mod/Fem/App/FemPostPipelinePyImp.cpp @@ -172,7 +172,8 @@ PyObject* FemPostPipelinePy::load(PyObject* args) std::string error = std::string( "Result and value must be list of ResultObject and number respectively."); - throw Base::TypeError(error); + PyErr_SetString(PyExc_TypeError, error.c_str()); + return nullptr; } // extract the result objects @@ -186,11 +187,15 @@ PyObject* FemPostPipelinePy::load(PyObject* args) if (!PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) { std::string error = std::string("type in result list must be 'ResultObject', not "); - throw Base::TypeError(error); + PyErr_SetString(PyExc_TypeError, error.c_str()); + return nullptr; } auto obj = static_cast(*item)->getDocumentObjectPtr(); if (!obj->isDerivedFrom()) { - throw Base::TypeError("object is not a result object"); + std::string error = + std::string("type in result list must be 'ResultObject', not "); + PyErr_SetString(PyExc_TypeError, error.c_str()); + return nullptr; } results[i] = static_cast(obj); } @@ -202,12 +207,12 @@ PyObject* FemPostPipelinePy::load(PyObject* args) values.resize(size); for (Py::Sequence::size_type i = 0; i < size; i++) { - Py::Object item = values_list[i]; - if (!PyFloat_Check(*item)) { - std::string error = std::string("Values must be float"); - throw Base::TypeError(error); + Py::Object value = values_list[i]; + if (!value.isNumeric()) { + PyErr_SetString(PyExc_TypeError, "Values must be numbers"); + return nullptr; } - values[i] = PyFloat_AsDouble(*item); + values[i] = Py::Float(value).as_double(); } // extract the unit @@ -223,7 +228,8 @@ PyObject* FemPostPipelinePy::load(PyObject* args) else { std::string error = std::string( "Multistep load requires 4 arguments: ResultList, ValueList, unit, type"); - throw Base::TypeError(error); + PyErr_SetString(PyExc_ValueError, error.c_str()); + return nullptr; } } return nullptr; diff --git a/src/Mod/Fem/feminout/importCcxFrdResults.py b/src/Mod/Fem/feminout/importCcxFrdResults.py index 79d88ec032..3f37879b46 100644 --- a/src/Mod/Fem/feminout/importCcxFrdResults.py +++ b/src/Mod/Fem/feminout/importCcxFrdResults.py @@ -32,6 +32,7 @@ __url__ = "https://www.freecad.org" # \brief FreeCAD Calculix FRD Reader for FEM workbench import os +import math import FreeCAD from FreeCAD import Console @@ -133,6 +134,8 @@ def importFrd(filename, analysis=None, result_name_prefix="", result_analysis_ty else: eigenmode_number = 0 step_time = result_set["time"] + if not math.isfinite(step_time): + step_time = 0 step_time = round(step_time, 2) if eigenmode_number > 0: results_name = "{}EigenMode_{}_Results".format( From 7a630f4b10f1fef06cc267103bb5ccfe9386a524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Fri, 25 Apr 2025 19:46:43 +0200 Subject: [PATCH 3/5] FEM: Add post pipeline for check analysis type. Fixes #20936 --- src/Mod/Fem/feminout/importCcxFrdResults.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Fem/feminout/importCcxFrdResults.py b/src/Mod/Fem/feminout/importCcxFrdResults.py index 3f37879b46..82859cd10d 100644 --- a/src/Mod/Fem/feminout/importCcxFrdResults.py +++ b/src/Mod/Fem/feminout/importCcxFrdResults.py @@ -255,6 +255,7 @@ def importFrd(filename, analysis=None, result_name_prefix="", result_analysis_ty elif result_analysis_type == "check": results_name = f"{result_name_prefix}Check" res_obj = make_result_mesh(results_name) + setupPipeline(doc, analysis, results_name, [res_obj]) if analysis: analysis.addObject(res_obj) @@ -279,6 +280,7 @@ def importFrd(filename, analysis=None, result_name_prefix="", result_analysis_ty results_name = "Results" res_obj = ObjectsFem.makeResultMechanical(doc, results_name) res_obj.Mesh = result_mesh_object + setupPipeline(doc, analysis, results_name, [res_obj]) # TODO, node numbers in result obj could be set if analysis: analysis.addObject(res_obj) From dcb35d0107c2ce31ef46ce5c40ef7fb1a9f1b101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Fri, 25 Apr 2025 20:24:31 +0200 Subject: [PATCH 4/5] FEM: Post task dialog opens transaction only if none is pending. Fixes item 4 in #20263 --- src/Mod/Fem/Gui/TaskPostBoxes.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index 37047f9883..909ca629e1 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -316,9 +316,11 @@ void TaskDlgPost::appendBox(TaskPostBox* box) void TaskDlgPost::open() { - // a transaction is already open at creation time of the pad - QString msg = QObject::tr("Edit post processing object"); - Gui::Command::openCommand(msg.toUtf8().constData()); + // only open a new command if non is pending (e.g. if the object was nely created) + if (!Gui::Command::hasPendingCommand()) { + auto text = std::string("Edit ") + m_view->getObject()->Label.getValue(); + Gui::Command::openCommand(text.c_str()); + } } void TaskDlgPost::clicked(int button) From 10eb015353bc0b114d6b6488d942234b423e8a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Mon, 28 Apr 2025 18:54:11 +0200 Subject: [PATCH 5/5] FEM: correct typos --- src/Mod/Fem/App/FemPostPipelinePyImp.cpp | 4 ++-- src/Mod/Fem/App/PropertyPostDataObject.cpp | 11 ++++++----- src/Mod/Fem/Gui/TaskPostBoxes.cpp | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Mod/Fem/App/FemPostPipelinePyImp.cpp b/src/Mod/Fem/App/FemPostPipelinePyImp.cpp index 971924c675..be59cdefb2 100644 --- a/src/Mod/Fem/App/FemPostPipelinePyImp.cpp +++ b/src/Mod/Fem/App/FemPostPipelinePyImp.cpp @@ -228,8 +228,8 @@ PyObject* FemPostPipelinePy::load(PyObject* args) else { std::string error = std::string( "Multistep load requires 4 arguments: ResultList, ValueList, unit, type"); - PyErr_SetString(PyExc_ValueError, error.c_str()); - return nullptr; + PyErr_SetString(PyExc_ValueError, error.c_str()); + return nullptr; } } return nullptr; diff --git a/src/Mod/Fem/App/PropertyPostDataObject.cpp b/src/Mod/Fem/App/PropertyPostDataObject.cpp index 37c28243a2..b8d953fcca 100644 --- a/src/Mod/Fem/App/PropertyPostDataObject.cpp +++ b/src/Mod/Fem/App/PropertyPostDataObject.cpp @@ -502,8 +502,8 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) if (father && father->isDerivedFrom()) { App::DocumentObject* obj = static_cast(father); Base::Console().Error("Dataset file '%s' with data of '%s' seems to be empty\n", - fi.filePath().c_str(), - obj->Label.getValue()); + fi.filePath().c_str(), + obj->Label.getValue()); } else { Base::Console().Warning("Loaded Dataset file '%s' seems to be empty\n", @@ -518,9 +518,10 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) } } else { - Base::Console().Error("Dataset file '%s' is of unsupportet type: %s. Data not loaded.\n", - fi.filePath().c_str(), - extension); + Base::Console().Error( + "Dataset file '%s' is of unsupported type: %s. Data not loaded.\n", + fi.filePath().c_str(), + extension); } } diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index 909ca629e1..fefe174507 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -316,7 +316,7 @@ void TaskDlgPost::appendBox(TaskPostBox* box) void TaskDlgPost::open() { - // only open a new command if non is pending (e.g. if the object was nely created) + // only open a new command if none is pending (e.g. if the object was newly created) if (!Gui::Command::hasPendingCommand()) { auto text = std::string("Edit ") + m_view->getObject()->Label.getValue(); Gui::Command::openCommand(text.c_str());