Files
create/src/Mod/PartDesign/Gui/TaskPatternParameters.cpp
2025-11-11 13:49:01 +01:00

429 lines
15 KiB
C++

/******************************************************************************
* Copyright (c) 2012 Jan Rheinländer <jrheinlaender@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 <QMessageBox>
#include <QTimer>
#include <QVBoxLayout>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/Origin.h>
#include <Base/Console.h>
#include <Gui/Application.h>
#include <Gui/MainWindow.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Selection/Selection.h>
#include <Gui/Command.h>
#include <Gui/ViewProviderCoordinateSystem.h>
#include <Mod/PartDesign/App/Body.h>
#include <Mod/PartDesign/App/DatumLine.h>
#include <Mod/PartDesign/App/DatumPlane.h>
#include <Mod/PartDesign/App/FeatureLinearPattern.h>
#include <Mod/PartDesign/App/FeaturePolarPattern.h>
#include <Mod/Part/Gui/PatternParametersWidget.h>
#include "ui_TaskPatternParameters.h"
#include "TaskPatternParameters.h"
#include "ReferenceSelection.h"
#include "TaskMultiTransformParameters.h"
using namespace PartDesignGui;
using namespace Gui;
/* TRANSLATOR PartDesignGui::TaskPatternParameters */
TaskPatternParameters::TaskPatternParameters(ViewProviderTransformed* TransformedView, QWidget* parent)
: TaskTransformedParameters(TransformedView, parent)
, ui(new Ui_TaskPatternParameters)
{
setupUI();
}
TaskPatternParameters::TaskPatternParameters(
TaskMultiTransformParameters* parentTask,
QWidget* parameterWidget
)
: TaskTransformedParameters(parentTask)
, ui(new Ui::TaskPatternParameters)
{
setupParameterUI(parameterWidget);
}
void TaskPatternParameters::setupParameterUI(QWidget* widget)
{
ui->setupUi(widget); // Setup the Task's own minimal UI (placeholder)
QMetaObject::connectSlotsByName(this);
// --- Create and Embed the Parameter Widget ---
auto pattern = getObject();
if (!pattern) {
return;
}
PartGui::PatternType type = pattern->isDerivedFrom<PartDesign::LinearPattern>()
? PartGui::PatternType::Linear
: PartGui::PatternType::Polar;
// Set first direction widget
parametersWidget = new PartGui::PatternParametersWidget(type, widget);
auto* placeholderLayout = new QVBoxLayout(ui->parametersWidgetPlaceholder);
placeholderLayout->setContentsMargins(0, 0, 0, 0);
placeholderLayout->addWidget(parametersWidget);
ui->parametersWidgetPlaceholder->setLayout(placeholderLayout);
auto* sketch = dynamic_cast<Part::Part2DObject*>(getSketchObject());
this->fillAxisCombo(parametersWidget->dirLinks, sketch);
connect(
parametersWidget,
&PartGui::PatternParametersWidget::requestReferenceSelection,
this,
&TaskPatternParameters::onParameterWidgetRequestReferenceSelection
);
connect(
parametersWidget,
&PartGui::PatternParametersWidget::parametersChanged,
this,
&TaskPatternParameters::onParameterWidgetParametersChanged
);
// Add second direction widget if necessary
if (type == PartGui::PatternType::Linear) {
parametersWidget2 = new PartGui::PatternParametersWidget(type, widget);
auto* placeholderLayout2 = new QVBoxLayout(ui->parametersWidgetPlaceholder2);
placeholderLayout2->setContentsMargins(0, 0, 0, 0);
placeholderLayout2->addWidget(parametersWidget2);
ui->parametersWidgetPlaceholder2->setLayout(placeholderLayout2);
this->fillAxisCombo(parametersWidget2->dirLinks, sketch);
connect(
parametersWidget2,
&PartGui::PatternParametersWidget::requestReferenceSelection,
this,
&TaskPatternParameters::onParameterWidgetRequestReferenceSelection2
);
connect(
parametersWidget2,
&PartGui::PatternParametersWidget::parametersChanged,
this,
&TaskPatternParameters::onParameterWidgetParametersChanged
);
parametersWidget2->setTitle(tr("Direction 2"));
parametersWidget2->setCheckable(true);
}
bindProperties();
// --- Task Specific Setup ---
showOriginAxes(true); // Show origin helper axes
updateViewTimer = new QTimer(this);
updateViewTimer->setSingleShot(true);
updateViewTimer->setInterval(getUpdateViewTimeout());
connect(updateViewTimer, &QTimer::timeout, this, &TaskPatternParameters::onUpdateViewTimer);
}
void TaskPatternParameters::bindProperties()
{
auto pattern = getObject();
if (!pattern) {
return;
}
if (pattern->isDerivedFrom<PartDesign::LinearPattern>()) {
auto* linear = static_cast<PartDesign::LinearPattern*>(pattern);
parametersWidget->bindProperties(
&linear->Direction,
&linear->Reversed,
&linear->Mode,
&linear->Length,
&linear->Offset,
&linear->SpacingPattern,
&linear->Occurrences,
linear
);
parametersWidget2->bindProperties(
&linear->Direction2,
&linear->Reversed2,
&linear->Mode2,
&linear->Length2,
&linear->Offset2,
&linear->SpacingPattern2,
&linear->Occurrences2,
linear
);
}
else if (pattern->isDerivedFrom<PartDesign::PolarPattern>()) {
auto* polar = static_cast<PartDesign::PolarPattern*>(pattern);
parametersWidget->bindProperties(
&polar->Axis,
&polar->Reversed,
&polar->Mode,
&polar->Angle,
&polar->Offset,
&polar->SpacingPattern,
&polar->Occurrences,
polar
);
}
else {
Base::Console().warning(
"PatternParametersWidget property binding failed. Something is wrong please report.\n"
);
}
}
void TaskPatternParameters::retranslateParameterUI(QWidget* widget)
{
ui->retranslateUi(widget);
}
void TaskPatternParameters::updateUI()
{
if (parametersWidget) {
parametersWidget->updateUI();
}
if (parametersWidget2) {
parametersWidget2->updateUI();
}
}
// --- Task-Specific Logic ---
void TaskPatternParameters::showOriginAxes(bool show)
{
PartDesign::Body* body = PartDesign::Body::findBodyOf(getObject());
if (body) {
try {
App::Origin* origin = body->getOrigin();
auto vpOrigin = static_cast<ViewProviderCoordinateSystem*>(
Gui::Application::Instance->getViewProvider(origin)
);
if (show) {
vpOrigin->setTemporaryVisibility(Gui::DatumElement::Axes);
}
else {
vpOrigin->resetTemporaryVisibility();
}
}
catch (const Base::Exception& ex) {
Base::Console().error("TaskPatternParameters: Error accessing origin axes: %s\n", ex.what());
}
}
}
void TaskPatternParameters::enterReferenceSelectionMode()
{
if (selectionMode == SelectionMode::Reference) {
return;
}
hideObject(); // Hide the pattern feature itself
showBase(); // Show the base features/body
Gui::Selection().clearSelection();
// Add selection gate (allow edges, faces, potentially datums)
addReferenceSelectionGate(AllowSelection::EDGE | AllowSelection::FACE | AllowSelection::PLANAR);
Gui::getMainWindow()->showMessage(
tr("Select a direction reference (edge, face, datum line)")
); // User feedback
}
void TaskPatternParameters::exitReferenceSelectionMode()
{
exitSelectionMode();
hideBase();
Gui::getMainWindow()->showMessage(QString());
activeDirectionWidget = nullptr;
}
// --- SLOTS ---
void TaskPatternParameters::onUpdateViewTimer()
{
// Recompute is triggered when parameters change and this timer fires
setupTransaction(); // Group potential property changes
recomputeFeature();
updateUI();
}
void TaskPatternParameters::onParameterWidgetRequestReferenceSelection()
{
// The embedded widget wants to enter reference selection mode
activeDirectionWidget = parametersWidget;
enterReferenceSelectionMode();
selectionMode = SelectionMode::Reference;
}
void TaskPatternParameters::onParameterWidgetRequestReferenceSelection2()
{
// The embedded widget wants to enter reference selection mode
activeDirectionWidget = parametersWidget2;
enterReferenceSelectionMode();
selectionMode = SelectionMode::Reference;
}
void TaskPatternParameters::onParameterWidgetParametersChanged()
{
// A parameter in the embedded widget changed, trigger a recompute
if (blockUpdate) {
return; // Avoid loops if change originated from Task update
}
kickUpdateViewTimer(); // Debounce recompute
}
void TaskPatternParameters::onUpdateView(bool on)
{
// This might be less relevant now if recomputes are triggered by parametersChanged
blockUpdate = !on;
if (on) {
kickUpdateViewTimer();
}
}
void TaskPatternParameters::kickUpdateViewTimer() const
{
updateViewTimer->start();
}
void TaskPatternParameters::onSelectionChanged(const Gui::SelectionChanges& msg)
{
// Handle selection ONLY when in reference selection mode
if (selectionMode == SelectionMode::None || msg.Type != Gui::SelectionChanges::AddSelection) {
return;
}
if (originalSelected(msg)) {
exitSelectionMode();
return;
}
auto patternObj = getObject();
if (!patternObj) {
return;
}
std::vector<std::string> directions;
App::DocumentObject* selObj = nullptr;
getReferencedSelection(patternObj, msg, selObj, directions);
if (!selObj) {
Base::Console().warning(
tr("Invalid selection. Select an edge, planar face, or datum line.").toStdString().c_str()
);
return;
}
// Note: ReferenceSelection has already checked the selection for validity
if (selectionMode == SelectionMode::Reference || selObj->isDerivedFrom<App::Line>()) {
setupTransaction();
if (patternObj->isDerivedFrom<PartDesign::LinearPattern>()) {
auto* linearPattern = static_cast<PartDesign::LinearPattern*>(patternObj);
if (activeDirectionWidget == parametersWidget) {
linearPattern->Direction.setValue(selObj, directions);
}
else {
linearPattern->Direction2.setValue(selObj, directions);
}
}
else if (patternObj->isDerivedFrom<PartDesign::PolarPattern>()) {
auto* polarPattern = static_cast<PartDesign::PolarPattern*>(patternObj);
polarPattern->Axis.setValue(selObj, directions);
}
recomputeFeature();
updateUI();
}
exitReferenceSelectionMode();
}
TaskPatternParameters::~TaskPatternParameters()
{
showOriginAxes(false); // Clean up temporary visibility
exitReferenceSelectionMode(); // Ensure gates are removed etc.
// ui unique_ptr handles deletion
// parametersWidget is deleted by Qt parent mechanism if added to layout correctly
}
void TaskPatternParameters::apply()
{
auto pattern = getObject();
if (!pattern || !parametersWidget) {
return;
}
std::vector<std::string> dirs;
App::DocumentObject* obj = nullptr;
parametersWidget->getAxis(obj, dirs);
std::string direction = buildLinkSingleSubPythonStr(obj, dirs);
bool isLinear = pattern->isDerivedFrom<PartDesign::LinearPattern>();
const char* propName = isLinear ? "Direction = " : "Axis = ";
FCMD_OBJ_CMD(pattern, propName << direction.c_str());
FCMD_OBJ_CMD(pattern, "Reversed = " << parametersWidget->getReverse());
FCMD_OBJ_CMD(pattern, "Mode = " << parametersWidget->getMode());
parametersWidget->applyQuantitySpinboxes();
FCMD_OBJ_CMD(pattern, "SpacingPattern = " << parametersWidget->getSpacingPatternsAsString());
if (parametersWidget2) {
parametersWidget2->getAxis(obj, dirs);
direction = buildLinkSingleSubPythonStr(obj, dirs);
FCMD_OBJ_CMD(pattern, "Direction2 = " << direction.c_str());
FCMD_OBJ_CMD(pattern, "Reversed2 = " << parametersWidget2->getReverse());
FCMD_OBJ_CMD(pattern, "Mode2 = " << parametersWidget2->getMode());
parametersWidget2->applyQuantitySpinboxes();
FCMD_OBJ_CMD(pattern, "SpacingPattern2 = " << parametersWidget2->getSpacingPatternsAsString());
}
// The user may have changed a value and immediately hit 'OK' or Enter.
// This triggers accept() before the update timer for the 3D view has a
// chance to fire. If the timer is active, it means a recompute is
// pending.
if (updateViewTimer && updateViewTimer->isActive()) {
updateViewTimer->stop();
recomputeFeature();
}
}
//**************************************************************************
// TaskDialog Implementation (Remains largely the same)
//**************************************************************************
TaskDlgLinearPatternParameters::TaskDlgLinearPatternParameters(
ViewProviderTransformed* LinearPatternView
)
: TaskDlgTransformedParameters(LinearPatternView) // Use base class constructor
{
// Create the specific parameter task panel
parameter = new TaskPatternParameters(LinearPatternView);
// Add it to the dialog's content list
Content.push_back(parameter);
Content.push_back(preview);
}
#include "moc_TaskPatternParameters.cpp"