// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2011 Werner Mayer * * * * 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 * * * ***************************************************************************/ /// Qt Include Files #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "EditDatumDialog.h" #include "CommandSketcherTools.h" #include "Utils.h" #include "ViewProviderSketch.h" #include "SketcherSettings.h" #include "ui_InsertDatum.h" #include using namespace SketcherGui; /* TRANSLATOR SketcherGui::EditDatumDialog */ bool SketcherGui::checkConstraintName(const Sketcher::SketchObject* sketch, std::string constraintName) { if (constraintName != Base::Tools::getIdentifier(constraintName)) { Gui::NotifyUserError( sketch, QT_TRANSLATE_NOOP("Notifications", "Value Error"), QT_TRANSLATE_NOOP( "Notifications", "Invalid constraint name (must only contain alphanumericals and " "underscores, and must not start with digit)" ) ); return false; } return true; } EditDatumDialog::EditDatumDialog(ViewProviderSketch* vp, int ConstrNbr) : ConstrNbr(ConstrNbr) , success(false) { sketch = vp->getSketchObject(); const std::vector& Constraints = sketch->Constraints.getValues(); Constr = Constraints[ConstrNbr]; } EditDatumDialog::EditDatumDialog(Sketcher::SketchObject* pcSketch, int ConstrNbr) : sketch(pcSketch) , ConstrNbr(ConstrNbr) { const std::vector& Constraints = sketch->Constraints.getValues(); Constr = Constraints[ConstrNbr]; } EditDatumDialog::~EditDatumDialog() {} int EditDatumDialog::exec(bool atCursor) { // Return if constraint doesn't have editable value if (Constr->isDimensional()) { if (sketch->hasConflicts()) { Gui::TranslatedUserWarning( sketch, QObject::tr("Dimensional constraint"), QObject::tr( "Not allowed to edit the datum because the " "sketch contains conflicting constraints" ) ); return QDialog::Rejected; } Base::Quantity init_val; QDialog dlg(Gui::getMainWindow()); if (!ui_ins_datum) { ui_ins_datum.reset(new Ui_InsertDatum); ui_ins_datum->setupUi(&dlg); } double datum = Constr->getValue(); bool showRadiusDiameterBtns = Constr->Type == Sketcher::Radius || Constr->Type == Sketcher::Diameter; ui_ins_datum->rbRadius->setVisible(showRadiusDiameterBtns); ui_ins_datum->rbDiameter->setVisible(showRadiusDiameterBtns); ui_ins_datum->labelEdit->setEntryName(QByteArray("DatumValue")); if (Constr->Type == Sketcher::Angle) { datum = Base::toDegrees(datum); dlg.setWindowTitle(tr("Insert Angle")); init_val.setUnit(Base::Unit::Angle); ui_ins_datum->label->setText(tr("Angle:")); ui_ins_datum->labelEdit->setParamGrpPath( QByteArray("User parameter:BaseApp/History/SketcherAngle") ); } else if (Constr->Type == Sketcher::Radius) { dlg.setWindowTitle(tr("Insert Radius")); init_val.setUnit(Base::Unit::Length); ui_ins_datum->label->setText(tr("Radius:")); ui_ins_datum->labelEdit->setParamGrpPath( QByteArray("User parameter:BaseApp/History/SketcherLength") ); ui_ins_datum->rbRadius->setChecked(true); } else if (Constr->Type == Sketcher::Diameter) { dlg.setWindowTitle(tr("Insert Diameter")); init_val.setUnit(Base::Unit::Length); ui_ins_datum->label->setText(tr("Diameter:")); ui_ins_datum->labelEdit->setParamGrpPath( QByteArray("User parameter:BaseApp/History/SketcherLength") ); ui_ins_datum->rbDiameter->setChecked(true); } else if (Constr->Type == Sketcher::Weight) { dlg.setWindowTitle(tr("Insert Weight")); ui_ins_datum->label->setText(tr("Weight:")); ui_ins_datum->labelEdit->setParamGrpPath( QByteArray("User parameter:BaseApp/History/SketcherWeight") ); } else if (Constr->Type == Sketcher::SnellsLaw) { dlg.setWindowTitle(tr("Refractive Index Ratio", "Constraint_SnellsLaw")); ui_ins_datum->label->setText(tr("Ratio n2/n1:", "Constraint_SnellsLaw")); ui_ins_datum->labelEdit->setParamGrpPath( QByteArray("User parameter:BaseApp/History/SketcherRefrIndexRatio") ); ui_ins_datum->labelEdit->setSingleStep(0.05); } else { dlg.setWindowTitle(tr("Insert Length")); init_val.setUnit(Base::Unit::Length); ui_ins_datum->label->setText(tr("Length:")); ui_ins_datum->labelEdit->setParamGrpPath( QByteArray("User parameter:BaseApp/History/SketcherLength") ); } init_val.setValue(datum); ui_ins_datum->labelEdit->setValue(init_val); ui_ins_datum->labelEdit->pushToHistory(); ui_ins_datum->labelEdit->selectNumber(); ui_ins_datum->labelEdit->bind(sketch->Constraints.createPath(ConstrNbr)); ui_ins_datum->name->setText(QString::fromStdString(Constr->Name)); ui_ins_datum->cbDriving->setChecked(!Constr->isDriving); connect(ui_ins_datum->cbDriving, &QCheckBox::toggled, this, &EditDatumDialog::drivingToggled); connect( ui_ins_datum->labelEdit, qOverload(&Gui::QuantitySpinBox::valueChanged), this, &EditDatumDialog::datumChanged ); connect( ui_ins_datum->labelEdit, &Gui::QuantitySpinBox::showFormulaDialog, this, &EditDatumDialog::formEditorOpened ); connect(ui_ins_datum->rbRadius, &QRadioButton::toggled, this, &EditDatumDialog::typeChanged); connect(&dlg, &QDialog::accepted, this, &EditDatumDialog::accepted); connect(&dlg, &QDialog::rejected, this, &EditDatumDialog::rejected); if (atCursor) { dlg.show(); // Need to show the dialog so geometry is computed QRect pg = dlg.parentWidget()->geometry(); int Xmin = pg.x() + 10; int Ymin = pg.y() + 10; int Xmax = pg.x() + pg.width() - dlg.geometry().width() - 10; int Ymax = pg.y() + pg.height() - dlg.geometry().height() - 10; int x = Xmax < Xmin ? (Xmin + Xmax) / 2 : std::min(std::max(QCursor::pos().x(), Xmin), Xmax); int y = Ymax < Ymin ? (Ymin + Ymax) / 2 : std::min(std::max(QCursor::pos().y(), Ymin), Ymax); dlg.setGeometry(x, y, dlg.geometry().width(), dlg.geometry().height()); } return dlg.exec(); } return QDialog::Rejected; } void EditDatumDialog::typeChanged(bool checked) { Q_UNUSED(checked); if (!ui_ins_datum->rbRadius->isVisible()) { return; } // Updates UI labels based on selection, but does NOT change value yet QWidget* dlg = ui_ins_datum->labelEdit->parentWidget(); while (dlg && !dlg->isWindow()) { dlg = dlg->parentWidget(); } if (ui_ins_datum->rbRadius->isChecked()) { ui_ins_datum->label->setText(tr("Radius")); if (dlg) { dlg->setWindowTitle(tr("Insert Radius")); } } else { ui_ins_datum->label->setText(tr("Diameter")); if (dlg) { dlg->setWindowTitle(tr("Insert Diameter")); } } } void EditDatumDialog::accepted() { // Check if we need to swap Radius <-> Diameter if (Constr->Type == Sketcher::Radius && ui_ins_datum->rbDiameter->isChecked()) { Constr->Type = Sketcher::Diameter; } else if (Constr->Type == Sketcher::Diameter && ui_ins_datum->rbRadius->isChecked()) { Constr->Type = Sketcher::Radius; } Base::Quantity newQuant = ui_ins_datum->labelEdit->value(); if (Constr->Type == Sketcher::SnellsLaw || Constr->Type == Sketcher::Weight || !newQuant.isDimensionless()) { // save the value for the history ui_ins_datum->labelEdit->pushToHistory(); double newDatum = newQuant.getValue(); try { /*if (ui_ins_datum->cbDriving->isChecked() == Constr->isDriving) { Gui::cmdAppObjectArgs(sketch, "toggleDriving(%i)", ConstrNbr); }*/ if (!ui_ins_datum->cbDriving->isChecked()) { if (ui_ins_datum->labelEdit->hasExpression()) { ui_ins_datum->labelEdit->apply(); } else { auto unitString = newQuant.getUnit().getString(); unitString = Base::Tools::escapeQuotesFromString(unitString); performAutoScale(newDatum); Gui::cmdAppObjectArgs( sketch, "setDatum(%i,App.Units.Quantity('%f %s'))", ConstrNbr, newDatum, unitString ); } } std::string constraintName = ui_ins_datum->name->text().trimmed().toStdString(); std::string currConstraintName = sketch->Constraints[ConstrNbr]->Name; if (constraintName != currConstraintName) { if (!SketcherGui::checkConstraintName(sketch, constraintName)) { constraintName = currConstraintName; } Gui::cmdAppObjectArgs( sketch, "renameConstraint(%d, u'%s')", ConstrNbr, constraintName.c_str() ); } Gui::Command::commitCommand(); // THIS IS A WORK-AROUND NOT TO DELAY 0.19 RELEASE // // depsAreTouched is not returning true in this case: // https://forum.freecad.org/viewtopic.php?f=3&t=55633&p=481061#p478477 // // It appears related to a drastic change in how dependencies are calculated, see: // https://forum.freecad.org/viewtopic.php?f=3&t=55633&p=481061#p481061 // // This is NOT the solution, as there is no point in systematically executing the // ExpressionEngine on every dimensional constraint change. Just a quick fix to avoid // clearly unwanted behaviour in absence of time to actually fix the root cause. // if (sketch->noRecomputes && sketch->ExpressionEngine.depsAreTouched()) { sketch->ExpressionEngine.execute(); sketch->solve(); //} tryAutoRecompute(sketch); success = true; } catch (const Base::Exception& e) { Gui::NotifyUserError(sketch, QT_TRANSLATE_NOOP("Notifications", "Value Error"), e.what()); Gui::Command::abortCommand(); if (sketch->noRecomputes) { // if setdatum failed, it is highly likely that solver // information is invalid. sketch->solve(); } } } } void EditDatumDialog::rejected() { Gui::Command::abortCommand(); sketch->recomputeFeature(); } bool EditDatumDialog::isSuccess() { return success; } void EditDatumDialog::drivingToggled(bool state) { if (state) { ui_ins_datum->labelEdit->setToLastUsedValue(); } sketch->setDriving(ConstrNbr, !state); if (!sketch->noRecomputes) { // if noRecomputes, solve() is already done by setDriving() sketch->solve(); } } void EditDatumDialog::datumChanged() { if (ui_ins_datum->labelEdit->text() != std::as_const(ui_ins_datum->labelEdit)->getHistory()[0]) { ui_ins_datum->cbDriving->setChecked(false); } } void EditDatumDialog::formEditorOpened(bool state) { if (state) { ui_ins_datum->cbDriving->setChecked(false); } } // This function checks an object's visible flag recursively in a Gui::Document // assuming that lastParent (if provided) is visible bool isVisibleUpTo(App::DocumentObject* obj, Gui::Document* doc, App::DocumentObject* lastParent) { while (obj && obj != lastParent) { auto parentviewprovider = doc->getViewProvider(obj); if (!parentviewprovider || !parentviewprovider->isVisible()) { return false; } obj = obj->getFirstParent(); } return true; } bool hasVisualFeature(App::DocumentObject* obj, App::DocumentObject* rootObj, Gui::Document* doc) { auto docObjects = doc->getDocument()->getObjects(); for (auto object : docObjects) { // Presumably, the sketch that is being edited has visual features, but // that's not interesting if (object == obj) { continue; } // No need to continue analysis if the object's visible flag is down bool visible = isVisibleUpTo(object, doc, rootObj); if (!visible) { continue; } App::DocumentObject* link = object->getLinkedObject(); if (link->getDocument() != doc->getDocument()) { Gui::Document* linkDoc = Gui::Application::Instance->getDocument(link->getDocument()); if (linkDoc && hasVisualFeature(link, link, linkDoc)) { return true; } continue; } // Skip objects that are not of geometric nature if (!object->isDerivedFrom()) { continue; } // Skip datum objects if (object->isDerivedFrom()) { continue; } // Skip container objects because getting their bounging box might // return a valid bounding box around annotations or datums if (object->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) { continue; } // Get the bounding box of the object auto viewProvider = doc->getViewProvider(object); if (viewProvider && viewProvider->getBoundingBox().IsValid()) { return true; } } return false; } void EditDatumDialog::performAutoScale(double newDatum) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/dimensioning" ); long autoScaleMode = hGrp->GetInt("AutoScaleMode", static_cast(SketcherGui::AutoScaleMode::Always)); // There is a single constraint in the sketch so it can // be used as a reference to scale the geometries around the origin // if there are external geometries, it is safe to assume that the sketch // was drawn with these geometries as scale references (use <= 2 because // the sketch axis are considered as external geometries) // if the sketch has blocked geometries, it is considered a scale indicator // and autoscale is not performed either if ((autoScaleMode == static_cast(SketcherGui::AutoScaleMode::Always) || (autoScaleMode == static_cast(SketcherGui::AutoScaleMode::WhenNoScaleFeatureIsVisible) && !hasVisualFeature(sketch, nullptr, Gui::Application::Instance->activeDocument()))) && sketch->getExternalGeometryCount() <= 2 && !sketch->hasBlockConstraint()) { try { // Handle the case where multiple datum constraints are present but only one is scale // defining e.g. a bunch of angle constraints and a single length constraint int scaleDefiningConstraint = sketch->getSingleScaleDefiningConstraint(); if (scaleDefiningConstraint != ConstrNbr) { return; } double oldDatum = sketch->getDatum(ConstrNbr); double scaleFactor = newDatum / oldDatum; centerScale(scaleFactor); // Some constraints cannot be scaled so the actual datum constraint // might change index ConstrNbr = sketch->getSingleScaleDefiningConstraint(); } catch (const Base::Exception& e) { Base::Console().error("Exception performing autoscale: %s\n", e.what()); } } } #include "moc_EditDatumDialog.cpp"