/*************************************************************************** * Copyright (c) 2009 Jürgen Riegel * * Copyright (c) 2017 Qingfeng Xia * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include "FemAnalysis.h" #include "FemResultObject.h" #include "FemVTKTools.h" namespace Fem { template vtkDataSet* readVTKFile(const char* fileName) { vtkSmartPointer reader = vtkSmartPointer::New(); reader->SetFileName(fileName); reader->Update(); auto output = reader->GetOutput(); if (output) { output->Register(reader); } return vtkDataSet::SafeDownCast(output); } template void writeVTKFile(const char* filename, vtkSmartPointer dataset) { vtkSmartPointer writer = vtkSmartPointer::New(); writer->SetFileName(filename); writer->SetInputData(dataset); writer->Write(); } namespace { // Helper function to fill vtkCellArray from SMDS_Mesh using vtk cell order template void fillVtkArray(vtkSmartPointer& elemArray, std::vector& types, const E* elem) { vtkSmartPointer cell = vtkSmartPointer::New(); const std::vector& order = SMDS_MeshCell::toVtkOrder(elem->GetEntityType()); if (!order.empty()) { for (int i = 0; i < elem->NbNodes(); ++i) { cell->GetPointIds()->SetId(i, elem->GetNode(order[i])->GetID() - 1); } } else { for (int i = 0; i < elem->NbNodes(); ++i) { cell->GetPointIds()->SetId(i, elem->GetNode(i)->GetID() - 1); } } elemArray->InsertNextCell(cell); types.push_back(SMDS_MeshCell::toVtkType(elem->GetEntityType())); } // Helper function to fill SMDS_Mesh elements ID from vtk cell void fillMeshElementIds(vtkCell* cell, std::vector& ids) { VTKCellType cellType = static_cast(cell->GetCellType()); const std::vector& order = SMDS_MeshCell::fromVtkOrder(cellType); vtkIdType* vtkIds = cell->GetPointIds()->GetPointer(0); ids.clear(); int nbPoints = cell->GetNumberOfPoints(); ids.resize(nbPoints); if (!order.empty()) { for (int i = 0; i < nbPoints; ++i) { ids[i] = vtkIds[order[i]] + 1; } } else { for (int i = 0; i < nbPoints; ++i) { ids[i] = vtkIds[i] + 1; } } } } // namespace void FemVTKTools::importVTKMesh(vtkSmartPointer dataset, FemMesh* mesh, float scale) { const vtkIdType nPoints = dataset->GetNumberOfPoints(); const vtkIdType nCells = dataset->GetNumberOfCells(); Base::Console().Log("%d nodes/points and %d cells/elements found!\n", nPoints, nCells); Base::Console().Log("Build SMESH mesh out of the vtk mesh data.\n", nPoints, nCells); // Now fill the SMESH datastructure SMESH_Mesh* smesh = mesh->getSMesh(); SMESHDS_Mesh* meshds = smesh->GetMeshDS(); meshds->ClearMesh(); for (vtkIdType i = 0; i < nPoints; i++) { double* p = dataset->GetPoint(i); meshds->AddNodeWithID(p[0] * scale, p[1] * scale, p[2] * scale, i + 1); } for (vtkIdType iCell = 0; iCell < nCells; iCell++) { vtkCell* cell = dataset->GetCell(iCell); std::vector ids; fillMeshElementIds(cell, ids); switch (cell->GetCellType()) { // 1D edges case VTK_LINE: // seg2 meshds->AddEdgeWithID(ids[0], ids[1], iCell + 1); break; case VTK_QUADRATIC_EDGE: // seg3 meshds->AddEdgeWithID(ids[0], ids[1], ids[2], iCell + 1); break; // 2D faces case VTK_TRIANGLE: // tria3 meshds->AddFaceWithID(ids[0], ids[1], ids[2], iCell + 1); break; case VTK_QUADRATIC_TRIANGLE: // tria6 meshds->AddFaceWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], iCell + 1); break; case VTK_QUAD: // quad4 meshds->AddFaceWithID(ids[0], ids[1], ids[2], ids[3], iCell + 1); break; case VTK_QUADRATIC_QUAD: // quad8 meshds->AddFaceWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], iCell + 1); break; // 3D volumes case VTK_TETRA: // tetra4 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], iCell + 1); break; case VTK_QUADRATIC_TETRA: // tetra10 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], ids[8], ids[9], iCell + 1); break; case VTK_HEXAHEDRON: // hexa8 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], iCell + 1); break; case VTK_QUADRATIC_HEXAHEDRON: // hexa20 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], ids[8], ids[9], ids[10], ids[11], ids[12], ids[13], ids[14], ids[15], ids[16], ids[17], ids[18], ids[19], iCell + 1); break; case VTK_WEDGE: // penta6 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], iCell + 1); break; case VTK_QUADRATIC_WEDGE: // penta15 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], ids[8], ids[9], ids[10], ids[11], ids[12], ids[13], ids[14], iCell + 1); break; case VTK_PYRAMID: // pyra5 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], ids[4], iCell + 1); break; case VTK_QUADRATIC_PYRAMID: // pyra13 meshds->AddVolumeWithID(ids[0], ids[1], ids[2], ids[3], ids[4], ids[5], ids[6], ids[7], ids[8], ids[9], ids[10], ids[11], ids[12], iCell + 1); break; // not handled cases default: { Base::Console().Error( "Only common 1D, 2D and 3D Cells are supported in VTK mesh import\n"); break; } } } } FemMesh* FemVTKTools::readVTKMesh(const char* filename, FemMesh* mesh) { Base::TimeElapsed Start; Base::Console().Log("Start: read FemMesh from VTK unstructuredGrid ======================\n"); Base::FileInfo f(filename); if (f.hasExtension("vtu")) { vtkSmartPointer dataset = readVTKFile(filename); if (!dataset.Get()) { Base::Console().Error("Failed to load file %s\n", filename); return nullptr; } importVTKMesh(dataset, mesh); } else if (f.hasExtension("pvtu")) { vtkSmartPointer dataset = readVTKFile(filename); if (!dataset.Get()) { Base::Console().Error("Failed to load file %s\n", filename); return nullptr; } importVTKMesh(dataset, mesh); } else if (f.hasExtension("vtk")) { vtkSmartPointer dataset = readVTKFile(filename); if (!dataset.Get()) { Base::Console().Error("Failed to load file %s\n", filename); return nullptr; } importVTKMesh(dataset, mesh); } else { Base::Console().Error("file name extension is not supported\n"); return nullptr; } // Mesh should link to the part feature, in order to set up FemConstraint Base::Console().Log(" %f: Done \n", Base::TimeElapsed::diffTimeF(Start, Base::TimeElapsed())); return mesh; } void exportFemMeshEdges(vtkSmartPointer& elemArray, std::vector& types, const SMDS_EdgeIteratorPtr& aEdgeIter) { Base::Console().Log(" Start: VTK mesh builder edges.\n"); while (aEdgeIter->more()) { const SMDS_MeshEdge* aEdge = aEdgeIter->next(); // edge if (aEdge->GetEntityType() == SMDSEntity_Edge) { fillVtkArray(elemArray, types, aEdge); } // quadratic edge else if (aEdge->GetEntityType() == SMDSEntity_Quad_Edge) { fillVtkArray(elemArray, types, aEdge); } else { throw Base::TypeError("Edge not yet supported by FreeCAD's VTK mesh builder\n"); } } Base::Console().Log(" End: VTK mesh builder edges.\n"); } void exportFemMeshFaces(vtkSmartPointer& elemArray, std::vector& types, const SMDS_FaceIteratorPtr& aFaceIter) { Base::Console().Log(" Start: VTK mesh builder faces.\n"); while (aFaceIter->more()) { const SMDS_MeshFace* aFace = aFaceIter->next(); // triangle if (aFace->GetEntityType() == SMDSEntity_Triangle) { fillVtkArray(elemArray, types, aFace); } // quad else if (aFace->GetEntityType() == SMDSEntity_Quadrangle) { fillVtkArray(elemArray, types, aFace); } // quadratic triangle else if (aFace->GetEntityType() == SMDSEntity_Quad_Triangle) { fillVtkArray(elemArray, types, aFace); } // quadratic quad else if (aFace->GetEntityType() == SMDSEntity_Quad_Quadrangle) { fillVtkArray(elemArray, types, aFace); } else { throw Base::TypeError("Face not yet supported by FreeCAD's VTK mesh builder\n"); } } Base::Console().Log(" End: VTK mesh builder faces.\n"); } void exportFemMeshCells(vtkSmartPointer& elemArray, std::vector& types, const SMDS_VolumeIteratorPtr& aVolIter) { Base::Console().Log(" Start: VTK mesh builder volumes.\n"); while (aVolIter->more()) { const SMDS_MeshVolume* aVol = aVolIter->next(); if (aVol->GetEntityType() == SMDSEntity_Tetra) { // tetra4 fillVtkArray(elemArray, types, aVol); } else if (aVol->GetEntityType() == SMDSEntity_Pyramid) { // pyra5 fillVtkArray(elemArray, types, aVol); } else if (aVol->GetEntityType() == SMDSEntity_Penta) { // penta6 fillVtkArray(elemArray, types, aVol); } else if (aVol->GetEntityType() == SMDSEntity_Hexa) { // hexa8 fillVtkArray(elemArray, types, aVol); } else if (aVol->GetEntityType() == SMDSEntity_Quad_Tetra) { // tetra10 fillVtkArray(elemArray, types, aVol); } else if (aVol->GetEntityType() == SMDSEntity_Quad_Pyramid) { // pyra13 fillVtkArray(elemArray, types, aVol); } else if (aVol->GetEntityType() == SMDSEntity_Quad_Penta) { // penta15 fillVtkArray(elemArray, types, aVol); } else if (aVol->GetEntityType() == SMDSEntity_Quad_Hexa) { // hexa20 fillVtkArray(elemArray, types, aVol); } else { throw Base::TypeError("Volume not yet supported by FreeCAD's VTK mesh builder\n"); } } Base::Console().Log(" End: VTK mesh builder volumes.\n"); } void FemVTKTools::exportVTKMesh(const FemMesh* mesh, vtkSmartPointer grid, bool highest, float scale) { Base::Console().Log("Start: VTK mesh builder ======================\n"); const SMESH_Mesh* smesh = mesh->getSMesh(); const SMESHDS_Mesh* meshDS = smesh->GetMeshDS(); // nodes Base::Console().Log(" Start: VTK mesh builder nodes.\n"); vtkSmartPointer points = vtkSmartPointer::New(); SMDS_NodeIteratorPtr aNodeIter = meshDS->nodesIterator(); while (aNodeIter->more()) { const SMDS_MeshNode* node = aNodeIter->next(); // why float, not double? double coords[3] = {double(node->X() * scale), double(node->Y() * scale), double(node->Z() * scale)}; points->InsertPoint(node->GetID() - 1, coords); // memory is allocated by VTK points size for max node id, not for point count // if the SMESH mesh has gaps in node numbering, points without any element // assignment will be inserted in these point gaps too // this needs to be taken into account on node mapping when FreeCAD FEM results // are exported to vtk } grid->SetPoints(points); // nodes debugging const SMDS_MeshInfo& info = meshDS->GetMeshInfo(); Base::Console().Log(" Size of nodes in SMESH grid: %i.\n", info.NbNodes()); const vtkIdType nNodes = grid->GetNumberOfPoints(); Base::Console().Log(" Size of nodes in VTK grid: %i.\n", nNodes); Base::Console().Log(" End: VTK mesh builder nodes.\n"); vtkSmartPointer elemArray = vtkSmartPointer::New(); std::vector types; if (highest) { // try volumes SMDS_VolumeIteratorPtr aVolIter = meshDS->volumesIterator(); exportFemMeshCells(elemArray, types, aVolIter); // try faces if (elemArray->GetNumberOfCells() == 0) { SMDS_FaceIteratorPtr aFaceIter = meshDS->facesIterator(); exportFemMeshFaces(elemArray, types, aFaceIter); } // try edges if (elemArray->GetNumberOfCells() == 0) { SMDS_EdgeIteratorPtr aEdgeIter = meshDS->edgesIterator(); exportFemMeshEdges(elemArray, types, aEdgeIter); } } else { // export all elements // edges SMDS_EdgeIteratorPtr aEdgeIter = meshDS->edgesIterator(); exportFemMeshEdges(elemArray, types, aEdgeIter); // faces SMDS_FaceIteratorPtr aFaceIter = meshDS->facesIterator(); exportFemMeshFaces(elemArray, types, aFaceIter); // volumes SMDS_VolumeIteratorPtr aVolIter = meshDS->volumesIterator(); exportFemMeshCells(elemArray, types, aVolIter); } if (elemArray->GetNumberOfCells() > 0) { grid->SetCells(types.data(), elemArray); } Base::Console().Log("End: VTK mesh builder ======================\n"); } void FemVTKTools::writeVTKMesh(const char* filename, const FemMesh* mesh, bool highest) { Base::TimeElapsed Start; Base::Console().Log("Start: write FemMesh from VTK unstructuredGrid ======================\n"); Base::FileInfo f(filename); vtkSmartPointer grid = vtkSmartPointer::New(); exportVTKMesh(mesh, grid, highest); Base::Console().Log("Start: writing mesh data ======================\n"); if (f.hasExtension("vtu")) { writeVTKFile(filename, grid); } else if (f.hasExtension("vtk")) { writeVTKFile(filename, grid); } else { Base::Console().Error("file name extension is not supported to write VTK\n"); } Base::Console().Log(" %f: Done \n", Base::TimeElapsed::diffTimeF(Start, Base::TimeElapsed())); } App::DocumentObject* getObjectByType(const Base::Type type) { App::Document* pcDoc = App::GetApplication().getActiveDocument(); if (!pcDoc) { Base::Console().Message("No active document is found thus created\n"); pcDoc = App::GetApplication().newDocument(); } App::DocumentObject* obj = pcDoc->getActiveObject(); if (obj->getTypeId() == type) { return obj; } if (obj->is()) { std::vector fem = (static_cast(obj))->Group.getValues(); for (const auto& it : fem) { if (it->isDerivedFrom(type)) { return static_cast(it); // return the first of that type } } } return nullptr; } App::DocumentObject* createObjectByType(const Base::Type type) { App::Document* pcDoc = App::GetApplication().getActiveDocument(); if (!pcDoc) { Base::Console().Message("No active document is found thus created\n"); pcDoc = App::GetApplication().newDocument(); } App::DocumentObject* obj = pcDoc->getActiveObject(); if (obj->is()) { App::DocumentObject* newobj = pcDoc->addObject(type.getName()); static_cast(obj)->addObject(newobj); return newobj; } else { return pcDoc->addObject(type.getName()); // create in the active document } } App::DocumentObject* FemVTKTools::readResult(const char* filename, App::DocumentObject* res) { Base::TimeElapsed Start; Base::Console().Log( "Start: read FemResult with FemMesh from VTK file ======================\n"); Base::FileInfo f(filename); vtkSmartPointer ds; if (f.hasExtension("vtu")) { ds = readVTKFile(filename); } else if (f.hasExtension("vtk")) { ds = readVTKFile(filename); } else { Base::Console().Error("file name extension is not supported\n"); } App::Document* pcDoc = App::GetApplication().getActiveDocument(); if (!pcDoc) { Base::Console().Message("No active document is found thus created\n"); pcDoc = App::GetApplication().newDocument(); } App::DocumentObject* obj = pcDoc->getActiveObject(); vtkSmartPointer dataset = ds; App::DocumentObject* result = nullptr; if (res) { Base::Console().Message( "FemResultObject pointer is NULL, trying to get the active object\n"); if (obj->getTypeId() == Base::Type::fromName("Fem::FemResultObjectPython")) { result = obj; } else { Base::Console().Message("the active object is not the correct type, do nothing\n"); return nullptr; } } auto* mesh = pcDoc->addObject("ResultMesh"); std::unique_ptr fmesh(new FemMesh()); importVTKMesh(dataset, fmesh.get()); static_cast(mesh->getPropertyByName("FemMesh"))->setValuePtr(fmesh.release()); if (result) { // PropertyLink is the property type to store DocumentObject pointer App::PropertyLink* link = dynamic_cast(result->getPropertyByName("Mesh")); if (link) { link->setValue(mesh); } // vtkSmartPointer pd = dataset->GetPointData(); importFreeCADResult(dataset, result); } pcDoc->recompute(); Base::Console().Log(" %f: Done \n", Base::TimeElapsed::diffTimeF(Start, Base::TimeElapsed())); Base::Console().Log("End: read FemResult with FemMesh from VTK file ======================\n"); return result; } void FemVTKTools::writeResult(const char* filename, const App::DocumentObject* res) { if (!res) { App::Document* pcDoc = App::GetApplication().getActiveDocument(); if (!pcDoc) { Base::Console().Message("No active document is found thus do nothing and return\n"); return; } res = pcDoc->getActiveObject(); // type checking is done by caller } if (!res) { Base::Console().Error("Result object pointer is invalid and it is not active object"); return; } Base::TimeElapsed Start; Base::Console().Log("Start: write FemResult to VTK unstructuredGrid dataset =======\n"); Base::FileInfo f(filename); // mesh vtkSmartPointer grid = vtkSmartPointer::New(); App::DocumentObject* mesh = static_cast(res->getPropertyByName("Mesh"))->getValue(); const FemMesh& fmesh = static_cast(mesh->getPropertyByName("FemMesh"))->getValue(); FemVTKTools::exportVTKMesh(&fmesh, grid); Base::Console().Log(" %f: vtk mesh builder finished\n", Base::TimeElapsed::diffTimeF(Start, Base::TimeElapsed())); // result FemVTKTools::exportFreeCADResult(res, grid); if (f.hasExtension("vtu")) { writeVTKFile(filename, grid); } else if (f.hasExtension("vtk")) { writeVTKFile(filename, grid); } else { Base::Console().Error("file name extension is not supported to write VTK\n"); } Base::Console().Log(" %f: writing result object to vtk finished\n", Base::TimeElapsed::diffTimeF(Start, Base::TimeElapsed())); Base::Console().Log("End: write FemResult to VTK unstructuredGrid dataset =======\n"); } std::map _getFreeCADMechResultVectorProperties() { // see src/Mod/Fem/femobjects/_FemResultMechanical // App::PropertyVectorList will be a list of vectors in vtk std::map resFCVecProp; resFCVecProp["DisplacementVectors"] = "Displacement"; // the following three are filled only if there is a reinforced mat object // https://forum.freecad.org/viewtopic.php?f=18&t=33106&start=70#p296317 // https://forum.freecad.org/viewtopic.php?f=18&t=33106&p=416006#p412800 resFCVecProp["PS1Vector"] = "Major Principal Stress Vector"; resFCVecProp["PS2Vector"] = "Intermediate Principal Stress Vector"; resFCVecProp["PS3Vector"] = "Minor Principal Stress Vector"; resFCVecProp["HeatFlux"] = "Heat Flux"; return resFCVecProp; } // see https://forum.freecad.org/viewtopic.php?f=18&t=33106&start=30#p277434 for further // information regarding names etc... // some scalar list are not needed on VTK file export but they are needed for internal VTK pipeline // TODO some filter to only export the needed values to VTK file but have all // in FreeCAD VTK pipeline std::map _getFreeCADMechResultScalarProperties() { // see src/Mod/Fem/femobjects/result_mechanical.py // App::PropertyFloatList will be a list of scalars in vtk std::map resFCScalProp; resFCScalProp["DisplacementLengths"] = "Displacement Magnitude"; // can be plotted in Paraview as THE DISPLACEMENT MAGNITUDE resFCScalProp["MaxShear"] = "Tresca Stress"; resFCScalProp["NodeStressXX"] = "Stress xx component"; resFCScalProp["NodeStressYY"] = "Stress yy component"; resFCScalProp["NodeStressZZ"] = "Stress zz component"; resFCScalProp["NodeStressXY"] = "Stress xy component"; resFCScalProp["NodeStressXZ"] = "Stress xz component"; resFCScalProp["NodeStressYZ"] = "Stress yz component"; resFCScalProp["NodeStrainXX"] = "Strain xx component"; resFCScalProp["NodeStrainYY"] = "Strain yy component"; resFCScalProp["NodeStrainZZ"] = "Strain zz component"; resFCScalProp["NodeStrainXY"] = "Strain xy component"; resFCScalProp["NodeStrainXZ"] = "Strain xz component"; resFCScalProp["NodeStrainYZ"] = "Strain yz component"; resFCScalProp["Peeq"] = "Equivalent Plastic Strain"; resFCScalProp["CriticalStrainRatio"] = "Critical Strain Ratio"; // the following three are filled in all cases // https://forum.freecad.org/viewtopic.php?f=18&t=33106&start=70#p296317 // it might be these can be generated in paraview from stress tensor values as // THE MAJOR PRINCIPAL STRESS MAGNITUDE, THE INTERMEDIATE PRINCIPAL STRESS MAGNITUDE, // THE MINOR PRINCIPAL STRESS MAGNITUDE // but I do not know how (Bernd), for some help see paraview tutorial on FreeCAD wiki // thus TODO they might not be exported to external file format (first I need to know // how to generate them in paraview) // but there are needed anyway because the pipeline in FreeCAD needs the principal stress values // https://forum.freecad.org/viewtopic.php?f=18&t=33106&p=416006#p412800 resFCScalProp["PrincipalMax"] = "Major Principal Stress"; // can be plotted in Paraview as THE // MAJOR PRINCIPAL STRESS MAGNITUDE resFCScalProp["PrincipalMed"] = "Intermediate Principal Stress"; // can be plotted in Paraview as THE INTERMEDIATE // PRINCIPAL STRESS MAGNITUDE resFCScalProp["PrincipalMin"] = "Minor Principal Stress"; // can be plotted in Paraview as THE // MINOR PRINCIPAL STRESS MAGNITUDE resFCScalProp["vonMises"] = "von Mises Stress"; resFCScalProp["Temperature"] = "Temperature"; resFCScalProp["MohrCoulomb"] = "MohrCoulomb"; resFCScalProp["ReinforcementRatio_x"] = "ReinforcementRatio_x"; resFCScalProp["ReinforcementRatio_y"] = "ReinforcementRatio_y"; resFCScalProp["ReinforcementRatio_z"] = "ReinforcementRatio_z"; resFCScalProp["UserDefined"] = "UserDefinedMyName"; // this is empty or am I wrong ?! resFCScalProp["MassFlowRate"] = "Mass Flow Rate"; resFCScalProp["NetworkPressure"] = "Network Pressure"; return resFCScalProp; } void FemVTKTools::importFreeCADResult(vtkSmartPointer dataset, App::DocumentObject* result) { Base::Console().Log("Start: import vtk result file data into a FreeCAD result object.\n"); std::map vectors = _getFreeCADMechResultVectorProperties(); std::map scalars = _getFreeCADMechResultScalarProperties(); double ts = 0.0; // t=0.0 for static simulation static_cast(result->getPropertyByName("Time"))->setValue(ts); vtkSmartPointer pd = dataset->GetPointData(); if (pd->GetNumberOfArrays() == 0) { Base::Console().Error("No point data array is found in vtk data set, do nothing\n"); // if pointData is empty, data may be in cellDate, // cellData -> pointData interpolation is possible in VTK return; } // NodeNumbers const vtkIdType nPoints = dataset->GetNumberOfPoints(); std::vector nodeIds(nPoints); for (vtkIdType i = 0; i < nPoints; ++i) { nodeIds[i] = i + 1; } static_cast(result->getPropertyByName("NodeNumbers")) ->setValues(nodeIds); Base::Console().Log(" NodeNumbers have been filled with values.\n"); // vectors for (const auto& it : vectors) { int dim = 3; // Fixme: currently 3D only, here we could run into trouble, // FreeCAD only supports dim 3D, I do not know about VTK vtkDataArray* vector_field = vtkDataArray::SafeDownCast(pd->GetArray(it.second.c_str())); if (vector_field && vector_field->GetNumberOfComponents() == dim) { App::PropertyVectorList* vector_list = static_cast(result->getPropertyByName(it.first.c_str())); if (vector_list) { std::vector vec(nPoints); for (vtkIdType i = 0; i < nPoints; ++i) { double* p = vector_field->GetTuple( i); // both vtkFloatArray and vtkDoubleArray return double* for GetTuple(i) vec[i] = (Base::Vector3d(p[0], p[1], p[2])); } // PropertyVectorList will not show up in PropertyEditor vector_list->setValues(vec); Base::Console().Log(" A PropertyVectorList has been filled with values: %s\n", it.first.c_str()); } else { Base::Console().Error("static_cast((result->" "getPropertyByName(\"%s\")) failed.\n", it.first.c_str()); continue; } } else { Base::Console().Message(" PropertyVectorList NOT found in vkt file data: %s\n", it.first.c_str()); } } // scalars for (const auto& scalar : scalars) { vtkDataArray* vec = vtkDataArray::SafeDownCast(pd->GetArray(scalar.second.c_str())); if (nPoints && vec && vec->GetNumberOfComponents() == 1) { App::PropertyFloatList* field = static_cast( result->getPropertyByName(scalar.first.c_str())); if (!field) { Base::Console().Error("static_cast((result->" "getPropertyByName(\"%s\")) failed.\n", scalar.first.c_str()); continue; } double vmin = 1.0e100, vmax = -1.0e100; std::vector values(nPoints, 0.0); for (vtkIdType i = 0; i < vec->GetNumberOfTuples(); i++) { double v = *(vec->GetTuple(i)); values[i] = v; if (v > vmax) { vmax = v; } if (v < vmin) { vmin = v; } } field->setValues(values); Base::Console().Log(" A PropertyFloatList has been filled with vales: %s\n", scalar.first.c_str()); } else { Base::Console().Message(" PropertyFloatList NOT found in vkt file data %s\n", scalar.first.c_str()); } } // stats // stats are added by importVTKResults Base::Console().Log("End: import vtk result file data into a FreeCAD result object.\n"); } void FemVTKTools::exportFreeCADResult(const App::DocumentObject* result, vtkSmartPointer grid) { Base::Console().Log("Start: Create VTK result data from FreeCAD result data.\n"); std::map vectors = _getFreeCADMechResultVectorProperties(); std::map scalars = _getFreeCADMechResultScalarProperties(); const Fem::FemResultObject* res = static_cast(result); const vtkIdType nPoints = grid->GetNumberOfPoints(); // we need the corresponding mesh to get the correct id for the result data // (when the freecad smesh mesh has gaps in the points // vtk has more points. Vtk does not support point gaps, thus the gaps are // filled with points. Then the mapping must be correct) App::DocumentObject* meshObj = res->Mesh.getValue(); if (!meshObj || !meshObj->isDerivedFrom()) { Base::Console().Error("Result object does not correctly link to mesh"); return; } const SMESH_Mesh* smesh = static_cast(meshObj)->FemMesh.getValue().getSMesh(); const SMESHDS_Mesh* meshDS = smesh->GetMeshDS(); // all result object meshes are in mm therefore for e.g. length outputs like // displacement we must divide by 1000 double factor = 1.0; // vectors for (const auto& it : vectors) { const int dim = 3; // Fixme, detect dim, but FreeCAD PropertyVectorList ATM only has DIM of 3 App::PropertyVectorList* field = nullptr; if (res->getPropertyByName(it.first.c_str())) { field = static_cast(res->getPropertyByName(it.first.c_str())); } else { Base::Console().Error(" PropertyVectorList not found: %s\n", it.first.c_str()); } if (field && field->getSize() > 0) { const std::vector& vel = field->getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfComponents(dim); data->SetNumberOfTuples(nPoints); data->SetName(it.second.c_str()); // we need to set values for the unused points. // TODO: ensure that the result bar does not include the used 0 if it is not // part of the result (e.g. does the result bar show 0 as smallest value?) if (nPoints != field->getSize()) { double tuple[] = {0, 0, 0}; for (vtkIdType i = 0; i < nPoints; ++i) { data->SetTuple(i, tuple); } } if (it.first.compare("DisplacementVectors") == 0) { factor = 0.001; // to get meter } else { factor = 1.0; } SMDS_NodeIteratorPtr aNodeIter = meshDS->nodesIterator(); for (const auto& jt : vel) { const SMDS_MeshNode* node = aNodeIter->next(); double tuple[] = {jt.x * factor, jt.y * factor, jt.z * factor}; data->SetTuple(node->GetID() - 1, tuple); } grid->GetPointData()->AddArray(data); Base::Console().Log( " The PropertyVectorList %s was exported to VTK vector list: %s\n", it.first.c_str(), it.second.c_str()); } else if (field) { Base::Console().Log(" PropertyVectorList NOT exported to vtk: %s size is: %i\n", it.first.c_str(), field->getSize()); } } // scalars for (const auto& scalar : scalars) { App::PropertyFloatList* field = nullptr; if (res->getPropertyByName(scalar.first.c_str())) { field = static_cast(res->getPropertyByName(scalar.first.c_str())); } else { Base::Console().Error("PropertyFloatList %s not found \n", scalar.first.c_str()); } if (field && field->getSize() > 0) { const std::vector& vec = field->getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(nPoints); data->SetName(scalar.second.c_str()); // we need to set values for the unused points. // TODO: ensure that the result bar does not include the used 0 if it is not part // of the result (e.g. does the result bar show 0 as smallest value?) if (nPoints != field->getSize()) { for (vtkIdType i = 0; i < nPoints; ++i) { data->SetValue(i, 0); } } if ((scalar.first.compare("MaxShear") == 0) || (scalar.first.compare("NodeStressXX") == 0) || (scalar.first.compare("NodeStressXY") == 0) || (scalar.first.compare("NodeStressXZ") == 0) || (scalar.first.compare("NodeStressYY") == 0) || (scalar.first.compare("NodeStressYZ") == 0) || (scalar.first.compare("NodeStressZZ") == 0) || (scalar.first.compare("PrincipalMax") == 0) || (scalar.first.compare("PrincipalMed") == 0) || (scalar.first.compare("PrincipalMin") == 0) || (scalar.first.compare("vonMises") == 0) || (scalar.first.compare("NetworkPressure") == 0)) { factor = 1e6; // to get Pascal } else if (scalar.first.compare("DisplacementLengths") == 0) { factor = 0.001; // to get meter } else { factor = 1.0; } SMDS_NodeIteratorPtr aNodeIter = meshDS->nodesIterator(); for (double i : vec) { const SMDS_MeshNode* node = aNodeIter->next(); // for the MassFlowRate the last vec entries can be a nullptr, thus check this if (node) { data->SetValue(node->GetID() - 1, i * factor); } } grid->GetPointData()->AddArray(data); Base::Console().Log( " The PropertyFloatList %s was exported to VTK scalar list: %s\n", scalar.first.c_str(), scalar.second.c_str()); } else if (field) { Base::Console().Log(" PropertyFloatList NOT exported to vtk: %s size is: %i\n", scalar.first.c_str(), field->getSize()); } } Base::Console().Log("End: Create VTK result data from FreeCAD result data.\n"); } namespace FRDReader { enum class ElementType { Edge = 11, QuadEdge = 12, Triangle = 7, QuadTriangle = 8, Quadrangle = 9, QuadQuadrangle = 10, Tetra = 3, QuadTetra = 6, Hexa = 1, QuadHexa = 4, Penta = 2, QuadPenta = 5 }; enum class AnalysisType { Static = 0, TimeStep = 1, Frequency = 2, LoadStep = 3, UserNamed = 4 }; std::map mapAnalysisTypeToStr = {{AnalysisType::Static, "Static"}, {AnalysisType::TimeStep, "TimeStep"}, {AnalysisType::Frequency, "Frequency"}, {AnalysisType::LoadStep, "LoadStep"}, {AnalysisType::UserNamed, "User"}}; // value format indicator enum class Indicator { Short = 0, Long = 1, // BinaryFloat = 2, not used // BinaryDouble = 3 not used }; // number of nodes per CalculiX element type: {type, nodes} std::map mapCcxTypeNodes = { {ElementType::Edge, 2}, {ElementType::QuadEdge, 3}, {ElementType::Triangle, 3}, {ElementType::QuadTriangle, 6}, {ElementType::Quadrangle, 4}, {ElementType::QuadQuadrangle, 8}, {ElementType::Tetra, 4}, {ElementType::QuadTetra, 10}, {ElementType::Hexa, 8}, {ElementType::QuadHexa, 20}, {ElementType::Penta, 6}, {ElementType::QuadPenta, 15}, }; // map CalculiX nodes order to Vtk order std::map> mapCcxToVtk = { {VTK_LINE, {0, 1}}, {VTK_QUADRATIC_EDGE, {0, 1, 2}}, {VTK_TRIANGLE, {0, 1, 2}}, {VTK_QUADRATIC_TRIANGLE, {0, 1, 2, 3, 4, 5}}, {VTK_QUAD, {0, 1, 2, 3}}, {VTK_QUADRATIC_QUAD, {0, 1, 2, 3, 4, 5, 6, 7}}, {VTK_TETRA, {0, 1, 2, 3}}, {VTK_QUADRATIC_TETRA, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}, {VTK_HEXAHEDRON, {0, 1, 2, 3, 4, 5, 6, 7}}, {VTK_QUADRATIC_HEXAHEDRON, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16, 17, 18, 19, 12, 13, 14, 15}}, {VTK_WEDGE, {0, 1, 2, 3, 4, 5}}, {VTK_QUADRATIC_WEDGE, {0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 9, 10, 11}}}; // give position of first non-blank character of string_view size_t getFirstNotBlankPos(const std::string_view& view) { size_t pos = view.find_first_not_of(" "); if (pos == std::string_view::npos) { pos = 0; } return pos; } // get n-digits value from string_view // not used until libc++ std::from_chars supports double values // template // void valueFromLine(const std::string_view::const_iterator& it, int digits, T& value) //{ // std::string_view sub(it, digits); // auto pos = getFirstNotBlankPos(sub); // std::from_chars(sub.data() + pos, sub.data() + digits, value, 10); //} template void valueFromLine(const std::string_view::iterator& it, int digits, T& value) { std::string_view sub(&*it, digits); value = std::strtol(sub.data(), nullptr, 10); } template<> void valueFromLine(const std::string_view::iterator& it, int digits, double& value) { std::string_view sub(&*it, digits); value = std::strtof(sub.data(), nullptr); } // add cell from sorted nodes template void addCell(vtkSmartPointer& cellArray, const std::vector& topoElem) { vtkSmartPointer cell = vtkSmartPointer::New(); cell->GetPointIds()->SetNumberOfIds(topoElem.size()); int type = cell->GetCellType(); for (size_t i = 0; i < topoElem.size(); ++i) { cell->GetPointIds()->SetId(i, topoElem[mapCcxToVtk[type][i]]); } cellArray->InsertNextCell(cell); } // fill cell array void fillCell(vtkSmartPointer& cellArray, std::vector& topoElem, std::vector& vtkType, ElementType elemType) { switch (elemType) { case ElementType::Hexa: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_HEXAHEDRON); break; case ElementType::Penta: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_WEDGE); break; case ElementType::Tetra: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_TETRA); break; case ElementType::QuadHexa: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_QUADRATIC_HEXAHEDRON); break; case ElementType::QuadPenta: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_QUADRATIC_WEDGE); break; case ElementType::QuadTetra: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_QUADRATIC_TETRA); break; case ElementType::Triangle: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_TRIANGLE); break; case ElementType::QuadTriangle: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_QUADRATIC_TRIANGLE); break; case ElementType::Quadrangle: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_QUAD); break; case ElementType::QuadQuadrangle: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_QUADRATIC_QUAD); break; case ElementType::Edge: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_LINE); break; case ElementType::QuadEdge: addCell(cellArray, topoElem); vtkType.emplace_back(VTK_QUADRATIC_EDGE); break; } } struct FRDResultInfo { double value; long numNodes; AnalysisType analysisType; int step; Indicator indicator; bool operator==(const FRDResultInfo& other) const { return (this->step == other.step) && (this->analysisType == other.analysisType); } bool operator<(const FRDResultInfo& other) const { if (this->step < other.step) { return true; } else if (this->step > other.step) { return false; } else if (static_cast(this->analysisType) < static_cast(other.analysisType)) { return true; } else { return false; } } }; // get number of digits from format indicator int getDigits(Indicator indicator) { int digits = 0; switch (indicator) { case Indicator::Short: digits = 5; break; case Indicator::Long: digits = 10; break; } return digits; } // get position of scalar entities in line result vector std::vector identifyScalarEntities(const std::vector> entities) { std::vector pos; for (auto it = entities.begin(); it != entities.end(); ++it) { // check type == 1 or component < 1 if ((*it)[0] == 1 || (*it)[1] < 1) { pos.emplace_back(it - entities.begin()); } } return pos; } // read nodes and fill vtkPoints object std::map readNodes(std::ifstream& ifstr, const std::string& lines, vtkSmartPointer& points) { std::string keyCode = " 2C"; std::string keyCodeCoord = " -1"; long numNodes; int indicator; int node; long nodeID = 0; // frd file might have nodes that are not numbered starting from zero. // Use the map to identify them std::map mapNodes; std::string_view view {lines}; std::string_view sub = view.substr(keyCode.length() + 18); valueFromLine(sub.begin(), 12, numNodes); sub = sub.substr(12 + 37); valueFromLine(sub.begin(), 1, indicator); int digits = getDigits(static_cast(indicator)); points->SetNumberOfPoints(numNodes); std::string line; while (nodeID < numNodes && std::getline(ifstr, line)) { std::vector coords; std::string_view view {line}; if (view.rfind(keyCodeCoord, 0) == 0) { std::string_view v(line.data() + keyCodeCoord.length(), digits); valueFromLine(v.begin(), digits, node); std::string_view vi = view.substr(keyCodeCoord.length() + digits); double value; for (auto it = vi.begin(); it != vi.end(); it += 12) { valueFromLine(it, 12, value); coords.emplace_back(value); } } points->SetPoint(nodeID, coords.data()); mapNodes[node] = nodeID++; } return mapNodes; } // fill elements and fill cell array std::vector readElements(std::ifstream& ifstr, const std::string& lines, const std::map& mapNodes, vtkSmartPointer& cellArray) { std::string line; std::string keyCode = " 3C"; std::string keyCodeType = " -1"; std::string keyCodeNodes = " -2"; long numElem; int indicator; int elem; long elemID = 0; // element info: {type, group, material} std::vector info(3); std::map mapElem; std::vector topoElem; std::vector vtkType; std::string_view view {lines}; std::string_view sub = view.substr(keyCode.length() + 18); valueFromLine(sub.begin(), 12, numElem); sub = sub.substr(12 + 37); valueFromLine(sub.begin(), 1, indicator); int digits = getDigits(static_cast(indicator)); while (elemID < numElem && std::getline(ifstr, line)) { std::string_view view {line}; if (view.rfind(keyCodeType, 0) == 0) { std::string_view v(line.data() + keyCodeType.length()); valueFromLine(v.begin(), digits, elem); v = v.substr(digits); std::string_view::iterator it1; std::vector::iterator it2; for (it1 = v.begin(), it2 = info.begin(); it1 != v.end() && it2 != info.end(); it1 += 5, ++it2) { valueFromLine(it1, 5, *it2); } } if (view.rfind(keyCodeNodes, 0) == 0) { std::string_view vi = view.substr(keyCodeNodes.length()); int node; for (auto it = vi.begin(); it != vi.end(); it += digits) { valueFromLine(it, digits, node); topoElem.emplace_back(mapNodes.at(node)); } // add cell to cellArray if (topoElem.size() == mapCcxTypeNodes[static_cast(info[0])]) { fillCell(cellArray, topoElem, vtkType, static_cast(info[0])); topoElem.clear(); mapElem[elem] = elemID++; } } } return vtkType; } // read parameter header (not used) void readParameter(std::ifstream& ifstr, const std::string& line) { // do nothing (void)ifstr; (void)line; } // read first header from nodal result block void readResultInfo(std::ifstream& ifstr, const std::string& lines, FRDResultInfo& info) { (void)ifstr; std::string keyCode = " 100C"; std::string_view view {lines}; std::string_view sub = view.substr(keyCode.length() + 6); valueFromLine(sub.begin(), 12, info.value); sub = sub.substr(12); valueFromLine(sub.begin(), 12, info.numNodes); sub = sub.substr(12 + 20); int anType; valueFromLine(sub.begin(), 2, anType); info.analysisType = static_cast(anType); sub = sub.substr(2); valueFromLine(sub.begin(), 5, info.step); sub = sub.substr(5 + 10); int ind; valueFromLine(sub.begin(), 2, ind); info.indicator = static_cast(ind); } // read result from nodal result block and add result array to grid void readResults(std::ifstream& ifstr, const std::string& lines, const std::map& mapNodes, const FRDResultInfo& info, vtkSmartPointer& grid) { int digits = getDigits(info.indicator); (void)lines; // get dataset info, start with " -4" std::string line; std::string keyDataSet = " -4"; unsigned int numComps; std::getline(ifstr, line); std::string_view view = line; std::string_view sub = view.substr(keyDataSet.length() + 2); std::string dataSetName {sub.substr(0, 8)}; // remove trailing spaces dataSetName.erase(dataSetName.find_last_not_of(" ") + 1); sub = sub.substr(8); valueFromLine(sub.begin(), 5, numComps); // get entity info std::string keyEntity = " -5"; std::vector entityNames; // type: 1: scalar; 2: vector; 4: matrix; 12: vector (3 amp - 3 phase); 14: tensor (6 amp - 6 // phase) {type, row, col, exist} std::vector> entityTypes; unsigned int countComp = 0; while (countComp < numComps && std::getline(ifstr, line)) { std::string_view view {line}; if (view.rfind(keyEntity, 0) == 0) { sub = view.substr(keyEntity.length() + 2); std::string en {sub.substr(0, 8)}; // remove trailing spaces en.erase(en.find_last_not_of(" ") + 1); std::vector et = {0, 0, 0, 0}; // fill entityType, ignore MENU: " 1" sub = sub.substr(8 + 5, 4 * 5); std::string_view::iterator it1; std::vector::iterator it2; for (it1 = sub.begin(), it2 = et.begin(); it1 != sub.end() && it2 != et.end(); (it1 += 5), ++it2) { valueFromLine(it1, digits, *it2); } if (et[3] == 0) { // ignore predefined entity entityNames.emplace_back(en); entityTypes.emplace_back(et); } ++countComp; } } // used components numComps = entityNames.size(); // enter in node values block std::string code1 = " -1"; std::string code2 = " -2"; int node {-1}; double value {0.0}; std::vector vecValues; std::vector scaValues; std::vector nodes; int countNodes = 0; size_t countScaPos {0}; // result block could have both vector/matrix and scalar components // save each scalars entity in his own array auto scalarPos = identifyScalarEntities(entityTypes); // array for vector entities (if needed) vtkSmartPointer vecArray = vtkSmartPointer::New(); // arrays for scalar entities (if needed) std::vector> scaArrays; for (size_t i = 0; i < scalarPos.size(); ++i) { scaArrays.emplace_back(vtkSmartPointer::New()); } vecArray->SetNumberOfComponents(numComps - scalarPos.size()); vecArray->SetNumberOfTuples(mapNodes.size()); vecArray->SetName(dataSetName.c_str()); // set all values to zero for (int i = 0; i < vecArray->GetNumberOfComponents(); ++i) { vecArray->FillComponent(i, 0.0); } // vecArray->Fill(0.0); for (size_t i = 0; i < scaArrays.size(); ++i) { scaArrays[i]->SetNumberOfComponents(1); scaArrays[i]->SetNumberOfTuples(mapNodes.size()); std::string name = entityNames[scalarPos[i]]; scaArrays[i]->SetName(name.c_str()); // scaArrays[i]->Fill(0.0); for (int j = 0; j < scaArrays[i]->GetNumberOfComponents(); ++j) { scaArrays[i]->FillComponent(j, 0.0); } } while (countNodes < info.numNodes && std::getline(ifstr, line)) { std::string_view view {line}; if (view.rfind(code1, 0) == 0) { sub = view.substr(code1.length()); valueFromLine(sub.begin(), digits, node); // clear values vector for each node result block vecValues.clear(); scaValues.clear(); countScaPos = 0; try { // result nodes could not exist in .frd file due to element expansion // so mapNodes.at() could throw an exception nodes.emplace_back(mapNodes.at(node)); sub = sub.substr(digits); for (auto it = sub.begin(); it != sub.end(); it += 12, ++countScaPos) { valueFromLine(it, 12, value); // search if value is scalar or vector/matrix component auto pos = std::ranges::find(scalarPos, countScaPos); if (pos == scalarPos.end()) { vecValues.emplace_back(value); } else { scaValues.emplace_back(value); } } } catch (const std::out_of_range&) { Base::Console().Warning("Invalid node: %d\n", node); } ++countNodes; } else if (view.rfind(code2, 0) == 0) { sub = view.substr(code2.length() + digits); for (auto it = sub.begin(); it != sub.end(); it += 12) { valueFromLine(it, 12, value); // search if value is scalar or vector/matrix component auto pos = std::ranges::find(scalarPos, countScaPos); if (pos == scalarPos.end()) { vecValues.emplace_back(value); } else { scaValues.emplace_back(value); } } } if ((vecValues.size() + scaValues.size()) == numComps) { if (!vecValues.empty()) { if (node == -1) { throw Base::FileException("File to load not readable"); } vecArray->SetTuple(mapNodes.at(node), vecValues.data()); } if (!scaValues.empty()) { if (node == -1) { throw Base::FileException("File to load not readable"); } std::vector>::iterator it1; std::vector::iterator it2; for (it1 = scaArrays.begin(), it2 = scaValues.begin(); it1 != scaArrays.end() && it2 != scaValues.end(); ++it1, ++it2) { (*it1)->SetTuple1(mapNodes.at(node), *it2); } } } } // add vecArray only if not all scalars if (numComps != scalarPos.size()) { grid->GetPointData()->AddArray(vecArray); } for (auto& s : scaArrays) { grid->GetPointData()->AddArray(s); } } vtkSmartPointer createTimeInfo(const std::string& type) { auto timeInfo = vtkSmartPointer::New(); timeInfo->SetName("TimeInfo"); timeInfo->InsertNextValue(type); // set unit to empty string timeInfo->InsertNextValue(""); return timeInfo; } vtkSmartPointer createTimeValue(const double& value) { auto stepValue = vtkSmartPointer::New(); stepValue->SetName("TimeValue"); stepValue->InsertNextValue(value); return stepValue; } vtkSmartPointer readFRD(std::ifstream& ifstr) { auto points = vtkSmartPointer::New(); auto cells = vtkSmartPointer::New(); auto multiBlock = vtkSmartPointer::New(); vtkSmartPointer grid; vtkSmartPointer block; std::map> grids; std::map> blocks; std::string line; std::map mapNodes; std::vector cellTypes; while (std::getline(ifstr, line)) { std::string keyCode = " 2C"; std::string_view view = line; if (view.rfind(keyCode, 0) == 0) { // read nodes block mapNodes = readNodes(ifstr, line, points); } keyCode = " 3C"; if (view.rfind(keyCode, 0) == 0) { // read elements block cellTypes = readElements(ifstr, line, mapNodes, cells); } keyCode = " 1P"; if (view.rfind(keyCode, 0) == 0) { // read parameter readParameter(ifstr, line); } keyCode = " 100C"; if (view.rfind(keyCode, 0) == 0) { // read result info block FRDResultInfo info; readResultInfo(ifstr, line, info); auto it = grids.find(info); if (it == grids.end()) { // create TimeInfo metadata auto timeInfo = createTimeInfo(mapAnalysisTypeToStr[info.analysisType]); // search analysis type block and create it if necessary auto it2 = blocks.find(info.analysisType); if (it2 == blocks.end()) { block = vtkSmartPointer::New(); block->GetFieldData()->AddArray(timeInfo); blocks[info.analysisType] = block; } else { block = it2->second; } // create unstructured grid grid = vtkSmartPointer::New(); grid->SetPoints(points); grid->SetCells(cellTypes.data(), cells); // create TimeValue metadata auto stepValue = createTimeValue(info.value); grid->GetFieldData()->AddArray(stepValue); grid->GetFieldData()->AddArray(timeInfo); grids[info] = grid; unsigned int nb = block->GetNumberOfBlocks(); block->SetBlock(nb, grid); } else { grid = (*it).second; } // read result entries and node results readResults(ifstr, line, mapNodes, info, grid); } } int i = 0; for (const auto& b : blocks) { multiBlock->SetBlock(i, b.second); ++i; } // save points and elements even without results if (grids.empty()) { block = vtkSmartPointer::New(); grid = vtkSmartPointer::New(); grid->SetPoints(points); grid->SetCells(cellTypes.data(), cells); auto timeInfo = createTimeInfo(""); auto stepValue = createTimeValue(0); grid->GetFieldData()->AddArray(stepValue); grid->GetFieldData()->AddArray(timeInfo); block->SetBlock(0, grid); block->GetFieldData()->AddArray(timeInfo); multiBlock->SetBlock(0, block); } return multiBlock; } } // namespace FRDReader void FemVTKTools::frdToVTK(const char* filename) { Base::FileInfo fi(filename); if (!fi.isReadable()) { throw Base::FileException("File to load not existing or not readable", filename); } std::ifstream ifstr(filename, std::ios::in | std::ios::binary); vtkSmartPointer multiBlock = FRDReader::readFRD(ifstr); std::string dir = fi.dirPath(); for (unsigned int i = 0; i < multiBlock->GetNumberOfBlocks(); ++i) { vtkDataObject* block = multiBlock->GetBlock(i); // get TimeInfo vtkSmartPointer info = vtkStringArray::SafeDownCast(block->GetFieldData()->GetAbstractArray(0)); std::string type = info->GetValue(0).c_str(); auto writer = vtkSmartPointer::New(); std::string blockFile = dir + "/" + fi.fileNamePure() + type + "." + writer->GetDefaultFileExtension(); writer->SetFileName(blockFile.c_str()); writer->SetInputData(block); writer->Update(); } } } // namespace Fem