494 lines
18 KiB
C++
494 lines
18 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
/***************************************************************************
|
|
* Copyright (c) 2011 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 *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
/// Qt Include Files
|
|
#include <Inventor/sensors/SoSensor.h>
|
|
#include <QApplication>
|
|
#include <QDialog>
|
|
|
|
|
|
#include <Base/Tools.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/CommandT.h>
|
|
#include <Gui/Document.h>
|
|
#include <Gui/MainWindow.h>
|
|
#include <Gui/Notifications.h>
|
|
#include <Gui/View3DInventor.h>
|
|
#include <Gui/View3DInventorViewer.h>
|
|
#include <Gui/Document.h>
|
|
#include <Mod/Sketcher/App/GeometryFacade.h>
|
|
#include <Mod/Sketcher/App/SketchObject.h>
|
|
#include <App/Datums.h>
|
|
|
|
#include "EditDatumDialog.h"
|
|
#include "CommandSketcherTools.h"
|
|
#include "Utils.h"
|
|
#include "ViewProviderSketch.h"
|
|
#include "SketcherSettings.h"
|
|
#include "ui_InsertDatum.h"
|
|
|
|
#include <numeric>
|
|
|
|
|
|
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<Sketcher::Constraint*>& Constraints = sketch->Constraints.getValues();
|
|
Constr = Constraints[ConstrNbr];
|
|
}
|
|
|
|
EditDatumDialog::EditDatumDialog(Sketcher::SketchObject* pcSketch, int ConstrNbr)
|
|
: sketch(pcSketch)
|
|
, ConstrNbr(ConstrNbr)
|
|
{
|
|
const std::vector<Sketcher::Constraint*>& 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<double>(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<const Base::Quantity&>(&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<App::GeoFeature>()) {
|
|
continue;
|
|
}
|
|
|
|
// Skip datum objects
|
|
if (object->isDerivedFrom<App::DatumElement>()) {
|
|
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<int>(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<int>(SketcherGui::AutoScaleMode::Always)
|
|
|| (autoScaleMode == static_cast<int>(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"
|