#include "BaseViewerApplication.hxx"
#include "ImportRunnable.hxx"
#include "CustomEvents.hxx"
#include <cadex/ModelData_RepresentationSelector.hxx>
#include <cadex/ModelPrs_Scene.hxx>
#include <cadex/ModelPrs_SceneNodeFactory.hxx>
#include <cadex/ModelPrsQtQuick_ViewPort.hxx>
#include <cadex/STEP_ReaderParameters.hxx>
#include <QtCore/QFileInfo>
#include <QtCore/QThreadPool>
#include <QtGui/QOpenGLContext>
#include <QtQml/QQmlApplicationEngine>
#include <QtQuick/QQuickWindow>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMessageBox>
#if __ANDROID__
#include <QtAndroidExtras/QtAndroid>
#endif
#if __ANDROID__ && __ANDROID_DEBUG
#include <android/log.h>
namespace {
void QMessageToAndroidLogRedirector (QtMsgType theType, const QMessageLogContext& , const QString& theMsg)
{
QString aText;
switch (theType) {
case QtDebugMsg: aText += "qDebug(): "; break;
case QtWarningMsg: aText += "qWarning(): "; break;
case QtCriticalMsg: aText += "qCritical(): "; break;
case QtFatalMsg: aText += "qFatal(): "; break;
case QtInfoMsg: aText += "qInfo(): "; break;
default: break;
}
aText += theMsg;
for (const QString& line : aText.split ('\n')) {
__android_log_print (ANDROID_LOG_DEBUG, "baseviewer", "%s", qPrintable (line));
}
}
}
#endif
BaseViewerApplication::BaseViewerApplication()
{
myScene.AddRoot (myRoot);
qApp->setOrganizationName ("CAD Exchanger");
qApp->setOrganizationDomain ("cadexchanger.com");
}
namespace {
#if !__ANDROID__
QPair<int, int> OpenGlVersion()
{
auto aVersion = qMakePair (-1, -1);
QOpenGLContext aTestOpenGLContext;
aTestOpenGLContext.create();
if (aTestOpenGLContext.isValid()) {
aVersion = aTestOpenGLContext.format().version();
}
return aVersion;
}
#endif
}
bool BaseViewerApplication::Initialize (const QUrl& theUrl, const QString& theViewPortName)
{
#if __ANDROID__ && __ANDROID_DEBUG
qInstallMessageHandler (QMessageToAndroidLogRedirector);
#endif
#if !__ANDROID__
auto anOpenGlVersion = OpenGlVersion();
auto aRequiredOpenGlVersion = qMakePair (2, 1);
if (anOpenGlVersion < aRequiredOpenGlVersion) {
QMessageBox::critical (nullptr,
QCoreApplication::applicationName(),
tr ("%1 requires graphic card with drivers supporting OpenGL %2.%3 or later.\n"
"Detected driver supports OpenGL %4.%5.\n"
"Please make sure you don't run %1 inside virtual machine "
"and your graphic card driver is upgraded to the latest version "
"Visit your graphic card manufacturer web-site to download the latest drivers.")
.arg (QCoreApplication::applicationName())
.arg (aRequiredOpenGlVersion.first).arg (aRequiredOpenGlVersion.second)
.arg (anOpenGlVersion.first).arg (anOpenGlVersion.second)
);
return false;
}
#endif
ModelPrsQtQuick_ViewPort::RegisterQtTypes();
myMainWindow = CreateMainWindow (theUrl);
Q_ASSERT (myMainWindow);
#if __ANDROID__
if (!QtAndroid::shouldShowRequestPermissionRationale("android.permission.READ_EXTERNAL_STORAGE")) {
QtAndroid::requestPermissions({{ "android.permission.READ_EXTERNAL_STORAGE" }},
[] (const QtAndroid::PermissionResultMap & theResult) {
if (theResult["android.permission.READ_EXTERNAL_STORAGE"] == QtAndroid::PermissionResult::Denied) {
qWarning ("User refused request to allow access to sdcard for reading.");
}
});
}
#endif
connect (myMainWindow, SIGNAL (importFile (const QVariant&)),
this, SLOT (onImportFile (const QVariant&)));
Q_ASSERT (myViewPort);
auto aRes = myViewPort->AttachToScene (myScene);
myMainWindow->showNormal();
myMainWindow->setProperty ("fileDialogNameFilters", "STEP files (*.stp *.step)");
emit initialized();
auto anArguments = QApplication::instance()->arguments();
if (anArguments.size() > 1) {
auto anArgument = anArguments.at (1);
Import (anArgument);
}
return aRes;
}
QQuickWindow* BaseViewerApplication::CreateMainWindow (const QUrl& theUrl)
{
auto anEngine = new QQmlApplicationEngine (this);
QQmlComponent aComponent (anEngine);
aComponent.loadUrl (theUrl);
QObject* aQmlApp = nullptr;
if (!aComponent.isReady()) {
qDebug() << aComponent.errorString();
QCoreApplication::postEvent (QCoreApplication::instance(), new QEvent (QEvent::Quit));
return nullptr;
}
aQmlApp = aComponent.create();
aQmlApp->setParent (this);
return qobject_cast<QQuickWindow*> (aQmlApp);
}
void BaseViewerApplication::onImportFile (const QVariant& theUrl)
{
if (!(theUrl.isValid() || !theUrl.canConvert<QUrl>())) {
qWarning() << "BaseViewerApplication::OnImport: Invalid argument [" << theUrl << "]";
return;
}
auto aFilename = theUrl.toUrl().toLocalFile();
Import (aFilename);
}
void BaseViewerApplication::Import (const QString& theFilename)
{
QFileInfo aFileInfo (theFilename);
if (!aFileInfo.exists() || !aFileInfo.isFile() || !aFileInfo.isReadable()) {
return;
}
Clear();
auto aRunnable = new ImportRunnable (theFilename, myScene, myModel, myReaderParameters, this);
QThreadPool::globalInstance()->start (aRunnable);
myMainWindow->setProperty ("loading", true);
myMainWindow->setTitle (QString ("CAD Exchanger [%1]").arg (theFilename));
}
void BaseViewerApplication::Clear()
{
myModel.Clear();
myRoot.RemoveChildrenNodes();
myScene.Update();
}
void BaseViewerApplication::CreateSceneNodes()
{
auto aRoot = aFactory.
CreateGraph (myModel, ModelData_RM_BRep);
myRoot.AddChildNode (aRoot);
}
bool BaseViewerApplication::event (QEvent* theEvent)
{
auto anEventType = static_cast<ImportResult> (theEvent->type());
if (ImportResult::Error != anEventType && anEventType != ImportResult::Success) {
return QObject::event (theEvent);
}
onImportCompleted (theEvent);
return true;
}
void BaseViewerApplication::onImportCompleted (QEvent* theEvent)
{
auto anEventType = static_cast<ImportResult> (theEvent->type());
switch (anEventType) {
case ImportResult::Success: {
myMainWindow->setProperty ("loading", false);
myViewPort->animatedFitAll();
break;
}
case ImportResult::Error: {
Q_ASSERT (dynamic_cast<ErrorEvent*> (theEvent));
auto anEvent = static_cast<ErrorEvent*> (theEvent);
myMainWindow->setProperty ("loading", false);
ShowMessageBox (anEvent->Message());
break;
}
}
}
void BaseViewerApplication::ShowMessageBox (const QString& theMessage)
{
if (auto aDialog = myMainWindow->findChild<QObject*> ("dialog")) {
aDialog->setProperty ("message", theMessage);
QMetaObject::invokeMethod (aDialog, "open");
}
}
Creates a scene nodes and its children from input data model objects.
Definition: ModelPrs_SceneNodeFactory.hxx:53
ModelPrs_SceneNode CreateGraph(const ModelData_Model &theModel, ModelData_RepresentationMask theRepresentationMask)
Creates scene graph using ModelData_Model.
Definition: ModelPrs_SceneNodeFactory.cxx:735
Defines parameters of the STEP_Reader.
Definition: STEP_ReaderParameters.hxx:27
import QtQuick 2.7
import QtQuick.Controls 2.3
import Qt.labs.platform 1.0 as Platform
import Qt.labs.settings 1.0
import CadEx 1.2
ApplicationWindow {
title: qsTr ("CAD Exchanger")
minimumWidth : 1000
minimumHeight: 600
width : 1000
height: 600
property alias loading: busyIndicator.running
property alias menuBarItem: menuBarItem
property alias fileMenuItem: fileMenuItem
property alias fileDialogNameFilters: fileDialog.nameFilters
signal menuCompleted
signal importFile (var url);
menuBar: MenuBar {
id: menuBarItem
enabled: !loading
Menu {
id: fileMenuItem
title: qsTr("&File")
MenuItem {
text: qsTr ("&Import...")
onTriggered: {
fileDialog.open()
}
}
MenuSeparator { }
MenuItem {
text: qsTr ("&Quit")
onTriggered: {
Qt.quit();
}
}
}
Component.onCompleted: {
menuCompleted();
}
}
Settings {
id: settings
property url lastImportDirectory: Platform.StandardPaths.writableLocation (Platform.StandardPaths.HomeLocation)
}
Platform.FileDialog {
id: fileDialog
title: qsTr ("Please choose a file")
fileMode: Platform.FileDialog.OpenFile
options: Platform.FileDialog.DontResolveSymlinks | Platform.FileDialog.DontUseCustomDirectoryIcons
folder: settings.lastImportDirectory
onAccepted: {
settings.lastImportDirectory = folder;
importFile (file);
}
}
Dialog {
id: dialog
objectName: "dialog"
title: qsTr ("Error")
parent: ApplicationWindow.overlay
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: 300
standardButtons: Dialog.Ok
visible: false
closePolicy: Popup.CloseOnEscape
dim: true
property string message
Label {
anchors.left: parent.left
anchors.right: parent.right
text: dialog.message
wrapMode: Text.WordWrap
}
}
BusyIndicator {
id: busyIndicator
running: false
visible: running
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 20
width: 80
height: 80
parent: ApplicationWindow.overlay
}
}