diff --git a/src/Mod/Mesh/App/Core/Segmentation.cpp b/src/Mod/Mesh/App/Core/Segmentation.cpp index c0f693fee5ff..a77b80fbe355 100644 --- a/src/Mod/Mesh/App/Core/Segmentation.cpp +++ b/src/Mod/Mesh/App/Core/Segmentation.cpp @@ -171,6 +171,21 @@ float PlaneSurfaceFit::GetDistanceToSurface(const Base::Vector3f& pnt) const return fitter->GetDistanceToPlane(pnt); } +Base::Vector3f PlaneSurfaceFit::Project(const Base::Vector3f& pt) const +{ + Base::Vector3f prj(pt); + if (!fitter) { + prj.ProjectToPlane(basepoint, normal); + } + else { + Base::Vector3f base = fitter->GetBase(); + Base::Vector3f norm = fitter->GetNormal(); + prj.ProjectToPlane(base, norm); + } + + return prj; +} + // -------------------------------------------------------- CylinderSurfaceFit::CylinderSurfaceFit() @@ -257,6 +272,12 @@ float CylinderSurfaceFit::GetDistanceToSurface(const Base::Vector3f& pnt) const return (dist - radius); } +Base::Vector3f CylinderSurfaceFit::Project(const Base::Vector3f& pt) const +{ + //TODO + return pt; +} + // -------------------------------------------------------- SphereSurfaceFit::SphereSurfaceFit() @@ -332,6 +353,12 @@ float SphereSurfaceFit::GetDistanceToSurface(const Base::Vector3f& pnt) const return (dist - radius); } +Base::Vector3f SphereSurfaceFit::Project(const Base::Vector3f& pt) const +{ + //TODO + return pt; +} + // -------------------------------------------------------- MeshDistanceGenericSurfaceFitSegment::MeshDistanceGenericSurfaceFitSegment(AbstractSurfaceFit* fit, @@ -383,6 +410,17 @@ void MeshDistanceGenericSurfaceFitSegment::AddFacet(const MeshFacet& face) fitter->AddTriangle(triangle); } +std::vector MeshDistanceGenericSurfaceFitSegment::Project(const std::vector& pts) const +{ + std::vector prj; + prj.reserve(pts.size()); + for (const auto it : pts) { + prj.push_back(fitter->Project(it)); + } + + return prj; +} + // -------------------------------------------------------- bool MeshCurvaturePlanarSegment::TestFacet (const MeshFacet &rclFacet) const diff --git a/src/Mod/Mesh/App/Core/Segmentation.h b/src/Mod/Mesh/App/Core/Segmentation.h index e1fe5d28d8bf..6b4d16132c36 100644 --- a/src/Mod/Mesh/App/Core/Segmentation.h +++ b/src/Mod/Mesh/App/Core/Segmentation.h @@ -99,6 +99,7 @@ class MeshExport AbstractSurfaceFit virtual bool Done() const = 0; virtual float Fit() = 0; virtual float GetDistanceToSurface(const Base::Vector3f&) const = 0; + virtual Base::Vector3f Project(const Base::Vector3f&) const = 0; }; class MeshExport PlaneSurfaceFit : public AbstractSurfaceFit @@ -114,6 +115,7 @@ class MeshExport PlaneSurfaceFit : public AbstractSurfaceFit bool Done() const; float Fit(); float GetDistanceToSurface(const Base::Vector3f&) const; + Base::Vector3f Project(const Base::Vector3f&) const; private: Base::Vector3f basepoint; @@ -134,6 +136,7 @@ class MeshExport CylinderSurfaceFit : public AbstractSurfaceFit bool Done() const; float Fit(); float GetDistanceToSurface(const Base::Vector3f&) const; + Base::Vector3f Project(const Base::Vector3f&) const; private: Base::Vector3f basepoint; @@ -155,6 +158,7 @@ class MeshExport SphereSurfaceFit : public AbstractSurfaceFit bool Done() const; float Fit(); float GetDistanceToSurface(const Base::Vector3f&) const; + Base::Vector3f Project(const Base::Vector3f&) const; private: Base::Vector3f center; @@ -173,6 +177,7 @@ class MeshExport MeshDistanceGenericSurfaceFitSegment : public MeshDistanceSurfa void Initialize(unsigned long); bool TestInitialFacet(unsigned long) const; void AddFacet(const MeshFacet& rclFacet); + std::vector Project(const std::vector&) const; protected: AbstractSurfaceFit* fitter; diff --git a/src/Mod/ReverseEngineering/Gui/CMakeLists.txt b/src/Mod/ReverseEngineering/Gui/CMakeLists.txt index a8c99480101e..fca294202ecc 100644 --- a/src/Mod/ReverseEngineering/Gui/CMakeLists.txt +++ b/src/Mod/ReverseEngineering/Gui/CMakeLists.txt @@ -31,6 +31,7 @@ endif() set(ReenGui_MOC_HDRS FitBSplineSurface.h Poisson.h + Segmentation.h ) fc_wrap_cpp(ReenGui_MOC_SRCS ${ReenGui_MOC_HDRS}) SOURCE_GROUP("Moc" FILES ${ReenGui_MOC_SRCS}) @@ -38,6 +39,7 @@ SOURCE_GROUP("Moc" FILES ${ReenGui_MOC_SRCS}) set(Dialogs_UIC_SRCS FitBSplineSurface.ui Poisson.ui + Segmentation.ui ) if(BUILD_QT5) @@ -53,6 +55,8 @@ SET(Dialogs_SRCS FitBSplineSurface.h Poisson.cpp Poisson.h + Segmentation.cpp + Segmentation.h ) SOURCE_GROUP("Dialogs" FILES ${Dialogs_SRCS}) diff --git a/src/Mod/ReverseEngineering/Gui/Command.cpp b/src/Mod/ReverseEngineering/Gui/Command.cpp index 43f0f4d1ec6f..840950e25f76 100644 --- a/src/Mod/ReverseEngineering/Gui/Command.cpp +++ b/src/Mod/ReverseEngineering/Gui/Command.cpp @@ -25,6 +25,9 @@ #ifndef _PreComp_ # include # include +# include +# include +# include #endif #include @@ -34,7 +37,9 @@ #include #include #include +#include +#include #include #include #include @@ -47,6 +52,7 @@ #include "../App/ApproxSurface.h" #include "FitBSplineSurface.h" #include "Poisson.h" +#include "Segmentation.h" using namespace std; @@ -186,6 +192,89 @@ bool CmdApproxPlane::isActive(void) return false; } +DEF_STD_CMD_A(CmdSegmentation) + +CmdSegmentation::CmdSegmentation() + : Command("Reen_Segmentation") +{ + sAppModule = "Reen"; + sGroup = QT_TR_NOOP("Reverse Engineering"); + sMenuText = QT_TR_NOOP("Create mesh segments..."); + sToolTipText = QT_TR_NOOP("Create mesh segments"); + sWhatsThis = "Reen_Segmentation"; + sStatusTip = sToolTipText; +} + +void CmdSegmentation::activated(int) +{ + std::vector objs = Gui::Selection().getObjectsOfType(); + Mesh::Feature* mesh = static_cast(objs.front()); + Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); + if (!dlg) { + dlg = new ReverseEngineeringGui::TaskSegmentation(mesh); + } + Gui::Control().showDialog(dlg); +} + +bool CmdSegmentation::isActive(void) +{ + if (Gui::Control().activeDialog()) + return false; + return Gui::Selection().countObjectsOfType + (Mesh::Feature::getClassTypeId()) == 1; +} + +DEF_STD_CMD_A(CmdMeshBoundary) + +CmdMeshBoundary::CmdMeshBoundary() + : Command("Reen_MeshBoundary") +{ + sAppModule = "Reen"; + sGroup = QT_TR_NOOP("Reverse Engineering"); + sMenuText = QT_TR_NOOP("Wire from mesh..."); + sToolTipText = QT_TR_NOOP("Create wire from mesh"); + sWhatsThis = "Reen_Segmentation"; + sStatusTip = sToolTipText; +} + +void CmdMeshBoundary::activated(int) +{ + std::vector objs = Gui::Selection().getObjectsOfType(); + App::Document* document = App::GetApplication().getActiveDocument(); + document->openTransaction("Wire from mesh"); + for (auto it : objs) { + const Mesh::MeshObject& mesh = it->Mesh.getValue(); + std::list > bounds; + MeshCore::MeshAlgorithm algo(mesh.getKernel()); + algo.GetMeshBorders(bounds); + + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + + for (auto bt = bounds.begin(); bt != bounds.end(); ++bt) { + BRepBuilderAPI_MakePolygon mkPoly; + for (std::vector::reverse_iterator it = bt->rbegin(); it != bt->rend(); ++it) { + mkPoly.Add(gp_Pnt(it->x,it->y,it->z)); + } + if (mkPoly.IsDone()) { + builder.Add(compound, mkPoly.Wire()); + } + } + + Part::Feature* shapeFea = static_cast(document->addObject("Part::Feature", "Wires from mesh")); + shapeFea->Shape.setValue(compound); + + } + document->commitTransaction(); +} + +bool CmdMeshBoundary::isActive(void) +{ + return Gui::Selection().countObjectsOfType + (Mesh::Feature::getClassTypeId()) > 0; +} + DEF_STD_CMD_A(CmdPoissonReconstruction) CmdPoissonReconstruction::CmdPoissonReconstruction() @@ -277,6 +366,8 @@ void CreateReverseEngineeringCommands(void) Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdApproxSurface()); rcCmdMgr.addCommand(new CmdApproxPlane()); + rcCmdMgr.addCommand(new CmdSegmentation()); + rcCmdMgr.addCommand(new CmdMeshBoundary()); rcCmdMgr.addCommand(new CmdPoissonReconstruction()); rcCmdMgr.addCommand(new CmdViewTriangulation()); } diff --git a/src/Mod/ReverseEngineering/Gui/Segmentation.cpp b/src/Mod/ReverseEngineering/Gui/Segmentation.cpp new file mode 100644 index 000000000000..831f63023def --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/Segmentation.cpp @@ -0,0 +1,254 @@ +/*************************************************************************** + * Copyright (c) 2012 Werner Mayer * + * * + * 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 +# include +# include +# include +# include +# include +#endif + +#include "Segmentation.h" +#include "ui_Segmentation.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ReverseEngineeringGui; + +Segmentation::Segmentation(Mesh::Feature* mesh, QWidget* parent, Qt::WindowFlags fl) + : QWidget(parent, fl) + , ui(new Ui_Segmentation) + , myMesh(mesh) +{ + ui->setupUi(this); + ui->numPln->setRange(1, INT_MAX); + ui->numPln->setValue(100); + + ui->checkBoxSmooth->setChecked(false); +} + +Segmentation::~Segmentation() +{ +} + +void Segmentation::accept() +{ + if (myMesh.expired()) + return; + + Gui::WaitCursor wc; + bool createUnused = ui->createUnused->isChecked(); + bool createCompound = ui->createCompound->isChecked(); + BRep_Builder builder; + TopoDS_Compound compound; + builder.MakeCompound(compound); + + const Mesh::MeshObject* mesh = myMesh.get()->Mesh.getValuePtr(); + // make a copy because we might smooth the mesh before + MeshCore::MeshKernel kernel = mesh->getKernel(); + MeshCore::MeshAlgorithm algo(kernel); + + if (ui->checkBoxSmooth->isChecked()) { + MeshCore::LaplaceSmoothing smoother(kernel); + smoother.Smooth(ui->smoothSteps->value()); + } + + MeshCore::MeshSegmentAlgorithm finder(kernel); + MeshCore::MeshCurvature meshCurv(kernel); + meshCurv.ComputePerVertex(); + + std::vector segm; + if (ui->groupBoxPln->isChecked()) { + segm.emplace_back(new MeshCore::MeshCurvaturePlanarSegment + (meshCurv.GetCurvature(), ui->numPln->value(), ui->curvTolPln->value())); + } + finder.FindSegments(segm); + + // For each planar segment compute a plane and use this then for a more accurate 2nd segmentation + std::vector segmSurf; + for (std::vector::iterator it = segm.begin(); it != segm.end(); ++it) { + const std::vector& data = (*it)->GetSegments(); + for (std::vector::const_iterator jt = data.begin(); jt != data.end(); ++jt) { + std::vector indexes = kernel.GetFacetPoints(*jt); + MeshCore::PlaneFit fit; + fit.AddPoints(kernel.GetPoints(indexes)); + if (fit.Fit() < FLOAT_MAX) { + Base::Vector3f base = fit.GetBase(); + Base::Vector3f axis = fit.GetNormal(); + MeshCore::AbstractSurfaceFit* fitter = new MeshCore::PlaneSurfaceFit(base, axis); + segmSurf.emplace_back(new MeshCore::MeshDistanceGenericSurfaceFitSegment + (fitter, kernel, ui->numPln->value(), ui->distToPln->value())); + } + } + } + finder.FindSegments(segmSurf); + + App::Document* document = App::GetApplication().getActiveDocument(); + document->openTransaction("Segmentation"); + + std::string internalname = "Segments_"; + internalname += myMesh->getNameInDocument(); + + App::DocumentObjectGroup* group = static_cast(document->addObject + ("App::DocumentObjectGroup", internalname.c_str())); + std::string labelname = "Segments "; + labelname += myMesh->Label.getValue(); + group->Label.setValue(labelname); + + std::vector failures; + algo.SetFacetFlag(MeshCore::MeshFacet::TMP0); + + for (std::vector::iterator it = segmSurf.begin(); it != segmSurf.end(); ++it) { + const std::vector& data = (*it)->GetSegments(); + std::shared_ptr genSegm = std::dynamic_pointer_cast + (*it); + + for (std::vector::const_iterator jt = data.begin(); jt != data.end(); ++jt) { + // reset flag for facets of segment + algo.ResetFacetsFlag(*jt, MeshCore::MeshFacet::TMP0); + + Mesh::MeshObject* segment = mesh->meshFromSegment(*jt); + Mesh::Feature* feaSegm = static_cast(group->addObject("Mesh::Feature", "Segment")); + Mesh::MeshObject* feaMesh = feaSegm->Mesh.startEditing(); + feaMesh->swap(*segment); + feaSegm->Mesh.finishEditing(); + delete segment; + + std::stringstream label; + label << feaSegm->Label.getValue() << " (" << (*it)->GetType() << ")"; + feaSegm->Label.setValue(label.str()); + + if (createCompound) { + std::list > bounds; + algo.GetFacetBorders(*jt, bounds); + + std::vector wires; + for (auto bt = bounds.begin(); bt != bounds.end(); ++bt) { + // project the points onto the surface + auto prj = genSegm->Project(*bt); + BRepBuilderAPI_MakePolygon mkPoly; + for (std::vector::reverse_iterator it = prj.rbegin(); it != prj.rend(); ++it) { + mkPoly.Add(gp_Pnt(it->x,it->y,it->z)); + } + if (mkPoly.IsDone()) { + wires.push_back(mkPoly.Wire()); + } + } + + try { + TopoDS_Shape shape = Part::FaceMakerCheese::makeFace(wires); + if (!shape.IsNull()) { + builder.Add(compound, shape); + } + else { + failures.push_back(feaSegm); + Base::Console().Warning("Failed to create face from %s\n", feaSegm->Label.getValue()); + } + } + catch (Standard_Failure&) { + failures.push_back(feaSegm); + Base::Console().Error("Fatal failure to create face from %s\n", feaSegm->Label.getValue()); + } + } + } + } + + if (createUnused) { + // collect all facets that don't have set the flag TMP0 + std::vector unusedFacets; + algo.GetFacetsFlag(unusedFacets, MeshCore::MeshFacet::TMP0); + + if (!unusedFacets.empty()) { + std::unique_ptr segment(mesh->meshFromSegment(unusedFacets)); + Mesh::Feature* feaSegm = static_cast(group->addObject("Mesh::Feature", "Unused")); + Mesh::MeshObject* feaMesh = feaSegm->Mesh.startEditing(); + feaMesh->swap(*segment); + feaSegm->Mesh.finishEditing(); + } + } + if (createCompound) { + Part::Feature* shapeFea = static_cast(group->addObject("Part::Feature", "Compound")); + shapeFea->Shape.setValue(compound); + + // create a sub-group where to move the problematic segments + if (!failures.empty()) { + App::DocumentObjectGroup* subgroup = static_cast(group->addObject + ("App::DocumentObjectGroup", "Failed")); + failures = group->removeObjects(failures); + subgroup->Group.setValues(failures); + } + } + + document->commitTransaction(); +} + +void Segmentation::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } + QWidget::changeEvent(e); +} + +// --------------------------------------- + +/* TRANSLATOR MeshGui::TaskRemoveComponents */ + +TaskSegmentation::TaskSegmentation(Mesh::Feature* mesh) +{ + widget = new Segmentation(mesh); + taskbox = new Gui::TaskView::TaskBox( + QPixmap(), widget->windowTitle(), false, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskSegmentation::~TaskSegmentation() +{ + // automatically deleted in the sub-class +} + +bool TaskSegmentation::accept() +{ + widget->accept(); + return true; +} + +#include "moc_Segmentation.cpp" diff --git a/src/Mod/ReverseEngineering/Gui/Segmentation.h b/src/Mod/ReverseEngineering/Gui/Segmentation.h new file mode 100644 index 000000000000..ea9771d2acea --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/Segmentation.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (c) 2012 Werner Mayer * + * * + * 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 REVERSEENGINEERINGGUI_SEGMENTATION_H +#define REVERSEENGINEERINGGUI_SEGMENTATION_H + +#include +#include +#include +#include +#include + +// forward declarations +namespace Mesh { class Feature; } + +namespace ReverseEngineeringGui { +class Ui_Segmentation; + +class Segmentation : public QWidget +{ + Q_OBJECT + +public: + Segmentation(Mesh::Feature* mesh, QWidget* parent = 0, Qt::WindowFlags fl = 0); + ~Segmentation(); + void accept(); + +protected: + void changeEvent(QEvent *e); + +private: + std::unique_ptr ui; + App::DocumentObjectWeakPtrT myMesh; +}; + +/** + * Embed the panel into a task dialog. + */ +class TaskSegmentation : public Gui::TaskView::TaskDialog +{ +public: + TaskSegmentation(Mesh::Feature* mesh); + ~TaskSegmentation(); + +public: + bool accept(); + + virtual QDialogButtonBox::StandardButtons getStandardButtons() const + { return QDialogButtonBox::Ok | QDialogButtonBox::Cancel; } + +private: + Segmentation* widget; + Gui::TaskView::TaskBox* taskbox; +}; + +} + +#endif // REVERSEENGINEERINGGUI_SEGMENTATION_H diff --git a/src/Mod/ReverseEngineering/Gui/Segmentation.ui b/src/Mod/ReverseEngineering/Gui/Segmentation.ui new file mode 100644 index 000000000000..4c9926b48b00 --- /dev/null +++ b/src/Mod/ReverseEngineering/Gui/Segmentation.ui @@ -0,0 +1,115 @@ + + + ReverseEngineeringGui::Segmentation + + + + 0 + 0 + 343 + 242 + + + + Mesh segmentation + + + + + + 3 + + + + + + + Create compound + + + + + + + Smooth mesh + + + true + + + + + + + Plane + + + true + + + + + + Curvature tolerance + + + + + + + 0.010000000000000 + + + 0.010000000000000 + + + + + + + Distance to plane + + + + + + + 0.010000000000000 + + + 0.010000000000000 + + + + + + + Minimum number of faces + + + + + + + 100000 + + + 100 + + + + + + + + + + Create mesh from unused triangles + + + + + + + + diff --git a/src/Mod/ReverseEngineering/Gui/Workbench.cpp b/src/Mod/ReverseEngineering/Gui/Workbench.cpp index a6ac472aafa7..d5f5845f5981 100644 --- a/src/Mod/ReverseEngineering/Gui/Workbench.cpp +++ b/src/Mod/ReverseEngineering/Gui/Workbench.cpp @@ -50,13 +50,13 @@ Workbench::~Workbench() Gui::MenuItem* Workbench::setupMenuBar() const { - Gui::MenuItem* root = StdWorkbench::setupMenuBar(); - Gui::MenuItem* item = root->findItem("&Windows"); - Gui::MenuItem* reen = new Gui::MenuItem; - root->insertItem(item, reen); - reen->setCommand("&REEN"); - *reen << "Reen_ApproxPlane" - << "Reen_ApproxSurface"; + Gui::MenuItem* root = StdWorkbench::setupMenuBar(); + Gui::MenuItem* item = root->findItem("&Windows"); + Gui::MenuItem* reen = new Gui::MenuItem; + root->insertItem(item, reen); + reen->setCommand("&REEN"); + *reen << "Reen_ApproxPlane" + << "Reen_ApproxSurface"; Gui::MenuItem *reconstruct = new Gui::MenuItem(); reconstruct->setCommand("Surface reconstruction"); @@ -64,7 +64,17 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Reen_ViewTriangulation"; *reen << reconstruct; - return root; + Gui::MenuItem *segm = new Gui::MenuItem(); + segm->setCommand("Segmentation"); + *segm << "Mesh_RemeshGmsh" + << "Mesh_VertexCurvature" + << "Mesh_CurvatureInfo" + << "Separator" + << "Reen_Segmentation" + << "Reen_MeshBoundary"; + *reen << segm; + + return root; } Gui::ToolBarItem* Workbench::setupToolBars() const