Merge pull request #22389 from PaddleStroke/pd_transform
PartDesign: Transform tools
This commit is contained in:
@@ -1166,6 +1166,7 @@ SOURCE_GROUP("View3D\\Inventor" FILES ${Inventor_SRCS})
|
||||
|
||||
# The widget sources
|
||||
SET(Widget_CPP_SRCS
|
||||
ComboLinks.cpp
|
||||
FileDialog.cpp
|
||||
MainWindow.cpp
|
||||
MainWindowPy.cpp
|
||||
@@ -1189,6 +1190,7 @@ SET(Widget_CPP_SRCS
|
||||
FontScaledSVG.cpp
|
||||
)
|
||||
SET(Widget_HPP_SRCS
|
||||
ComboLinks.h
|
||||
FileDialog.h
|
||||
MainWindow.h
|
||||
MainWindowPy.h
|
||||
|
||||
218
src/Gui/ComboLinks.cpp
Normal file
218
src/Gui/ComboLinks.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* Copyright (c) 2012 Jan Rheinländer jrheinlaender@users.sourceforge.net *
|
||||
* Copyright (c) 2025 AstoCAD <hello@astocad.com> *
|
||||
* *
|
||||
* 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"
|
||||
|
||||
#include "ComboLinks.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QComboBox>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
|
||||
namespace Gui {
|
||||
|
||||
ComboLinks::ComboLinks(QComboBox* combo)
|
||||
: combo(combo)
|
||||
{
|
||||
if (combo) {
|
||||
combo->clear();
|
||||
}
|
||||
}
|
||||
|
||||
ComboLinks::~ComboLinks()
|
||||
{
|
||||
clear(); // Deletes owned pointers in linksInList
|
||||
}
|
||||
|
||||
void ComboLinks::setCombo(QComboBox* combobox)
|
||||
{
|
||||
clear(); // Clear old state if any
|
||||
combo = combobox;
|
||||
if (combo) {
|
||||
combo->clear();
|
||||
}
|
||||
}
|
||||
|
||||
int ComboLinks::addLink(const App::PropertyLinkSub& lnk, const QString& itemText, int userData)
|
||||
{
|
||||
if (!combo) {
|
||||
return -1;
|
||||
}
|
||||
int newIndex = combo->count();
|
||||
int finalUserData = (userData == -1) ? newIndex : userData;
|
||||
|
||||
// Store link internally (create a copy)
|
||||
auto* newLink = new App::PropertyLinkSub();
|
||||
newLink->Paste(lnk);
|
||||
linksInList.push_back(newLink);
|
||||
|
||||
// Add to combo box
|
||||
combo->addItem(itemText, QVariant(finalUserData));
|
||||
|
||||
// Track document context from the first valid object link
|
||||
if (!doc && newLink->getValue()) {
|
||||
doc = newLink->getValue()->getDocument();
|
||||
}
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
int ComboLinks::addLink(App::DocumentObject* linkObj, const std::string& linkSubname, const QString& itemText, int userData)
|
||||
{
|
||||
App::PropertyLinkSub lnk;
|
||||
std::vector<std::string> sub = { linkSubname };
|
||||
// Handle empty subname correctly
|
||||
if (linkSubname.empty()) {
|
||||
sub.clear();
|
||||
}
|
||||
lnk.setValue(linkObj, sub);
|
||||
return addLink(lnk, itemText, userData);
|
||||
}
|
||||
|
||||
int ComboLinks::addLinkBefore(const App::PropertyLinkSub& lnk, const QString& itemText, int targetUserData, int userData)
|
||||
{
|
||||
if (!combo) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int insertPos = -1;
|
||||
for (int i = 0; i < combo->count(); ++i) {
|
||||
if (combo->itemData(i).toInt() == targetUserData) {
|
||||
insertPos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Store link internally (create a copy)
|
||||
auto* newLink = new App::PropertyLinkSub();
|
||||
newLink->Paste(lnk);
|
||||
|
||||
int finalUserData = (userData == -1) ? ((insertPos == -1) ? count() : insertPos) : userData;
|
||||
|
||||
if (insertPos != -1) {
|
||||
linksInList.insert(linksInList.begin() + insertPos, newLink);
|
||||
combo->insertItem(insertPos, itemText, QVariant(finalUserData));
|
||||
}
|
||||
else {
|
||||
// Target not found, append to end
|
||||
insertPos = combo->count();
|
||||
linksInList.push_back(newLink);
|
||||
combo->addItem(itemText, QVariant(finalUserData));
|
||||
}
|
||||
|
||||
// Update user data for subsequent items if default (-1) was used and inserting
|
||||
if (userData == -1 && insertPos != -1 && insertPos < count() - 1) {
|
||||
for (int i = insertPos + 1; i < count(); ++i) {
|
||||
if (combo->itemData(i).toInt() == i - 1) { // Check if it was using default index
|
||||
combo->setItemData(i, QVariant(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Track document context
|
||||
if (!doc && newLink->getValue()) {
|
||||
doc = newLink->getValue()->getDocument();
|
||||
}
|
||||
return insertPos;
|
||||
}
|
||||
|
||||
|
||||
void ComboLinks::clear()
|
||||
{
|
||||
if (combo) {
|
||||
// Block signals while clearing to prevent issues if connected elsewhere
|
||||
QSignalBlocker blocker(combo);
|
||||
combo->clear();
|
||||
}
|
||||
for (App::PropertyLinkSub* linkPtr : linksInList) {
|
||||
delete linkPtr; // Delete the objects pointed to
|
||||
}
|
||||
linksInList.clear(); // Clear the vector itself
|
||||
doc = nullptr; // Reset document context
|
||||
}
|
||||
|
||||
App::PropertyLinkSub& ComboLinks::getLink(int index) const
|
||||
{
|
||||
if (index < 0 || static_cast<size_t>(index) >= linksInList.size()) {
|
||||
throw Base::IndexError("ComboLinks::getLink: Index out of range");
|
||||
}
|
||||
App::PropertyLinkSub* linkPtr = linksInList[static_cast<size_t>(index)];
|
||||
// Perform validity check only if we have a document context and a linked object
|
||||
if (doc && linkPtr->getValue() && !(doc->isIn(linkPtr->getValue()))) {
|
||||
throw Base::ValueError("Linked object is not in the document; it may have been deleted");
|
||||
}
|
||||
return *linkPtr;
|
||||
}
|
||||
|
||||
App::PropertyLinkSub& ComboLinks::getCurrentLink() const
|
||||
{
|
||||
assert(combo);
|
||||
return getLink(combo->currentIndex());
|
||||
|
||||
}
|
||||
|
||||
int ComboLinks::getUserData(int index) const
|
||||
{
|
||||
if (!combo || index < 0 || index >= combo->count()) {
|
||||
return -1; // Indicate invalid index or no combo
|
||||
}
|
||||
return combo->itemData(index).toInt();
|
||||
}
|
||||
|
||||
int ComboLinks::getCurrentUserData() const
|
||||
{
|
||||
if (!combo) {
|
||||
return -1;
|
||||
}
|
||||
return combo->currentData().toInt();
|
||||
}
|
||||
|
||||
|
||||
int ComboLinks::setCurrentLink(const App::PropertyLinkSub& lnk)
|
||||
{
|
||||
if (!combo) {
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < linksInList.size(); ++i) {
|
||||
const App::PropertyLinkSub* it = linksInList[i];
|
||||
// Compare object pointer and sub-values vector
|
||||
if (lnk.getValue() == it->getValue() && lnk.getSubValues() == it->getSubValues()) {
|
||||
QSignalBlocker blocker(combo);
|
||||
combo->setCurrentIndex(static_cast<int>(i));
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
int ComboLinks::count() const
|
||||
{
|
||||
return combo ? combo->count() : 0;
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
168
src/Gui/ComboLinks.h
Normal file
168
src/Gui/ComboLinks.h
Normal file
@@ -0,0 +1,168 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 AstoCAD <hello@astocad.com> *
|
||||
* *
|
||||
* 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 GUI_COMBOLINKS_H
|
||||
#define GUI_COMBOLINKS_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <App/PropertyLinks.h>
|
||||
#include <Base/Exception.h>
|
||||
|
||||
// Forward declarations
|
||||
class QComboBox;
|
||||
class QString;
|
||||
namespace App {
|
||||
class Document;
|
||||
class DocumentObject;
|
||||
}
|
||||
|
||||
namespace Gui {
|
||||
|
||||
/**
|
||||
* @brief The ComboLinks class is a helper class that binds to a QComboBox and
|
||||
* provides an interface to add App::PropertyLinkSub items, retrieve links,
|
||||
* and select items by link value.
|
||||
*/
|
||||
class GuiExport ComboLinks
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor. Binds to an existing QComboBox.
|
||||
* @param combo The combo box to manage. It will be cleared upon binding.
|
||||
* Do not add/remove items directly to/from the combo after binding.
|
||||
*/
|
||||
explicit ComboLinks(QComboBox* combo);
|
||||
|
||||
/**
|
||||
* @brief Default constructor. Use setCombo() later.
|
||||
*/
|
||||
ComboLinks() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor. Clears internal resources.
|
||||
*/
|
||||
~ComboLinks();
|
||||
|
||||
FC_DISABLE_COPY_MOVE(ComboLinks)
|
||||
|
||||
/**
|
||||
* @brief Binds the helper to a QComboBox. Clears the combo box.
|
||||
* @param combo The combo box to manage.
|
||||
*/
|
||||
void setCombo(QComboBox* combo);
|
||||
|
||||
/**
|
||||
* @brief Adds an item to the combo box associated with a PropertyLinkSub.
|
||||
* @param lnk The link data. Can point to nullptr (e.g., for "Select reference...").
|
||||
* @param itemText The text to display in the combo box.
|
||||
* @param userData Optional integer data associated with the item (default: item index).
|
||||
* @return The index of the added item.
|
||||
*/
|
||||
int addLink(const App::PropertyLinkSub& lnk, const QString& itemText, int userData = -1);
|
||||
|
||||
/**
|
||||
* @brief Adds an item to the combo box associated with specific object and subname.
|
||||
* @param linkObj The document object (can be nullptr).
|
||||
* @param linkSubname The sub-element name (e.g., "Edge1", "Face2", "X_Axis").
|
||||
* @param itemText The text to display in the combo box.
|
||||
* @param userData Optional integer data associated with the item (default: item index).
|
||||
* @return The index of the added item.
|
||||
*/
|
||||
int addLink(App::DocumentObject* linkObj, const std::string& linkSubname, const QString& itemText, int userData = -1);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Adds an item *before* an item identified by its user data.
|
||||
* Useful for inserting custom items before a standard item like "Select reference...".
|
||||
* If targetUserData is not found, appends to the end.
|
||||
*
|
||||
* @param lnk The link data.
|
||||
* @param itemText The text to display.
|
||||
* @param targetUserData The user data of the item to insert before.
|
||||
* @param userData Optional integer data for the new item (default: item index).
|
||||
* @return The index of the inserted item.
|
||||
*/
|
||||
int addLinkBefore(const App::PropertyLinkSub& lnk, const QString& itemText, int targetUserData, int userData = -1);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Clears all items from the combo box and the internal list.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Gets the PropertyLinkSub associated with the item at the given index.
|
||||
* @param index The index of the item.
|
||||
* @return A reference to the stored PropertyLinkSub.
|
||||
* @throws Base::IndexError if index is out of range.
|
||||
* @throws Base::ValueError if the linked object exists but is not in the tracked document.
|
||||
*/
|
||||
App::PropertyLinkSub& getLink(int index) const;
|
||||
|
||||
/**
|
||||
* @brief Gets the PropertyLinkSub associated with the currently selected item.
|
||||
* @return A reference to the stored PropertyLinkSub.
|
||||
* @throws Base::IndexError if no item is selected or combo is invalid.
|
||||
* @throws Base::ValueError if linked object validity check fails.
|
||||
*/
|
||||
App::PropertyLinkSub& getCurrentLink() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the user data associated with the item at the given index.
|
||||
* @param index The index of the item.
|
||||
* @return The user data. Returns -1 if index is invalid.
|
||||
*/
|
||||
int getUserData(int index) const;
|
||||
|
||||
/**
|
||||
* @brief Gets the user data associated with the currently selected item.
|
||||
* @return The user data. Returns -1 if no item is selected or combo is invalid.
|
||||
*/
|
||||
int getCurrentUserData() const;
|
||||
|
||||
/**
|
||||
* @brief Selects the item whose PropertyLinkSub matches the provided link.
|
||||
* Blocks combo box signals during the operation.
|
||||
* @param lnk The link to match.
|
||||
* @return The index of the selected item, or -1 if no match is found.
|
||||
*/
|
||||
int setCurrentLink(const App::PropertyLinkSub& lnk);
|
||||
|
||||
/**
|
||||
* @brief Gets the number of items in the combo box.
|
||||
* @return Item count.
|
||||
*/
|
||||
int count() const;
|
||||
|
||||
|
||||
private:
|
||||
QComboBox* combo = nullptr;
|
||||
App::Document* doc = nullptr; // Document context for validation
|
||||
std::vector<App::PropertyLinkSub*> linksInList; // Owned pointers
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
#endif // GUI_COMBOLINKS_H
|
||||
@@ -59,6 +59,7 @@ set(PartGui_UIC_SRCS
|
||||
DlgSettingsGeneral.ui
|
||||
DlgSettingsObjectColor.ui
|
||||
DlgProjectionOnSurface.ui
|
||||
PatternParametersWidget.ui
|
||||
SectionCutting.ui
|
||||
ShapeFromMesh.ui
|
||||
TaskFaceAppearances.ui
|
||||
@@ -145,6 +146,9 @@ SET(PartGui_SRCS
|
||||
DlgProjectionOnSurface.cpp
|
||||
DlgProjectionOnSurface.h
|
||||
DlgProjectionOnSurface.ui
|
||||
PatternParametersWidget.cpp
|
||||
PatternParametersWidget.h
|
||||
PatternParametersWidget.ui
|
||||
Resources/Part.qrc
|
||||
PreCompiled.cpp
|
||||
PreCompiled.h
|
||||
|
||||
494
src/Mod/Part/Gui/PatternParametersWidget.cpp
Normal file
494
src/Mod/Part/Gui/PatternParametersWidget.cpp
Normal file
@@ -0,0 +1,494 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 AstoCAD <hello@astocad.com> *
|
||||
* *
|
||||
* 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 <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QHBoxLayout>
|
||||
#include <QToolButton>
|
||||
#include <QLabel>
|
||||
#include <QFormLayout>
|
||||
#endif
|
||||
|
||||
#include "ui_PatternParametersWidget.h"
|
||||
#include "PatternParametersWidget.h"
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/PropertyUnits.h>
|
||||
#include <Base/Parameter.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Gui/ComboLinks.h>
|
||||
#include <Gui/QuantitySpinBox.h>
|
||||
#include <Gui/SpinBox.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Base/Console.h>
|
||||
|
||||
using namespace PartGui;
|
||||
|
||||
PatternParametersWidget::PatternParametersWidget(PatternType type, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
type(type),
|
||||
ui(new Ui_PatternParametersWidget)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setupUiElements();
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
PatternParametersWidget::~PatternParametersWidget() = default;
|
||||
|
||||
void PatternParametersWidget::setupUiElements()
|
||||
{
|
||||
// Configure UI elements if needed (e.g., icons for mode)
|
||||
QIcon iconExtent = Gui::BitmapFactory().iconFromTheme("Part_LinearPattern_extent");
|
||||
QIcon iconSpacing = Gui::BitmapFactory().iconFromTheme("Part_LinearPattern_spacing");
|
||||
|
||||
ui->comboMode->setItemIcon(0, iconExtent);
|
||||
ui->comboMode->setItemIcon(1, iconSpacing);
|
||||
|
||||
if (type == PatternType::Polar) {
|
||||
setTitle(tr("Axis"));
|
||||
}
|
||||
|
||||
// Set combo box helper
|
||||
dirLinks.setCombo(ui->comboDirection);
|
||||
|
||||
ParameterGrp::handle hPart = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Mod/Part");
|
||||
ui->addSpacingButton->setVisible(hPart->GetBool("ExperimentalFeatures", false));
|
||||
}
|
||||
|
||||
void PatternParametersWidget::connectSignals()
|
||||
{
|
||||
connect(ui->comboDirection, qOverload<int>(&QComboBox::activated),
|
||||
this, &PatternParametersWidget::onDirectionChanged);
|
||||
connect(ui->PushButtonReverse, &QToolButton::pressed,
|
||||
this, &PatternParametersWidget::onReversePressed);
|
||||
connect(ui->comboMode, qOverload<int>(&QComboBox::activated),
|
||||
this, &PatternParametersWidget::onModeChanged);
|
||||
|
||||
connect(ui->spinExtent, qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
|
||||
this, &PatternParametersWidget::onLengthChanged);
|
||||
connect(ui->spinSpacing, qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
|
||||
this, &PatternParametersWidget::onOffsetChanged);
|
||||
connect(ui->spinOccurrences, &Gui::UIntSpinBox::unsignedChanged,
|
||||
this, &PatternParametersWidget::onOccurrencesChanged);
|
||||
|
||||
// Dynamic spacing buttons
|
||||
connect(ui->addSpacingButton, &QToolButton::clicked,
|
||||
this, &PatternParametersWidget::onAddSpacingButtonClicked);
|
||||
|
||||
// Note: Connections for dynamic rows are done in addSpacingRow()
|
||||
}
|
||||
|
||||
void PatternParametersWidget::bindProperties(App::PropertyLinkSub* directionProp,
|
||||
App::PropertyBool* reversedProp,
|
||||
App::PropertyEnumeration* modeProp,
|
||||
App::PropertyQuantity* lengthProp,
|
||||
App::PropertyQuantity* offsetProp,
|
||||
App::PropertyFloatList* spacingPatternProp,
|
||||
App::PropertyIntegerConstraint* occurrencesProp,
|
||||
App::DocumentObject* feature)
|
||||
{
|
||||
// Store pointers to the properties
|
||||
m_directionProp = directionProp;
|
||||
m_reversedProp = reversedProp;
|
||||
m_modeProp = modeProp;
|
||||
m_extentProp = lengthProp;
|
||||
m_spacingProp = offsetProp;
|
||||
m_spacingPatternProp = spacingPatternProp;
|
||||
m_occurrencesProp = occurrencesProp;
|
||||
m_feature = feature; // Store feature for context (units, etc.)
|
||||
|
||||
ui->spinExtent->bind(*m_extentProp);
|
||||
Base::Unit unit = type == PatternType::Linear ? Base::Unit::Length : Base::Unit::Angle;
|
||||
|
||||
ui->spinExtent->blockSignals(true);
|
||||
ui->spinExtent->setUnit(unit);
|
||||
ui->spinExtent->blockSignals(false);
|
||||
|
||||
ui->spinSpacing->bind(*m_spacingProp);
|
||||
ui->spinSpacing->blockSignals(true);
|
||||
ui->spinSpacing->setUnit(unit);
|
||||
ui->spinSpacing->blockSignals(false);
|
||||
|
||||
ui->spinOccurrences->bind(*m_occurrencesProp);
|
||||
ui->spinOccurrences->blockSignals(true);
|
||||
ui->spinOccurrences->setMaximum(m_occurrencesProp->getMaximum());
|
||||
ui->spinOccurrences->setMinimum(m_occurrencesProp->getMinimum());
|
||||
ui->spinOccurrences->blockSignals(false);
|
||||
|
||||
// Initial UI update from properties
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void PatternParametersWidget::addDirection(App::DocumentObject* linkObj, const std::string& linkSubname, const QString& itemText, int userData)
|
||||
{
|
||||
// Insert custom directions before "Select reference..."
|
||||
dirLinks.addLink(linkObj, linkSubname, itemText, userData);
|
||||
}
|
||||
|
||||
void PatternParametersWidget::updateUI()
|
||||
{
|
||||
if (blockUpdate || !m_feature) { // Need properties to be bound
|
||||
return;
|
||||
}
|
||||
Base::StateLocker locker(blockUpdate, true);
|
||||
|
||||
// Update direction combo
|
||||
if (dirLinks.setCurrentLink(*m_directionProp) == -1) {
|
||||
// failed to set current, because the link isn't in the list yet
|
||||
if (m_directionProp->getValue()) {
|
||||
QString refStr =
|
||||
QStringLiteral("%1:%2").arg(QString::fromLatin1(m_directionProp->getValue()->getNameInDocument()),
|
||||
QString::fromLatin1(m_directionProp->getSubValues().front().c_str()));
|
||||
dirLinks.addLink(*m_directionProp, refStr);
|
||||
dirLinks.setCurrentLink(*m_directionProp);
|
||||
}
|
||||
}
|
||||
|
||||
// Update other controls directly from properties
|
||||
ui->comboMode->setCurrentIndex(m_modeProp->getValue());
|
||||
ui->spinExtent->setValue(m_extentProp->getValue());
|
||||
ui->spinSpacing->setValue(m_spacingProp->getValue());
|
||||
ui->spinOccurrences->setValue(m_occurrencesProp->getValue());
|
||||
|
||||
rebuildDynamicSpacingUI();
|
||||
|
||||
adaptVisibilityToMode();
|
||||
}
|
||||
|
||||
|
||||
void PatternParametersWidget::adaptVisibilityToMode()
|
||||
{
|
||||
if (!m_modeProp) {
|
||||
return;
|
||||
}
|
||||
// Use the enum names defined in FeatureLinearPattern.h
|
||||
auto mode = static_cast<PartGui::PatternMode>(m_modeProp->getValue());
|
||||
|
||||
ui->formLayout->labelForField(ui->spinExtent)->setVisible(mode == PartGui::PatternMode::Extent);
|
||||
ui->spinExtent->setVisible(mode == PartGui::PatternMode::Extent);
|
||||
ui->formLayout->labelForField(ui->spacingControlsWidget)
|
||||
->setVisible(mode == PartGui::PatternMode::Spacing);
|
||||
ui->spacingControlsWidget->setVisible(mode == PartGui::PatternMode::Spacing);
|
||||
}
|
||||
|
||||
const App::PropertyLinkSub& PatternParametersWidget::getCurrentDirectionLink() const
|
||||
{
|
||||
return dirLinks.getCurrentLink();
|
||||
}
|
||||
|
||||
bool PatternParametersWidget::isSelectReferenceMode() const
|
||||
{
|
||||
return !dirLinks.getCurrentLink().getValue();
|
||||
}
|
||||
|
||||
void PatternParametersWidget::setTitle(const QString& title)
|
||||
{
|
||||
ui->groupBox->setTitle(title);
|
||||
}
|
||||
|
||||
void PatternParametersWidget::setCheckable(bool on)
|
||||
{
|
||||
ui->groupBox->setCheckable(on);
|
||||
}
|
||||
|
||||
void PatternParametersWidget::setChecked(bool on)
|
||||
{
|
||||
ui->groupBox->setChecked(on);
|
||||
}
|
||||
|
||||
// --- Slots ---
|
||||
|
||||
void PatternParametersWidget::onDirectionChanged(int /*index*/)
|
||||
{
|
||||
if (blockUpdate || !m_directionProp) return;
|
||||
|
||||
if (isSelectReferenceMode()) {
|
||||
// Emit signal for the task panel to handle reference selection
|
||||
requestReferenceSelection();
|
||||
}
|
||||
else {
|
||||
m_directionProp->Paste(dirLinks.getCurrentLink()); // Update the property
|
||||
parametersChanged(); // Notify change
|
||||
}
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onReversePressed()
|
||||
{
|
||||
if (blockUpdate || !m_reversedProp) return;
|
||||
|
||||
m_reversedProp->setValue(!m_reversedProp->getValue());
|
||||
parametersChanged();
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onModeChanged(int index)
|
||||
{
|
||||
if (blockUpdate || !m_modeProp) return;
|
||||
m_modeProp->setValue(index); // Assuming enum values match index
|
||||
adaptVisibilityToMode(); // Update visibility based on new mode
|
||||
parametersChanged();
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onLengthChanged(double value)
|
||||
{
|
||||
// Usually handled by bind(). If manual update needed:
|
||||
if (blockUpdate || !m_extentProp) return;
|
||||
m_extentProp->setValue(value);
|
||||
parametersChanged(); // Still emit signal even if bound
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onOffsetChanged(double value)
|
||||
{
|
||||
if (blockUpdate || !m_spacingProp || !m_spacingPatternProp) return;
|
||||
|
||||
m_spacingProp->setValue(value);
|
||||
|
||||
// Crucially, also update the *first* element of the SpacingPattern list
|
||||
std::vector<double> currentSpacings = m_spacingPatternProp->getValues();
|
||||
if (currentSpacings.empty()) {
|
||||
currentSpacings.push_back(ui->spinSpacing->value().getValue()); // Use UI value which includes units
|
||||
}
|
||||
else {
|
||||
currentSpacings[0] = ui->spinSpacing->value().getValue();
|
||||
}
|
||||
|
||||
m_spacingPatternProp->setValues(currentSpacings); // Update the property list
|
||||
parametersChanged(); // Emit signal
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onOccurrencesChanged(unsigned int value)
|
||||
{
|
||||
// Usually handled by bind(). If manual update needed:
|
||||
if (blockUpdate || !m_occurrencesProp) return;
|
||||
|
||||
m_occurrencesProp->setValue(value);
|
||||
parametersChanged(); // Still emit signal even if bound
|
||||
}
|
||||
|
||||
|
||||
// --- Dynamic Spacing Logic ---
|
||||
|
||||
void PatternParametersWidget::clearDynamicSpacingRows()
|
||||
{
|
||||
for (QWidget* fieldWidget : dynamicSpacingRows) {
|
||||
ui->formLayout->removeRow(fieldWidget);
|
||||
}
|
||||
dynamicSpacingRows.clear();
|
||||
dynamicSpacingSpinBoxes.clear();
|
||||
}
|
||||
|
||||
void PatternParametersWidget::addSpacingRow(double value)
|
||||
{
|
||||
if (!m_spacingProp) {
|
||||
return; // Need context for units
|
||||
}
|
||||
|
||||
// Find position to insert before "Occurrences"
|
||||
int insertPos = -1;
|
||||
QFormLayout::ItemRole role;
|
||||
ui->formLayout->getWidgetPosition(ui->spinOccurrences, &insertPos, &role);
|
||||
if (insertPos == -1) {
|
||||
insertPos = ui->formLayout->rowCount(); // Fallback to appending
|
||||
}
|
||||
|
||||
int newIndex = dynamicSpacingRows.count();
|
||||
QLabel* label = new QLabel(tr("Spacing %1").arg(newIndex + 2), this);
|
||||
|
||||
// Create the field widget (spinbox + remove button)
|
||||
QWidget* fieldWidget = new QWidget(this);
|
||||
QHBoxLayout* fieldLayout = new QHBoxLayout(fieldWidget);
|
||||
fieldLayout->setContentsMargins(0, 0, 0, 0);
|
||||
fieldLayout->setSpacing(3);
|
||||
|
||||
Gui::QuantitySpinBox* spinBox = new Gui::QuantitySpinBox(fieldWidget);
|
||||
Base::Unit unit = type == PatternType::Linear ? Base::Unit::Length : Base::Unit::Angle;
|
||||
spinBox->setUnit(unit);
|
||||
spinBox->setKeyboardTracking(false);
|
||||
spinBox->setValue(value); // Set initial value
|
||||
|
||||
QToolButton* removeButton = new QToolButton(fieldWidget);
|
||||
removeButton->setIcon(Gui::BitmapFactory().iconFromTheme("list-remove"));
|
||||
removeButton->setToolTip(tr("Remove this spacing definition."));
|
||||
removeButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||
|
||||
fieldLayout->addWidget(spinBox);
|
||||
fieldLayout->addWidget(removeButton);
|
||||
|
||||
ui->formLayout->insertRow(insertPos, label, fieldWidget);
|
||||
dynamicSpacingRows.append(fieldWidget);
|
||||
dynamicSpacingSpinBoxes.append(spinBox);
|
||||
|
||||
// Connect signals for the new row
|
||||
connect(spinBox,
|
||||
qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
|
||||
this,
|
||||
&PatternParametersWidget::onDynamicSpacingChanged);
|
||||
connect(removeButton, &QToolButton::clicked, this, [this, fieldWidget]() {
|
||||
this->onRemoveSpacingButtonClicked(fieldWidget);
|
||||
});
|
||||
}
|
||||
|
||||
void PatternParametersWidget::rebuildDynamicSpacingUI()
|
||||
{
|
||||
if (!m_spacingPatternProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearDynamicSpacingRows(); // Clear existing dynamic UI first
|
||||
|
||||
std::vector<double> currentSpacings = m_spacingPatternProp->getValues();
|
||||
// Start from index 1, as index 0 corresponds to ui->spinSpacing
|
||||
for (size_t i = 1; i < currentSpacings.size(); ++i) {
|
||||
// Values in PropertyFloatList are unitless. Assume they match the Offset unit.
|
||||
addSpacingRow(currentSpacings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onAddSpacingButtonClicked()
|
||||
{
|
||||
if (blockUpdate || !m_spacingProp) return;
|
||||
|
||||
// Add a new row to the UI with a default value (same as main offset)
|
||||
addSpacingRow(ui->spinSpacing->value().getValue());
|
||||
|
||||
// Update the underlying property list
|
||||
updateSpacingPatternProperty(); // This will emit parametersChanged
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onDynamicSpacingChanged()
|
||||
{
|
||||
if (blockUpdate) return;
|
||||
// Update the entire property list based on the current UI state.
|
||||
updateSpacingPatternProperty(); // This will emit parametersChanged
|
||||
}
|
||||
|
||||
void PatternParametersWidget::onRemoveSpacingButtonClicked(QWidget* fieldWidget)
|
||||
{
|
||||
if (blockUpdate || !m_spacingPatternProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
int indexToRemove = dynamicSpacingRows.indexOf(fieldWidget);
|
||||
if (indexToRemove == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// removeRow also deletes the widgets (label and field)
|
||||
ui->formLayout->removeRow(fieldWidget);
|
||||
|
||||
dynamicSpacingRows.removeAt(indexToRemove);
|
||||
dynamicSpacingSpinBoxes.removeAt(indexToRemove);
|
||||
|
||||
// Update labels of subsequent rows
|
||||
for (int i = indexToRemove; i < dynamicSpacingRows.size(); ++i) {
|
||||
if (auto* label =
|
||||
qobject_cast<QLabel*>(ui->formLayout->labelForField(dynamicSpacingRows[i]))) {
|
||||
label->setText(tr("Spacing %1").arg(i + 2));
|
||||
}
|
||||
}
|
||||
|
||||
// Update the underlying property list
|
||||
updateSpacingPatternProperty(); // This will emit parametersChanged
|
||||
}
|
||||
|
||||
void PatternParametersWidget::updateSpacingPatternProperty()
|
||||
{
|
||||
if (blockUpdate || !m_spacingPatternProp || !m_spacingProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<double> newSpacings;
|
||||
|
||||
// First element is always the main offset's value
|
||||
newSpacings.push_back(ui->spinSpacing->value().getValue());
|
||||
|
||||
// Add values from dynamic spin boxes
|
||||
for (Gui::QuantitySpinBox* spinBox : dynamicSpacingSpinBoxes) {
|
||||
newSpacings.push_back(spinBox->value().getValue());
|
||||
}
|
||||
|
||||
m_spacingPatternProp->setValues(newSpacings); // Set the property list
|
||||
parametersChanged(); // Emit signal after property is set
|
||||
}
|
||||
|
||||
// --- Getters ---
|
||||
|
||||
void PatternParametersWidget::getAxis(App::DocumentObject*& obj,
|
||||
std::vector<std::string>& sub) const
|
||||
{
|
||||
const App::PropertyLinkSub& lnk = dirLinks.getCurrentLink();
|
||||
obj = lnk.getValue();
|
||||
sub = lnk.getSubValues();
|
||||
}
|
||||
|
||||
bool PatternParametersWidget::getReverse() const
|
||||
{
|
||||
return m_reversedProp->getValue();
|
||||
}
|
||||
|
||||
int PatternParametersWidget::getMode() const
|
||||
{
|
||||
return ui->comboMode->currentIndex();
|
||||
}
|
||||
|
||||
double PatternParametersWidget::getExtent() const
|
||||
{
|
||||
return ui->spinExtent->value().getValue();
|
||||
}
|
||||
|
||||
double PatternParametersWidget::getSpacing() const
|
||||
{
|
||||
return ui->spinSpacing->value().getValue();
|
||||
}
|
||||
|
||||
unsigned PatternParametersWidget::getOccurrences() const
|
||||
{
|
||||
return ui->spinOccurrences->value();
|
||||
}
|
||||
|
||||
std::string PatternParametersWidget::getSpacingPatternsAsString() const
|
||||
{
|
||||
// Build the Python list string for SpacingPattern
|
||||
std::stringstream ss;
|
||||
ss << "[";
|
||||
const auto& spacingValues = m_spacingPatternProp->getValues();
|
||||
for (size_t i = 0; i < spacingValues.size(); ++i) {
|
||||
ss << (i > 0 ? ", " : "") << spacingValues[i];
|
||||
}
|
||||
ss << "]";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void PatternParametersWidget::applyQuantitySpinboxes() const
|
||||
{
|
||||
ui->spinExtent->apply();
|
||||
ui->spinSpacing->apply();
|
||||
ui->spinOccurrences->apply();
|
||||
}
|
||||
|
||||
//#include "moc_PatternParametersWidget.cpp"
|
||||
213
src/Mod/Part/Gui/PatternParametersWidget.h
Normal file
213
src/Mod/Part/Gui/PatternParametersWidget.h
Normal file
@@ -0,0 +1,213 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 AstoCAD <hello@astocad.com> *
|
||||
* *
|
||||
* 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 PARTGUI_PATTERNPARAMETERSWIDGET_H
|
||||
#define PARTGUI_PATTERNPARAMETERSWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <App/PropertyStandard.h> // For Property types
|
||||
#include <App/PropertyLinks.h> // For PropertyLinkSub
|
||||
#include <Gui/ComboLinks.h>
|
||||
|
||||
#include <Mod/Part/PartGlobal.h>
|
||||
|
||||
class Ui_PatternParametersWidget;
|
||||
|
||||
namespace App {
|
||||
class PropertyLinkSub;
|
||||
class PropertyBool;
|
||||
class PropertyEnumeration;
|
||||
class PropertyQuantity;
|
||||
class PropertyFloatList;
|
||||
class PropertyIntegerConstraint;
|
||||
class DocumentObject;
|
||||
}
|
||||
namespace Gui {
|
||||
class QuantitySpinBox;
|
||||
}
|
||||
class QToolButton;
|
||||
|
||||
namespace PartGui {
|
||||
|
||||
enum class PatternType {
|
||||
Linear,
|
||||
Polar
|
||||
};
|
||||
|
||||
enum class PatternMode {
|
||||
Extent,
|
||||
Spacing
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A widget to configure the parameters for a single direction of a linear pattern.
|
||||
*
|
||||
* This widget encapsulates the UI and logic for Direction, Reverse, Mode,
|
||||
* Length/Spacing (including dynamic spacing), and Occurrences. It binds directly
|
||||
* to the corresponding properties of a Feature.
|
||||
*/
|
||||
class PartGuiExport PatternParametersWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PatternParametersWidget(PatternType type, QWidget* parent = nullptr);
|
||||
~PatternParametersWidget() override;
|
||||
|
||||
/**
|
||||
* @brief Binds the widget's UI elements to the properties of a DocumentObject.
|
||||
*
|
||||
* This must be called after creating the widget to link its controls
|
||||
* to the underlying feature's data.
|
||||
*
|
||||
* @param directionProp Reference to the Direction property (PropertyLinkSub).
|
||||
* @param reversedProp Reference to the Reversed property (PropertyBool).
|
||||
* @param modeProp Reference to the Mode property (PropertyEnumeration).
|
||||
* @param lengthProp Reference to the Length property (PropertyQuantity).
|
||||
* @param offsetProp Reference to the Offset property (PropertyQuantity).
|
||||
* @param spacingPatternProp Reference to the SpacingPattern property (PropertyFloatList).
|
||||
* @param occurrencesProp Reference to the Occurrences property (PropertyIntegerConstraint).
|
||||
* @param feature The feature object itself, needed for context (e.g., units).
|
||||
*/
|
||||
void bindProperties(App::PropertyLinkSub* directionProp,
|
||||
App::PropertyBool* reversedProp,
|
||||
App::PropertyEnumeration* modeProp,
|
||||
App::PropertyQuantity* lengthProp,
|
||||
App::PropertyQuantity* offsetProp,
|
||||
App::PropertyFloatList* spacingPatternProp,
|
||||
App::PropertyIntegerConstraint* occurrencesProp,
|
||||
App::DocumentObject* feature); // Pass feature for context
|
||||
|
||||
|
||||
/**
|
||||
* @brief Adds a custom direction option to the Direction combo box.
|
||||
*
|
||||
* Used by consuming tools (like PartDesign tasks) to add context-specific
|
||||
* directions (e.g., Sketch axes).
|
||||
*
|
||||
* @param link The PropertyLinkSub representing the custom direction.
|
||||
* @param text The user-visible text for the combo box item.
|
||||
*/
|
||||
void addDirection(App::DocumentObject* linkObj, const std::string& linkSubname, const QString& itemText, int userData = -1);
|
||||
|
||||
/**
|
||||
* @brief Updates the UI elements to reflect the current values of the bound properties.
|
||||
*/
|
||||
void updateUI();
|
||||
|
||||
/**
|
||||
* @brief Returns the currently selected direction link from the combo box.
|
||||
* Returns an empty link if "Select reference..." is chosen.
|
||||
*/
|
||||
const App::PropertyLinkSub& getCurrentDirectionLink() const;
|
||||
|
||||
/**
|
||||
* @brief Checks if the "Select reference..." item is currently selected.
|
||||
*/
|
||||
bool isSelectReferenceMode() const;
|
||||
|
||||
void getAxis(App::DocumentObject*& obj, std::vector<std::string>& sub) const;
|
||||
bool getReverse() const;
|
||||
int getMode() const;
|
||||
double getExtent() const;
|
||||
double getSpacing() const;
|
||||
unsigned getOccurrences() const;
|
||||
std::string getSpacingPatternsAsString() const;
|
||||
|
||||
void setTitle(const QString& title);
|
||||
void setCheckable(bool on);
|
||||
void setChecked(bool on);
|
||||
|
||||
void applyQuantitySpinboxes() const;
|
||||
|
||||
Gui::ComboLinks dirLinks;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* @brief Emitted when the user selects the "Select reference..." option
|
||||
* in the direction combo box, indicating the need to enter selection mode.
|
||||
*/
|
||||
void requestReferenceSelection();
|
||||
|
||||
/**
|
||||
* @brief Emitted when any parameter value controlled by this widget changes
|
||||
* that requires a recompute of the feature.
|
||||
*/
|
||||
void parametersChanged();
|
||||
|
||||
|
||||
private Q_SLOTS:
|
||||
// Slots connected to UI elements
|
||||
void onDirectionChanged(int index);
|
||||
void onReversePressed();
|
||||
void onModeChanged(int index);
|
||||
// Note: Spinbox value changes are often handled by direct binding,
|
||||
// but we might need slots if extra logic (like updating SpacingPattern[0]) is needed.
|
||||
void onLengthChanged(double value);
|
||||
void onOffsetChanged(double value);
|
||||
void onOccurrencesChanged(unsigned int value);
|
||||
|
||||
// Slots for dynamic spacing
|
||||
void onAddSpacingButtonClicked();
|
||||
void onDynamicSpacingChanged(); // Simplified slot
|
||||
void onRemoveSpacingButtonClicked(QWidget* rowWidget);
|
||||
|
||||
private:
|
||||
// Initialization and setup
|
||||
void setupUiElements();
|
||||
void connectSignals();
|
||||
|
||||
// UI Update and state management
|
||||
void adaptVisibilityToMode();
|
||||
|
||||
// Dynamic spacing helpers
|
||||
void addSpacingRow(double value);
|
||||
void clearDynamicSpacingRows();
|
||||
void rebuildDynamicSpacingUI();
|
||||
void updateSpacingPatternProperty(); // Updates the property from the UI
|
||||
|
||||
std::unique_ptr<Ui_PatternParametersWidget> ui;
|
||||
|
||||
// Pointers to bound properties (raw pointers, lifetime managed externally)
|
||||
App::PropertyLinkSub* m_directionProp = nullptr;
|
||||
App::PropertyBool* m_reversedProp = nullptr;
|
||||
App::PropertyEnumeration* m_modeProp = nullptr;
|
||||
App::PropertyQuantity* m_extentProp = nullptr;
|
||||
App::PropertyQuantity* m_spacingProp = nullptr;
|
||||
App::PropertyFloatList* m_spacingPatternProp = nullptr;
|
||||
App::PropertyIntegerConstraint* m_occurrencesProp = nullptr;
|
||||
App::DocumentObject* m_feature = nullptr; // Store feature for context
|
||||
|
||||
bool blockUpdate = false; // Prevents signal loops
|
||||
|
||||
// Store pointers to dynamically created widgets for removal and access
|
||||
QList<QWidget*> dynamicSpacingRows;
|
||||
QList<Gui::QuantitySpinBox*> dynamicSpacingSpinBoxes;
|
||||
|
||||
PatternType type;
|
||||
};
|
||||
|
||||
} // namespace PartGui
|
||||
|
||||
#endif // PARTGUI_PATTERNPARAMETERSWIDGET_H
|
||||
193
src/Mod/Part/Gui/PatternParametersWidget.ui
Normal file
193
src/Mod/Part/Gui/PatternParametersWidget.ui
Normal file
@@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PartGui::PatternParametersWidget</class>
|
||||
<widget class="QWidget" name="PartGui::PatternParametersWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>270</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="generalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Direction</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QToolButton" name="PushButtonReverse">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reverse the direction of the pattern.</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="Resources/resource.qrc">
|
||||
<normaloff>:/icons/button_sort.svg</normaloff>:/icons/button_sort.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboDirection"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelMode">
|
||||
<property name="text">
|
||||
<string>Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Extent</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Spacing</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelLength">
|
||||
<property name="text">
|
||||
<string>Length</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="spinExtent" native="true">
|
||||
<property name="keyboardTracking" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelOffset">
|
||||
<property name="text">
|
||||
<string>Spacing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QWidget" name="spacingControlsWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="spacingControlsLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="spinSpacing" native="true">
|
||||
<property name="keyboardTracking" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="addSpacingButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add spacing to create spacing patterns.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/icons/list-add.svg</normaloff>:/icons/list-add.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelOccurrences">
|
||||
<property name="text">
|
||||
<string>Occurrences</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="Gui::UIntSpinBox" name="spinOccurrences"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::QuantitySpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Gui/QuantitySpinBox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::UIntSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>Gui/SpinBox.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -82,5 +82,7 @@
|
||||
<file>icons/PartWorkbench.svg</file>
|
||||
<file>icons/preferences-part_design.svg</file>
|
||||
<file>icons/Part_Detached.svg</file>
|
||||
<file>icons/Part_LinearPattern_extent.svg</file>
|
||||
<file>icons/Part_LinearPattern_spacing.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
1912
src/Mod/Part/Gui/Resources/icons/Part_LinearPattern_extent.svg
Normal file
1912
src/Mod/Part/Gui/Resources/icons/Part_LinearPattern_extent.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 63 KiB |
1911
src/Mod/Part/Gui/Resources/icons/Part_LinearPattern_spacing.svg
Normal file
1911
src/Mod/Part/Gui/Resources/icons/Part_LinearPattern_spacing.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 63 KiB |
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <App/Datums.h>
|
||||
#include <Base/Axis.h>
|
||||
#include <Mod/Part/App/Tools.h>
|
||||
#include <Mod/Part/App/TopoShape.h>
|
||||
#include <Mod/Part/App/Part2DObject.h>
|
||||
|
||||
@@ -53,21 +54,123 @@ PROPERTY_SOURCE(PartDesign::LinearPattern, PartDesign::Transformed)
|
||||
const App::PropertyIntegerConstraint::Constraints LinearPattern::intOccurrences = {
|
||||
1, std::numeric_limits<int>::max(), 1 };
|
||||
|
||||
const char* LinearPattern::ModeEnums[] = { "length", "offset", nullptr };
|
||||
const char* LinearPattern::ModeEnums[] = { "Extent", "Spacing", nullptr };
|
||||
|
||||
LinearPattern::LinearPattern()
|
||||
{
|
||||
auto initialMode = LinearPatternMode::length;
|
||||
auto initialMode = LinearPatternMode::Extent;
|
||||
|
||||
ADD_PROPERTY_TYPE(Direction,(nullptr),"LinearPattern",(App::PropertyType)(App::Prop_None),"Direction");
|
||||
ADD_PROPERTY(Reversed,(0));
|
||||
ADD_PROPERTY(Mode, (long(initialMode)));
|
||||
ADD_PROPERTY(Length,(100.0));
|
||||
ADD_PROPERTY(Offset,(10.0));
|
||||
ADD_PROPERTY(Occurrences,(3));
|
||||
ADD_PROPERTY_TYPE(Direction,
|
||||
(nullptr),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"The first direction of the pattern. This can be a straight edge, a datum "
|
||||
"line, a sketch axis, or the normal of a planar face.");
|
||||
ADD_PROPERTY_TYPE(Reversed,
|
||||
(0),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"Reverse the first direction of the pattern");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Mode,
|
||||
(long(initialMode)),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"Selects how the pattern is dimensioned.\n'Extent': Uses the total length from the first "
|
||||
"to the last instance.\n'Spacing': Uses the distance between consecutive instances.");
|
||||
ADD_PROPERTY_TYPE(Length,
|
||||
(100.0),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"The total length of the pattern, measured from the first to the last "
|
||||
"instance. This is only used when the Mode is set to 'Extent'.");
|
||||
ADD_PROPERTY_TYPE(Offset,
|
||||
(10.0),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"The distance between each instance of the pattern. This is only used when "
|
||||
"the Mode is set to 'Spacing'.");
|
||||
ADD_PROPERTY_TYPE(Spacings,
|
||||
({-1.0}),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"A list of custom spacings between instances. If a value is -1, the global "
|
||||
"'Offset' is used for that spacing. The list should have one less item than "
|
||||
"the number of occurrences.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
SpacingPattern,
|
||||
({}),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"(Experimental and subject to change. To enable "
|
||||
"this in the UI you can add a boolean parameter 'ExperiementalFeature' in "
|
||||
"Preferences/Mod/Part)\nDefines a repeating pattern of spacings for the second direction. "
|
||||
"For example, a list of [10, 20] will create alternating spacings of 10mm and 20mm.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Occurrences,
|
||||
(2),
|
||||
"Direction 1",
|
||||
App::Prop_None,
|
||||
"The total number of instances in the first direction, including the original feature.");
|
||||
Occurrences.setConstraints(&intOccurrences);
|
||||
Mode.setEnums(ModeEnums);
|
||||
setReadWriteStatusForMode(initialMode);
|
||||
setReadWriteStatusForMode(LinearPatternDirection::First);
|
||||
|
||||
ADD_PROPERTY_TYPE(Direction2,
|
||||
(nullptr),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"The second direction of the pattern. This can be a straight edge, a datum "
|
||||
"line, a sketch axis, or the normal of a planar face.");
|
||||
ADD_PROPERTY_TYPE(Reversed2,
|
||||
(0),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"Reverse the second direction of the pattern");
|
||||
ADD_PROPERTY_TYPE(Mode2,
|
||||
(long(initialMode)),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"Selects how the pattern is dimensioned in the second direction.\n'Extent': "
|
||||
"Uses the total length.\n'Spacing': Uses the distance between instances.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Length2,
|
||||
(100.0),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"The total length of the pattern in the second direction, measured from the first to the "
|
||||
"last instance. This is only used when the Mode is set to 'Extent'.");
|
||||
ADD_PROPERTY_TYPE(Offset2,
|
||||
(10.0),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"The distance between each instance of the pattern in the second direction. "
|
||||
"This is only used when the Mode is set to 'Spacing'.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Spacings2,
|
||||
({}),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"A list of custom spacings for the second direction. If a value is -1, the global 'Offset' "
|
||||
"is used. The list should have one less item than the number of occurrences.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
SpacingPattern2,
|
||||
({}),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"(Experimental and subject to change. To enable "
|
||||
"this in the UI you can add a boolean parameter 'ExperiementalFeature' in "
|
||||
"Preferences/Mod/Part)\nDefines a repeating pattern of spacings for the second direction. "
|
||||
"For example, a list of [10, 20] will create alternating spacings of 10mm and 20mm.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Occurrences2,
|
||||
(1),
|
||||
"Direction 2",
|
||||
App::Prop_None,
|
||||
"The total number of instances in the second direction, including the original feature.");
|
||||
Occurrences2.setConstraints(&intOccurrences);
|
||||
Mode2.setEnums(ModeEnums);
|
||||
setReadWriteStatusForMode(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
short LinearPattern::mustExecute() const
|
||||
@@ -78,42 +181,170 @@ short LinearPattern::mustExecute() const
|
||||
// Length and Offset are mutually exclusive, only one could be updated at once
|
||||
Length.isTouched() ||
|
||||
Offset.isTouched() ||
|
||||
Occurrences.isTouched())
|
||||
Spacings.isTouched() ||
|
||||
SpacingPattern.isTouched() ||
|
||||
Occurrences.isTouched() ||
|
||||
Direction2.isTouched() ||
|
||||
Reversed2.isTouched() ||
|
||||
Mode2.isTouched() ||
|
||||
Length2.isTouched() ||
|
||||
Offset2.isTouched() ||
|
||||
Spacings2.isTouched() ||
|
||||
SpacingPattern2.isTouched() ||
|
||||
Occurrences2.isTouched())
|
||||
return 1;
|
||||
return Transformed::mustExecute();
|
||||
}
|
||||
|
||||
void LinearPattern::setReadWriteStatusForMode(LinearPatternMode mode)
|
||||
void LinearPattern::setReadWriteStatusForMode(LinearPatternDirection dir)
|
||||
{
|
||||
Length.setReadOnly(mode != LinearPatternMode::length);
|
||||
Offset.setReadOnly(mode != LinearPatternMode::offset);
|
||||
bool isExtentMode = false;
|
||||
if (dir == LinearPatternDirection::First) {
|
||||
isExtentMode = (Mode.getValue() == static_cast<long>(LinearPatternMode::Extent));
|
||||
Length.setReadOnly(!isExtentMode);
|
||||
Offset.setReadOnly(isExtentMode);
|
||||
}
|
||||
else {
|
||||
isExtentMode = (Mode2.getValue() == static_cast<long>(LinearPatternMode::Extent));
|
||||
Length2.setReadOnly(!isExtentMode);
|
||||
Offset2.setReadOnly(isExtentMode);
|
||||
}
|
||||
}
|
||||
|
||||
const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App::DocumentObject*>)
|
||||
{
|
||||
int occurrences = Occurrences.getValue();
|
||||
if (occurrences < 1)
|
||||
int occurrences2 = Occurrences2.getValue();
|
||||
if (occurrences < 1 || occurrences2 < 1) {
|
||||
throw Base::ValueError("At least one occurrence required");
|
||||
}
|
||||
|
||||
if (occurrences == 1)
|
||||
if (occurrences == 1 && occurrences2 == 1) {
|
||||
return {gp_Trsf()};
|
||||
}
|
||||
|
||||
double distance = Length.getValue();
|
||||
if (distance < Precision::Confusion())
|
||||
// make sure spacings are correct size :
|
||||
updateSpacings();
|
||||
|
||||
// Calculate the base offset vector and final step positions for each direction
|
||||
gp_Vec offset1 = calculateOffsetVector(LinearPatternDirection::First);
|
||||
std::vector<gp_Vec> steps1 = calculateSteps(LinearPatternDirection::First, offset1);
|
||||
|
||||
gp_Vec offset2 = calculateOffsetVector(LinearPatternDirection::Second);
|
||||
std::vector<gp_Vec> steps2 = calculateSteps(LinearPatternDirection::Second, offset2);
|
||||
|
||||
// Combine the steps from both directions
|
||||
std::list<gp_Trsf> transformations;
|
||||
for (const auto& step1 : steps1) {
|
||||
for (const auto& step2 : steps2) {
|
||||
gp_Trsf trans;
|
||||
trans.SetTranslation(step1 + step2);
|
||||
transformations.push_back(trans);
|
||||
}
|
||||
}
|
||||
|
||||
return transformations;
|
||||
}
|
||||
|
||||
gp_Vec LinearPattern::calculateOffsetVector(LinearPatternDirection dir) const
|
||||
{
|
||||
const auto& occurrencesProp =
|
||||
(dir == LinearPatternDirection::First) ? Occurrences : Occurrences2;
|
||||
int occurrences = occurrencesProp.getValue();
|
||||
if (occurrences <= 1) {
|
||||
return gp_Vec(); // Return zero vector if no transformation is needed
|
||||
}
|
||||
|
||||
const auto& dirProp = (dir == LinearPatternDirection::First) ? Direction : Direction2;
|
||||
const auto& reversedProp = (dir == LinearPatternDirection::First) ? Reversed : Reversed2;
|
||||
const auto& modeProp = (dir == LinearPatternDirection::First) ? Mode : Mode2;
|
||||
const auto& lengthProp = (dir == LinearPatternDirection::First) ? Length : Length2;
|
||||
|
||||
double distance = lengthProp.getValue();
|
||||
if (distance < Precision::Confusion()) {
|
||||
throw Base::ValueError("Pattern length too small");
|
||||
bool reversed = Reversed.getValue();
|
||||
}
|
||||
|
||||
App::DocumentObject* refObject = Direction.getValue();
|
||||
if (!refObject)
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
gp_Vec offset = getDirectionFromProperty(dirProp);
|
||||
if (reversedProp.getValue()) {
|
||||
offset.Reverse();
|
||||
}
|
||||
|
||||
std::vector<std::string> subStrings = Direction.getSubValues();
|
||||
if (subStrings.empty())
|
||||
// For 'Extent' mode, the vector represents one full step.
|
||||
// For 'Spacing' mode, it's just a normalized direction vector.
|
||||
if (static_cast<LinearPatternMode>(modeProp.getValue()) == LinearPatternMode::Extent) {
|
||||
offset *= distance / (occurrences - 1);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::vector<gp_Vec> LinearPattern::calculateSteps(LinearPatternDirection dir,
|
||||
const gp_Vec& offsetVector) const
|
||||
{
|
||||
const auto& occurrencesProp =
|
||||
(dir == LinearPatternDirection::First) ? Occurrences : Occurrences2;
|
||||
const auto& modeProp = (dir == LinearPatternDirection::First) ? Mode : Mode2;
|
||||
const auto& offsetValueProp = (dir == LinearPatternDirection::First) ? Offset : Offset2;
|
||||
const auto& spacingsProp = (dir == LinearPatternDirection::First) ? Spacings : Spacings2;
|
||||
const auto& spacingPatternProp =
|
||||
(dir == LinearPatternDirection::First) ? SpacingPattern : SpacingPattern2;
|
||||
|
||||
int occurrences = occurrencesProp.getValue();
|
||||
std::vector<gp_Vec> steps {gp_Vec()}; // First step is always zero
|
||||
steps.reserve(occurrences);
|
||||
|
||||
if (occurrences <= 1) {
|
||||
return steps;
|
||||
}
|
||||
|
||||
if (modeProp.getValue() == static_cast<int>(LinearPatternMode::Spacing)) {
|
||||
const std::vector<double> spacings = spacingsProp.getValues();
|
||||
const std::vector<double> pattern = spacingPatternProp.getValues();
|
||||
bool usePattern = pattern.size() > 1;
|
||||
double cumulativeDistance = 0.0;
|
||||
|
||||
// Spacing priority: individual spacing > pattern > global offset
|
||||
const auto spacingAt = [&](unsigned i) {
|
||||
if (spacings.at(i - 1) != -1.0) {
|
||||
return spacings.at(i - 1);
|
||||
}
|
||||
|
||||
if (usePattern) {
|
||||
return pattern.at(static_cast<size_t>(fmod(i - 1, pattern.size())));
|
||||
}
|
||||
|
||||
return offsetValueProp.getValue();
|
||||
};
|
||||
|
||||
for (int i = 1; i < occurrences; ++i) {
|
||||
cumulativeDistance += spacingAt(i);
|
||||
steps.push_back(offsetVector * cumulativeDistance);
|
||||
}
|
||||
}
|
||||
else { // Extent Mode
|
||||
for (int i = 1; i < occurrences; ++i) {
|
||||
steps.push_back(offsetVector * i);
|
||||
}
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
gp_Dir LinearPattern::getDirectionFromProperty(const App::PropertyLinkSub& dirProp) const
|
||||
{
|
||||
App::DocumentObject* refObject = dirProp.getValue();
|
||||
if (!refObject) {
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
}
|
||||
|
||||
std::vector<std::string> subStrings = dirProp.getSubValues();
|
||||
if (subStrings.empty()) {
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
}
|
||||
|
||||
gp_Dir dir;
|
||||
if (refObject->isDerivedFrom<Part::Part2DObject>()) {
|
||||
Part::Part2DObject* refSketch = static_cast<Part::Part2DObject*>(refObject);
|
||||
if (auto* refSketch = freecad_cast<Part::Part2DObject*>(refObject)) {
|
||||
Base::Axis axis;
|
||||
if (subStrings[0] == "H_Axis") {
|
||||
axis = refSketch->getAxis(Part::Part2DObject::H_Axis);
|
||||
@@ -128,7 +359,7 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
axis *= refSketch->Placement.getValue();
|
||||
}
|
||||
else if (subStrings[0].compare(0, 4, "Axis") == 0) {
|
||||
int AxId = std::atoi(subStrings[0].substr(4,4000).c_str());
|
||||
int AxId = std::atoi(subStrings[0].substr(4, 4000).c_str());
|
||||
if (AxId >= 0 && AxId < refSketch->getAxisCount()) {
|
||||
axis = refSketch->getAxis(AxId);
|
||||
axis *= refSketch->Placement.getValue();
|
||||
@@ -153,22 +384,23 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
axis.setDirection(Base::Vector3d(d.X(), d.Y(), d.Z()));
|
||||
}
|
||||
dir = gp_Dir(axis.getDirection().x, axis.getDirection().y, axis.getDirection().z);
|
||||
} else if (refObject->isDerivedFrom<PartDesign::Plane>()) {
|
||||
PartDesign::Plane* plane = static_cast<PartDesign::Plane*>(refObject);
|
||||
}
|
||||
else if (auto* plane = freecad_cast<PartDesign::Plane*>(refObject)) {
|
||||
Base::Vector3d d = plane->getNormal();
|
||||
dir = gp_Dir(d.x, d.y, d.z);
|
||||
} else if (refObject->isDerivedFrom<PartDesign::Line>()) {
|
||||
PartDesign::Line* line = static_cast<PartDesign::Line*>(refObject);
|
||||
}
|
||||
else if (auto* line = freecad_cast<PartDesign::Line*>(refObject)) {
|
||||
Base::Vector3d d = line->getDirection();
|
||||
dir = gp_Dir(d.x, d.y, d.z);
|
||||
} else if (refObject->isDerivedFrom<App::Line>()) {
|
||||
App::Line* line = static_cast<App::Line*>(refObject);
|
||||
}
|
||||
else if (auto* line = freecad_cast<App::Line*>(refObject)) {
|
||||
Base::Vector3d d = line->getDirection();
|
||||
dir = gp_Dir(d.x, d.y, d.z);
|
||||
} else if (refObject->isDerivedFrom<Part::Feature>()) {
|
||||
if (subStrings[0].empty())
|
||||
}
|
||||
else if (auto* refFeature = freecad_cast<Part::Feature*>(refObject)) {
|
||||
if (subStrings[0].empty()) {
|
||||
throw Base::ValueError("No direction reference specified");
|
||||
Part::Feature* refFeature = static_cast<Part::Feature*>(refObject);
|
||||
}
|
||||
Part::TopoShape refShape = refFeature->Shape.getShape();
|
||||
TopoDS_Shape ref = refShape.getSubShape(subStrings[0].c_str());
|
||||
|
||||
@@ -181,7 +413,8 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
throw Base::TypeError("Direction face must be planar");
|
||||
|
||||
dir = adapt.Plane().Axis().Direction();
|
||||
} else if (ref.ShapeType() == TopAbs_EDGE) {
|
||||
}
|
||||
else if (ref.ShapeType() == TopAbs_EDGE) {
|
||||
TopoDS_Edge refEdge = TopoDS::Edge(ref);
|
||||
if (refEdge.IsNull())
|
||||
throw Base::ValueError("Failed to extract direction edge");
|
||||
@@ -190,45 +423,58 @@ const std::list<gp_Trsf> LinearPattern::getTransformations(const std::vector<App
|
||||
throw Base::ValueError("Direction edge must be a straight line");
|
||||
|
||||
dir = adapt.Line().Direction();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw Base::ValueError("Direction reference must be edge or face");
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw Base::ValueError("Direction reference must be edge/face of a feature or a datum line/plane");
|
||||
}
|
||||
|
||||
TopLoc_Location invObjLoc = this->getLocation().Inverted();
|
||||
dir.Transform(invObjLoc.Transformation());
|
||||
|
||||
gp_Vec offset(dir.X(), dir.Y(), dir.Z());
|
||||
return Base::convertTo<gp_Vec>(dir);
|
||||
}
|
||||
|
||||
switch (static_cast<LinearPatternMode>(Mode.getValue())) {
|
||||
case LinearPatternMode::length:
|
||||
offset *= distance / (occurrences - 1);
|
||||
break;
|
||||
void LinearPattern::updateSpacings()
|
||||
{
|
||||
updateSpacings(LinearPatternDirection::First);
|
||||
updateSpacings(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
case LinearPatternMode::offset:
|
||||
offset *= Offset.getValue();
|
||||
break;
|
||||
void LinearPattern::updateSpacings(LinearPatternDirection dir)
|
||||
{
|
||||
bool isSecondDir = dir == LinearPatternDirection::Second;
|
||||
|
||||
default:
|
||||
throw Base::ValueError("Invalid mode");
|
||||
App::PropertyFloatList& spacingsProp = isSecondDir ? Spacings2 : Spacings;
|
||||
App::PropertyLength& offsetProp = isSecondDir ? Offset2 : Offset;
|
||||
const App::PropertyIntegerConstraint& occurrencesProp = isSecondDir ? Occurrences2 : Occurrences;
|
||||
|
||||
std::vector<double> spacings = spacingsProp.getValues();
|
||||
size_t targetCount = occurrencesProp.getValue() - 1; // 1 less spacing than there are occurrences.
|
||||
|
||||
for (auto& spacing : spacings) {
|
||||
if (spacing == offsetProp.getValue()) {
|
||||
spacing = -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (reversed)
|
||||
offset.Reverse();
|
||||
|
||||
std::list<gp_Trsf> transformations;
|
||||
gp_Trsf trans;
|
||||
transformations.push_back(trans);
|
||||
|
||||
// NOTE: The original feature is already included in the list of transformations!
|
||||
// Therefore we start with occurrence number 1
|
||||
for (int i = 1; i < occurrences; i++) {
|
||||
trans.SetTranslation(offset * i);
|
||||
transformations.push_back(trans);
|
||||
if (spacings.size() == targetCount) {
|
||||
return;
|
||||
}
|
||||
else if (spacings.size() < targetCount) {
|
||||
spacings.reserve(targetCount);
|
||||
while (spacings.size() < targetCount) {
|
||||
spacings.push_back(-1.0);
|
||||
}
|
||||
}
|
||||
else if ((int)spacings.size() > targetCount) {
|
||||
spacings.resize(targetCount);
|
||||
}
|
||||
|
||||
return transformations;
|
||||
spacingsProp.setValues(spacings);
|
||||
}
|
||||
|
||||
void LinearPattern::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop)
|
||||
@@ -249,23 +495,60 @@ void LinearPattern::handleChangedPropertyType(Base::XMLReader& reader, const cha
|
||||
void LinearPattern::onChanged(const App::Property* prop)
|
||||
{
|
||||
auto mode = static_cast<LinearPatternMode>(Mode.getValue());
|
||||
auto mode2 = static_cast<LinearPatternMode>(Mode2.getValue());
|
||||
|
||||
if (prop == &Mode) {
|
||||
setReadWriteStatusForMode(mode);
|
||||
setReadWriteStatusForMode(LinearPatternDirection::First);
|
||||
}
|
||||
else if (prop == &Occurrences) {
|
||||
updateSpacings(LinearPatternDirection::First);
|
||||
syncLengthAndOffset(LinearPatternDirection::First);
|
||||
}
|
||||
else if (prop == &Offset && mode == LinearPatternMode::Spacing) {
|
||||
syncLengthAndOffset(LinearPatternDirection::First);
|
||||
}
|
||||
else if (prop == &Length && mode == LinearPatternMode::Extent) {
|
||||
syncLengthAndOffset(LinearPatternDirection::First);
|
||||
}
|
||||
|
||||
// Keep Length in sync with Offset, catch Occurrences changes
|
||||
if (mode == LinearPatternMode::offset && (prop == &Offset || prop == &Occurrences)
|
||||
&& !Length.testStatus(App::Property::Status::Immutable)) {
|
||||
Length.setValue(Offset.getValue() * (Occurrences.getValue() - 1));
|
||||
else if (prop == &Mode2) {
|
||||
setReadWriteStatusForMode(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
if (mode == LinearPatternMode::length && (prop == &Length || prop == &Occurrences)
|
||||
&& !Offset.testStatus(App::Property::Status::Immutable)) {
|
||||
Offset.setValue(Length.getValue() / (Occurrences.getValue() - 1));
|
||||
else if (prop == &Occurrences2) {
|
||||
updateSpacings(LinearPatternDirection::Second);
|
||||
syncLengthAndOffset(LinearPatternDirection::Second);
|
||||
}
|
||||
else if (prop == &Offset2 && mode2 == LinearPatternMode::Spacing) {
|
||||
syncLengthAndOffset(LinearPatternDirection::Second);
|
||||
}
|
||||
else if (prop == &Length2 && mode2 == LinearPatternMode::Extent) {
|
||||
syncLengthAndOffset(LinearPatternDirection::Second);
|
||||
}
|
||||
|
||||
Transformed::onChanged(prop);
|
||||
}
|
||||
|
||||
void LinearPattern::syncLengthAndOffset(LinearPatternDirection dir)
|
||||
{
|
||||
// Get references to the correct properties based on the direction
|
||||
auto& modeProp = (dir == LinearPatternDirection::First) ? Mode : Mode2;
|
||||
auto& lengthProp = (dir == LinearPatternDirection::First) ? Length : Length2;
|
||||
auto& offsetProp = (dir == LinearPatternDirection::First) ? Offset : Offset2;
|
||||
auto& occurrencesProp = (dir == LinearPatternDirection::First) ? Occurrences : Occurrences2;
|
||||
|
||||
auto mode = static_cast<LinearPatternMode>(modeProp.getValue());
|
||||
int occurrences = occurrencesProp.getValue();
|
||||
occurrences = occurrences <= 1 ? 1 : occurrences - 1;
|
||||
|
||||
if (mode == LinearPatternMode::Spacing
|
||||
&& !lengthProp.testStatus(App::Property::Status::Immutable)) {
|
||||
lengthProp.setValue(offsetProp.getValue() * occurrences);
|
||||
}
|
||||
else if (mode == LinearPatternMode::Extent
|
||||
&& !offsetProp.testStatus(App::Property::Status::Immutable)) {
|
||||
offsetProp.setValue(lengthProp.getValue() / occurrences);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,19 @@
|
||||
#include <App/PropertyUnits.h>
|
||||
#include "FeatureTransformed.h"
|
||||
|
||||
class gp_Vec;
|
||||
|
||||
namespace PartDesign
|
||||
{
|
||||
enum class LinearPatternMode {
|
||||
length,
|
||||
offset
|
||||
Extent,
|
||||
Spacing
|
||||
};
|
||||
|
||||
enum class LinearPatternDirection : std::uint8_t
|
||||
{
|
||||
First,
|
||||
Second
|
||||
};
|
||||
|
||||
class PartDesignExport LinearPattern : public PartDesign::Transformed
|
||||
@@ -47,6 +55,18 @@ public:
|
||||
App::PropertyLength Length;
|
||||
App::PropertyLength Offset;
|
||||
App::PropertyIntegerConstraint Occurrences;
|
||||
App::PropertyFloatList Spacings;
|
||||
App::PropertyFloatList SpacingPattern;
|
||||
|
||||
App::PropertyLinkSub Direction2;
|
||||
App::PropertyBool Reversed2;
|
||||
App::PropertyEnumeration Mode2;
|
||||
App::PropertyLength Length2;
|
||||
App::PropertyLength Offset2;
|
||||
App::PropertyIntegerConstraint Occurrences2;
|
||||
App::PropertyFloatList Spacings2;
|
||||
App::PropertyFloatList SpacingPattern2;
|
||||
|
||||
|
||||
/** @name methods override feature */
|
||||
//@{
|
||||
@@ -85,7 +105,16 @@ protected:
|
||||
private:
|
||||
static const char* ModeEnums[];
|
||||
|
||||
void setReadWriteStatusForMode(LinearPatternMode mode);
|
||||
gp_Dir getDirectionFromProperty(const App::PropertyLinkSub& dirProp) const;
|
||||
|
||||
void setReadWriteStatusForMode(LinearPatternDirection dir);
|
||||
void syncLengthAndOffset(LinearPatternDirection dir);
|
||||
|
||||
void updateSpacings();
|
||||
void updateSpacings(LinearPatternDirection dir);
|
||||
|
||||
gp_Vec calculateOffsetVector(LinearPatternDirection dir) const;
|
||||
std::vector<gp_Vec> calculateSteps(LinearPatternDirection dir, const gp_Vec& offsetVector) const;
|
||||
};
|
||||
|
||||
} //namespace PartDesign
|
||||
|
||||
@@ -53,21 +53,68 @@ const App::PropertyIntegerConstraint::Constraints PolarPattern::intOccurrences =
|
||||
1, std::numeric_limits<int>::max(), 1 };
|
||||
const App::PropertyAngle::Constraints PolarPattern::floatAngle = { Base::toDegrees<double>(Precision::Angular()), 360.0, 1.0 };
|
||||
|
||||
const char* PolarPattern::ModeEnums[] = {"angle", "offset", nullptr};
|
||||
const char* PolarPattern::ModeEnums[] = { "Extent", "Spacing", nullptr};
|
||||
|
||||
PolarPattern::PolarPattern()
|
||||
{
|
||||
auto initialMode = PolarPatternMode::angle;
|
||||
auto initialMode = PolarPatternMode::Extent;
|
||||
|
||||
ADD_PROPERTY_TYPE(Axis, (nullptr), "PolarPattern", (App::PropertyType)(App::Prop_None), "Direction");
|
||||
ADD_PROPERTY(Reversed, (0));
|
||||
ADD_PROPERTY(Mode, (long(initialMode)));
|
||||
ADD_PROPERTY_TYPE(Axis,
|
||||
(nullptr),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"The axis of rotation for the pattern. This can be a datum axis, a sketch "
|
||||
"axis, a circular edge, or the normal of a planar face.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Reversed,
|
||||
(0),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"Reverses the pattern direction from counter-clockwise (default) to clockwise.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Mode,
|
||||
(long(initialMode)),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"Selects how the pattern is dimensioned.\n'Extent': Uses the total angle to contain all "
|
||||
"instances.\n'Spacing': Uses the angle between consecutive instances.");
|
||||
Mode.setEnums(PolarPattern::ModeEnums);
|
||||
ADD_PROPERTY(Angle, (360.0));
|
||||
ADD_PROPERTY(Offset, (120.0));
|
||||
ADD_PROPERTY_TYPE(Angle,
|
||||
(360.0),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"The total angle of the pattern, measured from the first to the last "
|
||||
"instance. This is only used when the Mode is set to 'Extent'.");
|
||||
Angle.setConstraints(&floatAngle);
|
||||
ADD_PROPERTY_TYPE(Offset,
|
||||
(120.0),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"The angular distance between each instance of the pattern. This is only "
|
||||
"used when the Mode is set to 'Spacing'.");
|
||||
Offset.setConstraints(&floatAngle);
|
||||
ADD_PROPERTY(Occurrences, (3));
|
||||
ADD_PROPERTY_TYPE(Spacings,
|
||||
({-1.0, -1.0, -1.0}),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"A list of custom angular spacings between instances. If a value is -1, the "
|
||||
"global 'Offset' is used for that spacing. The list should have one less "
|
||||
"item than the number of occurrences.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
SpacingPattern,
|
||||
({}),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"(Experimental and subject to change. To enable "
|
||||
"this in the UI you can add a boolean parameter 'ExperiementalFeature' in "
|
||||
"Preferences/Mod/Part)\nDefines a repeating pattern of spacings for the second direction. "
|
||||
"For example, a list of [10, 20] will create alternating spacings of 10mm and 20mm.");
|
||||
ADD_PROPERTY_TYPE(
|
||||
Occurrences,
|
||||
(3),
|
||||
"PolarPattern",
|
||||
App::Prop_None,
|
||||
"The total number of instances in the pattern, including the original feature.");
|
||||
Occurrences.setConstraints(&intOccurrences);
|
||||
|
||||
setReadWriteStatusForMode(initialMode);
|
||||
@@ -80,7 +127,9 @@ short PolarPattern::mustExecute() const
|
||||
Mode.isTouched() ||
|
||||
// Angle and Offset are mutually exclusive, only one could be updated at once
|
||||
Angle.isTouched() ||
|
||||
Offset.isTouched() ||
|
||||
Offset.isTouched() ||
|
||||
Spacings.isTouched() ||
|
||||
SpacingPattern.isTouched() ||
|
||||
Occurrences.isTouched())
|
||||
return 1;
|
||||
return Transformed::mustExecute();
|
||||
@@ -173,38 +222,54 @@ const std::list<gp_Trsf> PolarPattern::getTransformations(const std::vector<App:
|
||||
|
||||
double angle;
|
||||
|
||||
switch (static_cast<PolarPatternMode>(Mode.getValue())) {
|
||||
case PolarPatternMode::angle:
|
||||
angle = Angle.getValue();
|
||||
if (Mode.getValue() == (int)PolarPatternMode::Extent) {
|
||||
angle = Angle.getValue();
|
||||
|
||||
if (std::fabs(angle - 360.0) < Precision::Confusion())
|
||||
angle /= occurrences; // Because e.g. two occurrences in 360 degrees need to be 180 degrees apart
|
||||
else
|
||||
angle /= occurrences - 1;
|
||||
if (std::fabs(angle - 360.0) < Precision::Confusion())
|
||||
angle /= occurrences; // Because e.g. two occurrences in 360 degrees need to be 180 degrees apart
|
||||
else
|
||||
angle /= occurrences - 1;
|
||||
|
||||
break;
|
||||
|
||||
case PolarPatternMode::offset:
|
||||
angle = Offset.getValue();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Base::ValueError("Invalid mode");
|
||||
angle = Base::toRadians(angle);
|
||||
if (angle < Precision::Angular()){
|
||||
throw Base::ValueError("Pattern angle too small");
|
||||
}
|
||||
}
|
||||
|
||||
double offset = Base::toRadians<double>(angle);
|
||||
// make sure spacings are correct size :
|
||||
updateSpacings();
|
||||
|
||||
if (offset < Precision::Angular())
|
||||
throw Base::ValueError("Pattern angle too small");
|
||||
const std::vector<double> spacings = Spacings.getValues();
|
||||
const std::vector<double> pattern = SpacingPattern.getValues();
|
||||
bool usePattern = pattern.size() > 1;
|
||||
|
||||
double cumulativeSpacings = 0.0;
|
||||
|
||||
std::list<gp_Trsf> transformations;
|
||||
gp_Trsf trans;
|
||||
transformations.push_back(trans);
|
||||
|
||||
|
||||
// Note: The original feature is already included in the list of transformations!
|
||||
// Therefore we start with occurrence number 1
|
||||
for (int i = 1; i < occurrences; i++) {
|
||||
trans.SetRotation(axis.Axis(), i * offset);
|
||||
if (Mode.getValue() == (int)PolarPatternMode::Spacing) {
|
||||
double spacing;
|
||||
if (spacings[i] != -1.0) {
|
||||
spacing = spacings[i];
|
||||
}
|
||||
else if (usePattern) {
|
||||
spacing = pattern[(size_t)fmod(i, pattern.size())];
|
||||
}
|
||||
else {
|
||||
spacing = Offset.getValue();
|
||||
}
|
||||
cumulativeSpacings += Base::toRadians(spacing);
|
||||
trans.SetRotation(axis.Axis(), cumulativeSpacings);
|
||||
}
|
||||
else {
|
||||
trans.SetRotation(axis.Axis(), angle * i);
|
||||
}
|
||||
transformations.push_back(trans);
|
||||
}
|
||||
|
||||
@@ -239,8 +304,36 @@ void PolarPattern::onChanged(const App::Property* prop)
|
||||
|
||||
void PolarPattern::setReadWriteStatusForMode(PolarPatternMode mode)
|
||||
{
|
||||
Offset.setReadOnly(mode != PolarPatternMode::offset);
|
||||
Angle.setReadOnly(mode != PolarPatternMode::angle);
|
||||
Offset.setReadOnly(mode != PolarPatternMode::Spacing);
|
||||
Angle.setReadOnly(mode != PolarPatternMode::Extent);
|
||||
}
|
||||
|
||||
|
||||
void PolarPattern::updateSpacings()
|
||||
{
|
||||
std::vector<double> spacings = Spacings.getValues();
|
||||
size_t targetCount = Occurrences.getValue();
|
||||
|
||||
for (auto& spacing : spacings) {
|
||||
if (spacing == Offset.getValue()) {
|
||||
spacing = -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (spacings.size() == targetCount) {
|
||||
return;
|
||||
}
|
||||
else if (spacings.size() < targetCount) {
|
||||
spacings.reserve(targetCount);
|
||||
while (spacings.size() < targetCount) {
|
||||
spacings.push_back(-1.0);
|
||||
}
|
||||
}
|
||||
else if ((int)spacings.size() > targetCount) {
|
||||
spacings.resize(targetCount);
|
||||
}
|
||||
|
||||
Spacings.setValues(spacings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
namespace PartDesign
|
||||
{
|
||||
enum class PolarPatternMode {
|
||||
angle,
|
||||
offset
|
||||
Extent,
|
||||
Spacing
|
||||
};
|
||||
|
||||
class PartDesignExport PolarPattern : public PartDesign::Transformed
|
||||
@@ -49,6 +49,9 @@ public:
|
||||
App::PropertyAngle Angle;
|
||||
App::PropertyAngle Offset;
|
||||
App::PropertyIntegerConstraint Occurrences;
|
||||
App::PropertyFloatList Spacings;
|
||||
App::PropertyFloatList SpacingPattern;
|
||||
|
||||
|
||||
/** @name methods override feature */
|
||||
//@{
|
||||
@@ -92,6 +95,8 @@ private:
|
||||
static const char* ModeEnums[];
|
||||
|
||||
void setReadWriteStatusForMode(PolarPatternMode mode);
|
||||
|
||||
void updateSpacings();
|
||||
};
|
||||
|
||||
} //namespace PartDesign
|
||||
|
||||
@@ -62,8 +62,8 @@ extern bool getPDRefineModelParameter();
|
||||
|
||||
PROPERTY_SOURCE(PartDesign::Transformed, PartDesign::FeatureRefine)
|
||||
|
||||
std::array<char const*, 3> transformModeEnums = {"Transform tool shapes",
|
||||
"Transform body",
|
||||
std::array<char const*, 3> transformModeEnums = {"Features",
|
||||
"Whole shape",
|
||||
nullptr};
|
||||
|
||||
Transformed::Transformed()
|
||||
@@ -72,7 +72,7 @@ Transformed::Transformed()
|
||||
Originals.setSize(0);
|
||||
Placement.setStatus(App::Property::ReadOnly, true);
|
||||
|
||||
ADD_PROPERTY(TransformMode, (static_cast<long>(Mode::TransformToolShapes)));
|
||||
ADD_PROPERTY(TransformMode, (static_cast<long>(Mode::Features)));
|
||||
TransformMode.setEnums(transformModeEnums.data());
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ std::vector<App::DocumentObject*> Transformed::getOriginals() const
|
||||
{
|
||||
auto const mode = static_cast<Mode>(TransformMode.getValue());
|
||||
|
||||
if (mode == Mode::TransformBody) {
|
||||
if (mode == Mode::WholeShape) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -248,11 +248,11 @@ App::DocumentObjectExecReturn* Transformed::recomputePreview()
|
||||
};
|
||||
|
||||
switch (mode) {
|
||||
case Mode::TransformToolShapes:
|
||||
case Mode::Features:
|
||||
PreviewShape.setValue(makeCompoundOfToolShapes());
|
||||
return StdReturn;
|
||||
|
||||
case Mode::TransformBody:
|
||||
case Mode::WholeShape:
|
||||
PreviewShape.setValue(getBaseShape());
|
||||
return StdReturn;
|
||||
|
||||
@@ -265,7 +265,7 @@ void Transformed::onChanged(const App::Property* prop)
|
||||
{
|
||||
if (prop == &TransformMode) {
|
||||
auto const mode = static_cast<Mode>(TransformMode.getValue());
|
||||
Originals.setStatus(App::Property::Status::Hidden, mode == Mode::TransformBody);
|
||||
Originals.setStatus(App::Property::Status::Hidden, mode == Mode::WholeShape);
|
||||
}
|
||||
|
||||
FeatureRefine::onChanged(prop);
|
||||
@@ -281,7 +281,7 @@ App::DocumentObjectExecReturn* Transformed::execute()
|
||||
|
||||
std::vector<DocumentObject*> originals = getOriginals();
|
||||
|
||||
if (mode == Mode::TransformToolShapes && originals.empty()) {
|
||||
if (mode == Mode::Features && originals.empty()) {
|
||||
return App::DocumentObject::StdReturn;
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ App::DocumentObjectExecReturn* Transformed::execute()
|
||||
};
|
||||
|
||||
switch (mode) {
|
||||
case Mode::TransformToolShapes:
|
||||
case Mode::Features:
|
||||
// NOTE: It would be possible to build a compound from all original addShapes/subShapes
|
||||
// and then transform the compounds as a whole. But we choose to apply the
|
||||
// transformations to each Original separately. This way it is easier to discover what
|
||||
@@ -398,7 +398,7 @@ App::DocumentObjectExecReturn* Transformed::execute()
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Mode::TransformBody: {
|
||||
case Mode::WholeShape: {
|
||||
auto shapes = getTransformedCompShape(supportShape, supportShape);
|
||||
if (OCCTProgressIndicator::getAppIndicator().UserBreak()) {
|
||||
return new App::DocumentObjectExecReturn("User aborted");
|
||||
@@ -409,9 +409,6 @@ App::DocumentObjectExecReturn* Transformed::execute()
|
||||
}
|
||||
|
||||
supportShape = refineShapeIfActive((supportShape));
|
||||
if (!isSingleSolidRuleSatisfied(supportShape.getShape())) {
|
||||
Base::Console().warning("Transformed: Result has multiple solids. Only keeping the first. Or enable 'Allow Compounds' in the active body.\n");
|
||||
}
|
||||
|
||||
this->Shape.setValue(getSolid(supportShape)); // picking the first solid
|
||||
rejected = getRemainingSolids(supportShape.getShape());
|
||||
|
||||
@@ -44,8 +44,8 @@ class PartDesignExport Transformed: public PartDesign::FeatureRefine
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
TransformToolShapes,
|
||||
TransformBody
|
||||
Features,
|
||||
WholeShape
|
||||
};
|
||||
|
||||
Transformed();
|
||||
|
||||
@@ -39,8 +39,7 @@ set(PartDesignGui_UIC_SRCS
|
||||
TaskTransformedMessages.ui
|
||||
TaskTransformedParameters.ui
|
||||
TaskMirroredParameters.ui
|
||||
TaskLinearPatternParameters.ui
|
||||
TaskPolarPatternParameters.ui
|
||||
TaskPatternParameters.ui
|
||||
TaskScaledParameters.ui
|
||||
TaskMultiTransformParameters.ui
|
||||
TaskShapeBinder.ui
|
||||
@@ -165,12 +164,9 @@ SET(PartDesignGuiTaskDlgs_SRCS
|
||||
TaskMirroredParameters.ui
|
||||
TaskMirroredParameters.cpp
|
||||
TaskMirroredParameters.h
|
||||
TaskLinearPatternParameters.ui
|
||||
TaskLinearPatternParameters.cpp
|
||||
TaskLinearPatternParameters.h
|
||||
TaskPolarPatternParameters.ui
|
||||
TaskPolarPatternParameters.cpp
|
||||
TaskPolarPatternParameters.h
|
||||
TaskPatternParameters.ui
|
||||
TaskPatternParameters.cpp
|
||||
TaskPatternParameters.h
|
||||
TaskScaledParameters.ui
|
||||
TaskScaledParameters.cpp
|
||||
TaskScaledParameters.h
|
||||
|
||||
@@ -1942,7 +1942,7 @@ void prepareTransformed(PartDesign::Body *pcActiveBody, Gui::Command* cmd, const
|
||||
auto Feat = pcActiveBody->getDocument()->getObject(FeatName.c_str());
|
||||
|
||||
if (features.empty()) {
|
||||
FCMD_OBJ_CMD(Feat, "TransformMode = \"Transform body\"");
|
||||
FCMD_OBJ_CMD(Feat, "TransformMode = \"Whole shape\"");
|
||||
} else {
|
||||
std::stringstream str;
|
||||
str << "Originals = [";
|
||||
@@ -2064,6 +2064,7 @@ void CmdPartDesignLinearPattern::activated(int iMsg)
|
||||
Part::Part2DObject *sketch = (static_cast<PartDesign::ProfileBased*>(features.front()))->getVerifiedSketch(/* silent =*/ true);
|
||||
if (sketch) {
|
||||
FCMD_OBJ_CMD(Feat,"Direction = ("<<Gui::Command::getObjectCmd(sketch)<<", ['H_Axis'])");
|
||||
FCMD_OBJ_CMD(Feat,"Direction2 = ("<<Gui::Command::getObjectCmd(sketch)<<", ['V_Axis'])");
|
||||
direction = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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 "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#endif
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/Origin.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Gui/Application.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 "ui_TaskLinearPatternParameters.h"
|
||||
#include "TaskLinearPatternParameters.h"
|
||||
#include "ReferenceSelection.h"
|
||||
#include "TaskMultiTransformParameters.h"
|
||||
|
||||
|
||||
using namespace PartDesignGui;
|
||||
using namespace Gui;
|
||||
|
||||
/* TRANSLATOR PartDesignGui::TaskLinearPatternParameters */
|
||||
|
||||
TaskLinearPatternParameters::TaskLinearPatternParameters(ViewProviderTransformed* TransformedView,
|
||||
QWidget* parent)
|
||||
: TaskTransformedParameters(TransformedView, parent)
|
||||
, ui(new Ui_TaskLinearPatternParameters)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
TaskLinearPatternParameters::TaskLinearPatternParameters(TaskMultiTransformParameters* parentTask,
|
||||
QWidget* parameterWidget)
|
||||
: TaskTransformedParameters(parentTask)
|
||||
, ui(new Ui_TaskLinearPatternParameters)
|
||||
{
|
||||
setupParameterUI(parameterWidget);
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::setupParameterUI(QWidget* widget)
|
||||
{
|
||||
ui->setupUi(widget);
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
|
||||
// Get the feature data
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
|
||||
ui->spinLength->bind(pcLinearPattern->Length);
|
||||
ui->spinOffset->bind(pcLinearPattern->Offset);
|
||||
ui->spinOccurrences->bind(pcLinearPattern->Occurrences);
|
||||
ui->spinOccurrences->setMaximum(pcLinearPattern->Occurrences.getMaximum());
|
||||
ui->spinOccurrences->setMinimum(pcLinearPattern->Occurrences.getMinimum());
|
||||
|
||||
ui->comboDirection->setEnabled(true);
|
||||
ui->checkReverse->setEnabled(true);
|
||||
ui->comboMode->setEnabled(true);
|
||||
ui->spinLength->blockSignals(true);
|
||||
ui->spinLength->setEnabled(true);
|
||||
ui->spinLength->setUnit(Base::Unit::Length);
|
||||
ui->spinLength->blockSignals(false);
|
||||
ui->spinOffset->blockSignals(true);
|
||||
ui->spinOffset->setEnabled(true);
|
||||
ui->spinOffset->setUnit(Base::Unit::Length);
|
||||
ui->spinOffset->blockSignals(false);
|
||||
ui->spinOccurrences->setEnabled(true);
|
||||
|
||||
dirLinks.setCombo(*(ui->comboDirection));
|
||||
App::DocumentObject* sketch = getSketchObject();
|
||||
if (sketch && sketch->isDerivedFrom<Part::Part2DObject>()) {
|
||||
this->fillAxisCombo(dirLinks, static_cast<Part::Part2DObject*>(sketch));
|
||||
}
|
||||
else {
|
||||
this->fillAxisCombo(dirLinks, nullptr);
|
||||
}
|
||||
|
||||
// show the parts coordinate system axis for selection
|
||||
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));
|
||||
vpOrigin->setTemporaryVisibility(Gui::DatumElement::Axes);
|
||||
}
|
||||
catch (const Base::Exception& ex) {
|
||||
Base::Console().error("%s\n", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
adaptVisibilityToMode();
|
||||
|
||||
updateViewTimer = new QTimer(this);
|
||||
updateViewTimer->setSingleShot(true);
|
||||
updateViewTimer->setInterval(getUpdateViewTimeout());
|
||||
connect(updateViewTimer,
|
||||
&QTimer::timeout,
|
||||
this,
|
||||
&TaskLinearPatternParameters::onUpdateViewTimer);
|
||||
|
||||
connect(ui->comboDirection,
|
||||
qOverload<int>(&QComboBox::activated),
|
||||
this,
|
||||
&TaskLinearPatternParameters::onDirectionChanged);
|
||||
connect(ui->checkReverse,
|
||||
&QCheckBox::toggled,
|
||||
this,
|
||||
&TaskLinearPatternParameters::onCheckReverse);
|
||||
connect(ui->comboMode,
|
||||
qOverload<int>(&QComboBox::activated),
|
||||
this,
|
||||
&TaskLinearPatternParameters::onModeChanged);
|
||||
connect(ui->spinLength,
|
||||
qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
|
||||
this,
|
||||
&TaskLinearPatternParameters::onLength);
|
||||
connect(ui->spinOffset,
|
||||
qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
|
||||
this,
|
||||
&TaskLinearPatternParameters::onOffset);
|
||||
connect(ui->spinOccurrences,
|
||||
&Gui::UIntSpinBox::unsignedChanged,
|
||||
this,
|
||||
&TaskLinearPatternParameters::onOccurrences);
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::retranslateParameterUI(QWidget* widget)
|
||||
{
|
||||
ui->retranslateUi(widget);
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::updateUI()
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
blockUpdate = true;
|
||||
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
auto mode = static_cast<PartDesign::LinearPatternMode>(pcLinearPattern->Mode.getValue());
|
||||
|
||||
bool reverse = pcLinearPattern->Reversed.getValue();
|
||||
double length = pcLinearPattern->Length.getValue();
|
||||
double offset = pcLinearPattern->Offset.getValue();
|
||||
unsigned occurrences = pcLinearPattern->Occurrences.getValue();
|
||||
|
||||
if (dirLinks.setCurrentLink(pcLinearPattern->Direction) == -1) {
|
||||
// failed to set current, because the link isn't in the list yet
|
||||
dirLinks.addLink(pcLinearPattern->Direction,
|
||||
getRefStr(pcLinearPattern->Direction.getValue(),
|
||||
pcLinearPattern->Direction.getSubValues()));
|
||||
dirLinks.setCurrentLink(pcLinearPattern->Direction);
|
||||
}
|
||||
|
||||
// Note: This block of code would trigger change signal handlers (e.g. onOccurrences())
|
||||
// and another updateUI() if we didn't check for blockUpdate
|
||||
ui->checkReverse->setChecked(reverse);
|
||||
ui->comboMode->setCurrentIndex(static_cast<int>(mode));
|
||||
ui->spinLength->setValue(length);
|
||||
ui->spinOffset->setValue(offset);
|
||||
ui->spinOccurrences->setValue(occurrences);
|
||||
|
||||
blockUpdate = false;
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::adaptVisibilityToMode()
|
||||
{
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
auto mode = static_cast<PartDesign::LinearPatternMode>(pcLinearPattern->Mode.getValue());
|
||||
|
||||
ui->lengthWrapper->setVisible(mode == PartDesign::LinearPatternMode::length);
|
||||
ui->offsetWrapper->setVisible(mode == PartDesign::LinearPatternMode::offset);
|
||||
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onUpdateViewTimer()
|
||||
{
|
||||
setupTransaction();
|
||||
recomputeFeature();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::kickUpdateViewTimer() const
|
||||
{
|
||||
updateViewTimer->start();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onSelectionChanged(const Gui::SelectionChanges& msg)
|
||||
{
|
||||
if (selectionMode != SelectionMode::None && msg.Type == Gui::SelectionChanges::AddSelection) {
|
||||
if (originalSelected(msg)) {
|
||||
exitSelectionMode();
|
||||
}
|
||||
else {
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
|
||||
std::vector<std::string> directions;
|
||||
App::DocumentObject* selObj = nullptr;
|
||||
getReferencedSelection(pcLinearPattern, msg, selObj, directions);
|
||||
if (!selObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: ReferenceSelection has already checked the selection for validity
|
||||
if (selectionMode == SelectionMode::Reference || selObj->isDerivedFrom<App::Line>()) {
|
||||
setupTransaction();
|
||||
pcLinearPattern->Direction.setValue(selObj, directions);
|
||||
recomputeFeature();
|
||||
updateUI();
|
||||
}
|
||||
exitSelectionMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onCheckReverse(const bool on)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
pcLinearPattern->Reversed.setValue(on);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onModeChanged(const int mode)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
pcLinearPattern->Mode.setValue(mode);
|
||||
|
||||
adaptVisibilityToMode();
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onLength(const double length)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
pcLinearPattern->Length.setValue(length);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onOffset(const double offset)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
pcLinearPattern->Offset.setValue(offset);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onOccurrences(const uint number)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
pcLinearPattern->Occurrences.setValue(number);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onDirectionChanged(int /*num*/)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
try {
|
||||
if (!dirLinks.getCurrentLink().getValue()) {
|
||||
// enter reference selection mode
|
||||
hideObject();
|
||||
showBase();
|
||||
selectionMode = SelectionMode::Reference;
|
||||
Gui::Selection().clearSelection();
|
||||
addReferenceSelectionGate(AllowSelection::EDGE | AllowSelection::FACE
|
||||
| AllowSelection::PLANAR);
|
||||
}
|
||||
else {
|
||||
exitSelectionMode();
|
||||
pcLinearPattern->Direction.Paste(dirLinks.getCurrentLink());
|
||||
}
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
QMessageBox::warning(nullptr, tr("Error"), QApplication::translate("Exception", e.what()));
|
||||
}
|
||||
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::onUpdateView(bool on)
|
||||
{
|
||||
blockUpdate = !on;
|
||||
if (on) {
|
||||
// Do the same like in TaskDlgLinearPatternParameters::accept() but without doCommand
|
||||
auto pcLinearPattern = getObject<PartDesign::LinearPattern>();
|
||||
std::vector<std::string> directions;
|
||||
App::DocumentObject* obj = nullptr;
|
||||
|
||||
setupTransaction();
|
||||
getDirection(obj, directions);
|
||||
pcLinearPattern->Direction.setValue(obj, directions);
|
||||
pcLinearPattern->Reversed.setValue(getReverse());
|
||||
pcLinearPattern->Length.setValue(getLength());
|
||||
pcLinearPattern->Offset.setValue(getOffset());
|
||||
pcLinearPattern->Occurrences.setValue(getOccurrences());
|
||||
|
||||
recomputeFeature();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::getDirection(App::DocumentObject*& obj,
|
||||
std::vector<std::string>& sub) const
|
||||
{
|
||||
const App::PropertyLinkSub& lnk = dirLinks.getCurrentLink();
|
||||
obj = lnk.getValue();
|
||||
sub = lnk.getSubValues();
|
||||
}
|
||||
|
||||
bool TaskLinearPatternParameters::getReverse() const
|
||||
{
|
||||
return ui->checkReverse->isChecked();
|
||||
}
|
||||
|
||||
int TaskLinearPatternParameters::getMode() const
|
||||
{
|
||||
return ui->comboMode->currentIndex();
|
||||
}
|
||||
|
||||
double TaskLinearPatternParameters::getLength() const
|
||||
{
|
||||
return ui->spinLength->value().getValue();
|
||||
}
|
||||
|
||||
double TaskLinearPatternParameters::getOffset() const
|
||||
{
|
||||
return ui->spinOffset->value().getValue();
|
||||
}
|
||||
|
||||
unsigned TaskLinearPatternParameters::getOccurrences() const
|
||||
{
|
||||
return ui->spinOccurrences->value();
|
||||
}
|
||||
|
||||
TaskLinearPatternParameters::~TaskLinearPatternParameters()
|
||||
{
|
||||
try {
|
||||
// hide the parts coordinate system axis for selection
|
||||
PartDesign::Body* body = PartDesign::Body::findBodyOf(getObject());
|
||||
if (body) {
|
||||
App::Origin* origin = body->getOrigin();
|
||||
auto vpOrigin = static_cast<ViewProviderCoordinateSystem*>(
|
||||
Gui::Application::Instance->getViewProvider(origin));
|
||||
vpOrigin->resetTemporaryVisibility();
|
||||
}
|
||||
}
|
||||
catch (const Base::Exception& ex) {
|
||||
Base::Console().error("%s\n", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void TaskLinearPatternParameters::apply()
|
||||
{
|
||||
std::vector<std::string> directions;
|
||||
App::DocumentObject* obj = nullptr;
|
||||
getDirection(obj, directions);
|
||||
std::string direction = buildLinkSingleSubPythonStr(obj, directions);
|
||||
|
||||
auto tobj = getObject();
|
||||
FCMD_OBJ_CMD(tobj, "Direction = " << direction);
|
||||
FCMD_OBJ_CMD(tobj, "Reversed = " << getReverse());
|
||||
FCMD_OBJ_CMD(tobj, "Mode = " << getMode());
|
||||
ui->spinLength->apply();
|
||||
ui->spinOffset->apply();
|
||||
ui->spinOccurrences->apply();
|
||||
}
|
||||
|
||||
//**************************************************************************
|
||||
//**************************************************************************
|
||||
// TaskDialog
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
TaskDlgLinearPatternParameters::TaskDlgLinearPatternParameters(
|
||||
ViewProviderLinearPattern* LinearPatternView)
|
||||
: TaskDlgTransformedParameters(LinearPatternView)
|
||||
{
|
||||
parameter = new TaskLinearPatternParameters(LinearPatternView);
|
||||
|
||||
Content.push_back(parameter);
|
||||
Content.push_back(preview);
|
||||
}
|
||||
|
||||
#include "moc_TaskLinearPatternParameters.cpp"
|
||||
@@ -1,188 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PartDesignGui::TaskLinearPatternParameters</class>
|
||||
<widget class="QWidget" name="PartDesignGui::TaskLinearPatternParameters">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>270</width>
|
||||
<height>188</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelDirection">
|
||||
<property name="text">
|
||||
<string>Direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboDirection"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkReverse">
|
||||
<property name="text">
|
||||
<string>Reverse direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelMode">
|
||||
<property name="text">
|
||||
<string>Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Overall length</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Offset</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="lengthWrapper" native="true">
|
||||
<layout class="QHBoxLayout" name="_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLength">
|
||||
<property name="text">
|
||||
<string>Length</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="spinLength" native="true">
|
||||
<property name="keyboardTracking" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>100.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="offsetWrapper" native="true">
|
||||
<layout class="QHBoxLayout" name="_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelOffset">
|
||||
<property name="text">
|
||||
<string>Offset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="spinOffset" native="true">
|
||||
<property name="keyboardTracking" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">mm</string>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Occurrences</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::UIntSpinBox" name="spinOccurrences"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::QuantitySpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Gui/QuantitySpinBox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::UIntSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>Gui/SpinBox.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>comboDirection</tabstop>
|
||||
<tabstop>checkReverse</tabstop>
|
||||
<tabstop>spinLength</tabstop>
|
||||
<tabstop>spinOccurrences</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -74,7 +74,7 @@ void TaskMirroredParameters::setupParameterUI(QWidget* widget)
|
||||
this,
|
||||
&TaskMirroredParameters::onPlaneChanged);
|
||||
|
||||
this->planeLinks.setCombo(*(ui->comboPlane));
|
||||
this->planeLinks.setCombo(ui->comboPlane);
|
||||
ui->comboPlane->setEnabled(true);
|
||||
|
||||
App::DocumentObject* sketch = getSketchObject();
|
||||
@@ -131,27 +131,22 @@ void TaskMirroredParameters::onSelectionChanged(const Gui::SelectionChanges& msg
|
||||
{
|
||||
if (selectionMode != SelectionMode::None && msg.Type == Gui::SelectionChanges::AddSelection) {
|
||||
|
||||
if (originalSelected(msg)) {
|
||||
exitSelectionMode();
|
||||
}
|
||||
else {
|
||||
auto pcMirrored = getObject<PartDesign::Mirrored>();
|
||||
auto pcMirrored = getObject<PartDesign::Mirrored>();
|
||||
|
||||
std::vector<std::string> mirrorPlanes;
|
||||
App::DocumentObject* selObj = nullptr;
|
||||
getReferencedSelection(pcMirrored, msg, selObj, mirrorPlanes);
|
||||
if (!selObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectionMode == SelectionMode::Reference || selObj->isDerivedFrom<App::Plane>()) {
|
||||
setupTransaction();
|
||||
pcMirrored->MirrorPlane.setValue(selObj, mirrorPlanes);
|
||||
recomputeFeature();
|
||||
updateUI();
|
||||
}
|
||||
exitSelectionMode();
|
||||
std::vector<std::string> mirrorPlanes;
|
||||
App::DocumentObject* selObj = nullptr;
|
||||
getReferencedSelection(pcMirrored, msg, selObj, mirrorPlanes);
|
||||
if (!selObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectionMode == SelectionMode::Reference || selObj->isDerivedFrom<App::Plane>()) {
|
||||
setupTransaction();
|
||||
pcMirrored->MirrorPlane.setValue(selObj, mirrorPlanes);
|
||||
recomputeFeature();
|
||||
updateUI();
|
||||
}
|
||||
exitSelectionMode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ private:
|
||||
void getMirrorPlane(App::DocumentObject*& obj, std::vector<std::string>& sub) const;
|
||||
|
||||
private:
|
||||
ComboLinks planeLinks;
|
||||
Gui::ComboLinks planeLinks;
|
||||
std::unique_ptr<Ui_TaskMirroredParameters> ui;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
#include "ui_TaskMultiTransformParameters.h"
|
||||
#include "TaskMultiTransformParameters.h"
|
||||
#include "TaskMirroredParameters.h"
|
||||
#include "TaskLinearPatternParameters.h"
|
||||
#include "TaskPolarPatternParameters.h"
|
||||
#include "TaskPatternParameters.h"
|
||||
#include "TaskScaledParameters.h"
|
||||
#include "Utils.h"
|
||||
|
||||
@@ -170,6 +169,10 @@ void TaskMultiTransformParameters::closeSubTask()
|
||||
subTask->apply();
|
||||
}
|
||||
|
||||
delete subTask;
|
||||
subTask = nullptr;
|
||||
subFeature = nullptr;
|
||||
|
||||
// Remove all parameter ui widgets and layout
|
||||
ui->subFeatureWidget->setUpdatesEnabled(false);
|
||||
qDeleteAll(
|
||||
@@ -177,10 +180,6 @@ void TaskMultiTransformParameters::closeSubTask()
|
||||
qDeleteAll(
|
||||
ui->subFeatureWidget->findChildren<QLayout*>(QString(), Qt::FindDirectChildrenOnly));
|
||||
ui->subFeatureWidget->setUpdatesEnabled(true);
|
||||
|
||||
delete subTask;
|
||||
subTask = nullptr;
|
||||
subFeature = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,11 +229,9 @@ void TaskMultiTransformParameters::onTransformEdit()
|
||||
if (subFeature->is<PartDesign::Mirrored>()) {
|
||||
subTask = new TaskMirroredParameters(this, ui->subFeatureWidget);
|
||||
}
|
||||
else if (subFeature->is<PartDesign::LinearPattern>()) {
|
||||
subTask = new TaskLinearPatternParameters(this, ui->subFeatureWidget);
|
||||
}
|
||||
else if (subFeature->is<PartDesign::PolarPattern>()) {
|
||||
subTask = new TaskPolarPatternParameters(this, ui->subFeatureWidget);
|
||||
else if (subFeature->is<PartDesign::LinearPattern>()
|
||||
|| subFeature->is<PartDesign::PolarPattern>()) {
|
||||
subTask = new TaskPatternParameters(this, ui->subFeatureWidget);
|
||||
}
|
||||
else if (subFeature->is<PartDesign::Scaled>()) {
|
||||
subTask = new TaskScaledParameters(this, ui->subFeatureWidget);
|
||||
|
||||
383
src/Mod/PartDesign/Gui/TaskPatternParameters.cpp
Normal file
383
src/Mod/PartDesign/Gui/TaskPatternParameters.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
/******************************************************************************
|
||||
* 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 "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#endif
|
||||
|
||||
#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"));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
//**************************************************************************
|
||||
// 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"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/******************************************************************************
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2012 Jan Rheinländer <jrheinlaender@users.sourceforge.net> *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
@@ -20,24 +20,18 @@
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef GUI_TASKVIEW_TaskLinearPatternParameters_H
|
||||
#define GUI_TASKVIEW_TaskLinearPatternParameters_H
|
||||
#ifndef GUI_TASKVIEW_TaskPatternParameters_H
|
||||
#define GUI_TASKVIEW_TaskPatternParameters_H
|
||||
|
||||
#include "TaskTransformedParameters.h"
|
||||
#include "ViewProviderLinearPattern.h"
|
||||
#include "ViewProviderTransformed.h"
|
||||
|
||||
|
||||
class QTimer;
|
||||
class Ui_TaskLinearPatternParameters;
|
||||
class Ui_TaskPatternParameters;
|
||||
|
||||
namespace App
|
||||
{
|
||||
class Property;
|
||||
}
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class ViewProvider;
|
||||
namespace PartGui {
|
||||
class PatternParametersWidget;
|
||||
}
|
||||
|
||||
namespace PartDesignGui
|
||||
@@ -45,17 +39,17 @@ namespace PartDesignGui
|
||||
|
||||
class TaskMultiTransformParameters;
|
||||
|
||||
class TaskLinearPatternParameters: public TaskTransformedParameters
|
||||
class TaskPatternParameters : public TaskTransformedParameters
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// Constructor for task with ViewProvider
|
||||
explicit TaskLinearPatternParameters(ViewProviderTransformed* TransformedView,
|
||||
explicit TaskPatternParameters(ViewProviderTransformed* TransformedView,
|
||||
QWidget* parent = nullptr);
|
||||
/// Constructor for task with parent task (MultiTransform mode)
|
||||
TaskLinearPatternParameters(TaskMultiTransformParameters* parentTask, QWidget* parameterWidget);
|
||||
~TaskLinearPatternParameters() override;
|
||||
TaskPatternParameters(TaskMultiTransformParameters* parentTask, QWidget* parameterWidget);
|
||||
~TaskPatternParameters() override;
|
||||
|
||||
void apply() override;
|
||||
|
||||
@@ -64,47 +58,49 @@ protected:
|
||||
|
||||
private Q_SLOTS:
|
||||
void onUpdateViewTimer();
|
||||
void onDirectionChanged(int num);
|
||||
void onCheckReverse(bool on);
|
||||
void onModeChanged(int mode);
|
||||
void onLength(double length);
|
||||
void onOffset(double offset);
|
||||
void onOccurrences(uint number);
|
||||
void onUpdateView(bool /*unused*/) override;
|
||||
// Slot to handle reference selection request from the widget
|
||||
void onParameterWidgetRequestReferenceSelection();
|
||||
void onParameterWidgetRequestReferenceSelection2();
|
||||
// Slot to handle parameter changes from the widget
|
||||
void onParameterWidgetParametersChanged();
|
||||
// Update view signal (might be redundant now)
|
||||
void onUpdateView(bool on) override;
|
||||
|
||||
|
||||
private:
|
||||
void setupParameterUI(QWidget* widget) override;
|
||||
void retranslateParameterUI(QWidget* widget) override;
|
||||
|
||||
void connectSignals();
|
||||
void updateUI();
|
||||
void adaptVisibilityToMode();
|
||||
void kickUpdateViewTimer() const;
|
||||
|
||||
void getDirection(App::DocumentObject*& obj, std::vector<std::string>& sub) const;
|
||||
bool getReverse() const;
|
||||
int getMode() const;
|
||||
double getLength() const;
|
||||
double getOffset() const;
|
||||
unsigned getOccurrences() const;
|
||||
void bindProperties();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui_TaskLinearPatternParameters> ui;
|
||||
// Task-specific logic remains
|
||||
void showOriginAxes(bool show);
|
||||
void enterReferenceSelectionMode();
|
||||
void exitReferenceSelectionMode(); // Ensure this clears gates etc.
|
||||
|
||||
PartGui::PatternParametersWidget* parametersWidget = nullptr;
|
||||
PartGui::PatternParametersWidget* parametersWidget2 = nullptr;
|
||||
|
||||
PartGui::PatternParametersWidget* activeDirectionWidget = nullptr;
|
||||
|
||||
std::unique_ptr<Ui_TaskPatternParameters> ui;
|
||||
QTimer* updateViewTimer = nullptr;
|
||||
|
||||
ComboLinks dirLinks;
|
||||
};
|
||||
|
||||
|
||||
/// simulation dialog for the TaskView
|
||||
class TaskDlgLinearPatternParameters: public TaskDlgTransformedParameters
|
||||
class TaskDlgLinearPatternParameters : public TaskDlgTransformedParameters
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TaskDlgLinearPatternParameters(ViewProviderLinearPattern* LinearPatternView);
|
||||
explicit TaskDlgLinearPatternParameters(ViewProviderTransformed* LinearPatternView);
|
||||
};
|
||||
|
||||
} // namespace PartDesignGui
|
||||
|
||||
#endif // GUI_TASKVIEW_TASKAPPERANCE_H
|
||||
#endif // GUI_TASKVIEW_TaskPatternParameters_H
|
||||
|
||||
39
src/Mod/PartDesign/Gui/TaskPatternParameters.ui
Normal file
39
src/Mod/PartDesign/Gui/TaskPatternParameters.ui
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PartDesignGui::TaskPatternParameters</class>
|
||||
<widget class="QWidget" name="PartDesignGui::TaskPatternParameters">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>270</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="parametersWidgetPlaceholder" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="parametersWidgetPlaceholder2" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,432 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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 "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#endif
|
||||
|
||||
#include <Base/UnitsApi.h>
|
||||
#include <App/Application.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/Origin.h>
|
||||
#include <App/Datums.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/Document.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Gui/ViewProvider.h>
|
||||
#include <Gui/WaitCursor.h>
|
||||
#include <Base/Console.h>
|
||||
#include <Gui/Selection/Selection.h>
|
||||
#include <Gui/Command.h>
|
||||
#include <Gui/ViewProviderCoordinateSystem.h>
|
||||
#include <Mod/PartDesign/App/FeaturePolarPattern.h>
|
||||
#include <Mod/Sketcher/App/SketchObject.h>
|
||||
#include <Mod/PartDesign/App/DatumLine.h>
|
||||
#include <Mod/PartDesign/App/Body.h>
|
||||
|
||||
#include "ReferenceSelection.h"
|
||||
#include "TaskMultiTransformParameters.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "ui_TaskPolarPatternParameters.h"
|
||||
#include "TaskPolarPatternParameters.h"
|
||||
|
||||
using namespace PartDesignGui;
|
||||
using namespace Gui;
|
||||
|
||||
/* TRANSLATOR PartDesignGui::TaskPolarPatternParameters */
|
||||
|
||||
TaskPolarPatternParameters::TaskPolarPatternParameters(ViewProviderTransformed* TransformedView,
|
||||
QWidget* parent)
|
||||
: TaskTransformedParameters(TransformedView, parent)
|
||||
, ui(new Ui_TaskPolarPatternParameters)
|
||||
{
|
||||
setupUI();
|
||||
}
|
||||
|
||||
TaskPolarPatternParameters::TaskPolarPatternParameters(TaskMultiTransformParameters* parentTask,
|
||||
QWidget* parameterWidget)
|
||||
: TaskTransformedParameters(parentTask)
|
||||
, ui(new Ui_TaskPolarPatternParameters)
|
||||
{
|
||||
setupParameterUI(parameterWidget);
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::setupParameterUI(QWidget* widget)
|
||||
{
|
||||
ui->setupUi(widget);
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
|
||||
// Get the feature data
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
|
||||
ui->polarAngle->bind(pcPolarPattern->Angle);
|
||||
ui->angleOffset->bind(pcPolarPattern->Offset);
|
||||
|
||||
ui->spinOccurrences->bind(pcPolarPattern->Occurrences);
|
||||
ui->spinOccurrences->setMaximum(pcPolarPattern->Occurrences.getMaximum());
|
||||
ui->spinOccurrences->setMinimum(pcPolarPattern->Occurrences.getMinimum());
|
||||
|
||||
ui->comboAxis->setEnabled(true);
|
||||
ui->comboMode->setEnabled(true);
|
||||
ui->checkReverse->setEnabled(true);
|
||||
ui->polarAngle->setEnabled(true);
|
||||
ui->spinOccurrences->setEnabled(true);
|
||||
|
||||
this->axesLinks.setCombo(*(ui->comboAxis));
|
||||
App::DocumentObject* sketch = getSketchObject();
|
||||
if (sketch && sketch->isDerivedFrom<Part::Part2DObject>()) {
|
||||
this->fillAxisCombo(axesLinks, static_cast<Part::Part2DObject*>(sketch));
|
||||
}
|
||||
else {
|
||||
this->fillAxisCombo(axesLinks, nullptr);
|
||||
}
|
||||
|
||||
// show the parts coordinate system axis for selection
|
||||
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));
|
||||
vpOrigin->setTemporaryVisibility(Gui::DatumElement::Axes);
|
||||
}
|
||||
catch (const Base::Exception& ex) {
|
||||
Base::Console().error("%s\n", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
adaptVisibilityToMode();
|
||||
updateUI();
|
||||
|
||||
updateViewTimer = new QTimer(this);
|
||||
updateViewTimer->setSingleShot(true);
|
||||
updateViewTimer->setInterval(getUpdateViewTimeout());
|
||||
connect(updateViewTimer,
|
||||
&QTimer::timeout,
|
||||
this,
|
||||
&TaskPolarPatternParameters::onUpdateViewTimer);
|
||||
connect(ui->comboAxis,
|
||||
qOverload<int>(&QComboBox::activated),
|
||||
this,
|
||||
&TaskPolarPatternParameters::onAxisChanged);
|
||||
connect(ui->comboMode,
|
||||
qOverload<int>(&QComboBox::activated),
|
||||
this,
|
||||
&TaskPolarPatternParameters::onModeChanged);
|
||||
connect(ui->checkReverse,
|
||||
&QCheckBox::toggled,
|
||||
this,
|
||||
&TaskPolarPatternParameters::onCheckReverse);
|
||||
connect(ui->polarAngle,
|
||||
qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
|
||||
this,
|
||||
&TaskPolarPatternParameters::onAngle);
|
||||
connect(ui->angleOffset,
|
||||
qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
|
||||
this,
|
||||
&TaskPolarPatternParameters::onOffset);
|
||||
connect(ui->spinOccurrences,
|
||||
&Gui::UIntSpinBox::unsignedChanged,
|
||||
this,
|
||||
&TaskPolarPatternParameters::onOccurrences);
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::retranslateParameterUI(QWidget* widget)
|
||||
{
|
||||
ui->retranslateUi(widget);
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::updateUI()
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
blockUpdate = true;
|
||||
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
|
||||
auto mode = static_cast<PartDesign::PolarPatternMode>(pcPolarPattern->Mode.getValue());
|
||||
bool reverse = pcPolarPattern->Reversed.getValue();
|
||||
double angle = pcPolarPattern->Angle.getValue();
|
||||
double offset = pcPolarPattern->Offset.getValue();
|
||||
unsigned occurrences = pcPolarPattern->Occurrences.getValue();
|
||||
|
||||
if (axesLinks.setCurrentLink(pcPolarPattern->Axis) == -1) {
|
||||
// failed to set current, because the link isn't in the list yet
|
||||
axesLinks.addLink(
|
||||
pcPolarPattern->Axis,
|
||||
getRefStr(pcPolarPattern->Axis.getValue(), pcPolarPattern->Axis.getSubValues()));
|
||||
axesLinks.setCurrentLink(pcPolarPattern->Axis);
|
||||
}
|
||||
|
||||
// Note: This block of code would trigger change signal handlers (e.g. onOccurrences())
|
||||
// and another updateUI() if we didn't check for blockUpdate
|
||||
ui->checkReverse->setChecked(reverse);
|
||||
ui->comboMode->setCurrentIndex(static_cast<int>(mode));
|
||||
ui->polarAngle->setValue(angle);
|
||||
ui->angleOffset->setValue(offset);
|
||||
ui->spinOccurrences->setValue(occurrences);
|
||||
|
||||
blockUpdate = false;
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onUpdateViewTimer()
|
||||
{
|
||||
setupTransaction();
|
||||
recomputeFeature();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::kickUpdateViewTimer() const
|
||||
{
|
||||
updateViewTimer->start();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::adaptVisibilityToMode()
|
||||
{
|
||||
auto pcLinearPattern = getObject<PartDesign::PolarPattern>();
|
||||
auto mode = static_cast<PartDesign::PolarPatternMode>(pcLinearPattern->Mode.getValue());
|
||||
|
||||
ui->polarAngleWrapper->setVisible(mode == PartDesign::PolarPatternMode::angle);
|
||||
ui->angleOffsetWrapper->setVisible(mode == PartDesign::PolarPatternMode::offset);
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onSelectionChanged(const Gui::SelectionChanges& msg)
|
||||
{
|
||||
if (selectionMode != SelectionMode::None && msg.Type == Gui::SelectionChanges::AddSelection) {
|
||||
if (originalSelected(msg)) {
|
||||
exitSelectionMode();
|
||||
}
|
||||
else {
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
|
||||
std::vector<std::string> axes;
|
||||
App::DocumentObject* selObj = nullptr;
|
||||
getReferencedSelection(pcPolarPattern, msg, selObj, axes);
|
||||
if (!selObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectionMode == SelectionMode::Reference || selObj->isDerivedFrom<App::Line>()) {
|
||||
setupTransaction();
|
||||
pcPolarPattern->Axis.setValue(selObj, axes);
|
||||
recomputeFeature();
|
||||
updateUI();
|
||||
}
|
||||
exitSelectionMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onCheckReverse(const bool on)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
pcPolarPattern->Reversed.setValue(on);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onModeChanged(const int mode)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
pcPolarPattern->Mode.setValue(mode);
|
||||
|
||||
adaptVisibilityToMode();
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onAngle(const double angle)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
pcPolarPattern->Angle.setValue(angle);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onOffset(const double offset)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
pcPolarPattern->Offset.setValue(offset);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onOccurrences(const uint n)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
pcPolarPattern->Occurrences.setValue(n);
|
||||
|
||||
exitSelectionMode();
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onAxisChanged(int /*num*/)
|
||||
{
|
||||
if (blockUpdate) {
|
||||
return;
|
||||
}
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
|
||||
try {
|
||||
if (!axesLinks.getCurrentLink().getValue()) {
|
||||
// enter reference selection mode
|
||||
hideObject();
|
||||
showBase();
|
||||
selectionMode = SelectionMode::Reference;
|
||||
Gui::Selection().clearSelection();
|
||||
addReferenceSelectionGate(AllowSelection::EDGE | AllowSelection::CIRCLE);
|
||||
}
|
||||
else {
|
||||
exitSelectionMode();
|
||||
pcPolarPattern->Axis.Paste(axesLinks.getCurrentLink());
|
||||
}
|
||||
}
|
||||
catch (Base::Exception& e) {
|
||||
QMessageBox::warning(nullptr, tr("Error"), QApplication::translate("Exception", e.what()));
|
||||
}
|
||||
|
||||
kickUpdateViewTimer();
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::onUpdateView(bool on)
|
||||
{
|
||||
blockUpdate = !on;
|
||||
if (on) {
|
||||
// Do the same like in TaskDlgPolarPatternParameters::accept() but without doCommand
|
||||
auto pcPolarPattern = getObject<PartDesign::PolarPattern>();
|
||||
std::vector<std::string> axes;
|
||||
App::DocumentObject* obj = nullptr;
|
||||
|
||||
setupTransaction();
|
||||
getAxis(obj, axes);
|
||||
pcPolarPattern->Axis.setValue(obj, axes);
|
||||
pcPolarPattern->Reversed.setValue(getReverse());
|
||||
pcPolarPattern->Angle.setValue(getAngle());
|
||||
pcPolarPattern->Occurrences.setValue(getOccurrences());
|
||||
|
||||
recomputeFeature();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::getAxis(App::DocumentObject*& obj,
|
||||
std::vector<std::string>& sub) const
|
||||
{
|
||||
const App::PropertyLinkSub& lnk = axesLinks.getCurrentLink();
|
||||
obj = lnk.getValue();
|
||||
sub = lnk.getSubValues();
|
||||
}
|
||||
|
||||
bool TaskPolarPatternParameters::getReverse() const
|
||||
{
|
||||
return ui->checkReverse->isChecked();
|
||||
}
|
||||
|
||||
int TaskPolarPatternParameters::getMode() const
|
||||
{
|
||||
return ui->comboMode->currentIndex();
|
||||
}
|
||||
|
||||
double TaskPolarPatternParameters::getAngle() const
|
||||
{
|
||||
return ui->polarAngle->value().getValue();
|
||||
}
|
||||
|
||||
unsigned TaskPolarPatternParameters::getOccurrences() const
|
||||
{
|
||||
return ui->spinOccurrences->value();
|
||||
}
|
||||
|
||||
|
||||
TaskPolarPatternParameters::~TaskPolarPatternParameters()
|
||||
{
|
||||
// hide the parts coordinate system axis for selection
|
||||
try {
|
||||
PartDesign::Body* body = PartDesign::Body::findBodyOf(getObject());
|
||||
if (body) {
|
||||
App::Origin* origin = body->getOrigin();
|
||||
auto vpOrigin = static_cast<ViewProviderCoordinateSystem*>(
|
||||
Gui::Application::Instance->getViewProvider(origin));
|
||||
vpOrigin->resetTemporaryVisibility();
|
||||
}
|
||||
}
|
||||
catch (const Base::Exception& ex) {
|
||||
Base::Console().error("%s\n", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void TaskPolarPatternParameters::apply()
|
||||
{
|
||||
std::vector<std::string> axes;
|
||||
App::DocumentObject* obj = nullptr;
|
||||
getAxis(obj, axes);
|
||||
std::string axis = buildLinkSingleSubPythonStr(obj, axes);
|
||||
|
||||
auto tobj = getObject();
|
||||
FCMD_OBJ_CMD(tobj, "Axis = " << axis.c_str());
|
||||
FCMD_OBJ_CMD(tobj, "Reversed = " << getReverse());
|
||||
FCMD_OBJ_CMD(tobj, "Mode = " << getMode());
|
||||
ui->polarAngle->apply();
|
||||
ui->angleOffset->apply();
|
||||
ui->spinOccurrences->apply();
|
||||
}
|
||||
|
||||
//**************************************************************************
|
||||
//**************************************************************************
|
||||
// TaskDialog
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
TaskDlgPolarPatternParameters::TaskDlgPolarPatternParameters(
|
||||
ViewProviderPolarPattern* PolarPatternView)
|
||||
: TaskDlgTransformedParameters(PolarPatternView)
|
||||
{
|
||||
parameter = new TaskPolarPatternParameters(PolarPatternView);
|
||||
|
||||
Content.push_back(parameter);
|
||||
Content.push_back(preview);
|
||||
}
|
||||
|
||||
#include "moc_TaskPolarPatternParameters.cpp"
|
||||
@@ -1,111 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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 *
|
||||
* *
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef GUI_TASKVIEW_TaskPolarPatternParameters_H
|
||||
#define GUI_TASKVIEW_TaskPolarPatternParameters_H
|
||||
|
||||
#include "TaskTransformedParameters.h"
|
||||
#include "ViewProviderPolarPattern.h"
|
||||
|
||||
|
||||
class QTimer;
|
||||
class Ui_TaskPolarPatternParameters;
|
||||
|
||||
namespace App
|
||||
{
|
||||
class Property;
|
||||
}
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class ViewProvider;
|
||||
}
|
||||
|
||||
namespace PartDesignGui
|
||||
{
|
||||
|
||||
class TaskMultiTransformParameters;
|
||||
|
||||
class TaskPolarPatternParameters: public TaskTransformedParameters
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// Constructor for task with ViewProvider
|
||||
explicit TaskPolarPatternParameters(ViewProviderTransformed* TransformedView,
|
||||
QWidget* parent = nullptr);
|
||||
/// Constructor for task with parent task (MultiTransform mode)
|
||||
TaskPolarPatternParameters(TaskMultiTransformParameters* parentTask, QWidget* parameterWidget);
|
||||
~TaskPolarPatternParameters() override;
|
||||
|
||||
void apply() override;
|
||||
|
||||
protected:
|
||||
void onSelectionChanged(const Gui::SelectionChanges& msg) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onUpdateViewTimer();
|
||||
void onAxisChanged(int num);
|
||||
void onModeChanged(int mode);
|
||||
void onCheckReverse(bool on);
|
||||
void onAngle(double angle);
|
||||
void onOffset(double offset);
|
||||
void onOccurrences(uint number);
|
||||
void onUpdateView(bool /*unused*/) override;
|
||||
|
||||
private:
|
||||
void setupParameterUI(QWidget* widget) override;
|
||||
void retranslateParameterUI(QWidget* widget) override;
|
||||
|
||||
void connectSignals();
|
||||
void updateUI();
|
||||
void kickUpdateViewTimer() const;
|
||||
void adaptVisibilityToMode();
|
||||
|
||||
void getAxis(App::DocumentObject*& obj, std::vector<std::string>& sub) const;
|
||||
const std::string getStdAxis() const;
|
||||
const std::string getAxis() const;
|
||||
bool getReverse() const;
|
||||
int getMode() const;
|
||||
double getAngle() const;
|
||||
unsigned getOccurrences() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui_TaskPolarPatternParameters> ui;
|
||||
QTimer* updateViewTimer = nullptr;
|
||||
|
||||
ComboLinks axesLinks;
|
||||
};
|
||||
|
||||
|
||||
/// simulation dialog for the TaskView
|
||||
class TaskDlgPolarPatternParameters: public TaskDlgTransformedParameters
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TaskDlgPolarPatternParameters(ViewProviderPolarPattern* PolarPatternView);
|
||||
};
|
||||
|
||||
} // namespace PartDesignGui
|
||||
|
||||
#endif // GUI_TASKVIEW_TASKAPPERANCE_H
|
||||
@@ -1,200 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PartDesignGui::TaskPolarPatternParameters</class>
|
||||
<widget class="QWidget" name="PartDesignGui::TaskPolarPatternParameters">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>253</width>
|
||||
<height>206</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelAxis">
|
||||
<property name="text">
|
||||
<string>Axis</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboAxis"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkReverse">
|
||||
<property name="text">
|
||||
<string>Reverse direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Overall angle</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Offset angle</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="polarAngleWrapper" native="true">
|
||||
<layout class="QHBoxLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="polarAngle" native="true">
|
||||
<property name="keyboardTracking" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">deg</string>
|
||||
</property>
|
||||
<property name="minimum" stdset="0">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum" stdset="0">
|
||||
<double>360.000000000000000</double>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>360.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="angleOffsetWrapper" native="true">
|
||||
<layout class="QHBoxLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label2">
|
||||
<property name="text">
|
||||
<string>Offset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::QuantitySpinBox" name="angleOffset" native="true">
|
||||
<property name="keyboardTracking" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string notr="true">deg</string>
|
||||
</property>
|
||||
<property name="minimum" stdset="0">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum" stdset="0">
|
||||
<double>360.000000000000000</double>
|
||||
</property>
|
||||
<property name="value" stdset="0">
|
||||
<double>360.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label3">
|
||||
<property name="text">
|
||||
<string>Occurrences</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::UIntSpinBox" name="spinOccurrences"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::QuantitySpinBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Gui/QuantitySpinBox.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::UIntSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>Gui/SpinBox.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>comboAxis</tabstop>
|
||||
<tabstop>checkReverse</tabstop>
|
||||
<tabstop>polarAngle</tabstop>
|
||||
<tabstop>spinOccurrences</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -125,8 +125,8 @@ void TaskTransformedParameters::setupUI()
|
||||
|
||||
using Mode = PartDesign::Transformed::Mode;
|
||||
|
||||
ui->buttonGroupMode->setId(ui->radioTransformBody, static_cast<int>(Mode::TransformBody));
|
||||
ui->buttonGroupMode->setId(ui->radioTransformToolShapes, static_cast<int>(Mode::TransformToolShapes));
|
||||
ui->buttonGroupMode->setId(ui->radioTransformBody, static_cast<int>(Mode::WholeShape));
|
||||
ui->buttonGroupMode->setId(ui->radioTransformToolShapes, static_cast<int>(Mode::Features));
|
||||
|
||||
connect(ui->buttonGroupMode,
|
||||
&QButtonGroup::idClicked,
|
||||
@@ -134,12 +134,12 @@ void TaskTransformedParameters::setupUI()
|
||||
&TaskTransformedParameters::onModeChanged);
|
||||
|
||||
auto const mode = static_cast<Mode>(pcTransformed->TransformMode.getValue());
|
||||
ui->groupFeatureList->setEnabled(mode == Mode::TransformToolShapes);
|
||||
ui->groupFeatureList->setEnabled(mode == Mode::Features);
|
||||
switch (mode) {
|
||||
case Mode::TransformBody:
|
||||
case Mode::WholeShape:
|
||||
ui->radioTransformBody->setChecked(true);
|
||||
break;
|
||||
case Mode::TransformToolShapes:
|
||||
case Mode::Features:
|
||||
ui->radioTransformToolShapes->setChecked(true);
|
||||
break;
|
||||
}
|
||||
@@ -307,8 +307,8 @@ void TaskTransformedParameters::onModeChanged(int mode_id)
|
||||
using Mode = PartDesign::Transformed::Mode;
|
||||
Mode const mode = static_cast<Mode>(mode_id);
|
||||
|
||||
ui->groupFeatureList->setEnabled(mode == Mode::TransformToolShapes);
|
||||
if (mode == Mode::TransformBody) {
|
||||
ui->groupFeatureList->setEnabled(mode == Mode::Features);
|
||||
if (mode == Mode::WholeShape) {
|
||||
ui->listWidgetFeatures->clear();
|
||||
}
|
||||
setupTransaction();
|
||||
@@ -393,15 +393,15 @@ void TaskTransformedParameters::removeItemFromListWidget(QListWidget* widget,
|
||||
}
|
||||
}
|
||||
|
||||
void TaskTransformedParameters::fillAxisCombo(ComboLinks& combolinks, Part::Part2DObject* sketch)
|
||||
void TaskTransformedParameters::fillAxisCombo(Gui::ComboLinks& combolinks, Part::Part2DObject* sketch)
|
||||
{
|
||||
combolinks.clear();
|
||||
|
||||
// add sketch axes
|
||||
if (sketch) {
|
||||
combolinks.addLink(sketch, "N_Axis", tr("Normal sketch axis"));
|
||||
combolinks.addLink(sketch, "V_Axis", tr("Vertical sketch axis"));
|
||||
combolinks.addLink(sketch, "H_Axis", tr("Horizontal sketch axis"));
|
||||
combolinks.addLink(sketch, "V_Axis", tr("Vertical sketch axis"));
|
||||
combolinks.addLink(sketch, "N_Axis", tr("Normal sketch axis"));
|
||||
for (int i = 0; i < sketch->getAxisCount(); i++) {
|
||||
QString itemText = tr("Construction line %1").arg(i + 1);
|
||||
std::stringstream sub;
|
||||
@@ -430,7 +430,8 @@ void TaskTransformedParameters::fillAxisCombo(ComboLinks& combolinks, Part::Part
|
||||
combolinks.addLink(nullptr, std::string(), tr("Select reference…"));
|
||||
}
|
||||
|
||||
void TaskTransformedParameters::fillPlanesCombo(ComboLinks& combolinks, Part::Part2DObject* sketch)
|
||||
void TaskTransformedParameters::fillPlanesCombo(Gui::ComboLinks& combolinks,
|
||||
Part::Part2DObject* sketch)
|
||||
{
|
||||
combolinks.clear();
|
||||
|
||||
@@ -615,8 +616,6 @@ void TaskTransformedParameters::indexesMoved()
|
||||
TaskDlgTransformedParameters::TaskDlgTransformedParameters(ViewProviderTransformed* viewProvider)
|
||||
: TaskDlgFeatureParameters(viewProvider)
|
||||
{
|
||||
message = new TaskTransformedMessages(viewProvider);
|
||||
Content.push_back(message);
|
||||
}
|
||||
|
||||
//==== calls from the TaskView ===============================================================
|
||||
@@ -638,84 +637,3 @@ bool TaskDlgTransformedParameters::reject()
|
||||
|
||||
|
||||
#include "moc_TaskTransformedParameters.cpp"
|
||||
|
||||
|
||||
ComboLinks::ComboLinks(QComboBox& combo)
|
||||
: _combo(&combo)
|
||||
{
|
||||
_combo->clear();
|
||||
}
|
||||
|
||||
int ComboLinks::addLink(const App::PropertyLinkSub& lnk, QString const& itemText)
|
||||
{
|
||||
if (!_combo) {
|
||||
return 0;
|
||||
}
|
||||
_combo->addItem(itemText);
|
||||
this->linksInList.push_back(new App::PropertyLinkSub());
|
||||
App::PropertyLinkSub& newitem = *(linksInList[linksInList.size() - 1]);
|
||||
newitem.Paste(lnk);
|
||||
if (newitem.getValue() && !this->doc) {
|
||||
this->doc = newitem.getValue()->getDocument();
|
||||
}
|
||||
return linksInList.size() - 1;
|
||||
}
|
||||
|
||||
int ComboLinks::addLink(App::DocumentObject* linkObj,
|
||||
std::string const& linkSubname,
|
||||
QString const& itemText)
|
||||
{
|
||||
if (!_combo) {
|
||||
return 0;
|
||||
}
|
||||
_combo->addItem(itemText);
|
||||
this->linksInList.push_back(new App::PropertyLinkSub());
|
||||
App::PropertyLinkSub& newitem = *(linksInList[linksInList.size() - 1]);
|
||||
newitem.setValue(linkObj, std::vector<std::string>(1, linkSubname));
|
||||
if (newitem.getValue() && !this->doc) {
|
||||
this->doc = newitem.getValue()->getDocument();
|
||||
}
|
||||
return linksInList.size() - 1;
|
||||
}
|
||||
|
||||
void ComboLinks::clear()
|
||||
{
|
||||
for (size_t i = 0; i < this->linksInList.size(); i++) {
|
||||
delete linksInList[i];
|
||||
}
|
||||
if (this->_combo) {
|
||||
_combo->clear();
|
||||
}
|
||||
}
|
||||
|
||||
App::PropertyLinkSub& ComboLinks::getLink(int index) const
|
||||
{
|
||||
if (index < 0 || index > static_cast<int>(linksInList.size()) - 1) {
|
||||
throw Base::IndexError("ComboLinks::getLink:Index out of range");
|
||||
}
|
||||
if (linksInList[index]->getValue() && doc && !(doc->isIn(linksInList[index]->getValue()))) {
|
||||
throw Base::ValueError("Linked object is not in the document; it may have been deleted");
|
||||
}
|
||||
return *(linksInList[index]);
|
||||
}
|
||||
|
||||
App::PropertyLinkSub& ComboLinks::getCurrentLink() const
|
||||
{
|
||||
assert(_combo);
|
||||
return getLink(_combo->currentIndex());
|
||||
}
|
||||
|
||||
int ComboLinks::setCurrentLink(const App::PropertyLinkSub& lnk)
|
||||
{
|
||||
for (size_t i = 0; i < linksInList.size(); i++) {
|
||||
App::PropertyLinkSub& it = *(linksInList[i]);
|
||||
if (lnk.getValue() == it.getValue() && lnk.getSubValues() == it.getSubValues()) {
|
||||
bool wasBlocked = _combo->signalsBlocked();
|
||||
_combo->blockSignals(true);
|
||||
_combo->setCurrentIndex(i);
|
||||
_combo->blockSignals(wasBlocked);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
#ifndef GUI_TASKVIEW_TaskTransformedParameters_H
|
||||
#define GUI_TASKVIEW_TaskTransformedParameters_H
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include <Gui/ComboLinks.h>
|
||||
#include <Gui/DocumentObserver.h>
|
||||
#include <Gui/Selection/Selection.h>
|
||||
#include <Gui/TaskView/TaskView.h>
|
||||
@@ -55,78 +54,6 @@ namespace PartDesignGui
|
||||
|
||||
class TaskMultiTransformParameters;
|
||||
|
||||
/**
|
||||
* @brief The ComboLinks class is a helper class that binds to a combo box and
|
||||
* provides an interface to add links, retrieve links and select items by link
|
||||
* value
|
||||
*/
|
||||
class ComboLinks
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief ComboLinks constructor.
|
||||
* @param combo. It will be cleared as soon as it is bound. Don't add or
|
||||
* remove items from the combo directly, otherwise internal tracking list
|
||||
* will go out of sync, and crashes may result.
|
||||
*/
|
||||
explicit ComboLinks(QComboBox& combo);
|
||||
ComboLinks() = default;
|
||||
|
||||
void setCombo(QComboBox& combo)
|
||||
{
|
||||
assert(!_combo);
|
||||
this->_combo = &combo;
|
||||
_combo->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief addLink adds an item to the combo. Doesn't check for duplicates.
|
||||
* @param lnk can be a link to NULL, which is usually used for special item "Select Reference"
|
||||
* @param itemText
|
||||
* @return
|
||||
*/
|
||||
int addLink(const App::PropertyLinkSub& lnk, QString const& itemText);
|
||||
int addLink(App::DocumentObject* linkObj, std::string const& linkSubname, QString const& itemText);
|
||||
void clear();
|
||||
App::PropertyLinkSub& getLink(int index) const;
|
||||
|
||||
/**
|
||||
* @brief getCurrentLink
|
||||
* @return the link corresponding to the selected item. May be null link,
|
||||
* which is usually used to indicate a "Select reference..." special item.
|
||||
* Otherwise, the link is automatically tested for validity (oif an object
|
||||
* doesn't exist in the document, an exception will be thrown.)
|
||||
*/
|
||||
App::PropertyLinkSub& getCurrentLink() const;
|
||||
|
||||
/**
|
||||
* @brief setCurrentLink selects the item with the link that matches the
|
||||
* argument. If there is no such link in the list, -1 is returned and
|
||||
* selected item is not changed. Signals from combo are blocked in this
|
||||
* function.
|
||||
* @param lnk
|
||||
* @return the index of an item that was selected, -1 if link is not in the list yet.
|
||||
*/
|
||||
int setCurrentLink(const App::PropertyLinkSub& lnk);
|
||||
|
||||
QComboBox& combo() const
|
||||
{
|
||||
assert(_combo);
|
||||
return *_combo;
|
||||
}
|
||||
|
||||
~ComboLinks()
|
||||
{
|
||||
_combo = nullptr;
|
||||
clear();
|
||||
}
|
||||
|
||||
private:
|
||||
QComboBox* _combo = nullptr;
|
||||
App::Document* doc = nullptr;
|
||||
std::vector<App::PropertyLinkSub*> linksInList;
|
||||
};
|
||||
|
||||
/**
|
||||
The transformed subclasses will be used in two different modes:
|
||||
1. As a stand-alone feature
|
||||
@@ -212,9 +139,9 @@ protected:
|
||||
void onSelectionChanged(const Gui::SelectionChanges& msg) override;
|
||||
|
||||
/// Fill combobox with the axis from the sketch and the own bodys origin axis
|
||||
void fillAxisCombo(ComboLinks& combolinks, Part::Part2DObject* sketch);
|
||||
void fillAxisCombo(Gui::ComboLinks& combolinks, Part::Part2DObject* sketch);
|
||||
/// Fill combobox with the planes from the sketch and the own bodys origin planes
|
||||
void fillPlanesCombo(ComboLinks& combolinks, Part::Part2DObject* sketch);
|
||||
void fillPlanesCombo(Gui::ComboLinks& combolinks, Part::Part2DObject* sketch);
|
||||
|
||||
/**
|
||||
* Returns the base transformed objectfromStdString
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include "ViewProviderLinearPattern.h"
|
||||
#include "TaskLinearPatternParameters.h"
|
||||
#include "TaskPatternParameters.h"
|
||||
|
||||
using namespace PartDesignGui;
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include "ViewProviderPolarPattern.h"
|
||||
#include "TaskPolarPatternParameters.h"
|
||||
#include "TaskPatternParameters.h"
|
||||
|
||||
using namespace PartDesignGui;
|
||||
|
||||
PROPERTY_SOURCE(PartDesignGui::ViewProviderPolarPattern,PartDesignGui::ViewProviderTransformed)
|
||||
|
||||
TaskDlgFeatureParameters *ViewProviderPolarPattern::getEditDialog() {
|
||||
return new TaskDlgPolarPatternParameters (this);
|
||||
return new TaskDlgLinearPatternParameters(this);
|
||||
}
|
||||
|
||||
void ViewProviderPolarPattern::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
|
||||
|
||||
@@ -144,27 +144,21 @@ void ViewProviderTransformed::handleTransformedResult(PartDesign::Transformed* p
|
||||
rejected++;
|
||||
}
|
||||
|
||||
QString msg = QStringLiteral("%1");
|
||||
if (rejected > 0) {
|
||||
msg = QStringLiteral("<font color='orange'>%1<br/></font>\r\n%2");
|
||||
if (rejected == 1)
|
||||
msg = msg.arg(QObject::tr("One transformed shape does not intersect the support"));
|
||||
if (rejected == 1) {
|
||||
Base::Console().translatedUserWarning("ViewProviderTransformed",
|
||||
"One transformed shape does not intersect the support");
|
||||
}
|
||||
else {
|
||||
msg = msg.arg(QObject::tr("%1 transformed shapes do not intersect the support"));
|
||||
msg = msg.arg(rejected);
|
||||
Base::Console().translatedUserWarning("ViewProviderTransformed",
|
||||
"%d transformed shapes do not intersect the support", rejected);
|
||||
}
|
||||
}
|
||||
|
||||
if (const char* error = pcTransformed->getDocument()->getErrorDescription(pcTransformed)) {
|
||||
msg = msg.arg(QStringLiteral("<font color='red'>%1<br/></font>"));
|
||||
msg = msg.arg(QString::fromUtf8(error));
|
||||
} else {
|
||||
msg = msg.arg(QStringLiteral("<font color='green'>%1<br/></font>"));
|
||||
msg = msg.arg(QObject::tr("Transformation succeeded"));
|
||||
auto error = pcTransformed->getDocument()->getErrorDescription(pcTransformed);
|
||||
if (error) {
|
||||
Base::Console().translatedUserError("ViewProviderTransformed", error);
|
||||
}
|
||||
|
||||
diagMessage = msg;
|
||||
signalDiagnosis(msg);
|
||||
}
|
||||
|
||||
void ViewProviderTransformed::recomputeFeature(bool recompute)
|
||||
|
||||
Reference in New Issue
Block a user