FreeCAD / src /Mod /MeshPart /Gui /CurveOnMesh.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* 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"