diff options
author | Miikka Heikkinen <miikka.heikkinen@qt.io> | 2016-12-14 17:55:21 +0200 |
---|---|---|
committer | Miikka Heikkinen <miikka.heikkinen@qt.io> | 2016-12-16 09:33:03 +0000 |
commit | 00b01d064c981e872a1a9eadfc43e5a83e92220a (patch) | |
tree | 436ab04a8a437efc97529726d24bbcd4b89dc5e4 | |
parent | 2b9ddcd68e2b2813a08aafef6e89210997def07f (diff) |
Change editor save format from QML to JSON + QGLTF when supported
Change-Id: If2bf099b5e77b3350ee5670e968cfa9a96d563b2
Reviewed-by: Antti Määttä <antti.maatta@qt.io>
-rw-r--r-- | editorlib/editorlib.pro | 7 | ||||
-rw-r--r-- | editorlib/qml/EditorContent.qml | 10 | ||||
-rw-r--r-- | editorlib/src/editorscene.cpp | 39 | ||||
-rw-r--r-- | editorlib/src/editorscene.h | 11 | ||||
-rw-r--r-- | editorlib/src/editorscenesaver.cpp | 244 | ||||
-rw-r--r-- | editorlib/src/editorscenesaver.h | 57 | ||||
-rw-r--r-- | editorlib/src/editorutils.cpp | 16 | ||||
-rw-r--r-- | editorlib/src/editorutils.h | 1 |
8 files changed, 376 insertions, 9 deletions
diff --git a/editorlib/editorlib.pro b/editorlib/editorlib.pro index d47c2b8..303f7a7 100644 --- a/editorlib/editorlib.pro +++ b/editorlib/editorlib.pro @@ -13,7 +13,6 @@ SOURCES += src/qt3dsceneeditor.cpp \ src/editorsceneitemmodel.cpp \ src/editorsceneitem.cpp \ src/editorsceneitemcomponentsmodel.cpp \ - src/editorsceneparser.cpp \ src/components/transformcomponentproxyitem.cpp \ src/components/editorsceneitemtransformcomponentsmodel.cpp \ src/components/materialcomponentproxyitem.cpp \ @@ -60,7 +59,6 @@ HEADERS += \ src/editorsceneitemmodel.h \ src/editorsceneitem.h \ src/editorsceneitemcomponentsmodel.h \ - src/editorsceneparser.h \ src/components/transformcomponentproxyitem.h \ src/components/editorsceneitemtransformcomponentsmodel.h \ src/components/materialcomponentproxyitem.h \ @@ -102,4 +100,9 @@ SOURCES = qml/*.qml \ greaterThan(QT_MAJOR_VERSION, 4):greaterThan(QT_MINOR_VERSION, 8) { DEFINES += GLTF_EXPORTER_AVAILABLE + SOURCES += src/editorscenesaver.cpp + HEADERS += src/editorscenesaver.h +} else { + SOURCES += src/editorsceneparser.cpp + HEADERS += src/editorsceneparser.h } diff --git a/editorlib/qml/EditorContent.qml b/editorlib/qml/EditorContent.qml index 3cde3ef..8eb8726 100644 --- a/editorlib/qml/EditorContent.qml +++ b/editorlib/qml/EditorContent.qml @@ -52,6 +52,12 @@ Item { else saveFileUrl.toString() } + property string saveFilterString: { + if (editorScene.canExportGltf) + qsTr("Qt3D Scenes (*.qt3dscene)") + editorScene.emptyString; + else + qsTr("Qt3D Scenes (*.qt3d.qrc)") + editorScene.emptyString; + } property int currentHelperPlane: 1 property alias selectedEntityType: generalPropertyView.entityType @@ -389,7 +395,7 @@ Item { selectMultiple: false selectExisting: true title: qsTr("Load Scene") + editorScene.emptyString - nameFilters: [qsTr("Qt3D Scenes (*.qt3d.qrc)") + editorScene.emptyString] + nameFilters: [editorContent.saveFilterString] onAccepted: { editorContent.loadScene(fileUrl, folder) } @@ -401,7 +407,7 @@ Item { selectExisting: false property bool exiting: false title: qsTr("Save Scene") + editorScene.emptyString - nameFilters: [qsTr("Qt3D Scenes (*.qt3d.qrc)") + editorScene.emptyString] + nameFilters: [editorContent.saveFilterString] onAccepted: { editorScene.saveScene(fileUrl) editorContent.saveFolder = folder diff --git a/editorlib/src/editorscene.cpp b/editorlib/src/editorscene.cpp index 069f4b4..a3bbce2 100644 --- a/editorlib/src/editorscene.cpp +++ b/editorlib/src/editorscene.cpp @@ -28,7 +28,6 @@ #include "editorscene.h" #include "editorutils.h" #include "editorsceneitem.h" -#include "editorsceneparser.h" #include "editorsceneitemcomponentsmodel.h" #include "editorviewportitem.h" #include "ontopeffect.h" @@ -70,6 +69,9 @@ #ifdef GLTF_EXPORTER_AVAILABLE #include <Qt3DRender/private/qsceneexportfactory_p.h> #include <Qt3DRender/private/qsceneexporter_p.h> +#include "editorscenesaver.h" +#else +#include "editorsceneparser.h" #endif #include <cfloat> @@ -85,7 +87,6 @@ static const QString cameraVisibleEntityName = QStringLiteral("__internal camera static const QString lightVisibleEntityName = QStringLiteral("__internal light visible entity"); static const QString sceneLoaderSubEntityName = QStringLiteral("__internal sceneloader sub entity"); static const QString helperArrowName = QStringLiteral("__internal helper arrow"); -static const QString autoSavePostfix = QStringLiteral(".autosave"); static const QVector3D defaultLightDirection(0.0f, -1.0f, 0.0f); static const float freeViewCameraNearPlane = 0.1f; static const float freeViewCameraFarPlane = 10000.0f; @@ -97,6 +98,9 @@ static const QColor helperPlaneColor("#585a5c"); static const QColor helperArrowColorX("red"); static const QColor helperArrowColorY("green"); static const QColor helperArrowColorZ("blue"); +#ifndef GLTF_EXPORTER_AVAILABLE +static const QString autoSavePostfix = QStringLiteral(".autosave"); +#endif EditorScene::EditorScene(QObject *parent) : QObject(parent) @@ -104,7 +108,6 @@ EditorScene::EditorScene(QObject *parent) , m_componentCache(nullptr) , m_rootItem(nullptr) , m_sceneModel(new EditorSceneItemModel(this)) - , m_sceneParser(new EditorSceneParser(this)) , m_renderSettings(nullptr) , m_renderer(nullptr) , m_sceneEntity(nullptr) @@ -142,6 +145,9 @@ EditorScene::EditorScene(QObject *parent) , m_groupBoxUpdatePending(false) #ifdef GLTF_EXPORTER_AVAILABLE , m_gltfExporter(nullptr) + , m_sceneSaver(new EditorSceneSaver(this)) +#else + , m_sceneParser(new EditorSceneParser(this)) #endif { setLanguage(language()); @@ -300,6 +306,16 @@ void EditorScene::resetScene() bool EditorScene::saveScene(const QUrl &fileUrl, bool autosave) { + if (!fileUrl.isValid()) + return false; + +#ifdef GLTF_EXPORTER_AVAILABLE + QString camera; + if (m_activeSceneCameraIndex >= 0 && m_activeSceneCameraIndex < m_sceneCameras.size()) + camera = m_sceneCameras.at(m_activeSceneCameraIndex).cameraEntity->objectName(); + + bool retval = m_sceneSaver->saveScene(m_sceneEntity, camera, fileUrl.toLocalFile(), autosave); +#else QUrl url = fileUrl; if (!url.toString().endsWith(QStringLiteral(".qt3d.qrc"))) { QString filePath = url.toString() + QStringLiteral(".qt3d.qrc"); @@ -310,6 +326,8 @@ bool EditorScene::saveScene(const QUrl &fileUrl, bool autosave) if (m_activeSceneCameraIndex >= 0 && m_activeSceneCameraIndex < m_sceneCameras.size()) camera = m_sceneCameras.at(m_activeSceneCameraIndex).cameraEntity; bool retval = m_sceneParser->exportQmlScene(m_sceneEntity, url, camera, autosave); +#endif + if (retval) m_undoHandler->setClean(); else @@ -319,8 +337,15 @@ bool EditorScene::saveScene(const QUrl &fileUrl, bool autosave) bool EditorScene::loadScene(const QUrl &fileUrl) { + if (!fileUrl.isValid()) + return false; + Qt3DCore::QEntity *camera = nullptr; +#ifdef GLTF_EXPORTER_AVAILABLE + Qt3DCore::QEntity *newSceneEntity = m_sceneSaver->loadScene(fileUrl.toLocalFile(), camera); +#else Qt3DCore::QEntity *newSceneEntity = m_sceneParser->importQmlScene(fileUrl, camera); +#endif if (newSceneEntity) { clearSingleSelection(); @@ -347,6 +372,12 @@ bool EditorScene::loadScene(const QUrl &fileUrl) void EditorScene::deleteScene(const QUrl &fileUrl, bool autosave) { + if (!fileUrl.isValid()) + return; + +#ifdef GLTF_EXPORTER_AVAILABLE + m_sceneSaver->deleteSavedScene(fileUrl.toLocalFile(), autosave); +#else // Remove qml file QString fileName = fileUrl.toLocalFile(); if (autosave) @@ -362,6 +393,7 @@ void EditorScene::deleteScene(const QUrl &fileUrl, bool autosave) resourceDirName.append(autoSavePostfix); QDir dir = QDir(resourceDirName); dir.removeRecursively(); +#endif } QString EditorScene::cameraName(int index) const @@ -2207,6 +2239,7 @@ bool EditorScene::exportGltfScene(const QUrl &folder, const QString &exportName, #else Q_UNUSED(folder) Q_UNUSED(exportName) + Q_UNUSED(exportSelected) Q_UNUSED(options) #endif return false; diff --git a/editorlib/src/editorscene.h b/editorlib/src/editorscene.h index 19322c0..0c1cff7 100644 --- a/editorlib/src/editorscene.h +++ b/editorlib/src/editorscene.h @@ -64,11 +64,16 @@ namespace Qt3DExtras { class EditorSceneItemModel; class EditorSceneItem; -class EditorSceneParser; class EditorViewportItem; class UndoHandler; class QMouseEvent; +#ifdef GLTF_EXPORTER_AVAILABLE +class EditorSceneSaver; +#else +class EditorSceneParser; +#endif + class EditorScene : public QObject { Q_OBJECT @@ -438,7 +443,6 @@ private: QMap<Qt3DCore::QNodeId, EditorSceneItem *> m_sceneItems; - EditorSceneParser *m_sceneParser; Qt3DRender::QRenderSettings *m_renderSettings; Qt3DExtras::QForwardRenderer *m_renderer; Qt3DCore::QEntity *m_sceneEntity; @@ -529,6 +533,9 @@ private: bool m_groupBoxUpdatePending; #ifdef GLTF_EXPORTER_AVAILABLE Qt3DRender::QSceneExporter *m_gltfExporter; + EditorSceneSaver *m_sceneSaver; +#else + EditorSceneParser *m_sceneParser; #endif }; diff --git a/editorlib/src/editorscenesaver.cpp b/editorlib/src/editorscenesaver.cpp new file mode 100644 index 0000000..2e128f6 --- /dev/null +++ b/editorlib/src/editorscenesaver.cpp @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D Editor of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "editorscenesaver.h" +#include "editorutils.h" + +#include <Qt3DCore/qentity.h> +#include <private/qsceneexportfactory_p.h> +#include <private/qsceneexporter_p.h> +#include <private/qsceneimportfactory_p.h> +#include <private/qsceneimporter_p.h> + +#include <QtCore/qfile.h> +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qtemporarydir.h> +#include <QtCore/qtemporaryfile.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qdatastream.h> +#include <QtCore/qdatetime.h> + +namespace { + +const QString autoSavePostfix = QStringLiteral("_autosave"); +const QString saveSuffix = QStringLiteral(".qt3dscene"); +const QString exportNameTemplate = QStringLiteral("%1_scene_res"); +const QString editorDataFile = QStringLiteral("qt3dscene_editor_data.json"); +const qint32 saveFileVersion = 1; +const QByteArray saveFileId = QByteArrayLiteral("Qt3DSceneEditor_SaveFile"); +const QString activeCameraKey = QStringLiteral("activeCamera"); +const QString rootEntityNameKey = QStringLiteral("rootEntityName"); + +} // namespace + +EditorSceneSaver::EditorSceneSaver(QObject *parent) + : QObject(parent) + , m_loadDir(nullptr) +{ +} + +EditorSceneSaver::~EditorSceneSaver() +{ + delete m_loadDir; +} + +bool EditorSceneSaver::saveScene(Qt3DCore::QEntity *sceneEntity, + const QString &activeSceneCamera, + const QString &saveFileName, + bool autosave) +{ + // Save consists of a single JSON file with .qt3dscene extension and the exported QGLTF scene + // in a separate subfolder in the same folder as the main save file. + + // The save is first created in a temp directory to ensure saving over existing save doesn't + // partially overwrite the old save in case of error. + QString finalFullSaveFilePathName = saveFileName; + if (!finalFullSaveFilePathName.endsWith(saveSuffix)) + finalFullSaveFilePathName.append(saveSuffix); + QFileInfo saveFileInfo(finalFullSaveFilePathName); + QString finalSavePath = saveFileInfo.path(); + QString finalFileName = saveFileInfo.fileName(); + QString saveExportName = exportNameTemplate.arg(saveFileInfo.completeBaseName()); + QDir finalSaveDir = saveFileInfo.absoluteDir(); + if (autosave) { + finalFullSaveFilePathName.append(autoSavePostfix); + finalFileName.append(autoSavePostfix); + saveExportName.append(autoSavePostfix); + } + QString finalScenePath = finalSavePath + QStringLiteral("/") + saveExportName; + + // Save scene to a temp folder using GLTF export plugin + QTemporaryDir exportDir(finalScenePath + QStringLiteral("_temp_save_XXXXXX")); + + const QStringList keys = Qt3DRender::QSceneExportFactory::keys(); + for (const QString &key : keys) { + Qt3DRender::QSceneExporter *exporter = + Qt3DRender::QSceneExportFactory::create(key, QStringList()); + if (exporter != nullptr && key == QStringLiteral("gltfexport")) { + QVariantHash options; + if (!exporter->exportScene(sceneEntity, exportDir.path(), saveExportName, options)) { + qWarning() << "Failed to export GLTF when saving the scene"; + return false; + } + break; + } + } + + // Create new save file in the temp folder + QJsonDocument jsonDoc; + QJsonObject editorData; + editorData[activeCameraKey] = activeSceneCamera; + editorData[rootEntityNameKey] = sceneEntity->objectName(); + jsonDoc.setObject(editorData); + const QString tempScenePath = exportDir.path() + QStringLiteral("/") + saveExportName; + const QString saveFilePath = exportDir.path() + QStringLiteral("/") + finalFileName; + QFile saveFile(saveFilePath); + if (saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QByteArray json = jsonDoc.toJson(); + saveFile.write(json); + saveFile.close(); + } else { + qWarning() << "Failed to create editor data file when saving the scene"; + return false; + } + + // Create a unique backup suffix from current time + QDateTime currentTime = QDateTime::currentDateTime(); + QString uniqueSuffix = currentTime.toString(QStringLiteral("yyyyMMddHHmmsszzz")); + QString backupExportName = saveExportName + uniqueSuffix; + QString backupSaveFileName = finalFullSaveFilePathName + uniqueSuffix; + + // Rename the old save file and exported resources and savefile + if (finalSaveDir.exists(saveExportName)) { + if (!finalSaveDir.rename(saveExportName, backupExportName)) { + qWarning() << "Failed to rename the old resource dir:" << saveExportName; + return false; + } + } + QFile oldSaveFile(finalFullSaveFilePathName); + if (oldSaveFile.exists()) { + if (!oldSaveFile.rename(backupSaveFileName)) { + qWarning() << "Failed to rename the old save file:" << finalFullSaveFilePathName; + finalSaveDir.rename(backupExportName, saveExportName); + return false; + } + } + + // Rename the temporary files as finals + if (!QFile::rename(tempScenePath, finalScenePath)) { + qWarning() << "Failed to rename the temp scene:" << tempScenePath << "->" << finalScenePath; + return false; + } + if (!saveFile.rename(finalFullSaveFilePathName)) { + qWarning() << "Failed to rename the temp save file:" << saveFilePath << + "->" << finalFullSaveFilePathName; + return false; + } + + // If everything went well, remove the renamed originals. + QFile::remove(backupSaveFileName); + if (finalSaveDir.cd(backupExportName)) + finalSaveDir.removeRecursively(); + + return true; +} + +Qt3DCore::QEntity *EditorSceneSaver::loadScene(const QString &fileName, Qt3DCore::QEntity *camera) +{ + Qt3DCore::QEntity *loadedScene = nullptr; + camera = nullptr; + + // Read the scene data + QFile jsonFile(fileName); + QString activeCameraId; + QString sceneName; + if (jsonFile.open(QIODevice::ReadOnly)) { + QByteArray jsonData = jsonFile.readAll(); + jsonFile.close(); + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + QJsonObject editorData = jsonDocument.object(); + activeCameraId = editorData.value(activeCameraKey).toString(); + sceneName = editorData.value(rootEntityNameKey).toString(); + } else { + qWarning() << "Failed to open scene save file:" << fileName; + return nullptr; + } + + // Import scene from temp folder + const QStringList keys = Qt3DRender::QSceneImportFactory::keys(); + for (const QString &key : keys) { + Qt3DRender::QSceneImporter *importer = + Qt3DRender::QSceneImportFactory::create(key, QStringList()); + if (importer != nullptr && key == QStringLiteral("gltf")) { + QFileInfo saveFileInfo(fileName); + const QString exportName = exportNameTemplate.arg(saveFileInfo.completeBaseName()); + QString sceneSource = saveFileInfo.absolutePath(); + sceneSource += QStringLiteral("/"); + sceneSource += exportName; + sceneSource += QStringLiteral("/"); + sceneSource += exportName; + sceneSource += QStringLiteral(".qgltf"); + importer->setSource(QUrl::fromLocalFile(sceneSource)); + loadedScene = importer->scene(); + break; + } + } + + if (!loadedScene) { + qWarning() << "Failed to load the saved scene"; + return nullptr; + } + + loadedScene->setObjectName(sceneName); + + // Find the active camera + camera = EditorUtils::findEntityByName(loadedScene, activeCameraId); + if (!EditorUtils::entityCameraLens(camera)) + camera = nullptr; + + return loadedScene; +} + +void EditorSceneSaver::deleteSavedScene(const QString &saveFileName, bool autosave) +{ + QString fullSaveFilePathName = saveFileName; + if (!fullSaveFilePathName.endsWith(saveSuffix)) + fullSaveFilePathName.append(saveSuffix); + QFileInfo saveFileInfo(fullSaveFilePathName); + QString savePath = saveFileInfo.path(); + QString saveExportName = exportNameTemplate.arg(saveFileInfo.completeBaseName()); + if (autosave) { + fullSaveFilePathName.append(autoSavePostfix); + saveExportName.append(autoSavePostfix); + } + QDir sceneResourcesDir(savePath + QStringLiteral("/") + saveExportName); + sceneResourcesDir.removeRecursively(); + QFile::remove(fullSaveFilePathName); +} diff --git a/editorlib/src/editorscenesaver.h b/editorlib/src/editorscenesaver.h new file mode 100644 index 0000000..d55314b --- /dev/null +++ b/editorlib/src/editorscenesaver.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D Editor of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef EDITORSCENESAVER_H +#define EDITORSCENESAVER_H + +#include <QtCore/qobject.h> + +namespace Qt3DCore { + class QEntity; +} + +class QTemporaryDir; + +class EditorSceneSaver : public QObject +{ + Q_OBJECT + +public: + explicit EditorSceneSaver(QObject *parent = 0); + ~EditorSceneSaver(); + + bool saveScene(Qt3DCore::QEntity *sceneEntity, const QString &activeSceneCamera, + const QString &saveFileName, bool autosave = false); + + Qt3DCore::QEntity *loadScene(const QString &fileName, Qt3DCore::QEntity *camera); + void deleteSavedScene(const QString &saveFileName, bool autosave = false); + +private: + QTemporaryDir *m_loadDir; +}; + +#endif // EDITORSCENESAVER_H diff --git a/editorlib/src/editorutils.cpp b/editorlib/src/editorutils.cpp index 82a2fe2..fb8b619 100644 --- a/editorlib/src/editorutils.cpp +++ b/editorlib/src/editorutils.cpp @@ -1298,6 +1298,22 @@ void EditorUtils::setEnabledToSubtree(Qt3DCore::QEntity *entity, bool enable) } } +Qt3DCore::QEntity *EditorUtils::findEntityByName(Qt3DCore::QEntity *entity, const QString &name) +{ + if (entity->objectName() == name) { + return entity; + } else { + for (QObject *child : entity->children()) { + Qt3DCore::QEntity *childEntity = qobject_cast<Qt3DCore::QEntity *>(child); + if (childEntity) { + Qt3DCore::QEntity *foundEntity = findEntityByName(childEntity, name); + if (foundEntity) + return foundEntity; + } + } + } + return nullptr; +} template <typename T> void EditorUtils::copyRenderParameters(T *source, T *target) diff --git a/editorlib/src/editorutils.h b/editorlib/src/editorutils.h index 582462f..c2fde0c 100644 --- a/editorlib/src/editorutils.h +++ b/editorlib/src/editorutils.h @@ -173,6 +173,7 @@ public: static void lockProperty(const QByteArray &lockPropertyName, QObject *obj, bool lock); static InsertableEntities insertableEntityType(Qt3DCore::QEntity *entity); static void setEnabledToSubtree(Qt3DCore::QEntity *entity, bool enable); + static Qt3DCore::QEntity *findEntityByName(Qt3DCore::QEntity *entity, const QString &name); private: // Private constructor to ensure no actual instance is created |