Files
create/src/Gui/ManualAlignment.cpp
Markus Reitböck a72a0d6405 Gui: use CMake to generate precompiled headers on all platforms
"Professional CMake" book suggest the following:

"Targets should build successfully with or without compiler support for precompiled headers. It
should be considered an optimization, not a requirement. In particular, do not explicitly include a
precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically
generated precompile header on the compiler command line instead. This is more portable across
the major compilers and is likely to be easier to maintain. It will also avoid warnings being
generated from certain code checking tools like iwyu (include what you use)."

Therefore, removed the "#include <PreCompiled.h>" from sources, also
there is no need for the "#ifdef _PreComp_" anymore
2025-09-14 09:47:03 +02:00

1339 lines
43 KiB
C++

/***************************************************************************
* Copyright (c) 2012 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 <QAction>
# include <QApplication>
# include <QLabel>
# include <QMenu>
# include <QMessageBox>
# include <QPainter>
# include <QSplitter>
# include <QSurfaceFormat>
# include <QTimer>
# include <QVBoxLayout>
# include <Inventor/SoPickedPoint.h>
# include <Inventor/actions/SoSearchAction.h>
# include <Inventor/events/SoMouseButtonEvent.h>
# include <Inventor/fields/SoSFImage.h>
# include <Inventor/nodes/SoImage.h>
# include <Inventor/nodes/SoMaterial.h>
# include <Inventor/nodes/SoOrthographicCamera.h>
# include <Inventor/nodes/SoSeparator.h>
# include <Inventor/nodes/SoTranslation.h>
# include <Inventor/sensors/SoNodeSensor.h>
#include <App/Document.h>
#include <App/GeoFeature.h>
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Document.h>
#include <Gui/Inventor/SoAxisCrossKit.h>
#include <Gui/MainWindow.h>
#include <Gui/Selection/Selection.h>
#include <Gui/SplitView3DInventor.h>
#include <Gui/Tools.h>
#include <Gui/View3DInventorViewer.h>
#include <Gui/ViewProviderGeometryObject.h>
#include <Gui/WaitCursor.h>
#include "ManualAlignment.h"
using namespace Gui;
namespace sp = std::placeholders;
AlignmentGroup::AlignmentGroup() = default;
AlignmentGroup::~AlignmentGroup() = default;
void AlignmentGroup::addView(App::DocumentObject* pView)
{
if (pView) {
App::Document* rDoc = pView->getDocument();
Gui::Document* pDoc = Gui::Application::Instance->getDocument(rDoc);
auto pProvider = static_cast<Gui::ViewProviderDocumentObject*>
(pDoc->getViewProvider(pView));
this->_views.push_back(pProvider);
}
}
std::vector<App::DocumentObject*> AlignmentGroup::getViews() const
{
std::vector<App::DocumentObject*> views;
std::vector<Gui::ViewProviderDocumentObject*>::const_iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it) {
App::DocumentObject* pView = (*it)->getObject();
views.push_back(pView);
}
return views;
}
bool AlignmentGroup::hasView(Gui::ViewProviderDocumentObject* pView) const
{
std::vector<Gui::ViewProviderDocumentObject*>::const_iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it) {
if (*it == pView)
return true;
}
return false;
}
void AlignmentGroup::removeView(Gui::ViewProviderDocumentObject* pView)
{
std::vector<Gui::ViewProviderDocumentObject*>::iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it) {
if (*it == pView) {
this->_views.erase(it);
break;
}
}
}
void AlignmentGroup::addToViewer(Gui::View3DInventorViewer* viewer) const
{
std::vector<Gui::ViewProviderDocumentObject*>::const_iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it)
viewer->addViewProvider(*it);
viewer->viewAll();
}
void AlignmentGroup::removeFromViewer(Gui::View3DInventorViewer* viewer) const
{
std::vector<Gui::ViewProviderDocumentObject*>::const_iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it)
viewer->removeViewProvider(*it);
}
void AlignmentGroup::setRandomColor()
{
std::vector<Gui::ViewProviderDocumentObject*>::iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it) {
float r = /*(float)rand()/(float)RAND_MAX*/0.0f;
float g = (float)rand()/(float)RAND_MAX;
float b = (float)rand()/(float)RAND_MAX;
if ((*it)->isDerivedFrom<Gui::ViewProviderGeometryObject>()) {
SoSearchAction searchAction;
searchAction.setType(SoMaterial::getClassTypeId());
searchAction.setInterest(SoSearchAction::FIRST);
searchAction.apply((*it)->getRoot());
SoPath* selectionPath = searchAction.getPath();
if (selectionPath) {
auto material = static_cast<SoMaterial*>(selectionPath->getTail());
material->diffuseColor.setValue(r, g, b);
}
}
}
}
Gui::Document* AlignmentGroup::getDocument() const
{
if (this->_views.empty())
return nullptr;
App::DocumentObject* pView = this->_views[0]->getObject();
if (pView) {
App::Document* rDoc = pView->getDocument();
Gui::Document* pDoc = Gui::Application::Instance->getDocument(rDoc);
return pDoc;
}
return nullptr;
}
void AlignmentGroup::addPoint(const PickedPoint& pnt)
{
this->_pickedPoints.push_back(pnt);
}
void AlignmentGroup::removeLastPoint()
{
this->_pickedPoints.pop_back();
}
int AlignmentGroup::countPoints() const
{
return this->_pickedPoints.size();
}
const std::vector<PickedPoint>& AlignmentGroup::getPoints() const
{
return this->_pickedPoints;
}
void AlignmentGroup::clearPoints()
{
this->_pickedPoints.clear();
}
void AlignmentGroup::setAlignable(bool align)
{
std::vector<Gui::ViewProviderDocumentObject*>::iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it) {
auto pAlignMode = dynamic_cast<App::PropertyBool*>((*it)->getPropertyByName("AlignMode"));
if (pAlignMode) {
pAlignMode->setValue(align);
}
// leaving alignment mode
else if (!align){
auto pAppearance =
dynamic_cast<App::PropertyMaterial*>((*it)->getPropertyByName("ShapeAppearance"));
if (pAppearance) {
pAppearance->touch(); // resets to color defined by property
}
}
}
}
void AlignmentGroup::moveTo(AlignmentGroup& that)
{
std::vector<Gui::ViewProviderDocumentObject*>::iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it)
that._views.push_back(*it);
this->_views.clear();
}
void AlignmentGroup::clear()
{
this->_views.clear();
this->_pickedPoints.clear();
}
bool AlignmentGroup::isEmpty() const
{
return this->_views.empty();
}
int AlignmentGroup::count() const
{
return this->_views.size();
}
Base::BoundBox3d AlignmentGroup::getBoundingBox() const
{
Base::BoundBox3d box;
std::vector<Gui::ViewProviderDocumentObject*>::const_iterator it;
for (it = this->_views.begin(); it != this->_views.end(); ++it) {
if ((*it)->isDerivedFrom<Gui::ViewProviderGeometryObject>()) {
auto geo = (*it)->getObject<App::GeoFeature>();
const App::PropertyComplexGeoData* prop = geo->getPropertyOfGeometry();
if (prop)
box.Add(prop->getBoundingBox());
}
}
return box;
}
// ------------------------------------------------------------------
MovableGroup::MovableGroup() = default;
MovableGroup::~MovableGroup() = default;
// ------------------------------------------------------------------
FixedGroup::FixedGroup() = default;
FixedGroup::~FixedGroup() = default;
// ------------------------------------------------------------------
MovableGroupModel::MovableGroupModel() = default;
MovableGroupModel::~MovableGroupModel() = default;
void MovableGroupModel::addGroup(const MovableGroup& grp)
{
this->_groups.push_back(grp);
}
void MovableGroupModel::addGroups(const std::map<int, MovableGroup>& grps)
{
for (const auto & grp : grps)
this->_groups.push_back(grp.second);
}
void MovableGroupModel::removeActiveGroup()
{
this->_groups.erase(this->_groups.begin());
}
MovableGroup& MovableGroupModel::activeGroup()
{
// Make sure that the array is not empty
if (this->_groups.empty())
throw Base::RuntimeError("Empty group");
return *(this->_groups.begin());
}
const MovableGroup& MovableGroupModel::activeGroup() const
{
// Make sure that the array is not empty
if (this->_groups.empty())
throw Base::RuntimeError("Empty group");
return this->_groups.front();
}
void MovableGroupModel::continueAlignment()
{
if (!isEmpty())
removeActiveGroup();
}
void MovableGroupModel::clear()
{
this->_groups.clear();
}
bool MovableGroupModel::isEmpty() const
{
return this->_groups.empty();
}
int MovableGroupModel::count() const
{
return this->_groups.size();
}
const MovableGroup& MovableGroupModel::getGroup(int i) const
{
if (i >= count())
throw Base::IndexError("Index out of range");
return this->_groups[i];
}
Base::BoundBox3d MovableGroupModel::getBoundingBox() const
{
Base::BoundBox3d box;
std::vector<MovableGroup>::const_iterator it;
for (it = this->_groups.begin(); it != this->_groups.end(); ++it) {
box.Add(it->getBoundingBox());
}
return box;
}
// ------------------------------------------------------------------
namespace Gui {
class AlignmentView : public Gui::AbstractSplitView
{
public:
QLabel* myLabel;
AlignmentView(Gui::Document* pcDocument, QWidget* parent, Qt::WindowFlags wflags=Qt::WindowFlags())
: AbstractSplitView(pcDocument, parent, wflags)
{
//anti-aliasing settings
bool smoothing = false;
bool glformat = false;
int samples = View3DInventorViewer::getNumSamples();
QSurfaceFormat f;
if (samples > 1) {
glformat = true;
f.setSamples(samples);
}
else if (samples > 0) {
smoothing = true;
}
QSplitter* mainSplitter=nullptr;
mainSplitter = new QSplitter(Qt::Horizontal, this);
if (glformat) {
_viewer.push_back(new View3DInventorViewer(f, mainSplitter));
_viewer.push_back(new View3DInventorViewer(f, mainSplitter));
}
else {
_viewer.push_back(new View3DInventorViewer(mainSplitter));
_viewer.push_back(new View3DInventorViewer(mainSplitter));
}
setDocumentOfViewers(pcDocument);
auto vbox = new QFrame(this);
auto layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
vbox->setLayout(layout);
myLabel = new QLabel(this);
myLabel->setAutoFillBackground(true);
QPalette pal = myLabel->palette();
pal.setColor(QPalette::Window, Qt::darkGray);
pal.setColor(QPalette::WindowText, Qt::white);
myLabel->setPalette(pal);
mainSplitter->setPalette(pal);
myLabel->setAlignment(Qt::AlignCenter);
myLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QFont font = myLabel->font();
font.setPointSize(14);
myLabel->setFont(font);
layout->addWidget(myLabel);
layout->addWidget(mainSplitter);
vbox->show();
setCentralWidget(vbox);
// apply the user settings
setupSettings();
if (smoothing) {
for (const auto & i : _viewer)
i->getSoRenderManager()->getGLRenderAction()->setSmoothing(true);
}
static_cast<SoGroup*>(getViewer(0)->getSoRenderManager()->getSceneGraph())->
addChild(setupHeadUpDisplay(tr("Movable object")));
static_cast<SoGroup*>(getViewer(1)->getSoRenderManager()->getSceneGraph())->
addChild(setupHeadUpDisplay(tr("Fixed object")));
}
~AlignmentView() override = default;
PyObject* getPyObject() override
{
Py_Return;
}
bool canClose() override
{
return false;
}
SoNode* setupHeadUpDisplay(const QString& text) const
{
auto hudRoot = new SoSeparator;
hudRoot->ref();
auto hudCam = new SoOrthographicCamera();
hudCam->viewportMapping = SoCamera::LEAVE_ALONE;
// Set the position in the window.
// [0, 0] is in the center of the screen.
//
auto hudTrans = new SoTranslation;
hudTrans->translation.setValue(-0.95f, -0.95f, 0.0f);
QFont font = this->font();
font.setPointSize(24);
QFontMetrics fm(font);
QColor front;
front.setRgbF(0.8f, 0.8f, 0.8f);
int w = QtTools::horizontalAdvance(fm, text);
int h = fm.height();
QImage image(w,h,QImage::Format_ARGB32_Premultiplied);
image.fill(0x00000000);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(front);
painter.setFont(font);
painter.drawText(0,0,w,h,Qt::AlignLeft,text);
painter.end();
SoSFImage sfimage;
Gui::BitmapFactory().convert(image, sfimage);
auto hudImage = new SoImage();
hudImage->image = sfimage;
// Assemble the parts...
//
hudRoot->addChild(hudCam);
hudRoot->addChild(hudTrans);
hudRoot->addChild(hudImage);
return hudRoot;
}
};
}
class ManualAlignment::Private {
public:
SoSeparator * picksepLeft;
SoSeparator * picksepRight;
SoNodeSensor* sensorCam1{nullptr};
SoNodeSensor* sensorCam2{nullptr};
SbRotation rot_cam1, rot_cam2;
SbVec3f pos_cam1, pos_cam2;
Private()
{
// left view
picksepLeft = new SoSeparator;
picksepLeft->ref();
// right view
picksepRight = new SoSeparator;
picksepRight->ref();
}
~Private()
{
picksepLeft->unref();
picksepRight->unref();
delete sensorCam1;
delete sensorCam2;
}
static
void reorientCamera(SoCamera * cam, const SbRotation & rot)
{
if (!cam)
return;
// Find global coordinates of focal point.
SbVec3f direction;
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
SbVec3f focalpoint = cam->position.getValue() +
cam->focalDistance.getValue() * direction;
// Set new orientation value by accumulating the new rotation.
cam->orientation = rot * cam->orientation.getValue();
// Reposition camera so we are still pointing at the same old focal point.
cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
cam->position = focalpoint - cam->focalDistance.getValue() * direction;
}
static
void copyCameraSettings(SoCamera* cam1, SbRotation& rot_cam1, SbVec3f& pos_cam1,
SoCamera* cam2, SbRotation& rot_cam2, SbVec3f& pos_cam2)
{
Q_UNUSED(pos_cam2);
// recompute the diff we have applied to the camera's orientation
SbRotation rot = cam1->orientation.getValue();
SbRotation dif = rot * rot_cam1.inverse();
rot_cam1 = rot;
// copy the values
cam2->enableNotify(false);
cam2->nearDistance = cam1->nearDistance;
cam2->farDistance = cam1->farDistance;
cam2->focalDistance = cam1->focalDistance;
reorientCamera(cam2,dif);
rot_cam2 = cam2->orientation.getValue();
// reverse engineer the translation part in wc
SbVec3f pos = cam1->position.getValue();
SbVec3f difpos = pos - pos_cam1;
pos_cam1 = pos;
// the translation in pixel coords
cam1->orientation.getValue().inverse().multVec(difpos,difpos);
// the translation again in wc for the second camera
cam2->orientation.getValue().multVec(difpos,difpos);
cam2->position.setValue(cam2->position.getValue()+difpos);
if (cam1->getTypeId() == cam2->getTypeId()) {
if (cam1->getTypeId() == SoOrthographicCamera::getClassTypeId())
static_cast<SoOrthographicCamera*>(cam2)->height =
static_cast<SoOrthographicCamera*>(cam1)->height;
}
cam2->enableNotify(true);
}
static
void syncCameraCB(void * data, SoSensor * s)
{
auto self = static_cast<ManualAlignment*>(data);
if (!self->myViewer)
return; // already destroyed
SoCamera* cam1 = self->myViewer->getViewer(0)->getSoRenderManager()->getCamera();
SoCamera* cam2 = self->myViewer->getViewer(1)->getSoRenderManager()->getCamera();
if (!cam1 || !cam2)
return; // missing camera
auto sensor = static_cast<SoNodeSensor*>(s);
SoNode* node = sensor->getAttachedNode();
if (node && node->getTypeId().isDerivedFrom(SoCamera::getClassTypeId())) {
if (node == cam1) {
Private::copyCameraSettings(cam1, self->d->rot_cam1, self->d->pos_cam1,
cam2, self->d->rot_cam2, self->d->pos_cam2);
self->myViewer->getViewer(1)->redraw();
}
else if (node == cam2) {
Private::copyCameraSettings(cam2, self->d->rot_cam2, self->d->pos_cam2,
cam1, self->d->rot_cam1, self->d->pos_cam1);
self->myViewer->getViewer(0)->redraw();
}
}
}
static Base::Placement
transformation2x2(const Base::Vector3d& plane1_base,
const Base::Vector3d& plane1_xaxis,
const Base::Vector3d& plane2_base,
const Base::Vector3d& plane2_xaxis)
{
// the transformation is:
// * move from plane1_base to plane2_base
// * rotate from plane1_zaxis to plane2_zaxis around plane2_base as center point
Base::Rotation rot(plane1_xaxis, plane2_xaxis);
Base::Vector3d pln_base;
rot.multVec(plane1_base,pln_base);
Base::Vector3d dif = plane2_base - pln_base;
return {dif, rot};
}
static Base::Placement
transformation3x3(const Base::Vector3d& plane1_base,
const Base::Vector3d& plane1_zaxis,
const Base::Vector3d& plane1_xaxis,
const Base::Vector3d& plane2_base,
const Base::Vector3d& plane2_zaxis,
const Base::Vector3d& plane2_xaxis)
{
// the transformation is:
// * move from plane1_base to plane2_base
// * rotate from plane1_zaxis to plane2_zaxis around plane2_base as center point
Base::Rotation rot(plane1_zaxis, plane2_zaxis);
// first transformation to align the plane normals and base points
Base::Vector3d dif1 = plane1_base;
rot.multVec(dif1,dif1);
dif1 = plane2_base - dif1;
Base::Placement plm1(dif1, rot);
// second transformation to align the planes' x axes
Base::Vector3d pln_xaxis;
rot.multVec(plane1_xaxis,pln_xaxis);
Base::Rotation rot2(pln_xaxis, plane2_xaxis);
Base::Vector3d dif2 = plane2_base;
rot2.multVec(dif2,dif2);
dif2 = plane2_base - dif2;
Base::Placement plm2(dif2, rot2);
plm2 = plm2 * plm1;
return plm2;
}
};
/* TRANSLATOR Gui::ManualAlignment */
ManualAlignment* ManualAlignment::_instance = nullptr;
/**
* Construction.
*/
ManualAlignment::ManualAlignment()
: myViewer(nullptr), myDocument(nullptr), myPickPoints(3), d(new Private)
{
//NOLINTBEGIN
// connect with the application's signal for deletion of documents
this->connectApplicationDeletedDocument = Gui::Application::Instance->signalDeleteDocument
.connect(std::bind(&ManualAlignment::slotDeletedDocument, this, sp::_1));
//NOLINTEND
// setup sensor connection
d->sensorCam1 = new SoNodeSensor(Private::syncCameraCB, this);
d->sensorCam2 = new SoNodeSensor(Private::syncCameraCB, this);
}
/**
* Destruction.
*/
ManualAlignment::~ManualAlignment()
{
this->connectDocumentDeletedObject.disconnect();
this->connectApplicationDeletedDocument.disconnect();
closeViewer();
delete d;
_instance = nullptr;
}
/**
* Creates the one and only instance of this class.
*/
ManualAlignment* ManualAlignment::instance()
{
// not initialized?
if (!_instance)
_instance = new ManualAlignment();
return _instance;
}
/**
* Destructs the one and only instance of this class.
*/
void ManualAlignment::destruct()
{
if (_instance) {
ManualAlignment* tmp = _instance;
_instance = nullptr;
delete tmp;
}
}
/**
* Checks whether the one instance exists.
*/
bool ManualAlignment::hasInstance()
{
return _instance != nullptr;
}
void ManualAlignment::setMinPoints(int minPoints)
{
if ((minPoints > 0) && (minPoints <= 3))
myPickPoints = minPoints;
}
void ManualAlignment::setFixedGroup(const FixedGroup& fixed)
{
this->myFixedGroup = fixed;
this->myDocument = fixed.getDocument();
}
void ManualAlignment::setModel(const MovableGroupModel& model)
{
this->myAlignModel = model;
}
void ManualAlignment::clearAll()
{
myFixedGroup.clear();
myAlignModel.clear();
myDocument = nullptr;
}
void ManualAlignment::setViewingDirections(const Base::Vector3d& view1, const Base::Vector3d& up1,
const Base::Vector3d& view2, const Base::Vector3d& up2)
{
if (myViewer.isNull())
return;
{
SbVec3f vz(-view1.x, -view1.y, -view1.z);
vz.normalize();
SbVec3f vy(up1.x, up1.y, up1.z);
vy.normalize();
SbVec3f vx = vy.cross(vz);
vy = vz.cross(vx);
SbMatrix rot = SbMatrix::identity();
rot[0][0] = vx[0];
rot[0][1] = vx[1];
rot[0][2] = vx[2];
rot[1][0] = vy[0];
rot[1][1] = vy[1];
rot[1][2] = vy[2];
rot[2][0] = vz[0];
rot[2][1] = vz[1];
rot[2][2] = vz[2];
SbRotation total(rot);
myViewer->getViewer(0)->getSoRenderManager()->getCamera()->orientation.setValue(total);
myViewer->getViewer(0)->viewAll();
}
{
SbVec3f vz(-view2.x, -view2.y, -view2.z);
vz.normalize();
SbVec3f vy(up2.x, up2.y, up2.z);
vy.normalize();
SbVec3f vx = vy.cross(vz);
vy = vz.cross(vx);
SbMatrix rot = SbMatrix::identity();
rot[0][0] = vx[0];
rot[0][1] = vx[1];
rot[0][2] = vx[2];
rot[1][0] = vy[0];
rot[1][1] = vy[1];
rot[1][2] = vy[2];
rot[2][0] = vz[0];
rot[2][1] = vz[1];
rot[2][2] = vz[2];
SbRotation total(rot);
myViewer->getViewer(1)->getSoRenderManager()->getCamera()->orientation.setValue(total);
myViewer->getViewer(1)->viewAll();
}
}
/**
* Performs the alignment for the specified aligned and non-aligned views specified by setModel() and setFixedGroup().
*/
void ManualAlignment::startAlignment(Base::Type mousemodel)
{
// allow only one alignment at a time
if (!myViewer.isNull()) {
QMessageBox::warning(qApp->activeWindow(), tr("Manual alignment"), tr("The alignment is already in progress."));
return;
}
myTransform = Base::Placement();
if (myFixedGroup.isEmpty())
return;
if (myAlignModel.isEmpty())
return;
// create a split window for picking the points
myViewer = new AlignmentView(myDocument,Gui::getMainWindow());
myViewer->setWindowTitle(tr("Alignment[*]"));
myViewer->setWindowIcon(QApplication::windowIcon());
myViewer->resize(400, 300);
Gui::getMainWindow()->addWindow(myViewer);
myViewer->showMaximized();
int n = this->myPickPoints;
QString msg = n == 1
? tr("Select at least 1 point in the left and the right view")
: tr("Select at least %1 points in the left and the right view").arg(n);
myViewer->myLabel->setText(msg);
connect(myViewer, &QObject::destroyed, this, &ManualAlignment::reset);
// show all aligned views in the 2nd view
myFixedGroup.addToViewer(myViewer->getViewer(1));
myFixedGroup.setAlignable(true);
// set picked points root
SoNode* node1 = myViewer->getViewer(0)->getSceneGraph();
if (node1->getTypeId().isDerivedFrom(SoGroup::getClassTypeId())){
((SoGroup*)node1)->addChild(d->picksepLeft);
}
SoNode* node2 = myViewer->getViewer(1)->getSceneGraph();
if (node2->getTypeId().isDerivedFrom(SoGroup::getClassTypeId())){
((SoGroup*)node2)->addChild(d->picksepRight);
}
myViewer->getViewer(0)->setEditing(true);
myViewer->getViewer(0)->addEventCallback(SoMouseButtonEvent::getClassTypeId(),
ManualAlignment::probePickedCallback);
myViewer->getViewer(1)->setEditing(true);
myViewer->getViewer(1)->addEventCallback(SoMouseButtonEvent::getClassTypeId(),
ManualAlignment::probePickedCallback);
// apply the mouse model
myViewer->getViewer(0)->setNavigationType(mousemodel);
myViewer->getViewer(1)->setNavigationType(mousemodel);
// Connect to the document's signal as we want to be notified when something happens
if (this->connectDocumentDeletedObject.connected())
this->connectDocumentDeletedObject.disconnect();
//NOLINTBEGIN
this->connectDocumentDeletedObject = myDocument->signalDeletedObject.connect(std::bind
(&ManualAlignment::slotDeletedObject, this, sp::_1));
//NOLINTEND
continueAlignment();
}
/**
* If still one view needs to be aligned then it is shown in the first window. If all views are aligned the process will be terminated.
*/
void ManualAlignment::continueAlignment()
{
myFixedGroup.clearPoints();
coinRemoveAllChildren(d->picksepLeft);
coinRemoveAllChildren(d->picksepRight);
if (!myAlignModel.isEmpty()) {
AlignmentGroup& grp = myAlignModel.activeGroup();
grp.clearPoints();
grp.addToViewer(myViewer->getViewer(0));
grp.setAlignable(true);
Gui::getMainWindow()->showMessage(tr("Select points in the left and right view"));
myViewer->getViewer(0)->setEditingCursor(QCursor(Qt::PointingHandCursor));
myViewer->getViewer(1)->setEditingCursor(QCursor(Qt::PointingHandCursor));
}
else {
finish();
}
}
void ManualAlignment::closeViewer()
{
if (!myViewer)
return;
// Close the viewer
if (myViewer->parentWidget())
myViewer->parentWidget()->deleteLater();
myViewer = nullptr;
}
/**
* Make all views unpickable and resets internal data.
*/
void ManualAlignment::reset()
{
if (!myAlignModel.isEmpty()) {
myAlignModel.activeGroup().setAlignable(false);
myAlignModel.activeGroup().clear();
myAlignModel.clear();
}
myFixedGroup.setAlignable(false);
myFixedGroup.clear();
coinRemoveAllChildren(d->picksepLeft);
coinRemoveAllChildren(d->picksepRight);
if (myDocument) {
this->connectDocumentDeletedObject.disconnect();
myDocument = nullptr;
}
}
/**
* Terminates the process and closes the windows.
*/
void ManualAlignment::finish()
{
if (myViewer.isNull())
return;
if (myDocument)
myDocument->getDocument()->recompute();
closeViewer();
reset();
Gui::getMainWindow()->showMessage(tr("The alignment has finished"));
// If an event receiver has been defined send the manual alignment finished event to it
Q_EMIT emitFinished();
}
/**
* Cancels the process and closes the windows without performing an alignment.
*/
void ManualAlignment::cancel()
{
if (myViewer.isNull())
return;
closeViewer();
myTransform = Base::Placement();
reset();
Gui::getMainWindow()->showMessage(tr("The alignment has been canceled"));
// If an event receiver has been defined send the manual alignment cancelled event to it
Q_EMIT emitCanceled();
}
void ManualAlignment::align()
{
// Now we can start the actual alignment
if (myAlignModel.activeGroup().countPoints() < myPickPoints) {
QMessageBox::warning(myViewer, tr("Manual alignment"),
tr("Too few points picked in the left view."
" At least %1 points are needed.").arg(myPickPoints));
}
else if (myFixedGroup.countPoints() < myPickPoints) {
QMessageBox::warning(myViewer, tr("Manual alignment"),
tr("Too few points picked in the right view."
" At least %1 points are needed.").arg(myPickPoints));
}
else if (myAlignModel.activeGroup().countPoints() != myFixedGroup.countPoints()) {
QMessageBox::warning(myViewer, tr("Manual alignment"),
tr("Different number of points picked in left and right view.\n"
"On the left view %1 points are picked,\n"
"on the right view %2 points are picked.")
.arg(myAlignModel.activeGroup().countPoints())
.arg(myFixedGroup.countPoints()));
}
else {
// do not allow one to pick further points
myAlignModel.activeGroup().removeFromViewer(myViewer->getViewer(0));
myAlignModel.activeGroup().setAlignable(false);
std::vector<App::DocumentObject*> pViews = myAlignModel.activeGroup().getViews();
Gui::getMainWindow()->showMessage(tr("Try to align group of views"));
// Compute alignment
bool ok = computeAlignment(myAlignModel.activeGroup().getPoints(), myFixedGroup.getPoints());
if (ok && myDocument) {
// Align views
myDocument->openCommand(QT_TRANSLATE_NOOP("Command", "Align"));
for (const auto & pView : pViews)
alignObject(pView);
myDocument->commitCommand();
// the alignment was successful so show it in the right view now
//myAlignModel.activeGroup().setRandomColor();
myAlignModel.activeGroup().setAlignable(true);
myAlignModel.activeGroup().addToViewer(myViewer->getViewer(1));
myAlignModel.activeGroup().moveTo(myFixedGroup);
myAlignModel.continueAlignment();
}
else {
// Inform user that alignment failed
auto ret = QMessageBox::critical(myViewer, tr("Manual alignment"),
tr("The alignment failed.\nHow do you want to proceed?"),
QMessageBox::Retry | QMessageBox::Ignore | QMessageBox::Abort);
if ( ret == QMessageBox::Ignore ) {
myAlignModel.continueAlignment();
}
else if ( ret == QMessageBox::Abort ) {
finish();
return;
}
}
continueAlignment();
}
}
void ManualAlignment::showInstructions()
{
// Now we can start the actual alignment
if (myAlignModel.activeGroup().countPoints() < myPickPoints) {
Gui::getMainWindow()->showMessage(
tr("Too few points picked in the left view."
" At least %1 points are needed.").arg(myPickPoints));
}
else if (myFixedGroup.countPoints() < myPickPoints) {
Gui::getMainWindow()->showMessage(
tr("Too few points picked in the right view."
" At least %1 points are needed.").arg(myPickPoints));
}
else if (myAlignModel.activeGroup().countPoints() != myFixedGroup.countPoints()) {
Gui::getMainWindow()->showMessage(
tr("Different number of points picked in left and right view. "
"On the left view %1 points are picked, "
"on the right view %2 points are picked.")
.arg(myAlignModel.activeGroup().countPoints())
.arg(myFixedGroup.countPoints()));
}
}
bool ManualAlignment::canAlign() const
{
if (myAlignModel.activeGroup().countPoints() == myFixedGroup.countPoints()) {
if (myFixedGroup.countPoints() >= myPickPoints)
return true;
}
return false;
}
/**
* This method computes the alignment. For the calculation of the alignment the picked points of both views
* are taken. If the alignment fails false is returned, true otherwise.
*/
bool ManualAlignment::computeAlignment(const std::vector<PickedPoint>& movPts,
const std::vector<PickedPoint>& fixPts)
{
assert((int)movPts.size() >= myPickPoints);
assert((int)fixPts.size() >= myPickPoints);
assert((int)movPts.size() == (int)fixPts.size());
myTransform = Base::Placement();
if (movPts.size() == 1) {
// 1 point partial solution: Simple translation only
myTransform.setPosition(fixPts[0].point - movPts[0].point);
}
else if (movPts.size() == 2) {
const Base::Vector3d& p1 = movPts[0].point;
const Base::Vector3d& p2 = movPts[1].point;
Base::Vector3d d1 = p2-p1;
d1.Normalize();
const Base::Vector3d& q1 = fixPts[0].point;
const Base::Vector3d& q2 = fixPts[1].point;
Base::Vector3d d2 = q2-q1;
d2.Normalize();
myTransform = Private::transformation2x2(p1, d1, q1, d2);
}
else if (movPts.size() >= 3) {
const Base::Vector3d& p1 = movPts[0].point;
const Base::Vector3d& p2 = movPts[1].point;
const Base::Vector3d& p3 = movPts[2].point;
Base::Vector3d d1 = p2-p1;
d1.Normalize();
Base::Vector3d n1 = (p2-p1) % (p3-p1);
n1.Normalize();
const Base::Vector3d& q1 = fixPts[0].point;
const Base::Vector3d& q2 = fixPts[1].point;
const Base::Vector3d& q3 = fixPts[2].point;
Base::Vector3d d2 = q2-q1;
d2.Normalize();
Base::Vector3d n2 = (q2-q1) % (q3-q1);
n2.Normalize();
myTransform = Private::transformation3x3(p1, d1, n1, q1, d2, n2);
}
return true;
}
/**
* This method performs the actual alignment of view \a pView.
*/
void ManualAlignment::alignObject(App::DocumentObject *obj)
{
if (obj->isDerivedFrom<App::GeoFeature>()) {
auto geom = static_cast<App::GeoFeature*>(obj);
geom->transformPlacement(this->myTransform);
}
}
/**
* Creates a point element as visible feedback for the user.
*/
SoNode* ManualAlignment::pickedPointsSubGraph(const SbVec3f& p, const SbVec3f& n, int id)
{
static const float color_table [10][3] = {
{1.0f,0.0f,0.0f}, // red
{0.0f,1.0f,0.0f}, // green
{0.0f,0.0f,1.0f}, // blue
{1.0f,1.0f,0.0f}, // yellow
{0.0f,1.0f,1.0f}, // cyan
{0.7f,0.0f,0.0f},
{0.0f,0.7f,0.0f},
{0.7f,0.7f,0.0f},
{0.7f,0.0f,0.5f},
{1.0f,0.7f,0.0f}
};
int index = (id-1) % 10;
auto probe = new SoRegPoint();
probe->base.setValue(p);
probe->normal.setValue(n);
probe->color.setValue(color_table[index][0],color_table[index][1],color_table[index][2]);
SbString s(tr("Point_%1").arg(id).toStdString().c_str());
probe->text.setValue(s);
return probe;
}
/**
* Handle if the current document is about to being closed.
*/
void ManualAlignment::slotDeletedDocument(const Gui::Document& Doc)
{
if (&Doc == this->myDocument)
reset();
}
/**
* Handle if the a view provider is about to being destroyed.
*/
void ManualAlignment::slotDeletedObject(const Gui::ViewProvider& Obj)
{
// remove the view provider either from the left or the right view
if (Obj.isDerivedFrom<Gui::ViewProviderDocumentObject>()) {
// remove the view provider immediately from the split window
bool found = false;
auto vp = const_cast<Gui::ViewProviderDocumentObject*>
(static_cast<const Gui::ViewProviderDocumentObject*>(&Obj));
if (myAlignModel.activeGroup().hasView(vp)) {
myViewer->getViewer(0)->removeViewProvider(vp);
found = true;
}
if (myFixedGroup.hasView(vp)) {
myViewer->getViewer(1)->removeViewProvider(vp);
found = true;
}
if (found)
cancel();
}
}
void ManualAlignment::onAlign()
{
align();
}
void ManualAlignment::onRemoveLastPointMoveable()
{
int nPoints = myAlignModel.activeGroup().countPoints();
if (nPoints > 0) {
myAlignModel.activeGroup().removeLastPoint();
d->picksepLeft->removeChild(nPoints-1);
}
}
void ManualAlignment::onRemoveLastPointFixed()
{
int nPoints = myFixedGroup.countPoints();
if (nPoints > 0) {
myFixedGroup.removeLastPoint();
d->picksepRight->removeChild(nPoints-1);
}
}
void ManualAlignment::onClear()
{
myAlignModel.activeGroup().clear();
myFixedGroup.clear();
coinRemoveAllChildren(d->picksepLeft);
coinRemoveAllChildren(d->picksepRight);
}
void ManualAlignment::onCancel()
{
cancel();
}
void ManualAlignment::probePickedCallback(void * ud, SoEventCallback * n)
{
Q_UNUSED(ud);
auto view = static_cast<Gui::View3DInventorViewer*>(n->getUserData());
const SoEvent* ev = n->getEvent();
if (ev->getTypeId() == SoMouseButtonEvent::getClassTypeId()) {
// set as handled
n->getAction()->setHandled();
n->setHandled();
auto mbe = static_cast<const SoMouseButtonEvent *>(ev);
if (mbe->getButton() == SoMouseButtonEvent::BUTTON1 && mbe->getState() == SoButtonEvent::DOWN) {
// if we are in 'align' mode then handle the click event
ManualAlignment* self = ManualAlignment::instance();
// Get the closest point to the camera of the whole scene.
// This point doesn't need to be part of this view provider.
Gui::WaitCursor wc;
const SoPickedPoint * point = view->getPickedPoint(n);
if (point) {
auto vp = static_cast<Gui::ViewProvider*>(view->getViewProviderByPath(point->getPath()));
if (vp && vp->isDerivedFrom<Gui::ViewProviderDocumentObject>()) {
auto that = static_cast<Gui::ViewProviderDocumentObject*>(vp);
if (self->applyPickedProbe(that, point)) {
const SbVec3f& vec = point->getPoint();
Gui::getMainWindow()->showMessage(
tr("Point picked at (%1,%2,%3)")
.arg(vec[0]).arg(vec[1]).arg(vec[2]));
}
else {
Gui::getMainWindow()->showMessage(
tr("No point was found on model"));
}
}
}
else {
Gui::getMainWindow()->showMessage(
tr("No point was picked"));
}
}
else if (mbe->getButton() == SoMouseButtonEvent::BUTTON2 && mbe->getState() == SoButtonEvent::UP) {
ManualAlignment* self = ManualAlignment::instance();
if (self->myAlignModel.isEmpty() || self->myFixedGroup.isEmpty())
return;
self->showInstructions();
int nPoints;
if (view == self->myViewer->getViewer(0))
nPoints = self->myAlignModel.activeGroup().countPoints();
else
nPoints = self->myFixedGroup.countPoints();
QMenu menu;
QAction* fi = menu.addAction(tr("&Align"));
QAction* rem = menu.addAction(tr("&Remove Last Point"));
//QAction* cl = menu.addAction("C&lear");
QAction* ca = menu.addAction(tr("&Cancel"));
fi->setEnabled(self->canAlign());
rem->setEnabled(nPoints > 0);
menu.addSeparator();
QAction* sync = menu.addAction(tr("&Synchronize Views"));
sync->setCheckable(true);
if (self->d->sensorCam1->getAttachedNode())
sync->setChecked(true);
QAction* id = menu.exec(QCursor::pos());
if (id == fi) {
// call align->align();
QTimer::singleShot(300, self, &ManualAlignment::onAlign);
}
else if ((id == rem) && (view == self->myViewer->getViewer(0))) {
QTimer::singleShot(300, self, &ManualAlignment::onRemoveLastPointMoveable);
}
else if ((id == rem) && (view == self->myViewer->getViewer(1))) {
QTimer::singleShot(300, self, &ManualAlignment::onRemoveLastPointFixed);
}
//else if (id == cl) {
// // call align->clear();
// QTimer::singleShot(300, self, &ManualAlignment::onClear);
//}
else if (id == ca) {
// call align->cancel();
QTimer::singleShot(300, self, &ManualAlignment::onCancel);
}
else if (id == sync) {
// setup sensor connection
if (sync->isChecked()) {
SoCamera* cam1 = self->myViewer->getViewer(0)->getSoRenderManager()->getCamera();
SoCamera* cam2 = self->myViewer->getViewer(1)->getSoRenderManager()->getCamera();
if (cam1 && cam2) {
self->d->sensorCam1->attach(cam1);
self->d->rot_cam1 = cam1->orientation.getValue();
self->d->pos_cam1 = cam1->position.getValue();
self->d->sensorCam2->attach(cam2);
self->d->rot_cam2 = cam2->orientation.getValue();
self->d->pos_cam2 = cam2->position.getValue();
}
}
else {
self->d->sensorCam1->detach();
self->d->sensorCam2->detach();
}
}
}
}
}
/**
* This method stores the picked point \a pnt from the view provider \a prov. If enough points in both windows have been picked
* the alignment gets invoked.
*/
bool ManualAlignment::applyPickedProbe(Gui::ViewProviderDocumentObject* prov, const SoPickedPoint* pnt)
{
const SbVec3f& vec = pnt->getPoint();
const SbVec3f& nor = pnt->getNormal();
// add to the list for the non-aligned view in the left view
if (myAlignModel.activeGroup().hasView(prov)) {
std::vector<Base::Vector3d> pts = prov->getModelPoints(pnt);
if (pts.empty())
return false;
PickedPoint pp;
pp.point = pts.front();
myAlignModel.activeGroup().addPoint(pp);
// Adds a point marker for the picked point.
d->picksepLeft->addChild(pickedPointsSubGraph(vec, nor, myAlignModel.activeGroup().countPoints()));
return true;
}
else if (myFixedGroup.hasView(prov)) {
std::vector<Base::Vector3d> pts = prov->getModelPoints(pnt);
if (pts.empty())
return false;
PickedPoint pp;
pp.point = pts.front();
myFixedGroup.addPoint(pp);
// Adds a point marker for the picked point.
d->picksepRight->addChild(pickedPointsSubGraph(vec, nor, myFixedGroup.countPoints()));
return true;
}
return false;
}
#include "moc_ManualAlignment.cpp"