Some checks failed
Build and Test / build (pull_request) Has been cancelled
Convert BreadcrumbToolBar from a QToolBar in the MainWindow toolbar area to a QFrame overlay at the top-left of each 3D viewport. Changes: - BreadcrumbToolBar: change base class from QToolBar to QFrame, use QHBoxLayout instead of toolbar actions, semi-transparent overlay background with rounded corners - MainWindow: remove breadcrumb toolbar creation, toolbar break, and signal connection - View3DInventor: create per-view BreadcrumbToolBar as a child of the GL viewer widget, positioned at (8,8) with event filter to keep it raised on viewport resize Each 3D view now has its own breadcrumb instance connected to the EditingContextResolver singleton. This reclaims the full-width toolbar row and keeps the editing context visually tied to the viewport.
982 lines
31 KiB
C++
982 lines
31 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
|
|
* *
|
|
* 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 <string>
|
|
#include <QApplication>
|
|
#include <QKeyEvent>
|
|
#include <QEvent>
|
|
#include <QDropEvent>
|
|
#include <QDragEnterEvent>
|
|
#include <QLayout>
|
|
#include <QMdiSubWindow>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QPainter>
|
|
#include <QPrinter>
|
|
#include <QPrintDialog>
|
|
#include <QPrintPreviewDialog>
|
|
#include <QStackedWidget>
|
|
#include <QSurfaceFormat>
|
|
#include <QTimer>
|
|
#include <QUrl>
|
|
#include <QWindow>
|
|
#include <Inventor/actions/SoGetPrimitiveCountAction.h>
|
|
#include <Inventor/fields/SoSFString.h>
|
|
#include <Inventor/nodes/SoOrthographicCamera.h>
|
|
#include <Inventor/nodes/SoPerspectiveCamera.h>
|
|
#include <Inventor/nodes/SoSeparator.h>
|
|
#include <Inventor/SoPickedPoint.h>
|
|
|
|
|
|
#include <App/Application.h>
|
|
#include <App/Document.h>
|
|
#include <App/GeoFeature.h>
|
|
#include <Base/Builder3D.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Interpreter.h>
|
|
|
|
#include <Gui/PreferencePages/DlgSettingsPDF.h>
|
|
|
|
#include "View3DInventor.h"
|
|
#include "View3DSettings.h"
|
|
#include "Application.h"
|
|
#include "BreadcrumbToolBar.h"
|
|
#include "BitmapFactory.h"
|
|
#include "EditingContext.h"
|
|
#include "Camera.h"
|
|
#include "Document.h"
|
|
#include "FileDialog.h"
|
|
#include "MainWindow.h"
|
|
#include "NaviCube.h"
|
|
#include "Navigation/NavigationStyle.h"
|
|
#include "SoFCDB.h"
|
|
#include "SoFCSelectionAction.h"
|
|
#include "SoFCVectorizeSVGAction.h"
|
|
#include "View3DInventorViewer.h"
|
|
#include "View3DPy.h"
|
|
#include "ViewProvider.h"
|
|
#include "ViewProviderDocumentObject.h"
|
|
#include "WaitCursor.h"
|
|
|
|
#include "Utilities.h"
|
|
|
|
using namespace Gui;
|
|
|
|
void GLOverlayWidget::paintEvent(QPaintEvent*)
|
|
{
|
|
QPainter paint(this);
|
|
paint.drawImage(0, 0, image);
|
|
paint.end();
|
|
}
|
|
|
|
/* TRANSLATOR Gui::View3DInventor */
|
|
|
|
TYPESYSTEM_SOURCE_ABSTRACT(Gui::View3DInventor, Gui::MDIView)
|
|
|
|
View3DInventor::View3DInventor(
|
|
Gui::Document* pcDocument,
|
|
QWidget* parent,
|
|
const QOpenGLWidget* sharewidget,
|
|
Qt::WindowFlags wflags
|
|
)
|
|
: MDIView(pcDocument, parent, wflags)
|
|
, _viewerPy(nullptr)
|
|
{
|
|
stack = new QStackedWidget(this);
|
|
// important for highlighting
|
|
setMouseTracking(true);
|
|
// accept drops on the window, get handled in dropEvent, dragEnterEvent
|
|
setAcceptDrops(true);
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (glformat) {
|
|
_viewer = new View3DInventorViewer(f, this, sharewidget);
|
|
}
|
|
else {
|
|
_viewer = new View3DInventorViewer(this, sharewidget);
|
|
}
|
|
|
|
if (smoothing) {
|
|
_viewer->getSoRenderManager()->getGLRenderAction()->setSmoothing(true);
|
|
}
|
|
|
|
// create the inventor widget and set the defaults
|
|
_viewer->setDocument(this->_pcDocument);
|
|
stack->addWidget(_viewer->getWidget());
|
|
// https://forum.freecad.org/viewtopic.php?f=3&t=6055&sid=150ed90cbefba50f1e2ad4b4e6684eba
|
|
// describes a minor error but trying to fix it leads to a major issue
|
|
// https://forum.freecad.org/viewtopic.php?f=3&t=6085&sid=3f4bcab8007b96aaf31928b564190fd7
|
|
// so the change is commented out
|
|
// By default, the wheel events are processed by the 3d view AND the mdi area.
|
|
//_viewer->getGLWidget()->setAttribute(Qt::WA_NoMousePropagation);
|
|
setCentralWidget(stack);
|
|
|
|
// apply the user settings
|
|
applySettings();
|
|
|
|
// Breadcrumb overlay at top-left of viewport
|
|
_breadcrumb = new BreadcrumbToolBar(_viewer->getWidget());
|
|
_breadcrumb->move(8, 8);
|
|
_breadcrumb->raise();
|
|
_breadcrumb->show();
|
|
_viewer->getWidget()->installEventFilter(this);
|
|
|
|
auto* ctxResolver = EditingContextResolver::instance();
|
|
connect(ctxResolver, &EditingContextResolver::contextChanged,
|
|
_breadcrumb, &BreadcrumbToolBar::updateContext);
|
|
// Apply the current context immediately
|
|
_breadcrumb->updateContext(ctxResolver->currentContext());
|
|
|
|
stopSpinTimer = new QTimer(this);
|
|
connect(stopSpinTimer, &QTimer::timeout, this, &View3DInventor::stopAnimating);
|
|
|
|
setWindowIcon(Gui::BitmapFactory().pixmap("Document"));
|
|
}
|
|
|
|
View3DInventor::~View3DInventor()
|
|
{
|
|
if (_pcDocument) {
|
|
SoCamera* Cam = _viewer->getSoRenderManager()->getCamera();
|
|
if (Cam) {
|
|
_pcDocument->saveCameraSettings(SoFCDB::writeNodesToString(Cam).c_str());
|
|
}
|
|
}
|
|
|
|
viewSettings.reset();
|
|
|
|
// If we destroy this viewer by calling 'delete' directly the focus proxy widget which is
|
|
// defined by a widget in SoQtViewer isn't reset. This widget becomes a dangling pointer and
|
|
// makes the application crash. (Probably it's better to destroy this viewer by calling
|
|
// close().) See also Gui::Document::~Document().
|
|
QWidget* foc = qApp->focusWidget();
|
|
if (foc) {
|
|
QWidget* par = foc->parentWidget();
|
|
while (par) {
|
|
if (par == this) {
|
|
foc->setFocusProxy(nullptr);
|
|
foc->clearFocus();
|
|
break;
|
|
}
|
|
par = par->parentWidget();
|
|
}
|
|
}
|
|
|
|
if (_viewerPy) {
|
|
Base::PyGILStateLocker lock;
|
|
Py_DECREF(_viewerPy);
|
|
}
|
|
|
|
// here is from time to time trouble!!!
|
|
delete _viewer;
|
|
}
|
|
|
|
void View3DInventor::deleteSelf()
|
|
{
|
|
_viewer->setSceneGraph(nullptr);
|
|
_viewer->setDocument(nullptr);
|
|
MDIView::deleteSelf();
|
|
}
|
|
|
|
View3DInventor* View3DInventor::clone()
|
|
{
|
|
auto mdiView = _pcDocument->createView(getClassTypeId(), CreateViewMode::Clone);
|
|
auto view3D = static_cast<View3DInventor*>(mdiView);
|
|
|
|
view3D->cloneFrom(*this);
|
|
view3D->getViewer()->setAxisCross(getViewer()->hasAxisCross());
|
|
|
|
// FIXME: Add parameter to define behaviour by the calling instance
|
|
// View provider editing
|
|
|
|
int editMode;
|
|
ViewProvider* editViewProvider = _pcDocument->getInEdit(nullptr, nullptr, &editMode);
|
|
if (editViewProvider) {
|
|
getViewer()->resetEditingViewProvider();
|
|
view3D->getViewer()->setEditingViewProvider(editViewProvider, editMode);
|
|
}
|
|
|
|
return view3D;
|
|
}
|
|
|
|
PyObject* View3DInventor::getPyObject()
|
|
{
|
|
if (!_viewerPy) {
|
|
_viewerPy = new View3DInventorPy(this);
|
|
}
|
|
|
|
Py_INCREF(_viewerPy);
|
|
return _viewerPy;
|
|
}
|
|
|
|
void View3DInventor::applySettings()
|
|
{
|
|
viewSettings = std::make_unique<View3DSettings>(
|
|
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"),
|
|
_viewer
|
|
);
|
|
naviSettings = std::make_unique<NaviCubeSettings>(
|
|
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube"),
|
|
_viewer
|
|
);
|
|
viewSettings->applySettings();
|
|
naviSettings->applySettings();
|
|
}
|
|
|
|
void View3DInventor::onRename(Gui::Document* pDoc)
|
|
{
|
|
SoSFString name;
|
|
name.setValue(pDoc->getDocument()->getName());
|
|
SoFCDocumentAction cAct(name);
|
|
cAct.apply(_viewer->getSceneGraph());
|
|
}
|
|
|
|
void View3DInventor::onUpdate()
|
|
{
|
|
#ifdef FC_LOGUPDATECHAIN
|
|
Base::Console().log("Acti: Gui::View3DInventor::onUpdate()");
|
|
#endif
|
|
update();
|
|
_viewer->redraw();
|
|
}
|
|
|
|
void View3DInventor::viewAll()
|
|
{
|
|
_viewer->viewAll();
|
|
}
|
|
|
|
const char* View3DInventor::getName() const
|
|
{
|
|
return "View3DInventor";
|
|
}
|
|
|
|
void View3DInventor::print()
|
|
{
|
|
QPrinter printer(QPrinter::ScreenResolution);
|
|
printer.setFullPage(true);
|
|
restorePrinterSettings(&printer);
|
|
|
|
QPrintDialog dlg(&printer, this);
|
|
if (dlg.exec() == QDialog::Accepted) {
|
|
Gui::WaitCursor wc;
|
|
print(&printer);
|
|
savePrinterSettings(&printer);
|
|
}
|
|
}
|
|
|
|
void View3DInventor::printPdf()
|
|
{
|
|
QString filename = FileDialog::getSaveFileName(
|
|
this,
|
|
tr("Export PDF"),
|
|
QString(),
|
|
QStringLiteral("%1 (*.pdf)").arg(tr("PDF file"))
|
|
);
|
|
if (!filename.isEmpty()) {
|
|
Gui::WaitCursor wc;
|
|
QPrinter printer(QPrinter::ScreenResolution);
|
|
// setPdfVersion sets the printed PDF Version to what is chosen in
|
|
// Preferences/Import-Export/PDF more details under:
|
|
// https://www.kdab.com/creating-pdfa-documents-qt/
|
|
printer.setPdfVersion(Gui::Dialog::DlgSettingsPDF::evaluatePDFVersion());
|
|
printer.setOutputFormat(QPrinter::PdfFormat);
|
|
printer.setPageOrientation(QPageLayout::Landscape);
|
|
printer.setOutputFileName(filename);
|
|
printer.setCreator(QString::fromStdString(App::Application::getNameWithVersion()));
|
|
print(&printer);
|
|
}
|
|
}
|
|
|
|
void View3DInventor::printPreview()
|
|
{
|
|
QPrinter printer(QPrinter::ScreenResolution);
|
|
printer.setFullPage(true);
|
|
restorePrinterSettings(&printer);
|
|
|
|
QPrintPreviewDialog dlg(&printer, this);
|
|
connect(
|
|
&dlg,
|
|
&QPrintPreviewDialog::paintRequested,
|
|
this,
|
|
qOverload<QPrinter*>(&View3DInventor::print)
|
|
);
|
|
dlg.exec();
|
|
savePrinterSettings(&printer);
|
|
}
|
|
|
|
void View3DInventor::print(QPrinter* printer)
|
|
{
|
|
QPainter p(printer);
|
|
p.setRenderHints(QPainter::Antialiasing);
|
|
if (!p.isActive() && !printer->outputFileName().isEmpty()) {
|
|
qApp->setOverrideCursor(Qt::ArrowCursor);
|
|
QMessageBox::critical(
|
|
this,
|
|
tr("Opening file failed"),
|
|
tr("Can't open file '%1' for writing.").arg(printer->outputFileName())
|
|
);
|
|
qApp->restoreOverrideCursor();
|
|
return;
|
|
}
|
|
|
|
QRect rect = printer->pageLayout().paintRectPixels(printer->resolution());
|
|
QImage img;
|
|
_viewer->imageFromFramebuffer(rect.width(), rect.height(), 8, QColor(255, 255, 255), img);
|
|
p.drawImage(0, 0, img);
|
|
p.end();
|
|
}
|
|
|
|
bool View3DInventor::containsViewProvider(const ViewProvider* vp) const
|
|
{
|
|
return _viewer->containsViewProvider(vp);
|
|
}
|
|
|
|
// **********************************************************************************
|
|
|
|
bool View3DInventor::onMsg(const char* pMsg, const char** ppReturn)
|
|
{
|
|
if (strcmp("ViewFit", pMsg) == 0) {
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewVR", pMsg) == 0) {
|
|
// call the VR portion of the viewer
|
|
_viewer->viewVR();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewSelection", pMsg) == 0) {
|
|
_viewer->viewSelection();
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoRedGreen", pMsg) == 0) {
|
|
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::ANAGLYPH);
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoQuadBuff", pMsg) == 0) {
|
|
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::QUAD_BUFFER);
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoInterleavedRows", pMsg) == 0) {
|
|
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::INTERLEAVED_ROWS);
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoInterleavedColumns", pMsg) == 0) {
|
|
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::INTERLEAVED_COLUMNS);
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoOff", pMsg) == 0) {
|
|
_viewer->setStereoMode(Quarter::SoQTQuarterAdaptor::MONO);
|
|
return true;
|
|
}
|
|
else if (strcmp("GetCamera", pMsg) == 0) {
|
|
SoCamera* Cam = _viewer->getSoRenderManager()->getCamera();
|
|
if (!Cam) {
|
|
return false;
|
|
}
|
|
*ppReturn = SoFCDB::writeNodesToString(Cam).c_str();
|
|
return true;
|
|
}
|
|
else if (strncmp("SetCamera", pMsg, 9) == 0) {
|
|
return setCamera(pMsg + 10);
|
|
}
|
|
else if (strncmp("Dump", pMsg, 4) == 0) {
|
|
dump(pMsg + 5);
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewBottom", pMsg) == 0) {
|
|
_viewer->setCameraOrientation(Camera::rotation(Camera::Bottom));
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewFront", pMsg) == 0) {
|
|
_viewer->setCameraOrientation(Camera::rotation(Camera::Front));
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewLeft", pMsg) == 0) {
|
|
_viewer->setCameraOrientation(Camera::rotation(Camera::Left));
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewRear", pMsg) == 0) {
|
|
_viewer->setCameraOrientation(Camera::rotation(Camera::Rear));
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewRight", pMsg) == 0) {
|
|
_viewer->setCameraOrientation(Camera::rotation(Camera::Right));
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewTop", pMsg) == 0) {
|
|
_viewer->setCameraOrientation(Camera::rotation(Camera::Top));
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewAxo", pMsg) == 0) {
|
|
_viewer->setCameraOrientation(Camera::rotation(Camera::Isometric));
|
|
_viewer->viewAll();
|
|
return true;
|
|
}
|
|
else if (strcmp("OrthographicCamera", pMsg) == 0) {
|
|
_viewer->setCameraType(SoOrthographicCamera::getClassTypeId());
|
|
return true;
|
|
}
|
|
else if (strcmp("PerspectiveCamera", pMsg) == 0) {
|
|
_viewer->setCameraType(SoPerspectiveCamera::getClassTypeId());
|
|
return true;
|
|
}
|
|
else if (strcmp("Undo", pMsg) == 0) {
|
|
getGuiDocument()->undo(1);
|
|
return true;
|
|
}
|
|
else if (strcmp("Redo", pMsg) == 0) {
|
|
getGuiDocument()->redo(1);
|
|
return true;
|
|
}
|
|
else if (strcmp("Save", pMsg) == 0) {
|
|
getGuiDocument()->save();
|
|
return true;
|
|
}
|
|
else if (strcmp("SaveAs", pMsg) == 0) {
|
|
getGuiDocument()->saveAs();
|
|
return true;
|
|
}
|
|
else if (strcmp("SaveCopy", pMsg) == 0) {
|
|
getGuiDocument()->saveCopy();
|
|
return true;
|
|
}
|
|
else if (strcmp("AlignToSelection", pMsg) == 0) {
|
|
_viewer->alignToSelection();
|
|
return true;
|
|
}
|
|
else if (strcmp("ZoomIn", pMsg) == 0) {
|
|
View3DInventorViewer* viewer = getViewer();
|
|
viewer->navigationStyle()->zoomIn();
|
|
return true;
|
|
}
|
|
else if (strcmp("ZoomOut", pMsg) == 0) {
|
|
View3DInventorViewer* viewer = getViewer();
|
|
viewer->navigationStyle()->zoomOut();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool View3DInventor::onHasMsg(const char* pMsg) const
|
|
{
|
|
if (strcmp("CanPan", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("Save", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("SaveAs", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("SaveCopy", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("Undo", pMsg) == 0) {
|
|
App::Document* doc = getAppDocument();
|
|
return doc && doc->getAvailableUndos() > 0;
|
|
}
|
|
else if (strcmp("Redo", pMsg) == 0) {
|
|
App::Document* doc = getAppDocument();
|
|
return doc && doc->getAvailableRedos() > 0;
|
|
}
|
|
else if (strcmp("Print", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("PrintPreview", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("PrintPdf", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoRedGreen", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoQuadBuff", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoInterleavedRows", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoInterleavedColumns", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("SetStereoOff", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewFit", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewVR", pMsg) == 0) {
|
|
#ifdef BUILD_VR
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
else if (strcmp("ViewSelection", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewBottom", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewFront", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewLeft", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewRear", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewRight", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewTop", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("ViewAxo", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("GetCamera", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
else if (strncmp("SetCamera", pMsg, 9) == 0) {
|
|
return true;
|
|
}
|
|
else if (strncmp("Dump", pMsg, 4) == 0) {
|
|
return true;
|
|
}
|
|
else if (strcmp("AlignToSelection", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
if (strcmp("ZoomIn", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
if (strcmp("ZoomOut", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
if (strcmp("AllowsOverlayOnHover", pMsg) == 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool View3DInventor::setCamera(const char* pCamera)
|
|
{
|
|
SoCamera* CamViewer = _viewer->getSoRenderManager()->getCamera();
|
|
if (!CamViewer) {
|
|
throw Base::RuntimeError("No camera set so far…");
|
|
}
|
|
|
|
SoInput in;
|
|
in.setBuffer((void*)pCamera, std::strlen(pCamera));
|
|
|
|
SoNode* Cam;
|
|
SoDB::read(&in, Cam);
|
|
|
|
if (!Cam || !Cam->isOfType(SoCamera::getClassTypeId())) {
|
|
throw Base::RuntimeError("Camera settings failed to read");
|
|
}
|
|
|
|
// this is to make sure to reliably delete the node
|
|
CoinPtr<SoNode> camPtr(Cam, true);
|
|
|
|
// toggle between perspective and orthographic camera
|
|
if (Cam->getTypeId() != CamViewer->getTypeId()) {
|
|
_viewer->setCameraType(Cam->getTypeId());
|
|
CamViewer = _viewer->getSoRenderManager()->getCamera();
|
|
}
|
|
|
|
SoPerspectiveCamera* CamViewerP = nullptr;
|
|
SoOrthographicCamera* CamViewerO = nullptr;
|
|
|
|
if (CamViewer->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
|
|
CamViewerP = static_cast<SoPerspectiveCamera*>(CamViewer); // safe downward cast, knows the type
|
|
}
|
|
else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
|
|
CamViewerO = static_cast<SoOrthographicCamera*>(CamViewer); // safe downward cast, knows
|
|
// the type
|
|
}
|
|
|
|
if (Cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
|
|
if (CamViewerP) {
|
|
CamViewerP->position = static_cast<SoPerspectiveCamera*>(Cam)->position;
|
|
CamViewerP->orientation = static_cast<SoPerspectiveCamera*>(Cam)->orientation;
|
|
CamViewerP->nearDistance = static_cast<SoPerspectiveCamera*>(Cam)->nearDistance;
|
|
CamViewerP->farDistance = static_cast<SoPerspectiveCamera*>(Cam)->farDistance;
|
|
CamViewerP->focalDistance = static_cast<SoPerspectiveCamera*>(Cam)->focalDistance;
|
|
}
|
|
else {
|
|
throw Base::TypeError("Camera type mismatch");
|
|
}
|
|
}
|
|
else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
|
|
if (CamViewerO) {
|
|
CamViewerO->viewportMapping = static_cast<SoOrthographicCamera*>(Cam)->viewportMapping;
|
|
CamViewerO->position = static_cast<SoOrthographicCamera*>(Cam)->position;
|
|
CamViewerO->orientation = static_cast<SoOrthographicCamera*>(Cam)->orientation;
|
|
CamViewerO->nearDistance = static_cast<SoOrthographicCamera*>(Cam)->nearDistance;
|
|
CamViewerO->farDistance = static_cast<SoOrthographicCamera*>(Cam)->farDistance;
|
|
CamViewerO->focalDistance = static_cast<SoOrthographicCamera*>(Cam)->focalDistance;
|
|
CamViewerO->aspectRatio = static_cast<SoOrthographicCamera*>(Cam)->aspectRatio;
|
|
CamViewerO->height = static_cast<SoOrthographicCamera*>(Cam)->height;
|
|
}
|
|
else {
|
|
throw Base::TypeError("Camera type mismatch");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void View3DInventor::toggleClippingPlane()
|
|
{
|
|
_viewer->toggleClippingPlane();
|
|
}
|
|
|
|
bool View3DInventor::hasClippingPlane() const
|
|
{
|
|
return _viewer->hasClippingPlane();
|
|
}
|
|
|
|
void View3DInventor::setOverlayWidget(QWidget* widget)
|
|
{
|
|
removeOverlayWidget();
|
|
stack->addWidget(widget);
|
|
stack->setCurrentIndex(1);
|
|
}
|
|
|
|
void View3DInventor::removeOverlayWidget()
|
|
{
|
|
stack->setCurrentIndex(0);
|
|
QWidget* overlay = stack->widget(1);
|
|
if (overlay) {
|
|
stack->removeWidget(overlay);
|
|
}
|
|
}
|
|
|
|
void View3DInventor::setOverrideCursor(const QCursor& aCursor)
|
|
{
|
|
_viewer->getWidget()->setCursor(aCursor);
|
|
}
|
|
|
|
void View3DInventor::restoreOverrideCursor()
|
|
{
|
|
_viewer->getWidget()->setCursor(QCursor(Qt::ArrowCursor));
|
|
}
|
|
|
|
// defined in SoFCDB.cpp
|
|
extern SoNode* replaceSwitchesInSceneGraph(SoNode*);
|
|
|
|
void View3DInventor::dump(const char* filename, bool onlyVisible)
|
|
{
|
|
SoGetPrimitiveCountAction action;
|
|
action.setCanApproximate(true);
|
|
action.apply(_viewer->getSceneGraph());
|
|
|
|
SoNode* node = _viewer->getSceneGraph();
|
|
if (onlyVisible) {
|
|
node = replaceSwitchesInSceneGraph(node);
|
|
node->ref();
|
|
}
|
|
|
|
if (action.getTriangleCount() > 100000 || action.getPointCount() > 30000
|
|
|| action.getLineCount() > 10000) {
|
|
_viewer->dumpToFile(node, filename, true);
|
|
}
|
|
else {
|
|
_viewer->dumpToFile(node, filename, false);
|
|
}
|
|
|
|
if (onlyVisible) {
|
|
node->unref();
|
|
}
|
|
}
|
|
|
|
void View3DInventor::windowStateChanged(QWidget* view)
|
|
{
|
|
bool canStartTimer = false;
|
|
if (this != view) {
|
|
// If both views are child widgets of the workspace and view is maximized this view
|
|
// must be hidden, hence we can start the timer.
|
|
// Note: If view is top-level or fullscreen it doesn't necessarily hide the other view
|
|
// e.g. if it is on a second monitor.
|
|
canStartTimer = (!this->isWindow() && !view->isWindow() && view->isMaximized());
|
|
}
|
|
else if (isMinimized()) {
|
|
// I am the active view but minimized
|
|
canStartTimer = true;
|
|
}
|
|
|
|
if (canStartTimer) {
|
|
int msecs = viewSettings->stopAnimatingIfDeactivated();
|
|
if (!stopSpinTimer->isActive() && msecs >= 0) { // if < 0 do not stop rotation
|
|
stopSpinTimer->setSingleShot(true);
|
|
stopSpinTimer->start(msecs);
|
|
}
|
|
}
|
|
else if (stopSpinTimer->isActive()) {
|
|
// If this view may be visible again we can stop the timer
|
|
stopSpinTimer->stop();
|
|
}
|
|
}
|
|
|
|
void View3DInventor::stopAnimating()
|
|
{
|
|
_viewer->stopAnimating();
|
|
}
|
|
|
|
/**
|
|
* Drops the event \a e and writes the right Python command.
|
|
*/
|
|
void View3DInventor::dropEvent(QDropEvent* e)
|
|
{
|
|
const QMimeData* data = e->mimeData();
|
|
if (data->hasUrls()) {
|
|
getMainWindow()->loadUrls(getAppDocument(), data->urls());
|
|
}
|
|
else {
|
|
MDIView::dropEvent(e);
|
|
}
|
|
}
|
|
|
|
void View3DInventor::dragEnterEvent(QDragEnterEvent* e)
|
|
{
|
|
// Here we must allow uri drags and check them in dropEvent
|
|
const QMimeData* data = e->mimeData();
|
|
if (data->hasUrls()) {
|
|
e->accept();
|
|
}
|
|
else {
|
|
e->ignore();
|
|
}
|
|
}
|
|
|
|
void View3DInventor::setCurrentViewMode(ViewMode mode)
|
|
{
|
|
ViewMode oldmode = currentViewMode();
|
|
if (mode == oldmode) {
|
|
return;
|
|
}
|
|
|
|
if (mode == Child) {
|
|
// Fix in two steps:
|
|
// The mdi view got a QWindow when it became a top-level widget and when resetting it to a
|
|
// child widget the QWindow must be deleted because it has an impact on resize events and
|
|
// may break the layout of mdi view inside the QMdiSubWindow. In the second step below the
|
|
// layout must be invalidated after it's again a child widget to make sure the mdi view fits
|
|
// into the QMdiSubWindow.
|
|
QWindow* winHandle = this->windowHandle();
|
|
if (winHandle) {
|
|
winHandle->destroy();
|
|
}
|
|
}
|
|
|
|
MDIView::setCurrentViewMode(mode);
|
|
|
|
// This widget becomes the focus proxy of the embedded GL widget if we leave
|
|
// the 'Child' mode. If we reenter 'Child' mode the focus proxy is reset to 0.
|
|
// If we change from 'TopLevel' mode to 'Fullscreen' mode or vice versa nothing
|
|
// happens.
|
|
// Grabbing keyboard when leaving 'Child' mode (as done in a recent version) should
|
|
// be avoided because when two or more windows are either in 'TopLevel' or 'Fullscreen'
|
|
// mode only the last window gets all key event even if it is not the active one.
|
|
//
|
|
// It is important to set the focus proxy to get all key events otherwise we would lose
|
|
// control after redirecting the first key event to the GL widget.
|
|
|
|
if (oldmode == Child) {
|
|
_viewer->getGLWidget()->setFocusProxy(this);
|
|
}
|
|
else if (mode == Child) {
|
|
_viewer->getGLWidget()->setFocusProxy(nullptr);
|
|
|
|
// Step two
|
|
auto mdi = qobject_cast<QMdiSubWindow*>(parentWidget());
|
|
if (mdi && mdi->layout()) {
|
|
mdi->layout()->invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
RayPickInfo View3DInventor::getObjInfoRay(Base::Vector3d* startvec, Base::Vector3d* dirvec)
|
|
{
|
|
double vsx, vsy, vsz;
|
|
double vdx, vdy, vdz;
|
|
vsx = startvec->x;
|
|
vsy = startvec->y;
|
|
vsz = startvec->z;
|
|
vdx = dirvec->x;
|
|
vdy = dirvec->y;
|
|
vdz = dirvec->z;
|
|
// near plane clipping is required to avoid false intersections
|
|
float nearClippingPlane = 0.1F;
|
|
|
|
RayPickInfo ret = {false, Base::Vector3d(), "", "", std::nullopt, std::nullopt, std::nullopt};
|
|
SoRayPickAction action(getViewer()->getSoRenderManager()->getViewportRegion());
|
|
action.setRay(SbVec3f(vsx, vsy, vsz), SbVec3f(vdx, vdy, vdz), nearClippingPlane);
|
|
action.apply(getViewer()->getSoRenderManager()->getSceneGraph());
|
|
SoPickedPoint* Point = action.getPickedPoint();
|
|
|
|
if (!Point) {
|
|
return ret;
|
|
}
|
|
|
|
ret.point = Base::convertTo<Base::Vector3d>(Point->getPoint());
|
|
ViewProvider* vp = getViewer()->getViewProviderByPath(Point->getPath());
|
|
if (vp && vp->isDerivedFrom<ViewProviderDocumentObject>()) {
|
|
if (!vp->isSelectable()) {
|
|
return ret;
|
|
}
|
|
auto vpd = static_cast<ViewProviderDocumentObject*>(vp);
|
|
if (vp->useNewSelectionModel()) {
|
|
std::string subname;
|
|
if (!vp->getElementPicked(Point, subname)) {
|
|
return ret;
|
|
}
|
|
auto obj = vpd->getObject();
|
|
if (!obj) {
|
|
return ret;
|
|
}
|
|
if (!subname.empty()) {
|
|
App::ElementNamePair elementName;
|
|
auto sobj = App::GeoFeature::resolveElement(obj, subname.c_str(), elementName);
|
|
if (!sobj) {
|
|
return ret;
|
|
}
|
|
if (sobj != obj) {
|
|
ret.parentObject = obj->getExportName();
|
|
ret.subName = subname;
|
|
obj = sobj;
|
|
}
|
|
subname = !elementName.oldName.empty() ? elementName.oldName : elementName.newName;
|
|
}
|
|
ret.document = obj->getDocument()->getName();
|
|
ret.object = obj->getNameInDocument();
|
|
ret.component = subname;
|
|
ret.isValid = true;
|
|
}
|
|
else {
|
|
ret.document = vpd->getObject()->getDocument()->getName();
|
|
ret.object = vpd->getObject()->getNameInDocument();
|
|
// search for a SoFCSelection node
|
|
SoFCDocumentObjectAction objaction;
|
|
objaction.apply(Point->getPath());
|
|
if (objaction.isHandled()) {
|
|
ret.component = objaction.componentName.getString();
|
|
}
|
|
}
|
|
// ok, found the node of interest
|
|
ret.isValid = true;
|
|
}
|
|
else {
|
|
// custom nodes not in a VP: search for a SoFCSelection node
|
|
SoFCDocumentObjectAction objaction;
|
|
objaction.apply(Point->getPath());
|
|
if (objaction.isHandled()) {
|
|
ret.document = objaction.documentName.getString();
|
|
ret.object = objaction.objectName.getString();
|
|
ret.component = objaction.componentName.getString();
|
|
// ok, found the node of interest
|
|
ret.isValid = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void View3DInventor::keyPressEvent(QKeyEvent* e)
|
|
{
|
|
// See StdViewDockUndockFullscreen::activated()
|
|
// With Qt5 one cannot directly use 'setCurrentViewMode'
|
|
// of an MDI view because it causes rendering problems.
|
|
// The only reliable solution is to clone the MDI view,
|
|
// set its view mode and close the original MDI view.
|
|
|
|
QMainWindow::keyPressEvent(e);
|
|
}
|
|
|
|
void View3DInventor::keyReleaseEvent(QKeyEvent* e)
|
|
{
|
|
QMainWindow::keyReleaseEvent(e);
|
|
}
|
|
|
|
void View3DInventor::focusInEvent(QFocusEvent*)
|
|
{
|
|
_viewer->getGLWidget()->setFocus();
|
|
}
|
|
|
|
void View3DInventor::contextMenuEvent(QContextMenuEvent* e)
|
|
{
|
|
MDIView::contextMenuEvent(e);
|
|
}
|
|
|
|
bool View3DInventor::eventFilter(QObject* obj, QEvent* ev)
|
|
{
|
|
if (obj == _viewer->getWidget() && ev->type() == QEvent::Resize) {
|
|
// Keep breadcrumb at top-left after viewport resize
|
|
if (_breadcrumb) {
|
|
_breadcrumb->raise();
|
|
}
|
|
}
|
|
return MDIView::eventFilter(obj, ev);
|
|
}
|
|
|
|
void View3DInventor::customEvent(QEvent* e)
|
|
{
|
|
if (e->type() == QEvent::User) {
|
|
auto se = static_cast<NavigationStyleEvent*>(e);
|
|
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/View"
|
|
);
|
|
if (hGrp->GetBool("SameStyleForAllViews", true)) {
|
|
hGrp->SetASCII("NavigationStyle", se->style().getName());
|
|
}
|
|
else {
|
|
_viewer->setNavigationType(se->style());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#include "moc_View3DInventor.cpp"
|