1100 lines
36 KiB
C++
1100 lines
36 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2008 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 *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
#ifndef _PreComp_
|
|
#include <QClipboard>
|
|
#include <QDockWidget>
|
|
#include <QKeyEvent>
|
|
#include <QMessageBox>
|
|
#include <QMetaObject>
|
|
#include <QSignalMapper>
|
|
#endif
|
|
|
|
#include <App/ComplexGeoData.h>
|
|
#include <App/Document.h>
|
|
#include <App/GeoFeature.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Tools.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/DockWindowManager.h>
|
|
#include <Gui/Document.h>
|
|
#include <Gui/Selection.h>
|
|
#include <Gui/ViewProvider.h>
|
|
#include <Gui/Window.h>
|
|
|
|
#include "Placement.h"
|
|
#include "ui_Placement.h"
|
|
|
|
|
|
using namespace Gui::Dialog;
|
|
namespace bp = boost::placeholders;
|
|
|
|
namespace Gui { namespace Dialog {
|
|
class find_placement
|
|
{
|
|
public:
|
|
explicit find_placement(const std::string& name) : propertyname(name)
|
|
{
|
|
}
|
|
bool operator () (const std::pair<std::string, App::Property*>& elem) const
|
|
{
|
|
if (elem.first == propertyname) {
|
|
// flag set that property is read-only or hidden
|
|
if (elem.second->testStatus(App::Property::ReadOnly) || elem.second->testStatus(App::Property::Hidden))
|
|
return false;
|
|
App::PropertyContainer* parent = elem.second->getContainer();
|
|
if (parent) {
|
|
// flag set that property is read-only or hidden
|
|
if (parent->isReadOnly(elem.second) ||
|
|
parent->isHidden(elem.second))
|
|
return false;
|
|
}
|
|
return elem.second->isDerivedFrom
|
|
(Base::Type::fromName("App::PropertyPlacement"));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static App::PropertyPlacement* getProperty(App::DocumentObject* obj, const std::string& propertyName)
|
|
{
|
|
std::map<std::string,App::Property*> props;
|
|
obj->getPropertyMap(props);
|
|
|
|
// search for the placement property
|
|
std::map<std::string,App::Property*>::iterator jt;
|
|
jt = std::find_if(props.begin(), props.end(), find_placement(propertyName));
|
|
if (jt != props.end()) {
|
|
return dynamic_cast<App::PropertyPlacement*>(jt->second);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::string propertyname;
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
PlacementHandler::PlacementHandler()
|
|
: propertyName{"Placement"}
|
|
, changeProperty{false}
|
|
{
|
|
|
|
}
|
|
|
|
void PlacementHandler::openTransactionIfNeeded()
|
|
{
|
|
if (changeProperty) {
|
|
openTransaction();
|
|
}
|
|
}
|
|
|
|
void PlacementHandler::setPropertyName(const std::string& name)
|
|
{
|
|
propertyName = name;
|
|
// Only the Placement property it's possible to directly change the Inventor representation.
|
|
// For other placement properties with a different name the standard property handling must be used.
|
|
changeProperty = (propertyName != "Placement");
|
|
}
|
|
|
|
const std::string& PlacementHandler::getPropertyName() const
|
|
{
|
|
return propertyName;
|
|
}
|
|
|
|
void PlacementHandler::appendDocument(const std::string& name)
|
|
{
|
|
documents.insert(name);
|
|
}
|
|
|
|
void PlacementHandler::activatedDocument(const std::string& name)
|
|
{
|
|
appendDocument(name);
|
|
|
|
if (changeProperty) {
|
|
QMetaObject::invokeMethod(this, "openTransaction", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
void PlacementHandler::openTransaction()
|
|
{
|
|
App::Document* activeDoc = App::GetApplication().getActiveDocument();
|
|
if (activeDoc)
|
|
activeDoc->openTransaction("Placement");
|
|
}
|
|
|
|
void PlacementHandler::revertTransformation()
|
|
{
|
|
for (const auto & it : documents) {
|
|
Gui::Document* document = Application::Instance->getDocument(it.c_str());
|
|
if (document) {
|
|
if (!changeProperty) {
|
|
revertTransformationOfViewProviders(document);
|
|
}
|
|
else {
|
|
document->abortCommand();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<App::DocumentObject*> PlacementHandler::getObjects(Gui::Document* document) const
|
|
{
|
|
return document->getDocument()->getObjectsOfType(App::DocumentObject::getClassTypeId());
|
|
}
|
|
|
|
std::vector<App::DocumentObject*> PlacementHandler::getSelectedObjects(Gui::Document* document) const
|
|
{
|
|
return Gui::Selection().getObjectsOfType(App::DocumentObject::getClassTypeId(), document->getDocument()->getName());
|
|
}
|
|
|
|
void PlacementHandler::revertTransformationOfViewProviders(Gui::Document* document)
|
|
{
|
|
std::vector<App::DocumentObject*> obj = getObjects(document);
|
|
for (const auto & it : obj) {
|
|
auto property = find_placement::getProperty(it, this->propertyName);
|
|
if (property) {
|
|
Base::Placement cur = property->getValue();
|
|
Gui::ViewProvider* vp = document->getViewProvider(it);
|
|
if (vp) {
|
|
vp->setTransformation(cur.toMatrix());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlacementHandler::applyPlacement(const Base::Placement& p, bool incremental)
|
|
{
|
|
Gui::Document* document = Application::Instance->activeDocument();
|
|
if (!document)
|
|
return;
|
|
|
|
std::vector<App::DocumentObject*> sel = getSelectedObjects(document);
|
|
if (!sel.empty()) {
|
|
for (const auto & it : sel) {
|
|
applyPlacement(document, it, p, incremental);
|
|
}
|
|
}
|
|
else {
|
|
Base::Console().Warning("No object selected.\n");
|
|
}
|
|
}
|
|
|
|
void PlacementHandler::applyPlacement(Gui::Document* document, App::DocumentObject* obj, const Base::Placement& p, bool incremental)
|
|
{
|
|
auto property = find_placement::getProperty(obj, this->propertyName);
|
|
if (property) {
|
|
Base::Placement cur = property->getValue();
|
|
if (incremental)
|
|
cur = p * cur;
|
|
else
|
|
cur = p;
|
|
|
|
if (!changeProperty) {
|
|
Gui::ViewProvider* vp = document->getViewProvider(obj);
|
|
if (vp) {
|
|
vp->setTransformation(cur.toMatrix());
|
|
}
|
|
}
|
|
else {
|
|
property->setValue(cur);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlacementHandler::applyPlacement(const QString& data, bool incremental)
|
|
{
|
|
Gui::Document* document = Application::Instance->activeDocument();
|
|
if (!document)
|
|
return;
|
|
|
|
// When directly changing the property we now only have to commit the transaction,
|
|
// do a recompute and open a new transaction
|
|
if (changeProperty) {
|
|
document->commitCommand();
|
|
tryRecompute(document);
|
|
document->openCommand(QT_TRANSLATE_NOOP("Command", "Placement"));
|
|
}
|
|
else {
|
|
std::vector<App::DocumentObject*> sel = getSelectedObjects(document);
|
|
if (!sel.empty()) {
|
|
document->openCommand(QT_TRANSLATE_NOOP("Command", "Placement"));
|
|
for (const auto & it : sel) {
|
|
applyPlacement(it, data, incremental);
|
|
}
|
|
|
|
document->commitCommand();
|
|
tryRecompute(document);
|
|
}
|
|
else {
|
|
Base::Console().Warning("No object selected.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlacementHandler::applyPlacement(App::DocumentObject* obj, const QString& data, bool incremental)
|
|
{
|
|
auto property = find_placement::getProperty(obj, this->propertyName);
|
|
if (property) {
|
|
QString cmd;
|
|
if (incremental) {
|
|
cmd = getIncrementalPlacement(obj, data);
|
|
}
|
|
else {
|
|
cmd = getSimplePlacement(obj, data);
|
|
}
|
|
|
|
Gui::Command::runCommand(Gui::Command::App, cmd.toLatin1());
|
|
}
|
|
}
|
|
|
|
QString PlacementHandler::getIncrementalPlacement(App::DocumentObject* obj, const QString& data) const
|
|
{
|
|
return QString::fromLatin1(
|
|
"App.getDocument(\"%1\").%2.%3=%4.multiply(App.getDocument(\"%1\").%2.%3)")
|
|
.arg(QString::fromLatin1(obj->getDocument()->getName()),
|
|
QString::fromLatin1(obj->getNameInDocument()),
|
|
QString::fromLatin1(this->propertyName.c_str()),
|
|
data);
|
|
}
|
|
|
|
QString PlacementHandler::getSimplePlacement(App::DocumentObject* obj, const QString& data) const
|
|
{
|
|
return QString::fromLatin1(
|
|
"App.getDocument(\"%1\").%2.%3=%4")
|
|
.arg(QString::fromLatin1(obj->getDocument()->getName()),
|
|
QString::fromLatin1(obj->getNameInDocument()),
|
|
QString::fromLatin1(this->propertyName.c_str()),
|
|
data);
|
|
}
|
|
|
|
void PlacementHandler::tryRecompute(Gui::Document* document)
|
|
{
|
|
try {
|
|
document->getDocument()->recompute();
|
|
}
|
|
catch (...) {
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/* TRANSLATOR Gui::Dialog::Placement */
|
|
|
|
Placement::Placement(QWidget* parent, Qt::WindowFlags fl)
|
|
: QDialog(parent, fl)
|
|
, ui{nullptr}
|
|
{
|
|
setupUi();
|
|
setupConnections();
|
|
setupUnits();
|
|
setupSignalMapper();
|
|
setupDocument();
|
|
setupRotationMethod();
|
|
}
|
|
|
|
Placement::~Placement()
|
|
{
|
|
connectAct.disconnect();
|
|
delete ui;
|
|
}
|
|
|
|
void Placement::setupUi()
|
|
{
|
|
ui = new Ui_Placement();
|
|
ui->setupUi(this);
|
|
ui->gridLayout->removeItem(ui->vSpacer);
|
|
}
|
|
|
|
void Placement::setupConnections()
|
|
{
|
|
connect(ui->applyButton, &QPushButton::clicked,
|
|
this, &Placement::onApplyButtonClicked);
|
|
connect(ui->applyIncrementalPlacement, &QCheckBox::toggled,
|
|
this, &Placement::onApplyIncrementalPlacementToggled);
|
|
connect(ui->resetButton, &QPushButton::clicked,
|
|
this, &Placement::onResetButtonClicked);
|
|
connect(ui->centerOfMass, &QCheckBox::toggled,
|
|
this, &Placement::onCenterOfMassToggled);
|
|
connect(ui->selectedVertex, &QPushButton::clicked,
|
|
this, &Placement::onSelectedVertexClicked);
|
|
connect(ui->applyAxial, &QPushButton::clicked,
|
|
this, &Placement::onApplyAxialClicked);
|
|
}
|
|
|
|
void Placement::setupUnits()
|
|
{
|
|
ui->xPos->setUnit(Base::Unit::Length);
|
|
ui->yPos->setUnit(Base::Unit::Length);
|
|
ui->zPos->setUnit(Base::Unit::Length);
|
|
ui->axialPos->setUnit(Base::Unit::Length);
|
|
ui->xCnt->setValue(Base::Quantity(0, Base::Unit::Length));
|
|
ui->yCnt->setValue(Base::Quantity(0, Base::Unit::Length));
|
|
ui->zCnt->setValue(Base::Quantity(0, Base::Unit::Length));
|
|
ui->angle->setUnit(Base::Unit::Angle);
|
|
ui->yawAngle->setMaximum(180.0F);
|
|
ui->yawAngle->setMinimum(-180.0F);
|
|
ui->yawAngle->setUnit(Base::Unit::Angle);
|
|
ui->yawAngle->checkRangeInExpression(true);
|
|
ui->pitchAngle->setMaximum(90.0F);
|
|
ui->pitchAngle->setMinimum(-90.0F);
|
|
ui->pitchAngle->setUnit(Base::Unit::Angle);
|
|
ui->pitchAngle->checkRangeInExpression(true);
|
|
ui->rollAngle->setMaximum(180.0F);
|
|
ui->rollAngle->setMinimum(-180.0F);
|
|
ui->rollAngle->setUnit(Base::Unit::Angle);
|
|
ui->rollAngle->checkRangeInExpression(true);
|
|
}
|
|
|
|
void Placement::setupSignalMapper()
|
|
{
|
|
// create a signal mapper in order to have one slot to perform the change
|
|
signalMapper = new QSignalMapper(this);
|
|
signalMapper->setMapping(this, 0);
|
|
|
|
int id = 1;
|
|
QList<Gui::QuantitySpinBox*> sb = this->findChildren<Gui::QuantitySpinBox*>();
|
|
for (const auto & it : sb) {
|
|
connect(it, qOverload<double>(&QuantitySpinBox::valueChanged), signalMapper, qOverload<>(&QSignalMapper::map));
|
|
signalMapper->setMapping(it, id++);
|
|
}
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5,15,0)
|
|
connect(signalMapper, qOverload<int>(&QSignalMapper::mapped),
|
|
this, &Placement::onPlacementChanged);
|
|
#else
|
|
connect(signalMapper, &QSignalMapper::mappedInt,
|
|
this, &Placement::onPlacementChanged);
|
|
#endif
|
|
}
|
|
|
|
void Placement::setupDocument()
|
|
{
|
|
connectAct = Application::Instance->signalActiveDocument.connect
|
|
(boost::bind(&Placement::slotActiveDocument, this, bp::_1));
|
|
App::Document* activeDoc = App::GetApplication().getActiveDocument();
|
|
if (activeDoc) {
|
|
handler.appendDocument(activeDoc->getName());
|
|
}
|
|
}
|
|
|
|
void Placement::setupRotationMethod()
|
|
{
|
|
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Placement");
|
|
long index = hGrp->GetInt("RotationMethod");
|
|
ui->rotationInput->setCurrentIndex(index);
|
|
ui->stackedWidget->setCurrentIndex(index);
|
|
}
|
|
|
|
void Placement::showDefaultButtons(bool ok)
|
|
{
|
|
ui->oKButton->setVisible(ok);
|
|
ui->closeButton->setVisible(ok);
|
|
ui->applyButton->setVisible(ok);
|
|
ui->buttonBoxLayout->invalidate();
|
|
if (ok) {
|
|
ui->buttonBoxLayout->insertSpacerItem(0, ui->buttonBoxSpacer);
|
|
}
|
|
else {
|
|
ui->buttonBoxLayout->removeItem(ui->buttonBoxSpacer);
|
|
}
|
|
}
|
|
|
|
void Placement::open()
|
|
{
|
|
handler.openTransactionIfNeeded();
|
|
}
|
|
|
|
void Placement::slotActiveDocument(const Gui::Document& doc)
|
|
{
|
|
handler.activatedDocument(doc.getDocument()->getName());
|
|
}
|
|
|
|
QWidget* Placement::getInvalidInput() const
|
|
{
|
|
QList<Gui::QuantitySpinBox*> sb = this->findChildren<Gui::QuantitySpinBox*>();
|
|
for (const auto & it : sb) {
|
|
if (!it->hasValidInput())
|
|
return it;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Placement::onPlacementChanged(int)
|
|
{
|
|
// If there are listeners to the 'placementChanged' signal we rely
|
|
// on that the listener updates any placement. If no listeners
|
|
// are connected the placement is applied to all selected objects
|
|
// automatically.
|
|
bool incr = ui->applyIncrementalPlacement->isChecked();
|
|
Base::Placement plm = this->getPlacement();
|
|
handler.applyPlacement(plm, incr);
|
|
|
|
QVariant data = QVariant::fromValue<Base::Placement>(plm);
|
|
Q_EMIT placementChanged(data, incr, false);
|
|
}
|
|
|
|
void Placement::onCenterOfMassToggled(bool on)
|
|
{
|
|
ui->xCnt->setDisabled(on);
|
|
ui->yCnt->setDisabled(on);
|
|
ui->zCnt->setDisabled(on);
|
|
|
|
if (on) {
|
|
cntOfMass = getCenterOfMass();
|
|
ui->xCnt->setValue(cntOfMass.x);
|
|
ui->yCnt->setValue(cntOfMass.y);
|
|
ui->zCnt->setValue(cntOfMass.z);
|
|
}
|
|
}
|
|
|
|
void Placement::onSelectedVertexClicked()
|
|
{
|
|
cntOfMass.Set(0,0,0);
|
|
ui->centerOfMass->setChecked(false);
|
|
|
|
bool success=false;
|
|
std::vector<Gui::SelectionObject> selection = Gui::Selection().getSelectionEx();
|
|
std::vector<Base::Vector3d> picked;
|
|
//combine all pickedpoints into single vector
|
|
//even if points are from separate objects
|
|
Base::Vector3d firstSelected; //first selected will be central point when 3 points picked
|
|
for (auto it=selection.begin(); it!=selection.end(); ++it){
|
|
std::vector<Base::Vector3d> points = it->getPickedPoints();
|
|
if (it==selection.begin() && !points.empty()){
|
|
firstSelected=points[0];
|
|
}
|
|
picked.insert(picked.begin(),points.begin(),points.end());
|
|
}
|
|
//we have to clear selection and reselect original object(s)
|
|
//else later on the rotation is applied twice because there will
|
|
//be 2 (vertex) objects in the selection, and even if both are subobjects
|
|
//of the same object the rotation still gets applied twice
|
|
Gui::Selection().clearSelection();
|
|
//reselect original object that was selected when placement dlg first opened
|
|
for (const auto& it : selectionObjects)
|
|
Gui::Selection().addSelection(it);
|
|
|
|
if (picked.size() == 1) {
|
|
ui->xCnt->setValue(picked[0].x);
|
|
ui->yCnt->setValue(picked[0].y);
|
|
ui->zCnt->setValue(picked[0].z);
|
|
cntOfMass.x=picked[0].x;
|
|
cntOfMass.y=picked[0].y;
|
|
cntOfMass.z=picked[0].z;
|
|
success=true;
|
|
}
|
|
else if (picked.size() == 2) {
|
|
//average the coords to get center of rotation
|
|
ui->xCnt->setValue((picked[0].x+picked[1].x)/2.0);
|
|
ui->yCnt->setValue((picked[0].y+picked[1].y)/2.0);
|
|
ui->zCnt->setValue((picked[0].z+picked[1].z)/2.0);
|
|
cntOfMass.x=(picked[0].x+picked[1].x)/2.0;
|
|
cntOfMass.y=(picked[0].y+picked[1].y)/2.0;
|
|
cntOfMass.z=(picked[0].z+picked[1].z)/2.0;
|
|
//setup a customized axis since the user selected 2 points
|
|
//keep any existing angle, but setup our own axis
|
|
Base::Placement plm = getPlacement();
|
|
Base::Rotation rot = plm.getRotation();
|
|
Base::Vector3d tmp;
|
|
double angle;
|
|
rot.getRawValue(tmp, angle);
|
|
Base::Vector3d axis;
|
|
if (firstSelected==picked[0]){
|
|
axis = Base::Vector3d(picked[1]-picked[0]);
|
|
}
|
|
else {
|
|
axis = Base::Vector3d(picked[0]-picked[1]);
|
|
}
|
|
double length = axis.Length();
|
|
Base::Console().Message("Distance: %.8f\n",length);
|
|
if (QApplication::keyboardModifiers() == Qt::ShiftModifier){ //copy to clipboard on Shift+click
|
|
QLocale loc;
|
|
QApplication::clipboard()->setText(loc.toString(length,'g',8));
|
|
}
|
|
else {
|
|
Base::Console().Message("(Shift + click Selected points button to copy distance to clipboard)\n");
|
|
}
|
|
axis.Normalize();
|
|
rot.setValue(axis, angle);
|
|
plm.setRotation(rot);
|
|
setPlacementData(plm); //creates custom axis, if needed
|
|
ui->rotationInput->setCurrentIndex(0); //use rotation with axis instead of euler
|
|
ui->stackedWidget->setCurrentIndex(0);
|
|
success=true;
|
|
}
|
|
else if (picked.size() == 3){
|
|
/* User selected 3 points, so we find the plane defined by those
|
|
* and use the normal vector that contains the first point picked
|
|
* as the axis of rotation.
|
|
*/
|
|
|
|
Base::Vector3d a, b(firstSelected), c; //b is on central axis
|
|
if (picked[0] == firstSelected){
|
|
a = picked[1];
|
|
c = picked[2];
|
|
}
|
|
else if (picked[1]==firstSelected){
|
|
a = picked[0];
|
|
c = picked[2];
|
|
}
|
|
else if (picked[2] == firstSelected){
|
|
a = picked[0];
|
|
c = picked[1];
|
|
}
|
|
|
|
Base::Vector3d norm((a-b).Cross(c-b));
|
|
norm.Normalize();
|
|
ui->xCnt->setValue(b.x);
|
|
ui->yCnt->setValue(b.y);
|
|
ui->zCnt->setValue(b.z);
|
|
cntOfMass.x=b.x;
|
|
cntOfMass.y=b.y;
|
|
cntOfMass.z=b.z;
|
|
//setup a customized axis normal to the plane
|
|
//keep any existing angle, but setup our own axis
|
|
Base::Placement plm = getPlacement();
|
|
Base::Rotation rot = plm.getRotation();
|
|
Base::Vector3d tmp;
|
|
double angle;
|
|
rot.getRawValue(tmp, angle);
|
|
double length = (a-c).Length();
|
|
Base::Console().Message("Distance: %.8f\n",length);
|
|
Base::Vector3d v1(a-b);
|
|
Base::Vector3d v2(c-b);
|
|
v1.Normalize();
|
|
v2.Normalize();
|
|
double targetAngle = Base::toDegrees(v2.GetAngle(v1));
|
|
Base::Console().Message("Target angle: %.8f degrees, complementary: %.8f degrees\n",targetAngle, 90.0-targetAngle);
|
|
if (QApplication::keyboardModifiers() == Qt::ShiftModifier){ //copy to clipboard on Shift+click
|
|
QLocale loc;
|
|
QApplication::clipboard()->setText(loc.toString(targetAngle,'g',8));
|
|
Base::Console().Message("(Angle copied to clipboard, but you might need to use a negative (-) angle sometimes.)\n");
|
|
}
|
|
else {
|
|
Base::Console().Message("(Shift + click Selected points button to copy angle to clipboard)\n");
|
|
}
|
|
rot.setValue(norm, angle);
|
|
plm.setRotation(rot);
|
|
setPlacementData(plm); //creates custom axis, if needed
|
|
ui->rotationInput->setCurrentIndex(0); //use rotation with axis instead of euler
|
|
ui->stackedWidget->setCurrentIndex(0);
|
|
success=true;
|
|
}
|
|
|
|
if (!success){
|
|
Base::Console().Warning("Placement selection error. Select either 1 or 2 points.\n");
|
|
QMessageBox msgBox;
|
|
msgBox.setText(tr("Please select 1, 2, or 3 points before clicking this button. A point may be on a vertex, \
|
|
face, or edge. If on a face or edge the point used will be the point at the mouse position along \
|
|
face or edge. If 1 point is selected it will be used as the center of rotation. If 2 points are \
|
|
selected the midpoint between them will be the center of rotation and a new custom axis will be \
|
|
created, if needed. If 3 points are selected the first point becomes the center of rotation and \
|
|
lies on the vector that is normal to the plane defined by the 3 points. Some distance and angle \
|
|
information is provided in the report view, which can be useful when aligning objects. For your \
|
|
convenience when Shift + click is used the appropriate distance or angle is copied to the clipboard."));
|
|
msgBox.exec();
|
|
ui->xCnt->setValue(0);
|
|
ui->yCnt->setValue(0);
|
|
ui->zCnt->setValue(0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Placement::onApplyAxialClicked()
|
|
{
|
|
signalMapper->blockSignals(true);
|
|
double axPos = ui->axialPos->value().getValue();
|
|
Base::Placement p = getPlacementData();
|
|
double angle;
|
|
Base::Vector3d axis;
|
|
p.getRotation().getValue(axis, angle);
|
|
Base::Vector3d curPos (p.getPosition());
|
|
Base::Vector3d newPos;
|
|
Qt::KeyboardModifiers km = QApplication::keyboardModifiers();
|
|
if (km == Qt::ShiftModifier){ //go opposite direction on Shift+click
|
|
newPos = Base::Vector3d(curPos.x-(axis.x*axPos),curPos.y-(axis.y*axPos),curPos.z-(axis.z*axPos));
|
|
}
|
|
else {
|
|
newPos = Base::Vector3d(curPos.x+(axis.x*axPos),curPos.y+(axis.y*axPos),curPos.z+(axis.z*axPos));
|
|
}
|
|
ui->xPos->setValue(Base::Quantity(newPos.x,Base::Unit::Length));
|
|
ui->yPos->setValue(Base::Quantity(newPos.y,Base::Unit::Length));
|
|
ui->zPos->setValue(Base::Quantity(newPos.z,Base::Unit::Length));
|
|
signalMapper->blockSignals(false);
|
|
onPlacementChanged(0);
|
|
}
|
|
|
|
void Placement::onApplyIncrementalPlacementToggled(bool on)
|
|
{
|
|
if (on) {
|
|
this->ref = getPlacementData();
|
|
onResetButtonClicked();
|
|
}
|
|
else {
|
|
Base::Placement p = getPlacementData();
|
|
p = p * this->ref;
|
|
setPlacementData(p);
|
|
onPlacementChanged(0);
|
|
}
|
|
}
|
|
|
|
void Placement::keyPressEvent(QKeyEvent* ke)
|
|
{
|
|
// The placement dialog is embedded into a task panel
|
|
// which is a parent widget and will handle the event
|
|
ke->ignore();
|
|
}
|
|
|
|
void Placement::reject()
|
|
{
|
|
Base::Placement plm;
|
|
handler.applyPlacement(plm, true);
|
|
|
|
QVariant data = QVariant::fromValue<Base::Placement>(plm);
|
|
Q_EMIT placementChanged(data, true, false);
|
|
|
|
handler.revertTransformation();
|
|
|
|
// One of the quantity spin boxes still can emit a signal when it has the focus
|
|
// but its content is not fully updated.
|
|
// In order to override again the placement the signalMapper is blocked
|
|
// See related forum thread:
|
|
// https://forum.freecadweb.org/viewtopic.php?f=3&t=44341#p378659
|
|
QSignalBlocker block(signalMapper);
|
|
QDialog::reject();
|
|
}
|
|
|
|
void Placement::accept()
|
|
{
|
|
if (onApply()) {
|
|
handler.revertTransformation();
|
|
QDialog::accept();
|
|
}
|
|
}
|
|
|
|
void Placement::onApplyButtonClicked()
|
|
{
|
|
onApply();
|
|
}
|
|
|
|
void Placement::showErrorMessage()
|
|
{
|
|
QMessageBox msg(this);
|
|
msg.setWindowTitle(tr("Incorrect quantity"));
|
|
msg.setIcon(QMessageBox::Critical);
|
|
msg.setText(tr("There are input fields with incorrect input, please ensure valid placement values!"));
|
|
msg.exec();
|
|
}
|
|
|
|
bool Placement::onApply()
|
|
{
|
|
//only process things when we have valid inputs!
|
|
QWidget* input = getInvalidInput();
|
|
if (input) {
|
|
input->setFocus();
|
|
showErrorMessage();
|
|
return false;
|
|
}
|
|
|
|
// If there are listeners to the 'placementChanged' signal we rely
|
|
// on that the listener updates any placement. If no listeners
|
|
// are connected the placement is applied to all selected objects
|
|
// automatically.
|
|
bool incr = ui->applyIncrementalPlacement->isChecked();
|
|
Base::Placement plm = this->getPlacement();
|
|
handler.applyPlacement(getPlacementString(), incr);
|
|
|
|
QVariant data = QVariant::fromValue<Base::Placement>(plm);
|
|
Q_EMIT placementChanged(data, incr, true);
|
|
|
|
if (ui->applyIncrementalPlacement->isChecked()) {
|
|
QList<Gui::QuantitySpinBox*> sb = this->findChildren<Gui::QuantitySpinBox*>();
|
|
for (auto & it : sb) {
|
|
it->blockSignals(true);
|
|
it->setValue(0);
|
|
it->blockSignals(false);
|
|
}
|
|
}
|
|
|
|
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Placement");
|
|
hGrp->SetInt("RotationMethod", ui->rotationInput->currentIndex());
|
|
|
|
return true;
|
|
}
|
|
|
|
void Placement::onResetButtonClicked()
|
|
{
|
|
QList<Gui::QuantitySpinBox*> sb = this->findChildren<Gui::QuantitySpinBox*>();
|
|
for (auto & it : sb) {
|
|
it->blockSignals(true);
|
|
it->setValue(0);
|
|
it->blockSignals(false);
|
|
}
|
|
|
|
onPlacementChanged(0);
|
|
}
|
|
|
|
/*!
|
|
* \brief Placement::setSelection
|
|
* Sets the array of selection objects.
|
|
* \param selection
|
|
*/
|
|
void Placement::setSelection(const std::vector<Gui::SelectionObject>& selection)
|
|
{
|
|
selectionObjects = selection;
|
|
}
|
|
|
|
void Placement::setPropertyName(const std::string& name)
|
|
{
|
|
handler.setPropertyName(name);
|
|
}
|
|
|
|
/*!
|
|
* \brief Placement::bindObject
|
|
* Binds the spin boxes to the placement components of the first object of the selection.
|
|
* This requires the call of \a setSelection() beforehand.
|
|
*/
|
|
void Placement::bindObject()
|
|
{
|
|
if (!selectionObjects.empty()) {
|
|
App::DocumentObject* obj = selectionObjects.front().getObject();
|
|
|
|
std::string propertyName = handler.getPropertyName();
|
|
ui->xPos->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Base.x")));
|
|
ui->yPos->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Base.y")));
|
|
ui->zPos->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Base.z")));
|
|
|
|
ui->xAxis->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Axis.x")));
|
|
ui->yAxis->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Axis.y")));
|
|
ui->zAxis->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Axis.z")));
|
|
ui->angle->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Angle")));
|
|
|
|
ui->yawAngle ->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Yaw")));
|
|
ui->pitchAngle->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Pitch")));
|
|
ui->rollAngle ->bind(App::ObjectIdentifier::parse(obj, propertyName + std::string(".Rotation.Roll")));
|
|
|
|
ui->yawAngle->evaluateExpression();
|
|
ui->pitchAngle->evaluateExpression();
|
|
ui->rollAngle->evaluateExpression();
|
|
}
|
|
}
|
|
|
|
Base::Vector3d Placement::getDirection() const
|
|
{
|
|
double x = ui->xAxis->value().getValue();
|
|
double y = ui->yAxis->value().getValue();
|
|
double z = ui->zAxis->value().getValue();
|
|
return Base::Vector3d(x, y, z);
|
|
}
|
|
|
|
void Placement::setPlacement(const Base::Placement& p)
|
|
{
|
|
setPlacementData(p);
|
|
}
|
|
|
|
void Placement::setPlacementData(const Base::Placement& p)
|
|
{
|
|
QSignalBlocker block(signalMapper);
|
|
ui->xPos->setValue(Base::Quantity(p.getPosition().x, Base::Unit::Length));
|
|
ui->yPos->setValue(Base::Quantity(p.getPosition().y, Base::Unit::Length));
|
|
ui->zPos->setValue(Base::Quantity(p.getPosition().z, Base::Unit::Length));
|
|
|
|
double Y,P,R;
|
|
p.getRotation().getYawPitchRoll(Y,P,R);
|
|
ui->yawAngle->setValue(Base::Quantity(Y, Base::Unit::Angle));
|
|
ui->pitchAngle->setValue(Base::Quantity(P, Base::Unit::Angle));
|
|
ui->rollAngle->setValue(Base::Quantity(R, Base::Unit::Angle));
|
|
|
|
double angle;
|
|
Base::Vector3d axis;
|
|
p.getRotation().getRawValue(axis, angle);
|
|
ui->xAxis->setValue(axis.x);
|
|
ui->yAxis->setValue(axis.y);
|
|
ui->zAxis->setValue(axis.z);
|
|
ui->angle->setValue(Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle));
|
|
}
|
|
|
|
Base::Placement Placement::getPlacement() const
|
|
{
|
|
Base::Placement p = getPlacementData();
|
|
return p;
|
|
}
|
|
|
|
Base::Rotation Placement::getRotationData() const
|
|
{
|
|
Base::Rotation rot;
|
|
int index = ui->rotationInput->currentIndex();
|
|
if (index == 0) {
|
|
Base::Vector3d dir = getDirection();
|
|
rot.setValue(Base::Vector3d(dir.x,dir.y,dir.z),Base::toRadians(ui->angle->value().getValue()));
|
|
}
|
|
else if (index == 1) { // Euler angles (XY'Z'')
|
|
rot.setYawPitchRoll(
|
|
ui->yawAngle->value().getValue(),
|
|
ui->pitchAngle->value().getValue(),
|
|
ui->rollAngle->value().getValue());
|
|
}
|
|
|
|
return rot;
|
|
}
|
|
|
|
Base::Vector3d Placement::getPositionData() const
|
|
{
|
|
return Base::Vector3d(ui->xPos->value().getValue(),
|
|
ui->yPos->value().getValue(),
|
|
ui->zPos->value().getValue());
|
|
}
|
|
|
|
Base::Vector3d Placement::getAnglesData() const
|
|
{
|
|
return Base::Vector3d(ui->yawAngle->value().getValue(),
|
|
ui->pitchAngle->value().getValue(),
|
|
ui->rollAngle->value().getValue());
|
|
}
|
|
|
|
Base::Vector3d Placement::getCenterData() const
|
|
{
|
|
if (ui->centerOfMass->isChecked())
|
|
return this->cntOfMass;
|
|
return Base::Vector3d(ui->xCnt->value().getValue(),
|
|
ui->yCnt->value().getValue(),
|
|
ui->zCnt->value().getValue());
|
|
}
|
|
|
|
Base::Vector3d Placement::getCenterOfMass() const
|
|
{
|
|
Base::Vector3d centerOfMass;
|
|
std::vector<App::DocumentObject*> sel = Gui::Selection().getObjectsOfType
|
|
(App::GeoFeature::getClassTypeId());
|
|
if (!sel.empty()) {
|
|
for (auto it : sel) {
|
|
const App::PropertyComplexGeoData* propgeo = static_cast<App::GeoFeature*>(it)->getPropertyOfGeometry();
|
|
const Data::ComplexGeoData* geodata = propgeo ? propgeo->getComplexData() : nullptr;
|
|
if (geodata && geodata->getCenterOfGravity(centerOfMass)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return centerOfMass;
|
|
}
|
|
|
|
Base::Placement Placement::getPlacementData() const
|
|
{
|
|
Base::Rotation rot = getRotationData();
|
|
Base::Vector3d pos = getPositionData();
|
|
Base::Vector3d cnt = getCenterData();
|
|
|
|
Base::Placement plm(pos, rot, cnt);
|
|
return plm;
|
|
}
|
|
|
|
QString Placement::getPlacementFromEulerAngles() const
|
|
{
|
|
Base::Vector3d pos = getPositionData();
|
|
Base::Vector3d ypr = getAnglesData();
|
|
Base::Vector3d cnt = getCenterData();
|
|
return QString::fromLatin1(
|
|
"App.Placement(App.Vector(%1,%2,%3), App.Rotation(%4,%5,%6), App.Vector(%7,%8,%9))")
|
|
.arg(pos.x)
|
|
.arg(pos.y)
|
|
.arg(pos.z)
|
|
.arg(ypr.x)
|
|
.arg(ypr.y)
|
|
.arg(ypr.z)
|
|
.arg(cnt.x)
|
|
.arg(cnt.y)
|
|
.arg(cnt.z);
|
|
}
|
|
|
|
QString Placement::getPlacementFromAxisWithAngle() const
|
|
{
|
|
Base::Vector3d pos = getPositionData();
|
|
Base::Vector3d cnt = getCenterData();
|
|
Base::Vector3d dir = getDirection();
|
|
double angle = ui->angle->value().getValue();
|
|
return QString::fromLatin1(
|
|
"App.Placement(App.Vector(%1,%2,%3), App.Rotation(App.Vector(%4,%5,%6),%7), App.Vector(%8,%9,%10))")
|
|
.arg(pos.x)
|
|
.arg(pos.y)
|
|
.arg(pos.z)
|
|
.arg(dir.x)
|
|
.arg(dir.y)
|
|
.arg(dir.z)
|
|
.arg(angle)
|
|
.arg(cnt.x)
|
|
.arg(cnt.y)
|
|
.arg(cnt.z);
|
|
}
|
|
|
|
QString Placement::getPlacementString() const
|
|
{
|
|
QString cmd;
|
|
int index = ui->rotationInput->currentIndex();
|
|
|
|
if (index == 0) {
|
|
cmd = getPlacementFromAxisWithAngle();
|
|
}
|
|
else if (index == 1) {
|
|
cmd = getPlacementFromEulerAngles();
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
void Placement::changeEvent(QEvent *e)
|
|
{
|
|
if (e->type() == QEvent::LanguageChange) {
|
|
ui->retranslateUi(this);
|
|
}
|
|
else {
|
|
QDialog::changeEvent(e);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
|
|
/* TRANSLATOR Gui::Dialog::DockablePlacement */
|
|
|
|
DockablePlacement::DockablePlacement(QWidget* parent, Qt::WindowFlags fl) : Placement(parent, fl)
|
|
{
|
|
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
|
|
QDockWidget* dw = pDockMgr->addDockWindow(QT_TR_NOOP("Placement"),
|
|
this, Qt::BottomDockWidgetArea);
|
|
dw->setFeatures(QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable);
|
|
dw->show();
|
|
}
|
|
|
|
DockablePlacement::~DockablePlacement()
|
|
{
|
|
}
|
|
|
|
void DockablePlacement::accept()
|
|
{
|
|
// closes the dock window
|
|
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
|
|
pDockMgr->removeDockWindow(this);
|
|
Placement::accept();
|
|
}
|
|
|
|
void DockablePlacement::reject()
|
|
{
|
|
// closes the dock window
|
|
Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance();
|
|
pDockMgr->removeDockWindow(this);
|
|
Placement::reject();
|
|
}
|
|
|
|
// ----------------------------------------------
|
|
|
|
/* TRANSLATOR Gui::Dialog::TaskPlacement */
|
|
|
|
TaskPlacement::TaskPlacement()
|
|
{
|
|
this->setButtonPosition(TaskPlacement::South);
|
|
widget = new Placement();
|
|
widget->showDefaultButtons(false);
|
|
taskbox = new Gui::TaskView::TaskBox(QPixmap(), widget->windowTitle(),true, nullptr);
|
|
taskbox->groupLayout()->addWidget(widget);
|
|
|
|
Content.push_back(taskbox);
|
|
connect(widget, &Placement::placementChanged, this, &TaskPlacement::slotPlacementChanged);
|
|
}
|
|
|
|
TaskPlacement::~TaskPlacement()
|
|
{
|
|
// automatically deleted in the sub-class
|
|
}
|
|
|
|
|
|
/*!
|
|
* \brief TaskPlacement::setSelection
|
|
* Sets the array of selection objects.
|
|
* \param selection
|
|
*/
|
|
void TaskPlacement::setSelection(const std::vector<Gui::SelectionObject>& selection)
|
|
{
|
|
widget->setSelection(selection);
|
|
}
|
|
|
|
/*!
|
|
* \brief TaskPlacement::bindObject
|
|
* Binds the spin boxes to the placement components of the first object of the selection.
|
|
* This requires the call of \a setSelection() beforehand.
|
|
*/
|
|
void TaskPlacement::bindObject()
|
|
{
|
|
widget->bindObject();
|
|
}
|
|
|
|
void TaskPlacement::open()
|
|
{
|
|
widget->open();
|
|
}
|
|
|
|
void TaskPlacement::setPropertyName(const QString& name)
|
|
{
|
|
widget->setPropertyName(name.toStdString());
|
|
}
|
|
|
|
QDialogButtonBox::StandardButtons TaskPlacement::getStandardButtons() const
|
|
{
|
|
return QDialogButtonBox::Ok|
|
|
QDialogButtonBox::Cancel|
|
|
QDialogButtonBox::Apply;
|
|
}
|
|
|
|
void TaskPlacement::setPlacement(const Base::Placement& p)
|
|
{
|
|
widget->setPlacement(p);
|
|
}
|
|
|
|
void TaskPlacement::slotPlacementChanged(const QVariant & p, bool incr, bool data)
|
|
{
|
|
Q_EMIT placementChanged(p, incr, data);
|
|
}
|
|
|
|
bool TaskPlacement::accept()
|
|
{
|
|
widget->accept();
|
|
return (widget->result() == QDialog::Accepted);
|
|
}
|
|
|
|
bool TaskPlacement::reject()
|
|
{
|
|
widget->reject();
|
|
return (widget->result() == QDialog::Rejected);
|
|
}
|
|
|
|
void TaskPlacement::clicked(int id)
|
|
{
|
|
if (id == QDialogButtonBox::Apply) {
|
|
widget->onApplyButtonClicked();
|
|
}
|
|
}
|
|
|
|
#include "moc_Placement.cpp"
|