/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | Copyright held by original author \\/ M anipulation | ------------------------------------------------------------------------------- License This file is part of OpenFOAM. OpenFOAM is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenFOAM 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 General Public License for more details. You should have received a copy of the GNU General Public License along with OpenFOAM; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Description Reader module for Fieldview9 to read OpenFOAM mesh and data. Creates new 'fvbin' type executable which needs to be installed in place of bin/fvbin. Implements a reader for combined mesh&results on an unstructured mesh. See Fieldview Release 9 Reference Manual and coding in user/ directory of the Fieldview release. \*---------------------------------------------------------------------------*/ #include "fvCFD.H" #include "IOobjectList.H" #include "GeometricField.H" #include "pointFields.H" #include "volPointInterpolation.H" #include "readerDatabase.H" #include "wallPolyPatch.H" #include "ListOps.H" #include "cellModeller.H" // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // extern "C" { // Define various Fieldview constants and prototypes #include "fv_reader_tags.h" static const int FVHEX = 2; static const int FVPRISM = 3; static const int FVPYRAMID = 4; static const int FVTET = 1; static const int ITYPE = 1; unsigned int fv_encode_elem_header(int elem_type, int wall_info[]); void time_step_get_value(float*, int, int*, float*, int*); void fv_error_msg(const char*, const char*); void reg_single_unstruct_reader ( char *, void ( char*, int*, int*, int*, int*, int*, int*, int[], int*, char[][80], int[], int*, char[][80], int* ), void ( int*, int*, int*, float[], int*, float[], int* ) ); int create_tet(const int, const int[8], const int[]); int create_pyramid(const int, const int[8], const int[]); int create_prism(const int, const int[8], const int[]); int create_hex(const int, const int[8], const int[]); typedef unsigned char uChar; extern uChar create_bndry_face_buffered ( int bndry_type, int num_verts, int verts[], int *normals_flags, int num_grid_nodes ); /* * just define empty readers here for ease of linking. * Comment out if you have doubly defined linking error on this symbol */ void ftn_register_data_readers() {} /* * just define empty readers here for ease of linking. * Comment out if you have doubly defined linking error on this symbol */ void ftn_register_functions() {} /* * just define empty readers here for ease of linking. * Comment out if you have doubly defined linking error on this symbol */ void register_functions() {} // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // // Storage for all Foam state (mainly database & mesh) // static readerDatabase db_; // Write fv error message. static void errorMsg(const string& msg) { fv_error_msg("Foam Reader", msg.c_str()); } // Simple check if directory is valid case directory. static bool validCase(const fileName& rootAndCase) { //if (isDir(rootAndCase/"system") && isDir(rootAndCase/"constant")) if (isDir(rootAndCase/"constant")) { return true; } else { return false; } } // Check whether case has topo changes by looking back from last time // to first directory with polyMesh/cells. static bool hasTopoChange(const instantList& times) { label lastIndex = times.size()-1; const Time& runTime = db_.runTime(); // Only set time; do not update mesh. runTime.setTime(times[lastIndex], lastIndex); fileName facesInst(runTime.findInstance(polyMesh::meshSubDir, "faces")); // See if cellInst is constant directory. Note extra .name() is for // parallel cases when runTime.constant is '../constant' if (facesInst != runTime.constant().name()) { Info<< "Found cells file in " << facesInst << " so case has " << "topological changes" << endl; return true; } else { Info<< "Found cells file in " << facesInst << " so case has " << "no topological changes" << endl; return false; } } static bool selectTime(const instantList& times, int* iret) { List fvTimes(2*times.size()); forAll(times, timeI) { fvTimes[2*timeI] = float(timeI); fvTimes[2*timeI+1] = float(times[timeI].value()); } int istep; float time; *iret=0; time_step_get_value(fvTimes.begin(), times.size(), &istep, &time, iret); if (*iret == -5) { errorMsg("Out of memory."); return false; } if (*iret == -15) { // Cancel action. return false; } if (*iret != 0) { errorMsg("Unspecified error."); return false; } Info<< "Selected timeStep:" << istep << " time:" << scalar(time) << endl; // Set time and load mesh. db_.setTime(times[istep], istep); return true; } // Gets (names of) all fields in all timesteps. static void createFieldNames ( const Time& runTime, const instantList& Times, const word& setName ) { // From foamToFieldView9/getFieldNames.H: HashSet volScalarHash; HashSet volVectorHash; HashSet surfScalarHash; HashSet surfVectorHash; if (setName.empty()) { forAll(Times, timeI) { const word& timeName = Times[timeI].name(); // Add all fields to hashtable IOobjectList objects(runTime, timeName); wordList vsNames(objects.names(volScalarField::typeName)); forAll(vsNames, fieldI) { volScalarHash.insert(vsNames[fieldI]); } wordList vvNames(objects.names(volVectorField::typeName)); forAll(vvNames, fieldI) { volVectorHash.insert(vvNames[fieldI]); } } } db_.setFieldNames(volScalarHash.toc(), volVectorHash.toc()); } // Appends interpolated values of fieldName to vars array. static void storeScalarField ( const volPointInterpolation& pInterp, const word& fieldName, float vars[], label& pointI ) { label nPoints = db_.mesh().nPoints(); label nTotPoints = nPoints + db_.polys().size(); // Check if present IOobject ioHeader ( fieldName, db_.runTime().timeName(), db_.runTime(), IOobject::MUST_READ, IOobject::NO_WRITE ); if (ioHeader.headerOk()) { Info<< "Storing " << nTotPoints << " of interpolated " << fieldName << endl; volScalarField field(ioHeader, db_.mesh()); pointScalarField psf(pInterp.interpolate(field)); forAll(psf, i) { vars[pointI++] = float(psf[i]); } const labelList& polys = db_.polys(); forAll(polys, i) { label cellI = polys[i]; vars[pointI++] = float(field[cellI]); } } else { Info<< "Storing " << nTotPoints << " of dummy values of " << fieldName << endl; for(label i = 0; i < nPoints; i++) { vars[pointI++] = 0.0; } const labelList& polys = db_.polys(); forAll(polys, i) { vars[pointI++] = 0.0; } } } // Appends interpolated values of fieldName to vars array. static void storeVectorField ( const volPointInterpolation& pInterp, const word& fieldName, float vars[], label& pointI ) { label nPoints = db_.mesh().nPoints(); label nTotPoints = nPoints + db_.polys().size(); // Check if present IOobject ioHeader ( fieldName, db_.runTime().timeName(), db_.runTime(), IOobject::MUST_READ, IOobject::NO_WRITE ); if (ioHeader.headerOk()) { Info<< "Storing " << nTotPoints << " of interpolated " << fieldName << endl; volVectorField field(ioHeader, db_.mesh()); for (direction d = 0; d < vector::nComponents; d++) { tmp tcomp = field.component(d); const volScalarField& comp = tcomp(); pointScalarField psf(pInterp.interpolate(comp)); forAll(psf, i) { vars[pointI++] = float(psf[i]); } const labelList& polys = db_.polys(); forAll(polys, i) { label cellI = polys[i]; vars[pointI++] = float(comp[cellI]); } } } else { Info<< "Storing " << nTotPoints << " of dummy values of " << fieldName << endl; for (direction d = 0; d < vector::nComponents; d++) { for(label i = 0; i < nPoints; i++) { vars[pointI++] = 0.0; } const labelList& polys = db_.polys(); forAll(polys, i) { vars[pointI++] = 0.0; } } } } // Returns Fieldview face_type of mesh face faceI. static label getFvType(const polyMesh& mesh, const label faceI) { return mesh.boundaryMesh().whichPatch(faceI) + 1; } // Returns Fieldview face_type of face f. static label getFaceType ( const polyMesh& mesh, const labelList& faceLabels, const face& f ) { // Search in subset faceLabels of faces for index of face f. const faceList& faces = mesh.faces(); forAll(faceLabels, i) { label faceI = faceLabels[i]; if (f == faces[faceI]) { // Convert patch to Fieldview face_type. return getFvType(mesh, faceI); } } FatalErrorIn("getFaceType") << "Cannot find face " << f << " in mesh face subset " << faceLabels << abort(FatalError); return -1; } // Returns Fieldview face_types for set of faces static labelList getFaceTypes ( const polyMesh& mesh, const labelList& cellFaces, const faceList& cellShapeFaces ) { labelList faceLabels(cellShapeFaces.size()); forAll(cellShapeFaces, i) { faceLabels[i] = getFaceType(mesh, cellFaces, cellShapeFaces[i]); } return faceLabels; } /* * Callback for querying file contents. Taken from user/user_unstruct_combined.f */ void user_query_file_function ( /* input */ char* fname, /* filename */ int* lenf, /* length of fName */ int* iunit, /* fortran unit to use */ int* max_grids, /* maximum number of grids allowed */ int* max_face_types,/* maximum number of face types allowed */ int* max_vars, /* maximum number of result variables allowed per */ /* grid point*/ /* output */ int* num_grids, /* number of grids that will be read from data file */ int num_nodes[], /* (array of node counts for all grids) */ int* num_face_types, /* number of special face types */ char face_type_names[][80], /* array of face-type names */ int wall_flags[], /* array of flags for the "wall" behavior */ int* num_vars, /* number of result variables per grid point */ char var_names[][80], /* array of variable names */ int* iret /* return value */ ) { fprintf(stderr, "\n** user_query_file_function\n"); string rootAndCaseString(fname, *lenf); fileName rootAndCase(rootAndCaseString); word setName(""); if (!validCase(rootAndCase)) { setName = rootAndCase.name(); rootAndCase = rootAndCase.path(); word setDir = rootAndCase.name(); rootAndCase = rootAndCase.path(); word meshDir = rootAndCase.name(); rootAndCase = rootAndCase.path(); rootAndCase = rootAndCase.path(); if ( setDir == "sets" && meshDir == polyMesh::typeName && validCase(rootAndCase) ) { // Valid set (hopefully - cannot check contents of setName yet). } else { errorMsg ( "Could not find system/ and constant/ directory in\n" + rootAndCase + "\nPlease select a Foam case directory." ); *iret = 1; return; } } fileName rootDir(rootAndCase.path()); fileName caseName(rootAndCase.name()); // handle trailing '/' if (caseName.empty()) { caseName = rootDir.name(); rootDir = rootDir.path(); } Info<< "rootDir : " << rootDir << endl << "caseName : " << caseName << endl << "setName : " << setName << endl; // // Get/reuse database and mesh // bool caseChanged = db_.setRunTime(rootDir, caseName, setName); // // Select time // instantList Times = db_.runTime().times(); // If topo case set database time and update mesh. if (hasTopoChange(Times)) { if (!selectTime(Times, iret)) { return; } } else if (caseChanged) { // Load mesh (if case changed) to make sure we have nPoints etc. db_.loadMesh(); } // // Set output variables // *num_grids = 1; const fvMesh& mesh = db_.mesh(); label nTotPoints = mesh.nPoints() + db_.polys().size(); num_nodes[0] = nTotPoints; Info<< "setting num_nodes:" << num_nodes[0] << endl; Info<< "setting num_face_types:" << mesh.boundary().size() << endl; *num_face_types = mesh.boundary().size(); if (*num_face_types > *max_face_types) { errorMsg("Too many patches. FieldView limit:" + name(*max_face_types)); *iret = 1; return; } forAll(mesh.boundaryMesh(), patchI) { const polyPatch& patch = mesh.boundaryMesh()[patchI]; strcpy(face_type_names[patchI], patch.name().c_str()); if (isA(patch)) { wall_flags[patchI] = 1; } else { wall_flags[patchI] = 0; } Info<< "Patch " << patch.name() << " is wall:" << wall_flags[patchI] << endl; } //- Find all volFields and add them to database createFieldNames(db_.runTime(), Times, setName); *num_vars = db_.volScalarNames().size() + 3*db_.volVectorNames().size(); if (*num_vars > *max_vars) { errorMsg("Too many variables. FieldView limit:" + name(*max_vars)); *iret = 1; return; } int nameI = 0; forAll(db_.volScalarNames(), i) { const word& fieldName = db_.volScalarNames()[i]; const word& fvName = db_.getFvName(fieldName); strcpy(var_names[nameI++], fvName.c_str()); } forAll(db_.volVectorNames(), i) { const word& fieldName = db_.volVectorNames()[i]; const word& fvName = db_.getFvName(fieldName); strcpy(var_names[nameI++], (fvName + "x;" + fvName).c_str()); strcpy(var_names[nameI++], (fvName + "y").c_str()); strcpy(var_names[nameI++], (fvName + "z").c_str()); } *iret = 0; } /* * Callback for reading timestep. Taken from user/user_unstruct_combined.f */ void user_read_one_grid_function ( int* iunit, /* in: fortran unit number */ int* igrid, /* in: grid number to read */ int* nodecnt, /* in: number of nodes to read */ float xyz[], /* out: coordinates of nodes: x1..xN y1..yN z1..zN */ int* num_vars, /* in: number of results per node */ float vars[], /* out: values per node */ int* iret /* out: return value */ ) { fprintf(stderr, "\n** user_read_one_grid_function\n"); if (*igrid != 1) { errorMsg("Illegal grid number " + Foam::name(*igrid)); *iret = 1; return; } // Get current time instantList Times = db_.runTime().times(); // Set database time and update mesh. // Note: this should not be nessecary here. We already have the correct // time set and mesh loaded. This is only nessecary because Fieldview // otherwise thinks the case is non-transient. if (!selectTime(Times, iret)) { return; } const fvMesh& mesh = db_.mesh(); // With mesh now loaded check for change in number of points. label nTotPoints = mesh.nPoints() + db_.polys().size(); if (*nodecnt != nTotPoints) { errorMsg ( "nodecnt differs from number of points in mesh.\nnodecnt:" + Foam::name(*nodecnt) + " mesh:" + Foam::name(nTotPoints) ); *iret = 1; return; } if ( *num_vars != (db_.volScalarNames().size() + 3*db_.volVectorNames().size()) ) { errorMsg("Illegal number of variables " + name(*num_vars)); *iret = 1; return; } // // Set coordinates // const pointField& points = mesh.points(); int xIndex = 0; int yIndex = xIndex + nTotPoints; int zIndex = yIndex + nTotPoints; // Add mesh points first. forAll(points, pointI) { xyz[xIndex++] = points[pointI].x(); xyz[yIndex++] = points[pointI].y(); xyz[zIndex++] = points[pointI].z(); } // Add cell centres of polys const pointField& ctrs = mesh.cellCentres(); const labelList& polys = db_.polys(); forAll(polys, i) { label cellI = polys[i]; xyz[xIndex++] = ctrs[cellI].x(); xyz[yIndex++] = ctrs[cellI].y(); xyz[zIndex++] = ctrs[cellI].z(); } // // Define elements by calling fv routines // static const cellModel& tet = *(cellModeller::lookup("tet")); static const cellModel& tetWedge = *(cellModeller::lookup("tetWedge")); static const cellModel& pyr = *(cellModeller::lookup("pyr")); static const cellModel& prism = *(cellModeller::lookup("prism")); static const cellModel& wedge = *(cellModeller::lookup("wedge")); static const cellModel& hex = *(cellModeller::lookup("hex")); //static const cellModel& splitHex = *(cellModeller::lookup("splitHex")); int tetVerts[4]; int pyrVerts[5]; int prismVerts[6]; int hexVerts[8]; int tetFaces[4]; int pyrFaces[5]; int prismFaces[5]; int hexFaces[6]; const cellShapeList& cellShapes = mesh.cellShapes(); const faceList& faces = mesh.faces(); const cellList& cells = mesh.cells(); const labelList& owner = mesh.faceOwner(); label nPoints = mesh.nPoints(); // Fieldview face_types array with all faces marked as internal. labelList internalFaces(6, 0); // Mark all cells next to boundary so we don't have to calculate // wall_types for internal cells and can just pass internalFaces. boolList wallCell(mesh.nCells(), false); label nWallCells = 0; for (label faceI = mesh.nInternalFaces(); faceI < mesh.nFaces(); faceI++) { label cellI = owner[faceI]; if (!wallCell[cellI]) { wallCell[cellI] = true; nWallCells++; } } label nPolys = 0; forAll(cellShapes, cellI) { const cellShape& cellShape = cellShapes[cellI]; const cellModel& cellModel = cellShape.model(); const cell& cellFaces = cells[cellI]; int istat = 0; if (cellModel == tet) { tetVerts[0] = cellShape[3] + 1; tetVerts[1] = cellShape[0] + 1; tetVerts[2] = cellShape[1] + 1; tetVerts[3] = cellShape[2] + 1; if (wallCell[cellI]) { labelList faceTypes = getFaceTypes(mesh, cellFaces, cellShape.faces()); tetFaces[0] = faceTypes[2]; tetFaces[1] = faceTypes[3]; tetFaces[2] = faceTypes[0]; tetFaces[3] = faceTypes[1]; istat = create_tet(ITYPE, tetVerts, tetFaces); } else { // All faces internal so use precalculated zero. istat = create_tet(ITYPE, tetVerts, internalFaces.begin()); } } else if (cellModel == tetWedge) { prismVerts[0] = cellShape[0] + 1; prismVerts[1] = cellShape[3] + 1; prismVerts[2] = cellShape[4] + 1; prismVerts[3] = cellShape[1] + 1; prismVerts[4] = cellShape[4] + 1; prismVerts[5] = cellShape[2] + 1; if (wallCell[cellI]) { labelList faceTypes = getFaceTypes(mesh, cellFaces, cellShape.faces()); prismFaces[0] = faceTypes[1]; prismFaces[1] = faceTypes[2]; prismFaces[2] = faceTypes[3]; prismFaces[3] = faceTypes[0]; prismFaces[4] = faceTypes[3]; istat = create_prism(ITYPE, prismVerts, prismFaces); } else { istat = create_prism(ITYPE, prismVerts, internalFaces.begin()); } } else if (cellModel == pyr) { pyrVerts[0] = cellShape[0] + 1; pyrVerts[1] = cellShape[1] + 1; pyrVerts[2] = cellShape[2] + 1; pyrVerts[3] = cellShape[3] + 1; pyrVerts[4] = cellShape[4] + 1; if (wallCell[cellI]) { labelList faceTypes = getFaceTypes(mesh, cellFaces, cellShape.faces()); pyrFaces[0] = faceTypes[0]; pyrFaces[1] = faceTypes[3]; pyrFaces[2] = faceTypes[2]; pyrFaces[3] = faceTypes[1]; pyrFaces[4] = faceTypes[4]; istat = create_pyramid(ITYPE, pyrVerts, pyrFaces); } else { istat = create_pyramid(ITYPE, pyrVerts, internalFaces.begin()); } } else if (cellModel == prism) { prismVerts[0] = cellShape[0] + 1; prismVerts[1] = cellShape[3] + 1; prismVerts[2] = cellShape[4] + 1; prismVerts[3] = cellShape[1] + 1; prismVerts[4] = cellShape[5] + 1; prismVerts[5] = cellShape[2] + 1; if (wallCell[cellI]) { labelList faceTypes = getFaceTypes(mesh, cellFaces, cellShape.faces()); prismFaces[0] = faceTypes[4]; prismFaces[1] = faceTypes[2]; prismFaces[2] = faceTypes[3]; prismFaces[3] = faceTypes[0]; prismFaces[4] = faceTypes[1]; istat = create_prism(ITYPE, prismVerts, prismFaces); } else { istat = create_prism(ITYPE, prismVerts, internalFaces.begin()); } } else if (cellModel == wedge) { hexVerts[0] = cellShape[0] + 1; hexVerts[1] = cellShape[1] + 1; hexVerts[2] = cellShape[0] + 1; hexVerts[3] = cellShape[2] + 1; hexVerts[4] = cellShape[3] + 1; hexVerts[5] = cellShape[4] + 1; hexVerts[6] = cellShape[6] + 1; hexVerts[7] = cellShape[5] + 1; if (wallCell[cellI]) { labelList faceTypes = getFaceTypes(mesh, cellFaces, cellShape.faces()); hexFaces[0] = faceTypes[2]; hexFaces[1] = faceTypes[3]; hexFaces[2] = faceTypes[0]; hexFaces[3] = faceTypes[1]; hexFaces[4] = faceTypes[4]; hexFaces[5] = faceTypes[5]; istat = create_hex(ITYPE, hexVerts, hexFaces); } else { istat = create_hex(ITYPE, hexVerts, internalFaces.begin()); } } else if (cellModel == hex) { hexVerts[0] = cellShape[0] + 1; hexVerts[1] = cellShape[1] + 1; hexVerts[2] = cellShape[3] + 1; hexVerts[3] = cellShape[2] + 1; hexVerts[4] = cellShape[4] + 1; hexVerts[5] = cellShape[5] + 1; hexVerts[6] = cellShape[7] + 1; hexVerts[7] = cellShape[6] + 1; if (wallCell[cellI]) { labelList faceTypes = getFaceTypes(mesh, cellFaces, cellShape.faces()); hexFaces[0] = faceTypes[0]; hexFaces[1] = faceTypes[1]; hexFaces[2] = faceTypes[4]; hexFaces[3] = faceTypes[5]; hexFaces[4] = faceTypes[2]; hexFaces[5] = faceTypes[3]; istat = create_hex(ITYPE, hexVerts, hexFaces); } else { istat = create_hex(ITYPE, hexVerts, internalFaces.begin()); } } else { forAll(cellFaces, cFaceI) { label faceI = cellFaces[cFaceI]; // Get Fieldview facetype (internal/on patch) label fvFaceType = getFvType(mesh, faceI); const face& f = faces[faceI]; // Indices into storage label nQuads = 0; label nTris = 0; // Storage for triangles and quads created by face // decomposition (sized for worst case) faceList quadFaces((f.size() - 2)/2); faceList triFaces(f.size() - 2); f.trianglesQuads ( points, nTris, nQuads, triFaces, quadFaces ); // Label of cell centre in fv point list. label polyCentrePoint = nPoints + nPolys; for (label i=0; i