PartDesign: Transform rework

This commit is contained in:
PaddleStroke
2025-08-10 16:06:01 +02:00
committed by paddle
parent 5fa7fe5117
commit 5d2037c820
25 changed files with 1097 additions and 1817 deletions

View File

@@ -1,6 +1,6 @@
// 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. *
@@ -36,35 +36,34 @@
namespace Gui {
ComboLinks::ComboLinks(QComboBox& combo)
: _combo(&combo)
ComboLinks::ComboLinks(QComboBox* combo)
: combo(combo)
{
if (_combo) {
_combo->clear();
if (combo) {
combo->clear();
}
}
ComboLinks::~ComboLinks()
{
clear(); // Deletes owned pointers in linksInList
_combo = nullptr; // Don't delete, not owned
}
void ComboLinks::setCombo(QComboBox& combo)
void ComboLinks::setCombo(QComboBox* combobox)
{
clear(); // Clear old state if any
_combo = &combo;
if (_combo) {
_combo->clear();
combo = combobox;
if (combo) {
combo->clear();
}
}
int ComboLinks::addLink(const App::PropertyLinkSub& lnk, const QString& itemText, int userData)
{
if (!_combo) {
if (!combo) {
return -1;
}
int newIndex = _combo->count();
int newIndex = combo->count();
int finalUserData = (userData == -1) ? newIndex : userData;
// Store link internally (create a copy)
@@ -73,7 +72,7 @@ namespace Gui {
linksInList.push_back(newLink);
// Add to combo box
_combo->addItem(itemText, QVariant(finalUserData));
combo->addItem(itemText, QVariant(finalUserData));
// Track document context from the first valid object link
if (!doc && newLink->getValue()) {
@@ -96,13 +95,13 @@ namespace Gui {
int ComboLinks::addLinkBefore(const App::PropertyLinkSub& lnk, const QString& itemText, int targetUserData, int userData)
{
if (!_combo) {
if (!combo) {
return -1;
}
int insertPos = -1;
for (int i = 0; i < _combo->count(); ++i) {
if (_combo->itemData(i).toInt() == targetUserData) {
for (int i = 0; i < combo->count(); ++i) {
if (combo->itemData(i).toInt() == targetUserData) {
insertPos = i;
break;
}
@@ -116,20 +115,20 @@ namespace Gui {
if (insertPos != -1) {
linksInList.insert(linksInList.begin() + insertPos, newLink);
_combo->insertItem(insertPos, itemText, QVariant(finalUserData));
combo->insertItem(insertPos, itemText, QVariant(finalUserData));
}
else {
// Target not found, append to end
insertPos = _combo->count();
insertPos = combo->count();
linksInList.push_back(newLink);
_combo->addItem(itemText, QVariant(finalUserData));
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));
if (combo->itemData(i).toInt() == i - 1) { // Check if it was using default index
combo->setItemData(i, QVariant(i));
}
}
}
@@ -145,12 +144,10 @@ namespace Gui {
void ComboLinks::clear()
{
if (_combo) {
if (combo) {
// Block signals while clearing to prevent issues if connected elsewhere
bool wasBlocked = _combo->signalsBlocked();
_combo->blockSignals(true);
_combo->clear();
_combo->blockSignals(wasBlocked);
QSignalBlocker blocker(combo);
combo->clear();
}
for (App::PropertyLinkSub* linkPtr : linksInList) {
delete linkPtr; // Delete the objects pointed to
@@ -174,41 +171,39 @@ namespace Gui {
App::PropertyLinkSub& ComboLinks::getCurrentLink() const
{
assert(_combo);
return getLink(_combo->currentIndex());
assert(combo);
return getLink(combo->currentIndex());
}
int ComboLinks::getUserData(int index) const
{
if (!_combo || index < 0 || index >= _combo->count()) {
if (!combo || index < 0 || index >= combo->count()) {
return -1; // Indicate invalid index or no combo
}
return _combo->itemData(index).toInt();
return combo->itemData(index).toInt();
}
int ComboLinks::getCurrentUserData() const
{
if (!_combo) {
if (!combo) {
return -1;
}
return _combo->currentData().toInt();
return combo->currentData().toInt();
}
int ComboLinks::setCurrentLink(const App::PropertyLinkSub& lnk)
{
if (!_combo) {
if (!combo) {
return -1;
}
for (size_t i = 0; i < linksInList.size(); ++i) {
const App::PropertyLinkSub& it = *(linksInList[i]);
const App::PropertyLinkSub* it = linksInList[i];
// Compare object pointer and sub-values vector
if (lnk.getValue() == it.getValue() && lnk.getSubValues() == it.getSubValues()) {
bool wasBlocked = _combo->signalsBlocked();
_combo->blockSignals(true);
_combo->setCurrentIndex(static_cast<int>(i));
_combo->blockSignals(wasBlocked);
if (lnk.getValue() == it->getValue() && lnk.getSubValues() == it->getSubValues()) {
QSignalBlocker blocker(combo);
combo->setCurrentIndex(static_cast<int>(i));
return static_cast<int>(i);
}
}
@@ -217,7 +212,7 @@ namespace Gui {
int ComboLinks::count() const
{
return _combo ? _combo->count() : 0;
return combo ? combo->count() : 0;
}
} // namespace Gui

View File

@@ -27,8 +27,8 @@
#include <vector>
#include <string>
#include <App/PropertyLinks.h> // For PropertyLinkSub
#include <Base/Exception.h> // For exceptions
#include <App/PropertyLinks.h>
#include <Base/Exception.h>
// Forward declarations
class QComboBox;
@@ -53,7 +53,7 @@ namespace Gui {
* @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);
explicit ComboLinks(QComboBox* combo);
/**
* @brief Default constructor. Use setCombo() later.
@@ -65,17 +65,13 @@ namespace Gui {
*/
~ComboLinks();
// Disable copy/move semantics for simplicity with pointer management
ComboLinks(const ComboLinks&) = delete;
ComboLinks& operator=(const ComboLinks&) = delete;
ComboLinks(ComboLinks&&) = delete;
ComboLinks& operator=(ComboLinks&&) = delete;
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);
void setCombo(QComboBox* combo);
/**
* @brief Adds an item to the combo box associated with a PropertyLinkSub.
@@ -162,7 +158,7 @@ namespace Gui {
private:
QComboBox* _combo = nullptr;
QComboBox* combo = nullptr;
App::Document* doc = nullptr; // Document context for validation
std::vector<App::PropertyLinkSub*> linksInList; // Owned pointers
};

View File

@@ -29,13 +29,17 @@
#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>
@@ -54,27 +58,27 @@ PatternParametersWidget::PatternParametersWidget(PatternType type, QWidget* pare
connectSignals();
}
PatternParametersWidget::~PatternParametersWidget()
{
// ui is managed by unique_ptr
// Bound properties are managed externally
// Dynamic spacing rows are deleted by clearDynamicSpacingRows or Qt's parent mechanism
}
PatternParametersWidget::~PatternParametersWidget() = default;
void PatternParametersWidget::setupUiElements()
{
// Configure UI elements if needed (e.g., icons for mode)
QIcon icon1 = Gui::BitmapFactory().iconFromTheme("Part_LinearPattern_extent");
QIcon icon2 = Gui::BitmapFactory().iconFromTheme("Part_LinearPattern_spacing");
ui->comboMode->setItemIcon(0, icon1);
ui->comboMode->setItemIcon(1, icon2);
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));
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()
@@ -100,23 +104,23 @@ void PatternParametersWidget::connectSignals()
// 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,
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_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);
@@ -152,15 +156,15 @@ void PatternParametersWidget::updateUI()
if (blockUpdate || !m_feature) { // Need properties to be bound
return;
}
blockUpdate = true;
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 = QString::fromLatin1(m_directionProp->getValue()->getNameInDocument()) + QStringLiteral(":") +
QString::fromLatin1(m_directionProp->getSubValues().front().c_str());
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);
}
@@ -175,21 +179,22 @@ void PatternParametersWidget::updateUI()
rebuildDynamicSpacingUI();
adaptVisibilityToMode();
blockUpdate = false;
}
void PatternParametersWidget::adaptVisibilityToMode()
{
if (!m_modeProp) return;
if (!m_modeProp) {
return;
}
// Use the enum names defined in FeatureLinearPattern.h
auto mode = static_cast<PartGui::PatternMode>(m_modeProp->getValue());
ui->extentWrapper->setVisible(mode == PartGui::PatternMode::Extent);
ui->spacingWrapper->setVisible(mode == PartGui::PatternMode::Spacing);
ui->spacingPatternWidget->setVisible(mode == PartGui::PatternMode::Spacing);
ui->addSpacingButton->setVisible(mode == PartGui::PatternMode::Spacing);
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
@@ -206,16 +211,17 @@ 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*/)
@@ -285,68 +291,77 @@ void PatternParametersWidget::onOccurrencesChanged(unsigned int value)
}
// --- Dynamic Spacing Logic (Copied and adapted) ---
// --- Dynamic Spacing Logic ---
void PatternParametersWidget::clearDynamicSpacingRows()
{
for (Gui::QuantitySpinBox* spinBox : dynamicSpacingSpinBoxes) {
spinBox->blockSignals(true); // Block signals during deletion
for (QWidget* fieldWidget : dynamicSpacingRows) {
ui->formLayout->removeRow(fieldWidget);
}
qDeleteAll(dynamicSpacingRows);
dynamicSpacingRows.clear();
dynamicSpacingSpinBoxes.clear();
}
void PatternParametersWidget::addSpacingRow(double value)
{
if (!m_spacingProp) return; // Need context for units
if (!m_spacingProp) {
return; // Need context for units
}
QWidget* rowWidget = new QWidget(ui->spacingPatternWidget);
QHBoxLayout* rowLayout = new QHBoxLayout(rowWidget);
rowLayout->setContentsMargins(0, 0, 0, 0);
rowLayout->setSpacing(3);
// 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();
// Use qApp->translate for dynamic labels
QLabel* label = new QLabel(qApp->translate("PartGui::PatternParameters", "Spacing %1").arg(newIndex + 2), rowWidget);
QLabel* label = new QLabel(tr("Spacing %1").arg(newIndex + 2), this);
Gui::QuantitySpinBox* spinBox = new Gui::QuantitySpinBox(rowWidget);
// 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
spinBox->setValue(value); // Set initial value
QToolButton* removeButton = new QToolButton(rowWidget);
QToolButton* removeButton = new QToolButton(fieldWidget);
removeButton->setIcon(Gui::BitmapFactory().iconFromTheme("list-remove"));
removeButton->setToolTip(qApp->translate("PartGui::PatternParameters", "Remove this spacing definition."));
removeButton->setToolTip(tr("Remove this spacing definition."));
removeButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
rowLayout->addWidget(label);
rowLayout->addWidget(spinBox);
rowLayout->addWidget(removeButton);
// No need to explicitly call rowWidget->setLayout(rowLayout); QHBoxLayout constructor handles it
fieldLayout->addWidget(spinBox);
fieldLayout->addWidget(removeButton);
ui->spacingPatternLayout->addWidget(rowWidget);
dynamicSpacingRows.append(rowWidget);
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, rowWidget]() {
this->onRemoveSpacingButtonClicked(rowWidget);
connect(spinBox,
qOverload<double>(&Gui::QuantitySpinBox::valueChanged),
this,
&PatternParametersWidget::onDynamicSpacingChanged);
connect(removeButton, &QToolButton::clicked, this, [this, fieldWidget]() {
this->onRemoveSpacingButtonClicked(fieldWidget);
});
// rowWidget->show(); // Usually not needed when added to a visible layout
}
void PatternParametersWidget::rebuildDynamicSpacingUI()
{
if (!m_spacingPatternProp) return;
if (!m_spacingPatternProp) {
return;
}
clearDynamicSpacingRows(); // Clear existing dynamic UI first
std::vector<double> currentSpacings = m_spacingPatternProp->getValues();
clearDynamicSpacingRows(); // Clear existing dynamic UI first
// 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.
@@ -372,32 +387,33 @@ void PatternParametersWidget::onDynamicSpacingChanged()
updateSpacingPatternProperty(); // This will emit parametersChanged
}
void PatternParametersWidget::onRemoveSpacingButtonClicked(QWidget* rowWidget)
void PatternParametersWidget::onRemoveSpacingButtonClicked(QWidget* fieldWidget)
{
if (blockUpdate || !m_spacingPatternProp) return;
if (blockUpdate || !m_spacingPatternProp) {
return;
}
int indexToRemove = dynamicSpacingRows.indexOf(rowWidget);
if (indexToRemove == -1) return;
int indexToRemove = dynamicSpacingRows.indexOf(fieldWidget);
if (indexToRemove == -1) {
return;
}
// removeRow also deletes the widgets (label and field)
ui->formLayout->removeRow(fieldWidget);
// Remove from UI lists
dynamicSpacingRows.removeAt(indexToRemove);
dynamicSpacingSpinBoxes.removeAt(indexToRemove); // Just remove, widget deleted below
// Remove from layout and delete
ui->spacingPatternLayout->removeWidget(rowWidget);
delete rowWidget; // Deletes children too
dynamicSpacingSpinBoxes.removeAt(indexToRemove);
// Update labels of subsequent rows
for (int i = indexToRemove; i < dynamicSpacingRows.count(); ++i) {
QWidget* row = dynamicSpacingRows.at(i);
QLabel* label = row->findChild<QLabel*>();
if (label) {
label->setText(qApp->translate("PartGui::PatternParameters", "Spacing %1").arg(i + 2));
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
updateSpacingPatternProperty(); // This will emit parametersChanged
}
void PatternParametersWidget::updateSpacingPatternProperty()

View File

@@ -90,13 +90,13 @@ namespace PartGui {
* @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,
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
@@ -201,9 +201,6 @@ namespace PartGui {
bool blockUpdate = false; // Prevents signal loops
// Helpers for direction combo box
static const int SELECT_REFERENCE_INDEX = -10; // Special index for "Select reference..."
// Store pointers to dynamically created widgets for removal and access
QList<QWidget*> dynamicSpacingRows;
QList<Gui::QuantitySpinBox*> dynamicSpacingSpinBoxes;

View File

@@ -28,7 +28,7 @@
<property name="title">
<string>Direction</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="mainLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
@@ -54,15 +54,15 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_Mode">
<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>
<item row="0" column="1">
<widget class="QComboBox" name="comboMode">
<item>
<property name="text">
@@ -76,125 +76,85 @@
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="extentWrapper" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_Length">
<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="spinExtent" native="true">
<property name="keyboardTracking" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="spacingWrapper" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_Offset">
<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>Spacing</string>
</property>
</widget>
</item>
<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>
<widget class="QWidget" name="spacingPatternWidget" native="true">
<layout class="QVBoxLayout" name="spacingPatternLayout">
<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>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_Occurrences">
<item>
<widget class="QLabel" name="labelOccurrences">
<item row="1" column="0">
<widget class="QLabel" name="labelLength">
<property name="text">
<string>Occurrences</string>
<string>Length</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::UIntSpinBox" name="spinOccurrences"/>
<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>
@@ -230,4 +190,4 @@
</customwidgets>
<resources/>
<connections/>
</ui>
</ui>

View File

@@ -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,35 @@ 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::PropertyType)(App::Prop_None),"Direction");
ADD_PROPERTY_TYPE(Reversed,(0), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Mode, (long(initialMode)), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Length,(100.0), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Offset,(10.0), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Spacings, ({ -1.0 }), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(SpacingPattern, ({}), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Occurrences,(2), "Direction 1", (App::PropertyType)(App::Prop_None), "Direction");
Occurrences.setConstraints(&intOccurrences);
Mode.setEnums(ModeEnums);
setReadWriteStatusForMode(initialMode);
setReadWriteStatusForMode(LinearPatternDirection::First);
ADD_PROPERTY_TYPE(Direction2,(nullptr),"Direction 2",(App::PropertyType)(App::Prop_None),"Direction");
ADD_PROPERTY_TYPE(Reversed2,(0), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Mode2, (long(initialMode)), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Length2,(100.0), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Offset2,(10.0), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Spacings2, ({}), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(SpacingPattern2, ({}), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
ADD_PROPERTY_TYPE(Occurrences2,(1), "Direction 2", (App::PropertyType)(App::Prop_None), "Direction");
Occurrences2.setConstraints(&intOccurrences);
Mode2.setEnums(ModeEnums);
setReadWriteStatusForMode(LinearPatternDirection::Second);
}
short LinearPattern::mustExecute() const
@@ -78,42 +93,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 +271,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 +296,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 +325,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 +335,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 +407,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);
}
}
}

View File

@@ -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

View File

@@ -53,11 +53,11 @@ 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));
@@ -67,6 +67,8 @@ PolarPattern::PolarPattern()
ADD_PROPERTY(Offset, (120.0));
Angle.setConstraints(&floatAngle);
Offset.setConstraints(&floatAngle);
ADD_PROPERTY(Spacings, ({ -1.0, -1.0, -1.0 }));
ADD_PROPERTY(SpacingPattern, ({}));
ADD_PROPERTY(Occurrences, (3));
Occurrences.setConstraints(&intOccurrences);
@@ -80,7 +82,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 +177,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 +259,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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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"

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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);

View 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"

View File

@@ -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;
Gui::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

View 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>

View File

@@ -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"

View File

@@ -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;
Gui::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

View File

@@ -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>

View File

@@ -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();

View File

@@ -139,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

View File

@@ -24,7 +24,7 @@
#include "PreCompiled.h"
#include "ViewProviderLinearPattern.h"
#include "TaskLinearPatternParameters.h"
#include "TaskPatternParameters.h"
using namespace PartDesignGui;

View File

@@ -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)