Download src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 51.5 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp
-
curl -L -o TaskExtrudeParameters.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp
51.5 kB
| /*************************************************************************** | |
| * Copyright (c) 2021 Werner Mayer <wmayer[at]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 * | |
| * * | |
| ***************************************************************************/ | |
| using namespace PartDesignGui; | |
| using namespace Gui; | |
| /* TRANSLATOR PartDesignGui::TaskExtrudeParameters */ | |
| TaskExtrudeParameters::TaskExtrudeParameters( | |
| ViewProviderExtrude* SketchBasedView, | |
| QWidget* parent, | |
| const std::string& pixmapname, | |
| const QString& parname | |
| ) | |
| : TaskSketchBasedParameters(SketchBasedView, parent, pixmapname, parname) | |
| , propReferenceAxis(nullptr) | |
| , ui(new Ui_TaskPadPocketParameters) | |
| { | |
| // we need a separate container widget to add all controls to | |
| proxy = new QWidget(this); | |
| ui->setupUi(proxy); | |
| handleLineFaceNameNo(ui->lineFaceName); | |
| handleLineFaceNameNo(ui->lineFaceName2); | |
| Gui::ButtonGroup* group = new Gui::ButtonGroup(this); | |
| group->addButton(ui->checkBoxReversed); | |
| group->setExclusive(true); | |
| this->groupLayout()->addWidget(proxy); | |
| } | |
| void TaskExtrudeParameters::setupDialog() | |
| { | |
| auto createRemoveAction = [this]() -> QAction* { | |
| auto action = new QAction(tr("Remove"), this); | |
| action->setShortcut(Gui::QtTools::deleteKeySequence()); | |
| action->setShortcutVisibleInContextMenu(true); | |
| return action; | |
| }; | |
| unselectShapeFaceAction = createRemoveAction(); | |
| unselectShapeFaceAction2 = createRemoveAction(); | |
| createSideControllers(); | |
| // --- Global, Non-Side-Specific Setup --- | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| int UserDecimals = Base::UnitsApi::getDecimals(); | |
| ui->XDirectionEdit->setDecimals(UserDecimals); | |
| ui->YDirectionEdit->setDecimals(UserDecimals); | |
| ui->ZDirectionEdit->setDecimals(UserDecimals); | |
| ui->checkBoxAlongDirection->setChecked(extrude->AlongSketchNormal.getValue()); | |
| ui->XDirectionEdit->setValue(extrude->Direction.getValue().x); | |
| ui->YDirectionEdit->setValue(extrude->Direction.getValue().y); | |
| ui->ZDirectionEdit->setValue(extrude->Direction.getValue().z); | |
| ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, "Direction.x")); | |
| ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, "Direction.y")); | |
| ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, "Direction.z")); | |
| ui->checkBoxReversed->setChecked(extrude->Reversed.getValue()); | |
| // --- Per-Side Setup using the Helper --- | |
| setupSideDialog(m_side1); | |
| setupSideDialog(m_side2); | |
| // --- Final Global UI State Setup --- | |
| translateSidesList(extrude->SideType.getValue()); | |
| connectSlots(); | |
| this->propReferenceAxis = &(extrude->ReferenceAxis); | |
| updateUI(Side::First); | |
| setupGizmos(); | |
| // trigger recompute to ensure external geometry references update correctly. | |
| // see freecad issue #25794 | |
| tryRecomputeFeature(); | |
| } | |
| void TaskExtrudeParameters::setupSideDialog(SideController& side) | |
| { | |
| // --- Get initial values from the correct side's properties --- | |
| Base::Quantity length = side.Length->getQuantityValue(); | |
| Base::Quantity offset = side.Offset->getQuantityValue(); | |
| Base::Quantity taper = side.TaperAngle->getQuantityValue(); | |
| int typeIndex = side.Type->getValue(); | |
| // The Type/Type2 properties are stuck with the deprecated 'TwoLength' mode. | |
| // Because enums do not store text just an index. So this create | |
| // a inconsistence between the UI and the property. | |
| if (typeIndex > static_cast<int>(Mode::ToShape)) { | |
| typeIndex--; | |
| } | |
| // --- Set up UI widgets with initial values --- | |
| side.lengthEdit->setValue(length); | |
| side.offsetEdit->setValue(offset); | |
| side.taperEdit->setMinimum(side.TaperAngle->getMinimum()); | |
| side.taperEdit->setMaximum(side.TaperAngle->getMaximum()); | |
| side.taperEdit->setSingleStep(side.TaperAngle->getStepSize()); | |
| side.taperEdit->setValue(taper); | |
| // --- Bind UI widgets to the correct properties --- | |
| side.lengthEdit->bind(*side.Length); | |
| side.offsetEdit->bind(*side.Offset); | |
| side.taperEdit->bind(*side.TaperAngle); | |
| // --- Handle "Up to face" label logic --- | |
| App::DocumentObject* faceObj = side.UpToFace->getValue(); | |
| std::vector<std::string> subStrings = side.UpToFace->getSubValues(); | |
| std::string upToFaceName; | |
| int faceId = -1; | |
| if (faceObj && !subStrings.empty()) { | |
| upToFaceName = subStrings.front(); | |
| if (upToFaceName.rfind("Face", 0) == 0) { // starts_with | |
| faceId = std::atoi(&upToFaceName[4]); | |
| } | |
| } | |
| if (faceObj && PartDesign::Feature::isDatum(faceObj)) { | |
| side.lineFaceName->setText(QString::fromUtf8(faceObj->Label.getValue())); | |
| side.lineFaceName->setProperty("FeatureName", QByteArray(faceObj->getNameInDocument())); | |
| } | |
| else if (faceObj && faceId >= 0) { | |
| side.lineFaceName->setText(QStringLiteral("%1:%2%3").arg( | |
| QString::fromUtf8(faceObj->Label.getValue()), | |
| tr("Face"), | |
| QString::number(faceId) | |
| )); | |
| side.lineFaceName->setProperty("FeatureName", QByteArray(faceObj->getNameInDocument())); | |
| } | |
| else { | |
| side.lineFaceName->clear(); | |
| side.lineFaceName->setProperty("FeatureName", QVariant()); | |
| } | |
| side.lineFaceName->setProperty("FaceName", QByteArray(upToFaceName.c_str())); | |
| // --- Update shape-related UI --- | |
| updateShapeName(side.lineShapeName, *side.UpToShape); | |
| updateShapeFaces(side.listWidgetReferences, *side.UpToShape); | |
| // --- Set up the mode combobox and list widget context menu --- | |
| translateModeList(side.changeMode, typeIndex); | |
| side.listWidgetReferences->addAction(side.unselectShapeFaceAction); | |
| side.listWidgetReferences->setContextMenuPolicy(Qt::ActionsContextMenu); | |
| side.checkBoxAllFaces->setChecked(side.listWidgetReferences->count() == 0); | |
| } | |
| void TaskExtrudeParameters::createSideControllers() | |
| { | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| // --- Initialize Side 1 Controller --- | |
| m_side1.changeMode = ui->changeMode; | |
| m_side1.labelLength = ui->labelLength; | |
| m_side1.labelOffset = ui->labelOffset; | |
| m_side1.labelTaperAngle = ui->labelTaperAngle; | |
| m_side1.lengthEdit = ui->lengthEdit; | |
| m_side1.offsetEdit = ui->offsetEdit; | |
| m_side1.taperEdit = ui->taperEdit; | |
| m_side1.lineFaceName = ui->lineFaceName; | |
| m_side1.buttonFace = ui->buttonFace; | |
| m_side1.lineShapeName = ui->lineShapeName; | |
| m_side1.buttonShape = ui->buttonShape; | |
| m_side1.listWidgetReferences = ui->listWidgetReferences; | |
| m_side1.buttonShapeFace = ui->buttonShapeFace; | |
| m_side1.checkBoxAllFaces = ui->checkBoxAllFaces; | |
| m_side1.upToShapeList = ui->upToShapeList; | |
| m_side1.upToShapeFaces = ui->upToShapeFaces; | |
| m_side1.unselectShapeFaceAction = unselectShapeFaceAction; | |
| m_side1.Type = &extrude->Type; | |
| m_side1.Length = &extrude->Length; | |
| m_side1.Offset = &extrude->Offset; | |
| m_side1.TaperAngle = &extrude->TaperAngle; | |
| m_side1.UpToFace = &extrude->UpToFace; | |
| m_side1.UpToShape = &extrude->UpToShape; | |
| // --- Initialize Side 2 Controller --- | |
| m_side2.changeMode = ui->changeMode2; | |
| m_side2.labelLength = ui->labelLength2; | |
| m_side2.labelOffset = ui->labelOffset2; | |
| m_side2.labelTaperAngle = ui->labelTaperAngle2; | |
| m_side2.lengthEdit = ui->lengthEdit2; | |
| m_side2.offsetEdit = ui->offsetEdit2; | |
| m_side2.taperEdit = ui->taperEdit2; | |
| m_side2.lineFaceName = ui->lineFaceName2; | |
| m_side2.buttonFace = ui->buttonFace2; | |
| m_side2.lineShapeName = ui->lineShapeName2; | |
| m_side2.buttonShape = ui->buttonShape2; | |
| m_side2.listWidgetReferences = ui->listWidgetReferences2; | |
| m_side2.buttonShapeFace = ui->buttonShapeFace2; | |
| m_side2.checkBoxAllFaces = ui->checkBoxAllFaces2; | |
| m_side2.upToShapeList = ui->upToShapeList2; | |
| m_side2.upToShapeFaces = ui->upToShapeFaces2; | |
| m_side2.unselectShapeFaceAction = unselectShapeFaceAction2; | |
| m_side2.Type = &extrude->Type2; | |
| m_side2.Length = &extrude->Length2; | |
| m_side2.Offset = &extrude->Offset2; | |
| m_side2.TaperAngle = &extrude->TaperAngle2; | |
| m_side2.UpToFace = &extrude->UpToFace2; | |
| m_side2.UpToShape = &extrude->UpToShape2; | |
| } | |
| void TaskExtrudeParameters::readValuesFromHistory() | |
| { | |
| ui->lengthEdit->setToLastUsedValue(); | |
| ui->lengthEdit->selectNumber(); | |
| ui->lengthEdit2->setToLastUsedValue(); | |
| ui->lengthEdit2->selectNumber(); | |
| ui->offsetEdit->setToLastUsedValue(); | |
| ui->offsetEdit->selectNumber(); | |
| ui->offsetEdit2->setToLastUsedValue(); | |
| ui->offsetEdit2->selectNumber(); | |
| ui->taperEdit->setToLastUsedValue(); | |
| ui->taperEdit->selectNumber(); | |
| ui->taperEdit2->setToLastUsedValue(); | |
| ui->taperEdit2->selectNumber(); | |
| } | |
| void TaskExtrudeParameters::connectSlots() | |
| { | |
| QMetaObject::connectSlotsByName(this); | |
| auto connectSideSlots = [this](auto& side, Side sideEnum, auto modeChangedSlot) { | |
| connect( | |
| side.lengthEdit, | |
| qOverload<double>(&Gui::PrefQuantitySpinBox::valueChanged), | |
| this, | |
| [this, sideEnum](double val) { onLengthChanged(val, sideEnum); } | |
| ); | |
| connect( | |
| side.offsetEdit, | |
| qOverload<double>(&Gui::PrefQuantitySpinBox::valueChanged), | |
| this, | |
| [this, sideEnum](double val) { onOffsetChanged(val, sideEnum); } | |
| ); | |
| connect( | |
| side.taperEdit, | |
| qOverload<double>(&Gui::PrefQuantitySpinBox::valueChanged), | |
| this, | |
| [this, sideEnum](double val) { onTaperChanged(val, sideEnum); } | |
| ); | |
| connect(side.changeMode, qOverload<int>(&QComboBox::currentIndexChanged), this, modeChangedSlot); | |
| connect(side.buttonFace, &QToolButton::toggled, this, [this, sideEnum](bool checked) { | |
| onSelectFaceToggle(checked, sideEnum); | |
| }); | |
| connect(side.lineFaceName, &QLineEdit::textEdited, this, [this, sideEnum](const QString& text) { | |
| onFaceName(text, sideEnum); | |
| }); | |
| connect(side.checkBoxAllFaces, &QCheckBox::toggled, this, [this, sideEnum](bool checked) { | |
| onAllFacesToggled(checked, sideEnum); | |
| }); | |
| connect(side.buttonShape, &QToolButton::toggled, this, [this, sideEnum](bool checked) { | |
| onSelectShapeToggle(checked, sideEnum); | |
| }); | |
| connect(side.buttonShapeFace, &QToolButton::toggled, this, [this, sideEnum](bool checked) { | |
| onSelectShapeFacesToggle(checked, sideEnum); | |
| }); | |
| connect(side.unselectShapeFaceAction, &QAction::triggered, this, [this, sideEnum]() { | |
| onUnselectShapeFacesTrigger(sideEnum); | |
| }); | |
| }; | |
| // Use the lambda to connect slots for both sides | |
| connectSideSlots(m_side1, Side::First, &TaskExtrudeParameters::onModeChanged_Side1); | |
| connectSideSlots(m_side2, Side::Second, &TaskExtrudeParameters::onModeChanged_Side2); | |
| // clang-format off | |
| connect(ui->directionCB, qOverload<int>(&QComboBox::activated), | |
| this, &TaskExtrudeParameters::onDirectionCBChanged); | |
| connect(ui->checkBoxAlongDirection, &QCheckBox::toggled, | |
| this, &TaskExtrudeParameters::onAlongSketchNormalChanged); | |
| connect(ui->XDirectionEdit, qOverload<double>(&QDoubleSpinBox::valueChanged), | |
| this, &TaskExtrudeParameters::onXDirectionEditChanged); | |
| connect(ui->YDirectionEdit, qOverload<double>(&QDoubleSpinBox::valueChanged), | |
| this, &TaskExtrudeParameters::onYDirectionEditChanged); | |
| connect(ui->ZDirectionEdit, qOverload<double>(&QDoubleSpinBox::valueChanged), | |
| this, &TaskExtrudeParameters::onZDirectionEditChanged); | |
| connect(ui->checkBoxReversed, &QCheckBox::toggled, | |
| this, &TaskExtrudeParameters::onReversedChanged); | |
| connect(ui->sidesMode, qOverload<int>(&QComboBox::currentIndexChanged), | |
| this, &TaskExtrudeParameters::onSidesModeChanged); | |
| connect(ui->checkBoxUpdateView, &QCheckBox::toggled, | |
| this, &TaskExtrudeParameters::onUpdateView); | |
| // clang-format on | |
| } | |
| void TaskExtrudeParameters::onModeChanged_Side1(int index) | |
| { | |
| onModeChanged(index, Side::First); | |
| setGizmoPositions(); | |
| } | |
| void TaskExtrudeParameters::onModeChanged_Side2(int index) | |
| { | |
| onModeChanged(index, Side::Second); | |
| setGizmoPositions(); | |
| } | |
| void TaskExtrudeParameters::onSelectShapeFacesToggle(bool checked, Side side) | |
| { | |
| auto& sideCtrl = getSideController(side); | |
| if (checked) { | |
| setSelectionMode(SelectShapeFaces, side); | |
| sideCtrl.buttonShapeFace->setText(tr("Preview")); | |
| } | |
| else { | |
| setSelectionMode(None); | |
| sideCtrl.buttonShapeFace->setText(tr("Select Faces")); | |
| } | |
| } | |
| void PartDesignGui::TaskExtrudeParameters::onUnselectShapeFacesTrigger(Side side) | |
| { | |
| auto& sideCtrl = getSideController(side); | |
| auto selected = sideCtrl.listWidgetReferences->selectedItems(); | |
| auto faces = getShapeFaces(*sideCtrl.UpToShape); | |
| faces.erase(std::remove_if(faces.begin(), faces.end(), [selected](const std::string& face) { | |
| for (auto& item : selected) { | |
| if (item->text().toStdString() == face) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| })); | |
| sideCtrl.UpToShape->setValue(sideCtrl.UpToShape->getValue(), faces); | |
| updateShapeFaces(sideCtrl.listWidgetReferences, *sideCtrl.UpToShape); | |
| } | |
| void TaskExtrudeParameters::setSelectionMode(SelectionMode mode, Side side) | |
| { | |
| if (selectionMode == mode && activeSelectionSide == side) { | |
| return; | |
| } | |
| const auto updateCheckedForSide = [mode, side]( | |
| Side relatedSide, | |
| QAbstractButton* buttonFace, | |
| QAbstractButton* buttonShape, | |
| QAbstractButton* buttonShapeFace | |
| ) { | |
| buttonFace->setChecked(mode == SelectFace && side == relatedSide); | |
| buttonShape->setChecked(mode == SelectShape && side == relatedSide); | |
| buttonShapeFace->setChecked(mode == SelectShapeFaces && side == relatedSide); | |
| }; | |
| updateCheckedForSide(Side::First, ui->buttonFace, ui->buttonShape, ui->buttonShapeFace); | |
| updateCheckedForSide(Side::Second, ui->buttonFace2, ui->buttonShape2, ui->buttonShapeFace2); | |
| selectionMode = mode; | |
| activeSelectionSide = side; | |
| switch (mode) { | |
| case SelectShape: | |
| onSelectReference(AllowSelection::WHOLE); | |
| Gui::Selection().addSelectionGate(new SelectionFilterGate("SELECT Part::Feature COUNT 1")); | |
| break; | |
| case SelectFace: | |
| onSelectReference(AllowSelection::FACE); | |
| break; | |
| case SelectShapeFaces: { | |
| onSelectReference(AllowSelection::FACE); | |
| auto& sideCtrl = getSideController(activeSelectionSide); | |
| getViewObject<ViewProviderExtrude>()->highlightShapeFaces( | |
| getShapeFaces(*sideCtrl.UpToShape) | |
| ); | |
| break; | |
| } | |
| case SelectReferenceAxis: | |
| onSelectReference(AllowSelection::EDGE | AllowSelection::PLANAR | AllowSelection::CIRCLE); | |
| break; | |
| default: | |
| getViewObject<ViewProviderExtrude>()->highlightShapeFaces({}); | |
| onSelectReference(AllowSelection::NONE); | |
| } | |
| } | |
| void TaskExtrudeParameters::tryRecomputeFeature() | |
| { | |
| try { | |
| // recompute and update the direction | |
| recomputeFeature(); | |
| } | |
| catch (const Base::Exception& e) { | |
| e.reportException(); | |
| } | |
| } | |
| void TaskExtrudeParameters::onSelectionChanged(const Gui::SelectionChanges& msg) | |
| { | |
| auto& sideCtrl = getSideController(activeSelectionSide); | |
| if (msg.Type == Gui::SelectionChanges::AddSelection) { | |
| switch (selectionMode) { | |
| case SelectShape: | |
| selectedShape(msg, sideCtrl); | |
| break; | |
| case SelectShapeFaces: | |
| selectedShapeFace(msg, sideCtrl); | |
| break; | |
| case SelectFace: | |
| selectedFace(msg, sideCtrl); | |
| break; | |
| case SelectReferenceAxis: | |
| selectedReferenceAxis(msg); | |
| break; | |
| default: | |
| // no-op | |
| break; | |
| } | |
| } | |
| else if (msg.Type == Gui::SelectionChanges::ClrSelection) { | |
| if (selectionMode == SelectFace) { | |
| clearFaceName(sideCtrl.lineFaceName); | |
| } | |
| } | |
| } | |
| void TaskExtrudeParameters::selectedReferenceAxis(const Gui::SelectionChanges& msg) | |
| { | |
| std::vector<std::string> edge; | |
| App::DocumentObject* selObj; | |
| if (getReferencedSelection(getObject(), msg, selObj, edge) && selObj) { | |
| setSelectionMode(None); | |
| propReferenceAxis->setValue(selObj, edge); | |
| tryRecomputeFeature(); | |
| // update direction combobox | |
| fillDirectionCombo(); | |
| setGizmoPositions(); | |
| } | |
| } | |
| void TaskExtrudeParameters::selectedShapeFace(const Gui::SelectionChanges& msg, SideController& side) | |
| { | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| auto document = extrude->getDocument(); | |
| if (strcmp(msg.pDocName, document->getName()) != 0) { | |
| return; | |
| } | |
| // Get the base shape from the correct side's property | |
| auto base = static_cast<Part::Feature*>(side.UpToShape->getValue()); | |
| if (!base) { | |
| base = static_cast<Part::Feature*>(extrude); | |
| } | |
| else if (strcmp(msg.pObjectName, base->getNameInDocument()) != 0) { | |
| return; | |
| } | |
| std::vector<std::string> faces = getShapeFaces(*side.UpToShape); | |
| const std::string subName(msg.pSubName); | |
| if (subName.empty()) { | |
| return; | |
| } | |
| // Add or remove the face from the list | |
| if (const auto positionInList = std::ranges::find(faces, subName); positionInList != faces.end()) { | |
| faces.erase(positionInList); // Remove it if it exists | |
| } | |
| else { | |
| faces.push_back(subName); // Add it if it's new | |
| } | |
| side.UpToShape->setValue(base, faces); | |
| updateShapeFaces(side.listWidgetReferences, *side.UpToShape); | |
| tryRecomputeFeature(); | |
| } | |
| void PartDesignGui::TaskExtrudeParameters::selectedFace( | |
| const Gui::SelectionChanges& msg, | |
| SideController& side | |
| ) | |
| { | |
| QString refText = onAddSelection(msg, *side.UpToFace); | |
| if (refText.length() > 0) { | |
| QSignalBlocker block(side.lineFaceName); | |
| side.lineFaceName->setText(refText); | |
| side.lineFaceName->setProperty("FeatureName", QByteArray(msg.pObjectName)); | |
| side.lineFaceName->setProperty("FaceName", QByteArray(msg.pSubName)); | |
| // Turn off reference selection mode | |
| side.buttonFace->setChecked(false); | |
| } | |
| else { | |
| clearFaceName(side.lineFaceName); | |
| } | |
| setSelectionMode(None); | |
| } | |
| void PartDesignGui::TaskExtrudeParameters::selectedShape( | |
| const Gui::SelectionChanges& msg, | |
| SideController& side | |
| ) | |
| { | |
| auto document = getObject()->getDocument(); | |
| if (strcmp(msg.pDocName, document->getName()) != 0) { | |
| return; | |
| } | |
| Gui::Selection().clearSelection(); | |
| auto ref = document->getObject(msg.pObjectName); | |
| side.UpToShape->setValue(ref); | |
| side.checkBoxAllFaces->setChecked(true); | |
| setSelectionMode(None); | |
| updateShapeName(side.lineShapeName, *side.UpToShape); | |
| updateShapeFaces(side.listWidgetReferences, *side.UpToShape); | |
| tryRecomputeFeature(); | |
| } | |
| void TaskExtrudeParameters::clearFaceName(QLineEdit* lineEdit) | |
| { | |
| QSignalBlocker block(lineEdit); | |
| lineEdit->clear(); | |
| lineEdit->setProperty("FeatureName", QVariant()); | |
| lineEdit->setProperty("FaceName", QVariant()); | |
| } | |
| void TaskExtrudeParameters::updateShapeName(QLineEdit* lineEdit, App::PropertyLinkSubList& prop) | |
| { | |
| QSignalBlocker block(lineEdit); | |
| auto shape = prop.getValue(); | |
| if (shape) { | |
| lineEdit->setText(QString::fromStdString(shape->getFullName())); | |
| } | |
| else { | |
| lineEdit->setText({}); | |
| lineEdit->setPlaceholderText(tr("No shape selected")); | |
| } | |
| } | |
| void TaskExtrudeParameters::updateShapeFaces(QListWidget* list, App::PropertyLinkSubList& prop) | |
| { | |
| auto faces = getShapeFaces(prop); | |
| list->clear(); | |
| for (auto& ref : faces) { | |
| list->addItem(QString::fromStdString(ref)); | |
| } | |
| if (selectionMode == SelectShapeFaces) { | |
| getViewObject<ViewProviderExtrude>()->highlightShapeFaces(faces); | |
| } | |
| } | |
| std::vector<std::string> PartDesignGui::TaskExtrudeParameters::getShapeFaces( | |
| App::PropertyLinkSubList& prop | |
| ) | |
| { | |
| std::vector<std::string> faces; | |
| auto allRefs = prop.getSubValues(); | |
| std::copy_if(allRefs.begin(), allRefs.end(), std::back_inserter(faces), [](const std::string& ref) { | |
| return boost::starts_with(ref, "Face"); | |
| }); | |
| return faces; | |
| } | |
| void TaskExtrudeParameters::onLengthChanged(double len, Side side) | |
| { | |
| getSideController(side).Length->setValue(len); | |
| tryRecomputeFeature(); | |
| } | |
| void TaskExtrudeParameters::onOffsetChanged(double len, Side side) | |
| { | |
| getSideController(side).Offset->setValue(len); | |
| tryRecomputeFeature(); | |
| } | |
| void TaskExtrudeParameters::onTaperChanged(double angle, Side side) | |
| { | |
| getSideController(side).TaperAngle->setValue(angle); | |
| tryRecomputeFeature(); | |
| } | |
| bool TaskExtrudeParameters::hasProfileFace(PartDesign::ProfileBased* profile) const | |
| { | |
| try { | |
| Part::Feature* pcFeature = profile->getVerifiedObject(); | |
| Base::Vector3d SketchVector = profile->getProfileNormal(); | |
| Q_UNUSED(pcFeature) | |
| Q_UNUSED(SketchVector) | |
| return true; | |
| } | |
| catch (const Base::Exception&) { | |
| } | |
| return false; | |
| } | |
| void TaskExtrudeParameters::fillDirectionCombo() | |
| { | |
| Base::StateLocker lock(getUpdateBlockRef(), true); | |
| if (axesInList.empty()) { | |
| bool hasFace = false; | |
| ui->directionCB->clear(); | |
| // we can have sketches or faces | |
| // for sketches just get the sketch normal | |
| auto pcFeat = getObject<PartDesign::ProfileBased>(); | |
| Part::Part2DObject* pcSketch = dynamic_cast<Part::Part2DObject*>(pcFeat->Profile.getValue()); | |
| // for faces we test if it is verified and if we can get its normal | |
| if (!pcSketch) { | |
| hasFace = hasProfileFace(pcFeat); | |
| } | |
| if (pcSketch) { | |
| addAxisToCombo(pcSketch, "N_Axis", tr("Sketch normal")); | |
| } | |
| else if (hasFace) { | |
| addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Face normal"), false); | |
| } | |
| // add the other entries | |
| addAxisToCombo(nullptr, std::string(), tr("Select reference…")); | |
| // we start with the sketch normal as proposal for the custom direction | |
| if (pcSketch) { | |
| addAxisToCombo(pcSketch, "N_Axis", tr("Custom direction")); | |
| } | |
| else if (hasFace) { | |
| addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Custom direction"), false); | |
| } | |
| } | |
| // add current link, if not in list | |
| // first, figure out the item number for current axis | |
| int indexOfCurrent = -1; | |
| App::DocumentObject* ax = propReferenceAxis->getValue(); | |
| const std::vector<std::string>& subList = propReferenceAxis->getSubValues(); | |
| for (size_t i = 0; i < axesInList.size(); i++) { | |
| if (ax == axesInList[i]->getValue() && subList == axesInList[i]->getSubValues()) { | |
| indexOfCurrent = i; | |
| break; | |
| } | |
| } | |
| // if the axis is not yet listed in the combobox | |
| if (indexOfCurrent == -1 && ax) { | |
| assert(subList.size() <= 1); | |
| std::string sub; | |
| if (!subList.empty()) { | |
| sub = subList[0]; | |
| } | |
| addAxisToCombo(ax, sub, getRefStr(ax, subList)); | |
| indexOfCurrent = axesInList.size() - 1; | |
| // the axis is not the normal, thus enable along direction | |
| ui->checkBoxAlongDirection->setEnabled(true); | |
| // we don't have custom direction thus disable its settings | |
| ui->XDirectionEdit->setEnabled(false); | |
| ui->YDirectionEdit->setEnabled(false); | |
| ui->ZDirectionEdit->setEnabled(false); | |
| } | |
| // highlight either current index or set custom direction | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| bool hasCustom = extrude->UseCustomVector.getValue(); | |
| if (indexOfCurrent != -1 && !hasCustom) { | |
| ui->directionCB->setCurrentIndex(indexOfCurrent); | |
| updateDirectionEdits(); | |
| setDirectionMode(indexOfCurrent); | |
| } | |
| if (hasCustom) { | |
| ui->directionCB->setCurrentIndex(DirectionModes::Custom); | |
| setDirectionMode(ui->directionCB->currentIndex()); | |
| } | |
| } | |
| void TaskExtrudeParameters::addAxisToCombo( | |
| App::DocumentObject* linkObj, | |
| std::string linkSubname, | |
| QString itemText, | |
| bool hasSketch | |
| ) | |
| { | |
| this->ui->directionCB->addItem(itemText); | |
| this->axesInList.emplace_back(new App::PropertyLinkSub); | |
| App::PropertyLinkSub& lnk = *(axesInList.back()); | |
| // if we have a face, we leave the link empty since we cannot | |
| // store the face normal as sublink | |
| if (hasSketch) { | |
| lnk.setValue(linkObj, std::vector<std::string>(1, linkSubname)); | |
| } | |
| } | |
| void TaskExtrudeParameters::updateWholeUI(Type type, Side side) | |
| { | |
| SidesMode sidesMode = static_cast<SidesMode>(ui->sidesMode->currentIndex()); | |
| Mode mode1 = static_cast<Mode>(ui->changeMode->currentIndex()); | |
| Mode mode2 = static_cast<Mode>(ui->changeMode2->currentIndex()); | |
| // --- Global UI visibility based on SidesMode --- | |
| const bool isSide2GroupVisible = (sidesMode == SidesMode::TwoSides); | |
| ui->side1Label->setVisible(isSide2GroupVisible); | |
| ui->side2Label->setVisible(isSide2GroupVisible); | |
| ui->line2->setVisible(isSide2GroupVisible); | |
| ui->typeLabel2->setVisible(isSide2GroupVisible); | |
| ui->changeMode2->setVisible(isSide2GroupVisible); | |
| // --- Configure each side using the helper method --- | |
| // Side 1 is always conceptually visible, and we pass whether it should receive focus. | |
| updateSideUI(m_side1, type, mode1, true, (side == Side::First)); | |
| // Side 2 is only visible if in TwoSides mode, and we pass whether it should receive focus. | |
| updateSideUI(m_side2, type, mode2, isSide2GroupVisible, (side == Side::Second)); | |
| ui->checkBoxReversed->setEnabled(sidesMode != SidesMode::Symmetric || mode1 != Mode::Dimension); | |
| } | |
| void TaskExtrudeParameters::updateSideUI( | |
| const SideController& s, | |
| Type featureType, | |
| Mode sideMode, | |
| bool isParentVisible, | |
| bool setFocus | |
| ) | |
| { | |
| // Default states for all controls for this side | |
| bool isLengthVisible = false; | |
| bool isOffsetVisible = false; | |
| bool isTaperVisible = false; | |
| bool isFaceVisible = false; | |
| bool isShapeVisible = false; | |
| // This logic block is a direct translation of the original 'if/else if' chain | |
| if (sideMode == Mode::Dimension) { | |
| isLengthVisible = true; | |
| isTaperVisible = true; | |
| if (setFocus) { | |
| s.lengthEdit->selectNumber(); | |
| QMetaObject::invokeMethod(s.lengthEdit, "setFocus", Qt::QueuedConnection); | |
| } | |
| } | |
| else if (sideMode == Mode::ThroughAll && featureType == Type::Pocket) { | |
| isTaperVisible = true; | |
| } | |
| else if (sideMode == Mode::ToLast && featureType == Type::Pad) { | |
| isOffsetVisible = true; | |
| } | |
| else if (sideMode == Mode::ToFirst) { | |
| isOffsetVisible = true; | |
| } | |
| else if (sideMode == Mode::ToFace) { | |
| isOffsetVisible = true; | |
| isFaceVisible = true; | |
| if (setFocus) { | |
| QMetaObject::invokeMethod(s.lineFaceName, "setFocus", Qt::QueuedConnection); | |
| // Go into reference selection mode if no face has been selected yet | |
| if (s.lineFaceName->property("FeatureName").isNull()) { | |
| s.buttonFace->setChecked(true); | |
| } | |
| } | |
| } | |
| else if (sideMode == Mode::ToShape) { | |
| isShapeVisible = true; | |
| if (setFocus) { | |
| if (!s.checkBoxAllFaces->isChecked()) { | |
| s.buttonShapeFace->setChecked(true); | |
| } | |
| } | |
| } | |
| // Apply visibility based on the logic above AND the parent visibility. | |
| // This single 'isParentVisible' check correctly hides all of Side 2's UI at once. | |
| const bool finalLengthVisible = isParentVisible && isLengthVisible; | |
| s.labelLength->setVisible(finalLengthVisible); | |
| s.lengthEdit->setVisible(finalLengthVisible); | |
| s.lengthEdit->setEnabled(finalLengthVisible); | |
| const bool finalOffsetVisible = isParentVisible && isOffsetVisible; | |
| s.labelOffset->setVisible(finalOffsetVisible); | |
| s.offsetEdit->setVisible(finalOffsetVisible); | |
| s.offsetEdit->setEnabled(finalOffsetVisible); | |
| const bool finalTaperVisible = isParentVisible && isTaperVisible; | |
| s.labelTaperAngle->setVisible(finalTaperVisible); | |
| s.taperEdit->setVisible(finalTaperVisible); | |
| s.taperEdit->setEnabled(finalTaperVisible); | |
| s.buttonFace->setVisible(isParentVisible && isFaceVisible); | |
| s.lineFaceName->setVisible(isParentVisible && isFaceVisible); | |
| if (!isFaceVisible) { | |
| s.buttonFace->setChecked(false); // Ensure button is unchecked when hidden | |
| } | |
| s.upToShapeList->setVisible(isParentVisible && isShapeVisible); | |
| } | |
| void TaskExtrudeParameters::onDirectionCBChanged(int num) | |
| { | |
| if (axesInList.empty()) { | |
| return; | |
| } | |
| // we use this scheme for 'num' | |
| // 0: normal to sketch or face | |
| // 1: selection mode | |
| // 2: custom | |
| // 3-x: edges selected in the 3D model | |
| // check the axis | |
| // when the link is empty we are either in selection mode | |
| // or we are normal to a face | |
| App::PropertyLinkSub& lnk = *(axesInList[num]); | |
| if (num == DirectionModes::Select) { | |
| // to distinguish that this is the direction selection | |
| setSelectionMode(SelectReferenceAxis); | |
| setDirectionMode(num); | |
| } | |
| else if (auto extrude = getObject<PartDesign::FeatureExtrude>()) { | |
| if (lnk.getValue()) { | |
| if (!extrude->getDocument()->isIn(lnk.getValue())) { | |
| Base::Console().error("Object was deleted\n"); | |
| return; | |
| } | |
| propReferenceAxis->Paste(lnk); | |
| } | |
| // in case the user is in selection mode, but changed his mind before selecting anything | |
| setSelectionMode(None); | |
| setDirectionMode(num); | |
| extrude->ReferenceAxis.setValue(lnk.getValue(), lnk.getSubValues()); | |
| tryRecomputeFeature(); | |
| updateDirectionEdits(); | |
| setGizmoPositions(); | |
| } | |
| } | |
| void TaskExtrudeParameters::onAlongSketchNormalChanged(bool on) | |
| { | |
| if (auto extrude = getObject<PartDesign::FeatureExtrude>()) { | |
| extrude->AlongSketchNormal.setValue(on); | |
| tryRecomputeFeature(); | |
| setGizmoPositions(); | |
| } | |
| } | |
| void TaskExtrudeParameters::onAllFacesToggled(bool on, Side side) | |
| { | |
| auto& sideCtrl = getSideController(side); | |
| sideCtrl.upToShapeFaces->setVisible(!on); | |
| sideCtrl.buttonShapeFace->setChecked(false); | |
| if (on) { | |
| if (auto extrude = getObject<PartDesign::FeatureExtrude>()) { | |
| extrude->UpToShape.setValue(extrude->UpToShape.getValue()); | |
| updateShapeFaces(sideCtrl.listWidgetReferences, *sideCtrl.UpToShape); | |
| tryRecomputeFeature(); | |
| } | |
| } | |
| } | |
| void TaskExtrudeParameters::onXDirectionEditChanged(double len) | |
| { | |
| if (auto extrude = getObject<PartDesign::FeatureExtrude>()) { | |
| extrude->Direction | |
| .setValue(len, extrude->Direction.getValue().y, extrude->Direction.getValue().z); | |
| tryRecomputeFeature(); | |
| // checking for case of a null vector is done in FeatureExtrude.cpp | |
| // if there was a null vector, the normal vector of the sketch is used. | |
| // therefore the vector component edits must be updated | |
| updateDirectionEdits(); | |
| setGizmoPositions(); | |
| } | |
| } | |
| void TaskExtrudeParameters::onYDirectionEditChanged(double len) | |
| { | |
| if (auto extrude = getObject<PartDesign::FeatureExtrude>()) { | |
| extrude->Direction | |
| .setValue(extrude->Direction.getValue().x, len, extrude->Direction.getValue().z); | |
| tryRecomputeFeature(); | |
| updateDirectionEdits(); | |
| setGizmoPositions(); | |
| } | |
| } | |
| void TaskExtrudeParameters::onZDirectionEditChanged(double len) | |
| { | |
| if (auto extrude = getObject<PartDesign::FeatureExtrude>()) { | |
| extrude->Direction | |
| .setValue(extrude->Direction.getValue().x, extrude->Direction.getValue().y, len); | |
| tryRecomputeFeature(); | |
| updateDirectionEdits(); | |
| setGizmoPositions(); | |
| } | |
| } | |
| void TaskExtrudeParameters::updateDirectionEdits() | |
| { | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| // we don't want to execute the onChanged edits, but just update their contents | |
| QSignalBlocker xdir(ui->XDirectionEdit); | |
| QSignalBlocker ydir(ui->YDirectionEdit); | |
| QSignalBlocker zdir(ui->ZDirectionEdit); | |
| ui->XDirectionEdit->setValue(extrude->Direction.getValue().x); | |
| ui->YDirectionEdit->setValue(extrude->Direction.getValue().y); | |
| ui->ZDirectionEdit->setValue(extrude->Direction.getValue().z); | |
| } | |
| void TaskExtrudeParameters::setDirectionMode(int index) | |
| { | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| if (!extrude) { | |
| return; | |
| } | |
| switch (index) { | |
| case DirectionModes::Normal: | |
| ui->groupBoxDirection->hide(); | |
| extrude->UseCustomVector.setValue(false); | |
| ui->XDirectionEdit->setEnabled(false); | |
| ui->YDirectionEdit->setEnabled(false); | |
| ui->ZDirectionEdit->setEnabled(false); | |
| ui->checkBoxAlongDirection->setEnabled(false); | |
| ui->checkBoxAlongDirection->hide(); | |
| break; | |
| // Covered by the default option | |
| // case DirectionModes::Select: | |
| case DirectionModes::Custom: | |
| ui->groupBoxDirection->show(); | |
| extrude->UseCustomVector.setValue(true); | |
| ui->XDirectionEdit->setEnabled(true); | |
| ui->YDirectionEdit->setEnabled(true); | |
| ui->ZDirectionEdit->setEnabled(true); | |
| ui->checkBoxAlongDirection->setEnabled(true); | |
| ui->checkBoxAlongDirection->show(); | |
| break; | |
| default: | |
| ui->groupBoxDirection->show(); | |
| updateDirectionEdits(); | |
| extrude->UseCustomVector.setValue(false); | |
| ui->XDirectionEdit->setEnabled(false); | |
| ui->YDirectionEdit->setEnabled(false); | |
| ui->ZDirectionEdit->setEnabled(false); | |
| ui->checkBoxAlongDirection->setEnabled(true); | |
| ui->checkBoxAlongDirection->show(); | |
| break; | |
| } | |
| } | |
| void TaskExtrudeParameters::onReversedChanged(bool on) | |
| { | |
| if (auto extrude = getObject<PartDesign::FeatureExtrude>()) { | |
| extrude->Reversed.setValue(on); | |
| // update the direction | |
| tryRecomputeFeature(); | |
| updateDirectionEdits(); | |
| setGizmoPositions(); | |
| } | |
| } | |
| void TaskExtrudeParameters::getReferenceAxis(App::DocumentObject*& obj, std::vector<std::string>& sub) const | |
| { | |
| if (axesInList.empty()) { | |
| throw Base::RuntimeError("Not initialized!"); | |
| } | |
| int num = ui->directionCB->currentIndex(); | |
| const App::PropertyLinkSub& lnk = *(axesInList[num]); | |
| if (!lnk.getValue()) { | |
| // Note: It is possible that a face of an object is directly padded/pocketed without | |
| // defining a profile shape | |
| obj = nullptr; | |
| sub.clear(); | |
| } | |
| else { | |
| auto pcDirection = getObject<PartDesign::ProfileBased>(); | |
| if (!pcDirection->getDocument()->isIn(lnk.getValue())) { | |
| throw Base::RuntimeError("Object was deleted"); | |
| } | |
| obj = lnk.getValue(); | |
| sub = lnk.getSubValues(); | |
| } | |
| } | |
| void TaskExtrudeParameters::onSelectFaceToggle(const bool checked, Side side) | |
| { | |
| auto& sideCtrl = getSideController(side); | |
| if (checked) { | |
| handleLineFaceNameClick(sideCtrl.lineFaceName); | |
| setSelectionMode(SelectFace, side); | |
| } | |
| else { | |
| handleLineFaceNameNo(sideCtrl.lineFaceName); | |
| } | |
| } | |
| void TaskExtrudeParameters::onSelectShapeToggle(bool checked, Side side) | |
| { | |
| auto& sideCtrl = getSideController(side); | |
| if (checked) { | |
| setSelectionMode(SelectShape, side); | |
| sideCtrl.lineShapeName->setText({}); | |
| sideCtrl.lineShapeName->setPlaceholderText(tr("Click on a shape in the model")); | |
| } | |
| else { | |
| setSelectionMode(None); | |
| updateShapeName(sideCtrl.lineShapeName, *sideCtrl.UpToShape); | |
| } | |
| } | |
| void TaskExtrudeParameters::onFaceName(const QString& text, Side side) | |
| { | |
| auto& sideCtrl = getSideController(side); | |
| changeFaceName(sideCtrl.lineFaceName, text); | |
| } | |
| void TaskExtrudeParameters::changeFaceName(QLineEdit* lineEdit, const QString& text) | |
| { | |
| if (text.isEmpty()) { | |
| // if user cleared the text field then also clear the properties | |
| lineEdit->setProperty("FeatureName", QVariant()); | |
| lineEdit->setProperty("FaceName", QVariant()); | |
| } | |
| else { | |
| // expect that the label of an object is used | |
| QStringList parts = text.split(QChar::fromLatin1(':')); | |
| QString label = parts[0]; | |
| QVariant name = objectNameByLabel(label, lineEdit->property("FeatureName")); | |
| if (name.isValid()) { | |
| parts[0] = name.toString(); | |
| QString uptoface = parts.join(QStringLiteral(":")); | |
| lineEdit->setProperty("FeatureName", name); | |
| lineEdit->setProperty("FaceName", setUpToFace(uptoface)); | |
| } | |
| else { | |
| lineEdit->setProperty("FeatureName", QVariant()); | |
| lineEdit->setProperty("FaceName", QVariant()); | |
| } | |
| } | |
| } | |
| void TaskExtrudeParameters::translateFaceName(QLineEdit* lineEdit) | |
| { | |
| handleLineFaceNameNo(lineEdit); | |
| QVariant featureName = lineEdit->property("FeatureName"); | |
| if (featureName.isValid()) { | |
| QStringList parts = lineEdit->text().split(QChar::fromLatin1(':')); | |
| QByteArray upToFace = lineEdit->property("FaceName").toByteArray(); | |
| int faceId = -1; | |
| bool ok = false; | |
| if (upToFace.indexOf("Face") == 0) { | |
| faceId = upToFace.remove(0, 4).toInt(&ok); | |
| } | |
| if (ok) { | |
| lineEdit->setText(QStringLiteral("%1:%2%3").arg(parts[0], tr("Face")).arg(faceId)); | |
| } | |
| else { | |
| lineEdit->setText(parts[0]); | |
| } | |
| } | |
| } | |
| double TaskExtrudeParameters::getOffset() const | |
| { | |
| return ui->offsetEdit->value().getValue(); | |
| } | |
| double TaskExtrudeParameters::getOffset2() const | |
| { | |
| return ui->offsetEdit2->value().getValue(); | |
| } | |
| bool TaskExtrudeParameters::getAlongSketchNormal() const | |
| { | |
| return ui->checkBoxAlongDirection->isChecked(); | |
| } | |
| bool TaskExtrudeParameters::getCustom() const | |
| { | |
| return (ui->directionCB->currentIndex() == DirectionModes::Custom); | |
| } | |
| std::string TaskExtrudeParameters::getReferenceAxis() const | |
| { | |
| std::vector<std::string> sub; | |
| App::DocumentObject* obj; | |
| getReferenceAxis(obj, sub); | |
| return buildLinkSingleSubPythonStr(obj, sub); | |
| } | |
| double TaskExtrudeParameters::getXDirection() const | |
| { | |
| return ui->XDirectionEdit->value(); | |
| } | |
| double TaskExtrudeParameters::getYDirection() const | |
| { | |
| return ui->YDirectionEdit->value(); | |
| } | |
| double TaskExtrudeParameters::getZDirection() const | |
| { | |
| return ui->ZDirectionEdit->value(); | |
| } | |
| bool TaskExtrudeParameters::getReversed() const | |
| { | |
| return ui->checkBoxReversed->isChecked(); | |
| } | |
| int TaskExtrudeParameters::getMode() const | |
| { | |
| return ui->changeMode->currentIndex(); | |
| } | |
| int TaskExtrudeParameters::getMode2() const | |
| { | |
| return ui->changeMode2->currentIndex(); | |
| } | |
| int TaskExtrudeParameters::getSidesMode() const | |
| { | |
| return ui->sidesMode->currentIndex(); | |
| } | |
| QString TaskExtrudeParameters::getFaceName(QLineEdit* lineEdit) const | |
| { | |
| QVariant featureName = lineEdit->property("FeatureName"); | |
| if (featureName.isValid()) { | |
| QString faceName = lineEdit->property("FaceName").toString(); | |
| return getFaceReference(featureName.toString(), faceName); | |
| } | |
| return QStringLiteral("None"); | |
| } | |
| void TaskExtrudeParameters::changeEvent(QEvent* e) | |
| { | |
| TaskBox::changeEvent(e); | |
| if (e->type() == QEvent::LanguageChange) { | |
| QSignalBlocker length(ui->lengthEdit); | |
| QSignalBlocker length2(ui->lengthEdit2); | |
| QSignalBlocker offset(ui->offsetEdit); | |
| QSignalBlocker offset2(ui->offsetEdit2); | |
| QSignalBlocker taper(ui->taperEdit); | |
| QSignalBlocker taper2(ui->taperEdit2); | |
| QSignalBlocker xdir(ui->XDirectionEdit); | |
| QSignalBlocker ydir(ui->YDirectionEdit); | |
| QSignalBlocker zdir(ui->ZDirectionEdit); | |
| QSignalBlocker dir(ui->directionCB); | |
| QSignalBlocker face(ui->lineFaceName); | |
| QSignalBlocker face2(ui->lineFaceName2); | |
| QSignalBlocker mode(ui->changeMode); | |
| QSignalBlocker mode2(ui->changeMode2); | |
| QSignalBlocker sidesMode(ui->sidesMode); | |
| // Save all items | |
| QStringList items; | |
| for (int i = 0; i < ui->directionCB->count(); i++) { | |
| items << ui->directionCB->itemText(i); | |
| } | |
| // Translate direction items | |
| int index = ui->directionCB->currentIndex(); | |
| ui->retranslateUi(proxy); | |
| // Keep custom items | |
| for (int i = 0; i < ui->directionCB->count(); i++) { | |
| items.pop_front(); | |
| } | |
| ui->directionCB->addItems(items); | |
| ui->directionCB->setCurrentIndex(index); | |
| // Translate mode items | |
| translateModeList(ui->changeMode, ui->changeMode->currentIndex()); | |
| translateModeList(ui->changeMode2, ui->changeMode2->currentIndex()); | |
| translateSidesList(ui->sidesMode->currentIndex()); | |
| translateFaceName(ui->lineFaceName); | |
| translateFaceName(ui->lineFaceName2); | |
| } | |
| } | |
| void TaskExtrudeParameters::saveHistory() | |
| { | |
| // save the user values to history | |
| ui->lengthEdit->pushToHistory(); | |
| ui->lengthEdit2->pushToHistory(); | |
| ui->offsetEdit->pushToHistory(); | |
| ui->offsetEdit2->pushToHistory(); | |
| ui->taperEdit->pushToHistory(); | |
| ui->taperEdit2->pushToHistory(); | |
| } | |
| void TaskExtrudeParameters::applyParameters() | |
| { | |
| auto obj = getObject(); | |
| QString facename = QStringLiteral("None"); | |
| QString facename2 = QStringLiteral("None"); | |
| if (static_cast<Mode>(getMode()) == Mode::ToFace) { | |
| facename = getFaceName(ui->lineFaceName); | |
| } | |
| if (static_cast<Mode>(getMode2()) == Mode::ToFace) { | |
| facename2 = getFaceName(ui->lineFaceName2); | |
| } | |
| // Handle deprecated 'TwoLength' mode. | |
| int type1 = getMode(); | |
| if (static_cast<Mode>(type1) == Mode::ToShape) { | |
| type1++; | |
| } | |
| int type2 = getMode2(); | |
| if (static_cast<Mode>(type2) == Mode::ToShape) { | |
| type2++; | |
| } | |
| ui->lengthEdit->apply(); | |
| ui->lengthEdit2->apply(); | |
| ui->taperEdit->apply(); | |
| ui->taperEdit2->apply(); | |
| FCMD_OBJ_CMD(obj, "UseCustomVector = " << (getCustom() ? 1 : 0)); | |
| FCMD_OBJ_CMD( | |
| obj, | |
| "Direction = (" << getXDirection() << ", " << getYDirection() << ", " << getZDirection() << ")" | |
| ); | |
| FCMD_OBJ_CMD(obj, "ReferenceAxis = " << getReferenceAxis()); | |
| FCMD_OBJ_CMD(obj, "AlongSketchNormal = " << (getAlongSketchNormal() ? 1 : 0)); | |
| FCMD_OBJ_CMD(obj, "SideType = " << getSidesMode()); | |
| FCMD_OBJ_CMD(obj, "Type = " << type1); | |
| FCMD_OBJ_CMD(obj, "Type2 = " << type2); | |
| FCMD_OBJ_CMD(obj, "UpToFace = " << facename.toUtf8().data()); | |
| FCMD_OBJ_CMD(obj, "UpToFace2 = " << facename2.toUtf8().data()); | |
| FCMD_OBJ_CMD(obj, "Reversed = " << (getReversed() ? 1 : 0)); | |
| FCMD_OBJ_CMD(obj, "Offset = " << getOffset()); | |
| FCMD_OBJ_CMD(obj, "Offset2 = " << getOffset2()); | |
| } | |
| void TaskExtrudeParameters::onSidesModeChanged(int index) | |
| { | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| switch (static_cast<SidesMode>(index)) { | |
| case SidesMode::OneSide: | |
| extrude->SideType.setValue("One side"); | |
| updateUI(Side::First); | |
| break; | |
| case SidesMode::TwoSides: | |
| extrude->SideType.setValue("Two sides"); | |
| updateUI(Side::Second); | |
| break; | |
| case SidesMode::Symmetric: | |
| extrude->SideType.setValue("Symmetric"); | |
| updateUI(Side::First); | |
| break; | |
| } | |
| recomputeFeature(); | |
| } | |
| void TaskExtrudeParameters::updateUI(Side) | |
| { | |
| // implement in sub-class | |
| } | |
| void TaskExtrudeParameters::translateModeList(QComboBox*, int) | |
| { | |
| // implement in sub-class | |
| } | |
| void TaskExtrudeParameters::translateSidesList(int index) | |
| { | |
| ui->sidesMode->clear(); | |
| ui->sidesMode->addItem(tr("One sided")); | |
| ui->sidesMode->addItem(tr("Two sided")); | |
| ui->sidesMode->addItem(tr("Symmetric")); | |
| ui->sidesMode->setCurrentIndex(index); | |
| } | |
| void TaskExtrudeParameters::handleLineFaceNameClick(QLineEdit* lineEdit) | |
| { | |
| lineEdit->setPlaceholderText(tr("Click on a face in the model")); | |
| } | |
| void TaskExtrudeParameters::handleLineFaceNameNo(QLineEdit* lineEdit) | |
| { | |
| lineEdit->setPlaceholderText(tr("No face selected")); | |
| } | |
| void TaskExtrudeParameters::setupGizmos() | |
| { | |
| if (GizmoContainer::isEnabled() == false) { | |
| return; | |
| } | |
| lengthGizmo1 = new Gui::LinearGizmo(ui->lengthEdit); | |
| lengthGizmo2 = new Gui::LinearGizmo(ui->lengthEdit2); | |
| taperAngleGizmo1 = new Gui::RotationGizmo(ui->taperEdit); | |
| taperAngleGizmo2 = new Gui::RotationGizmo(ui->taperEdit2); | |
| connect(ui->sidesMode, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { | |
| setGizmoPositions(); | |
| }); | |
| gizmoContainer = GizmoContainer::create( | |
| {lengthGizmo1, lengthGizmo2, taperAngleGizmo1, taperAngleGizmo2}, | |
| vp | |
| ); | |
| setGizmoPositions(); | |
| } | |
| void TaskExtrudeParameters::setGizmoPositions() | |
| { | |
| if (!gizmoContainer) { | |
| return; | |
| } | |
| auto extrude = getObject<PartDesign::FeatureExtrude>(); | |
| if (!extrude || extrude->isError()) { | |
| gizmoContainer->visible = false; | |
| return; | |
| } | |
| gizmoContainer->visible = true; | |
| PartDesign::TopoShape shape = extrude->getProfileShape(); | |
| Base::Vector3d center = getMidPointFromProfile(shape); | |
| std::string sideType = std::string(extrude->SideType.getValueAsString()); | |
| std::string extrudeType = std::string(extrude->Type.getValueAsString()); | |
| std::string extrudeType2 = std::string(extrude->Type2.getValueAsString()); | |
| double dir = extrude->Reversed.getValue() ? -1 : 1; | |
| lengthGizmo1->Gizmo::setDraggerPlacement(center, extrude->Direction.getValue() * dir); | |
| lengthGizmo1->setVisibility(extrudeType == "Length"); | |
| taperAngleGizmo1->placeOverLinearGizmo(lengthGizmo1); | |
| taperAngleGizmo1->setVisibility(extrudeType == "Length"); | |
| lengthGizmo2->Gizmo::setDraggerPlacement(center, -extrude->Direction.getValue() * dir); | |
| lengthGizmo2->setVisibility(sideType == "Two sides" && extrudeType2 == "Length"); | |
| taperAngleGizmo2->placeOverLinearGizmo(lengthGizmo2); | |
| taperAngleGizmo2->setVisibility(sideType == "Two sides" && extrudeType2 == "Length"); | |
| Base::Vector3d padDir = extrude->Direction.getValue().Normalized(); | |
| Base::Vector3d sketchDir = extrude->getProfileNormal().Normalized(); | |
| double lengthFactor = padDir.Dot(sketchDir); | |
| double multFactor = (sideType == "Symmetric") ? 0.5 : 1.0; | |
| // Important note: This code assumes that nothing other than alongSketchNormal | |
| // and symmetric option influence the multFactor. If some custom gizmos changes | |
| // it then that also should be handled properly here | |
| if (extrude->AlongSketchNormal.getValue()) { | |
| lengthGizmo1->setMultFactor(multFactor / lengthFactor); | |
| lengthGizmo2->setMultFactor(multFactor / lengthFactor); | |
| } | |
| else { | |
| lengthGizmo1->setMultFactor(multFactor); | |
| lengthGizmo2->setMultFactor(multFactor); | |
| } | |
| gizmoContainer->calculateScaleAndOrientation(); | |
| } | |
| TaskDlgExtrudeParameters::TaskDlgExtrudeParameters(PartDesignGui::ViewProviderExtrude* vp) | |
| : TaskDlgSketchBasedParameters(vp) | |
| {} | |
| bool TaskDlgExtrudeParameters::accept() | |
| { | |
| getTaskParameters()->setSelectionMode(TaskExtrudeParameters::None); | |
| return TaskDlgSketchBasedParameters::accept(); | |
| } | |
| bool TaskDlgExtrudeParameters::reject() | |
| { | |
| getTaskParameters()->setSelectionMode(TaskExtrudeParameters::None); | |
| return TaskDlgSketchBasedParameters::reject(); | |
| } | |