// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2018 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SegmentationBestFit.h" #include "ui_SegmentationBestFit.h" using namespace MeshGui; namespace MeshGui { class PlaneFitParameter: public FitParameter { public: PlaneFitParameter() = default; std::vector getParameter(FitParameter::Points pts) const override { std::vector values; MeshCore::PlaneFit fit; fit.AddPoints(pts.points); if (fit.Fit() < std::numeric_limits::max()) { Base::Vector3f base = fit.GetBase(); Base::Vector3f axis = fit.GetNormal(); values.push_back(base.x); values.push_back(base.y); values.push_back(base.z); values.push_back(axis.x); values.push_back(axis.y); values.push_back(axis.z); } return values; } }; class CylinderFitParameter: public FitParameter { public: CylinderFitParameter() = default; std::vector getParameter(FitParameter::Points pts) const override { std::vector values; MeshCore::CylinderFit fit; fit.AddPoints(pts.points); if (!pts.normals.empty()) { Base::Vector3f base = fit.GetGravity(); Base::Vector3f axis = fit.GetInitialAxisFromNormals(pts.normals); fit.SetInitialValues(base, axis); #if defined(FC_DEBUG) Base::Console().message("Initial axis: (%f, %f, %f)\n", axis.x, axis.y, axis.z); #endif } if (fit.Fit() < std::numeric_limits::max()) { Base::Vector3f base, top; fit.GetBounding(base, top); Base::Vector3f axis = fit.GetAxis(); float radius = fit.GetRadius(); values.push_back(base.x); values.push_back(base.y); values.push_back(base.z); values.push_back(axis.x); values.push_back(axis.y); values.push_back(axis.z); values.push_back(radius); #if defined(FC_DEBUG) // Only for testing purposes try { float height = Base::Distance(base, top); Gui::Command::doCommand( Gui::Command::App, "cyl = App.ActiveDocument.addObject('Part::Cylinder', 'Cylinder')\n" "cyl.Radius = %f\n" "cyl.Height = %f\n" "cyl.Placement = App.Placement(App.Vector(%f,%f,%f), " "App.Rotation(App.Vector(0,0,1), App.Vector(%f,%f,%f)))\n", radius, height, base.x, base.y, base.z, axis.x, axis.y, axis.z ); Gui::Command::doCommand( Gui::Command::App, "axis = cyl.Placement.Rotation.multVec(App.Vector(0,0,1))\n" "print('Final axis: ({}, {}, {})'.format(axis.x, axis.y, axis.z))\n" ); } catch (...) { } #endif } return values; } }; class SphereFitParameter: public FitParameter { public: SphereFitParameter() = default; std::vector getParameter(FitParameter::Points pts) const override { std::vector values; MeshCore::SphereFit fit; fit.AddPoints(pts.points); if (fit.Fit() < std::numeric_limits::max()) { Base::Vector3f base = fit.GetCenter(); float radius = fit.GetRadius(); values.push_back(base.x); values.push_back(base.y); values.push_back(base.z); values.push_back(radius); } return values; } }; } // namespace MeshGui ParametersDialog::ParametersDialog( std::vector& val, FitParameter* fitPar, ParameterList par, Mesh::Feature* mesh, QWidget* parent ) : QDialog(parent) , values(val) , fitParameter(fitPar) , parameter(std::move(par)) , myMesh(mesh) { this->setWindowTitle(tr("Surface Fit")); QGridLayout* gridLayout {}; gridLayout = new QGridLayout(this); QGroupBox* groupBox {}; groupBox = new QGroupBox(this); groupBox->setTitle(tr("Parameters")); gridLayout->addWidget(groupBox, 0, 0, 1, 1); QGroupBox* selectBox {}; selectBox = new QGroupBox(this); selectBox->setTitle(tr("Selection")); gridLayout->addWidget(selectBox, 1, 0, 1, 1); QVBoxLayout* selectLayout {}; selectLayout = new QVBoxLayout(selectBox); QPushButton* regionButton {}; regionButton = new QPushButton(this); regionButton->setText(tr("Region")); regionButton->setObjectName(QStringLiteral("region")); selectLayout->addWidget(regionButton); QPushButton* singleButton {}; singleButton = new QPushButton(this); singleButton->setText(tr("Triangle")); singleButton->setObjectName(QStringLiteral("single")); selectLayout->addWidget(singleButton); QPushButton* clearButton {}; clearButton = new QPushButton(this); clearButton->setText(tr("Clear")); clearButton->setObjectName(QStringLiteral("clear")); selectLayout->addWidget(clearButton); QPushButton* computeButton {}; computeButton = new QPushButton(this); computeButton->setText(tr("Compute")); computeButton->setObjectName(QStringLiteral("compute")); gridLayout->addWidget(computeButton, 2, 0, 1, 1); QDialogButtonBox* buttonBox {}; buttonBox = new QDialogButtonBox(this); buttonBox->setOrientation(Qt::Horizontal); buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); gridLayout->addWidget(buttonBox, 3, 0, 1, 1); int index = 0; QGridLayout* layout {}; layout = new QGridLayout(groupBox); groupBox->setLayout(layout); for (const auto& it : parameter) { QLabel* label = new QLabel(groupBox); label->setText(it.first); layout->addWidget(label, index, 0, 1, 1); QDoubleSpinBox* doubleSpinBox = new QDoubleSpinBox(groupBox); doubleSpinBox->setObjectName(it.first); doubleSpinBox->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); doubleSpinBox->setValue(it.second); layout->addWidget(doubleSpinBox, index, 1, 1, 1); spinBoxes.push_back(doubleSpinBox); ++index; } // clang-format off connect(buttonBox, &QDialogButtonBox::accepted, this, &ParametersDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ParametersDialog::reject); connect(regionButton, &QPushButton::clicked, this, &ParametersDialog::onRegionClicked); connect(singleButton, &QPushButton::clicked, this, &ParametersDialog::onSingleClicked); connect(clearButton, &QPushButton::clicked, this, &ParametersDialog::onClearClicked); connect(computeButton, &QPushButton::clicked, this, &ParametersDialog::onComputeClicked); // clang-format on Gui::SelectionObject obj(mesh); std::vector sel; sel.push_back(obj); Gui::Selection().clearSelection(); meshSel.setObjects(sel); meshSel.setCheckOnlyPointToUserTriangles(true); meshSel.setCheckOnlyVisibleTriangles(true); meshSel.setEnabledViewerSelection(false); } ParametersDialog::~ParametersDialog() { meshSel.clearSelection(); meshSel.setEnabledViewerSelection(true); delete fitParameter; } void ParametersDialog::onRegionClicked() { meshSel.startSelection(); } void ParametersDialog::onSingleClicked() { meshSel.selectTriangle(); } void ParametersDialog::onClearClicked() { meshSel.clearSelection(); } void ParametersDialog::onComputeClicked() { const Mesh::MeshObject& kernel = myMesh->Mesh.getValue(); if (kernel.hasSelectedFacets()) { FitParameter::Points fitpts; std::vector facets, points; kernel.getFacetsFromSelection(facets); points = kernel.getPointsFromFacets(facets); MeshCore::MeshPointArray coords = kernel.getKernel().GetPoints(points); fitpts.normals = kernel.getKernel().GetFacetNormals(facets); // Copy points into right format fitpts.points.insert(fitpts.points.end(), coords.begin(), coords.end()); coords.clear(); values = fitParameter->getParameter(fitpts); if (values.size() == spinBoxes.size()) { for (std::size_t i = 0; i < values.size(); i++) { spinBoxes[i]->setValue(values[i]); } } meshSel.stopSelection(); meshSel.clearSelection(); } else { QMessageBox::warning(this, tr("No selection"), tr("Before fitting the surface select an area.")); } } void ParametersDialog::accept() { std::vector v; for (auto it : spinBoxes) { v.push_back(it->value()); } values = v; QDialog::accept(); } void ParametersDialog::reject() { values.clear(); QDialog::reject(); } // ---------------------------------------------------------------------------- /* TRANSLATOR MeshGui::SegmentationBestFit */ SegmentationBestFit::SegmentationBestFit(Mesh::Feature* mesh, QWidget* parent, Qt::WindowFlags fl) : QWidget(parent, fl) , ui(new Ui_SegmentationBestFit) , myMesh(mesh) { ui->setupUi(this); setupConnections(); ui->numPln->setRange(1, std::numeric_limits::max()); ui->numPln->setValue(100); ui->numCyl->setRange(1, std::numeric_limits::max()); ui->numCyl->setValue(100); ui->numSph->setRange(1, std::numeric_limits::max()); ui->numSph->setValue(100); Gui::SelectionObject obj(myMesh); std::vector sel; sel.push_back(obj); meshSel.setObjects(sel); } SegmentationBestFit::~SegmentationBestFit() { // no need to delete child widgets, Qt does it all for us delete ui; } void SegmentationBestFit::setupConnections() { // clang-format off connect(ui->planeParameters, &QPushButton::clicked, this, &SegmentationBestFit::onPlaneParametersClicked); connect(ui->cylinderParameters, &QPushButton::clicked, this, &SegmentationBestFit::onCylinderParametersClicked); connect(ui->sphereParameters, &QPushButton::clicked, this, &SegmentationBestFit::onSphereParametersClicked); // clang-format on } void SegmentationBestFit::onPlaneParametersClicked() { ParameterList list; std::vector p = planeParameter; p.resize(6); QString base = tr("Base"); QString axis = tr("Normal"); QString x = QStringLiteral(" x"); QString y = QStringLiteral(" y"); QString z = QStringLiteral(" z"); list.push_back(std::make_pair(base + x, p[0])); list.push_back(std::make_pair(base + y, p[1])); list.push_back(std::make_pair(base + z, p[2])); list.push_back(std::make_pair(axis + x, p[3])); list.push_back(std::make_pair(axis + y, p[4])); list.push_back(std::make_pair(axis + z, p[5])); static QPointer dialog = nullptr; if (!dialog) { dialog = new ParametersDialog(planeParameter, new PlaneFitParameter, list, myMesh, this); } dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void SegmentationBestFit::onCylinderParametersClicked() { ParameterList list; std::vector p = cylinderParameter; p.resize(7); QString base = tr("Base"); QString axis = tr("Axis"); QString radius = tr("Radius"); QString x = QStringLiteral(" x"); QString y = QStringLiteral(" y"); QString z = QStringLiteral(" z"); list.push_back(std::make_pair(base + x, p[0])); list.push_back(std::make_pair(base + y, p[1])); list.push_back(std::make_pair(base + z, p[2])); list.push_back(std::make_pair(axis + x, p[3])); list.push_back(std::make_pair(axis + y, p[4])); list.push_back(std::make_pair(axis + z, p[5])); list.push_back(std::make_pair(radius, p[6])); static QPointer dialog = nullptr; if (!dialog) { dialog = new ParametersDialog(cylinderParameter, new CylinderFitParameter, list, myMesh, this); } dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void SegmentationBestFit::onSphereParametersClicked() { ParameterList list; std::vector p = sphereParameter; p.resize(4); QString base = tr("Center"); QString radius = tr("Radius"); QString x = QStringLiteral(" x"); QString y = QStringLiteral(" y"); QString z = QStringLiteral(" z"); list.push_back(std::make_pair(base + x, p[0])); list.push_back(std::make_pair(base + y, p[1])); list.push_back(std::make_pair(base + z, p[2])); list.push_back(std::make_pair(radius, p[3])); static QPointer dialog = nullptr; if (!dialog) { dialog = new ParametersDialog(sphereParameter, new SphereFitParameter, list, myMesh, this); } dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void SegmentationBestFit::accept() { const Mesh::MeshObject* mesh = myMesh->Mesh.getValuePtr(); const MeshCore::MeshKernel& kernel = mesh->getKernel(); MeshCore::MeshSegmentAlgorithm finder(kernel); std::vector segm; if (ui->groupBoxCyl->isChecked()) { MeshCore::AbstractSurfaceFit* fitter {}; if (cylinderParameter.size() == 7) { std::vector& p = cylinderParameter; fitter = new MeshCore::CylinderSurfaceFit( Base::Vector3f(p[0], p[1], p[2]), Base::Vector3f(p[3], p[4], p[5]), p[6] ); } else { fitter = new MeshCore::CylinderSurfaceFit; } segm.emplace_back( std::make_shared( fitter, kernel, ui->numCyl->value(), ui->tolCyl->value() ) ); } if (ui->groupBoxSph->isChecked()) { MeshCore::AbstractSurfaceFit* fitter {}; if (sphereParameter.size() == 4) { std::vector& p = sphereParameter; fitter = new MeshCore::SphereSurfaceFit(Base::Vector3f(p[0], p[1], p[2]), p[3]); } else { fitter = new MeshCore::SphereSurfaceFit; } segm.emplace_back( std::make_shared( fitter, kernel, ui->numSph->value(), ui->tolSph->value() ) ); } if (ui->groupBoxPln->isChecked()) { MeshCore::AbstractSurfaceFit* fitter {}; if (planeParameter.size() == 6) { std::vector& p = planeParameter; fitter = new MeshCore::PlaneSurfaceFit( Base::Vector3f(p[0], p[1], p[2]), Base::Vector3f(p[3], p[4], p[5]) ); } else { fitter = new MeshCore::PlaneSurfaceFit; } segm.emplace_back( std::make_shared( fitter, kernel, ui->numPln->value(), ui->tolPln->value() ) ); } finder.FindSegments(segm); App::Document* document = App::GetApplication().getActiveDocument(); document->openTransaction("Segmentation"); std::string internalname = "Segments_"; internalname += myMesh->getNameInDocument(); auto* group = document->addObject(internalname.c_str()); std::string labelname = "Segments "; labelname += myMesh->Label.getValue(); group->Label.setValue(labelname); for (const auto& it : segm) { const std::vector& data = it->GetSegments(); for (const auto& jt : data) { Mesh::MeshObject* segment = mesh->meshFromSegment(jt); auto* feaSegm = group->addObject("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()); } } document->commitTransaction(); } void SegmentationBestFit::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } QWidget::changeEvent(e); } // --------------------------------------- /* TRANSLATOR MeshGui::TaskSegmentationBestFit */ TaskSegmentationBestFit::TaskSegmentationBestFit(Mesh::Feature* mesh) { widget = new SegmentationBestFit(mesh); // NOLINT addTaskBox(widget, false); } bool TaskSegmentationBestFit::accept() { widget->accept(); return true; } #include "moc_SegmentationBestFit.cpp"