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.
This commit is contained in:
Kacper Donat
2023-08-26 16:00:03 +02:00
parent 2b1108439f
commit 6d7fea506c
6 changed files with 239 additions and 41 deletions

View File

@@ -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[];

View File

@@ -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<double>(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<gp_Trsf> PolarPattern::getTransformations(const std::vector<App:
if (occurrences == 1)
return {gp_Trsf()};
double angle = Angle.getValue();
double radians = Base::toRadians<double>(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<gp_Trsf> PolarPattern::getTransformations(const std::vector<App:
if (reversed)
axis.SetDirection(axis.Direction().Reversed());
double angle;
switch (static_cast<PolarPatternMode>(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<double>(angle);
if (offset < Precision::Angular())
throw Base::ValueError("Pattern angle too small");
std::list<gp_Trsf> 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<PolarPatternMode>(Mode.getValue());
setReadWriteStatusForMode(mode);
}
Transformed::onChanged(prop);
}
void PolarPattern::setReadWriteStatusForMode(PolarPatternMode mode)
{
Offset.setReadOnly(mode != PolarPatternMode::offset);
Angle.setReadOnly(mode != PolarPatternMode::angle);
}
}

View File

@@ -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<gp_Trsf> getTransformations(const std::vector<App::DocumentObject*>) 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

View File

@@ -131,10 +131,14 @@ void TaskPolarPatternParameters::connectSignals()
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);
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<PartDesign::PolarPattern*>(getObject());
PartDesign::PolarPatternMode 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){
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<PartDesign::PolarPattern*>(getObject());
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::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<PartDesign::PolarPattern*>(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<PartDesign::PolarPattern*>(getObject());
pcPolarPattern->Offset.setValue(a);
exitSelectionMode();
kickUpdateViewTimer();
}
void TaskPolarPatternParameters::onOccurrences(const uint n) {
if (blockUpdate)
return;

View File

@@ -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_TaskPolarPatternParameters> ui;

View File

@@ -70,35 +70,118 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="label">
<property name="text">
<string>Angle</string>
<string>Mode</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::QuantitySpinBox" name="polarAngle">
<property name="keyboardTracking">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">deg</string>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>360.000000000000000</double>
</property>
<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="label">
<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>