/****************************************************************************** * Copyright (c) 2012 Jan Rheinländer * * * * 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 #include #include #include #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() ? 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(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()) { auto* linear = static_cast(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()) { auto* polar = static_cast(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( 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 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()) { setupTransaction(); if (patternObj->isDerivedFrom()) { auto* linearPattern = static_cast(patternObj); if (activeDirectionWidget == parametersWidget) { linearPattern->Direction.setValue(selObj, directions); } else { linearPattern->Direction2.setValue(selObj, directions); } } else if (patternObj->isDerivedFrom()) { auto* polarPattern = static_cast(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 dirs; App::DocumentObject* obj = nullptr; parametersWidget->getAxis(obj, dirs); std::string direction = buildLinkSingleSubPythonStr(obj, dirs); bool isLinear = pattern->isDerivedFrom(); 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"