1
/***************************************************************************
2
* Copyright (c) 2020 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
25
#include <QElapsedTimer>
31
#include <App/Application.h>
32
#include <App/Document.h>
33
#include <App/DocumentObserver.h>
34
#include <Base/Console.h>
35
#include <Base/FileInfo.h>
36
#include <Base/Stream.h>
37
#include <Gui/ReportView.h>
38
#include <Mod/Mesh/App/MeshFeature.h>
40
#include "RemeshGmsh.h"
41
#include "ui_RemeshGmsh.h"
44
using namespace MeshGui;
46
class GmshWidget::Private
49
explicit Private(QWidget* parent)
52
/* coverity[uninit_ctor] Members of ui are set in setupUI() */
55
void appendText(const QString& text, bool error)
57
syntax->setParagraphType(error ? Gui::DockWnd::ReportHighlighter::Error
58
: Gui::DockWnd::ReportHighlighter::Message);
59
QTextCursor cursor(ui.outputWindow->document());
60
cursor.beginEditBlock();
61
cursor.movePosition(QTextCursor::End);
62
cursor.insertText(text);
63
cursor.endEditBlock();
64
ui.outputWindow->ensureCursorVisible();
69
QPointer<Gui::StatusWidget> label;
70
QPointer<Gui::DockWnd::ReportHighlighter> syntax;
75
GmshWidget::GmshWidget(QWidget* parent, Qt::WindowFlags fl)
77
, d(new Private(parent))
81
d->ui.fileChooser->onRestore();
82
d->syntax = new Gui::DockWnd::ReportHighlighter(d->ui.outputWindow);
83
d->ui.outputWindow->setReadOnly(true);
85
// 2D Meshing algorithms
86
// https://gmsh.info/doc/texinfo/gmsh.html#index-Mesh_002eAlgorithm
94
FrontalDelaunayForQuads = 8,
95
PackingOfParallelograms = 9
98
d->ui.method->addItem(tr("Automatic"), static_cast<int>(Automatic));
99
d->ui.method->addItem(tr("Adaptive"), static_cast<int>(MeshAdapt));
100
d->ui.method->addItem(QString::fromLatin1("Delaunay"), static_cast<int>(Delaunay));
101
d->ui.method->addItem(tr("Frontal"), static_cast<int>(FrontalDelaunay));
102
d->ui.method->addItem(QString::fromLatin1("BAMG"), static_cast<int>(BAMG));
103
d->ui.method->addItem(tr("Frontal Quad"), static_cast<int>(FrontalDelaunayForQuads));
104
d->ui.method->addItem(tr("Parallelograms"), static_cast<int>(PackingOfParallelograms));
107
GmshWidget::~GmshWidget()
109
d->ui.fileChooser->onSave();
112
void GmshWidget::setupConnections()
115
connect(&d->gmsh, &QProcess::started, this, &GmshWidget::started);
116
connect(&d->gmsh, qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
117
this, &GmshWidget::finished);
118
connect(&d->gmsh, &QProcess::errorOccurred,
119
this, &GmshWidget::errorOccurred);
120
connect(&d->gmsh, &QProcess::readyReadStandardError,
121
this, &GmshWidget::readyReadStandardError);
122
connect(&d->gmsh, &QProcess::readyReadStandardOutput,
123
this, &GmshWidget::readyReadStandardOutput);
124
connect(d->ui.killButton, &QPushButton::clicked,
125
this, &GmshWidget::onKillButtonClicked);
126
connect(d->ui.clearButton, &QPushButton::clicked,
127
this, &GmshWidget::onClearButtonClicked);
131
void GmshWidget::changeEvent(QEvent* e)
133
if (e->type() == QEvent::LanguageChange) {
134
d->ui.retranslateUi(this);
136
QWidget::changeEvent(e);
139
bool GmshWidget::writeProject(QString& inpFile, QString& outFile)
147
bool GmshWidget::loadOutput()
152
int GmshWidget::meshingAlgorithm() const
154
return d->ui.method->itemData(d->ui.method->currentIndex()).toInt();
157
double GmshWidget::getAngle() const
159
return d->ui.angle->value().getValue();
162
double GmshWidget::getMaxSize() const
164
return d->ui.maxSize->value().getValue();
167
double GmshWidget::getMinSize() const
169
return d->ui.minSize->value().getValue();
172
void GmshWidget::accept()
174
if (d->gmsh.state() == QProcess::Running) {
175
Base::Console().Warning("Cannot start gmsh because it's already running\n");
182
if (writeProject(inpFile, outFile)) {
183
// ./gmsh - -bin -2 /tmp/mesh.geo -o /tmp/best.stl
184
QString proc = d->ui.fileChooser->fileName();
186
args << QLatin1String("-")
187
<< QLatin1String("-bin")
188
<< QLatin1String("-2")
190
<< QLatin1String("-o")
192
d->gmsh.start(proc, args);
195
d->ui.labelTime->setText(tr("Time:"));
200
void GmshWidget::readyReadStandardError()
202
QByteArray msg = d->gmsh.readAllStandardError();
203
if (msg.startsWith("\0[1m\0[31m")) {
206
if (msg.endsWith("\0[0m")) {
210
QString text = QString::fromUtf8(msg.data());
211
d->appendText(text, true);
214
void GmshWidget::readyReadStandardOutput()
216
QByteArray msg = d->gmsh.readAllStandardOutput();
217
QString text = QString::fromUtf8(msg.data());
218
d->appendText(text, false);
221
void GmshWidget::onKillButtonClicked()
223
if (d->gmsh.state() == QProcess::Running) {
225
d->gmsh.waitForFinished(1000);
226
d->ui.killButton->setDisabled(true);
230
void GmshWidget::onClearButtonClicked()
232
d->ui.outputWindow->clear();
235
void GmshWidget::started()
237
d->ui.killButton->setEnabled(true);
239
d->label = new Gui::StatusWidget(this);
240
d->label->setAttribute(Qt::WA_DeleteOnClose);
241
d->label->setStatusText(tr("Running gmsh..."));
246
void GmshWidget::finished(int /*exitCode*/, QProcess::ExitStatus exitStatus)
248
d->ui.killButton->setDisabled(true);
253
d->ui.labelTime->setText(
254
QString::fromLatin1("%1 %2 ms").arg(tr("Time:")).arg(d->time.elapsed()));
255
if (exitStatus == QProcess::NormalExit) {
260
void GmshWidget::errorOccurred(QProcess::ProcessError error)
264
case QProcess::FailedToStart:
265
msg = tr("Failed to start");
271
if (!msg.isEmpty()) {
272
QMessageBox::warning(this, tr("Error"), msg);
276
void GmshWidget::reject()
278
onKillButtonClicked();
281
// -------------------------------------------------
283
class RemeshGmsh::Private
286
explicit Private(Mesh::Feature* mesh)
291
App::DocumentObjectWeakPtrT mesh;
292
MeshCore::MeshKernel copy;
297
RemeshGmsh::RemeshGmsh(Mesh::Feature* mesh, QWidget* parent, Qt::WindowFlags fl)
298
: GmshWidget(parent, fl)
299
, d(new Private(mesh))
301
// Copy mesh that is used each time when applying Gmsh's remeshing function
302
d->copy = mesh->Mesh.getValue().getKernel();
303
d->stlFile = App::Application::getTempFileName() + "mesh.stl";
304
d->geoFile = App::Application::getTempFileName() + "mesh.geo";
307
RemeshGmsh::~RemeshGmsh() = default;
309
bool RemeshGmsh::writeProject(QString& inpFile, QString& outFile)
312
if (!d->mesh.expired()) {
313
Base::FileInfo stl(d->stlFile);
314
MeshCore::MeshOutput output(d->copy);
315
Base::ofstream stlOut(stl, std::ios::out | std::ios::binary);
316
output.SaveBinarySTL(stlOut);
320
int algorithm = meshingAlgorithm();
321
double maxSize = getMaxSize();
324
double minSize = getMinSize();
325
double angle = getAngle();
330
Base::FileInfo geo(d->geoFile);
331
Base::ofstream geoOut(geo, std::ios::out);
332
// Examples on how to use Gmsh: https://sfepy.org/doc-devel/preprocessing.html
333
// https://gmsh.info//doc/texinfo/gmsh.html
334
// https://docs.salome-platform.org/latest/gui/GMSHPLUGIN/gmsh_2d_3d_hypo_page.html
335
geoOut << "// geo file for meshing with Gmsh meshing software created by FreeCAD\n"
336
<< "If(GMSH_MAJOR_VERSION < 4)\n"
337
<< " Error(\"Too old Gmsh version %g.%g. At least 4.x is required\", GMSH_MAJOR_VERSION, GMSH_MINOR_VERSION);\n"
340
<< "Merge \"" << stl.filePath() << "\";\n\n"
341
<< "// 2D mesh algorithm (1=MeshAdapt, 2=Automatic, 5=Delaunay, 6=Frontal, 7=BAMG, 8=Frontal Quad, 9=Packing of Parallelograms)\n"
342
<< "Mesh.Algorithm = " << algorithm << ";\n\n"
343
<< "// 3D mesh algorithm (1=Delaunay, 2=New Delaunay, 4=Frontal, 7=MMG3D, 9=R-tree, 10=HTX)\n"
344
<< "// Mesh.Algorithm3D = 1;\n\n"
345
<< "Mesh.CharacteristicLengthMax = " << maxSize << ";\n"
346
<< "Mesh.CharacteristicLengthMin = " << minSize << ";\n\n"
347
<< "// We first classify (\"color\") the surfaces by splitting the original surface\n"
348
<< "// along sharp geometrical features. This will create new discrete surfaces,\n"
349
<< "// curves and points.\n"
350
<< "angle = DefineNumber[" << angle << ", Min " << minAngle << ", Max " << maxAngle << ", Step 1,\n"
351
<< " Name \"Parameters/Angle for surface detection\" ];\n\n"
352
<< "forceParametrizablePatches = DefineNumber[0, Choices{0,1},\n"
353
<< " Name \"Parameters/Create surfaces guaranteed to be parametrizable\"];\n\n"
354
<< "includeBoundary = 1;\n"
355
<< "ClassifySurfaces{angle * Pi/180, includeBoundary, forceParametrizablePatches};\n"
356
<< "// Create a geometry for all the discrete curves and surfaces in the mesh, by\n"
357
<< "// computing a parametrization for each one\n"
358
<< "CreateGeometry;\n\n"
359
<< "// Create a volume as usual\n"
360
<< "Surface Loop(1) = Surface{:};\n"
361
<< "Volume(1) = {1};\n";
364
inpFile = QString::fromUtf8(d->geoFile.c_str());
365
outFile = QString::fromUtf8(d->stlFile.c_str());
374
bool RemeshGmsh::loadOutput()
376
if (d->mesh.expired()) {
380
// Now read-in modified mesh
381
Base::FileInfo stl(d->stlFile);
382
Base::FileInfo geo(d->geoFile);
384
Mesh::MeshObject kernel;
385
MeshCore::MeshInput input(kernel.getKernel());
386
Base::ifstream stlIn(stl, std::ios::in | std::ios::binary);
387
input.LoadBinarySTL(stlIn);
389
kernel.harmonizeNormals();
391
Mesh::Feature* fea = d->mesh.get<Mesh::Feature>();
392
App::Document* doc = fea->getDocument();
393
doc->openTransaction("Remesh");
394
fea->Mesh.setValue(kernel.getKernel());
395
doc->commitTransaction();
402
// -------------------------------------------------
404
/* TRANSLATOR MeshGui::TaskRemeshGmsh */
406
TaskRemeshGmsh::TaskRemeshGmsh(Mesh::Feature* mesh)
408
widget = new RemeshGmsh(mesh);
409
addTaskBox(widget, false);
412
void TaskRemeshGmsh::clicked(int id)
414
if (id == QDialogButtonBox::Apply) {
417
else if (id == QDialogButtonBox::Close) {
422
#include "moc_RemeshGmsh.cpp"