Surface: Add task panel for blending curve

This commit is contained in:
wmayer
2025-05-22 18:11:45 +02:00
committed by Max Wilfinger
parent c9a434377a
commit 327222f780
6 changed files with 837 additions and 0 deletions

View File

@@ -0,0 +1,432 @@
/***************************************************************************
* Copyright (c) 2025 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 <QAction>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#endif
#include <App/Document.h>
#include <Base/Tools.h>
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/CommandT.h>
#include <Gui/Control.h>
#include <Gui/Selection/SelectionObject.h>
#include <Gui/Tools.h>
#include <Gui/Widgets.h>
#include <Mod/Part/Gui/ViewProvider.h>
#include <Mod/Surface/App/Blending/FeatureBlendCurve.h>
#include "TaskBlendCurve.h"
#include "ui_TaskBlendCurve.h"
#include "ViewProviderBlendCurve.h"
using namespace SurfaceGui;
class BlendCurvePanel::EdgeSelection: public Gui::SelectionFilterGate
{
public:
EdgeSelection(Surface::FeatureBlendCurve* editedObject)
: Gui::SelectionFilterGate(nullPointer())
, editedObject(editedObject)
{}
~EdgeSelection() override = default;
/**
* Allow the user to pick only edges.
*/
bool allow(App::Document*, App::DocumentObject* pObj, const char* sSubName) override
{
// don't allow references to itself
if (pObj == editedObject) {
return false;
}
if (!pObj->isDerivedFrom<Part::Feature>()) {
return false;
}
if (Base::Tools::isNullOrEmpty(sSubName)) {
return false;
}
std::string element(sSubName);
return (element.substr(0, 4) == "Edge");
}
private:
Surface::FeatureBlendCurve* editedObject;
};
// ----------------------------------------------------------------------------
BlendCurvePanel::BlendCurvePanel(ViewProviderBlendCurve* vp)
: ui(new Ui_BlendCurve())
, vp(vp)
{
ui->setupUi(this);
initControls();
setupConnections();
}
BlendCurvePanel::~BlendCurvePanel() = default;
void BlendCurvePanel::setupConnections()
{
// clang-format off
connect(ui->buttonFirstEdge,
&QToolButton::toggled,
this,
&BlendCurvePanel::onFirstEdgeButton);
connect(ui->buttonSecondEdge,
&QToolButton::toggled,
this,
&BlendCurvePanel::onSecondEdgeButton);
connect(ui->contFirstEdge,
qOverload<int>(&QComboBox::currentIndexChanged),
this,
&BlendCurvePanel::onFirstEdgeContChanged);
connect(ui->contSecondEdge,
qOverload<int>(&QComboBox::currentIndexChanged),
this,
&BlendCurvePanel::onSecondEdgeContChanged);
connect(ui->paramFirstEdge,
qOverload<double>(&QDoubleSpinBox::valueChanged),
this,
&BlendCurvePanel::onFirstEdgeParameterChanged);
connect(ui->paramSecondEdge,
qOverload<double>(&QDoubleSpinBox::valueChanged),
this,
&BlendCurvePanel::onSecondEdgeParameterChanged);
connect(ui->sizeFirstEdge,
qOverload<double>(&QDoubleSpinBox::valueChanged),
this,
&BlendCurvePanel::onFirstEdgeSizeChanged);
connect(ui->sizeSecondEdge,
qOverload<double>(&QDoubleSpinBox::valueChanged),
this,
&BlendCurvePanel::onSecondEdgeSizeChanged);
// clang-format on
}
void BlendCurvePanel::initControls()
{
initSubLinks();
initContinuity();
initParameter();
initSize();
}
void BlendCurvePanel::initSubLinks()
{
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
ui->firstEdgeEdit->setText(linkToString(fea->StartEdge));
ui->secondEdgeEdit->setText(linkToString(fea->EndEdge));
}
void BlendCurvePanel::initContinuity()
{
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
constexpr long maxCont = 4;
// clang-format off
ui->contFirstEdge->setCurrentIndex(static_cast<int>(std::min(maxCont, fea->StartContinuity.getValue())));
ui->contSecondEdge->setCurrentIndex(static_cast<int>(std::min(maxCont, fea->EndContinuity.getValue())));
// clang-format on
}
void BlendCurvePanel::initParameter()
{
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
const double minPara = fea->StartParameter.getMinimum();
const double maxPara = fea->StartParameter.getMaximum();
const double stepsPara = fea->StartParameter.getStepSize();
ui->paramFirstEdge->setRange(minPara, maxPara);
ui->paramFirstEdge->setSingleStep(stepsPara);
ui->paramSecondEdge->setRange(minPara, maxPara);
ui->paramSecondEdge->setSingleStep(stepsPara);
ui->paramFirstEdge->setValue(fea->StartParameter.getValue());
ui->paramSecondEdge->setValue(fea->EndParameter.getValue());
}
void BlendCurvePanel::initSize()
{
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
const double minSize = fea->StartSize.getMinimum();
const double maxSize = fea->StartSize.getMaximum();
const double stepsSize = fea->StartSize.getStepSize();
ui->sizeFirstEdge->setRange(minSize, maxSize);
ui->sizeFirstEdge->setSingleStep(stepsSize);
ui->sizeSecondEdge->setRange(minSize, maxSize);
ui->sizeSecondEdge->setSingleStep(stepsSize);
ui->sizeFirstEdge->setValue(fea->StartSize.getValue());
ui->sizeSecondEdge->setValue(fea->EndSize.getValue());
}
void BlendCurvePanel::onFirstEdgeButton(bool checked)
{
if (checked) {
onStartSelection();
selectionMode = StartEdge;
onUncheckSecondEdgeButton();
}
else {
exitSelectionMode();
}
}
void BlendCurvePanel::onSecondEdgeButton(bool checked)
{
if (checked) {
onStartSelection();
selectionMode = EndEdge;
onUncheckFirstEdgeButton();
}
else {
exitSelectionMode();
}
}
void BlendCurvePanel::onUncheckFirstEdgeButton()
{
QSignalBlocker block(ui->buttonFirstEdge);
ui->buttonFirstEdge->setChecked(false);
}
void BlendCurvePanel::onUncheckSecondEdgeButton()
{
QSignalBlocker block(ui->buttonSecondEdge);
ui->buttonSecondEdge->setChecked(false);
}
void BlendCurvePanel::onFirstEdgeContChanged(int index)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->StartContinuity.setValue(index);
fea->recomputeFeature();
}
void BlendCurvePanel::onSecondEdgeContChanged(int index)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->EndContinuity.setValue(index);
fea->recomputeFeature();
}
void BlendCurvePanel::onFirstEdgeParameterChanged(double value)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->StartParameter.setValue(value);
fea->recomputeFeature();
}
void BlendCurvePanel::onSecondEdgeParameterChanged(double value)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->EndParameter.setValue(value);
fea->recomputeFeature();
}
void BlendCurvePanel::onFirstEdgeSizeChanged(double value)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->StartSize.setValue(value);
fea->recomputeFeature();
}
void BlendCurvePanel::onSecondEdgeSizeChanged(double value)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->EndSize.setValue(value);
fea->recomputeFeature();
}
void BlendCurvePanel::onStartSelection()
{
if (vp.expired()) {
return;
}
auto gate = new EdgeSelection(vp->getObject<Surface::FeatureBlendCurve>());
Gui::Selection().addSelectionGate(gate);
}
void BlendCurvePanel::exitSelectionMode()
{
Gui::Selection().clearSelection();
Gui::Selection().rmvSelectionGate();
selectionMode = None;
}
void BlendCurvePanel::onSelectionChanged(const Gui::SelectionChanges& msg)
{
if (selectionMode == None) {
return;
}
if (msg.Type != Gui::SelectionChanges::AddSelection) {
return;
}
if (selectionMode == StartEdge) {
setStartEdge(msg.Object.getObject(), msg.Object.getSubName());
onUncheckFirstEdgeButton();
}
else if (selectionMode == EndEdge) {
setEndEdge(msg.Object.getObject(), msg.Object.getSubName());
onUncheckSecondEdgeButton();
}
QTimer::singleShot(50, this, &BlendCurvePanel::exitSelectionMode);
}
QString BlendCurvePanel::linkToString(const App::PropertyLinkSub& link)
{
auto obj = link.getValue();
const auto& sub = link.getSubValues();
std::string name = sub.empty() ? "" : sub.front();
return QString::fromLatin1("%1 [%2]").arg(QString::fromLatin1(obj->Label.getValue()),
QString::fromStdString(name));
}
void BlendCurvePanel::setStartEdge(App::DocumentObject* obj, const std::string& subname)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->StartEdge.setValue(obj, {{subname}});
fea->recomputeFeature();
ui->firstEdgeEdit->setText(linkToString(fea->StartEdge));
}
void BlendCurvePanel::setEndEdge(App::DocumentObject* obj, const std::string& subname)
{
if (vp.expired()) {
return;
}
auto fea = vp->getObject<Surface::FeatureBlendCurve>();
fea->EndEdge.setValue(obj, {{subname}});
fea->recomputeFeature();
ui->secondEdgeEdit->setText(linkToString(fea->EndEdge));
}
void BlendCurvePanel::changeEvent(QEvent* e)
{
if (e->type() == QEvent::LanguageChange) {
ui->retranslateUi(this);
}
else {
QWidget::changeEvent(e);
}
}
void BlendCurvePanel::open()
{
checkOpenCommand();
clearSelection();
}
void BlendCurvePanel::clearSelection()
{
Gui::Selection().clearSelection();
}
void BlendCurvePanel::checkOpenCommand()
{
if (!Gui::Command::hasPendingCommand()) {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Edit blending curve"));
}
}
bool BlendCurvePanel::accept()
{
Gui::cmdGuiDocument(vp->getObject(), "resetEdit()");
Gui::Command::commitCommand();
Gui::Command::updateActive();
return true;
}
bool BlendCurvePanel::reject()
{
Gui::cmdGuiDocument(vp->getObject(), "resetEdit()");
Gui::Command::abortCommand();
return true;
}
// ----------------------------------------------------------------------------
TaskBlendCurve::TaskBlendCurve(ViewProviderBlendCurve* vp)
: widget {new BlendCurvePanel(vp)}
{
addTaskBox(Gui::BitmapFactory().pixmap("Surface_BlendCurve"), widget);
}
void TaskBlendCurve::open()
{
widget->open();
}
bool TaskBlendCurve::accept()
{
return widget->accept();
}
bool TaskBlendCurve::reject()
{
return widget->reject();
}
#include "moc_TaskBlendCurve.cpp"

View File

@@ -0,0 +1,128 @@
/***************************************************************************
* Copyright (c) 2025 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 SURFACEGUI_TASKBLENDCURVE_H
#define SURFACEGUI_TASKBLENDCURVE_H
#include <memory>
#include <Gui/DocumentObserver.h>
#include <Gui/TaskView/TaskDialog.h>
#include <Gui/TaskView/TaskView.h>
class QListWidgetItem;
namespace Gui
{
class ButtonGroup;
}
namespace SurfaceGui
{
class ViewProviderBlendCurve;
class Ui_BlendCurve;
class BlendCurvePanel: public QWidget, public Gui::SelectionObserver
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(BlendCurvePanel)
private:
std::unique_ptr<Ui_BlendCurve> ui;
Gui::WeakPtrT<ViewProviderBlendCurve> vp;
public:
explicit BlendCurvePanel(ViewProviderBlendCurve* vp);
~BlendCurvePanel() override;
void open();
void checkOpenCommand();
bool accept();
bool reject();
protected:
void changeEvent(QEvent* e) override;
void onSelectionChanged(const Gui::SelectionChanges& msg) override;
private:
void setupConnections();
void initControls();
void initSubLinks();
void initContinuity();
void initParameter();
void initSize();
void onFirstEdgeButton(bool checked);
void onSecondEdgeButton(bool checked);
void onUncheckFirstEdgeButton();
void onUncheckSecondEdgeButton();
void onFirstEdgeContChanged(int index);
void onSecondEdgeContChanged(int index);
void onFirstEdgeParameterChanged(double value);
void onSecondEdgeParameterChanged(double value);
void onFirstEdgeSizeChanged(double value);
void onSecondEdgeSizeChanged(double value);
void onStartSelection();
void clearSelection();
void exitSelectionMode();
void setStartEdge(App::DocumentObject* obj, const std::string& subname);
void setEndEdge(App::DocumentObject* obj, const std::string& subname);
static QString linkToString(const App::PropertyLinkSub& link);
class EdgeSelection;
enum SelectionMode
{
None,
StartEdge,
EndEdge
};
SelectionMode selectionMode = None;
};
class TaskBlendCurve: public Gui::TaskView::TaskDialog
{
Q_OBJECT
public:
TaskBlendCurve(ViewProviderBlendCurve* vp);
public:
void open() override;
bool accept() override;
bool reject() override;
QDialogButtonBox::StandardButtons getStandardButtons() const override
{
return QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
}
private:
BlendCurvePanel* widget;
};
} // namespace SurfaceGui
#endif // SURFACEGUI_TASKBLENDCURVE_H

View File

@@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SurfaceGui::BlendCurve</class>
<widget class="QWidget" name="SurfaceGui::BlendCurve">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>297</width>
<height>352</height>
</rect>
</property>
<property name="windowTitle">
<string>Blending curve</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Start edge</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QToolButton" name="buttonFirstEdge">
<property name="text">
<string>Edge</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="firstEdgeEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Continuity</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="contFirstEdge">
<item>
<property name="text">
<string notr="true">C0</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G1</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G2</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G3</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G4</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Parameter</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::DoubleSpinBox" name="paramFirstEdge">
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::DoubleSpinBox" name="sizeFirstEdge">
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>End edge</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QToolButton" name="buttonSecondEdge">
<property name="text">
<string>Edge</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="secondEdgeEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Continuity</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="contSecondEdge">
<item>
<property name="text">
<string notr="true">C0</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G1</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G2</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G3</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">G4</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Parameter</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Gui::DoubleSpinBox" name="paramSecondEdge">
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="Gui::DoubleSpinBox" name="sizeSecondEdge">
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::DoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>Gui/SpinBox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>buttonFirstEdge</tabstop>
<tabstop>firstEdgeEdit</tabstop>
<tabstop>contFirstEdge</tabstop>
<tabstop>paramFirstEdge</tabstop>
<tabstop>sizeFirstEdge</tabstop>
<tabstop>buttonSecondEdge</tabstop>
<tabstop>secondEdgeEdit</tabstop>
<tabstop>contSecondEdge</tabstop>
<tabstop>paramSecondEdge</tabstop>
<tabstop>sizeSecondEdge</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -21,9 +21,15 @@
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <QMenu>
#endif
#include "ViewProviderBlendCurve.h"
#include "TaskBlendCurve.h"
#include <Gui/ActionFunction.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Control.h>
PROPERTY_SOURCE(SurfaceGui::ViewProviderBlendCurve, PartGui::ViewProviderSpline)
@@ -35,4 +41,41 @@ QIcon ViewProviderBlendCurve::getIcon() const
return Gui::BitmapFactory().pixmap("Surface_BlendCurve");
}
void ViewProviderBlendCurve::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
{
auto func = new Gui::ActionFunction(menu);
QAction* act = menu->addAction(
QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue())));
act->setData(QVariant((int)ViewProvider::Default));
func->trigger(act, [this]() {
this->startDefaultEditMode();
});
ViewProviderSpline::setupContextMenu(menu, receiver, member);
}
bool ViewProviderBlendCurve::setEdit(int ModNum)
{
if (ModNum == ViewProvider::Default) {
if (Gui::Control().activeDialog()) {
return false;
}
auto dlg = new TaskBlendCurve(this);
Gui::Control().showDialog(dlg);
return true;
}
return ViewProviderSpline::setEdit(ModNum);
}
void ViewProviderBlendCurve::unsetEdit(int ModNum)
{
if (ModNum == ViewProvider::Default) {
Gui::Control().closeDialog();
}
else {
ViewProviderSpline::unsetEdit(ModNum);
}
}
} // namespace SurfaceGui

View File

@@ -34,6 +34,11 @@ class ViewProviderBlendCurve: public PartGui::ViewProviderSpline
public:
QIcon getIcon() const override;
void setupContextMenu(QMenu* menu, QObject* receiver, const char* member) override;
protected:
bool setEdit(int ModNum) override;
void unsetEdit(int ModNum) override;
};
} // namespace SurfaceGui

View File

@@ -20,9 +20,12 @@ SET(SurfaceGui_UIC_SRCS
TaskFillingVertex.ui
TaskGeomFillSurface.ui
TaskSections.ui
Blending/TaskBlendCurve.ui
)
SET(BlendingGui_SRCS
Blending/TaskBlendCurve.cpp
Blending/TaskBlendCurve.h
Blending/ViewProviderBlendCurve.cpp
Blending/ViewProviderBlendCurve.h
)