Mesh: add gmsh for remeshing a mesh created from CAD

This commit is contained in:
wmayer
2020-02-20 18:20:11 +01:00
parent ae30bb4cb9
commit a2adf2110b
6 changed files with 726 additions and 0 deletions

View File

@@ -37,6 +37,7 @@ set(Mesh_MOC_HDRS
MeshEditor.h
PropertyEditorMesh.h
RemoveComponents.h
RemeshGmsh.h
SegmentationBestFit.h
Selection.h
)
@@ -51,6 +52,7 @@ set(Dialogs_UIC_SRCS
DlgSettingsImportExport.ui
DlgSmoothing.ui
RemoveComponents.ui
RemeshGmsh.ui
Segmentation.ui
SegmentationBestFit.ui
Selection.ui
@@ -79,6 +81,9 @@ SET(Dialogs_SRCS
RemoveComponents.ui
RemoveComponents.cpp
RemoveComponents.h
RemeshGmsh.ui
RemeshGmsh.cpp
RemeshGmsh.h
Segmentation.ui
Segmentation.cpp
Segmentation.h

View File

@@ -72,6 +72,7 @@
#include "DlgEvaluateMeshImp.h"
#include "DlgRegularSolidImp.h"
#include "RemoveComponents.h"
#include "RemeshGmsh.h"
#include "DlgSmoothing.h"
#include "ViewProviderMeshFaceSet.h"
#include "ViewProviderCurvature.h"
@@ -1261,6 +1262,39 @@ bool CmdMeshRemoveComponents::isActive(void)
//--------------------------------------------------------------------------------------
DEF_STD_CMD_A(CmdMeshRemeshGmsh)
CmdMeshRemeshGmsh::CmdMeshRemeshGmsh()
: Command("Mesh_RemeshGmsh")
{
sAppModule = "Mesh";
sGroup = QT_TR_NOOP("Mesh");
sMenuText = QT_TR_NOOP("Refinement...");
sToolTipText = QT_TR_NOOP("Refine existing mesh");
sStatusTip = QT_TR_NOOP("Refine existing mesh");
sWhatsThis = "Mesh_RemeshGmsh";
//sPixmap = "Mesh_RemeshGmsh";
}
void CmdMeshRemeshGmsh::activated(int)
{
Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
if (!dlg) {
std::vector<Mesh::Feature*> mesh = getSelection().getObjectsOfType<Mesh::Feature>();
if (mesh.size() != 1)
return;
dlg = new MeshGui::TaskRemeshGmsh(mesh.front());
}
Gui::Control().showDialog(dlg);
}
bool CmdMeshRemeshGmsh::isActive(void)
{
return getSelection().countObjectsOfType(Mesh::Feature::getClassTypeId()) == 1;
}
//--------------------------------------------------------------------------------------
DEF_STD_CMD_A(CmdMeshRemoveCompByHand)
CmdMeshRemoveCompByHand::CmdMeshRemoveCompByHand()
@@ -1812,6 +1846,7 @@ void CreateMeshCommands(void)
rcCmdMgr.addCommand(new CmdMeshBuildRegularSolid());
rcCmdMgr.addCommand(new CmdMeshFillupHoles());
rcCmdMgr.addCommand(new CmdMeshRemoveComponents());
rcCmdMgr.addCommand(new CmdMeshRemeshGmsh());
rcCmdMgr.addCommand(new CmdMeshFillInteractiveHole());
rcCmdMgr.addCommand(new CmdMeshRemoveCompByHand());
rcCmdMgr.addCommand(new CmdMeshFromGeometry());

View File

@@ -0,0 +1,357 @@
/***************************************************************************
* Copyright (c) 2020 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QMessageBox>
# include <QPushButton>
# include <QTextCursor>
# include <QTime>
#endif
#include "RemeshGmsh.h"
#include "ui_RemeshGmsh.h"
#include <Base/FileInfo.h>
#include <Base/Tools.h>
#include <App/Application.h>
#include <Gui/Application.h>
#include <Gui/Document.h>
#include <Gui/Widgets.h>
#include <Gui/ReportView.h>
#include <Mod/Mesh/App/Mesh.h>
#include <Mod/Mesh/App/MeshFeature.h>
#include <Mod/Mesh/App/Core/MeshIO.h>
using namespace MeshGui;
class RemeshGmsh::Private {
public:
Private(Mesh::Feature* mesh, QWidget* parent)
: mesh(mesh)
, mesher(parent)
{
}
void appendText(const QString& text, bool error) {
syntax->setParagraphType(error ? Gui::DockWnd::ReportHighlighter::Error
: Gui::DockWnd::ReportHighlighter::Message);
QTextCursor cursor(ui.outputWindow->document());
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::End);
cursor.insertText(text);
cursor.endEditBlock();
ui.outputWindow->ensureCursorVisible();
}
public:
Ui_RemeshGmsh ui;
QPointer<Gui::StatusWidget> label;
QPointer<Gui::DockWnd::ReportHighlighter> syntax;
App::DocumentObjectWeakPtrT mesh;
MeshCore::MeshKernel copy;
QProcess mesher;
QTime time;
std::string stlFile;
std::string geoFile;
};
RemeshGmsh::RemeshGmsh(Mesh::Feature* mesh, QWidget* parent, Qt::WindowFlags fl)
: QWidget(parent, fl)
, d(new Private(mesh, parent))
{
connect(&d->mesher, SIGNAL(started()), this, SLOT(started()));
connect(&d->mesher, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(finished(int, QProcess::ExitStatus)));
connect(&d->mesher, SIGNAL(errorOccurred(QProcess::ProcessError)),
this, SLOT(errorOccurred(QProcess::ProcessError)));
connect(&d->mesher, SIGNAL(readyReadStandardError()),
this, SLOT(readyReadStandardError()));
connect(&d->mesher, SIGNAL(readyReadStandardOutput()),
this, SLOT(readyReadStandardOutput()));
// Copy mesh that is used each time when applying gmsh's remeshing function
d->copy = mesh->Mesh.getValue().getKernel();
d->ui.setupUi(this);
d->ui.fileChooser->onRestore();
d->syntax = new Gui::DockWnd::ReportHighlighter(d->ui.outputWindow);
d->ui.outputWindow->setReadOnly(true);
// Meshing algorithms
// 1=MeshAdapt, 2=Automatic, 5=Delaunay, 6=Frontal, 7=BAMG, 8=Frontal Quad
// 9=Packing of Parallelograms
d->ui.method->addItem(tr("Automatic"), static_cast<int>(2));
d->ui.method->addItem(tr("Adaptive"), static_cast<int>(1));
d->ui.method->addItem(QString::fromLatin1("Delaunay"), static_cast<int>(5));
d->ui.method->addItem(tr("Frontal"), static_cast<int>(6));
d->ui.method->addItem(QString::fromLatin1("BAMG"), static_cast<int>(5));
d->ui.method->addItem(tr("Frontal Quad"), static_cast<int>(6));
d->ui.method->addItem(tr("Parallelograms"), static_cast<int>(9));
d->stlFile = App::Application::getTempFileName() + "mesh.stl";
d->geoFile = App::Application::getTempFileName() + "mesh.geo";
}
RemeshGmsh::~RemeshGmsh()
{
d->ui.fileChooser->onSave();
}
void RemeshGmsh::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
d->ui.retranslateUi(this);
}
QWidget::changeEvent(e);
}
#if 0 // this is for meshing a CAD shape see gmshtools.py write_geo
// geo file for meshing with Gmsh meshing software created by FreeCAD
// open brep geometry
Merge "/tmp/fcfem_f1enjjfa/Part__Feature_Geometry.brep";
// Characteristic Length
// no boundary layer settings for this mesh
// min, max Characteristic Length
Mesh.CharacteristicLengthMax = 1e+22;
Mesh.CharacteristicLengthMin = 0.0;
// optimize the mesh
Mesh.Optimize = 1;
Mesh.OptimizeNetgen = 0;
Mesh.HighOrderOptimize = 0; // for more HighOrderOptimize parameter check http://gmsh.info/doc/texinfo/gmsh.html
// mesh order
Mesh.ElementOrder = 2;
Mesh.SecondOrderLinear = 1; // Second order nodes are created by linear interpolation instead by curvilinear
// mesh algorithm, only a few algorithms are usable with 3D boundary layer generation
// 2D mesh algorithm (1=MeshAdapt, 2=Automatic, 5=Delaunay, 6=Frontal, 7=BAMG, 8=DelQuad)
Mesh.Algorithm = 2;
// 3D mesh algorithm (1=Delaunay, 2=New Delaunay, 4=Frontal, 5=Frontal Delaunay, 6=Frontal Hex, 7=MMG3D, 9=R-tree)
Mesh.Algorithm3D = 1;
// meshing
Geometry.Tolerance = 1e-06; // set geometrical tolerance (also used for merging nodes)
Mesh 2;
Coherence Mesh; // Remove duplicate vertices
#endif
void RemeshGmsh::accept()
{
if (!d->mesh.expired()) {
Base::FileInfo stl(d->stlFile);
MeshCore::MeshOutput output(d->copy);
Base::ofstream stlOut(stl, std::ios::out | std::ios::binary);
output.SaveBinarySTL(stlOut);
stlOut.close();
// Parameters
int algorithm = d->ui.method->itemData(d->ui.method->currentIndex()).toInt();
double maxSize = d->ui.maxSize->value().getValue();
if (maxSize == 0.0)
maxSize = 1.0e22;
double minSize = d->ui.minSize->value().getValue();
double angle = d->ui.angle->value().getValue();
int maxAngle = 120;
int minAngle = 20;
// gmsh geo file
Base::FileInfo geo(d->geoFile);
Base::ofstream geoOut(geo, std::ios::out);
// Examples on how to use gmsh: https://sfepy.org/doc-devel/preprocessing.html
// http://gmsh.info//doc/texinfo/gmsh.html
// https://docs.salome-platform.org/latest/gui/GMSHPLUGIN/gmsh_2d_3d_hypo_page.html
geoOut << "// geo file for meshing with Gmsh meshing software created by FreeCAD\n"
<< "If(GMSH_MAJOR_VERSION < 4)\n"
<< " Error(\"Too old gmsh version %g.%g. At least 4.x is required\", GMSH_MAJOR_VERSION, GMSH_MINOR_VERSION);\n"
<< " Exit;\n"
<< "EndIf\n"
<< "Merge \"" << stl.filePath() << "\";\n\n"
<< "// 2D mesh algorithm (1=MeshAdapt, 2=Automatic, 5=Delaunay, 6=Frontal, 7=BAMG, 8=Frontal Quad)\n"
<< "Mesh.Algorithm = " << algorithm << ";\n\n"
<< "// 3D mesh algorithm (1=Delaunay, 2=New Delaunay, 4=Frontal, 5=Frontal Delaunay, 6=Frontal Hex, 7=MMG3D, 9=R-tree)\n"
<< "// Mesh.Algorithm3D = 1;\n\n"
<< "Mesh.CharacteristicLengthMax = " << maxSize << ";\n"
<< "Mesh.CharacteristicLengthMin = " << minSize << ";\n\n"
<< "// We first classify (\"color\") the surfaces by splitting the original surface\n"
<< "// along sharp geometrical features. This will create new discrete surfaces,\n"
<< "// curves and points.\n"
<< "angle = DefineNumber[" << angle << ", Min " << minAngle << ", Max " << maxAngle << ", Step 1,\n"
<< " Name \"Parameters/Angle for surface detection\" ];\n\n"
<< "forceParametrizablePatches = DefineNumber[0, Choices{0,1},\n"
<< " Name \"Parameters/Create surfaces guaranteed to be parametrizable\"];\n\n"
<< "includeBoundary = 1;\n"
<< "ClassifySurfaces{angle * Pi/180, includeBoundary, forceParametrizablePatches};\n"
<< "// Create a geometry for all the discrete curves and surfaces in the mesh, by\n"
<< "// computing a parametrization for each one\n"
<< "CreateGeometry;\n\n"
<< "// Create a volume as usual\n"
<< "Surface Loop(1) = Surface{:};\n"
<< "Volume(1) = {1};\n";
geoOut.close();
// ./gmsh - -bin -2 /tmp/mesh.geo -o /tmp/best.stl
QString proc = d->ui.fileChooser->fileName();
QStringList args;
args << QLatin1String("-")
<< QLatin1String("-bin")
<< QLatin1String("-2")
<< QString::fromUtf8(d->geoFile.c_str())
<< QLatin1String("-o")
<< QString::fromUtf8(d->stlFile.c_str());
d->mesher.start(proc, args);
d->time.start();
d->ui.labelTime->setText(tr("Time:"));
}
}
void RemeshGmsh::readyReadStandardError()
{
QByteArray msg = d->mesher.readAllStandardError();
if (msg.startsWith("\0[1m\0[31m")) {
msg = msg.mid(9);
}
if (msg.endsWith("\0[0m")) {
msg.chop(5);
}
QString text = QString::fromUtf8(msg.data());
d->appendText(text, true);
}
void RemeshGmsh::readyReadStandardOutput()
{
QByteArray msg = d->mesher.readAllStandardOutput();
QString text = QString::fromUtf8(msg.data());
d->appendText(text, false);
}
void RemeshGmsh::on_killButton_clicked()
{
if (d->mesher.state() == QProcess::Running) {
d->mesher.kill();
d->mesher.waitForFinished(1000);
d->ui.killButton->setDisabled(true);
}
}
void RemeshGmsh::on_clearButton_clicked()
{
d->ui.outputWindow->clear();
}
void RemeshGmsh::started()
{
d->ui.killButton->setEnabled(true);
if (!d->label) {
d->label = new Gui::StatusWidget(this);
d->label->setAttribute(Qt::WA_DeleteOnClose);
d->label->setStatusText(tr("Running remeshing..."));
d->label->show();
}
}
void RemeshGmsh::finished(int /*exitCode*/, QProcess::ExitStatus exitStatus)
{
d->ui.killButton->setDisabled(true);
if (d->label)
d->label->close();
d->ui.labelTime->setText(QString::fromLatin1("%1 %2 ms").arg(tr("Time:")).arg(d->time.elapsed()));
if (exitStatus == QProcess::NormalExit) {
if (!d->mesh.expired()) {
// Now read-in modified mesh
Base::FileInfo stl(d->stlFile);
Base::FileInfo geo(d->geoFile);
Mesh::MeshObject kernel;
MeshCore::MeshInput input(kernel.getKernel());
Base::ifstream stlIn(stl, std::ios::in | std::ios::binary);
input.LoadBinarySTL(stlIn);
stlIn.close();
kernel.harmonizeNormals();
Mesh::Feature* fea = d->mesh.get<Mesh::Feature>();
App::Document* doc = fea->getDocument();
doc->openTransaction("Remesh");
fea->Mesh.setValue(kernel.getKernel());
doc->commitTransaction();
stl.deleteFile();
geo.deleteFile();
}
}
}
void RemeshGmsh::errorOccurred(QProcess::ProcessError error)
{
QString msg;
switch (error) {
case QProcess::FailedToStart:
msg = tr("Failed to start");
break;
default:
break;
}
if (!msg.isEmpty()) {
QMessageBox::warning(this, tr("Error"), msg);
}
}
void RemeshGmsh::reject()
{
on_killButton_clicked();
}
// -------------------------------------------------
/* TRANSLATOR MeshGui::TaskRemeshGmsh */
TaskRemeshGmsh::TaskRemeshGmsh(Mesh::Feature* mesh)
{
widget = new RemeshGmsh(mesh);
taskbox = new Gui::TaskView::TaskBox(
QPixmap(), widget->windowTitle(), false, 0);
taskbox->groupLayout()->addWidget(widget);
Content.push_back(taskbox);
}
TaskRemeshGmsh::~TaskRemeshGmsh()
{
// automatically deleted in the sub-class
}
void TaskRemeshGmsh::clicked(int id)
{
if (id == QDialogButtonBox::Apply) {
widget->accept();
}
else if (id == QDialogButtonBox::Close) {
widget->reject();
}
}
#include "moc_RemeshGmsh.cpp"

View File

@@ -0,0 +1,104 @@
/***************************************************************************
* Copyright (c) 2020 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef MESHGUI_REMESHGMSH_H
#define MESHGUI_REMESHGMSH_H
#include <memory>
#include <QDialog>
#include <QPointer>
#include <QProcess>
#include <App/DocumentObserver.h>
#include <Gui/TaskView/TaskDialog.h>
#include <Gui/TaskView/TaskView.h>
#include <Mod/Mesh/App/Core/MeshKernel.h>
namespace Mesh {
class Feature;
}
namespace Gui {
class StatusWidget;
}
namespace MeshGui {
/**
* Non-modal dialog to remesh an existing mesh.
* @author Werner Mayer
*/
class MeshGuiExport RemeshGmsh : public QWidget
{
Q_OBJECT
public:
RemeshGmsh(Mesh::Feature* mesh, QWidget* parent = 0, Qt::WindowFlags fl = 0);
~RemeshGmsh();
void accept();
void reject();
protected:
void changeEvent(QEvent *e);
private Q_SLOTS:
void started();
void finished(int exitCode, QProcess::ExitStatus exitStatus);
void errorOccurred(QProcess::ProcessError error);
void on_killButton_clicked();
void on_clearButton_clicked();
void readyReadStandardError();
void readyReadStandardOutput();
private:
class Private;
std::unique_ptr<Private> d;
};
/**
* Embed the panel into a task dialog.
*/
class TaskRemeshGmsh : public Gui::TaskView::TaskDialog
{
Q_OBJECT
public:
TaskRemeshGmsh(Mesh::Feature* mesh);
~TaskRemeshGmsh();
public:
void clicked(int);
virtual QDialogButtonBox::StandardButtons getStandardButtons() const
{ return QDialogButtonBox::Apply | QDialogButtonBox::Close; }
virtual bool isAllowedAlterDocument(void) const
{ return true; }
private:
RemeshGmsh* widget;
Gui::TaskView::TaskBox* taskbox;
};
}
#endif // MESHGUI_REMESHGMSH_H

View File

@@ -0,0 +1,224 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MeshGui::RemeshGmsh</class>
<widget class="QWidget" name="MeshGui::RemeshGmsh">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>458</width>
<height>506</height>
</rect>
</property>
<property name="windowTitle">
<string>Remesh by gmsh</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QGroupBox" name="remeshParam">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>1677215</height>
</size>
</property>
<property name="title">
<string>Remeshing Parameter</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="labelMethod">
<property name="text">
<string>Meshing:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="method"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelMax">
<property name="text">
<string>Max element size (0.0 = Auto):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::QuantitySpinBox" name="maxSize" native="true">
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<property name="minimum" stdset="0">
<double>0.000000000000000</double>
</property>
<property name="maximum" stdset="0">
<double>1000.000000000000000</double>
</property>
<property name="singleStep" stdset="0">
<double>0.100000000000000</double>
</property>
<property name="value" stdset="0">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelMin">
<property name="text">
<string>Min element size (0.0 = Auto):</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::QuantitySpinBox" name="minSize" native="true">
<property name="unit" stdset="0">
<string notr="true">mm</string>
</property>
<property name="minimum" stdset="0">
<double>0.000000000000000</double>
</property>
<property name="maximum" stdset="0">
<double>1000.000000000000000</double>
</property>
<property name="singleStep" stdset="0">
<double>0.100000000000000</double>
</property>
<property name="value" stdset="0">
<double>0.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelAngle">
<property name="text">
<string>Angle:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::QuantitySpinBox" name="angle" native="true">
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
<property name="minimum" stdset="0">
<double>20.000000000000000</double>
</property>
<property name="maximum" stdset="0">
<double>120.000000000000000</double>
</property>
<property name="value" stdset="0">
<double>40.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="gmshOutput">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>1677215</height>
</size>
</property>
<property name="title">
<string>Gmsh</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Path</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefFileChooser" name="fileChooser" native="true">
<property name="prefEntry" stdset="0">
<cstring>gmshExe</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Mesh/Meshing</cstring>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="killButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Kill</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QTextEdit" name="outputWindow">
<property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelTime">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Time:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="clearButton">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::FileChooser</class>
<extends>QWidget</extends>
<header>Gui/FileDialog.h</header>
</customwidget>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefFileChooser</class>
<extends>Gui::FileChooser</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>method</tabstop>
<tabstop>maxSize</tabstop>
<tabstop>minSize</tabstop>
<tabstop>angle</tabstop>
<tabstop>killButton</tabstop>
<tabstop>outputWindow</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -202,6 +202,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const
*mesh << "Mesh_Import"
<< "Mesh_Export"
<< "Mesh_FromPartShape"
<< "Mesh_RemeshGmsh"
<< "Separator"
<< analyze
<< "Mesh_VertexCurvature"