Files
create/src/Mod/MeshPart/Gui/CurveOnMesh.cpp
Markus Reitböck 015896f4e4 MeshPart: use CMake to generate precompiled headers on all platforms
"Professional CMake" book suggest the following:

"Targets should build successfully with or without compiler support for precompiled headers. It
 should be considered an optimization, not a requirement. In particular, do not explicitly include a
 precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically
 generated precompile header on the compiler command line instead. This is more portable across
 the major compilers and is likely to be easier to maintain. It will also avoid warnings being
 generated from certain code checking tools like iwyu (include what you use)."

Therefore, removed the "#include <PreCompiled.h>" from sources, also
there is no need for the "#ifdef _PreComp_" anymore
2025-09-23 22:39:36 +02:00

680 lines
22 KiB
C++

/***************************************************************************
* Copyright (c) 2017 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 <QMenu>
#include <QPointer>
#include <QStatusBar>
#include <QTimer>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakePolygon.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRep_Tool.hxx>
#include <GeomAPI_PointsToBSpline.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Poly_Polygon3D.hxx>
#include <TColgp_Array1OfPnt.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Wire.hxx>
#include <gp_Pnt.hxx>
#include <Inventor/SoPickedPoint.h>
#include <Inventor/details/SoFaceDetail.h>
#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/nodes/SoBaseColor.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoDrawStyle.h>
#include <Inventor/nodes/SoLineSet.h>
#include <Inventor/nodes/SoPointSet.h>
#include <Inventor/nodes/SoSeparator.h>
#include <App/Document.h>
#include <Base/Converter.h>
#include <Gui/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/Utilities.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Mod/Mesh/App/Core/Algorithm.h>
#include <Mod/Mesh/App/Core/Grid.h>
#include <Mod/Mesh/App/Core/MeshKernel.h>
#include <Mod/Mesh/App/Core/Projection.h>
#include <Mod/Mesh/App/MeshFeature.h>
#include <Mod/Mesh/Gui/ViewProvider.h>
#include <Mod/Part/App/PartFeature.h>
#include "CurveOnMesh.h"
#ifndef HAVE_ACOSH
#define HAVE_ACOSH
#endif
#ifndef HAVE_ASINH
#define HAVE_ASINH
#endif
#ifndef HAVE_ATANH
#define HAVE_ATANH
#endif
/* XPM */
// clang-format off
static const char* cursor_curveonmesh[] = {
"32 32 3 1",
"+ c white",
"# c red",
". c None",
"......+.........................",
"......+.........................",
"......+.........................",
"......+.........................",
"......+.........................",
"................................",
"+++++...+++++...................",
"................................",
"......+...............###.......",
"......+...............#.#.......",
"......+...............###.......",
"......+..............#..#.......",
"......+.............#....#......",
"....................#.+..#......",
"..................+#+..+..#...+.",
"................++#.....+.#..+..",
"......+........+..#......++#+...",
".......+......+..#.........#....",
"........++..++..#..........###..",
"..........++....#..........#.#..",
"......#........#...........###..",
".......#......#.................",
"........#.....#.................",
".........#...#..................",
"..........###...................",
"..........#.#...................",
"..........###...................",
"................................",
"................................",
"................................",
"................................",
"................................"};
// clang-format on
using namespace MeshPartGui;
PROPERTY_SOURCE(MeshPartGui::ViewProviderCurveOnMesh, Gui::ViewProviderDocumentObject)
ViewProviderCurveOnMesh::ViewProviderCurveOnMesh()
{
// the lines
pcCoords = new SoCoordinate3;
pcCoords->ref();
pcCoords->point.setNum(0);
pcLinesStyle = new SoDrawStyle;
pcLinesStyle->style = SoDrawStyle::LINES;
pcLinesStyle->lineWidth = 3;
pcLinesStyle->ref();
SoGroup* pcLineRoot = new SoSeparator();
pcLineRoot->addChild(pcLinesStyle);
SoBaseColor* linecol = new SoBaseColor;
linecol->rgb.setValue(1.0f, 1.0f, 0.0f);
pcLineRoot->addChild(linecol);
pcLineRoot->addChild(pcCoords);
pcLineRoot->addChild(new SoLineSet);
// the nodes
pcNodes = new SoCoordinate3;
pcNodes->ref();
pcNodes->point.setNum(0);
pcPointStyle = new SoDrawStyle;
pcPointStyle->style = SoDrawStyle::POINTS;
pcPointStyle->pointSize = 15;
pcPointStyle->ref();
SoGroup* pcPointRoot = new SoSeparator();
pcPointRoot->addChild(pcPointStyle);
SoBaseColor* pointcol = new SoBaseColor;
pointcol->rgb.setValue(1.0f, 0.5f, 0.0f);
pcPointRoot->addChild(pointcol);
pcPointRoot->addChild(pcNodes);
pcPointRoot->addChild(new SoPointSet);
SoGroup* group = new SoGroup;
group->addChild(pcLineRoot);
group->addChild(pcPointRoot);
addDisplayMaskMode(group, "Point");
}
ViewProviderCurveOnMesh::~ViewProviderCurveOnMesh()
{
pcCoords->unref();
pcLinesStyle->unref();
pcNodes->unref();
pcPointStyle->unref();
}
void ViewProviderCurveOnMesh::setDisplayMode(const char* ModeName)
{
setDisplayMaskMode(ModeName);
ViewProviderDocumentObject::setDisplayMode(ModeName);
}
void ViewProviderCurveOnMesh::addVertex(const SbVec3f& v)
{
int num = pcNodes->point.getNum();
pcNodes->point.set1Value(num, v);
}
void ViewProviderCurveOnMesh::clearVertex()
{
pcNodes->point.setNum(0);
}
void ViewProviderCurveOnMesh::setPoints(const std::vector<SbVec3f>& pts)
{
pcCoords->point.setNum(pts.size());
SbVec3f* coords = pcCoords->point.startEditing();
int index = 0;
for (auto it : pts) {
coords[index] = it;
index++;
}
pcCoords->point.finishEditing();
}
void ViewProviderCurveOnMesh::clearPoints()
{
pcCoords->point.setNum(0);
}
// ------------------------------------------------------------------
class CurveOnMeshHandler::Private
{
public:
struct PickedPoint
{
MeshCore::FacetIndex facet;
SbVec3f point;
SbVec3f normal;
};
struct ApproxPar
{
double weight1;
double weight2;
double weight3;
double tol3d;
int maxDegree;
GeomAbs_Shape cont;
ApproxPar()
{
weight1 = 0.2;
weight2 = 0.4;
weight3 = 0.2;
tol3d = 1.0e-2;
maxDegree = 5;
cont = GeomAbs_C2;
}
};
Private()
: curve(new ViewProviderCurveOnMesh)
, editcursor(QPixmap(cursor_curveonmesh), 7, 7)
{}
~Private()
{
delete curve;
delete grid;
}
static void vertexCallback(void* ud, SoEventCallback* n);
std::vector<SbVec3f> convert(const std::vector<Base::Vector3f>& points) const
{
std::vector<SbVec3f> pts;
pts.reserve(points.size());
for (const auto& it : points) {
pts.push_back(Base::convertTo<SbVec3f>(it));
}
return pts;
}
void createGrid()
{
Mesh::Feature* mf = mesh->getObject<Mesh::Feature>();
const Mesh::MeshObject& meshObject = mf->Mesh.getValue();
kernel = meshObject.getKernel();
kernel.Transform(meshObject.getTransform());
MeshCore::MeshAlgorithm alg(kernel);
float fAvgLen = alg.GetAverageEdgeLength();
grid = new MeshCore::MeshFacetGrid(kernel, 5.0f * fAvgLen);
}
bool projectLineOnMesh(const PickedPoint& pick)
{
PickedPoint last = pickedPoints.back();
std::vector<Base::Vector3f> polyline;
MeshCore::MeshProjection meshProjection(kernel);
Base::Vector3f v1 = Base::convertTo<Base::Vector3f>(last.point);
Base::Vector3f v2 = Base::convertTo<Base::Vector3f>(pick.point);
Base::Vector3f vd =
Base::convertTo<Base::Vector3f>(viewer->getViewer()->getViewDirection());
if (meshProjection.projectLineOnMesh(*grid, v1, last.facet, v2, pick.facet, vd, polyline)) {
if (polyline.size() > 1) {
if (cutLines.empty()) {
cutLines.push_back(polyline);
}
else {
SbVec3f dir1(0.0f, 0.0f, 0.0f);
SbVec3f dir2 = pick.point - last.point;
dir2.normalize();
std::size_t num = pickedPoints.size();
if (num >= 2) {
dir1 = pickedPoints[num - 1].point - pickedPoints[num - 2].point;
dir1.normalize();
}
// if the angle between two line segments is greater than the angle
// split the curve in this position
if (dir1.dot(dir2) < cosAngle) {
cutLines.push_back(polyline);
}
else {
std::vector<Base::Vector3f>& segm = cutLines.back();
segm.insert(segm.end(), polyline.begin() + 1, polyline.end());
}
}
return true;
}
}
return false;
}
std::vector<PickedPoint> pickedPoints;
std::list<std::vector<Base::Vector3f>> cutLines;
bool wireClosed {false};
double distance {1};
double cosAngle {0.7071}; // 45 degree
bool approximate {true};
ViewProviderCurveOnMesh* curve;
Gui::ViewProviderDocumentObject* mesh {0};
MeshCore::MeshFacetGrid* grid {nullptr};
MeshCore::MeshKernel kernel;
QPointer<Gui::View3DInventor> viewer;
QCursor editcursor;
ApproxPar par;
};
CurveOnMeshHandler::CurveOnMeshHandler(QObject* parent)
: QObject(parent)
, d_ptr(new Private)
{}
CurveOnMeshHandler::~CurveOnMeshHandler()
{
disableCallback();
}
void CurveOnMeshHandler::enableApproximation(bool on)
{
d_ptr->approximate = on;
}
void CurveOnMeshHandler::setParameters(int maxDegree,
GeomAbs_Shape cont,
double tol3d,
double angle)
{
d_ptr->par.maxDegree = maxDegree;
d_ptr->par.cont = cont;
d_ptr->par.tol3d = tol3d;
d_ptr->cosAngle = cos(angle);
}
void CurveOnMeshHandler::onContextMenu()
{
QMenu menu;
menu.addAction(tr("Create"), this, &CurveOnMeshHandler::onCreate);
if (!d_ptr->wireClosed && d_ptr->pickedPoints.size() >= 3) {
menu.addAction(tr("Close wire"), this, &CurveOnMeshHandler::onCloseWire);
}
menu.addAction(tr("Clear"), this, &CurveOnMeshHandler::onClear);
menu.addAction(tr("Cancel"), this, &CurveOnMeshHandler::onCancel);
menu.exec(QCursor::pos());
}
void CurveOnMeshHandler::onCreate()
{
for (auto it = d_ptr->cutLines.begin(); it != d_ptr->cutLines.end(); ++it) {
std::vector<SbVec3f> segm = d_ptr->convert(*it);
if (d_ptr->approximate) {
Handle(Geom_BSplineCurve) spline = approximateSpline(segm);
if (!spline.IsNull()) {
displaySpline(spline);
}
}
else {
TopoDS_Wire wire;
if (makePolyline(segm, wire)) {
displayPolyline(wire);
}
}
}
d_ptr->curve->clearVertex();
d_ptr->curve->clearPoints();
d_ptr->pickedPoints.clear();
d_ptr->cutLines.clear();
d_ptr->wireClosed = false;
disableCallback();
}
void CurveOnMeshHandler::onCloseWire()
{
if (d_ptr->wireClosed || d_ptr->pickedPoints.size() < 3) {
return;
}
closeWire();
}
void CurveOnMeshHandler::onClear()
{
d_ptr->curve->clearVertex();
d_ptr->curve->clearPoints();
d_ptr->pickedPoints.clear();
d_ptr->cutLines.clear();
d_ptr->wireClosed = false;
}
void CurveOnMeshHandler::onCancel()
{
d_ptr->curve->clearVertex();
d_ptr->curve->clearPoints();
d_ptr->pickedPoints.clear();
d_ptr->cutLines.clear();
d_ptr->wireClosed = false;
disableCallback();
}
void CurveOnMeshHandler::enableCallback(Gui::View3DInventor* v)
{
if (v && !d_ptr->viewer) {
d_ptr->viewer = v;
Gui::View3DInventorViewer* view3d = d_ptr->viewer->getViewer();
view3d->addEventCallback(SoEvent::getClassTypeId(), Private::vertexCallback, this);
view3d->addViewProvider(d_ptr->curve);
view3d->setEditing(true);
view3d->setEditingCursor(d_ptr->editcursor);
d_ptr->curve->setDisplayMode("Point");
}
}
void CurveOnMeshHandler::disableCallback()
{
if (d_ptr->viewer) {
Gui::View3DInventorViewer* view3d = d_ptr->viewer->getViewer();
view3d->setEditing(false);
view3d->removeViewProvider(d_ptr->curve);
view3d->removeEventCallback(SoEvent::getClassTypeId(), Private::vertexCallback, this);
}
d_ptr->viewer = nullptr;
}
std::vector<SbVec3f> CurveOnMeshHandler::getVertexes() const
{
std::vector<SbVec3f> pts;
pts.reserve(d_ptr->pickedPoints.size());
for (const auto& it : d_ptr->pickedPoints) {
pts.push_back(it.point);
}
return pts;
}
std::vector<SbVec3f> CurveOnMeshHandler::getPoints() const
{
std::vector<SbVec3f> pts;
for (auto it = d_ptr->cutLines.begin(); it != d_ptr->cutLines.end(); ++it) {
std::vector<SbVec3f> segm = d_ptr->convert(*it);
pts.insert(pts.end(), segm.begin(), segm.end());
}
return pts;
}
Handle(Geom_BSplineCurve) CurveOnMeshHandler::approximateSpline(const std::vector<SbVec3f>& points)
{
TColgp_Array1OfPnt pnts(1, points.size());
Standard_Integer index = 1;
for (const auto& it : points) {
float x, y, z;
it.getValue(x, y, z);
pnts(index++) = gp_Pnt(x, y, z);
}
try {
// GeomAPI_PointsToBSpline fit(pnts, 1, 2, GeomAbs_C0, 1.0e-3);
// GeomAPI_PointsToBSpline fit(pnts, d_ptr->par.weight1, d_ptr->par.weight2,
// d_ptr->par.weight3,
// d_ptr->par.maxDegree, d_ptr->par.cont, d_ptr->par.tol3d);
GeomAPI_PointsToBSpline fit(pnts,
1,
d_ptr->par.maxDegree,
d_ptr->par.cont,
d_ptr->par.tol3d);
Handle(Geom_BSplineCurve) spline = fit.Curve();
return spline;
}
catch (...) {
return Handle(Geom_BSplineCurve)();
}
}
void CurveOnMeshHandler::approximateEdge(const TopoDS_Edge& edge, double tolerance)
{
BRepMesh_IncrementalMesh(edge, tolerance);
TopLoc_Location loc;
Handle(Poly_Polygon3D) aPoly = BRep_Tool::Polygon3D(edge, loc);
if (!aPoly.IsNull()) {
int numNodes = aPoly->NbNodes();
const TColgp_Array1OfPnt& aNodes = aPoly->Nodes();
std::vector<SbVec3f> pts;
pts.reserve(numNodes);
for (int i = aNodes.Lower(); i <= aNodes.Upper(); i++) {
const gp_Pnt& p = aNodes.Value(i);
pts.emplace_back(static_cast<float>(p.X()),
static_cast<float>(p.Y()),
static_cast<float>(p.Z()));
}
d_ptr->curve->setPoints(pts);
}
}
void CurveOnMeshHandler::displaySpline(const Handle(Geom_BSplineCurve) & spline)
{
if (d_ptr->viewer) {
double u = spline->FirstParameter();
double v = spline->LastParameter();
BRepBuilderAPI_MakeEdge mkBuilder(spline, u, v);
TopoDS_Edge edge = mkBuilder.Edge();
Gui::View3DInventorViewer* view3d = d_ptr->viewer->getViewer();
App::Document* doc = view3d->getDocument()->getDocument();
doc->openTransaction("Add spline");
Part::Feature* part = doc->addObject<Part::Feature>("Spline");
part->Shape.setValue(edge);
doc->commitTransaction();
}
}
bool CurveOnMeshHandler::makePolyline(const std::vector<SbVec3f>& points, TopoDS_Wire& wire)
{
BRepBuilderAPI_MakePolygon mkPoly;
for (const auto& it : points) {
float x, y, z;
it.getValue(x, y, z);
mkPoly.Add(gp_Pnt(x, y, z));
}
if (mkPoly.IsDone()) {
wire = mkPoly.Wire();
return true;
}
return false;
}
void CurveOnMeshHandler::displayPolyline(const TopoDS_Wire& wire)
{
if (d_ptr->viewer) {
Gui::View3DInventorViewer* view3d = d_ptr->viewer->getViewer();
App::Document* doc = view3d->getDocument()->getDocument();
doc->openTransaction("Add polyline");
Part::Feature* part = doc->addObject<Part::Feature>("Polyline");
part->Shape.setValue(wire);
doc->commitTransaction();
}
}
bool CurveOnMeshHandler::tryCloseWire(const SbVec3f& p) const
{
if (d_ptr->pickedPoints.size() >= 3) {
Private::PickedPoint first = d_ptr->pickedPoints.front();
// if the distance of the first and last points is small enough (~1mm)
// the curve can be closed.
float len = (first.point - p).length();
if (len < d_ptr->distance) {
return true;
}
}
return false;
}
void CurveOnMeshHandler::closeWire()
{
Private::PickedPoint pick = d_ptr->pickedPoints.front();
if (d_ptr->projectLineOnMesh(pick)) {
d_ptr->curve->setPoints(getPoints());
d_ptr->wireClosed = true;
}
}
void CurveOnMeshHandler::Private::vertexCallback(void* ud, SoEventCallback* cb)
{
Gui::View3DInventorViewer* view = static_cast<Gui::View3DInventorViewer*>(cb->getUserData());
const SoEvent* ev = cb->getEvent();
if (ev->getTypeId() == SoMouseButtonEvent::getClassTypeId()) {
// set as handled
cb->setHandled();
const SoMouseButtonEvent* mbe = static_cast<const SoMouseButtonEvent*>(ev);
if (mbe->getButton() == SoMouseButtonEvent::BUTTON1
&& mbe->getState() == SoButtonEvent::DOWN) {
const SoPickedPoint* pp = cb->getPickedPoint();
if (pp) {
CurveOnMeshHandler* self = static_cast<CurveOnMeshHandler*>(ud);
if (!self->d_ptr->wireClosed) {
Gui::ViewProvider* vp = view->getViewProviderByPathFromTail(pp->getPath());
if (vp && vp->isDerivedFrom<MeshGui::ViewProviderMesh>()) {
MeshGui::ViewProviderMesh* mesh =
static_cast<MeshGui::ViewProviderMesh*>(vp);
const SoDetail* detail = pp->getDetail();
if (detail && detail->getTypeId() == SoFaceDetail::getClassTypeId()) {
// get the mesh and build a grid
if (!self->d_ptr->mesh) {
self->d_ptr->mesh = mesh;
self->d_ptr->createGrid();
}
else if (self->d_ptr->mesh != mesh) {
Gui::getMainWindow()->statusBar()->showMessage(
tr("Wrong mesh selected"));
return;
}
const SbVec3f& p = pp->getPoint();
const SbVec3f& n = pp->getNormal();
Private::PickedPoint pick;
pick.facet = static_cast<const SoFaceDetail*>(detail)->getFaceIndex();
pick.point = p;
pick.normal = n;
if (self->d_ptr->pickedPoints.empty()) {
self->d_ptr->pickedPoints.push_back(pick);
self->d_ptr->curve->addVertex(p);
}
else {
// check to auto-complete the curve
if (self->tryCloseWire(p)) {
self->closeWire();
}
else if (self->d_ptr->projectLineOnMesh(pick)) {
self->d_ptr->curve->setPoints(self->getPoints());
self->d_ptr->pickedPoints.push_back(pick);
self->d_ptr->curve->addVertex(p);
}
}
}
}
// try to 'complete' the curve
else if (vp && vp->isDerivedFrom<ViewProviderCurveOnMesh>()) {
const SbVec3f& p = pp->getPoint();
if (self->tryCloseWire(p)) {
self->closeWire();
}
}
}
}
else {
Gui::getMainWindow()->statusBar()->showMessage(tr("No point was selected"));
}
}
else if (mbe->getButton() == SoMouseButtonEvent::BUTTON2
&& mbe->getState() == SoButtonEvent::UP) {
CurveOnMeshHandler* self = static_cast<CurveOnMeshHandler*>(ud);
QTimer::singleShot(100, self, &CurveOnMeshHandler::onContextMenu);
}
}
}
void CurveOnMeshHandler::recomputeDocument()
{
if (d_ptr->viewer) {
Gui::View3DInventorViewer* view3d = d_ptr->viewer->getViewer();
App::Document* doc = view3d->getDocument()->getDocument();
doc->recompute();
}
}
#include "moc_CurveOnMesh.cpp"