FreeCAD

Форк
0
/
RemeshGmsh.cpp 
422 строки · 13.0 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2020 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
#include <QElapsedTimer>
26
#include <QMessageBox>
27
#include <QPointer>
28
#include <QTextCursor>
29
#endif
30

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>
39

40
#include "RemeshGmsh.h"
41
#include "ui_RemeshGmsh.h"
42

43

44
using namespace MeshGui;
45

46
class GmshWidget::Private
47
{
48
public:
49
    explicit Private(QWidget* parent)
50
        : gmsh(parent)
51
    {
52
        /* coverity[uninit_ctor] Members of ui are set in setupUI() */
53
    }
54

55
    void appendText(const QString& text, bool error)
56
    {
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();
65
    }
66

67
public:
68
    Ui_RemeshGmsh ui {};
69
    QPointer<Gui::StatusWidget> label;
70
    QPointer<Gui::DockWnd::ReportHighlighter> syntax;
71
    QProcess gmsh;
72
    QElapsedTimer time;
73
};
74

75
GmshWidget::GmshWidget(QWidget* parent, Qt::WindowFlags fl)
76
    : QWidget(parent, fl)
77
    , d(new Private(parent))
78
{
79
    d->ui.setupUi(this);
80
    setupConnections();
81
    d->ui.fileChooser->onRestore();
82
    d->syntax = new Gui::DockWnd::ReportHighlighter(d->ui.outputWindow);
83
    d->ui.outputWindow->setReadOnly(true);
84

85
    // 2D Meshing algorithms
86
    // https://gmsh.info/doc/texinfo/gmsh.html#index-Mesh_002eAlgorithm
87
    enum
88
    {
89
        MeshAdapt = 1,
90
        Automatic = 2,
91
        Delaunay = 5,
92
        FrontalDelaunay = 6,
93
        BAMG = 7,
94
        FrontalDelaunayForQuads = 8,
95
        PackingOfParallelograms = 9
96
    };
97

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));
105
}
106

107
GmshWidget::~GmshWidget()
108
{
109
    d->ui.fileChooser->onSave();
110
}
111

112
void GmshWidget::setupConnections()
113
{
114
    // clang-format off
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);
128
    // clang-format on
129
}
130

131
void GmshWidget::changeEvent(QEvent* e)
132
{
133
    if (e->type() == QEvent::LanguageChange) {
134
        d->ui.retranslateUi(this);
135
    }
136
    QWidget::changeEvent(e);
137
}
138

139
bool GmshWidget::writeProject(QString& inpFile, QString& outFile)
140
{
141
    Q_UNUSED(inpFile)
142
    Q_UNUSED(outFile)
143

144
    return false;
145
}
146

147
bool GmshWidget::loadOutput()
148
{
149
    return false;
150
}
151

152
int GmshWidget::meshingAlgorithm() const
153
{
154
    return d->ui.method->itemData(d->ui.method->currentIndex()).toInt();
155
}
156

157
double GmshWidget::getAngle() const
158
{
159
    return d->ui.angle->value().getValue();
160
}
161

162
double GmshWidget::getMaxSize() const
163
{
164
    return d->ui.maxSize->value().getValue();
165
}
166

167
double GmshWidget::getMinSize() const
168
{
169
    return d->ui.minSize->value().getValue();
170
}
171

172
void GmshWidget::accept()
173
{
174
    if (d->gmsh.state() == QProcess::Running) {
175
        Base::Console().Warning("Cannot start gmsh because it's already running\n");
176
        return;
177
    }
178

179
    // clang-format off
180
    QString inpFile;
181
    QString outFile;
182
    if (writeProject(inpFile, outFile)) {
183
        // ./gmsh - -bin -2 /tmp/mesh.geo -o /tmp/best.stl
184
        QString proc = d->ui.fileChooser->fileName();
185
        QStringList args;
186
        args << QLatin1String("-")
187
             << QLatin1String("-bin")
188
             << QLatin1String("-2")
189
             << inpFile
190
             << QLatin1String("-o")
191
             << outFile;
192
        d->gmsh.start(proc, args);
193

194
        d->time.start();
195
        d->ui.labelTime->setText(tr("Time:"));
196
    }
197
    // clang-format on
198
}
199

200
void GmshWidget::readyReadStandardError()
201
{
202
    QByteArray msg = d->gmsh.readAllStandardError();
203
    if (msg.startsWith("\0[1m\0[31m")) {
204
        msg = msg.mid(9);
205
    }
206
    if (msg.endsWith("\0[0m")) {
207
        msg.chop(5);
208
    }
209

210
    QString text = QString::fromUtf8(msg.data());
211
    d->appendText(text, true);
212
}
213

214
void GmshWidget::readyReadStandardOutput()
215
{
216
    QByteArray msg = d->gmsh.readAllStandardOutput();
217
    QString text = QString::fromUtf8(msg.data());
218
    d->appendText(text, false);
219
}
220

221
void GmshWidget::onKillButtonClicked()
222
{
223
    if (d->gmsh.state() == QProcess::Running) {
224
        d->gmsh.kill();
225
        d->gmsh.waitForFinished(1000);
226
        d->ui.killButton->setDisabled(true);
227
    }
228
}
229

230
void GmshWidget::onClearButtonClicked()
231
{
232
    d->ui.outputWindow->clear();
233
}
234

235
void GmshWidget::started()
236
{
237
    d->ui.killButton->setEnabled(true);
238
    if (!d->label) {
239
        d->label = new Gui::StatusWidget(this);
240
        d->label->setAttribute(Qt::WA_DeleteOnClose);
241
        d->label->setStatusText(tr("Running gmsh..."));
242
        d->label->show();
243
    }
244
}
245

246
void GmshWidget::finished(int /*exitCode*/, QProcess::ExitStatus exitStatus)
247
{
248
    d->ui.killButton->setDisabled(true);
249
    if (d->label) {
250
        d->label->close();
251
    }
252

253
    d->ui.labelTime->setText(
254
        QString::fromLatin1("%1 %2 ms").arg(tr("Time:")).arg(d->time.elapsed()));
255
    if (exitStatus == QProcess::NormalExit) {
256
        loadOutput();
257
    }
258
}
259

260
void GmshWidget::errorOccurred(QProcess::ProcessError error)
261
{
262
    QString msg;
263
    switch (error) {
264
        case QProcess::FailedToStart:
265
            msg = tr("Failed to start");
266
            break;
267
        default:
268
            break;
269
    }
270

271
    if (!msg.isEmpty()) {
272
        QMessageBox::warning(this, tr("Error"), msg);
273
    }
274
}
275

276
void GmshWidget::reject()
277
{
278
    onKillButtonClicked();
279
}
280

281
// -------------------------------------------------
282

283
class RemeshGmsh::Private
284
{
285
public:
286
    explicit Private(Mesh::Feature* mesh)
287
        : mesh(mesh)
288
    {}
289

290
public:
291
    App::DocumentObjectWeakPtrT mesh;
292
    MeshCore::MeshKernel copy;
293
    std::string stlFile;
294
    std::string geoFile;
295
};
296

297
RemeshGmsh::RemeshGmsh(Mesh::Feature* mesh, QWidget* parent, Qt::WindowFlags fl)
298
    : GmshWidget(parent, fl)
299
    , d(new Private(mesh))
300
{
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";
305
}
306

307
RemeshGmsh::~RemeshGmsh() = default;
308

309
bool RemeshGmsh::writeProject(QString& inpFile, QString& outFile)
310
{
311
    // clang-format off
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);
317
        stlOut.close();
318

319
        // Parameters
320
        int algorithm = meshingAlgorithm();
321
        double maxSize = getMaxSize();
322
        if (maxSize == 0.0)
323
            maxSize = 1.0e22;
324
        double minSize = getMinSize();
325
        double angle = getAngle();
326
        int maxAngle = 120;
327
        int minAngle = 20;
328

329
        // Gmsh geo file
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"
338
            << "   Exit;\n"
339
            << "EndIf\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";
362
        geoOut.close();
363

364
        inpFile = QString::fromUtf8(d->geoFile.c_str());
365
        outFile = QString::fromUtf8(d->stlFile.c_str());
366

367
        return true;
368
    }
369

370
    return false;
371
    // clang-format on
372
}
373

374
bool RemeshGmsh::loadOutput()
375
{
376
    if (d->mesh.expired()) {
377
        return false;
378
    }
379

380
    // Now read-in modified mesh
381
    Base::FileInfo stl(d->stlFile);
382
    Base::FileInfo geo(d->geoFile);
383

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);
388
    stlIn.close();
389
    kernel.harmonizeNormals();
390

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();
396
    stl.deleteFile();
397
    geo.deleteFile();
398

399
    return true;
400
}
401

402
// -------------------------------------------------
403

404
/* TRANSLATOR MeshGui::TaskRemeshGmsh */
405

406
TaskRemeshGmsh::TaskRemeshGmsh(Mesh::Feature* mesh)
407
{
408
    widget = new RemeshGmsh(mesh);
409
    addTaskBox(widget, false);
410
}
411

412
void TaskRemeshGmsh::clicked(int id)
413
{
414
    if (id == QDialogButtonBox::Apply) {
415
        widget->accept();
416
    }
417
    else if (id == QDialogButtonBox::Close) {
418
        widget->reject();
419
    }
420
}
421

422
#include "moc_RemeshGmsh.cpp"
423

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.