/*************************************************************************** * Copyright (c) 2011 Juergen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_TaskHoleParameters.h" #include "TaskHoleParameters.h" using namespace PartDesignGui; using namespace Gui; namespace sp = std::placeholders; /* TRANSLATOR PartDesignGui::TaskHoleParameters */ // See Hole::HoleCutType_ISOmetric_Enums // and Hole::HoleCutType_ISOmetricfine_Enums #if 0 // needed for Qt's lupdate utility qApp->translate("PartDesignGui::TaskHoleParameters", "Counterbore"); qApp->translate("PartDesignGui::TaskHoleParameters", "Countersink"); qApp->translate("PartDesignGui::TaskHoleParameters", "Counterdrill"); #endif TaskHoleParameters::TaskHoleParameters(ViewProviderHole* HoleView, QWidget* parent) : TaskSketchBasedParameters(HoleView, parent, "PartDesign_Hole", tr("Hole Parameters")) , observer(new Observer(this, getObject())) , ui(new Ui_TaskHoleParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); ui->setupUi(proxy); QMetaObject::connectSlotsByName(this); ui->ThreadType->addItem(tr("None"), QByteArray("None")); ui->ThreadType->addItem(tr("ISO metric regular"), QByteArray("ISO")); ui->ThreadType->addItem(tr("ISO metric fine"), QByteArray("ISO")); ui->ThreadType->addItem(tr("UTS coarse"), QByteArray("UTS")); ui->ThreadType->addItem(tr("UTS fine"), QByteArray("UTS")); ui->ThreadType->addItem(tr("UTS extra fine"), QByteArray("UTS")); ui->ThreadType->addItem(tr("ANSI pipes"), QByteArray("UTS")); ui->ThreadType->addItem(tr("ISO/BSP pipes"), QByteArray("ISO")); ui->ThreadType->addItem(tr("BSW whitworth"), QByteArray("Other")); ui->ThreadType->addItem(tr("BSF whitworth fine"), QByteArray("Other")); ui->ThreadType->addItem(tr("ISO tyre valves"), QByteArray("Other")); // read values from the hole properties auto pcHole = getObject(); bool isNone = std::string(pcHole->ThreadType.getValueAsString()) == "None"; bool isThreaded = pcHole->Threaded.getValue(); ui->labelThreading->setHidden(isNone); ui->labelHoleType->setHidden(isNone); ui->HoleType->setHidden(isNone); ui->ThreadSize->setHidden(isNone); ui->labelSize->setHidden(isNone); ui->ThreadFit->setHidden(isNone || isThreaded); ui->labelThreadClearance->setHidden(isNone || isThreaded); updateHoleTypeCombo(); ui->ThreadType->setCurrentIndex(pcHole->ThreadType.getValue()); ui->ThreadSize->clear(); std::vector cursor = pcHole->ThreadSize.getEnumVector(); for (const auto& it : cursor) { ui->ThreadSize->addItem(tr(it.c_str())); } ui->ThreadSize->setCurrentIndex(pcHole->ThreadSize.getValue()); ui->ThreadClass->clear(); cursor = pcHole->ThreadClass.getEnumVector(); for (const auto& it : cursor) { ui->ThreadClass->addItem(tr(it.c_str())); } ui->ThreadClass->setCurrentIndex(pcHole->ThreadClass.getValue()); // Class is only enabled (sensible) if threaded ui->ThreadClass->setEnabled(pcHole->Threaded.getValue()); ui->ThreadFit->setCurrentIndex(pcHole->ThreadFit.getValue()); // Fit is only enabled (sensible) if not threaded ui->ThreadFit->setEnabled(!pcHole->Threaded.getValue() && pcHole->ThreadType.getValue() != 0L); ui->Diameter->setMinimum(pcHole->Diameter.getMinimum()); ui->Diameter->setValue(pcHole->Diameter.getValue()); // Diameter is only enabled if ThreadType is None if (pcHole->ThreadType.getValue() != 0L) { ui->Diameter->setEnabled(false); } if (pcHole->ThreadDirection.getValue() == 0L) { ui->directionRightHand->setChecked(true); } else { ui->directionLeftHand->setChecked(true); } // ThreadDirection is only sensible if there is a thread ui->directionRightHand->setEnabled(pcHole->Threaded.getValue()); ui->directionLeftHand->setEnabled(pcHole->Threaded.getValue()); ui->HoleCutType->clear(); cursor = pcHole->HoleCutType.getEnumVector(); for (const auto& it : cursor) { ui->HoleCutType->addItem(tr(it.c_str())); } ui->HoleCutType->setCurrentIndex(pcHole->HoleCutType.getValue()); ui->HoleCutCustomValues->setChecked(pcHole->HoleCutCustomValues.getValue()); ui->HoleCutCustomValues->setHidden(pcHole->HoleCutType.getValue() < 4); // HoleCutDiameter must not be smaller or equal than the Diameter updateHoleCutLimits(pcHole); ui->HoleCutDiameter->setValue(pcHole->HoleCutDiameter.getValue()); ui->HoleCutDiameter->setDisabled(pcHole->HoleCutDiameter.isReadOnly()); ui->HoleCutDepth->setValue(pcHole->HoleCutDepth.getValue()); ui->HoleCutDepth->setDisabled(pcHole->HoleCutDepth.isReadOnly()); ui->HoleCutCountersinkAngle->setMinimum(pcHole->HoleCutCountersinkAngle.getMinimum()); ui->HoleCutCountersinkAngle->setMaximum(pcHole->HoleCutCountersinkAngle.getMaximum()); ui->HoleCutCountersinkAngle->setValue(pcHole->HoleCutCountersinkAngle.getValue()); ui->HoleCutCountersinkAngle->setDisabled(pcHole->HoleCutCountersinkAngle.isReadOnly()); ui->DepthType->setCurrentIndex(pcHole->DepthType.getValue()); ui->Depth->setValue(pcHole->Depth.getValue()); ui->DrillPointAngle->setMinimum(pcHole->DrillPointAngle.getMinimum()); ui->DrillPointAngle->setMaximum(pcHole->DrillPointAngle.getMaximum()); ui->DrillPointAngle->setValue(pcHole->DrillPointAngle.getValue()); ui->DrillForDepth->setChecked(pcHole->DrillForDepth.getValue()); bool isFlatDrill = pcHole->DrillPoint.getValue() == 0L; bool depthIsDimension = std::string(pcHole->DepthType.getValueAsString()) == "Dimension"; ui->DrillPointAngled->setChecked(!isFlatDrill && depthIsDimension); ui->DrillPointAngle->setEnabled(!isFlatDrill && depthIsDimension); ui->DrillForDepth->setEnabled(!isFlatDrill && depthIsDimension); ui->Tapered->setChecked(pcHole->Tapered.getValue()); // Angle is only enabled (sensible) if tapered ui->TaperedAngle->setEnabled(pcHole->Tapered.getValue()); ui->TaperedAngle->setMinimum(pcHole->TaperedAngle.getMinimum()); ui->TaperedAngle->setMaximum(pcHole->TaperedAngle.getMaximum()); ui->TaperedAngle->setValue(pcHole->TaperedAngle.getValue()); ui->Reversed->setChecked(pcHole->Reversed.getValue()); bool isModeled = pcHole->ModelThread.getValue(); ui->ThreadGroupBox->setVisible(isThreaded); ui->UseCustomThreadClearance->setChecked(pcHole->UseCustomThreadClearance.getValue()); ui->CustomThreadClearance->setValue(pcHole->CustomThreadClearance.getValue()); ui->ThreadDepthType->setCurrentIndex(pcHole->ThreadDepthType.getValue()); ui->ThreadDepth->setValue(pcHole->ThreadDepth.getValue()); ui->CustomClearanceWidget->setVisible(isThreaded && isModeled); ui->CustomThreadClearance->setEnabled(ui->UseCustomThreadClearance->isChecked()); ui->UpdateView->setChecked(false); ui->UpdateView->setVisible(isThreaded && isModeled); ui->Depth->setEnabled(depthIsDimension); ui->ThreadDepthWidget->setVisible(isThreaded); ui->ThreadDepthDimensionWidget->setVisible( std::string(pcHole->ThreadDepthType.getValueAsString()) == "Dimension" ); ui->BaseProfileType->setCurrentIndex( PartDesign::Hole::baseProfileOption_bitmaskToIdx(pcHole->BaseProfileType.getValue()) ); setCutDiagram(); // clang-format off connect(ui->HoleType, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::holeTypeChanged); connect(ui->ThreadType, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::threadTypeChanged); connect(ui->ThreadSize, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::threadSizeChanged); connect(ui->ThreadClass, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::threadClassChanged); connect(ui->ThreadFit, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::threadFitChanged); connect(ui->Diameter, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::threadDiameterChanged); connect(ui->directionRightHand, &QRadioButton::clicked, this, &TaskHoleParameters::threadDirectionChanged); connect(ui->directionLeftHand, &QRadioButton::clicked, this, &TaskHoleParameters::threadDirectionChanged); connect(ui->HoleCutType, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::holeCutTypeChanged); connect(ui->HoleCutCustomValues, &QCheckBox::clicked, this, &TaskHoleParameters::holeCutCustomValuesChanged); connect(ui->HoleCutDiameter, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::holeCutDiameterChanged); connect(ui->HoleCutDepth, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::holeCutDepthChanged); connect(ui->HoleCutCountersinkAngle, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::holeCutCountersinkAngleChanged); connect(ui->DepthType, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::depthChanged); connect(ui->Depth, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::depthValueChanged); connect(ui->DrillPointAngled, &QCheckBox::toggled, this, &TaskHoleParameters::drillPointChanged); connect(ui->DrillPointAngle, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::drillPointAngledValueChanged); connect(ui->DrillForDepth, &QCheckBox::clicked, this, &TaskHoleParameters::drillForDepthChanged); connect(ui->Tapered, &QCheckBox::clicked, this, &TaskHoleParameters::taperedChanged); connect(ui->Reversed, &QCheckBox::clicked, this, &TaskHoleParameters::reversedChanged); connect(ui->TaperedAngle, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::taperedAngleChanged); connect(ui->UpdateView, &QCheckBox::toggled, this, &TaskHoleParameters::updateViewChanged); connect(ui->UseCustomThreadClearance, &QCheckBox::toggled, this, &TaskHoleParameters::useCustomThreadClearanceChanged); connect(ui->CustomThreadClearance, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::customThreadClearanceChanged); connect(ui->ThreadDepthType, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::threadDepthTypeChanged); connect(ui->ThreadDepth, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &TaskHoleParameters::threadDepthChanged); connect(ui->BaseProfileType, qOverload(&QComboBox::currentIndexChanged), this, &TaskHoleParameters::baseProfileTypeChanged); // clang-format on ui->Diameter->bind(pcHole->Diameter); ui->HoleCutDiameter->bind(pcHole->HoleCutDiameter); ui->HoleCutDepth->bind(pcHole->HoleCutDepth); ui->HoleCutCountersinkAngle->bind(pcHole->HoleCutCountersinkAngle); ui->Depth->bind(pcHole->Depth); ui->DrillPointAngle->bind(pcHole->DrillPointAngle); ui->TaperedAngle->bind(pcHole->TaperedAngle); ui->ThreadDepth->bind(pcHole->ThreadDepth); ui->CustomThreadClearance->bind(pcHole->CustomThreadClearance); // NOLINTBEGIN connectPropChanged = App::GetApplication().signalChangePropertyEditor.connect( std::bind(&TaskHoleParameters::changedObject, this, sp::_1, sp::_2) ); // NOLINTEND this->groupLayout()->addWidget(proxy); setupGizmos(HoleView); } TaskHoleParameters::~TaskHoleParameters() = default; void TaskHoleParameters::holeTypeChanged(int index) { if (index < 0) { return; } auto pcHole = getObject(); if (!pcHole) { return; } bool isThreaded = getThreaded(); bool isModeled = getModelThread(); pcHole->Threaded.setValue(isThreaded); pcHole->ModelThread.setValue(isModeled); ui->ThreadFit->setHidden(isThreaded); ui->labelThreadClearance->setHidden(isThreaded); ui->ThreadGroupBox->setVisible(isThreaded); // update view not active if modeling threads // this will also ensure that the feature is recomputed. ui->UpdateView->setVisible(isModeled); setUpdateBlocked(isModeled && !(ui->UpdateView->isChecked())); // conditional enabling of thread modeling options ui->CustomClearanceWidget->setVisible(isModeled); ui->CustomThreadClearance->setEnabled(pcHole->UseCustomThreadClearance.getValue()); ui->ThreadDepthWidget->setVisible(isThreaded); ui->ThreadDepthDimensionWidget->setVisible( std::string(pcHole->ThreadDepthType.getValueAsString()) == "Dimension" ); recomputeFeature(); } void TaskHoleParameters::updateViewChanged(bool isChecked) { setUpdateBlocked(!isChecked); recomputeFeature(); } void TaskHoleParameters::threadDepthTypeChanged(int index) { if (auto hole = getObject()) { hole->ThreadDepthType.setValue(index); ui->ThreadDepthDimensionWidget->setVisible(index == 1); ui->ThreadDepth->setValue(hole->ThreadDepth.getValue()); recomputeFeature(); } } void TaskHoleParameters::threadDepthChanged(double value) { if (auto hole = getObject()) { hole->ThreadDepth.setValue(value); setCutDiagram(); recomputeFeature(); } } void TaskHoleParameters::useCustomThreadClearanceChanged() { bool isChecked = ui->UseCustomThreadClearance->isChecked(); ui->CustomThreadClearance->setEnabled(isChecked); ui->ThreadClass->setDisabled(isChecked); if (auto hole = getObject()) { hole->UseCustomThreadClearance.setValue(isChecked); recomputeFeature(); } } void TaskHoleParameters::customThreadClearanceChanged(double value) { if (auto hole = getObject()) { hole->CustomThreadClearance.setValue(value); recomputeFeature(); } } void TaskHoleParameters::threadPitchChanged(double value) { if (auto hole = getObject()) { hole->ThreadPitch.setValue(value); recomputeFeature(); } } void TaskHoleParameters::holeCutTypeChanged(int index) { if (index < 0) { return; } auto hole = getObject(); // the HoleCutDepth is something different for countersinks and counterbores // therefore reset it, it will be reset to sensible values by setting the new HoleCutType hole->HoleCutDepth.setValue(0.0); hole->HoleCutType.setValue(index); // when holeCutType was changed, reset HoleCutCustomValues to false because it should // be a purpose decision to overwrite the normed values // we will handle the case that there is no normed value later in this routine ui->HoleCutCustomValues->setChecked(false); hole->HoleCutCustomValues.setValue(false); // recompute to get the info about the HoleCutType properties recomputeFeature(); // apply the result to the widgets ui->HoleCutCustomValues->setChecked(hole->HoleCutCustomValues.getValue()); // HoleCutCustomValues is only enabled for screw definitions // we must do this after recomputeFeature() because this gives us the info if // the type is a countersink and thus if HoleCutCountersinkAngle can be enabled if (hole->HoleCutType.getValue() < 4) { ui->HoleCutCustomValues->setHidden(true); } else { // screw definition // we can have the case that we have no normed values // in this case HoleCutCustomValues is read-only AND true ui->HoleCutCustomValues->setHidden(false); bool isCustom = ui->HoleCutCustomValues->isChecked(); ui->HoleCutDiameter->setEnabled(isCustom); ui->HoleCutDepth->setEnabled(isCustom); ui->HoleCutCountersinkAngle->setEnabled( isCustom && !hole->HoleCutCountersinkAngle.isReadOnly() ); } setCutDiagram(); } void TaskHoleParameters::baseProfileTypeChanged(int index) { if (auto hole = getObject()) { hole->BaseProfileType.setValue(PartDesign::Hole::baseProfileOption_idxToBitmask(index)); recomputeFeature(); setGizmoPositions(); } } void TaskHoleParameters::setCutDiagram() { auto hole = getObject(); const std::string holeCutTypeString = hole->HoleCutType.getValueAsString(); const std::string threadTypeString = hole->ThreadType.getValueAsString(); bool isAngled = (std::string(hole->DepthType.getValueAsString()) == "Dimension" && ui->DrillPointAngled->isChecked()); bool isCountersink = (holeCutTypeString == "Countersink" || hole->isDynamicCountersink(threadTypeString, holeCutTypeString)); bool isCounterbore = (holeCutTypeString == "Counterbore" || hole->isDynamicCounterbore(threadTypeString, holeCutTypeString)); bool isCounterdrill = (holeCutTypeString == "Counterdrill"); bool includeAngle = hole->DrillForDepth.getValue(); bool isNotCut = holeCutTypeString == "None"; ui->labelHoleCutDiameter->setHidden(isNotCut); ui->labelHoleCutDepth->setHidden(isNotCut); ui->HoleCutDiameter->setHidden(isNotCut); ui->HoleCutDepth->setHidden(isNotCut); std::string baseFileName; if (isCounterbore) { baseFileName = "hole_counterbore"; ui->HoleCutCountersinkAngle->setVisible(false); ui->labelHoleCutCountersinkAngle->setVisible(false); } else if (isCountersink) { baseFileName = "hole_countersink"; ui->HoleCutCountersinkAngle->setVisible(true); ui->labelHoleCutCountersinkAngle->setVisible(true); } else if (isCounterdrill) { baseFileName = "hole_counterdrill"; ui->HoleCutCountersinkAngle->setVisible(true); ui->labelHoleCutCountersinkAngle->setVisible(true); } else { baseFileName = "hole_none"; ui->HoleCutCountersinkAngle->setVisible(false); ui->labelHoleCutCountersinkAngle->setVisible(false); } if (isAngled) { baseFileName += includeAngle ? "_angled_included" : "_angled"; } else { baseFileName += "_flat"; } ui->cutDiagram->setSvg(QString::fromUtf8((":images/" + baseFileName + ".svg").c_str())); } void TaskHoleParameters::holeCutCustomValuesChanged() { if (auto hole = getObject()) { hole->HoleCutCustomValues.setValue(ui->HoleCutCustomValues->isChecked()); if (ui->HoleCutCustomValues->isChecked()) { ui->HoleCutDiameter->setEnabled(true); ui->HoleCutDepth->setEnabled(true); if (!hole->HoleCutCountersinkAngle.isReadOnly()) { ui->HoleCutCountersinkAngle->setEnabled(true); } } else { ui->HoleCutDiameter->setEnabled(false); ui->HoleCutDepth->setEnabled(false); ui->HoleCutCountersinkAngle->setEnabled(false); } recomputeFeature(); } } void TaskHoleParameters::holeCutDiameterChanged(double value) { if (auto hole = getObject()) { hole->HoleCutDiameter.setValue(value); recomputeFeature(); } } void TaskHoleParameters::holeCutDepthChanged(double value) { auto hole = getObject(); if (!hole) { return; } std::string HoleCutTypeString = hole->HoleCutType.getValueAsString(); if (ui->HoleCutCountersinkAngle->isEnabled() && HoleCutTypeString != "Counterdrill") { // we have a countersink and recalculate the HoleCutDiameter // store current depth double DepthDifference = value - hole->HoleCutDepth.getValue(); // new diameter is the old one + 2 * tan(angle / 2) * DepthDifference double newDiameter = hole->HoleCutDiameter.getValue() + 2 * tan(Base::toRadians(hole->HoleCutCountersinkAngle.getValue() / 2)) * DepthDifference; // only apply if the result is not smaller than the hole diameter if (newDiameter > hole->Diameter.getValue()) { hole->HoleCutDiameter.setValue(newDiameter); hole->HoleCutDepth.setValue(value); } } else { hole->HoleCutDepth.setValue(value); } recomputeFeature(); } void TaskHoleParameters::holeCutCountersinkAngleChanged(double value) { if (auto hole = getObject()) { hole->HoleCutCountersinkAngle.setValue(value); recomputeFeature(); } } void TaskHoleParameters::depthChanged(int index) { auto hole = getObject(); if (!hole) { return; } hole->DepthType.setValue(index); recomputeFeature(); // enabling must be handled after recompute bool DepthisDimension = (std::string(hole->DepthType.getValueAsString()) == "Dimension"); ui->DrillPointAngled->setEnabled(DepthisDimension); ui->DrillPointAngle->setEnabled(DepthisDimension); ui->DrillForDepth->setEnabled(DepthisDimension); setCutDiagram(); setGizmoPositions(); } void TaskHoleParameters::depthValueChanged(double value) { if (auto hole = getObject()) { hole->Depth.setValue(value); recomputeFeature(); } } void TaskHoleParameters::drillPointChanged() { if (auto hole = getObject()) { bool angled = ui->DrillPointAngled->isChecked(); hole->DrillPoint.setValue(angled); ui->DrillPointAngle->setEnabled(angled); ui->DrillForDepth->setEnabled(angled); setCutDiagram(); recomputeFeature(); } } void TaskHoleParameters::drillPointAngledValueChanged(double value) { if (auto hole = getObject()) { hole->DrillPointAngle.setValue(value); recomputeFeature(); } } void TaskHoleParameters::drillForDepthChanged() { if (auto hole = getObject()) { hole->DrillForDepth.setValue(ui->DrillForDepth->isChecked()); recomputeFeature(); } setCutDiagram(); } void TaskHoleParameters::taperedChanged() { bool checked = ui->Tapered->isChecked(); ui->TaperedAngle->setEnabled(checked); if (auto hole = getObject()) { hole->Tapered.setValue(checked); recomputeFeature(); } } void TaskHoleParameters::reversedChanged() { if (auto hole = getObject()) { hole->Reversed.setValue(ui->Reversed->isChecked()); recomputeFeature(); setGizmoPositions(); } } void TaskHoleParameters::taperedAngleChanged(double value) { if (auto hole = getObject()) { hole->TaperedAngle.setValue(value); recomputeFeature(); } } void TaskHoleParameters::threadTypeChanged(int index) { if (index < 0) { return; } auto hole = getObject(); if (!hole) { return; } // A typical case is that users change from an ISO profile to another one. // When they had e.g. the size "M3" in one profile they expect // the same size in the other profile if it exists there. // Besides the size also the thread class" and hole cut type are affected. // at first check what type class is used QByteArray TypeClass = ui->ThreadType->itemData(index).toByteArray(); // store the current size QString ThreadSizeString = ui->ThreadSize->currentText(); // store the current class QString ThreadClassString = ui->ThreadClass->currentText(); // store the current type QString CutTypeString = ui->HoleCutType->currentText(); // now set the new type, this will reset the comboboxes to item 0 hole->ThreadType.setValue(index); // Threaded checkbox is meaningless if no thread profile is selected. bool isNone = std::string(hole->ThreadType.getValueAsString()) == "None"; bool isThreaded = hole->Threaded.getValue(); ui->ThreadGroupBox->setHidden(isNone || !isThreaded); ui->ThreadSize->setHidden(isNone); ui->labelSize->setHidden(isNone); ui->labelThreading->setHidden(isNone); ui->labelHoleType->setHidden(isNone); ui->HoleType->setHidden(isNone); ui->ThreadFit->setHidden(isNone || isThreaded); ui->labelThreadClearance->setHidden(isNone || isThreaded); if (TypeClass == QByteArray("None")) { QString noneText = QStringLiteral("-"); ui->ThreadFit->setItemText(0, noneText); ui->ThreadFit->setItemText(1, noneText); ui->ThreadFit->setItemText(2, noneText); } else if (TypeClass == QByteArray("ISO")) { ui->ThreadFit->setItemText( 0, tr("Medium", "Distance between thread crest and hole wall, use ISO-273 nomenclature or equivalent if possible") ); ui->ThreadFit->setItemText( 1, tr("Fine", "Distance between thread crest and hole wall, use ISO-273 nomenclature or equivalent if possible") ); ui->ThreadFit->setItemText( 2, tr("Coarse", "Distance between thread crest and hole wall, use ISO-273 nomenclature or equivalent if possible") ); } else if (TypeClass == QByteArray("UTS")) { ui->ThreadFit->setItemText( 0, tr("Normal", "Distance between thread crest and hole wall, use ASME B18.2.8 nomenclature or equivalent if possible") ); ui->ThreadFit->setItemText( 1, tr("Close", "Distance between thread crest and hole wall, use ASME B18.2.8 nomenclature or equivalent if possible") ); ui->ThreadFit->setItemText( 2, tr("Loose", "Distance between thread crest and hole wall, use ASME B18.2.8 nomenclature or equivalent if possible") ); } else { ui->ThreadFit->setItemText(0, tr("Normal", "Distance between thread crest and hole wall")); ui->ThreadFit->setItemText(1, tr("Close", "Distance between thread crest and hole wall")); ui->ThreadFit->setItemText(2, tr("Wide", "Distance between thread crest and hole wall")); } // Class and cut type // the class and cut types are the same for both TypeClass so we don't need to distinguish // between ISO and UTS int threadClassIndex = ui->ThreadClass->findText(ThreadClassString, Qt::MatchContains); if (threadClassIndex > -1) { ui->ThreadClass->setCurrentIndex(threadClassIndex); } int holeCutIndex = ui->HoleCutType->findText(CutTypeString, Qt::MatchContains); if (holeCutIndex > -1) { ui->HoleCutType->setCurrentIndex(holeCutIndex); } // we must set the read-only state according to the new HoleCutType holeCutTypeChanged(ui->HoleCutType->currentIndex()); recomputeFeature(); } void TaskHoleParameters::threadSizeChanged(int index) { if (index < 0) { return; } if (auto hole = getObject()) { hole->ThreadSize.setValue(index); recomputeFeature(); // apply the recompute result to the widgets ui->HoleCutCustomValues->setDisabled(hole->HoleCutCustomValues.isReadOnly()); ui->HoleCutCustomValues->setChecked(hole->HoleCutCustomValues.getValue()); } } void TaskHoleParameters::threadClassChanged(int index) { if (index < 0) { return; } if (auto hole = getObject()) { hole->ThreadClass.setValue(index); recomputeFeature(); } } void TaskHoleParameters::threadDiameterChanged(double value) { if (auto hole = getObject()) { hole->Diameter.setValue(value); updateHoleCutLimits(hole); recomputeFeature(); } } void TaskHoleParameters::threadFitChanged(int index) { if (auto hole = getObject()) { hole->ThreadFit.setValue(index); recomputeFeature(); } } void TaskHoleParameters::threadDirectionChanged() { if (auto hole = getObject()) { if (sender() == ui->directionRightHand) { hole->ThreadDirection.setValue(0L); } else { hole->ThreadDirection.setValue(1L); } recomputeFeature(); } } void TaskHoleParameters::changeEvent(QEvent* e) { TaskBox::changeEvent(e); if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(proxy); } } void TaskHoleParameters::changedObject(const App::Document&, const App::Property& Prop) { auto hole = getObject(); if (!hole) { return; // happens when aborting the command } bool ro = Prop.isReadOnly(); Base::Console().log("Parameter %s was updated\n", Prop.getName()); auto updateCheckable = [&](QCheckBox* widget, bool value) { [[maybe_unused]] QSignalBlocker blocker(widget); widget->setChecked(value); widget->setDisabled(ro); }; auto updateRadio = [&](QRadioButton* widget, bool value) { [[maybe_unused]] QSignalBlocker blocker(widget); widget->setChecked(value); widget->setDisabled(ro); }; auto updateComboBox = [&](QComboBox* widget, int value) { [[maybe_unused]] QSignalBlocker blocker(widget); widget->setCurrentIndex(value); widget->setDisabled(ro); }; auto updateSpinBox = [&](Gui::PrefQuantitySpinBox* widget, double value) { [[maybe_unused]] QSignalBlocker blocker(widget); widget->setValue(value); widget->setDisabled(ro); }; if (&Prop == &hole->Threaded || &Prop == &hole->ModelThread) { updateHoleTypeCombo(); } else if (&Prop == &hole->ThreadType) { ui->ThreadType->setEnabled(true); updateComboBox(ui->ThreadType, hole->ThreadType.getValue()); // Thread type also updates related properties auto updateComboBoxItems = [&](QComboBox* widget, const auto& values, int selected) { QSignalBlocker blocker(widget); widget->clear(); for (const auto& it : values) { widget->addItem(QString::fromStdString(it)); } widget->setCurrentIndex(selected); }; updateComboBoxItems( ui->ThreadSize, hole->ThreadSize.getEnumVector(), hole->ThreadSize.getValue() ); std::vector translatedCutTypes; for (const auto& it : hole->HoleCutType.getEnumVector()) { translatedCutTypes.push_back(tr(it.c_str()).toStdString()); } updateComboBoxItems(ui->HoleCutType, translatedCutTypes, hole->HoleCutType.getValue()); std::vector translatedClassTypes; for (const auto& it : hole->ThreadClass.getEnumVector()) { translatedClassTypes.push_back(tr(it.c_str()).toStdString()); } updateComboBoxItems(ui->ThreadClass, translatedClassTypes, hole->ThreadClass.getValue()); } else if (&Prop == &hole->ThreadSize) { ui->ThreadSize->setEnabled(true); updateComboBox(ui->ThreadSize, hole->ThreadSize.getValue()); } else if (&Prop == &hole->ThreadClass) { ui->ThreadClass->setEnabled(true); updateComboBox(ui->ThreadClass, hole->ThreadClass.getValue()); } else if (&Prop == &hole->ThreadFit) { ui->ThreadFit->setEnabled(true); updateComboBox(ui->ThreadFit, hole->ThreadFit.getValue()); } else if (&Prop == &hole->Diameter) { ui->Diameter->setEnabled(true); updateSpinBox(ui->Diameter, hole->Diameter.getValue()); updateHoleCutLimits(hole); } else if (&Prop == &hole->ThreadDirection) { ui->directionRightHand->setEnabled(true); ui->directionLeftHand->setEnabled(true); std::string direction(hole->ThreadDirection.getValueAsString()); updateRadio(ui->directionRightHand, direction == "Right"); updateRadio(ui->directionLeftHand, direction == "Left"); } else if (&Prop == &hole->HoleCutType) { ui->HoleCutType->setEnabled(true); updateComboBox(ui->HoleCutType, hole->HoleCutType.getValue()); } else if (&Prop == &hole->HoleCutDiameter) { ui->HoleCutDiameter->setEnabled(true); updateSpinBox(ui->HoleCutDiameter, hole->HoleCutDiameter.getValue()); } else if (&Prop == &hole->HoleCutDepth) { ui->HoleCutDepth->setEnabled(true); updateSpinBox(ui->HoleCutDepth, hole->HoleCutDepth.getValue()); } else if (&Prop == &hole->HoleCutCountersinkAngle) { ui->HoleCutCountersinkAngle->setEnabled(true); updateSpinBox(ui->HoleCutCountersinkAngle, hole->HoleCutCountersinkAngle.getValue()); } else if (&Prop == &hole->DepthType) { ui->DepthType->setEnabled(true); updateComboBox(ui->DepthType, hole->DepthType.getValue()); } else if (&Prop == &hole->Depth) { ui->Depth->setEnabled(true); updateSpinBox(ui->Depth, hole->Depth.getValue()); } else if (&Prop == &hole->DrillPoint) { ui->DrillPointAngled->setEnabled(true); updateCheckable( ui->DrillPointAngled, hole->DrillPoint.getValueAsString() == std::string("Angled") ); } else if (&Prop == &hole->DrillPointAngle) { ui->DrillPointAngle->setEnabled(true); updateSpinBox(ui->DrillPointAngle, hole->DrillPointAngle.getValue()); } else if (&Prop == &hole->DrillForDepth) { ui->DrillForDepth->setEnabled(true); updateCheckable(ui->DrillForDepth, hole->DrillForDepth.getValue()); } else if (&Prop == &hole->Tapered) { ui->Tapered->setEnabled(true); updateCheckable(ui->Tapered, hole->Tapered.getValue()); } else if (&Prop == &hole->TaperedAngle) { ui->TaperedAngle->setEnabled(true); updateSpinBox(ui->TaperedAngle, hole->TaperedAngle.getValue()); } else if (&Prop == &hole->UseCustomThreadClearance) { ui->UseCustomThreadClearance->setEnabled(true); updateCheckable(ui->UseCustomThreadClearance, hole->UseCustomThreadClearance.getValue()); } else if (&Prop == &hole->CustomThreadClearance) { ui->CustomThreadClearance->setEnabled(true); updateSpinBox(ui->CustomThreadClearance, hole->CustomThreadClearance.getValue()); } else if (&Prop == &hole->ThreadDepthType) { ui->ThreadDepthType->setEnabled(true); updateComboBox(ui->ThreadDepthType, hole->ThreadDepthType.getValue()); } else if (&Prop == &hole->ThreadDepth) { ui->ThreadDepth->setEnabled(true); updateSpinBox(ui->ThreadDepth, hole->ThreadDepth.getValue()); } else if (&Prop == &hole->BaseProfileType) { ui->BaseProfileType->setEnabled(true); updateComboBox( ui->BaseProfileType, PartDesign::Hole::baseProfileOption_bitmaskToIdx(hole->BaseProfileType.getValue()) ); } } void TaskHoleParameters::updateHoleTypeCombo() { auto hole = getObject(); if (!hole) { return; } [[maybe_unused]] QSignalBlocker blocker(ui->HoleType); if (hole->Threaded.getValue()) { if (hole->ModelThread.getValue()) { ui->HoleType->setCurrentIndex(ModeledThread); } else { ui->HoleType->setCurrentIndex(TapDrill); } } else { ui->HoleType->setCurrentIndex(Clearance); } } void TaskHoleParameters::onSelectionChanged(const Gui::SelectionChanges& msg) { Q_UNUSED(msg) } bool TaskHoleParameters::getThreaded() const { return ui->HoleType->currentIndex() != Clearance; } bool TaskHoleParameters::getModelThread() const { return ui->HoleType->currentIndex() == ModeledThread; } long TaskHoleParameters::getThreadType() const { return ui->ThreadType->currentIndex(); } long TaskHoleParameters::getThreadSize() const { if (ui->ThreadSize->currentIndex() == -1) { return 0; } else { return ui->ThreadSize->currentIndex(); } } long TaskHoleParameters::getThreadClass() const { if (ui->ThreadSize->currentIndex() == -1) { return 0; } else { return ui->ThreadClass->currentIndex(); } } long TaskHoleParameters::getThreadFit() const { // the fit (clearance) is independent if the hole is threaded or not // since an unthreaded hole for a screw can also have a close fit return ui->ThreadFit->currentIndex(); } Base::Quantity TaskHoleParameters::getDiameter() const { return ui->Diameter->value(); } long TaskHoleParameters::getThreadDirection() const { if (ui->directionRightHand->isChecked()) { return 0; } else { return 1; } } long TaskHoleParameters::getHoleCutType() const { if (ui->HoleCutType->currentIndex() == -1) { return 0; } else { return ui->HoleCutType->currentIndex(); } } bool TaskHoleParameters::getHoleCutCustomValues() const { return ui->HoleCutCustomValues->isChecked(); } Base::Quantity TaskHoleParameters::getHoleCutDiameter() const { return ui->HoleCutDiameter->value(); } Base::Quantity TaskHoleParameters::getHoleCutDepth() const { return ui->HoleCutDepth->value(); } Base::Quantity TaskHoleParameters::getHoleCutCountersinkAngle() const { return ui->HoleCutCountersinkAngle->value(); } long TaskHoleParameters::getDepthType() const { return ui->DepthType->currentIndex(); } Base::Quantity TaskHoleParameters::getDepth() const { return ui->Depth->value(); } long TaskHoleParameters::getDrillPoint() const { return ui->DrillPointAngled->isChecked() ? 1 : 0; } Base::Quantity TaskHoleParameters::getDrillPointAngle() const { return ui->DrillPointAngle->value(); } bool TaskHoleParameters::getTapered() const { return ui->Tapered->isChecked(); } bool TaskHoleParameters::getDrillForDepth() const { return ui->DrillForDepth->isChecked(); } Base::Quantity TaskHoleParameters::getTaperedAngle() const { return ui->TaperedAngle->value(); } bool TaskHoleParameters::getUseCustomThreadClearance() const { return ui->UseCustomThreadClearance->isChecked(); } double TaskHoleParameters::getCustomThreadClearance() const { return ui->CustomThreadClearance->value().getValue(); } long TaskHoleParameters::getThreadDepthType() const { return ui->ThreadDepthType->currentIndex(); } double TaskHoleParameters::getThreadDepth() const { return ui->ThreadDepth->value().getValue(); } int TaskHoleParameters::getBaseProfileType() const { return PartDesign::Hole::baseProfileOption_idxToBitmask(ui->BaseProfileType->currentIndex()); } void TaskHoleParameters::apply() { auto hole = getObject(); ui->Diameter->apply(); ui->HoleCutDiameter->apply(); ui->HoleCutDepth->apply(); ui->HoleCutCountersinkAngle->apply(); ui->Depth->apply(); ui->DrillPointAngle->apply(); ui->TaperedAngle->apply(); if (!hole->Threaded.isReadOnly()) { FCMD_OBJ_CMD(hole, "Threaded = " << (getThreaded() ? 1 : 0)); } if (!hole->ModelThread.isReadOnly()) { FCMD_OBJ_CMD(hole, "ModelThread = " << (getModelThread() ? 1 : 0)); } if (!hole->ThreadDepthType.isReadOnly()) { FCMD_OBJ_CMD(hole, "ThreadDepthType = " << getThreadDepthType()); } if (!hole->ThreadDepth.isReadOnly()) { FCMD_OBJ_CMD(hole, "ThreadDepth = " << getThreadDepth()); } if (!hole->UseCustomThreadClearance.isReadOnly()) { FCMD_OBJ_CMD(hole, "UseCustomThreadClearance = " << (getUseCustomThreadClearance() ? 1 : 0)); } if (!hole->CustomThreadClearance.isReadOnly()) { FCMD_OBJ_CMD(hole, "CustomThreadClearance = " << getCustomThreadClearance()); } if (!hole->ThreadType.isReadOnly()) { FCMD_OBJ_CMD(hole, "ThreadType = " << getThreadType()); } if (!hole->ThreadSize.isReadOnly()) { FCMD_OBJ_CMD(hole, "ThreadSize = " << getThreadSize()); } if (!hole->ThreadClass.isReadOnly()) { FCMD_OBJ_CMD(hole, "ThreadClass = " << getThreadClass()); } if (!hole->ThreadFit.isReadOnly()) { FCMD_OBJ_CMD(hole, "ThreadFit = " << getThreadFit()); } if (!hole->ThreadDirection.isReadOnly()) { FCMD_OBJ_CMD(hole, "ThreadDirection = " << getThreadDirection()); } if (!hole->HoleCutType.isReadOnly()) { FCMD_OBJ_CMD(hole, "HoleCutType = " << getHoleCutType()); } if (!hole->HoleCutCustomValues.isReadOnly()) { FCMD_OBJ_CMD(hole, "HoleCutCustomValues = " << (getHoleCutCustomValues() ? 1 : 0)); } if (!hole->DepthType.isReadOnly()) { FCMD_OBJ_CMD(hole, "DepthType = " << getDepthType()); } if (!hole->DrillPoint.isReadOnly()) { FCMD_OBJ_CMD(hole, "DrillPoint = " << getDrillPoint()); } if (!hole->DrillForDepth.isReadOnly()) { FCMD_OBJ_CMD(hole, "DrillForDepth = " << (getDrillForDepth() ? 1 : 0)); } if (!hole->Tapered.isReadOnly()) { FCMD_OBJ_CMD(hole, "Tapered = " << getTapered()); } if (!hole->BaseProfileType.isReadOnly()) { FCMD_OBJ_CMD(hole, "BaseProfileType = " << getBaseProfileType()); } } void TaskHoleParameters::updateHoleCutLimits(PartDesign::Hole* hole) { constexpr double minHoleCutDifference = 0.1; // HoleCutDiameter must not be smaller or equal than the Diameter ui->HoleCutDiameter->setMinimum(hole->Diameter.getValue() + minHoleCutDifference); } void TaskHoleParameters::setupGizmos(ViewProviderHole* vp) { if (!GizmoContainer::isEnabled()) { return; } holeDepthGizmo = new LinearGizmo(ui->Depth); gizmoContainer = GizmoContainer::create({holeDepthGizmo}, vp); setGizmoPositions(); } std::vector getHolePositionFromShape( const Part::TopoShape& profileshape, const long baseProfileType ) { using BaseProfileTypeOptions = PartDesign::Hole::BaseProfileTypeOptions; std::vector positions; // Iterate over edges and filter out non-circle/non-arc types if (baseProfileType & BaseProfileTypeOptions::OnCircles || baseProfileType & BaseProfileTypeOptions::OnArcs) { for (const auto& profileEdge : profileshape.getSubTopoShapes(TopAbs_EDGE)) { TopoDS_Edge edge = TopoDS::Edge(profileEdge.getShape()); BRepAdaptor_Curve adaptor(edge); // Circle base? if (adaptor.GetType() != GeomAbs_Circle) { continue; } // Filter for circles if (!(baseProfileType & BaseProfileTypeOptions::OnCircles) && adaptor.IsClosed()) { continue; } // Filter for arcs if (!(baseProfileType & BaseProfileTypeOptions::OnArcs) && !adaptor.IsClosed()) { continue; } gp_Circ circle = adaptor.Circle(); positions.push_back(Base::convertTo(circle.Axis().Location())); } } // To avoid breaking older files which where not made with // holes on points if (baseProfileType & BaseProfileTypeOptions::OnPoints) { // Iterate over vertices while avoiding edges so that curve handles are ignored for (const auto& profileVertex : profileshape.getSubTopoShapes(TopAbs_VERTEX, TopAbs_EDGE)) { TopoDS_Vertex vertex = TopoDS::Vertex(profileVertex.getShape()); positions.push_back(Base::convertTo(BRep_Tool::Pnt(vertex))); } } return positions; } void TaskHoleParameters::setGizmoPositions() { if (!gizmoContainer) { return; } auto hole = getObject(); if (!hole || hole->isError()) { gizmoContainer->visible = false; return; } Part::TopoShape profileShape = hole->getProfileShape( Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform | Part::ShapeOption::DontSimplifyCompound ); Base::Vector3d dir = hole->guessNormalDirection(profileShape); dir *= hole->Reversed.getValue() ? -1 : 1; std::vector holePositions = getHolePositionFromShape(profileShape, hole->BaseProfileType.getValue()); if (holePositions.size() == 0) { gizmoContainer->visible = false; return; } gizmoContainer->visible = true; holeDepthGizmo->Gizmo::setDraggerPlacement( holePositions[0] - ui->HoleCutDepth->value().getValue() * dir, -dir ); holeDepthGizmo->setVisibility(std::string(hole->DepthType.getValueAsString()) == "Dimension"); holeDepthGizmo->setDragLength(ui->Depth->rawValue()); } //************************************************************************** //************************************************************************** // TaskDialog //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskDlgHoleParameters::TaskDlgHoleParameters(ViewProviderHole* HoleView) : TaskDlgSketchBasedParameters(HoleView) { assert(HoleView); parameter = new TaskHoleParameters(HoleView); Content.push_back(parameter); Content.push_back(preview); } TaskDlgHoleParameters::~TaskDlgHoleParameters() = default; #include "moc_TaskHoleParameters.cpp" TaskHoleParameters::Observer::Observer(TaskHoleParameters* _owner, PartDesign::Hole* _hole) : DocumentObserver(_hole->getDocument()) , owner(_owner) , hole(_hole) {} void TaskHoleParameters::Observer::slotChangedObject( const App::DocumentObject& Obj, const App::Property& Prop ) { if (&Obj == hole) { Base::Console().log("Parameter %s was updated with a new value\n", Prop.getName()); if (Obj.getDocument()) { owner->changedObject(*Obj.getDocument(), Prop); } } }