[FastCAE source code reading 8] Call gmsh to generate grid

FastCAE uses gmsh for mesh division. When dividing, it directly starts a new gmsh process. I personally guess that this design is to avoid the risk of gmsh’s GPL agreement.
When meshing, the general operation is as follows:

1. Python to gmshModule module

The GUI operation does not need to be analyzed until the Python step, which is relatively simple. The executed Python code is roughly as follows:

gmsher = Mesher.Gmsher()
gmsher.setDim(3)
gmsher.selectedAll()
gmsher.setElementType("Tet")
gmsher.setElementOrder(1)
gmsher.setMethod(1)
gmsher.setSizeFactor(1)
gmsher.setMinSize(0)
gmsher.setMaxSize(100)
gmsher.cleanGeo()
gmsher.startGenerationThread()

These codes can be found in the console, as shown below:

This creates a gmsher object, assigns meshing control parameters, and finally calls the startGenerationThread() method. The source code of this method is in the Mesher.py file. For three-dimensional problems, the interface GenerateMesh3D() of the gmshModule module will be called. gmshModule also provides an interface for dividing two-dimensional and fluid meshes. The code is in the GmshPy.h file, as shown in the figure below.

2. Start the gmsh thread, write the gmsh file, and generate the grid

In the GmshPy::GenerateMesh3D() function, only the parameters for meshing are prepared, and finally the GmshModule::generateSlot() function is called.

void GmshModule::generateSlot(GMshPara *para)
{<!-- -->
GmshThread *thread = iniGmshThread(para);
auto processBar = new ModuleBase::ProcessBar(_mainwindow, tr("Gmsh Working..."));
_threadManager->insertThread(processBar, thread); // Run gmsh thread
}

//Initialize gmsh thread
GmshThread *GmshModule::iniGmshThread(GMshPara *para)
{<!-- -->
if (_threadManager->isRuning())
{<!-- -->
delete para;
ModuleBase::Message m(Common::Message::Error, QString("Gmsh is running !"));
emit printMessageToMessageWindow(m);
}
GmshThread *thread = new GmshThread(_mainwindow, _preWindow, this, para->_dim);
thread->setPara(para);
delete para;
return thread;
}

In the GmshModule::generateSlot function, a thread is initialized and started to run.
Then open the run method of GmshThread. The general process is to merge geometric objects to form a TopoDS_Compound object, and call BRepTools::Write to write out the geometry.brep file. Write out the gmsh control file, and finally call the gmsh command to generate the grid file.

 void GmshThread::run()
{<!-- -->
this->mergeGeometry(); // Write geometry file
this->initGmshEnvoirment(); //Write the gmsh control file
this->generate(); // Execute gmsh command
}

void GmshThread::mergeGeometry()
{<!-- -->
QString exelPath = QCoreApplication::applicationDirPath();
const QString tempDir = exelPath + "/../temp/";
QDir dir(tempDir);
if (!dir.exists())
dir.mkpath(tempDir);
        
        // Clean up previous temporary files
const QString meshfilename = exelPath + "/../temp/mesh.vtk";
if (QFile::exists(meshfilename))
QFile::remove(meshfilename);

const QString geofilename = exelPath + "/../temp/geometry.brep";
if (QFile::exists(geofilename))
QFile::remove(geofilename);

const QString gmshfilename = exelPath + "/../temp/gmsh.Geo";
if (QFile::exists(gmshfilename))
QFile::remove(gmshfilename);

const QString tempPath = tempDir + QString("geometry.brep");

if (_fluidMesh)
_fluidMeshProcess->mergeFluidField(_compounnd, _solidHash);
else if (_selectall)
mergeAllGeo(); // Combine the elements that need to be meshed into a compounnd object
else if (_selectvisible)
mergeVisibleGeo();
else
mergeSelectGeo();
QByteArray arr = tempPath.toLatin1();
BRepTools::Write(*_compounnd, arr.data()); // Use BRepTools to write the geometry.brep file
}

void GmshThread::initGmshEnvoirment()
{<!-- -->
QString exelPath = QCoreApplication::applicationDirPath();
const QString tempDir = exelPath + "/../temp/";
// const QString gmshDir = exelPath + "/gmsh/";
QFile::remove(tempDir + "gmsh.Geo");

_scriptWriter->setCompound(_compounnd);
setGmshScriptData();

if (_fluidMesh)
{<!-- -->
QList<int> curve = _fluidMeshProcess->getInerMember(1);
QList<int> surface = _fluidMeshProcess->getInerMember(2);
_scriptWriter->writeFluidMeshScript(tempDir, _solidHash, curve, surface);
}
else
_scriptWriter->writeGmshScript(tempDir); //Write out the gmsh control file
}

void GmshThread::generate()
{<!-- -->
QString exelPath = QCoreApplication::applicationDirPath();
const QString tempDir = exelPath + "/../temp/";
// const QString gmshDir = exelPath + "/gmsh/";
QString gmshexe = exelPath + "/gmsh";

bool ok = false;
#ifdef Q_OS_WIN
ok = QFile::exists(gmshexe + ".exe");
#endif
#ifdef Q_OS_LINUX
ok = QFile::exists(gmshexe);
#endif
if (!ok)
{<!-- -->
QMessageBox::warning(_mainwindow, QString(tr("Warning")), QString(tr("Gmsh is not exist !")));
return;
}

        // Call gmsh to generate grid
QString startProcess = QString("%1 %2 -format vtk -bin -o %3 -%4").arg(gmshexe).arg(tempDir + "gmsh.Geo").arg(tempDir + \ "mesh.vtk").arg(_dim);

if (gmshexe.contains(" "))
startProcess = QString(""%1"").arg(startProcess);
qDebug() << startProcess;

_process.start(startProcess);
}

The last gmsh command called is roughly as follows, generating the mesh.vtk grid file.
C:/workspace/FastCAE/build/Debug/gmsh C:/workspace/FastCAE/build/Debug/…/temp/gmsh.Geo -format vtk -bin -o C:/workspace/FastCAE/build/Debug/…/ temp/mesh.vtk -3

3. Reading grid files

The code for reading the mesh file is in the GmshThread::readMesh function. Note that the gmsh thread has ended when this code is executed.

void GmshThread::readMesh()
{<!-- -->
MeshData::MeshData *data = MeshData::MeshData::getInstance();
QString exelPath = QCoreApplication::applicationDirPath();
const QString fileName = exelPath + "/../temp/mesh.vtk";
QTextCodec *codec = QTextCodec::codecForName("GB18030");
QByteArray ba = codec->fromUnicode(fileName);
vtkSmartPointer<vtkDataSetReader> vtkReader = vtkSmartPointer<vtkDataSetReader>::New();
vtkReader->SetFileName(ba);
vtkReader->Update();
vtkDataSet *dataset = vtkReader->GetOutput();
if (dataset == nullptr)
return;
if (!_isSaveToKernal)
emit writeToSolveFileSig(dataset);
else
{<!-- -->
auto k = new MeshData::MeshKernal();
k->setName(QString("Mesh_%1").arg(k->getID()));
k->setMeshData(dataset);
data->appendMeshKernal(k);
if (!_fluidMesh)
setGmshSettingData(k);
emit _gmshModule->updateMeshTree();
emit _gmshModule->updateSetTree();
// emit _preWindow->updateMeshActorSig();
emit _gmshModule->updateActions();
emit updateMeshActor();
}
}

Since gmsh outputs vtk files, vtkDataSetReader is used to read them directly.