1473 lines
45 KiB
C++
1473 lines
45 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 <QMdiSubWindow>
|
|
#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;
|
|
}
|
|
};
|
|
} // namespace Gui
|
|
|
|
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;
|
|
}
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
// save the current window state before opening the alignment viewer
|
|
previousWindowState = Qt::WindowNoState;
|
|
if (auto* activeDoc = Gui::Application::Instance->activeDocument()) {
|
|
if (auto* activeView = activeDoc->getActiveView()) {
|
|
if (auto* subWindow = qobject_cast<QMdiSubWindow*>(activeView->parentWidget())) {
|
|
previousWindowState = subWindow->windowState();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
QTimer::singleShot(0, [this]() {
|
|
auto* activeDoc = Gui::Application::Instance->activeDocument();
|
|
if (!activeDoc) {
|
|
return;
|
|
}
|
|
|
|
auto* activeView = activeDoc->getActiveView();
|
|
if (!activeView) {
|
|
return;
|
|
}
|
|
|
|
Gui::getMainWindow()->setActiveWindow(activeView);
|
|
if (auto* subWindow = qobject_cast<QMdiSubWindow*>(activeView->parentWidget())) {
|
|
// restore the previous window state
|
|
if (previousWindowState & Qt::WindowMaximized) {
|
|
subWindow->showMaximized();
|
|
}
|
|
else if (previousWindowState & Qt::WindowMinimized) {
|
|
subWindow->showMinimized();
|
|
}
|
|
else {
|
|
subWindow->showNormal();
|
|
}
|
|
}
|
|
});
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* 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"
|