RE: Add function to approximate B-Spline from points

This commit is contained in:
wmayer
2024-04-03 15:23:14 +02:00
committed by wwmayer
parent ba27d6d92d
commit b47d72f8de
7 changed files with 716 additions and 1 deletions

View File

@@ -74,6 +74,7 @@ class Module : public Py::ExtensionModule<Module>
public:
Module() : Py::ExtensionModule<Module>("ReverseEngineering")
{
add_keyword_method("approxCurve", &Module::approxCurve, "Approximate curve");
add_keyword_method("approxSurface",&Module::approxSurface,
"approxSurface(Points, UDegree=3, VDegree=3, NbUPoles=6, NbVPoles=6,\n"
"Smooth=True, Weight=0.1, Grad=1.0, Bend=0.0, Curv=0.0\n"
@@ -159,6 +160,169 @@ public:
}
private:
static std::vector<Base::Vector3d> getPoints(PyObject* pts, bool closed)
{
std::vector<Base::Vector3d> data;
if (PyObject_TypeCheck(pts, &(Points::PointsPy::Type))) {
std::vector<Base::Vector3d> normal;
auto pypts = static_cast<Points::PointsPy*>(pts);
Points::PointKernel* points = pypts->getPointKernelPtr();
points->getPoints(data, normal, 0.0);
}
else {
Py::Sequence l(pts);
data.reserve(l.size());
for (Py::Sequence::iterator it = l.begin(); it != l.end(); ++it) {
Py::Tuple t(*it);
data.emplace_back(
Py::Float(t.getItem(0)),
Py::Float(t.getItem(1)),
Py::Float(t.getItem(2))
);
}
}
if (closed) {
if (!data.empty()) {
data.push_back(data.front());
}
}
return data;
}
static PyObject* approx1(const Py::Tuple& args, const Py::Dict& kwds)
{
PyObject* pts {};
PyObject* closed = Py_False;
int minDegree = 3; // NOLINT
int maxDegree = 8; // NOLINT
int cont = int(GeomAbs_C2);
double tol3d = 1.0e-3; // NOLINT
static const std::array<const char *, 7> kwds_approx{"Points",
"Closed",
"MinDegree",
"MaxDegree",
"Continuity",
"Tolerance",
nullptr};
if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "O|O!iiid", kwds_approx,
&pts, &PyBool_Type, &closed, &minDegree,
&maxDegree, &cont, &tol3d)) {
return nullptr;
}
std::vector<Base::Vector3d> data = getPoints(pts, Base::asBoolean(closed));
Part::GeomBSplineCurve curve;
curve.approximate(data, minDegree, maxDegree, GeomAbs_Shape(cont), tol3d);
return curve.getPyObject();
}
static PyObject* approx2(const Py::Tuple& args, const Py::Dict& kwds)
{
PyObject* pts {};
char* parType {};
PyObject* closed = Py_False;
int minDegree = 3; // NOLINT
int maxDegree = 8; // NOLINT
int cont = int(GeomAbs_C2);
double tol3d = 1.0e-3; // NOLINT
static const std::array<const char *, 8> kwds_approx{"Points",
"ParametrizationType",
"Closed",
"MinDegree",
"MaxDegree",
"Continuity",
"Tolerance",
nullptr};
if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "Os|O!iiid", kwds_approx,
&pts, &parType, &PyBool_Type, &closed, &minDegree,
&maxDegree, &cont, &tol3d)) {
return nullptr;
}
std::vector<Base::Vector3d> data = getPoints(pts, Base::asBoolean(closed));
Approx_ParametrizationType pt {Approx_ChordLength};
std::string pstr = parType;
if (pstr == "Uniform") {
pt = Approx_IsoParametric;
}
else if (pstr == "Centripetal") {
pt = Approx_Centripetal;
}
Part::GeomBSplineCurve curve;
curve.approximate(data, pt, minDegree, maxDegree, GeomAbs_Shape(cont), tol3d);
return curve.getPyObject();
}
static PyObject* approx3(const Py::Tuple& args, const Py::Dict& kwds)
{
PyObject* pts {};
double weight1 {};
double weight2 {};
double weight3 {};
PyObject* closed = Py_False;
int maxDegree = 8; // NOLINT
int cont = int(GeomAbs_C2);
double tol3d = 1.0e-3; // NOLINT
static const std::array<const char *, 9> kwds_approx{"Points",
"Weight1",
"Weight2",
"Weight3",
"Closed",
"MaxDegree",
"Continuity",
"Tolerance",
nullptr};
if (!Base::Wrapped_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), "Oddd|O!iid", kwds_approx,
&pts, &weight1, &weight2, &weight3,
&PyBool_Type, &closed,
&maxDegree, &cont, &tol3d)) {
return nullptr;
}
std::vector<Base::Vector3d> data = getPoints(pts, Base::asBoolean(closed));
Part::GeomBSplineCurve curve;
curve.approximate(data, weight1, weight2, weight3, maxDegree, GeomAbs_Shape(cont), tol3d);
return curve.getPyObject();
}
Py::Object approxCurve(const Py::Tuple& args, const Py::Dict& kwds)
{
try {
using approxFunc = std::function<PyObject*(const Py::Tuple& args, const Py::Dict& kwds)>;
std::vector<approxFunc> funcs;
funcs.emplace_back(approx3);
funcs.emplace_back(approx2);
funcs.emplace_back(approx1);
for (const auto& func : funcs) {
if (PyObject* py = func(args, kwds)) {
return Py::asObject(py);
}
PyErr_Clear();
}
throw Py::ValueError("Wrong arguments ReverseEngineering.approxCurve()");
}
catch (const Base::Exception& e) {
std::string msg = e.what();
if (msg.empty()) {
msg = "ReverseEngineering.approxCurve() failed";
}
throw Py::RuntimeError(msg);
}
}
Py::Object approxSurface(const Py::Tuple& args, const Py::Dict& kwds)
{
PyObject *o;

View File

@@ -23,6 +23,7 @@ qt_create_resource_file(${ReverseEngineering_TR_QRC} ${QM_SRCS})
qt_add_resources(ReenGui_QRC_SRCS Resources/ReverseEngineering.qrc ${ReverseEngineering_TR_QRC})
set(Dialogs_UIC_SRCS
FitBSplineCurve.ui
FitBSplineSurface.ui
Poisson.ui
Segmentation.ui
@@ -32,6 +33,8 @@ set(Dialogs_UIC_SRCS
SET(Dialogs_SRCS
${Dialogs_UIC_HDRS}
${Dialogs_UIC_SRCS}
FitBSplineCurve.cpp
FitBSplineCurve.h
FitBSplineSurface.cpp
FitBSplineSurface.h
Poisson.cpp

View File

@@ -51,6 +51,7 @@
#include <Mod/Points/App/Structured.h>
#include <Mod/ReverseEngineering/App/ApproxSurface.h>
#include "FitBSplineCurve.h"
#include "FitBSplineSurface.h"
#include "Poisson.h"
#include "Segmentation.h"
@@ -59,6 +60,39 @@
using namespace std;
DEF_STD_CMD_A(CmdApproxCurve)
CmdApproxCurve::CmdApproxCurve()
: Command("Reen_ApproxCurve")
{
sAppModule = "Reen";
sGroup = QT_TR_NOOP("Reverse Engineering");
sMenuText = QT_TR_NOOP("Approximate B-spline curve...");
sToolTipText = QT_TR_NOOP("Approximate a B-spline curve");
sWhatsThis = "Reen_ApproxCurve";
sStatusTip = sToolTipText;
}
void CmdApproxCurve::activated(int)
{
App::DocumentObjectT objT;
auto obj = Gui::Selection().getObjectsOfType(App::GeoFeature::getClassTypeId());
if (obj.size() != 1 || !(obj.at(0)->isDerivedFrom(Points::Feature::getClassTypeId()))) {
QMessageBox::warning(Gui::getMainWindow(),
qApp->translate("Reen_ApproxSurface", "Wrong selection"),
qApp->translate("Reen_ApproxSurface", "Please select a point cloud."));
return;
}
objT = obj.front();
Gui::Control().showDialog(new ReenGui::TaskFitBSplineCurve(objT));
}
bool CmdApproxCurve::isActive()
{
return (hasActiveDocument() && !Gui::Control().activeDialog());
}
DEF_STD_CMD_A(CmdApproxSurface)
CmdApproxSurface::CmdApproxSurface()
@@ -648,6 +682,7 @@ bool CmdViewTriangulation::isActive()
void CreateReverseEngineeringCommands()
{
Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
rcCmdMgr.addCommand(new CmdApproxCurve());
rcCmdMgr.addCommand(new CmdApproxSurface());
rcCmdMgr.addCommand(new CmdApproxPlane());
rcCmdMgr.addCommand(new CmdApproxCylinder());

View File

@@ -0,0 +1,182 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2024 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <algorithm>
#include <QMessageBox>
#endif
#include <Gui/CommandT.h>
#include <Gui/WaitCursor.h>
#include "FitBSplineCurve.h"
#include "ui_FitBSplineCurve.h"
using namespace ReenGui;
class FitBSplineCurveWidget::Private
{
public:
Ui_FitBSplineCurve ui {};
App::DocumentObjectT obj {};
};
/* TRANSLATOR ReenGui::FitBSplineCurveWidget */
FitBSplineCurveWidget::FitBSplineCurveWidget(const App::DocumentObjectT& obj, QWidget* parent)
: d(new Private())
{
Q_UNUSED(parent);
d->ui.setupUi(this);
d->obj = obj;
// clang-format off
connect(d->ui.checkBox, &QCheckBox::toggled,
this, &FitBSplineCurveWidget::toggleParametrizationType);
connect(d->ui.groupBoxSmooth, &QGroupBox::toggled,
this, &FitBSplineCurveWidget::toggleSmoothing);
// clang-format on
}
FitBSplineCurveWidget::~FitBSplineCurveWidget()
{
delete d;
}
void FitBSplineCurveWidget::toggleParametrizationType(bool on)
{
d->ui.paramType->setEnabled(on);
if (on) {
d->ui.groupBoxSmooth->setChecked(false);
}
}
void FitBSplineCurveWidget::toggleSmoothing(bool on)
{
if (on) {
d->ui.checkBox->setChecked(false);
d->ui.paramType->setEnabled(false);
}
}
bool FitBSplineCurveWidget::accept()
{
try {
tryAccept();
}
catch (const Base::Exception& e) {
Gui::Command::abortCommand();
QMessageBox::warning(this, tr("Input error"), QString::fromLatin1(e.what()));
return false;
}
return true;
}
void FitBSplineCurveWidget::tryAccept()
{
QString document = QString::fromStdString(d->obj.getDocumentPython());
QString object = QString::fromStdString(d->obj.getObjectPython());
QStringList arguments;
arguments.append(
QString::fromLatin1("Points=getattr(%1, %1.getPropertyNameOfGeometry())").arg(object));
if (!d->ui.groupBoxSmooth->isChecked()) {
arguments.append(QString::fromLatin1("MinDegree = %1").arg(d->ui.degreeMin->value()));
}
arguments.append(QString::fromLatin1("MaxDegree = %1").arg(d->ui.degreeMax->value()));
arguments.append(QString::fromLatin1("Continuity = %1").arg(d->ui.continuity->currentIndex()));
if (d->ui.checkBoxClosed->isChecked()) {
arguments.append(QString::fromLatin1("Closed = True"));
}
else {
arguments.append(QString::fromLatin1("Closed = False"));
}
if (d->ui.checkBox->isChecked()) {
int index = d->ui.paramType->currentIndex();
arguments.append(QString::fromLatin1("ParametrizationType = %1").arg(index));
}
if (d->ui.groupBoxSmooth->isChecked()) {
arguments.append(QString::fromLatin1("Weight1 = %1").arg(d->ui.curveLength->value()));
arguments.append(QString::fromLatin1("Weight2 = %1").arg(d->ui.curvature->value()));
arguments.append(QString::fromLatin1("Weight3 = %1").arg(d->ui.torsion->value()));
}
QString argument = arguments.join(QLatin1String(", "));
QString command = QString::fromLatin1("%1.addObject(\"Part::Spline\", \"Spline\").Shape = "
"ReverseEngineering.approxCurve(%2).toShape()")
.arg(document, argument);
tryCommand(command);
}
void FitBSplineCurveWidget::exeCommand(const QString& cmd)
{
Gui::WaitCursor wc;
Gui::Command::addModule(Gui::Command::App, "ReverseEngineering");
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Fit B-Spline"));
Gui::Command::runCommand(Gui::Command::Doc, cmd.toLatin1());
Gui::Command::commitCommand();
Gui::Command::updateActive();
}
void FitBSplineCurveWidget::tryCommand(const QString& cmd)
{
try {
exeCommand(cmd);
}
catch (const Base::Exception& e) {
Gui::Command::abortCommand();
e.ReportException();
}
}
void FitBSplineCurveWidget::changeEvent(QEvent* e)
{
QWidget::changeEvent(e);
if (e->type() == QEvent::LanguageChange) {
d->ui.retranslateUi(this);
}
}
/* TRANSLATOR ReenGui::TaskFitBSplineCurve */
TaskFitBSplineCurve::TaskFitBSplineCurve(const App::DocumentObjectT& obj)
: widget {new FitBSplineCurveWidget(obj)}
{
addTaskBox(widget);
}
void TaskFitBSplineCurve::open()
{}
bool TaskFitBSplineCurve::accept()
{
return widget->accept();
}
#include "moc_FitBSplineCurve.cpp"

View File

@@ -0,0 +1,81 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2024 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD 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 *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#ifndef REENGUI_FITBSPLINECURVE_H
#define REENGUI_FITBSPLINECURVE_H
#include <Gui/TaskView/TaskDialog.h>
#include <Gui/TaskView/TaskView.h>
namespace ReenGui
{
class FitBSplineCurveWidget: public QWidget
{
Q_OBJECT
public:
explicit FitBSplineCurveWidget(const App::DocumentObjectT&, QWidget* parent = nullptr);
~FitBSplineCurveWidget() override;
bool accept();
protected:
void changeEvent(QEvent* e) override;
private:
void toggleParametrizationType(bool on);
void toggleSmoothing(bool on);
void tryAccept();
void exeCommand(const QString&);
void tryCommand(const QString&);
private:
class Private;
Private* d;
};
class TaskFitBSplineCurve: public Gui::TaskView::TaskDialog
{
Q_OBJECT
public:
explicit TaskFitBSplineCurve(const App::DocumentObjectT&);
public:
void open() override;
bool accept() override;
QDialogButtonBox::StandardButtons getStandardButtons() const override
{
return QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
}
private:
FitBSplineCurveWidget* widget;
};
} // namespace ReenGui
#endif // REENGUI_FITBSPLINECURVE_H

View File

@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ReenGui::FitBSplineCurve</class>
<widget class="QWidget" name="ReenGui::FitBSplineCurve">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>375</height>
</rect>
</property>
<property name="windowTitle">
<string>Fit B-spline surface</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBoxU">
<property name="title">
<string>Parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Maximum degree</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="paramType">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Chord length</string>
</property>
</item>
<item>
<property name="text">
<string>Centripetal</string>
</property>
</item>
<item>
<property name="text">
<string>Iso-Parametric</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Continuity</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="degreeMax">
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>11</number>
</property>
<property name="value">
<number>6</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="degreeMin">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>11</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Parametrization type</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="continuity">
<item>
<property name="text">
<string>C0</string>
</property>
</item>
<item>
<property name="text">
<string>G1</string>
</property>
</item>
<item>
<property name="text">
<string>C1</string>
</property>
</item>
<item>
<property name="text">
<string>G2</string>
</property>
</item>
<item>
<property name="text">
<string>C2</string>
</property>
</item>
<item>
<property name="text">
<string>C3</string>
</property>
</item>
<item>
<property name="text">
<string>CN</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Minimum degree</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="checkBoxClosed">
<property name="text">
<string>Closed curve</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBoxSmooth">
<property name="title">
<string>Smoothing</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Torsion</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Curve length</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Curvature</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="curvature">
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="torsion">
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="curveLength">
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>15</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<tabstops>
<tabstop>degreeMin</tabstop>
<tabstop>degreeMax</tabstop>
<tabstop>continuity</tabstop>
<tabstop>checkBox</tabstop>
<tabstop>paramType</tabstop>
<tabstop>checkBoxClosed</tabstop>
<tabstop>groupBoxSmooth</tabstop>
<tabstop>curveLength</tabstop>
<tabstop>curvature</tabstop>
<tabstop>torsion</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -74,7 +74,8 @@ Gui::MenuItem* Workbench::setupMenuBar() const
<< "Reen_ApproxSphere"
<< "Reen_ApproxPolynomial"
<< "Separator"
<< "Reen_ApproxSurface";
<< "Reen_ApproxSurface"
<< "Reen_ApproxCurve";
*reen << approx;
return root;