From 6d7fea506c67e66f574ed76ae4b0260199253a0a Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sat, 26 Aug 2023 16:00:03 +0200 Subject: [PATCH] PD: Add offset / overall angle modes for PolarPatterns This commit adds two separate modes for defining angular spacing between elements in the PD's Polar Pattern: 1. Overall Angle - which behaves exactly like it behaved before, 2. Offset Angle - which allows user to specify separation angle between consecutive elements. This change is analogue to that introduced for LinearPattern in previous commits. --- src/Mod/PartDesign/App/FeatureLinearPattern.h | 3 +- .../PartDesign/App/FeaturePolarPattern.cpp | 68 ++++++++-- src/Mod/PartDesign/App/FeaturePolarPattern.h | 36 +++++- .../Gui/TaskPolarPatternParameters.cpp | 49 ++++++- .../Gui/TaskPolarPatternParameters.h | 3 + .../Gui/TaskPolarPatternParameters.ui | 121 +++++++++++++++--- 6 files changed, 239 insertions(+), 41 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureLinearPattern.h b/src/Mod/PartDesign/App/FeatureLinearPattern.h index f538335958..8876f2646a 100644 --- a/src/Mod/PartDesign/App/FeatureLinearPattern.h +++ b/src/Mod/PartDesign/App/FeatureLinearPattern.h @@ -78,9 +78,10 @@ public: protected: void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override; - static const App::PropertyIntegerConstraint::Constraints intOccurrences; void onChanged(const App::Property* prop) override; + static const App::PropertyIntegerConstraint::Constraints intOccurrences; + private: static const char* ModeEnums[]; diff --git a/src/Mod/PartDesign/App/FeaturePolarPattern.cpp b/src/Mod/PartDesign/App/FeaturePolarPattern.cpp index 66f5ad4dfb..c8436fc896 100644 --- a/src/Mod/PartDesign/App/FeaturePolarPattern.cpp +++ b/src/Mod/PartDesign/App/FeaturePolarPattern.cpp @@ -51,21 +51,34 @@ PROPERTY_SOURCE(PartDesign::PolarPattern, PartDesign::Transformed) const App::PropertyIntegerConstraint::Constraints PolarPattern::intOccurrences = { 1, INT_MAX, 1 }; const App::PropertyAngle::Constraints PolarPattern::floatAngle = { Base::toDegrees(Precision::Angular()), 360.0, 1.0 }; +const char* PolarPattern::ModeEnums[] = {"angle", "offset", nullptr}; + PolarPattern::PolarPattern() { + auto initialMode = PolarPatternMode::angle; + ADD_PROPERTY_TYPE(Axis, (nullptr), "PolarPattern", (App::PropertyType)(App::Prop_None), "Direction"); ADD_PROPERTY(Reversed, (0)); + ADD_PROPERTY(Mode, (long(initialMode))); + Mode.setEnums(PolarPattern::ModeEnums); ADD_PROPERTY(Angle, (360.0)); + ADD_PROPERTY(Offset, (120.0)); Angle.setConstraints(&floatAngle); + Offset.setConstraints(&floatAngle); ADD_PROPERTY(Occurrences, (3)); Occurrences.setConstraints(&intOccurrences); + + setReadWriteStatusForMode(initialMode); } short PolarPattern::mustExecute() const { if (Axis.isTouched() || Reversed.isTouched() || - Angle.isTouched() || + Mode.isTouched() || + // Angle and Offset are mutually exclusive, only one could be updated at once + Angle.isTouched() || + Offset.isTouched() || Occurrences.isTouched()) return 1; return Transformed::mustExecute(); @@ -80,17 +93,7 @@ const std::list PolarPattern::getTransformations(const std::vector(angle); - if (radians < Precision::Angular()) - throw Base::ValueError("Pattern angle too small"); - bool reversed = Reversed.getValue(); - double offset; - if (std::fabs(angle - 360.0) < Precision::Confusion()) - offset = radians / occurrences; // Because e.g. two occurrences in 360 degrees need to be 180 degrees apart - else - offset = radians / (occurrences - 1); App::DocumentObject* refObject = Axis.getValue(); if (!refObject) @@ -166,6 +169,32 @@ const std::list PolarPattern::getTransformations(const std::vector(Mode.getValue())) { + case PolarPatternMode::angle: + 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; + + break; + + case PolarPatternMode::offset: + angle = Offset.getValue(); + break; + + default: + throw Base::ValueError("Invalid mode"); + } + + double offset = Base::toRadians(angle); + + if (offset < Precision::Angular()) + throw Base::ValueError("Pattern angle too small"); + std::list transformations; gp_Trsf trans; transformations.push_back(trans); @@ -195,4 +224,21 @@ void PolarPattern::handleChangedPropertyType(Base::XMLReader& reader, const char } } + +void PolarPattern::onChanged(const App::Property* prop) +{ + if (prop == &Mode) { + auto mode = static_cast(Mode.getValue()); + setReadWriteStatusForMode(mode); + } + + Transformed::onChanged(prop); +} + +void PolarPattern::setReadWriteStatusForMode(PolarPatternMode mode) +{ + Offset.setReadOnly(mode != PolarPatternMode::offset); + Angle.setReadOnly(mode != PolarPatternMode::angle); +} + } diff --git a/src/Mod/PartDesign/App/FeaturePolarPattern.h b/src/Mod/PartDesign/App/FeaturePolarPattern.h index 7362f311a5..f250820fec 100644 --- a/src/Mod/PartDesign/App/FeaturePolarPattern.h +++ b/src/Mod/PartDesign/App/FeaturePolarPattern.h @@ -31,6 +31,10 @@ namespace PartDesign { +enum class PolarPatternMode { + angle, + offset +}; class PartDesignExport PolarPattern : public PartDesign::Transformed { @@ -39,9 +43,11 @@ class PartDesignExport PolarPattern : public PartDesign::Transformed public: PolarPattern(); - App::PropertyLinkSub Axis; - App::PropertyBool Reversed; - App::PropertyAngle Angle; + App::PropertyLinkSub Axis; + App::PropertyBool Reversed; + App::PropertyEnumeration Mode; + App::PropertyAngle Angle; + App::PropertyAngle Offset; App::PropertyIntegerConstraint Occurrences; /** @name methods override feature */ @@ -55,21 +61,37 @@ public: //@} /** Create transformations + * * Returns a list of (Occurrences - 1) transformations since the first, untransformed instance - * is not counted. Each transformation will rotate the shape it is applied to by the angle - * (Angle / (Occurrences - 1)) so that the transformations will cover the total Angle. The only - * exception is Angle = 360 degrees in which case the transformation angle will be - * (Angle / Occurrences) so that the last transformed shape is not identical with the original shape + * is not counted. Each transformation will rotate the shape it is applied to by the supplied angle. + * + * Depending on Mode selection list will be constructed differently: + * 1. For "angle" mode each feature will be rotated by (Angle / (Occurrences - 1)) so + * that the transformations will cover the total Angle. The only exception is Angle = 360 degrees in + * which case the transformation angle will be (Angle / Occurrences) so that the last transformed shape + * is not identical with the original shape. + * 2. For "offset" mode each feature will be rotated using exact angle from Offset parameter. It can + * potentially result in transformation that extends beyond full rotation or results in overlapping shapes. + * This situations are considered as potential user errors and should be solved by user. + * * If Axis contains a feature and an edge name, then the transformation axis will be * the given edge, which must be linear. + * * If Reversed is true, the direction of rotation will be opposite. */ const std::list getTransformations(const std::vector) override; protected: void handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) override; + void onChanged(const App::Property* prop) override; + static const App::PropertyIntegerConstraint::Constraints intOccurrences; static const App::PropertyAngle::Constraints floatAngle; + +private: + static const char* ModeEnums[]; + + void setReadWriteStatusForMode(PolarPatternMode mode); }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp index f3764ed5f8..92bdb55912 100644 --- a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp @@ -131,10 +131,14 @@ void TaskPolarPatternParameters::connectSignals() this, &TaskPolarPatternParameters::onUpdateViewTimer); connect(ui->comboAxis, qOverload(&QComboBox::activated), this, &TaskPolarPatternParameters::onAxisChanged); + connect(ui->comboMode, qOverload(&QComboBox::activated), + this, &TaskPolarPatternParameters::onModeChanged); connect(ui->checkReverse, &QCheckBox::toggled, this, &TaskPolarPatternParameters::onCheckReverse); connect(ui->polarAngle, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskPolarPatternParameters::onAngle); + connect(ui->angleOffset, qOverload(&Gui::QuantitySpinBox::valueChanged), + this, &TaskPolarPatternParameters::onOffset); connect(ui->spinOccurrences, &Gui::UIntSpinBox::unsignedChanged, this, &TaskPolarPatternParameters::onOccurrences); connect(ui->checkBoxUpdateView, &QCheckBox::toggled, @@ -159,11 +163,14 @@ void TaskPolarPatternParameters::setupUI() // --------------------- 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); @@ -191,6 +198,7 @@ void TaskPolarPatternParameters::setupUI() } } + adaptVisibilityToMode(); updateUI(); connectSignals(); } @@ -203,20 +211,24 @@ void TaskPolarPatternParameters::updateUI() PartDesign::PolarPattern* pcPolarPattern = static_cast(getObject()); + PartDesign::PolarPatternMode mode = static_cast(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){ + 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: These three lines would trigger onLength(), on Occurrences() and another updateUI() if we - // didn't check for blockUpdate + // 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((long)mode); ui->polarAngle->setValue(angle); + ui->angleOffset->setValue(offset); ui->spinOccurrences->setValue(occurrences); blockUpdate = false; @@ -233,6 +245,15 @@ void TaskPolarPatternParameters::kickUpdateViewTimer() const updateViewTimer->start(); } +void TaskPolarPatternParameters::adaptVisibilityToMode() +{ + auto pcLinearPattern = static_cast(getObject()); + auto mode = static_cast(pcLinearPattern->Mode.getValue()); + + ui->polarAngleWrapper->setVisible(mode == PartDesign::PolarPatternMode::angle); + ui->angleOffsetWrapper->setVisible(mode == PartDesign::PolarPatternMode::offset); +} + void TaskPolarPatternParameters::addObject(App::DocumentObject* obj) { QString label = QString::fromUtf8(obj->Label.getValue()); @@ -292,6 +313,18 @@ void TaskPolarPatternParameters::onCheckReverse(const bool on) { kickUpdateViewTimer(); } +void TaskPolarPatternParameters::onModeChanged(const int mode) { + if (blockUpdate) + return; + PartDesign::PolarPattern* pcPolarPattern = static_cast(getObject()); + pcPolarPattern->Mode.setValue(mode); + + adaptVisibilityToMode(); + + exitSelectionMode(); + kickUpdateViewTimer(); +} + void TaskPolarPatternParameters::onAngle(const double a) { if (blockUpdate) return; @@ -302,6 +335,16 @@ void TaskPolarPatternParameters::onAngle(const double a) { kickUpdateViewTimer(); } +void TaskPolarPatternParameters::onOffset(const double a) { + if (blockUpdate) + return; + PartDesign::PolarPattern* pcPolarPattern = static_cast(getObject()); + pcPolarPattern->Offset.setValue(a); + + exitSelectionMode(); + kickUpdateViewTimer(); +} + void TaskPolarPatternParameters::onOccurrences(const uint n) { if (blockUpdate) return; diff --git a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h index 0408574993..b01121c23c 100644 --- a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.h @@ -58,8 +58,10 @@ public: private Q_SLOTS: void onUpdateViewTimer(); void onAxisChanged(int num); + void onModeChanged(const int mode); void onCheckReverse(const bool on); void onAngle(const double a); + void onOffset(const double a); void onOccurrences(const uint n); void onUpdateView(bool) override; void onFeatureDeleted() override; @@ -82,6 +84,7 @@ private: void setupUI(); void updateUI(); void kickUpdateViewTimer() const; + void adaptVisibilityToMode(); private: std::unique_ptr ui; diff --git a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.ui b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.ui index 9e26831d5a..b95832289f 100644 --- a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.ui @@ -70,35 +70,118 @@ - + - + - Angle + Mode - - - false - - - deg - - - 0.000000000000000 - - - 360.000000000000000 - - - 360.000000000000000 - + + + + Overall Angle + + + + + Offset Angle + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Angle + + + + + + + false + + + deg + + + 0.000000000000000 + + + 360.000000000000000 + + + 360.000000000000000 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Offset + + + + + + + false + + + deg + + + 0.000000000000000 + + + 360.000000000000000 + + + 360.000000000000000 + + + + + +