diff --git a/Common/CommandLine.cpp b/Common/CommandLine.cpp index 4c611ebc2687b6a3facff5ed84f9bb6711e0a737..19c233f24cb160fcbfcc24bc40322283a00c8005 100644 --- a/Common/CommandLine.cpp +++ b/Common/CommandLine.cpp @@ -74,7 +74,7 @@ std::vector<std::pair<std::string, std::string> > GetUsage() s.push_back(mp("-o file", "Specify output file name")); s.push_back(mp("-format string", "Select output mesh format (auto (default), msh, " "msh1, msh2, unv, vrml, ply2, stl, mesh, bdf, cgns, " - "p3d, diff, med, ...)")); + "p3d, diff, med, neu, ...)")); s.push_back(mp("-bin", "Use binary format when available")); s.push_back(mp("-refine", "Perform uniform mesh refinement, then exit")); s.push_back(mp("-reclassify", "Reclassify mesh, then exit")); @@ -106,7 +106,7 @@ std::vector<std::pair<std::string, std::string> > GetUsage() s.push_back(mp("-rand float", "Set random perturbation factor")); s.push_back(mp("-bgm file", "Load background mesh from file")); s.push_back(mp("-check", "Perform various consistency checks on mesh")); - s.push_back(mp("-ignorePartBound", "Ignore partition boundaries")); + s.push_back(mp("-ignorePartBound", "Ignore partition boundaries")); s.push_back(mp("-ignorePeriocity", "Ignore periodic boundaries")); s.push_back(mp("-oneFilePerPart", "Save mesh partitions in separate files")); #if defined(HAVE_FLTK) @@ -1163,5 +1163,3 @@ void GetOptions(int argc, char *argv[]) if(CTX::instance()->terminal == 99) CTX::instance()->terminal = terminal; } - - diff --git a/Common/CreateFile.cpp b/Common/CreateFile.cpp index 7ec744426dacf7c2091fc665bf8c63b64f8c7d16..532c68dcabf1d64ecf205a15b9bd0f4f93e63b30 100644 --- a/Common/CreateFile.cpp +++ b/Common/CreateFile.cpp @@ -79,6 +79,7 @@ int GetFileFormatFromExtension(const std::string &ext) else if(ext == ".stp") return FORMAT_STEP; else if(ext == ".iges") return FORMAT_IGES; else if(ext == ".igs") return FORMAT_IGES; + else if(ext == ".neu") return FORMAT_NEU; else return -1; } @@ -133,6 +134,7 @@ std::string GetDefaultFileName(int format) case FORMAT_BREP: name += ".brep"; break; case FORMAT_IGES: name += ".iges"; break; case FORMAT_STEP: name += ".step"; break; + case FORMAT_NEU: name += ".neu"; break; default: break; } return name; @@ -396,6 +398,11 @@ void CreateOutputFile(const std::string &fileName, int format, GModel::current()->writeOCCSTEP(name); break; + case FORMAT_NEU: + GModel::current()->writeNEU + (name, CTX::instance()->mesh.saveAll, CTX::instance()->mesh.scalingFactor); + break; + #if defined(HAVE_FLTK) case FORMAT_PPM: case FORMAT_YUV: diff --git a/Common/GmshDefines.h b/Common/GmshDefines.h index 630462e3fd90e1cf2571754bc7be9ec00270d903..770752ca4961d2d75d57aafcad89db134f538d95 100644 --- a/Common/GmshDefines.h +++ b/Common/GmshDefines.h @@ -7,54 +7,55 @@ #define _GMSH_DEFINES_H_ // IO file formats (numbers should not be changed) -#define FORMAT_MSH 1 -#define FORMAT_UNV 2 -#define FORMAT_GREF 3 -#define FORMAT_XPM 4 -#define FORMAT_PS 5 -#define FORMAT_BMP 6 -#define FORMAT_GIF 7 -#define FORMAT_GEO 8 -#define FORMAT_JPEG 9 -#define FORMAT_AUTO 10 -#define FORMAT_PPM 11 -#define FORMAT_YUV 12 -#define FORMAT_DMG 13 -#define FORMAT_SMS 14 -#define FORMAT_OPT 15 -#define FORMAT_VTK 16 -#define FORMAT_MPEG 17 -#define FORMAT_TEX 18 -#define FORMAT_VRML 19 -#define FORMAT_EPS 20 -#define FORMAT_MAIL 21 -#define FORMAT_PNG 22 -#define FORMAT_TXT 23 -#define FORMAT_PDF 24 -#define FORMAT_RMED 25 -#define FORMAT_POS 26 -#define FORMAT_STL 27 -#define FORMAT_P3D 28 -#define FORMAT_SVG 29 -#define FORMAT_MESH 30 -#define FORMAT_BDF 31 -#define FORMAT_CGNS 32 -#define FORMAT_MED 33 -#define FORMAT_DIFF 34 -#define FORMAT_BREP 35 -#define FORMAT_STEP 36 -#define FORMAT_IGES 37 -#define FORMAT_IR3 38 -#define FORMAT_INP 39 -#define FORMAT_PLY2 40 -#define FORMAT_CELUM 41 -#define FORMAT_SU2 42 +#define FORMAT_MSH 1 +#define FORMAT_UNV 2 +#define FORMAT_GREF 3 +#define FORMAT_XPM 4 +#define FORMAT_PS 5 +#define FORMAT_BMP 6 +#define FORMAT_GIF 7 +#define FORMAT_GEO 8 +#define FORMAT_JPEG 9 +#define FORMAT_AUTO 10 +#define FORMAT_PPM 11 +#define FORMAT_YUV 12 +#define FORMAT_DMG 13 +#define FORMAT_SMS 14 +#define FORMAT_OPT 15 +#define FORMAT_VTK 16 +#define FORMAT_MPEG 17 +#define FORMAT_TEX 18 +#define FORMAT_VRML 19 +#define FORMAT_EPS 20 +#define FORMAT_MAIL 21 +#define FORMAT_PNG 22 +#define FORMAT_TXT 23 +#define FORMAT_PDF 24 +#define FORMAT_RMED 25 +#define FORMAT_POS 26 +#define FORMAT_STL 27 +#define FORMAT_P3D 28 +#define FORMAT_SVG 29 +#define FORMAT_MESH 30 +#define FORMAT_BDF 31 +#define FORMAT_CGNS 32 +#define FORMAT_MED 33 +#define FORMAT_DIFF 34 +#define FORMAT_BREP 35 +#define FORMAT_STEP 36 +#define FORMAT_IGES 37 +#define FORMAT_IR3 38 +#define FORMAT_INP 39 +#define FORMAT_PLY2 40 +#define FORMAT_CELUM 41 +#define FORMAT_SU2 42 #define FORMAT_MPEG_PREVIEW 43 -#define FORMAT_PGF 44 -#define FORMAT_PVTU 45 -#define FORMAT_X3D 46 -#define FORMAT_TOCHNOG 47 -#define FORMAT_TIKZ 48 +#define FORMAT_PGF 44 +#define FORMAT_PVTU 45 +#define FORMAT_X3D 46 +#define FORMAT_TOCHNOG 47 +#define FORMAT_TIKZ 48 +#define FORMAT_NEU 49 // Element types #define TYPE_PNT 1 diff --git a/Geo/CMakeLists.txt b/Geo/CMakeLists.txt index 2ac9ea624b919b808d1946d9943ee9b20984b87d..e8d73035adf0db89d78b9648e7a62cee26ddba3c 100644 --- a/Geo/CMakeLists.txt +++ b/Geo/CMakeLists.txt @@ -15,7 +15,7 @@ set(SRC gmshSurface.cpp OCCVertex.cpp OCCEdge.cpp OCCFace.cpp OCCRegion.cpp GenericVertex.cpp GenericEdge.cpp GenericFace.cpp GenericRegion.cpp - discreteEdge.cpp discreteFace.cpp discreteDiskFace.cpp discreteRegion.cpp + discreteEdge.cpp discreteFace.cpp discreteDiskFace.cpp discreteRegion.cpp fourierEdge.cpp fourierFace.cpp fourierProjectionFace.cpp ACISVertex.cpp ACISEdge.cpp @@ -25,12 +25,12 @@ set(SRC GModelVertexArrays.cpp GModelFactory.cpp GModelIO_GEO.cpp GModelIO_ACIS.cpp GModelIO_OCC.cpp GModelIO_TOCHNOG.cpp - GModelIO_Fourier.cpp - GModelIO_MSH.cpp GModelIO_MSH2.cpp GModelIO_VTK.cpp GModelIO_POS.cpp - GModelIO_CGNS.cpp GModelIO_MED.cpp GModelIO_MESH.cpp GModelIO_STL.cpp - GModelIO_PLY.cpp GModelIO_VRML.cpp GModelIO_UNV.cpp GModelIO_BDF.cpp + GModelIO_Fourier.cpp + GModelIO_MSH.cpp GModelIO_MSH2.cpp GModelIO_VTK.cpp GModelIO_POS.cpp + GModelIO_CGNS.cpp GModelIO_MED.cpp GModelIO_MESH.cpp GModelIO_STL.cpp + GModelIO_PLY.cpp GModelIO_VRML.cpp GModelIO_UNV.cpp GModelIO_BDF.cpp GModelIO_IR3.cpp GModelIO_DIFF.cpp GModelIO_GEOM.cpp GModelIO_INP.cpp - GModelIO_MAIL.cpp GModelIO_P3D.cpp GModelIO_CELUM.cpp + GModelIO_MAIL.cpp GModelIO_P3D.cpp GModelIO_CELUM.cpp GModelIO_NEU.cpp GModelIO_ACTRAN.cpp GModelIO_SU2.cpp GModelIO_SAMCEF.cpp ExtrudeParams.cpp Geo.cpp @@ -51,5 +51,5 @@ set(SRC MVertexBoundaryLayerData.cpp ) -file(GLOB HDR RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.h) +file(GLOB HDR RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.h) append_gmsh_src(Geo "${SRC};${HDR}") diff --git a/Geo/GModel.h b/Geo/GModel.h index b37ed3715174d4a968d805eae56ba4c54b2bdd43..4f382250a7f521dbd3bcaf37079279dabe623f72 100644 --- a/Geo/GModel.h +++ b/Geo/GModel.h @@ -724,6 +724,9 @@ class GModel { // SU2 mesh file int writeSU2(const std::string &name, bool saveAll, double scalingFactor); + + // GAMBIT neutral mesh file (.neu) + int writeNEU(const std::string &name, bool saveAll, double scalingFactor); }; #endif diff --git a/Geo/GModelIO_NEU.cpp b/Geo/GModelIO_NEU.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d5aa503e8e88336b09e6cb76258e666a424ef869 --- /dev/null +++ b/Geo/GModelIO_NEU.cpp @@ -0,0 +1,263 @@ +// Gmsh - Copyright (C) 1997-2017 C. Geuzaine, J.-F. Remacle +// +// See the LICENSE.txt file for license information. Please report all +// bugs and problems to the public mailing list <gmsh@onelab.info>. + +#include <algorithm> +#include <limits> +#include <unordered_map> +#include "GModel.h" +#include "OS.h" +#include "MTriangle.h" +#include "MTetrahedron.h" + +namespace +{ + static const auto GAMBIT_TYPE_EDGE = 1; + static const auto GAMBIT_TYPE_QUAD = 2; + static const auto GAMBIT_TYPE_TRI = 3; + static const auto GAMBIT_TYPE_TET = 6; + + static const std::unordered_map<std::string, unsigned> BOUNDARY_CODES { + {"UNSPECIFIED", 0}, + {"AXIS", 1}, + {"CONJUGATE", 2}, + {"CONVECTION", 3}, + {"CYCLIC", 4}, + {"DEAD", 5}, + {"ELEMENT_SID", 6}, + {"ESPECIES", 7}, + {"EXHAUST_FAN", 8}, + {"FAN", 9}, + {"FREE_SURFACE", 10}, + {"GAP", 11}, + {"INFLOW", 12}, + {"INLET", 13}, + {"INLET_VENT", 14}, + {"INTAKE_FAN", 15}, + {"INTERFACE", 16}, + {"INTERIOR", 17}, + {"INTERNAL", 18}, + {"LIVE", 19}, + {"MASS_FLOW_INLET", 20}, + {"MELT", 21}, + {"MELT_INTERFACE", 22}, + {"MOVING_BOUNDARY", 23}, + {"NODE", 24}, + {"OUTFLOW", 25}, + {"OUTLET", 26}, + {"OUTLET_VENT", 27}, + {"PERIODIC", 28}, + {"PLOT", 29}, + {"POROUS", 30}, + {"POROUS_JUMP", 31}, + {"PRESSURE", 32}, + {"PRESSURE_FAR_FIELD", 33}, + {"PRESSURE_INFLOW", 34}, + {"PRESSURE_INLET", 35}, + {"PRESSURE_OUTFLOW", 36}, + {"PRESSURE_OUTLET", 37}, + {"RADIATION", 38}, + {"RADIATOR", 39}, + {"RECIRCULATION_INLET", 40}, + {"RECIRCULATION_OUTLET", 41}, + {"SLIP", 42}, + {"SREACTION", 43}, + {"SURFACE", 44}, + {"SYMMETRY", 45}, + {"TRACTION", 46}, + {"TRAJECTORY", 47}, + {"VELOCITY", 48}, + {"VELOCITY_INLET", 49}, + {"VENT", 50}, + {"WALL", 51}, + {"SIDE", 51}, + {"SPRING", 52}, + }; + + // Gambit numbers its faces slightly differently + static const unsigned GAMBIT_FACE_MAP[4] = {1,2,4,3}; + + unsigned const gambitBoundaryCode(std::string name) + { + std::transform(name.begin(), name.end(),name.begin(), ::toupper); + auto code = BOUNDARY_CODES.find(name); + return code == BOUNDARY_CODES.end() ? 0 : code->second; + } + + typedef std::pair<unsigned, unsigned> TetFacePair; + typedef std::unordered_map<unsigned, std::vector<TetFacePair> > IDTetFaceMap; + + bool const sortBCs(TetFacePair const& lhs, TetFacePair const& rhs) + { + return lhs.first < rhs.first; + } + + IDTetFaceMap const gatherBC(GModel* gm) + { + // create association map for vertices and faces + std::unordered_map<unsigned, std::vector<unsigned> > vertmap; + for (auto it = gm->firstFace(); it != gm->lastFace(); ++it) { + for (auto const& tri: (*it)->triangles) { + for (auto i = 0; i < tri->getNumVertices(); ++i) { + vertmap[tri->getVertex(i)->getNum()].push_back(tri->getNum()); + } + } + } + + // determine which faces belong to which tetrahedra by comparing vertices + IDTetFaceMap tetfacemap; + for (auto it = gm->firstRegion(); it != gm->lastRegion(); ++it) { + for (auto const& tet: (*it)->tetrahedra) { + for (auto faceNum = 0; faceNum < tet->getNumFaces(); ++faceNum) { + std::vector<MVertex*> verts; + tet->getFaceVertices(faceNum, verts); + + auto current = vertmap[verts[0]->getNum()]; + for (unsigned j = 1; j < verts.size() && current.size() != 0; ++j) { + std::vector<unsigned> common_data; + set_intersection(current.begin(), current.end(), + vertmap[verts[j]->getNum()].begin(), + vertmap[verts[j]->getNum()].end(), + std::back_inserter(common_data)); + current = common_data; + } + if (current.size() == 1) { + tetfacemap[current[0]].push_back( + TetFacePair(tet->getNum(), GAMBIT_FACE_MAP[faceNum])); + } + } + } + } + + // populate boundary conditions for tetrahedra given triangle physicals + IDTetFaceMap boundaryConditions; + for (auto it = gm->firstFace(); it != gm->lastFace(); ++it) { + if ((*it)->physicals.size()) { + for (auto const& phys: (*it)->physicals) { + for (auto const& tri: (*it)->triangles) { + auto tets = tetfacemap.find(tri->getNum()); + if (tets != tetfacemap.end()) { + for (auto const& tet: tets->second) { + boundaryConditions[phys].push_back(tet); + } + } + } + } + } + } + + return boundaryConditions; + } +} + +// for a specification of the GAMBIT neutral format: +// http://web.stanford.edu/class/me469b/handouts/gambit_write.pdf +int GModel::writeNEU(const std::string &name, bool saveAll, + double scalingFactor) +{ + auto fp = Fopen(name.c_str(), "w"); + if (!fp) { + Msg::Error("Unable to open file '%s'", name.c_str()); + return 0; + } + + // gather tetrahedra and id normalization information + auto numTetrahedra = 0; + auto lowestId = std::numeric_limits<int>::max(); + std::unordered_map<unsigned, std::vector<unsigned> > elementGroups; + for (riter it = firstRegion(); it != lastRegion(); ++it) { + if (saveAll || (*it)->physicals.size()) { + numTetrahedra += (*it)->tetrahedra.size(); + + for (auto const& phys: (*it)->physicals) { + for (auto const& tet: (*it)->tetrahedra) { + elementGroups[phys].push_back(tet->getNum()); + + if (tet->getNum() < lowestId) lowestId = tet->getNum()-1; + } + } + } + } + + auto boundaryConditions = gatherBC(this); + + // Metadata + fprintf(fp, " CONTROL INFO 2.0.0\n"); + fprintf(fp, "** GAMBIT NEUTRAL FILE\n"); + fprintf(fp, "Gmsh mesh in GAMBIT neutral file format\n"); + fprintf(fp, "PROGRAM: Gambit VERSION: 2.0.0\n"); + + char datestring[256]; + time_t rawtime; + struct tm* timeinfo; + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(datestring, sizeof(datestring), "%c", timeinfo); + fprintf(fp, "%s\n", datestring); + + fprintf(fp, " NUMNP NELEM NGRPS NBSETS NDFCD NDFVL\n"); + fprintf(fp, " %9d %9d %9lu %9lu %9d %9d\n", indexMeshVertices(saveAll), numTetrahedra, + elementGroups.size(), boundaryConditions.size(), getDim(), + getDim()); + fprintf(fp, "ENDOFSECTION\n"); + + // Nodes + fprintf(fp, " NODAL COORDINATES 2.0.0\n"); + std::vector<GEntity*> entities; + getEntities(entities); + for (unsigned i = 0; i < entities.size(); ++i) { + for (unsigned j = 0; j < entities[i]->mesh_vertices.size(); ++j) { + entities[i]->mesh_vertices[j]->writeNEU(fp, getDim(), scalingFactor); + } + } + fprintf(fp, "ENDOFSECTION\n"); + + // Elements + fprintf(fp, " ELEMENTS/CELLS 2.0.0\n"); + for (riter it = firstRegion(); it != lastRegion(); ++it) { + auto numPhys = (*it)->physicals.size(); + if (saveAll || numPhys) { + for (unsigned i = 0; i < (*it)->tetrahedra.size(); ++i) { + (*it)->tetrahedra[i]->writeNEU(fp, GAMBIT_TYPE_TET, lowestId, + numPhys ? (*it)->physicals[0] : 0); + } + } + } + fprintf(fp, "ENDOFSECTION\n"); + + // Element Groups + for (auto const& kv: elementGroups) { + fprintf(fp, " ELEMENT GROUP 2.0.0\n"); + fprintf(fp, "GROUP: %10d ELEMENTS: %10lu MATERIAL: 0 NFLAGS: %10d\n", kv.first, kv.second.size(), 1); + fprintf(fp, "Material group %d\n", kv.first); + fprintf(fp, " 0"); + + unsigned i = 0; + for (auto const& elem: kv.second) { + if (i++ % 10 == 0) { + fprintf(fp, "\n"); + } + fprintf(fp, "%8d", elem-lowestId); + } + fprintf(fp, "\n"); + fprintf(fp, "ENDOFSECTION\n"); + } + + // Boundary Conditions + for (auto &kv: boundaryConditions) { + fprintf(fp, " BOUNDARY CONDITIONS 2.0.0\n"); + + auto regionName = getPhysicalName(2, kv.first); + fprintf(fp, "%32s%8d%8lu%8d%8d\n", regionName.c_str(), 1, kv.second.size(), 0, gambitBoundaryCode(regionName)); + std::sort(kv.second.begin(), kv.second.end(), sortBCs); + for (auto const& boundary: kv.second) { + fprintf(fp, "%10d %5d %5d\n", boundary.first-lowestId, GAMBIT_TYPE_TET, boundary.second); + } + + fprintf(fp, "ENDOFSECTION\n"); + } + + fclose(fp); + return 1; +} diff --git a/Geo/MElement.cpp b/Geo/MElement.cpp index 05689e94f56b26f12960f6908fd4d999707f25b3..4914775dd8b4b36cbc797055e4c4ae0f1564349f 100644 --- a/Geo/MElement.cpp +++ b/Geo/MElement.cpp @@ -1338,6 +1338,19 @@ void MElement::writeMESH(FILE *fp, int elementTagType, int elementary, if(physical < 0) reverse(); } +void MElement::writeNEU(FILE *fp, unsigned gambitType, int idAdjust, int phys) +{ + if(phys < 0) reverse(); + + fprintf(fp, "%8d %2d %2d ", _num-idAdjust, gambitType, getNumVertices()); + for(int i = 0; i < getNumVertices(); ++i) { + fprintf(fp, "%8d", getVertex(i)->getIndex()); + } + fprintf(fp, "\n"); + + if(phys < 0) reverse(); +} + void MElement::writeIR3(FILE *fp, int elementTagType, int num, int elementary, int physical) { diff --git a/Geo/MElement.h b/Geo/MElement.h index 20b7f37ffee9b91ce44b6cb17b833d9015387537..cf86a181b4c360fc157a8623725ac20c5c056599 100644 --- a/Geo/MElement.h +++ b/Geo/MElement.h @@ -385,6 +385,7 @@ class MElement virtual void writeTOCHNOG(FILE *fp, int num); virtual void writeMESH(FILE *fp, int elementTagType=1, int elementary=1, int physical=0); + virtual void writeNEU(FILE *fp, unsigned gambitType, int adjust, int phys=0); virtual void writeIR3(FILE *fp, int elementTagType, int num, int elementary, int physical); virtual void writeBDF(FILE *fp, int format=0, int elementTagType=1, diff --git a/Geo/MVertex.cpp b/Geo/MVertex.cpp index c56f6cedf6d4da65713d9930c95706a67d9028f6..cd1535f1d86152f204593e1963c6472f9c11bcb9 100644 --- a/Geo/MVertex.cpp +++ b/Geo/MVertex.cpp @@ -270,6 +270,26 @@ void MVertex::writeMESH(FILE *fp, double scalingFactor) _ge ? _ge->tag() : 0); } +void MVertex::writeNEU(FILE *fp, int dim, double scalingFactor) +{ + if(_index < 0) return; // negative index vertices are never saved + + switch(dim) { + case 3: + fprintf(fp, "%10d%20.11e%20.11e%20.11e\n", + _index, x() * scalingFactor, y() * scalingFactor, + z() * scalingFactor); + break; + case 2: + fprintf(fp, "%10d%20.11e%20.11e\n", + _index, x() * scalingFactor, y() * scalingFactor); + break; + case 1: + fprintf(fp, "%10d%20.11e\n", _index, x() * scalingFactor); + break; + } +} + static void double_to_char8(double val, char *str) { if(val >= 1.e6) diff --git a/Geo/MVertex.h b/Geo/MVertex.h index 0467a86606a63e5efab81e3e8687b247d235ac67..df093d162aa6a33fc6606b177298adeb8f03724d 100644 --- a/Geo/MVertex.h +++ b/Geo/MVertex.h @@ -100,6 +100,7 @@ class MVertex{ bool bigEndian=false); void writeTOCHNOG(FILE *fp, int dim, double scalingFactor=1.0); void writeMESH(FILE *fp, double scalingFactor=1.0); + void writeNEU(FILE *fp, int dim, double scalingFactor=1.0); void writeBDF(FILE *fp, int format=0, double scalingFactor=1.0); void writeINP(FILE *fp, double scalingFactor=1.0); void writeDIFF(FILE *fp, bool binary, double scalingFactor=1.0);