From fd8cb9db653b113960a5bf3d899bc3ea6e05d038 Mon Sep 17 00:00:00 2001 From: Achim Stremplat Date: Mon, 31 Jan 2022 00:32:07 +0100 Subject: [PATCH] Release v0.12.0 --- CHANGELOG.md | 41 + CMakeLists.txt | 12 +- EditorApp/CMakeLists.txt | 1 + EditorApp/DebugActions.cpp | 35 +- EditorApp/OpenRecentMenu.cpp | 16 +- EditorApp/main.cpp | 13 +- EditorApp/mainwindow.cpp | 111 +- EditorApp/mainwindow.h | 3 +- EditorApp/mainwindow.ui | 25 +- HeadlessApp/CMakeLists.txt | 1 + HeadlessApp/main.cpp | 47 +- README.md | 2 + .../include/application/RaCoProject.h | 12 +- .../src/ExternalProjectsStore.cpp | 4 +- .../libApplication/src/RaCoApplication.cpp | 22 +- components/libApplication/src/RaCoProject.cpp | 212 +- .../tests/RaCoApplication_test.cpp | 80 +- .../libApplication/tests/RaCoProject_test.cpp | 327 +-- .../include/components/DataChangeDispatcher.h | 7 +- .../components/FileChangeListenerImpl.h | 3 +- .../include/components/RaCoPreferences.h | 4 +- .../src/DataChangeDispatcher.cpp | 26 +- .../src/FileChangeListenerImpl.cpp | 24 +- .../libComponents/src/RaCoPreferences.cpp | 21 +- .../tests/FileChangeMonitor_test.cpp | 38 +- .../libMeshLoader/src/CTMFileLoader.cpp | 16 +- .../libMeshLoader/src/glTFFileLoader.cpp | 3 +- .../libMeshLoader/tests/FileLoader_test.cpp | 14 +- .../include/ramses_adaptor/NodeAdaptor.h | 1 + .../ramses_adaptor/TextureSamplerAdaptor.h | 2 +- .../libRamsesBase/include/ramses_base/Utils.h | 2 + .../src/ramses_adaptor/SceneAdaptor.cpp | 8 +- .../ramses_adaptor/TextureSamplerAdaptor.cpp | 10 +- .../src/ramses_base/BaseEngineBackend.cpp | 4 +- .../libRamsesBase/src/ramses_base/Utils.cpp | 49 +- .../tests/AnimationAdaptor_test.cpp | 34 +- .../tests/AnimationChannelAdaptor_test.cpp | 18 +- .../libRamsesBase/tests/LinkAdaptor_test.cpp | 78 +- .../tests/LuaScriptAdaptor_test.cpp | 42 +- .../libRamsesBase/tests/MeshAdaptor_test.cpp | 8 +- .../tests/MeshNodeAdaptor_test.cpp | 83 +- .../tests/OrthographicCameraAdaptor_test.cpp | 2 +- .../tests/PerspectiveCameraAdaptor_test.cpp | 2 +- .../libRamsesBase/tests/RamsesLogic_test.cpp | 32 + .../libRamsesBase/tests/Resources_test.cpp | 16 +- .../libRamsesBase/tests/SceneContext_test.cpp | 18 +- datamodel/libCore/CMakeLists.txt | 13 +- .../libCore/include/core/ChangeRecorder.h | 2 +- .../libCore/include/core/CommandInterface.h | 10 +- datamodel/libCore/include/core/Context.h | 17 +- .../libCore/include/core/CoreAnnotations.h | 8 + .../include/core/DynamicEditorObject.h | 102 + datamodel/libCore/include/core/EditorObject.h | 2 + datamodel/libCore/include/core/Handles.h | 10 +- datamodel/libCore/include/core/PathManager.h | 73 +- .../libCore/include/core/PrefabOperations.h | 4 +- ...sProjectMigration.h => ProjectMigration.h} | 17 +- .../include/core/ProjectMigrationToV23.h | 16 +- .../libCore/include/core/ProjectSettings.h | 56 + .../libCore/include/core/ProxyObjectFactory.h | 232 ++ datamodel/libCore/include/core/ProxyTypes.h | 163 ++ datamodel/libCore/include/core/Queries.h | 4 +- .../libCore/include/core/Serialization.h | 93 +- .../include/core/SerializationFunctions.h | 53 - .../libCore/include/core/SerializationKeys.h | 2 + datamodel/libCore/include/core/Undo.h | 61 +- .../include/core/UserObjectFactoryInterface.h | 12 +- datamodel/libCore/src/ChangeRecorder.cpp | 16 +- datamodel/libCore/src/CommandInterface.cpp | 59 +- datamodel/libCore/src/Context.cpp | 111 +- datamodel/libCore/src/EditorObject.cpp | 58 +- datamodel/libCore/src/ExtrefOperations.cpp | 2 +- datamodel/libCore/src/Handles.cpp | 31 + datamodel/libCore/src/PathManager.cpp | 137 +- datamodel/libCore/src/PathQueries.cpp | 8 +- datamodel/libCore/src/PrefabOperations.cpp | 41 +- datamodel/libCore/src/Project.cpp | 19 +- datamodel/libCore/src/ProjectMigration.cpp | 608 +++++ .../libCore/src/ProjectMigrationToV23.cpp | 1968 +++++++++++++++++ datamodel/libCore/src/ProxyObjectFactory.cpp | 141 ++ datamodel/libCore/src/ProxyTypes.cpp | 42 + datamodel/libCore/src/Queries.cpp | 22 +- .../libCore/src/RamsesProjectMigration.cpp | 957 -------- datamodel/libCore/src/Serialization.cpp | 550 +++-- datamodel/libCore/src/Undo.cpp | 93 +- .../src/UserObjectFactoryInterface.cpp | 34 - datamodel/libCore/tests/CMakeLists.txt | 3 + datamodel/libCore/tests/Context_test.cpp | 172 +- .../libCore/tests/Deserialization_test.cpp | 88 +- .../libCore/tests/ExternalReference_test.cpp | 348 +-- datamodel/libCore/tests/Link_test.cpp | 2 +- datamodel/libCore/tests/PathManager_test.cpp | 135 ++ datamodel/libCore/tests/Prefab_test.cpp | 260 ++- .../libCore/tests/ProjectMigration_test.cpp | 233 +- datamodel/libCore/tests/Queries_Tags_test.cpp | 2 +- .../libCore/tests/Serialization_test.cpp | 139 +- datamodel/libCore/tests/Undo_test.cpp | 483 +++- ...ScriptWithRefToUserTypeWithAnnotation.json | 6 +- .../libCore/tests/migrationTestData/V21.rca | 708 ++++++ .../libCore/tests/migrationTestData/V23.rca | 1109 ++++++++++ .../migrationTestData/version-current.rca | 274 ++- .../include/data_storage/BasicTypes.h | 241 +- .../include/data_storage/Table.h | 2 + .../include/data_storage/Value.h | 7 + datamodel/libDataStorage/src/Table.cpp | 6 + datamodel/libDataStorage/src/Value.cpp | 175 +- .../include/testing/MockUserTypes.h | 41 +- .../libTesting/include/testing/RacoBaseTest.h | 39 +- .../include/testing/TestEnvironmentCore.h | 86 +- .../libTesting/include/testing/TestUtil.h | 6 +- datamodel/libUserTypes/CMakeLists.txt | 5 + .../include/user_types/BaseCamera.h | 1 - .../include/user_types/MeshNode.h | 1 + .../include/user_types/PrefabInstance.h | 12 +- .../include/user_types/UserObjectFactory.h | 44 +- datamodel/libUserTypes/src/LuaScript.cpp | 18 +- .../libUserTypes/src/LuaScriptModule.cpp | 5 +- datamodel/libUserTypes/src/Material.cpp | 37 +- datamodel/libUserTypes/src/MeshNode.cpp | 8 + datamodel/libUserTypes/src/PrefabInstance.cpp | 71 +- .../libUserTypes/src/UserObjectFactory.cpp | 46 +- datamodel/libUserTypes/src/Validation.h | 4 +- .../tests/AnimationChannel_test.cpp | 14 +- .../libUserTypes/tests/Animation_test.cpp | 2 +- datamodel/libUserTypes/tests/CMakeLists.txt | 9 +- .../tests/LuaScriptModule_test.cpp | 22 +- .../libUserTypes/tests/LuaScript_test.cpp | 248 ++- .../libUserTypes/tests/Material_test.cpp | 97 + .../libUserTypes/tests/MeshNode_test.cpp | 250 +++ .../include/common_widgets/PreferencesView.h | 4 - gui/libCommonWidgets/src/ExportDialog.cpp | 31 +- gui/libCommonWidgets/src/PreferencesView.cpp | 42 +- .../include/object_tree_view/ObjectTreeView.h | 8 +- .../object_tree_view_model/ObjectTreeNode.h | 25 +- .../ObjectTreeViewDefaultModel.h | 20 +- .../ObjectTreeViewExternalProjectModel.h | 29 +- .../ObjectTreeViewPrefabModel.h | 4 + .../ObjectTreeViewResourceModel.h | 1 + .../src/object_tree_view/ObjectTreeView.cpp | 90 +- .../object_tree_view_model/ObjectTreeNode.cpp | 78 +- .../ObjectTreeViewDefaultModel.cpp | 200 +- .../ObjectTreeViewExternalProjectModel.cpp | 116 +- .../ObjectTreeViewPrefabModel.cpp | 24 +- .../ObjectTreeViewResourceModel.cpp | 9 +- .../ObjectTreeViewTopLevelSortProxyModel.cpp | 12 +- .../tests/ObjectTreeNode_test.cpp | 4 +- .../tests/ObjectTreeViewDefaultModel_test.cpp | 136 +- ...bjectTreeViewExternalProjectModel_test.cpp | 20 +- .../tests/ObjectTreeViewMultipleModels_test.h | 18 - .../tests/ObjectTreeViewPrefabModel_test.cpp | 47 +- .../ObjectTreeViewResourceModel_test.cpp | 42 +- gui/libPropertyBrowser/CMakeLists.txt | 2 + .../property_browser/PropertyBrowserItem.h | 10 +- .../property_browser/PropertySubtreeView.h | 2 +- .../include/property_browser/WidgetFactory.h | 5 +- .../property_browser/controls/SpinBox.h | 39 +- .../property_browser/editors/BoolEditor.h | 4 +- .../property_browser/editors/DoubleEditor.h | 4 +- .../editors/EnumerationEditor.h | 4 +- .../property_browser/editors/IntEditor.h | 4 +- .../property_browser/editors/PropertyEditor.h | 28 + .../property_browser/editors/RefEditor.h | 7 +- .../property_browser/editors/StringEditor.h | 6 +- .../editors/TagContainerEditor.h | 5 +- .../property_browser/editors/URIEditor.h | 1 - .../property_browser/editors/VecNTEditor.h | 193 +- .../src/PropertyBrowserItem.cpp | 54 +- .../src/PropertySubtreeView.cpp | 13 +- gui/libPropertyBrowser/src/WidgetFactory.cpp | 6 +- .../src/controls/ExpandButton.cpp | 10 +- .../src/controls/SpinBox.cpp | 42 + .../src/editors/BoolEditor.cpp | 3 +- .../src/editors/DoubleEditor.cpp | 2 +- .../src/editors/EnumerationEditor.cpp | 2 +- .../src/editors/IntEditor.cpp | 2 +- .../src/editors/LinkEditor.cpp | 8 +- .../src/editors/PropertyEditor.cpp | 24 + .../src/editors/RefEditor.cpp | 28 +- .../src/editors/StringEditor.cpp | 20 +- .../src/editors/TagContainerEditor.cpp | 8 +- .../src/editors/URIEditor.cpp | 77 +- gui/libPropertyBrowser/tests/CMakeLists.txt | 3 +- .../tests/PrimitiveEditorsDataChange_test.cpp | 5 +- .../tests/PropertyBrowserItem_test.cpp | 46 +- gui/libPropertyBrowser/tests/SpinBox_test.cpp | 78 + .../tests/URIEditor_test.cpp | 14 +- .../ramses_widgets/PreviewContentWidget.h | 4 + .../ramses_widgets/PreviewFramebufferScene.h | 7 +- .../ramses_widgets/RamsesPreviewWindow.h | 1 + .../src/PreviewContentWidget.cpp | 8 + .../src/PreviewFramebufferScene.cpp | 13 +- .../src/PreviewMainWindow.cpp | 56 +- gui/libRamsesWidgets/src/PreviewMainWindow.ui | 186 +- .../src/RamsesPreviewWindow.cpp | 7 +- gui/libRamsesWidgets/src/RendererBackend.cpp | 1 + gui/libStyle/include/style/Icons.h | 8 +- gui/libStyle/src/Icons.cpp | 8 +- resources/CMakeLists.txt | 1 + resources/example_scene.rca | 966 ++++++++ styles/icons.qrc | 2 +- third_party/CMakeLists.txt | 45 + third_party/ramses-logic | 2 +- utils/libLogSystem/include/log_system/log.h | 7 +- utils/libLogSystem/src/log.cpp | 39 +- utils/libUtils/CMakeLists.txt | 7 +- utils/libUtils/include/utils/FileUtils.h | 8 +- utils/libUtils/include/utils/u8path.h | 95 + utils/libUtils/src/FileUtils.cpp | 34 +- utils/libUtils/src/PathUtils.cpp | 40 - utils/libUtils/src/u8path.cpp | 200 ++ utils/libUtils/tests/CMakeLists.txt | 25 + utils/libUtils/tests/FileUtils_test.cpp | 77 + utils/libUtils/tests/u8path_test.cpp | 101 + 213 files changed, 12614 insertions(+), 3888 deletions(-) create mode 100644 datamodel/libCore/include/core/DynamicEditorObject.h rename datamodel/libCore/include/core/{RamsesProjectMigration.h => ProjectMigration.h} (85%) rename utils/libUtils/include/utils/PathUtils.h => datamodel/libCore/include/core/ProjectMigrationToV23.h (59%) create mode 100644 datamodel/libCore/include/core/ProxyObjectFactory.h create mode 100644 datamodel/libCore/include/core/ProxyTypes.h delete mode 100644 datamodel/libCore/include/core/SerializationFunctions.h create mode 100644 datamodel/libCore/src/ProjectMigration.cpp create mode 100644 datamodel/libCore/src/ProjectMigrationToV23.cpp create mode 100644 datamodel/libCore/src/ProxyObjectFactory.cpp create mode 100644 datamodel/libCore/src/ProxyTypes.cpp delete mode 100644 datamodel/libCore/src/RamsesProjectMigration.cpp delete mode 100644 datamodel/libCore/src/UserObjectFactoryInterface.cpp create mode 100644 datamodel/libCore/tests/PathManager_test.cpp create mode 100644 datamodel/libCore/tests/migrationTestData/V21.rca create mode 100644 datamodel/libCore/tests/migrationTestData/V23.rca create mode 100644 datamodel/libUserTypes/tests/Material_test.cpp create mode 100644 datamodel/libUserTypes/tests/MeshNode_test.cpp create mode 100644 gui/libPropertyBrowser/include/property_browser/editors/PropertyEditor.h create mode 100644 gui/libPropertyBrowser/src/editors/PropertyEditor.cpp create mode 100644 gui/libPropertyBrowser/tests/SpinBox_test.cpp create mode 100644 resources/example_scene.rca create mode 100644 utils/libUtils/include/utils/u8path.h delete mode 100644 utils/libUtils/src/PathUtils.cpp create mode 100644 utils/libUtils/src/u8path.cpp create mode 100644 utils/libUtils/tests/CMakeLists.txt create mode 100644 utils/libUtils/tests/FileUtils_test.cpp create mode 100644 utils/libUtils/tests/u8path_test.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b4ffacb..853bd2ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,47 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h ### Known Issues --> +## [0.12.0] Bug Fixes and Usability Improvements +* **File version number has changed. Files saved with RaCo 0.12.0 cannot be opened by previous versions.** + +### Added +* Whenever a private Material is created, it will now always be created with the same Options as the shared Material. +* New collapsable list entry "External References" serves to group all external references together in the resources and prefab views. +* Added command line argument "--loglevel" to headless Ramses Composer for adjusting log verbosity. +* Added color picker for vector properties in the property browser. +* In the scene graph, prefab, project browser and property browser views, shift click on the arrow symbols will now recursively expand or collapse items. +* A new filtering options menu for the ramses preview widget now allows changing from nearest neighbor sampling to linear sampling. +* Added a ramses-logic-viewer build to RaCo binary folder. +* It is now possible to do simple calculations (like "1920/1080" as aspect ratio) directly in the number inputs of the property browser. + * Currently allowed operations: addition (+), substraction (-), multiplication (*), division (/), integer division (//), modulo (%), exponentiation (^) and changing precedence using parentheses. + * Results are calculated immediately and the mathematical expression is not stored. + +### Changes +* Update from ramses-logic 0.13.0 to ramses-logic 0.14.2 +* Update from ramses 27.0.114 to 27.0.115 +* The default resource directories can now be set in the per-project in the project settings instead of the Ramses Composer preferences. +* The button to open the underlying file for LuaScripts and other objects is now enabled in cases where the object itself cannot be edited (due to being an ExtRef or part of a PrefabInstance). +* Log file size is now limited to 10 MB, with new log files being created once this is exceeded. A maximum of 250 MB of log files can be created before old files get deleted. +* Opening the link editor no longer has the search field pre-filled with the name of the current link, if one exists. +* More details for LuaScriptModule errors in LuaScripts - invalid LuaScriptModule assignments are now also shown as individual errors. +* Removed "Debug">"Add dummy scene" menu element. +* Config and log files moved from program folder to user folder (e.g. %APPDATA%/RamsesComposer on Windows). +* Log file for headless Ramses Composer is now named "RaCoHeadless.log". +* The values of new lua input properties for LuaScripts which are direct children of PrefabInstances are now propagated from the corresponding Prefab LuaScript during external reference updates making it possible to set default values in the external project. +* Optimize simultaneous deletion of many links in scenes with many objects. +* Object IDs of the PrefabInstance children objects are now deterministically determined from the corresponding Prefab child and the PrefabInstance itself. + +### Fixes +* The application now handles scenarios where saving configfiles is not possible more gracefully. +* Fixed problems loading projects from paths that contain non-latin characters. +* For empty LuaScript files the correct error message is now shown. +* Fixed Ramses API errors appearing in the log window during new project creation. +* Properties "Flip Vertically" and "Generate Mipmaps" in a texture are now updating the Ramses texture immediately. +* The ramses preview toolbar can no longer be hidden with right click, since this was an unintended feature. +* Removed non-functional "?"-Button from all dialog windows. +* Fixed MeshNodes in PrefabInstances having a different private Material uniform order than their Prefab counterparts after changing Material reference. +* Fixed RaCoHeadless not exporting links. + ## [0.11.1] Interim Release - The Tangent Fix ### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index 7236a87b..44fba4c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ cmake_minimum_required(VERSION 3.19) SET(CMAKE_CONFIGURATION_TYPES "Debug;RelWithDebInfo") -project(RaCoOS VERSION 0.11.1) +project(RaCoOS VERSION 0.12.0) SET(RACO_RELEASE_DIRECTORY ${CMAKE_BINARY_DIR}/release) @@ -23,7 +23,11 @@ if(NOT RACO_QT_BASE) # is shared with other products, so please don't randomly change that. set(QT_ROOT $ENV{QTBASEDIR}) if(NOT QT_ROOT) - set(QT_ROOT "C:/Qt") + if(MSVC_IDE) + set(QT_ROOT "C:/Qt") + else() + set(QT_ROOT "/usr/local/opt/Qt") + endif() message("Set QT_ROOT to ${QT_ROOT} from value hardcoded into CMakeLists.txt.") else() message("Set QT_ROOT to ${QT_ROOT} from QTBASEDIR environment variable.") @@ -154,6 +158,9 @@ macro(deploy_gui_shared_dlls tgt) deploy_headless_shared_dlls(${tgt}) deploy_dlls_and_strip_symbols(${tgt} qtadvanceddocking) endmacro() +macro(deploy_viewer tgt) + deploy_dlls_and_strip_symbols(${tgt} ramses-logic-viewer) +endmacro() if(PACKAGE_TESTS) enable_testing() @@ -272,7 +279,6 @@ set_target_properties (RaCoPrepareReleaseFolder PROPERTIES FOLDER Packaging) # Create default directories for configuration files and project files. add_custom_command(TARGET RaCoPrepareReleaseFolder POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "${RACO_RELEASE_DIRECTORY}/configfiles" COMMAND ${CMAKE_COMMAND} -E make_directory "${RACO_RELEASE_DIRECTORY}/projects" ) diff --git a/EditorApp/CMakeLists.txt b/EditorApp/CMakeLists.txt index 0b05c647..5e5351d2 100644 --- a/EditorApp/CMakeLists.txt +++ b/EditorApp/CMakeLists.txt @@ -61,3 +61,4 @@ deploy_gui_shared_dlls(RaCoEditor) deploy_ramses_with_renderer_shared_dlls(RaCoEditor) deploy_qt(RaCoEditor) deploy_raco_cppruntime_dlls(RaCoEditor) +deploy_viewer(RaCoEditor) \ No newline at end of file diff --git a/EditorApp/DebugActions.cpp b/EditorApp/DebugActions.cpp index efa4eebe..f920999d 100644 --- a/EditorApp/DebugActions.cpp +++ b/EditorApp/DebugActions.cpp @@ -27,7 +27,6 @@ QMetaObject::Connection openLogFileConnection; QMetaObject::Connection actionDumpObjectTree; -QMetaObject::Connection actionCreateDummyScene; void configureDebugActions(Ui::MainWindow* ui, QWidget* widget, raco::core::CommandInterface* commandInterface) { using raco::components::Naming; @@ -35,39 +34,7 @@ void configureDebugActions(Ui::MainWindow* ui, QWidget* widget, raco::core::Comm // Debug actions if (openLogFileConnection) QObject::disconnect(openLogFileConnection); - openLogFileConnection = QObject::connect(ui->actionOpenLogFile, &QAction::triggered, []() { QDesktopServices::openUrl(QUrl{raco::core::PathManager::logFilePath().c_str(), QUrl::TolerantMode}); }); + openLogFileConnection = QObject::connect(ui->actionOpenLogFileDirectory, &QAction::triggered, []() { QDesktopServices::openUrl(QUrl::fromLocalFile(raco::core::PathManager::logFileDirectory().string().c_str())); }); if (actionDumpObjectTree) QObject::disconnect(actionDumpObjectTree); actionDumpObjectTree = QObject::connect(ui->actionDumpObjectTree, &QAction::triggered, [widget]() { raco::debug::dumpLayoutInfo(widget); }); - if (actionCreateDummyScene) QObject::disconnect(actionCreateDummyScene); - actionCreateDummyScene = QObject::connect(ui->actionCreateDummyScene, &QAction::triggered, [commandInterface]() { - const auto& prefs = raco::components::RaCoPreferences::instance(); - - auto mesh = commandInterface->createObject(raco::user_types::Mesh::typeDescription.typeName, Naming::format("DuckMesh")); - commandInterface->set(raco::core::ValueHandle{mesh, &Mesh::bakeMeshes_}, true); - commandInterface->set(raco::core::ValueHandle{mesh, &Mesh::uri_}, - (raco::core::PathManager::defaultResourceDirectory() / prefs.meshSubdirectory.toStdString() / "Duck.glb").generic_string()); - auto material = commandInterface->createObject(raco::user_types::Material::typeDescription.typeName, Naming::format("DuckMaterial")); - commandInterface->set(raco::core::ValueHandle{material, &Material::uriVertex_}, - (raco::core::PathManager::defaultResourceDirectory() / prefs.shaderSubdirectory.toStdString() / "simple_texture.vert").generic_string()); - commandInterface->set(raco::core::ValueHandle{material, &Material::uriFragment_}, - (raco::core::PathManager::defaultResourceDirectory() / prefs.shaderSubdirectory.toStdString() / "simple_texture.frag").generic_string()); - auto texture = commandInterface->createObject(raco::user_types::Texture::typeDescription.typeName, Naming::format("DuckTexture")); - commandInterface->set(raco::core::ValueHandle{texture, &Texture::uri_}, - (raco::core::PathManager::defaultResourceDirectory() / prefs.imageSubdirectory.toStdString() / "DuckCM.png").generic_string()); - - auto node = commandInterface->createObject(raco::user_types::Node::typeDescription.typeName, Naming::format("DuckNode")); - auto meshNode = commandInterface->createObject(raco::user_types::MeshNode::typeDescription.typeName, Naming::format("DuckMeshNode")); - commandInterface->moveScenegraphChildren({meshNode}, node); - - commandInterface->set(raco::core::ValueHandle{meshNode, &MeshNode::mesh_}, mesh); - commandInterface->set(raco::core::ValueHandle{meshNode, {"materials", "material", "material"}}, material); - - commandInterface->set(raco::core::ValueHandle{meshNode, &MeshNode::translation_, &Vec3f::y}, -1.7); - commandInterface->set(raco::core::ValueHandle{meshNode, &MeshNode::rotation_, &Vec3f::y}, -160.0); - commandInterface->set(raco::core::ValueHandle{meshNode, &MeshNode::scale_, &Vec3f::x}, 2.0); - commandInterface->set(raco::core::ValueHandle{meshNode, &MeshNode::scale_, &Vec3f::y}, 2.0); - commandInterface->set(raco::core::ValueHandle{meshNode, &MeshNode::scale_, &Vec3f::z}, 2.0); - - commandInterface->set(raco::core::ValueHandle{material, {"uniforms", "u_Tex"}}, texture); - }); } diff --git a/EditorApp/OpenRecentMenu.cpp b/EditorApp/OpenRecentMenu.cpp index 2537b673..acf98023 100644 --- a/EditorApp/OpenRecentMenu.cpp +++ b/EditorApp/OpenRecentMenu.cpp @@ -10,7 +10,8 @@ #include "OpenRecentMenu.h" #include "core/PathManager.h" -#include "utils/PathUtils.h" +#include "log_system/log.h" +#include "utils/u8path.h" #include OpenRecentMenu::OpenRecentMenu(QWidget* parent) : QMenu{"Open Recent", parent} { @@ -21,7 +22,7 @@ OpenRecentMenu::OpenRecentMenu(QWidget* parent) : QMenu{"Open Recent", parent} { void OpenRecentMenu::addRecentFile(const QString& file) { if (file.size()) { - QSettings recentFilesStore(raco::core::PathManager::recentFilesStorePath().c_str(), QSettings::IniFormat); + auto recentFilesStore = raco::core::PathManager::recentFilesStoreSettings(); QStringList recentFiles{recentFilesStore.value("recent_files").toStringList()}; auto it = std::find(recentFiles.begin(), recentFiles.end(), file); if (it != recentFiles.end()) { @@ -34,11 +35,16 @@ void OpenRecentMenu::addRecentFile(const QString& file) { } recentFilesStore.setValue("recent_files", recentFiles); refreshRecentFileMenu(); + + recentFilesStore.sync(); + if (recentFilesStore.status() != QSettings::NoError) { + LOG_ERROR(raco::log_system::COMMON, "Saving recent files list failed: {}", raco::core::PathManager::recentFilesStorePath().string()); + } } } void OpenRecentMenu::refreshRecentFileMenu() { - QSettings recentFilesStore(raco::core::PathManager::recentFilesStorePath().c_str(), QSettings::IniFormat); + auto recentFilesStore = raco::core::PathManager::recentFilesStoreSettings(); QStringList recentFiles{recentFilesStore.value("recent_files").toStringList()}; setDisabled(recentFiles.size() == 0); while (actions().size() > 0) { @@ -48,10 +54,10 @@ void OpenRecentMenu::refreshRecentFileMenu() { auto* action = addAction(file); auto fileString = file.toStdString(); - if (!raco::utils::path::exists(fileString)) { + if (!raco::utils::u8path(fileString).exists()) { action->setEnabled(false); action->setText(file + " (unavailable)"); - } else if (!raco::utils::path::userHasReadAccess(fileString)) { + } else if (!raco::utils::u8path(fileString).userHasReadAccess()) { action->setEnabled(false); action->setText(file + " (no read access)"); } diff --git a/EditorApp/main.cpp b/EditorApp/main.cpp index adcc4a04..f91ad338 100644 --- a/EditorApp/main.cpp +++ b/EditorApp/main.cpp @@ -18,7 +18,7 @@ #include "ramses_widgets/RendererBackend.h" #include "style/RaCoStyle.h" #include "utils/CrashDump.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include #include @@ -44,6 +44,9 @@ int main(int argc, char *argv[]) { QCoreApplication::setApplicationName("Ramses Composer"); QCoreApplication::setApplicationVersion(RACO_OSS_VERSION); + // QDialogs will show a "?"-Button by default. While it is possible to disable this for every single dialog, we never need this and thus, disabling it globally is easier. + QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); + QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); @@ -92,9 +95,9 @@ int main(int argc, char *argv[]) { createStdOutConsole(); } - raco::core::PathManager::init(QCoreApplication::applicationDirPath().toStdString()); - - raco::log_system::init(raco::core::PathManager::logFilePath().c_str()); + auto appDataPath = raco::utils::u8path(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString()).parent_path() / "RamsesComposer"; + raco::core::PathManager::init(QCoreApplication::applicationDirPath().toStdString(), appDataPath); + raco::log_system::init(raco::core::PathManager::logFileEditorName().internalPath().native()); const QStringList args = parser.positionalArguments(); @@ -111,7 +114,7 @@ int main(int argc, char *argv[]) { if (projectFileCandidate) { if (projectFileCandidate->suffix().compare(raco::names::PROJECT_FILE_EXTENSION, Qt::CaseInsensitive) == 0) { if (projectFileCandidate->exists()) { - if (raco::utils::path::userHasReadAccess(projectFileCandidate->filePath().toStdString())) { + if (raco::utils::u8path(projectFileCandidate->filePath().toStdString()).userHasReadAccess()) { projectFile = projectFileCandidate->absoluteFilePath(); LOG_INFO(raco::log_system::COMMON, "starting Ramses Composer with project file {}", projectFile.toStdString()); } else { diff --git a/EditorApp/mainwindow.cpp b/EditorApp/mainwindow.cpp index 5a89f33e..b479f3e0 100644 --- a/EditorApp/mainwindow.cpp +++ b/EditorApp/mainwindow.cpp @@ -45,7 +45,7 @@ #include "components/Naming.h" #include "application/RaCoApplication.h" #include "components/RaCoPreferences.h" -#include "core/RamsesProjectMigration.h" +#include "core/ProjectMigration.h" #include "core/Serialization.h" #include "ui_mainwindow.h" @@ -68,7 +68,7 @@ #include "utils/FileUtils.h" #include "versiondialog.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include @@ -108,8 +108,8 @@ RaCoDockManager* createDockManager(MainWindow* parent) { dockManager->iconProvider().registerCustomIcon(ads::DockAreaMenuIcon, parent->style()->standardIcon(QStyle::StandardPixmap::SP_TitleBarMenuButton)); dockManager->iconProvider().registerCustomIcon(ads::DockAreaUndockIcon, parent->style()->standardIcon(QStyle::StandardPixmap::SP_TitleBarNormalButton)); - if (QFile{PathManager::layoutFilePath().c_str()}.exists()) { - QSettings settings(PathManager::layoutFilePath().c_str(), QSettings::IniFormat); + if (PathManager::layoutFilePath().existsFile()) { + auto settings = raco::core::PathManager::layoutSettings(); dockManager->loadAllLayouts(settings); } @@ -170,10 +170,9 @@ void createAndAddProjectSettings(MainWindow* mainWindow, const char* dockObjName dockManager->addDockWidget(ads::RightDockWidgetArea, dock); } -ads::CDockAreaWidget* createAndAddObjectTree(const char* title, const char* dockObjName, raco::object_tree::model::ObjectTreeViewDefaultModel *dockModel, QSortFilterProxyModel* sortFilterModel, const raco::object_tree::model::ObjectFilterFunc& filter, ads::DockWidgetArea area, MainWindow* mainWindow, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager, ads::CDockAreaWidget* dockArea) { +ads::CDockAreaWidget* createAndAddObjectTree(const char* title, const char* dockObjName, raco::object_tree::model::ObjectTreeViewDefaultModel *dockModel, QSortFilterProxyModel* sortFilterModel, ads::DockWidgetArea area, MainWindow* mainWindow, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager, ads::CDockAreaWidget* dockArea) { auto* dockObjectView = new raco::object_tree::view::ObjectTreeDock(title, mainWindow); QObject::connect(dockModel, &raco::object_tree::model::ObjectTreeViewDefaultModel::meshImportFailed, mainWindow, &MainWindow::showMeshImportErrorMessage); - dockModel->setProjectObjectFilterFunction(filter); dockModel->buildObjectTree(); auto newTreeView = new raco::object_tree::view::ObjectTreeView(title, dockModel, sortFilterModel); if (sortFilterModel) { @@ -190,7 +189,7 @@ ads::CDockAreaWidget* createAndAddObjectTree(const char* title, const char* dock ads::CDockAreaWidget* createAndAddProjectBrowser(MainWindow* mainWindow, const char* dockObjName, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager, raco::application::RaCoApplication* racoApplication, ads::CDockAreaWidget* dockArea) { auto* model = new raco::object_tree::model::ObjectTreeViewExternalProjectModel(racoApplication->activeRaCoProject().commandInterface(), racoApplication->dataChangeDispatcher(), racoApplication->externalProjects()); - return createAndAddObjectTree(MainWindow::DockWidgetTypes::PROJECT_BROWSER, dockObjName, model, new QSortFilterProxyModel, Queries::filterForVisibleObjects, ads::BottomDockWidgetArea, mainWindow, dockManager, treeDockManager, dockArea); + return createAndAddObjectTree(MainWindow::DockWidgetTypes::PROJECT_BROWSER, dockObjName, model, new QSortFilterProxyModel, ads::BottomDockWidgetArea, mainWindow, dockManager, treeDockManager, dockArea); } ads::CDockAreaWidget* createAndAddResourceTree(MainWindow* mainWindow, const char* dockObjName, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager, raco::application::RaCoApplication* racoApplication, ads::CDockAreaWidget* dockArea) { @@ -212,9 +211,6 @@ ads::CDockAreaWidget* createAndAddResourceTree(MainWindow* mainWindow, const cha auto* model = new raco::object_tree::model::ObjectTreeViewResourceModel(racoApplication->activeRaCoProject().commandInterface(), racoApplication->dataChangeDispatcher(), racoApplication->externalProjects(), allowedCreateableUserTypes); return createAndAddObjectTree( MainWindow::DockWidgetTypes::RESOURCES, dockObjName, model, new QSortFilterProxyModel, - [](const std::vector& objects) -> std::vector { - return Queries::filterByTypeName(objects, allowedCreateableUserTypes); - }, ads::BottomDockWidgetArea, mainWindow, dockManager, treeDockManager, dockArea); } @@ -235,19 +231,6 @@ ads::CDockAreaWidget* createAndAddPrefabTree(MainWindow* mainWindow, const char* return createAndAddObjectTree( MainWindow::DockWidgetTypes::PREFABS, dockObjName, model, new raco::object_tree::model::ObjectTreeViewTopLevelSortFilterProxyModel, - [](const std::vector& objects) -> std::vector { - std::vector result{}; - std::copy_if(objects.begin(), objects.end(), std::back_inserter(result), - [](const SEditorObject& object) { - for (auto parent = object; parent; parent = parent->getParent()) { - if (parent->getTypeDescription().typeName == raco::user_types::Prefab::typeDescription.typeName) { - return true; - } - } - return false; - }); - return result; - }, ads::BottomDockWidgetArea, mainWindow, dockManager, treeDockManager, dockArea); } @@ -265,9 +248,6 @@ ads::CDockAreaWidget* createAndAddSceneGraphTree(MainWindow* mainWindow, const c auto* model = new raco::object_tree::model::ObjectTreeViewDefaultModel(racoApplication->activeRaCoProject().commandInterface(), racoApplication->dataChangeDispatcher(), racoApplication->externalProjects(), allowedCreateableUserTypes); return createAndAddObjectTree(MainWindow::DockWidgetTypes::SCENE_GRAPH, dockObjName, model, nullptr, - [](const std::vector& objects) -> std::vector { - return Queries::filterByTypeName(objects, allowedCreateableUserTypes); - }, ads::LeftDockWidgetArea, mainWindow, dockManager, treeDockManager, nullptr); } @@ -347,7 +327,7 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco } QObject::connect(ui->actionOpen, &QAction::triggered, [this]() { - auto file = QFileDialog::getOpenFileName(this, "Open", QString::fromStdString(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project)), "Ramses Composer Assembly (*.rca)"); + auto file = QFileDialog::getOpenFileName(this, "Open", QString::fromStdString(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project).string()), "Ramses Composer Assembly (*.rca)"); if (file.size() > 0) { openProject(file); } @@ -367,14 +347,6 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco auto dialog = new raco::common_widgets::PreferencesView(this); dialog->resize(500, 500); dialog->exec(); - if (racoApplication_) { - const auto& prefs = raco::components::RaCoPreferences::instance(); - raco::core::PathManager::setAllCachedPathRoots(racoApplication_->activeProjectFolder(), - prefs.imageSubdirectory.toStdString(), - prefs.meshSubdirectory.toStdString(), - prefs.scriptSubdirectory.toStdString(), - prefs.shaderSubdirectory.toStdString()); - } }); // View actions @@ -406,15 +378,13 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco } dockManager_->addCustomLayout(layoutName); - QSettings settings(PathManager::layoutFilePath().c_str(), QSettings::IniFormat); - dockManager_->saveCustomLayouts(settings); + saveDockManagerCustomLayouts(); } }); QObject::connect(ui->actionManageLayouts, &QAction::triggered, [this]() { SavedLayoutsDialog(dockManager_, this).exec(); - QSettings settings(PathManager::layoutFilePath().c_str(), QSettings::IniFormat); - dockManager_->saveCustomLayouts(settings); + saveDockManagerCustomLayouts(); }); QObject::connect(ui->actionProjectSettings, &QAction::triggered, [this]() { createAndAddProjectSettings(this, EditorObject::normalizedObjectID("").c_str(), dockManager_, &racoApplication_->activeRaCoProject(), racoApplication_->dataChangeDispatcher(), racoApplication_->activeRaCoProject().commandInterface()); }); @@ -429,9 +399,8 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco about.exec(); }); - if (restoreSettings() == false) { - createInitialWidgets(this, *rendererBackend_, racoApplication_, dockManager_, treeDockManager_); - } + restoreSettings(); + restoreCachedLayout(); // Setup updateApplicationTitle(); @@ -443,6 +412,17 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco renderTimerId_ = startTimer(timerInterval60Fps); } +void MainWindow::saveDockManagerCustomLayouts() { + auto settings = PathManager::layoutSettings(); + dockManager_->saveCustomLayouts(settings); + settings.sync(); + if (settings.status() != QSettings::NoError) { + LOG_ERROR(raco::log_system::COMMON, "Saving custom layout failed: {}", raco::core::PathManager::recentFilesStorePath().string()); + QMessageBox::critical(this, "Saving custom layout failed", QString("Custom layout data could not be saved to disk and will be lost after closing Ramses Composer. Check whether the application can write to its config directory.\nFile: ") + + QString::fromStdString(PathManager::layoutFilePath().string())); + } +} + void MainWindow::timerEvent(QTimerEvent* event) { auto startLoop = std::chrono::high_resolution_clock::now(); racoApplication_->doOneLoop(); @@ -466,10 +446,15 @@ void MainWindow::timerEvent(QTimerEvent* event) { void MainWindow::closeEvent(QCloseEvent* event) { if (resolveDirtiness()) { killTimer(renderTimerId_); - QSettings settings(PathManager::layoutFilePath().c_str(), QSettings::IniFormat); + auto settings = raco::core::PathManager::layoutSettings(); settings.setValue("geometry", saveGeometry()); settings.setValue("windowState", saveState()); dockManager_->saveCurrentLayoutInCache(settings); + + settings.sync(); + if (settings.status() != QSettings::NoError) { + LOG_WARNING(raco::log_system::COMMON, "Saving layout failed: {}", raco::core::PathManager::recentFilesStorePath().string()); + } QMainWindow::closeEvent(event); event->accept(); } else { @@ -477,27 +462,17 @@ void MainWindow::closeEvent(QCloseEvent* event) { } } -bool MainWindow::restoreSettings() { - if (QFile{PathManager::layoutFilePath().c_str()}.exists()) { - QSettings settings(PathManager::layoutFilePath().c_str(), QSettings::IniFormat); +void MainWindow::restoreSettings() { + if (PathManager::layoutFilePath().existsFile()) { + auto settings = raco::core::PathManager::layoutSettings(); restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("windowState").toByteArray()); - - if (settings.childGroups().contains("cachedLayout")) { - restoreCachedLayout(); - } else { - createInitialWidgets(this, *rendererBackend_, racoApplication_, dockManager_, treeDockManager_); - } - - return true; - } else { - return false; } } void MainWindow::openProject(const QString& file) { auto fileString = file.toStdString(); - if (!fileString.empty() && (!raco::utils::path::exists(fileString) || !raco::utils::path::userHasReadAccess(fileString))) { + if (!fileString.empty() && (!raco::utils::u8path(fileString).exists() || !raco::utils::u8path(fileString).userHasReadAccess())) { QMessageBox::warning(this, "File Load Error", fmt::format("Project file {} is not available for loading.\n\nCheck whether the file at the specified path still exists and that you have read access to that file.", fileString).c_str(), QMessageBox::Close); return; } @@ -511,9 +486,12 @@ void MainWindow::openProject(const QString& file) { } { - QSettings settings(PathManager::layoutFilePath().c_str(), QSettings::IniFormat); + auto settings = raco::core::PathManager::layoutSettings(); dockManager_->saveCurrentLayoutInCache(settings); - // destroying QSettings actually creates and saves settings file + settings.sync(); + if (settings.status() != QSettings::NoError) { + LOG_WARNING(raco::log_system::COMMON, "Saving layout failed: {}", raco::core::PathManager::recentFilesStorePath().string()); + } } // Delete all ui widgets (and their listeners) before changing the project @@ -525,7 +503,7 @@ void MainWindow::openProject(const QString& file) { racoApplication_->switchActiveRaCoProject(file); } catch (const raco::application::FutureFileVersion& error) { racoApplication_->switchActiveRaCoProject({}); - QMessageBox::warning(this, "File Load Error", fmt::format("Project file was created with newer version of {app_name}. Please upgrade.\n\nExpected File Version: {expected_file_version}\nFound File Version: {file_version}", fmt::arg("app_name", "Ramses Composer"), fmt::arg("expected_file_version", raco::core::RAMSES_PROJECT_FILE_VERSION), fmt::arg("file_version", error.fileVersion_)).c_str(), QMessageBox::Close); + QMessageBox::warning(this, "File Load Error", fmt::format("Project file was created with newer version of {app_name}. Please upgrade.\n\nExpected File Version: {expected_file_version}\nFound File Version: {file_version}", fmt::arg("app_name", "Ramses Composer"), fmt::arg("expected_file_version", raco::serialization::RAMSES_PROJECT_FILE_VERSION), fmt::arg("file_version", error.fileVersion_)).c_str(), QMessageBox::Close); } catch (const ExtrefError& error) { racoApplication_->switchActiveRaCoProject({}); QMessageBox::warning(this, "File Load Error", fmt::format("External reference update failed.\n\n{}", error.what()).c_str(), QMessageBox::Close); @@ -552,6 +530,7 @@ void MainWindow::openProject(const QString& file) { } MainWindow::~MainWindow() { + resetDockManager(); // sceneBackend needs to be reset first to unregister all adaptors (and their file listeners) // before the file change monitors and mesh caches get destroyed racoApplication_->resetScene(); @@ -587,7 +566,7 @@ bool MainWindow::saveActiveProject() { bool MainWindow::saveAsActiveProject() { if (racoApplication_->canSaveActiveProject()) { bool setProjectName = racoApplication_->activeProjectPath().empty(); - auto newPath = QFileDialog::getSaveFileName(this, "Save As...", QString::fromStdString(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project)), "Ramses Composer Assembly (*.rca)"); + auto newPath = QFileDialog::getSaveFileName(this, "Save As...", QString::fromStdString(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project).string()), "Ramses Composer Assembly (*.rca)"); if (newPath.isEmpty()) { return false; } @@ -609,7 +588,7 @@ bool MainWindow::saveAsActiveProject() { void MainWindow::importScene() { auto sceneFolder = raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh, racoApplication_->activeProjectFolder()); - auto filePath = QFileDialog::getOpenFileName(this, "Load Asset File", QString::fromStdString(sceneFolder), "glTF files (*.gltf *.glb)"); + auto filePath = QFileDialog::getOpenFileName(this, "Load Asset File", QString::fromStdString(sceneFolder.string()), "glTF files (*.gltf *.glb)"); if (!filePath.isEmpty()) { MeshDescriptor meshDesc; meshDesc.absPath = filePath.toStdString(); @@ -659,9 +638,13 @@ QString MainWindow::getActiveProjectFolder() { void MainWindow::restoreCachedLayout() { auto cachedLayoutInfo = dockManager_->getCachedLayoutInfo(); - regenerateLayoutDocks(cachedLayoutInfo); + if (cachedLayoutInfo.empty()) { + createInitialWidgets(this, *rendererBackend_, racoApplication_, dockManager_, treeDockManager_); + } else { + regenerateLayoutDocks(cachedLayoutInfo); - dockManager_->restoreCachedLayoutState(); + dockManager_->restoreCachedLayoutState(); + } } void MainWindow::restoreCustomLayout(const QString& layoutName) { diff --git a/EditorApp/mainwindow.h b/EditorApp/mainwindow.h index 2f850d31..73124f3f 100644 --- a/EditorApp/mainwindow.h +++ b/EditorApp/mainwindow.h @@ -65,13 +65,14 @@ public Q_SLOTS: protected: void timerEvent(QTimerEvent* event) override; void closeEvent(QCloseEvent* event) override; - bool restoreSettings(); + void restoreSettings(); /** @returns if user canceled the dirty resolution */ bool resolveDirtiness(); QString getActiveProjectFolder(); void restoreCachedLayout(); void restoreCustomLayout(const QString& layoutName); void regenerateLayoutDocks(const RaCoDockManager::LayoutDocks& docks); + void saveDockManagerCustomLayouts(); protected Q_SLOTS: void openProject(const QString& file = {}); diff --git a/EditorApp/mainwindow.ui b/EditorApp/mainwindow.ui index c070c81e..dd3bcf40 100644 --- a/EditorApp/mainwindow.ui +++ b/EditorApp/mainwindow.ui @@ -20,9 +20,12 @@ 0 0 400 - 26 + 21 + + Qt::NoContextMenu + File @@ -80,9 +83,8 @@ Debug - - + @@ -96,14 +98,6 @@ - - - TopToolBarArea - - - false - - @@ -195,19 +189,14 @@ Project Settings - - - Create dummy scene - - Dump object tree - + - Open log file + Open log directory diff --git a/HeadlessApp/CMakeLists.txt b/HeadlessApp/CMakeLists.txt index 99d1e063..b739493d 100644 --- a/HeadlessApp/CMakeLists.txt +++ b/HeadlessApp/CMakeLists.txt @@ -43,3 +43,4 @@ deploy_headless_shared_dlls(RaCoCommand) deploy_ramses_client_only_shared_dlls(RaCoCommand) deploy_qt(RaCoCommand) deploy_raco_cppruntime_dlls(RaCoCommand) +deploy_viewer(RaCoCommand) \ No newline at end of file diff --git a/HeadlessApp/main.cpp b/HeadlessApp/main.cpp index 93103b4c..dbb51bca 100644 --- a/HeadlessApp/main.cpp +++ b/HeadlessApp/main.cpp @@ -15,7 +15,7 @@ #include "ramses_adaptor/SceneBackend.h" #include "ramses_base/HeadlessEngineBackend.h" #include "utils/CrashDump.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include #include @@ -58,6 +58,31 @@ public Q_SLOTS: #include "main.moc" +spdlog::level::level_enum getLevelFromArg(const QString& arg) { + bool logLevelValid; + int logLevel = arg.toInt(&logLevelValid); + spdlog::level::level_enum spdLogLevel; + switch (logLevelValid ? logLevel : -1) { + case 0: + return spdlog::level::level_enum::off; + case 1: + return spdlog::level::level_enum::critical; + case 2: + return spdlog::level::level_enum::err; + case 3: + return spdlog::level::level_enum::warn; + case 4: + return spdlog::level::level_enum::info; + case 5: + return spdlog::level::level_enum::debug; + case 6: + return spdlog::level::level_enum::trace; + default: + LOG_ERROR(raco::log_system::COMMON, "Invalid Log Level: \"{}\". Continuing with verbose log output.", arg.toStdString().c_str()); + return spdlog::level::level_enum::trace; + } +} + int main(int argc, char* argv[]) { QCoreApplication::setApplicationName("Ramses Composer Headless"); QCoreApplication::setApplicationVersion(RACO_OSS_VERSION); @@ -85,10 +110,18 @@ int main(int argc, char* argv[]) { QStringList() << "d" << "nodump", "Don't generate crash dumps on unhandled exceptions."); + QCommandLineOption logLevelOption( + QStringList() << "l" + << "loglevel", + "Maximum information level that should be printed as console log output. Possible options: 0 (off), 1 (critical), 2 (error), 3 (warn), 4 (info), 5 (debug), 6 (trace).", + "log-level", + "6" + ); parser.addOption(loadProjectAction); parser.addOption(exportProjectAction); parser.addOption(compressExportAction); parser.addOption(noDumpFileCheckOption); + parser.addOption(logLevelOption); // application must be instantiated before parsing command line QCoreApplication a(argc, argv); @@ -101,16 +134,22 @@ int main(int argc, char* argv[]) { bool noDumpFiles = parser.isSet(noDumpFileCheckOption); raco::utils::crashdump::installCrashDumpHandler(noDumpFiles); - raco::core::PathManager::init(QCoreApplication::applicationDirPath().toStdString()); + auto appDataPath = raco::utils::u8path(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString()).parent_path() / "RamsesComposer"; + raco::core::PathManager::init(QCoreApplication::applicationDirPath().toStdString(), appDataPath); - raco::log_system::init(raco::core::PathManager::logFilePath().c_str()); + std::filesystem::create_directory(raco::core::PathManager::defaultConfigDirectory()); + std::filesystem::create_directory(raco::core::PathManager::logFileDirectory()); + raco::log_system::init(raco::core::PathManager::logFileHeadlessName().internalPath().native()); + auto logLevel = getLevelFromArg(parser.value(logLevelOption)); + raco::log_system::setConsoleLogLevel(logLevel); + raco::ramses_base::setRamsesAndLogicConsoleLogLevel(logLevel); QString projectFile{}; if (parser.isSet(loadProjectAction)) { QFileInfo path(parser.value(loadProjectAction)); if (path.suffix().compare(raco::names::PROJECT_FILE_EXTENSION, Qt::CaseInsensitive) == 0) { if (path.exists()) { - if (raco::utils::path::userHasReadAccess(path.filePath().toStdString())) { + if (raco::utils::u8path(path.filePath().toStdString()).userHasReadAccess()) { projectFile = path.absoluteFilePath(); } else { LOG_ERROR(raco::log_system::COMMON, "project file could not be read {}", path.filePath().toStdString()); diff --git a/README.md b/README.md index 6e96c9df..d32905ce 100644 --- a/README.md +++ b/README.md @@ -192,3 +192,5 @@ Some icons originate from the [Google Material Design](https://material.io/resou There are some example files included in ```resources/```. For Meshes taken from the Khronos glTF library, their individual licenses [are listed here](resources/meshes/README.md). All other meshes, Lua scripts, shaders and textures are also under MPL 2.0. +There are no obligations on the projects you create with Ramses Composer. They are your property and you may use and license them in any way you wish. This applies to the .rca scene file, everything referenced by it and all files exported from Ramses Composer. + diff --git a/components/libApplication/include/application/RaCoProject.h b/components/libApplication/include/application/RaCoProject.h index 817d9813..d7096be8 100644 --- a/components/libApplication/include/application/RaCoProject.h +++ b/components/libApplication/include/application/RaCoProject.h @@ -21,6 +21,7 @@ #include #include #include +#include "components/DataChangeDispatcher.h" namespace raco::application { @@ -46,8 +47,7 @@ class RaCoProject : public QObject { * @exception ExtrefError */ static std::unique_ptr loadFromFile(const QString& filename, RaCoApplication* app, std::vector& pathStack); - static std::unique_ptr loadFromJson(const QJsonDocument& migratedJson, const QString& filename, RaCoApplication* app, std::vector& pathStack); - + QString name() const; bool dirty() const noexcept; @@ -66,6 +66,9 @@ class RaCoProject : public QObject { QJsonDocument serializeProject(const std::unordered_map>& currentVersions); + void applyDefaultCachedPaths(); + void subscribeDefaultCachedPathChanges(const raco::components::SDataChangeDispatcher& dataChangeDispatcher); + Q_SIGNALS: void activeProjectFileChanged(); @@ -82,6 +85,11 @@ class RaCoProject : public QObject { raco::core::Errors errors_; raco::core::Project project_; + raco::components::Subscription imageSubdirectoryUpdateSubscription_; + raco::components::Subscription meshSubdirectoryUpdateSubscription_; + raco::components::Subscription scriptSubdirectoryUpdateSubscription_; + raco::components::Subscription shaderSubdirectoryUpdateSubscription_; + std::shared_ptr context_; bool dirty_{false}; diff --git a/components/libApplication/src/ExternalProjectsStore.cpp b/components/libApplication/src/ExternalProjectsStore.cpp index 2370cb70..15a9aac2 100644 --- a/components/libApplication/src/ExternalProjectsStore.cpp +++ b/components/libApplication/src/ExternalProjectsStore.cpp @@ -11,7 +11,7 @@ #include "application/RaCoApplication.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" namespace raco::application { @@ -137,7 +137,7 @@ std::string ExternalProjectsStore::activeProjectPath() const { bool ExternalProjectsStore::loadExternalProject(const std::string& projectPath, std::vector& pathStack) { std::unique_ptr project; bool success = false; - if (utils::path::isExistingFile(projectPath)) { + if (utils::u8path(projectPath).existsFile()) { if (projectPath != activeProjectPath()) { try { project = RaCoProject::loadFromFile(QString::fromStdString(projectPath), application_, pathStack); diff --git a/components/libApplication/src/RaCoApplication.cpp b/components/libApplication/src/RaCoApplication.cpp index 23c5fdc2..df6db622 100644 --- a/components/libApplication/src/RaCoApplication.cpp +++ b/components/libApplication/src/RaCoApplication.cpp @@ -11,7 +11,7 @@ #include "components/RaCoPreferences.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "core/PathManager.h" #include "core/Project.h" @@ -22,6 +22,7 @@ #include "user_types/Animation.h" #include +#include "core/Handles.h" #ifdef OS_WINDOWS // see: https://doc.qt.io/qt-5/qfileinfo.html#ntfs-permissions @@ -46,12 +47,8 @@ RaCoApplication::RaCoApplication(ramses_base::BaseEngineBackend& engine, const Q logicEngineNeedsUpdate_ = true; scenesBackend_->setScene(activeRaCoProject().project(), activeRaCoProject().errors()); - const auto& prefs = raco::components::RaCoPreferences::instance(); - raco::core::PathManager::setAllCachedPathRoots(activeProjectFolder(), - prefs.imageSubdirectory.toStdString(), - prefs.meshSubdirectory.toStdString(), - prefs.scriptSubdirectory.toStdString(), - prefs.shaderSubdirectory.toStdString()); + activeProject_->applyDefaultCachedPaths(); + activeProject_->subscribeDefaultCachedPathChanges(dataChangeDispatcher_); startTime_ = std::chrono::high_resolution_clock::now(); } @@ -91,18 +88,15 @@ void RaCoApplication::switchActiveRaCoProject(const QString& file) { std::vector stack; activeProject_ = file.isEmpty() ? RaCoProject::createNew(this) : RaCoProject::loadFromFile(file, this, stack); + externalProjectsStore_.setActiveProject(activeProject_.get()); logicEngineNeedsUpdate_ = true; - scenesBackend_->setScene(activeRaCoProject().project(), activeRaCoProject().errors()); + activeProject_->applyDefaultCachedPaths(); + activeProject_->subscribeDefaultCachedPathChanges(dataChangeDispatcher_); - const auto& prefs = raco::components::RaCoPreferences::instance(); - raco::core::PathManager::setAllCachedPathRoots(activeProjectFolder(), - prefs.imageSubdirectory.toStdString(), - prefs.meshSubdirectory.toStdString(), - prefs.scriptSubdirectory.toStdString(), - prefs.shaderSubdirectory.toStdString()); + scenesBackend_->setScene(activeRaCoProject().project(), activeRaCoProject().errors()); } bool RaCoApplication::exportProject(const RaCoProject& project, const std::string& ramsesExport, const std::string& logicExport, bool compress, std::string& outError) const { diff --git a/components/libApplication/src/RaCoProject.cpp b/components/libApplication/src/RaCoProject.cpp index cf5ea93c..a354305d 100644 --- a/components/libApplication/src/RaCoProject.cpp +++ b/components/libApplication/src/RaCoProject.cpp @@ -16,13 +16,14 @@ #include "core/Iterators.h" #include "core/PathManager.h" #include "core/Project.h" +#include "core/ProxyObjectFactory.h" #include "core/Queries.h" #include "core/Undo.h" #include "ramses_base/BaseEngineBackend.h" #include "components/Naming.h" #include "application/RaCoApplication.h" #include "components/RaCoPreferences.h" -#include "core/RamsesProjectMigration.h" +#include "core/ProjectMigration.h" #include "core/Serialization.h" #include "core/SerializationKeys.h" #include "user_types/MeshNode.h" @@ -35,17 +36,19 @@ #include "user_types/RenderTarget.h" #include "user_types/RenderPass.h" #include "utils/FileUtils.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "core/CoreFormatter.h" +#include "utils/stdfilesystem.h" +#include "core/PrefabOperations.h" +#include "user_types/PrefabInstance.h" #include #include #include -#include "utils/stdfilesystem.h" + +#include #include -#include "core/PrefabOperations.h" -#include "user_types/PrefabInstance.h" namespace raco::application { @@ -64,7 +67,7 @@ RaCoProject::RaCoProject(const QString& file, Project& p, EngineInterface* engin meshCache_{app->meshCache()} { context_->setMeshCache(meshCache_); context_->setExternalProjectsStore(externalProjectsStore); - + // Abort file loading if we encounter external reference RenderPasses or extref cameras outside a Prefab. // A bug in V0.9.0 allowed to create such projects. // TODO: remove this eventually when we are reasonably certain that no such projects have been encountered. @@ -75,7 +78,7 @@ RaCoProject::RaCoProject(const QString& file, Project& p, EngineInterface* engin } if ((&object->getTypeDescription() == &user_types::PerspectiveCamera::typeDescription || - &object->getTypeDescription() == &user_types::OrthographicCamera::typeDescription) && + &object->getTypeDescription() == &user_types::OrthographicCamera::typeDescription) && object->query()) { if (!PrefabOperations::findContainingPrefab(object)) { throw std::runtime_error("file contains external reference camera outside a prefab"); @@ -90,45 +93,13 @@ RaCoProject::RaCoProject(const QString& file, Project& p, EngineInterface* engin context_->initLinkValidity(); } - // TODO: the following code repairs URIs which have been "rerooted" incorrectly during paste. - // This code should really be migration code but rewriting the code below to use only the JSON - // is much more complicated than the code below. - // TODO: deal with this once we know how to do migration code in an easier way. - for (auto& object : context_->project()->instances()) { - if (auto prefabInst = PrefabOperations::findContainingPrefabInstance(object)) { - if (auto prefab = *prefabInst->template_) { - if (prefab->query()) { - for (const auto& property : ValueTreeIteratorAdaptor(ValueHandle{object})) { - if (property.query()) { - // The PrefabInstance::mapToInstance_ property is only filled for the outermost PrefabInstance if - // there are nested instances. So we need to search the topmost PrefabInstance first: - while (auto parentPrefabInst = PrefabOperations::findContainingPrefabInstance(prefabInst->getParent())) { - prefabInst = parentPrefabInst; - } - auto prefabObj = user_types::PrefabInstance::mapFromInstance(object, prefabInst); - auto prefabProperty = ValueHandle::translatedHandle(property, prefabObj); - - if (property.asString() != prefabProperty.asString()) { - LOG_WARNING(raco::log_system::PROJECT, "Rewrite URI property '{}': '{}' -> '{}' in project '{}'", - property.getPropertyPath(), - property.asString(), prefabProperty.asString(), - project_.currentPath()); - context_->set(property, prefabProperty.asString()); - } - } - } - } - } - } - } - - context_->performExternalFileReload(project_.instances()); - // Push currently loading project on the project load stack to enable project loop detection to work. pathStack.emplace_back(file.toStdString()); context_->updateExternalReferences(pathStack); pathStack.pop_back(); + context_->performExternalFileReload(project_.instances()); + undoStack_.reset(); context_->changeMultiplexer().reset(); @@ -142,10 +113,10 @@ void RaCoProject::onAfterProjectPathChange(const std::string& oldPath, const std for (auto& object : context_->project()->instances()) { if (PathQueries::isPathRelativeToCurrentProject(object)) { for (const auto& property : ValueTreeIteratorAdaptor(ValueHandle{object})) { - if (property.query()) { + if (auto anno = property.query(); anno && !anno->isProjectSubdirectoryURI()) { auto uriPath = property.asString(); - if (!uriPath.empty() && std::filesystem::path{uriPath}.is_relative()) { - context_->set(property, PathManager::rerootRelativePath(uriPath, oldPath, newPath)); + if (!uriPath.empty() && raco::utils::u8path(uriPath).is_relative()) { + context_->set(property, raco::utils::u8path(uriPath).rerootRelativePath(oldPath, newPath).string()); } } } @@ -156,18 +127,61 @@ void RaCoProject::onAfterProjectPathChange(const std::string& oldPath, const std } void RaCoProject::generateProjectSubfolder(const std::string& subFolderPath) { - auto folderPath = project_.currentFolder() + "/" + subFolderPath; - if (!raco::utils::path::isExistingDirectory(folderPath)) { - std::filesystem::create_directories(folderPath); + auto path = raco::utils::u8path(subFolderPath).normalizedAbsolutePath(project_.currentFolder()); + if (!path.existsDirectory()) { + std::filesystem::create_directories(path); } } void RaCoProject::generateAllProjectSubfolders() { - const auto& prefs = raco::components::RaCoPreferences::instance(); - generateProjectSubfolder(prefs.imageSubdirectory.toStdString()); - generateProjectSubfolder(prefs.meshSubdirectory.toStdString()); - generateProjectSubfolder(prefs.scriptSubdirectory.toStdString()); - generateProjectSubfolder(prefs.shaderSubdirectory.toStdString()); + const auto& settings = project_.settings(); + generateProjectSubfolder(settings->defaultResourceDirectories_->imageSubdirectory_.asString()); + generateProjectSubfolder(settings->defaultResourceDirectories_->meshSubdirectory_.asString()); + generateProjectSubfolder(settings->defaultResourceDirectories_->scriptSubdirectory_.asString()); + generateProjectSubfolder(settings->defaultResourceDirectories_->shaderSubdirectory_.asString()); + + applyDefaultCachedPaths(); +} + +void RaCoProject::applyDefaultCachedPaths() { + auto settings = project_.settings(); + auto defaultFolders = settings->defaultResourceDirectories_; + + auto projectFolder = project_.currentFolder(); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Project, projectFolder); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Image, raco::utils::u8path(defaultFolders->imageSubdirectory_.asString()).normalizedAbsolutePath(projectFolder)); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Mesh, raco::utils::u8path(defaultFolders->meshSubdirectory_.asString()).normalizedAbsolutePath(projectFolder)); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Script, raco::utils::u8path(defaultFolders->scriptSubdirectory_.asString()).normalizedAbsolutePath(projectFolder)); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Shader, raco::utils::u8path(defaultFolders->shaderSubdirectory_.asString()).normalizedAbsolutePath(projectFolder)); +} + +void RaCoProject::subscribeDefaultCachedPathChanges(const raco::components::SDataChangeDispatcher& dataChangeDispatcher) { + auto project = this->project(); + auto settings = project_.settings(); + + imageSubdirectoryUpdateSubscription_ = dataChangeDispatcher->registerOn({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::imageSubdirectory_}, + [project, settings]() { + auto path = raco::utils::u8path(settings->defaultResourceDirectories_->imageSubdirectory_.asString()).normalizedAbsolutePath(project->currentFolder()); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Image, path); + }); + + meshSubdirectoryUpdateSubscription_ = dataChangeDispatcher->registerOn({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::meshSubdirectory_}, + [project, settings]() { + auto path = raco::utils::u8path(settings->defaultResourceDirectories_->meshSubdirectory_.asString()).normalizedAbsolutePath(project->currentFolder()); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Mesh, path); + }); + + scriptSubdirectoryUpdateSubscription_ = dataChangeDispatcher->registerOn({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::scriptSubdirectory_}, + [project, settings]() { + auto path = raco::utils::u8path(settings->defaultResourceDirectories_->scriptSubdirectory_.asString()).normalizedAbsolutePath(project->currentFolder()); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Script, path); + }); + + shaderSubdirectoryUpdateSubscription_ = dataChangeDispatcher->registerOn({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::shaderSubdirectory_}, + [project, settings]() { + auto path = raco::utils::u8path(settings->defaultResourceDirectories_->shaderSubdirectory_.asString()).normalizedAbsolutePath(project->currentFolder()); + PathManager::setCachedPath(PathManager::FolderTypeKeys::Shader, path); + }); } void RaCoProject::updateActiveFileListener() { @@ -203,7 +217,12 @@ std::unique_ptr RaCoProject::createNew(RaCoApplication* app) { auto sRenderPass = result->context_->createObject(raco::user_types::RenderPass::typeDescription.typeName, "MainRenderPass")->as(); auto sRenderLayer = result->context_->createObject(raco::user_types::RenderLayer::typeDescription.typeName, "MainRenderLayer")->as(); + const auto& prefs = raco::components::RaCoPreferences::instance(); auto settings = result->context_->createObject(ProjectSettings::typeDescription.typeName); + result->context_->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::imageSubdirectory_}, prefs.imageSubdirectory.toStdString()); + result->context_->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::meshSubdirectory_}, prefs.meshSubdirectory.toStdString()); + result->context_->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::scriptSubdirectory_}, prefs.scriptSubdirectory.toStdString()); + result->context_->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::shaderSubdirectory_}, prefs.shaderSubdirectory.toStdString()); result->context_->set({sRenderPass, &user_types::RenderPass::camera_}, sCamera); result->context_->set({sRenderPass, &user_types::RenderPass::layer0_}, sRenderLayer); @@ -224,7 +243,7 @@ std::unique_ptr RaCoProject::createNew(RaCoApplication* app) { std::unique_ptr RaCoProject::loadFromFile(const QString& filename, RaCoApplication* app, std::vector& pathStack) { LOG_INFO(raco::log_system::PROJECT, "Loading project from {}", filename.toLatin1()); - if (!raco::utils::path::isExistingFile(filename.toStdString())) { + if (!raco::utils::u8path(filename.toStdString()).existsFile()) { LOG_WARNING(raco::log_system::PROJECT, "File not found {}", filename.toLatin1()); return {}; } @@ -241,70 +260,46 @@ std::unique_ptr RaCoProject::loadFromFile(const QString& filename, file.close(); auto fileVersion{raco::serialization::deserializeFileVersion(document)}; - if (fileVersion > raco::core::RAMSES_PROJECT_FILE_VERSION) { + if (fileVersion > raco::serialization::RAMSES_PROJECT_FILE_VERSION) { throw FutureFileVersion{fileVersion}; } - std::unordered_map migrationObjWarnings; - auto migratedJson{migrateProject(document, migrationObjWarnings)}; - - auto newProject = loadFromJson(migratedJson, filename, app, pathStack); - - for (const auto& [objectID, infoMessage] : migrationObjWarnings) { - if (const auto migratedObj = newProject->project()->getInstanceByID(objectID)) { - newProject->errors()->addError(raco::core::ErrorCategory::MIGRATION_ERROR, ErrorLevel::WARNING, migratedObj, infoMessage); - } - } - - newProject->errors()->logAllErrors(); - - return newProject; -} - -std::unique_ptr RaCoProject::loadFromJson(const QJsonDocument& migratedJson, const QString& filename, RaCoApplication* app, std::vector& pathStack) { - auto result{ raco::serialization::deserializeProject(migratedJson, - user_types::UserObjectFactoryInterface::deserializationFactory(&user_types::UserObjectFactory::getInstance())) }; - - std::vector instances{}; - std::map instanceMap; - instances.reserve(result.objectsDeserialization.objects.size()); - for (auto& d : result.objectsDeserialization.objects) { - auto obj = std::dynamic_pointer_cast(d); - instances.push_back(obj); - instanceMap[obj->objectID()] = obj; - } - for (const auto& pair : result.objectsDeserialization.references) { - if (instanceMap.find(pair.second) != instanceMap.end()) { - *pair.first = instanceMap.at(pair.second); - } else { - LOG_WARNING(raco::log_system::PROJECT, "Load: referenced object not found: {}", pair.second); - } - } + auto result{raco::serialization::deserializeProject(document, filename.toStdString())}; - Project p{ instances }; + Project p{ result.objects }; p.setCurrentPath(filename.toStdString()); - for (const auto& instance : instances) { + for (const auto& instance : result.objects) { instance->onAfterDeserialization(); } - for (const auto& link : result.objectsDeserialization.links) { - p.addLink(std::dynamic_pointer_cast(link)); + for (const auto& link : result.links) { + p.addLink(link); } - for (auto [id, info] : result.objectsDeserialization.externalProjectsMap) { - auto absPath = PathManager::constructAbsolutePath(p.currentFolder(), info.path); - p.addExternalProjectMapping(id, absPath, info.name); + for (auto [id, info] : result.externalProjectsMap) { + auto absPath = raco::utils::u8path(info.path).normalizedAbsolutePath(p.currentFolder()); + p.addExternalProjectMapping(id, absPath.string(), info.name); } LOG_INFO(raco::log_system::PROJECT, "Finished loading project from {}", filename.toLatin1()); Consistency::checkProjectSettings(p); - return std::unique_ptr(new RaCoProject{ + auto newProject = new RaCoProject{ filename, p, app->engine(), [app]() { app->dataChangeDispatcher()->setUndoChanged(); }, app->externalProjects(), app, - pathStack }); + pathStack}; + + for (const auto& [objectID, infoMessage] : result.migrationObjWarnings) { + if (const auto migratedObj = newProject->project()->getInstanceByID(objectID)) { + newProject->errors()->addError(raco::core::ErrorCategory::MIGRATION_ERROR, ErrorLevel::WARNING, migratedObj, infoMessage); + } + } + + newProject->errors()->logAllErrors(); + + return std::unique_ptr(newProject); } QString RaCoProject::name() const { @@ -320,14 +315,7 @@ QJsonDocument RaCoProject::serializeProject(const std::unordered_map std::optional { - if (value.asRef()) { - return value.asRef()->objectID(); - } else { - return {}; - } - }); + project_.externalProjectsMap()); } bool RaCoProject::save() { @@ -343,7 +331,7 @@ bool RaCoProject::save() { auto ramsesLogicEngineVersion = raco::ramses_base::getLogicEngineVersion(); std::unordered_map> currentVersions = { - {raco::serialization::keys::FILE_VERSION, {raco::core::RAMSES_PROJECT_FILE_VERSION}}, + {raco::serialization::keys::FILE_VERSION, {raco::serialization::RAMSES_PROJECT_FILE_VERSION}}, {raco::serialization::keys::RAMSES_VERSION, {ramsesVersion.major, ramsesVersion.minor, ramsesVersion.patch}}, {raco::serialization::keys::RAMSES_LOGIC_ENGINE_VERSION, {static_cast(ramsesLogicEngineVersion.major), static_cast(ramsesLogicEngineVersion.minor), static_cast(ramsesLogicEngineVersion.patch)}}, {raco::serialization::keys::RAMSES_COMPOSER_VERSION, {RACO_VERSION_MAJOR, RACO_VERSION_MINOR, RACO_VERSION_PATCH}}}; @@ -354,14 +342,8 @@ bool RaCoProject::save() { return false; } file.close(); - generateAllProjectSubfolders(); - const auto& prefs = raco::components::RaCoPreferences::instance(); - PathManager::setAllCachedPathRoots(project_.currentFolder(), - prefs.imageSubdirectory.toStdString(), - prefs.meshSubdirectory.toStdString(), - prefs.scriptSubdirectory.toStdString(), - prefs.shaderSubdirectory.toStdString()); + generateAllProjectSubfolders(); dirty_ = false; LOG_INFO(raco::log_system::PROJECT, "Finished saving project to {}", path); @@ -378,7 +360,7 @@ bool RaCoProject::saveAs(const QString& fileName, bool setProjectName) { project_.setCurrentPath(newPath); onAfterProjectPathChange(oldProjectFolder, project_.currentFolder()); if (setProjectName) { - auto projName = std::filesystem::path(newPath).stem().string(); + auto projName = raco::utils::u8path(newPath).stem().string(); auto settings = project_.settings(); if (settings->objectName().empty()) { context_->set({settings, &raco::core::EditorObject::objectName_}, projName); diff --git a/components/libApplication/tests/RaCoApplication_test.cpp b/components/libApplication/tests/RaCoApplication_test.cpp index 8edc30eb..c2400249 100644 --- a/components/libApplication/tests/RaCoApplication_test.cpp +++ b/components/libApplication/tests/RaCoApplication_test.cpp @@ -36,8 +36,8 @@ TEST_F(RaCoApplicationFixture, exportNewProject) { std::string error; auto success = application.exportProject( application.activeRaCoProject(), - (cwd_path() / "new.ramses").string().c_str(), - (cwd_path() / "new.logic").string().c_str(), + (test_path() / "new.ramses").string().c_str(), + (test_path() / "new.logic").string().c_str(), false, error); ASSERT_TRUE(success); @@ -47,11 +47,11 @@ TEST_F(RaCoApplicationFixture, exportDuckProject) { auto* commandInterface = application.activeRaCoProject().commandInterface(); auto mesh = commandInterface->createObject(raco::user_types::Mesh::typeDescription.typeName, Naming::format("MeshDuck")); - commandInterface->set({mesh, {"uri"}}, std::string{(cwd_path() / "meshes" / "Duck.glb").string()}); + commandInterface->set({mesh, {"uri"}}, std::string{(test_path() / "meshes" / "Duck.glb").string()}); auto material = commandInterface->createObject(raco::user_types::Material::typeDescription.typeName, Naming::format("MaterialDuck")); - commandInterface->set({material, {"uriVertex"}}, (cwd_path() / "shaders" / "basic.vert").string()); - commandInterface->set({material, {"uriFragment"}}, (cwd_path() / "shaders" / "basic.frag").string()); + commandInterface->set({material, {"uriVertex"}}, (test_path() / "shaders" / "basic.vert").string()); + commandInterface->set({material, {"uriFragment"}}, (test_path() / "shaders" / "basic.frag").string()); auto node = commandInterface->createObject(raco::user_types::Node::typeDescription.typeName, Naming::format("NodeDuck")); auto meshNode = commandInterface->createObject(raco::user_types::MeshNode::typeDescription.typeName, Naming::format("MeshNodeDuck")); @@ -73,8 +73,8 @@ TEST_F(RaCoApplicationFixture, exportDuckProject) { std::string error; auto success = application.exportProject( application.activeRaCoProject(), - (cwd_path() / "new.ramses").string().c_str(), - (cwd_path() / "new.logic").string().c_str(), + (test_path() / "new.ramses").string().c_str(), + (test_path() / "new.logic").string().c_str(), true, error); ASSERT_TRUE(success); @@ -85,7 +85,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphCorrectNodeAmount) { commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -123,7 +123,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphVectorsGetLinked) { commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/InterpolationTest/InterpolationTest.gltf").string(); + desc.absPath = test_path().append("meshes/InterpolationTest/InterpolationTest.gltf").string(); desc.bakeAllSubmeshes = false; @@ -138,7 +138,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphMeshWithNegativeScaleWillBeIm commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/negativeScaleQuad.gltf").string(); + desc.absPath = test_path().append("meshes/negativeScaleQuad.gltf").string(); desc.bakeAllSubmeshes = false; @@ -155,10 +155,10 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphMeshWithNegativeScaleWillBeIm TEST_F(RaCoApplicationFixture, importglTFScenegraphCachedMeshPathGetsChanged) { auto* commandInterface = application.activeRaCoProject().commandInterface(); commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); - raco::core::PathManager::setCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh, ""); + raco::core::PathManager::setCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh, {}); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -166,7 +166,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphCachedMeshPathGetsChanged) { commandInterface->insertAssetScenegraph(*scenegraph, desc.absPath, nullptr); application.dataChangeDispatcher()->dispatch(*application.activeRaCoProject().recorder()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), (cwd_path() / "meshes/CesiumMilkTruck").generic_string()); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), test_path() / "meshes/CesiumMilkTruck"); } TEST_F(RaCoApplicationFixture, importglTFScenegraphCorrectScenegraphStructureTruck) { @@ -174,7 +174,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphCorrectScenegraphStructureTru commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -221,7 +221,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphCorrectRootNodeInsertion) { auto myRoot = commandInterface->createObject(raco::user_types::Node::typeDescription.typeName, "myRoot"); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -239,7 +239,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphCorrectRootNodeRenaming) { commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/ToyCar/ToyCar.gltf").string(); + desc.absPath = test_path().append("meshes/ToyCar/ToyCar.gltf").string(); desc.bakeAllSubmeshes = false; @@ -257,7 +257,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphImportSceneGraphTwice) { auto secondRoot = commandInterface->createObject(raco::user_types::Node::typeDescription.typeName, "myRoot"); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -276,7 +276,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphImportSceneGraphTwiceButMeshe auto secondRoot = commandInterface->createObject(raco::user_types::Node::typeDescription.typeName, "myRoot"); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -307,7 +307,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphImportSceneGraphTwiceButAnima auto secondRoot = commandInterface->createObject(raco::user_types::Node::typeDescription.typeName, "myRoot"); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -330,7 +330,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphUnbakedMeshesGetTransformed) commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -371,7 +371,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphCorrectAutomaticMaterialAssig auto wheels = commandInterface->createObject(raco::user_types::Material::typeDescription.typeName, "wheels"); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -401,7 +401,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphUnmarkedNodesDoNotGetImported commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -441,7 +441,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphImportedAnimationDoesNotGetPa commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/RiggedFigure/RiggedFigure.gltf").string(); + desc.absPath = test_path().append("meshes/RiggedFigure/RiggedFigure.gltf").string(); desc.bakeAllSubmeshes = false; @@ -461,7 +461,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphDeselectedAnimationsDoNotGetI commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -482,7 +482,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphDeselectedNodesWillNotCreateL commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -502,7 +502,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphDeselectedAnimationChannelsDo commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; @@ -534,7 +534,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphWrongFileReturnsEmptyScenegra commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("nonexistentFile.gltf").string(); + desc.absPath = test_path().append("nonexistentFile.gltf").string(); desc.bakeAllSubmeshes = false; auto scenegraph = commandInterface->meshCache()->getMeshScenegraph(desc); @@ -546,7 +546,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphWrongFileReturnsEmptyAnimSamp auto* commandInterface = application.activeRaCoProject().commandInterface(); commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); - auto animSampler = commandInterface->meshCache()->getAnimationSamplerData(cwd_path().append("nonexistentFile.gltf").string(), 0, 0); + auto animSampler = commandInterface->meshCache()->getAnimationSamplerData(test_path().append("nonexistentFile.gltf").string(), 0, 0); ASSERT_EQ(animSampler, nullptr); } @@ -556,7 +556,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphWithNoMeshes) { commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/meshless.gltf").string(); + desc.absPath = test_path().append("meshes/meshless.gltf").string(); desc.bakeAllSubmeshes = false; auto scenegraph = commandInterface->meshCache()->getMeshScenegraph(desc); @@ -599,7 +599,7 @@ TEST_F(RaCoApplicationFixture, importglTFScenegraphMeshNodesDontReferenceDeselec commandInterface->deleteObjects(application.activeRaCoProject().project()->instances()); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; auto scenegraph = commandInterface->meshCache()->getMeshScenegraph(desc); @@ -636,8 +636,8 @@ TEST_F(RaCoApplicationFixture, LuaScriptRuntimeErrorCausesInformationForAllScrip auto const workingScript{ commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName) }; auto const runtimeErrorScript{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; - commandInterface->set(raco::core::ValueHandle{ runtimeErrorScript, {"uri"}}, cwd_path().append("scripts/runtime-error.lua").string()); - commandInterface->set(raco::core::ValueHandle{ workingScript, {"uri"} }, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface->set(raco::core::ValueHandle{ runtimeErrorScript, {"uri"}}, test_path().append("scripts/runtime-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{ workingScript, {"uri"} }, test_path().append("scripts/SimpleScript.lua").string()); EXPECT_FALSE(application.activeRaCoProject().errors()->hasError(workingScript)); EXPECT_FALSE(application.activeRaCoProject().errors()->hasError(runtimeErrorScript)); @@ -658,7 +658,7 @@ TEST_F(RaCoApplicationFixture, LuaScriptFixingRuntimeErrorRemovesLogicError) { auto emptyScript{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; auto runtimeErrorScript{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; - commandInterface->set(raco::core::ValueHandle{runtimeErrorScript, {"uri"}}, cwd_path().append("scripts/runtime-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{runtimeErrorScript, {"uri"}}, test_path().append("scripts/runtime-error.lua").string()); commandInterface->set(raco::core::ValueHandle{runtimeErrorScript, {"luaInputs"}}.get("choice"), 1); application.doOneLoop(); @@ -676,10 +676,10 @@ TEST_F(RaCoApplicationFixture, LuaScriptFixingRuntimeErrorDoesNotDeleteOtherErro auto compileErrorScript{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; auto runtimeErrorScript1{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; auto runtimeErrorScript2{ commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName) }; - commandInterface->set(raco::core::ValueHandle{ compileErrorScript, {"uri"} }, cwd_path().append("scripts/compile-error.lua").string()); - commandInterface->set(raco::core::ValueHandle{runtimeErrorScript1, {"uri"}}, cwd_path().append("scripts/runtime-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{ compileErrorScript, {"uri"} }, test_path().append("scripts/compile-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{runtimeErrorScript1, {"uri"}}, test_path().append("scripts/runtime-error.lua").string()); commandInterface->set(raco::core::ValueHandle{runtimeErrorScript1, {"luaInputs"}}.get("choice"), 1); - commandInterface->set(raco::core::ValueHandle{ runtimeErrorScript2, {"uri"} }, cwd_path().append("scripts/runtime-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{ runtimeErrorScript2, {"uri"} }, test_path().append("scripts/runtime-error.lua").string()); commandInterface->set(raco::core::ValueHandle{ runtimeErrorScript2, {"luaInputs"} }.get("choice"), 0); application.doOneLoop(); @@ -704,8 +704,8 @@ TEST_F(RaCoApplicationFixture, LuaScriptNewestRuntimeErrorGetsProperlyUpdated) { auto runtimeErrorScript1{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; auto runtimeErrorScript2{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; - commandInterface->set(raco::core::ValueHandle{runtimeErrorScript1, {"uri"}}, cwd_path().append("scripts/runtime-error.lua").string()); - commandInterface->set(raco::core::ValueHandle{runtimeErrorScript2, {"uri"}}, cwd_path().append("scripts/runtime-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{runtimeErrorScript1, {"uri"}}, test_path().append("scripts/runtime-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{runtimeErrorScript2, {"uri"}}, test_path().append("scripts/runtime-error.lua").string()); commandInterface->set(raco::core::ValueHandle{runtimeErrorScript1, {"luaInputs"}}.get("choice"), 1); commandInterface->set(raco::core::ValueHandle{runtimeErrorScript2, {"luaInputs"}}.get("choice"), 0); @@ -735,7 +735,7 @@ TEST_F(RaCoApplicationFixture, LuaScriptCompileErrorDoesNotCauseErrorForAllScrip auto emptyScript{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; auto compileErrorScript{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; - commandInterface->set(raco::core::ValueHandle{compileErrorScript, {"uri"}}, cwd_path().append("scripts/compile-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{compileErrorScript, {"uri"}}, test_path().append("scripts/compile-error.lua").string()); application.doOneLoop(); @@ -750,7 +750,7 @@ TEST_F(RaCoApplicationFixture, LuaScriptDeletingScriptWithRunTimeErrorUpdatesAll auto runtimeErrorScript{commandInterface->createObject(raco::user_types::LuaScript::typeDescription.typeName)}; auto node{commandInterface->createObject(raco::user_types::Node::typeDescription.typeName)}; auto mesh{commandInterface->createObject(raco::user_types::Mesh::typeDescription.typeName)}; - commandInterface->set(raco::core::ValueHandle{runtimeErrorScript, {"uri"}}, cwd_path().append("scripts/runtime-error.lua").string()); + commandInterface->set(raco::core::ValueHandle{runtimeErrorScript, {"uri"}}, test_path().append("scripts/runtime-error.lua").string()); commandInterface->set(raco::core::ValueHandle{runtimeErrorScript, {"luaInputs"}}.get("choice"), 1); application.doOneLoop(); diff --git a/components/libApplication/tests/RaCoProject_test.cpp b/components/libApplication/tests/RaCoProject_test.cpp index e1e84f0a..cd6c69eb 100644 --- a/components/libApplication/tests/RaCoProject_test.cpp +++ b/components/libApplication/tests/RaCoProject_test.cpp @@ -1,4 +1,4 @@ -/* +/* * SPDX-License-Identifier: MPL-2.0 * * This file is part of Ramses Composer @@ -22,7 +22,7 @@ #include "user_types/Mesh.h" #include "user_types/MeshNode.h" #include "user_types/Node.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" class RaCoProjectFixture : public RacoBaseTest<> { public: @@ -34,11 +34,11 @@ using raco::application::RaCoApplication; TEST_F(RaCoProjectFixture, saveLoadWithLink) { { RaCoApplication app{backend}; - raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(1, app.activeRaCoProject().project()->links().size()); } } @@ -47,16 +47,16 @@ TEST_F(RaCoProjectFixture, saveLoadWithLink) { TEST_F(RaCoProjectFixture, saveLoadWithRunningAnimation) { { RaCoApplication app{backend}; - auto [anim, animChannel, node, link] = raco::createAnimatedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); + auto [anim, animChannel, node, link] = raco::createAnimatedScene(*app.activeRaCoProject().commandInterface(), test_path()); app.activeRaCoProject().commandInterface()->set({anim, {"play"}}, true); app.activeRaCoProject().commandInterface()->set({anim, {"loop"}}, true); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); app.doOneLoop(); app.doOneLoop(); app.doOneLoop(); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(1, app.activeRaCoProject().project()->links().size()); ASSERT_TRUE(app.activeRaCoProject().project()->links().front()->isValid()); } @@ -65,17 +65,17 @@ TEST_F(RaCoProjectFixture, saveLoadWithRunningAnimation) { TEST_F(RaCoProjectFixture, saveLoadWithPausedAnimation) { { RaCoApplication app{backend}; - auto [anim, animChannel, node, link] = raco::createAnimatedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); + auto [anim, animChannel, node, link] = raco::createAnimatedScene(*app.activeRaCoProject().commandInterface(), test_path()); app.activeRaCoProject().commandInterface()->set({anim, {"play"}}, true); app.activeRaCoProject().commandInterface()->set({anim, {"loop"}}, true); app.doOneLoop(); app.doOneLoop(); app.activeRaCoProject().commandInterface()->set({anim, {"play"}}, false); app.doOneLoop(); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(1, app.activeRaCoProject().project()->links().size()); ASSERT_TRUE(app.activeRaCoProject().project()->links().front()->isValid()); } @@ -84,17 +84,17 @@ TEST_F(RaCoProjectFixture, saveLoadWithPausedAnimation) { TEST_F(RaCoProjectFixture, saveLoadWithStoppedAnimation) { { RaCoApplication app{backend}; - auto [anim, animChannel, node, link] = raco::createAnimatedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); + auto [anim, animChannel, node, link] = raco::createAnimatedScene(*app.activeRaCoProject().commandInterface(), test_path()); app.activeRaCoProject().commandInterface()->set({anim, {"play"}}, true); app.activeRaCoProject().commandInterface()->set({anim, {"loop"}}, true); app.activeRaCoProject().commandInterface()->set({anim, {"rewindOnStop"}}, true); app.doOneLoop(); app.activeRaCoProject().commandInterface()->set({anim, {"play"}}, false); app.doOneLoop(); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(1, app.activeRaCoProject().project()->links().size()); ASSERT_TRUE(app.activeRaCoProject().project()->links().front()->isValid()); } @@ -103,18 +103,18 @@ TEST_F(RaCoProjectFixture, saveLoadWithStoppedAnimation) { TEST_F(RaCoProjectFixture, saveLoadWithBrokenLink) { { RaCoApplication app{backend}; - auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.newTranslation = VEC3F end function run() end )"); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(1, app.activeRaCoProject().project()->links().size()); ASSERT_FALSE(app.activeRaCoProject().project()->links()[0]->isValid()); auto node = raco::core::Queries::findByName(app.activeRaCoProject().project()->instances(), "node"); @@ -127,7 +127,7 @@ TEST_F(RaCoProjectFixture, saveLoadWithBrokenAndValidLink) { RaCoApplication app{backend}; const auto luaScript{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; const auto node{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.translation = VEC3F OUT.rotation = VEC3F @@ -135,7 +135,7 @@ end function run() end )"); - app.activeRaCoProject().commandInterface()->set({luaScript, {"uri"}}, (cwd_path() / "lua_script.lua").string()); + app.activeRaCoProject().commandInterface()->set({luaScript, {"uri"}}, (test_path() / "lua_script.lua").string()); app.activeRaCoProject().commandInterface()->addLink({luaScript, {"luaOutputs", "translation"}}, {node, {"translation"}}); app.activeRaCoProject().commandInterface()->addLink({luaScript, {"luaOutputs", "rotation"}}, {node, {"rotation"}}); @@ -143,10 +143,10 @@ end ASSERT_TRUE(app.activeRaCoProject().project()->links()[0]->isValid()); ASSERT_TRUE(app.activeRaCoProject().project()->links()[1]->isValid()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.newTranslation = VEC3F OUT.rotation = VEC3F @@ -156,7 +156,7 @@ end )"); { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(2, app.activeRaCoProject().project()->links().size()); ASSERT_FALSE(app.activeRaCoProject().project()->links()[0]->isValid()); ASSERT_TRUE(app.activeRaCoProject().project()->links()[1]->isValid()); @@ -164,7 +164,7 @@ end ASSERT_TRUE(app.activeRaCoProject().errors()->hasError(node)); } - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.translation = VEC3F OUT.rotation = VEC3F @@ -174,7 +174,7 @@ end )"); { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(2, app.activeRaCoProject().project()->links().size()); ASSERT_TRUE(app.activeRaCoProject().project()->links()[0]->isValid()); ASSERT_TRUE(app.activeRaCoProject().project()->links()[1]->isValid()); @@ -186,10 +186,10 @@ end TEST_F(RaCoProjectFixture, saveWithValidLinkLoadWithBrokenLink) { { RaCoApplication app{backend}; - auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.newTranslation = VEC3F end @@ -198,7 +198,7 @@ end )"); { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(1, app.activeRaCoProject().project()->links().size()); ASSERT_FALSE(app.activeRaCoProject().project()->links()[0]->isValid()); auto node = raco::core::Queries::findByName(app.activeRaCoProject().project()->instances(), "node"); @@ -210,18 +210,18 @@ end TEST_F(RaCoProjectFixture, saveWithBrokenLinkLoadWithValidLink) { { RaCoApplication app{backend}; - auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.newTranslation = VEC3F end function run() end )"); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.translation = VEC3F end @@ -230,7 +230,7 @@ end )"); { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(1, app.activeRaCoProject().project()->links().size()); ASSERT_TRUE(app.activeRaCoProject().project()->links()[0]->isValid()); } @@ -240,12 +240,12 @@ end TEST_F(RaCoProjectFixture, saveLoadWithLinkRemoveOutputPropertyBeforeLoading) { { RaCoApplication app{backend}; - raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } // replace OUT.translation with OUT.scale in external script file - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() OUT.scale = VEC3F end @@ -254,7 +254,7 @@ end )"); { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; app.doOneLoop(); ASSERT_EQ(app.activeRaCoProject().project()->links().size(), 1); ASSERT_EQ(app.activeRaCoProject().project()->links()[0]->isValid(), false); @@ -262,7 +262,7 @@ end } TEST_F(RaCoProjectFixture, saveLoadWithLuaScriptNewOutputPropertyGetsCalculated) { - auto luaScriptPath = (cwd_path() / "lua_script.lua").string(); + auto luaScriptPath = (test_path() / "lua_script.lua").string(); raco::utils::file::write(luaScriptPath, R"( function interface() @@ -282,10 +282,10 @@ end const auto luaScript{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; app.activeRaCoProject().commandInterface()->set(raco::core::ValueHandle{luaScript, {"uri"}}, luaScriptPath); app.activeRaCoProject().commandInterface()->set(raco::core::ValueHandle{luaScript, {"luaInputs", "integer"}}, 5); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str())); } - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() IN.integer = INT OUT.integerTwo = INT @@ -298,7 +298,7 @@ end )"); { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; app.doOneLoop(); auto luaScript = raco::core::Queries::findByName(app.activeRaCoProject().project()->instances(), "lua_script"); auto newPropertyOutput = raco::core::ValueHandle{luaScript, {"luaOutputs", "integerTwo"}}.asInt(); @@ -308,14 +308,14 @@ end TEST_F(RaCoProjectFixture, saveAsMeshRerootRelativeURIHierarchyDown) { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); auto mesh = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id"); std::string relativeUri{"Duck.glb"}; app.activeRaCoProject().commandInterface()->set({mesh, {"uri"}}, relativeUri); - QDir().mkdir((cwd_path() / "project").string().c_str()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project" / "project.file").string().c_str())); + QDir().mkdir((test_path() / "project").string().c_str()); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project" / "project.file").string().c_str())); std::string newRelativeDuckPath{"../" + relativeUri}; ASSERT_EQ(app.activeRaCoProject().project()->instances().back()->get("uri")->asString(), newRelativeDuckPath); @@ -327,16 +327,16 @@ TEST_F(RaCoProjectFixture, saveAsThenLoadAnimationKeepsChannelAmount) { auto* commandInterface = app.activeRaCoProject().commandInterface(); raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; auto scenegraph = commandInterface->meshCache()->getMeshScenegraph(desc); commandInterface->insertAssetScenegraph(*scenegraph, desc.absPath, nullptr); commandInterface->createObject(raco::user_types::Animation::typeDescription.typeName, "userAnim", "userAnim"); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "anims.rca").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "anims.rca").string().c_str())); app.switchActiveRaCoProject(""); - app.switchActiveRaCoProject(QString::fromStdString((cwd_path() / "anims.rca").string())); + app.switchActiveRaCoProject(QString::fromStdString((test_path() / "anims.rca").string())); auto anims = raco::core::Queries::filterByTypeName(app.activeRaCoProject().project()->instances(), {raco::user_types::Animation::typeDescription.typeName}); auto importedAnim = raco::core::Queries::findByName(anims, "Wheels"); @@ -348,13 +348,13 @@ TEST_F(RaCoProjectFixture, saveAsThenLoadAnimationKeepsChannelAmount) { TEST_F(RaCoProjectFixture, saveAsMeshRerootRelativeURIHierarchyUp) { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project" / "project.file").string()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project" / "project.file").string()); auto mesh = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id"); std::string relativeUri{"Duck.glb"}; app.activeRaCoProject().commandInterface()->set({mesh, {"uri"}}, relativeUri); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project.file").string().c_str())); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project.file").string().c_str())); std::string newRelativeDuckPath{"project/" + relativeUri}; ASSERT_EQ(app.activeRaCoProject().project()->instances().back()->get("uri")->asString(), newRelativeDuckPath); @@ -362,15 +362,15 @@ TEST_F(RaCoProjectFixture, saveAsMeshRerootRelativeURIHierarchyUp) { TEST_F(RaCoProjectFixture, saveAsMaterialRerootRelativeURI) { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); auto mat = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Material::typeDescription.typeName, "material"); std::string relativeUri{"relativeURI"}; app.activeRaCoProject().commandInterface()->set({mat, {"uriVertex"}}, relativeUri); app.activeRaCoProject().commandInterface()->set({mat, {"uriFragment"}}, relativeUri); - QDir().mkdir((cwd_path() / "project").string().c_str()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project" / "project.file").string().c_str())); + QDir().mkdir((test_path() / "project").string().c_str()); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project" / "project.file").string().c_str())); std::string newRelativePath{"../" + relativeUri}; ASSERT_EQ(app.activeRaCoProject().project()->instances().back()->get("uriVertex")->asString(), newRelativePath); @@ -379,14 +379,14 @@ TEST_F(RaCoProjectFixture, saveAsMaterialRerootRelativeURI) { TEST_F(RaCoProjectFixture, saveAsLuaScriptRerootRelativeURI) { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); auto lua = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua"); std::string relativeUri{"relativeURI"}; app.activeRaCoProject().commandInterface()->set({lua, {"uri"}}, relativeUri); - QDir().mkdir((cwd_path() / "project").string().c_str()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project" / "project.file").string().c_str())); + QDir().mkdir((test_path() / "project").string().c_str()); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project" / "project.file").string().c_str())); std::string newRelativePath{"../" + relativeUri}; ASSERT_EQ(app.activeRaCoProject().project()->instances().back()->get("uri")->asString(), newRelativePath); @@ -394,23 +394,23 @@ TEST_F(RaCoProjectFixture, saveAsLuaScriptRerootRelativeURI) { TEST_F(RaCoProjectFixture, saveAsSimulateSavingFromNewProjectCorrectlyRerootedRelativeURI) { RaCoApplication app{backend}; - std::filesystem::create_directory(cwd_path() / "project"); - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project").string()); + std::filesystem::create_directory(test_path() / "project"); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project").string()); auto mesh = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Mesh::typeDescription.typeName, "lua"); std::string relativeUri{"relativeURI.ctm"}; app.activeRaCoProject().commandInterface()->set({mesh, {"uri"}}, relativeUri); - QDir().mkdir((cwd_path() / "project").string().c_str()); - ASSERT_TRUE(app.activeRaCoProject().saveAs((cwd_path() / "project" / "project.file").string().c_str())); + QDir().mkdir((test_path() / "project").string().c_str()); + ASSERT_TRUE(app.activeRaCoProject().saveAs((test_path() / "project" / "project.file").string().c_str())); ASSERT_EQ(raco::core::ValueHandle(mesh, {"uri"}).asString(), relativeUri); } TEST_F(RaCoProjectFixture, saveAsToDifferentDriveSetsRelativeURIsToAbsolute) { RaCoApplication app{backend}; - std::filesystem::create_directory(cwd_path() / "project"); - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project").string()); + std::filesystem::create_directory(test_path() / "project"); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project").string()); auto mesh = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Mesh::typeDescription.typeName, "lua"); std::string relativeUri{"relativeURI.ctm"}; @@ -420,12 +420,12 @@ TEST_F(RaCoProjectFixture, saveAsToDifferentDriveSetsRelativeURIsToAbsolute) { // here saving this will fail if the drive Z: does not exist. app.activeRaCoProject().saveAs("Z:/projectOnDifferentDrive.rca"); - ASSERT_EQ(raco::core::ValueHandle(mesh, {"uri"}).asString(), (cwd_path() / "project" / relativeUri).generic_string()); + ASSERT_EQ(raco::core::ValueHandle(mesh, {"uri"}).asString(), (test_path() / "project" / relativeUri).string()); } TEST_F(RaCoProjectFixture, idChange) { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); app.doOneLoop(); ASSERT_EQ(123u, app.sceneBackend()->currentSceneIdValue()); @@ -436,7 +436,7 @@ TEST_F(RaCoProjectFixture, idChange) { TEST_F(RaCoProjectFixture, enableTimerFlagChange) { RaCoApplication app{backend}; - const auto PROJECT_PATH = QString::fromStdString((cwd_path() / "project.file").string()); + const auto PROJECT_PATH = QString::fromStdString((test_path() / "project.file").string()); app.activeRaCoProject().commandInterface()->set({app.activeRaCoProject().project()->settings(), {"enableTimerFlag"}}, true); ASSERT_TRUE(app.activeRaCoProject().saveAs(PROJECT_PATH)); app.switchActiveRaCoProject(PROJECT_PATH); @@ -446,11 +446,11 @@ TEST_F(RaCoProjectFixture, enableTimerFlagChange) { TEST_F(RaCoProjectFixture, restoredLinkWorksInLogicEngine) { RaCoApplication app{backend}; auto start{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "start", "start")}; - app.activeRaCoProject().commandInterface()->set({start, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + app.activeRaCoProject().commandInterface()->set({start, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); app.doOneLoop(); auto end{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "end", "end")}; - app.activeRaCoProject().commandInterface()->set({end, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + app.activeRaCoProject().commandInterface()->set({end, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); app.doOneLoop(); app.activeRaCoProject().commandInterface()->addLink({start, {"luaOutputs", "out_float"}}, {end, {"luaInputs", "in_float"}}); @@ -461,7 +461,7 @@ TEST_F(RaCoProjectFixture, restoredLinkWorksInLogicEngine) { ASSERT_EQ(app.activeRaCoProject() .project()->links().size(), 1); ASSERT_FALSE(app.activeRaCoProject() .project()->links()[0]->isValid()); - app.activeRaCoProject().commandInterface()->set({end, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + app.activeRaCoProject().commandInterface()->set({end, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); app.activeRaCoProject().commandInterface()->set({start, {"luaInputs", "in_float"}}, 3.0); app.doOneLoop(); ASSERT_EQ(raco::core::ValueHandle(end, {"luaInputs", "in_float"}).asDouble(), 3.0); @@ -470,10 +470,10 @@ TEST_F(RaCoProjectFixture, restoredLinkWorksInLogicEngine) { TEST_F(RaCoProjectFixture, brokenLinkDoesNotResetProperties) { RaCoApplication app{backend}; auto linkStart{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "start", "start")}; - app.activeRaCoProject().commandInterface()->set({linkStart, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + app.activeRaCoProject().commandInterface()->set({linkStart, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); auto linkEnd{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "end", "end")}; - app.activeRaCoProject().commandInterface()->set({linkEnd, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + app.activeRaCoProject().commandInterface()->set({linkEnd, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); app.doOneLoop(); app.activeRaCoProject().commandInterface()->addLink({linkStart, {"luaOutputs", "ointeger"}}, {linkEnd, {"luaInputs", "integer"}}); @@ -495,101 +495,144 @@ TEST_F(RaCoProjectFixture, brokenLinkDoesNotResetProperties) { } TEST_F(RaCoProjectFixture, launchApplicationWithNoResourceSubFoldersCachedPathsAreSetToUserProjectsDirectoryAndSubFolders) { - auto newProjectFolder = (cwd_path() / "newProject").generic_string(); + auto newProjectFolder = test_path() / "newProject"; std::filesystem::create_directory(newProjectFolder); - raco::components::RaCoPreferences::instance().userProjectsDirectory = QString::fromStdString(newProjectFolder); + raco::components::RaCoPreferences::instance().userProjectsDirectory = QString::fromStdString(newProjectFolder.string()); RaCoApplication app{backend}; + + const auto& defaultResourceDirectories = app.activeRaCoProject().project()->settings()->defaultResourceDirectories_; + auto imageSubdirectory = defaultResourceDirectories->imageSubdirectory_.asString(); + auto meshSubdirectory = defaultResourceDirectories->meshSubdirectory_.asString(); + auto scriptSubdirectory = defaultResourceDirectories->scriptSubdirectory_.asString(); + auto shaderSubdirectory = defaultResourceDirectories->shaderSubdirectory_.asString(); - const auto& prefs = raco::components::RaCoPreferences::instance(); ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project), newProjectFolder); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder + "/" + prefs.meshSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder + "/" + prefs.scriptSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder + "/" + prefs.imageSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder + "/" + prefs.shaderSubdirectory.toStdString()); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder / meshSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder / imageSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder / scriptSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder / shaderSubdirectory); } TEST_F(RaCoProjectFixture, launchApplicationWithResourceSubFoldersCachedPathsAreSetToUserProjectsDirectoryAndSubFolders) { - raco::components::RaCoPreferences::instance().userProjectsDirectory = QString::fromStdString(cwd_path().generic_string()); + raco::components::RaCoPreferences::instance().userProjectsDirectory = QString::fromStdString(test_path().string()); + const auto& prefs = raco::components::RaCoPreferences::instance(); - std::filesystem::create_directory(cwd_path() / prefs.meshSubdirectory.toStdString()); - std::filesystem::create_directory(cwd_path() / prefs.scriptSubdirectory.toStdString()); - std::filesystem::create_directory(cwd_path() / prefs.imageSubdirectory.toStdString()); - std::filesystem::create_directory(cwd_path() / prefs.shaderSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / prefs.meshSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / prefs.scriptSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / prefs.imageSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / prefs.shaderSubdirectory.toStdString()); RaCoApplication app{backend}; auto newProjectFolder = raco::components::RaCoPreferences::instance().userProjectsDirectory.toStdString(); + const auto& defaultResourceDirectories = app.activeRaCoProject().project()->settings()->defaultResourceDirectories_; + auto imageSubdirectory = defaultResourceDirectories->imageSubdirectory_.asString(); + auto meshSubdirectory = defaultResourceDirectories->meshSubdirectory_.asString(); + auto scriptSubdirectory = defaultResourceDirectories->scriptSubdirectory_.asString(); + auto shaderSubdirectory = defaultResourceDirectories->shaderSubdirectory_.asString(); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project), newProjectFolder); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder + "/" + prefs.meshSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder + "/" + prefs.scriptSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder + "/" + prefs.imageSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder + "/" + prefs.shaderSubdirectory.toStdString()); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder + "/" + meshSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder + "/" + scriptSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder + "/" + imageSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder + "/" + shaderSubdirectory); } TEST_F(RaCoProjectFixture, saveAsNewProjectGeneratesResourceSubFolders) { - auto newProjectFolder = (cwd_path() / "newProject").generic_string(); + auto newProjectFolder = (test_path() / "newProject").string(); std::filesystem::create_directory(newProjectFolder); + // TODO: use these after RAOS-690 is merged: + //std::string imageSubdirectory = newProjectFolder + u8"/images äöüß"; + //std::string meshSubdirectory = u8"meshes 滴滴启动纽交所退市"; + // workaround until RAOS-690 is merged: + std::string imageSubdirectory = newProjectFolder + u8"/images abc"; + std::string meshSubdirectory = u8"meshes def"; + + + std::string scriptSubdirectory = u8"shared"; + std::string shaderSubdirectory = u8"shared"; + + using namespace raco::user_types; + RaCoApplication app{backend}; + const auto& settings = app.activeRaCoProject().project()->settings(); + const auto& commandInterface = app.activeRaCoProject().commandInterface(); + commandInterface->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::imageSubdirectory_}, imageSubdirectory); + commandInterface->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::meshSubdirectory_}, meshSubdirectory); + commandInterface->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::scriptSubdirectory_}, scriptSubdirectory); + commandInterface->set({settings, &ProjectSettings::defaultResourceDirectories_, &ProjectSettings::DefaultResourceDirectories::shaderSubdirectory_}, shaderSubdirectory); ASSERT_TRUE(app.activeRaCoProject().saveAs(QString::fromStdString(newProjectFolder + "/project.rca"))); - - const auto& prefs = raco::components::RaCoPreferences::instance(); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project), newProjectFolder); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder + "/" + prefs.meshSubdirectory.toStdString()); - ASSERT_TRUE(raco::utils::path::isExistingDirectory(newProjectFolder + "/" + prefs.meshSubdirectory.toStdString())); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project).string(), newProjectFolder); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder + "/" + prefs.scriptSubdirectory.toStdString()); - ASSERT_TRUE(raco::utils::path::isExistingDirectory(newProjectFolder + "/" + prefs.scriptSubdirectory.toStdString())); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image).string(), imageSubdirectory); + ASSERT_TRUE(raco::utils::u8path(imageSubdirectory).existsDirectory()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder + "/" + prefs.imageSubdirectory.toStdString()); - ASSERT_TRUE(raco::utils::path::isExistingDirectory(newProjectFolder + "/" + prefs.imageSubdirectory.toStdString())); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh).string(), newProjectFolder + "/" + meshSubdirectory); + ASSERT_TRUE(raco::utils::u8path(newProjectFolder + "/" + meshSubdirectory).existsDirectory()); + + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script).string(), newProjectFolder + "/" + scriptSubdirectory); + ASSERT_TRUE(raco::utils::u8path(newProjectFolder + "/" + scriptSubdirectory).existsDirectory()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder + "/" + prefs.shaderSubdirectory.toStdString()); - ASSERT_TRUE(raco::utils::path::isExistingDirectory(newProjectFolder + "/" + prefs.shaderSubdirectory.toStdString())); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader).string(), newProjectFolder + "/" + shaderSubdirectory); + ASSERT_TRUE(raco::utils::u8path(newProjectFolder + "/" + shaderSubdirectory).existsDirectory()); } TEST_F(RaCoProjectFixture, saveAsThenCreateNewProjectResetsCachedPaths) { - raco::components::RaCoPreferences::instance().userProjectsDirectory = QString::fromStdString(cwd_path().generic_string()); + raco::components::RaCoPreferences::instance().userProjectsDirectory = QString::fromStdString(test_path().string()); + + const auto& prefs = raco::components::RaCoPreferences::instance(); - std::filesystem::create_directory(cwd_path() / prefs.meshSubdirectory.toStdString()); - std::filesystem::create_directory(cwd_path() / prefs.scriptSubdirectory.toStdString()); - std::filesystem::create_directory(cwd_path() / prefs.imageSubdirectory.toStdString()); - std::filesystem::create_directory(cwd_path() / prefs.shaderSubdirectory.toStdString()); - std::filesystem::create_directory(cwd_path() / "newProject"); + std::filesystem::create_directory(test_path() / prefs.meshSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / prefs.scriptSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / prefs.imageSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / prefs.shaderSubdirectory.toStdString()); + std::filesystem::create_directory(test_path() / "newProject"); RaCoApplication app{backend}; - ASSERT_TRUE(app.activeRaCoProject().saveAs(QString::fromStdString((cwd_path() / "newProject/project.rca").generic_string()))); + ASSERT_TRUE(app.activeRaCoProject().saveAs(QString::fromStdString((test_path() / "newProject/project.rca").string()))); app.switchActiveRaCoProject(""); + const auto& defaultResourceDirectories = app.activeRaCoProject().project()->settings()->defaultResourceDirectories_; + auto imageSubdirectory = defaultResourceDirectories->imageSubdirectory_.asString(); + auto meshSubdirectory = defaultResourceDirectories->meshSubdirectory_.asString(); + auto scriptSubdirectory = defaultResourceDirectories->scriptSubdirectory_.asString(); + auto shaderSubdirectory = defaultResourceDirectories->shaderSubdirectory_.asString(); + auto newProjectFolder = raco::components::RaCoPreferences::instance().userProjectsDirectory.toStdString(); ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project), newProjectFolder); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder + "/" + prefs.meshSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder + "/" + prefs.scriptSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder + "/" + prefs.imageSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder + "/" + prefs.shaderSubdirectory.toStdString()); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder + "/" + meshSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder + "/" + scriptSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder + "/" + imageSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder + "/" + shaderSubdirectory); } TEST_F(RaCoProjectFixture, saveAsThenLoadProjectProperlySetCachedPaths) { - auto newProjectFolder = (cwd_path() / "newProject").generic_string(); + auto newProjectFolder = test_path() / "newProject"; std::filesystem::create_directory(newProjectFolder); RaCoApplication app{backend}; - ASSERT_TRUE(app.activeRaCoProject().saveAs(QString::fromStdString(newProjectFolder + "/project.rca"))); + ASSERT_TRUE(app.activeRaCoProject().saveAs(QString::fromStdString((newProjectFolder / "project.rca").string()))); app.switchActiveRaCoProject(""); - app.switchActiveRaCoProject(QString::fromStdString(newProjectFolder + "/project.rca")); + app.switchActiveRaCoProject(QString::fromStdString((newProjectFolder / "project.rca").string())); + + const auto& defaultResourceDirectories = app.activeRaCoProject().project()->settings()->defaultResourceDirectories_; + auto imageSubdirectory = defaultResourceDirectories->imageSubdirectory_.asString(); + auto meshSubdirectory = defaultResourceDirectories->meshSubdirectory_.asString(); + auto scriptSubdirectory = defaultResourceDirectories->scriptSubdirectory_.asString(); + auto shaderSubdirectory = defaultResourceDirectories->shaderSubdirectory_.asString(); - const auto& prefs = raco::components::RaCoPreferences::instance(); ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Project), newProjectFolder); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder + "/" + prefs.meshSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder + "/" + prefs.scriptSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder + "/" + prefs.imageSubdirectory.toStdString()); - ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder + "/" + prefs.shaderSubdirectory.toStdString()); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh), newProjectFolder / meshSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Script), newProjectFolder / scriptSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Image), newProjectFolder / imageSubdirectory); + ASSERT_EQ(raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Shader), newProjectFolder / shaderSubdirectory); } TEST_F(RaCoProjectFixture, loadingBrokenJSONFileThrowsException) { - auto jsonPath = (cwd_path() / "brokenJSON.rca").string(); + auto jsonPath = (test_path() / "brokenJSON.rca").string(); raco::utils::file::write(jsonPath, R"( { @@ -603,7 +646,7 @@ TEST_F(RaCoProjectFixture, loadingBrokenJSONFileThrowsException) { TEST_F(RaCoProjectFixture, saveLoadRotationLinksGetReinstated) { { RaCoApplication app{backend}; - auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); + auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); auto lua = std::get(linkedScene); const auto nodeRotEuler{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Node::typeDescription.typeName, "node_eul", "node_eul")}; const auto nodeRotQuat{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Node::typeDescription.typeName, "node_quat", "node_quat")}; @@ -613,10 +656,10 @@ TEST_F(RaCoProjectFixture, saveLoadRotationLinksGetReinstated) { app.activeRaCoProject().commandInterface()->addLink({lua, {"luaOutputs", "rotation3"}}, {nodeRotEuler, {"rotation"}}); app.activeRaCoProject().commandInterface()->addLink({lua, {"luaOutputs", "rotation4"}}, {nodeRotQuat, {"rotation"}}); - app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str()); + app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str()); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(5, app.activeRaCoProject().project()->links().size()); } } @@ -624,7 +667,7 @@ TEST_F(RaCoProjectFixture, saveLoadRotationLinksGetReinstated) { TEST_F(RaCoProjectFixture, saveLoadRotationInvalidLinksGetReinstated) { { RaCoApplication app{backend}; - auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); + auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); auto lua = std::get(linkedScene); const auto nodeRotEuler{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Node::typeDescription.typeName, "node_eul", "node_eul")}; const auto nodeRotQuat{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Node::typeDescription.typeName, "node_quat", "node_quat")}; @@ -638,10 +681,10 @@ TEST_F(RaCoProjectFixture, saveLoadRotationInvalidLinksGetReinstated) { app.activeRaCoProject().commandInterface()->set({lua, {"uri"}}, std::string()); app.doOneLoop(); - app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str()); + app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str()); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; ASSERT_EQ(5, app.activeRaCoProject().project()->links().size()); for (const auto& link : app.activeRaCoProject().project()->links()) { ASSERT_FALSE(link->isValid()); @@ -653,7 +696,7 @@ TEST_F(RaCoProjectFixture, saveLoadRotationInvalidLinksGetReinstated) { TEST_F(RaCoProjectFixture, saveLoadRotationInvalidLinksGetReinstatedWithDifferentTypes) { { RaCoApplication app{backend}; - auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), cwd_path()); + auto linkedScene = raco::createLinkedScene(*app.activeRaCoProject().commandInterface(), test_path()); auto lua = std::get(linkedScene); const auto nodeRotEuler{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Node::typeDescription.typeName, "node_eul", "node_eul")}; const auto nodeRotQuat{app.activeRaCoProject().commandInterface()->createObject(raco::user_types::Node::typeDescription.typeName, "node_quat", "node_quat")}; @@ -667,10 +710,10 @@ TEST_F(RaCoProjectFixture, saveLoadRotationInvalidLinksGetReinstatedWithDifferen app.activeRaCoProject().commandInterface()->set({lua, {"uri"}}, std::string()); app.doOneLoop(); - app.activeRaCoProject().saveAs((cwd_path() / "project.rcp").string().c_str()); + app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str()); } { - RaCoApplication app{backend, (cwd_path() / "project.rcp").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; app.doOneLoop(); auto lua = raco::core::Queries::findByName(app.activeRaCoProject().project()->instances(), "lua_script"); @@ -693,8 +736,8 @@ TEST_F(RaCoProjectFixture, copyPasteShallowAnimationReferencingAnimationChannel) std::string clipboard; { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); - auto path = (cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string(); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); + auto path = (test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string(); auto scenegraph = app.activeRaCoProject().meshCache()->getMeshScenegraph({path, 0, false}); app.activeRaCoProject().commandInterface()->insertAssetScenegraph(*scenegraph, path, nullptr); app.doOneLoop(); @@ -715,8 +758,8 @@ TEST_F(RaCoProjectFixture, copyPasteDeepAnimationReferencingAnimationChannel) { std::string clipboard; { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); - auto path = (cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string(); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); + auto path = (test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string(); auto scenegraph = app.activeRaCoProject().meshCache()->getMeshScenegraph({path, 0, false}); app.activeRaCoProject().commandInterface()->insertAssetScenegraph(*scenegraph, path, nullptr); app.doOneLoop(); @@ -736,15 +779,15 @@ TEST_F(RaCoProjectFixture, copyPasteShallowLuaScriptReferencingLuaScriptModule) std::string clipboard; { RaCoApplication app{ backend }; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); auto module = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScriptModule::typeDescription.typeName, "m", "m"); auto script = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "s", "s"); app.doOneLoop(); - app.activeRaCoProject().commandInterface()->set({ module, &raco::user_types::LuaScriptModule::uri_ }, cwd_path().append("scripts/moduleDefinition.lua").string()); + app.activeRaCoProject().commandInterface()->set({ module, &raco::user_types::LuaScriptModule::uri_ }, test_path().append("scripts/moduleDefinition.lua").string()); app.doOneLoop(); - app.activeRaCoProject().commandInterface()->set({ script, &raco::user_types::LuaScript::uri_ }, cwd_path().append("scripts/moduleDependency.lua").string()); + app.activeRaCoProject().commandInterface()->set({ script, &raco::user_types::LuaScript::uri_ }, test_path().append("scripts/moduleDependency.lua").string()); app.doOneLoop(); app.activeRaCoProject().commandInterface()->set({ script, {"luaModules", "coalas"} }, module); @@ -763,15 +806,15 @@ TEST_F(RaCoProjectFixture, copyPasteDeepLuaScriptReferencingLuaScriptModule) { std::string clipboard; { RaCoApplication app{ backend }; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.file").string()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.file").string()); auto module = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScriptModule::typeDescription.typeName, "m", "m"); auto script = app.activeRaCoProject().commandInterface()->createObject(raco::user_types::LuaScript::typeDescription.typeName, "s", "s"); app.doOneLoop(); - app.activeRaCoProject().commandInterface()->set({ module, &raco::user_types::LuaScriptModule::uri_ }, cwd_path().append("scripts/moduleDefinition.lua").string()); + app.activeRaCoProject().commandInterface()->set({ module, &raco::user_types::LuaScriptModule::uri_ }, test_path().append("scripts/moduleDefinition.lua").string()); app.doOneLoop(); - app.activeRaCoProject().commandInterface()->set({ script, &raco::user_types::LuaScript::uri_ }, cwd_path().append("scripts/moduleDependency.lua").string()); + app.activeRaCoProject().commandInterface()->set({ script, &raco::user_types::LuaScript::uri_ }, test_path().append("scripts/moduleDependency.lua").string()); app.doOneLoop(); app.activeRaCoProject().commandInterface()->set({ script, {"luaModules", "coalas"} }, module); @@ -790,11 +833,11 @@ TEST_F(RaCoProjectFixture, copyPasteDeepLuaScriptReferencingLuaScriptModule) { // Skip this test in Linux because TC build on Linux seems to not properly change permissions TEST_F(RaCoProjectFixture, readOnlyProject_appTitleSuffix) { std::string clipboard; - auto projectPath = (cwd_path() / "project.rca"); + auto projectPath = (test_path() / "project.rca"); { RaCoApplication app{backend}; - app.activeRaCoProject().project()->setCurrentPath((cwd_path() / "project.rca").string()); - app.activeRaCoProject().saveAs((cwd_path() / "project.rca").string().c_str()); + app.activeRaCoProject().project()->setCurrentPath((test_path() / "project.rca").string()); + app.activeRaCoProject().saveAs((test_path() / "project.rca").string().c_str()); } std::error_code ec; @@ -802,7 +845,7 @@ TEST_F(RaCoProjectFixture, readOnlyProject_appTitleSuffix) { ASSERT_TRUE(!ec) << "Failed to set permissons. Error code: " << ec.value() << " Error message: '" << ec.message() << "'"; { - RaCoApplication app{backend, (cwd_path() / "project.rca").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rca").string().c_str()}; auto expectedAppTitle = fmt::format("{} - ({}) ", RaCoApplication::APPLICATION_NAME.toStdString(), app.activeProjectPath()); EXPECT_EQ(app.generateApplicationTitle().toStdString(), expectedAppTitle); @@ -812,7 +855,7 @@ TEST_F(RaCoProjectFixture, readOnlyProject_appTitleSuffix) { ASSERT_TRUE(!ec) << "Failed to set permissons. Error code: " << ec.value() << " Error message: '" << ec.message() << "'"; { - RaCoApplication app{backend, (cwd_path() / "project.rca").string().c_str()}; + RaCoApplication app{backend, (test_path() / "project.rca").string().c_str()}; auto expectedAppTitle = fmt::format("{} - ({})", RaCoApplication::APPLICATION_NAME.toStdString(), app.activeProjectPath()); EXPECT_EQ(app.generateApplicationTitle().toStdString(), expectedAppTitle); diff --git a/components/libComponents/include/components/DataChangeDispatcher.h b/components/libComponents/include/components/DataChangeDispatcher.h index 159a38ea..303b2acc 100644 --- a/components/libComponents/include/components/DataChangeDispatcher.h +++ b/components/libComponents/include/components/DataChangeDispatcher.h @@ -85,8 +85,9 @@ class DataChangeDispatcher { Subscription registerOnChildren(core::ValueHandle valueHandle, ValueHandleCallback callback) noexcept; Subscription registerOnPropertyChange(const std::string& propertyName, ValueHandleCallback callback) noexcept; Subscription registerOnObjectsLifeCycle(EditorObjectCallback onCreation, EditorObjectCallback onDeletion) noexcept; - /** Lifecycle changes to any link, creation and deletion. */ - Subscription registerOnLinksLifeCycle(LinkCallback onCreation, LinkCallback onDeletion) noexcept; + // Lifecycle changes (creation and deletion) to any link ending on endObject. + // If endObject == nullptr receive notification of link creation and deletion for any end object. + Subscription registerOnLinksLifeCycle(core::SEditorObject endObject, LinkCallback onCreation, LinkCallback onDeletion) noexcept; Subscription registerOnLinkValidityChange(LinkCallback callback) noexcept; Subscription registerOnErrorChanged(core::ValueHandle valueHandle, Callback callback) noexcept; Subscription registerOnErrorChangedInScene(Callback callback) noexcept; @@ -124,7 +125,7 @@ class DataChangeDispatcher { void emitBulkChange(const core::SEditorObjectSet& changedObjects); std::set, std::owner_less>> objectLifecycleListeners_{}; - std::set, std::owner_less>> linkLifecycleListeners_{}; + std::map, std::owner_less>>> linkLifecycleListeners_{}; std::set, std::owner_less>> linkValidityChangeListeners_{}; std::map, std::owner_less>>> listeners_{}; std::map, std::owner_less>>> childrenListeners_{}; diff --git a/components/libComponents/include/components/FileChangeListenerImpl.h b/components/libComponents/include/components/FileChangeListenerImpl.h index b3a9bab2..863121aa 100644 --- a/components/libComponents/include/components/FileChangeListenerImpl.h +++ b/components/libComponents/include/components/FileChangeListenerImpl.h @@ -13,6 +13,7 @@ #include "core/FileChangeMonitor.h" #include "utils/stdfilesystem.h" +#include "utils/u8path.h" #include #include @@ -38,7 +39,7 @@ class FileChangeListenerImpl { static constexpr int DELAYED_FILE_LOAD_TIME_MSEC = 100; private: - std::filesystem::path path_; + raco::utils::u8path path_; Callback fileChangeCallback_; QFileSystemWatcher fileWatcher_; QTimer delayedLoadTimer_; diff --git a/components/libComponents/include/components/RaCoPreferences.h b/components/libComponents/include/components/RaCoPreferences.h index 0eb6581f..462420c7 100644 --- a/components/libComponents/include/components/RaCoPreferences.h +++ b/components/libComponents/include/components/RaCoPreferences.h @@ -24,11 +24,11 @@ class RaCoPreferences final : public QObject { RaCoPreferences(); public: - static bool init() noexcept; + static void init() noexcept; static RaCoPreferences& instance() noexcept; bool save(); - bool load(); + void load(); QString userProjectsDirectory{}; diff --git a/components/libComponents/src/DataChangeDispatcher.cpp b/components/libComponents/src/DataChangeDispatcher.cpp index 61dcc048..89018d0a 100644 --- a/components/libComponents/src/DataChangeDispatcher.cpp +++ b/components/libComponents/src/DataChangeDispatcher.cpp @@ -183,8 +183,9 @@ void DataChangeDispatcher::dispatch(const DataChangeRecorder& dataChanges) { } for (auto& [endObjId, links] : dataChanges.getRemovedLinks()) { + auto copy{linkLifecycleListeners_[endObjId]}; + std::copy(linkLifecycleListeners_[std::string()].begin(), linkLifecycleListeners_[std::string()].end(), std::inserter(copy, copy.end())); for (auto& link : links) { - auto copy{linkLifecycleListeners_}; for (const auto& ptr : copy) { if (!ptr.expired()) { auto listener{ptr.lock()}; @@ -226,8 +227,9 @@ void DataChangeDispatcher::dispatch(const DataChangeRecorder& dataChanges) { } for (auto& [endObjId, links] : dataChanges.getAddedLinks()) { + auto copy{linkLifecycleListeners_[endObjId]}; + std::copy(linkLifecycleListeners_[std::string()].begin(), linkLifecycleListeners_[std::string()].end(), std::inserter(copy, copy.end())); for (auto& link : links) { - auto copy{linkLifecycleListeners_}; for (const auto& ptr : copy) { if (!ptr.expired()) { auto listener{ptr.lock()}; @@ -334,12 +336,22 @@ Subscription DataChangeDispatcher::registerOnObjectsLifeCycle(EditorObjectCallba }}; } -Subscription DataChangeDispatcher::registerOnLinksLifeCycle(LinkCallback onCreation, LinkCallback onDeletion) noexcept { +Subscription DataChangeDispatcher::registerOnLinksLifeCycle(SEditorObject endObject, LinkCallback onCreation, LinkCallback onDeletion) noexcept { auto listener{std::make_shared(onCreation, onDeletion)}; - linkLifecycleListeners_.insert(listener); - return Subscription{this, listener, [this, listener]() { - linkLifecycleListeners_.erase(listener); - }}; + std::string endObjectID; + if (endObject) { + endObjectID = endObject->objectID(); + } + linkLifecycleListeners_[endObjectID].insert(listener); + return Subscription { + this, listener, [this, endObjectID, listener]() { + auto it = linkLifecycleListeners_.find(endObjectID); + it->second.erase(listener); + if (it->second.empty()) { + linkLifecycleListeners_.erase(endObjectID); + } + } + }; } Subscription DataChangeDispatcher::registerOnLinkValidityChange(LinkCallback callback) noexcept { diff --git a/components/libComponents/src/FileChangeListenerImpl.cpp b/components/libComponents/src/FileChangeListenerImpl.cpp index 5c0342df..7cea0988 100644 --- a/components/libComponents/src/FileChangeListenerImpl.cpp +++ b/components/libComponents/src/FileChangeListenerImpl.cpp @@ -9,7 +9,7 @@ */ #include "components/FileChangeListenerImpl.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "core/PathManager.h" #include "log_system/log.h" @@ -32,7 +32,7 @@ FileChangeListenerImpl::FileChangeListenerImpl(std::string& absPath, const Callb directoryWatchConnection_ = QObject::connect(&fileWatcher_, &QFileSystemWatcher::directoryChanged, [this](const auto& dirPath) { onDirectoryChanged(dirPath); }); delayedLoadTimerConnection_ = QObject::connect(&delayedLoadTimer_, &QTimer::timeout, [this]() { onDelayedLoad(); }); - didFileExistOnLastWatch_ = raco::utils::path::exists(absPath); + didFileExistOnLastWatch_ = path_.exists(); installWatchers(); } @@ -45,7 +45,7 @@ FileChangeListenerImpl::~FileChangeListenerImpl() { } std::string FileChangeListenerImpl::getPath() const { - return path_.generic_string(); + return path_.string(); } void FileChangeListenerImpl::addPathToWatch(const QString& path) { @@ -63,8 +63,8 @@ void FileChangeListenerImpl::installWatchers() { } void FileChangeListenerImpl::installFileWatch() { - if (raco::utils::path::exists(path_.generic_string())) { - auto pathQtString = QString::fromStdString(path_.generic_string()); + if (path_.exists()) { + auto pathQtString = QString::fromStdString(path_.string()); auto fileWatcherContainsFilePath = fileWatcher_.files().contains(pathQtString); if (!fileWatcherContainsFilePath) { @@ -75,8 +75,8 @@ void FileChangeListenerImpl::installFileWatch() { void FileChangeListenerImpl::installDirectoryWatch() { auto previouslyWatchedDirs = fileWatcher_.directories(); - for (auto parentPath = path_.parent_path(); parentPath != path_.root_path() && raco::utils::path::isExistingDirectory(parentPath.generic_string()); parentPath = parentPath.parent_path()) { - auto parentPathQtString = QString::fromStdString(parentPath.generic_string()); + for (auto parentPath = path_.parent_path(); parentPath != path_.root_path() && parentPath.existsDirectory(); parentPath = parentPath.parent_path()) { + auto parentPathQtString = QString::fromStdString(parentPath.string()); auto fileWatcherContainsDirectory = fileWatcher_.files().contains(parentPathQtString); if (!fileWatcherContainsDirectory) { @@ -102,7 +102,7 @@ void FileChangeListenerImpl::onFileChanged(const QString& filePath) { } void FileChangeListenerImpl::onDelayedLoad() { - didFileExistOnLastWatch_ = raco::utils::path::exists(path_.generic_string()); + didFileExistOnLastWatch_ = path_.exists(); #if (defined(OS_WINDOWS) || defined(OS_UNIX)) if (didFileExistOnLastWatch_) { @@ -118,7 +118,7 @@ void FileChangeListenerImpl::onDelayedLoad() { } void FileChangeListenerImpl::onDirectoryChanged(const QString& dirPath) { - auto fileExists = raco::utils::path::exists(path_.generic_string()); + auto fileExists = path_.exists(); auto directoryOrFileWasRenamed = didFileExistOnLastWatch_ && !fileExists; auto fileWasCreatedOrMovedInPlace = !didFileExistOnLastWatch_ && fileExists; @@ -146,7 +146,7 @@ bool FileChangeListenerImpl::fileCanBeAccessedOnWindows() { return true; } - LOG_DEBUG(log_system::RAMSES_BACKEND, "Windows could not access file {} - it seems to be opened for writing by another process right now.", path_.generic_string()); + LOG_DEBUG(log_system::RAMSES_BACKEND, "Windows could not access file {} - it seems to be opened for writing by another process right now.", path_.string()); return false; } @@ -154,14 +154,14 @@ bool FileChangeListenerImpl::fileCanBeAccessedOnWindows() { #if (defined(OS_UNIX)) bool FileChangeListenerImpl::fileCanBeAccessedOnUnix() { - auto fileDescriptor = open(path_.generic_string().c_str(), O_RDONLY); + auto fileDescriptor = open(path_.string().c_str(), O_RDONLY); if (fileDescriptor > 0 && EACCES != errno && EAGAIN != errno) { close(fileDescriptor); return true; } - LOG_DEBUG(log_system::RAMSES_BACKEND, "Linux could not access file {} - {}", path_.generic_string(), strerror(errno)); + LOG_DEBUG(log_system::RAMSES_BACKEND, "Linux could not access file {} - {}", path_.string(), strerror(errno)); return false; } diff --git a/components/libComponents/src/RaCoPreferences.cpp b/components/libComponents/src/RaCoPreferences.cpp index ae83fe70..c3bbaed9 100644 --- a/components/libComponents/src/RaCoPreferences.cpp +++ b/components/libComponents/src/RaCoPreferences.cpp @@ -9,7 +9,7 @@ */ #include "components/RaCoPreferences.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "core/PathManager.h" #include "log_system/log.h" @@ -21,13 +21,12 @@ RaCoPreferences::RaCoPreferences() { load(); } -bool RaCoPreferences::init() noexcept { +void RaCoPreferences::init() noexcept { RaCoPreferences::instance(); - return true; } bool RaCoPreferences::save() { - QSettings settings(raco::core::PathManager::preferenceFileLocation().c_str(), QSettings::IniFormat); + auto settings = raco::core::PathManager::preferenceSettings(); settings.setValue("userProjectsDirectory", userProjectsDirectory); settings.setValue("imageSubdirectory", imageSubdirectory); @@ -35,25 +34,23 @@ bool RaCoPreferences::save() { settings.setValue("scriptSubdirectory", scriptSubdirectory); settings.setValue("shaderSubdirectory", shaderSubdirectory); - return true; + settings.sync(); + return settings.status() == QSettings::NoError; } -bool RaCoPreferences::load() { - LOG_INFO(log_system::COMMON, "{}", raco::core::PathManager::preferenceFileLocation()); - QSettings settings(raco::core::PathManager::preferenceFileLocation().c_str(), QSettings::IniFormat); +void RaCoPreferences::load() { + auto settings = raco::core::PathManager::preferenceSettings(); std::string dir = settings.value("userProjectsDirectory", "").toString().toStdString(); - if (raco::utils::path::isExistingDirectory(dir)) { + if (raco::utils::u8path(dir).existsDirectory()) { userProjectsDirectory = QString::fromStdString(dir); } else { - userProjectsDirectory = QString::fromStdString(raco::core::PathManager::defaultProjectFallbackPath()); + userProjectsDirectory = QString::fromStdString(raco::core::PathManager::defaultProjectFallbackPath().string()); } imageSubdirectory = settings.value("imageSubdirectory", "images").toString(); meshSubdirectory = settings.value("meshSubdirectory", "meshes").toString(); scriptSubdirectory = settings.value("scriptSubdirectory", "scripts").toString(); shaderSubdirectory = settings.value("shaderSubdirectory", "shaders").toString(); - - return true; } RaCoPreferences& RaCoPreferences::instance() noexcept { diff --git a/components/libComponents/tests/FileChangeMonitor_test.cpp b/components/libComponents/tests/FileChangeMonitor_test.cpp index e2565a6a..5413e8f8 100644 --- a/components/libComponents/tests/FileChangeMonitor_test.cpp +++ b/components/libComponents/tests/FileChangeMonitor_test.cpp @@ -13,7 +13,7 @@ #include "components/FileChangeMonitorImpl.h" #include "testing/TestEnvironmentCore.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include @@ -28,21 +28,21 @@ class FileChangeMonitorTest : public TestEnvironmentCore { TestEnvironmentCore::SetUp(); std::filesystem::create_directory(testFolderPath_); - testFileOutputStream_ = std::ofstream(testFilePath_.generic_string(), std::ios_base::out); + testFileOutputStream_ = std::ofstream(testFilePath_.string(), std::ios_base::out); testFileOutputStream_.close(); - createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.generic_string(), testCallback_)); + createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.string(), testCallback_)); } void TearDown() override { - if (raco::utils::path::exists(testFilePath_.generic_string())) { + if (testFilePath_.exists()) { std::filesystem::permissions(testFilePath_, std::filesystem::perms::all); } - if (raco::utils::path::exists(testFolderPath_.generic_string())) { + if (testFolderPath_.exists()) { std::filesystem::permissions(testFolderPath_, std::filesystem::perms::all); } - if (raco::utils::path::exists(cwd_path().generic_string())) { - std::filesystem::permissions(cwd_path(), std::filesystem::perms::all); + if (test_path().exists()) { + std::filesystem::permissions(test_path(), std::filesystem::perms::all); } TestEnvironmentCore::TearDown(); } @@ -69,7 +69,7 @@ class FileChangeMonitorTest : public TestEnvironmentCore { return fileChangeCounter_; } - void runRenamingRoutine(std::filesystem::path& originPath, const char* firstRename, const char* secondRename) { + void runRenamingRoutine(raco::utils::u8path& originPath, const char* firstRename, const char* secondRename) { auto newFilePath = originPath; newFilePath.replace_filename(firstRename); @@ -91,8 +91,8 @@ class FileChangeMonitorTest : public TestEnvironmentCore { std::ofstream testFileOutputStream_; int fileChangeCounter_{0}; - std::filesystem::path testFolderPath_{cwd_path().append(TEST_RESOURCES_FOLDER_NAME)}; - std::filesystem::path testFilePath_{std::filesystem::path(testFolderPath_).append(TEST_FILE_NAME)}; + raco::utils::u8path testFolderPath_{test_path().append(TEST_RESOURCES_FOLDER_NAME)}; + raco::utils::u8path testFilePath_{raco::utils::u8path(testFolderPath_).append(TEST_FILE_NAME)}; FileChangeCallback testCallback_ = {&context, nullptr, [this]() { ++fileChangeCounter_; }}; std::unique_ptr testFileChangeMonitor_ = std::make_unique(); std::vector createdFileListeners_; @@ -110,17 +110,17 @@ TEST_F(FileChangeMonitorTest, InstantiationNoFileChange) { /* TEST_F(FileChangeMonitorTest, DeInstantiationNoCrash) { auto secondFileChangeMonitor = std::make_unique(context); - auto secondFileListener_ = secondFileChangeMonitor->registerFileChangedHandler(testFilePath_.generic_string(), testCallback_); + auto secondFileListener_ = secondFileChangeMonitor->registerFileChangedHandler(testFilePath_.string(), testCallback_); secondFileChangeMonitor.reset(); } */ TEST_F(FileChangeMonitorTest, FileModificationCreation) { - testFilePath_ = std::filesystem::path(testFolderPath_).append("differentFile.txt"); - createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.generic_string(), testCallback_)); + testFilePath_ = raco::utils::u8path(testFolderPath_).append("differentFile.txt"); + createdFileListeners_.emplace_back(testFileChangeMonitor_->registerFileChangedHandler(testFilePath_.string(), testCallback_)); ASSERT_EQ(waitForFileChangeCounterGEq(0), 0); - testFileOutputStream_ = std::ofstream(testFilePath_.generic_string(), std::ios_base::out); + testFileOutputStream_ = std::ofstream(testFilePath_.string(), std::ios_base::out); testFileOutputStream_.close(); ASSERT_EQ(waitForFileChangeCounterGEq(1), 1); @@ -128,7 +128,7 @@ TEST_F(FileChangeMonitorTest, FileModificationCreation) { TEST_F(FileChangeMonitorTest, FileModificationEditing) { - testFileOutputStream_.open(testFilePath_.generic_string(), std::ios_base::out); + testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out); testFileOutputStream_ << "Test"; testFileOutputStream_.flush(); @@ -150,10 +150,10 @@ TEST_F(FileChangeMonitorTest, FileModificationRenaming) { TEST_F(FileChangeMonitorTest, FileModificationMultipleModificationsAtTheSameTime) { - testFileOutputStream_.open(testFilePath_.generic_string(), std::ios_base::out); + testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out); testFileOutputStream_ << "Test"; - std::ofstream otherOutputStream = std::ofstream(testFilePath_.generic_string(), std::ios_base::app); + std::ofstream otherOutputStream = std::ofstream(testFilePath_.string(), std::ios_base::app); otherOutputStream << "Other"; otherOutputStream.close(); @@ -173,7 +173,7 @@ TEST_F(FileChangeMonitorTest, FileModificationSetAndTryToEditReadOnly) { // fileChangeCounter_ will be 0 in WSL, but the proper value in Linux container ASSERT_EQ(waitForFileChangeCounterGEq(1), 1); - testFileOutputStream_.open(testFilePath_.generic_string(), std::ios_base::out); + testFileOutputStream_.open(testFilePath_.string(), std::ios_base::out); ASSERT_FALSE(testFileOutputStream_.is_open()); ASSERT_EQ(waitForFileChangeCounterGEq(1), 1); @@ -182,7 +182,7 @@ TEST_F(FileChangeMonitorTest, FileModificationSetAndTryToEditReadOnly) { TEST_F(FileChangeMonitorTest, FolderModificationDeletion) { - std::filesystem::remove_all(cwd_path().append(TEST_RESOURCES_FOLDER_NAME)); + std::filesystem::remove_all(test_path().append(TEST_RESOURCES_FOLDER_NAME)); ASSERT_EQ(waitForFileChangeCounterGEq(1), 1); } diff --git a/components/libMeshLoader/src/CTMFileLoader.cpp b/components/libMeshLoader/src/CTMFileLoader.cpp index e7e1fe56..5afd453c 100644 --- a/components/libMeshLoader/src/CTMFileLoader.cpp +++ b/components/libMeshLoader/src/CTMFileLoader.cpp @@ -11,7 +11,11 @@ #include "mesh_loader/CTMMesh.h" +#include "utils/stdfilesystem.h" +#include "utils/u8path.h" + #include +#include namespace { @@ -72,8 +76,18 @@ std::shared_ptr CTMFileLoader::getAnimatio bool CTMFileLoader::loadFile() { if (!importer_) { importer_ = std::make_unique(); + try { - importer_->Load(path_.c_str()); + // Since we want to work with UTF-8 paths, we need to supply our own stream reading function here. + std::ifstream in{raco::utils::u8path(path_).internalPath(), std::ifstream::in | std::ifstream::binary}; + + auto readFunction = [](void* aBuf, CTMuint aCount, void* aUserData) -> CTMuint { + auto in = static_cast(aUserData); + in->read(static_cast(aBuf), aCount); + return in->gcount(); + }; + + importer_->LoadCustom(readFunction, &in); valid_ = true; } catch (ctm_error const& e) { error_ = errorCodeToString(e.error_code()); diff --git a/components/libMeshLoader/src/glTFFileLoader.cpp b/components/libMeshLoader/src/glTFFileLoader.cpp index dbc92313..4f8ee508 100644 --- a/components/libMeshLoader/src/glTFFileLoader.cpp +++ b/components/libMeshLoader/src/glTFFileLoader.cpp @@ -17,6 +17,7 @@ #include "mesh_loader/glTFBufferData.h" #include "mesh_loader/glTFMesh.h" #include "utils/MathUtils.h" +#include "utils/u8path.h" #include "utils/stdfilesystem.h" #include @@ -157,7 +158,7 @@ bool glTFFileLoader::importglTFScene(const std::string& absPath) { std::string err; std::string warn; - if (std::filesystem::path(absPath).extension() == ".glb") { + if (raco::utils::u8path(absPath).extension() == ".glb") { importer_->LoadBinaryFromFile(&*scene_, &err, &warn, absPath); } else { importer_->LoadASCIIFromFile(&*scene_, &err, &warn, absPath); diff --git a/components/libMeshLoader/tests/FileLoader_test.cpp b/components/libMeshLoader/tests/FileLoader_test.cpp index a9a059f3..8b65c03d 100644 --- a/components/libMeshLoader/tests/FileLoader_test.cpp +++ b/components/libMeshLoader/tests/FileLoader_test.cpp @@ -34,7 +34,7 @@ class MeshLoaderTest : public TestEnvironmentCore { TEST_F(MeshLoaderTest, glTFLoadBaked) { core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = true; mesh_loader::glTFFileLoader fileloader(desc.absPath); @@ -49,7 +49,7 @@ TEST_F(MeshLoaderTest, glTFLoadBaked) { TEST_F(MeshLoaderTest, glTFLoadUnbaked) { core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; desc.submeshIndex = 1; @@ -63,7 +63,7 @@ TEST_F(MeshLoaderTest, glTFLoadUnbaked) { TEST_F(MeshLoaderTest, glTFLoadUnbakedThenBaked) { core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; desc.submeshIndex = 1; @@ -84,7 +84,7 @@ TEST_F(MeshLoaderTest, glTFLoadUnbakedThenBaked) { TEST_F(MeshLoaderTest, glTFLoadBakedThenUnbakedThenBakedCorrectPositionData) { core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = true; desc.submeshIndex = 0; @@ -104,7 +104,7 @@ TEST_F(MeshLoaderTest, glTFLoadBakedThenUnbakedThenBakedCorrectPositionData) { TEST_F(MeshLoaderTest, glTFLoadUnbakedThenBakedThenUnbakedCorrectPositionData) { core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); desc.bakeAllSubmeshes = false; desc.submeshIndex = 0; @@ -134,14 +134,14 @@ TEST_F(MeshLoaderTest, glTFLoadInvalidThenValid) { auto mesh = fileloader.loadMesh(desc); ASSERT_EQ(mesh, nullptr); - desc.absPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); + desc.absPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); mesh = fileloader.loadMesh(desc); ASSERT_NE(mesh, nullptr); } TEST_F(MeshLoaderTest, glTFWithTangentsAndBitangents) { core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/AnimatedMorphCube/AnimatedMorphCube.gltf").string(); + desc.absPath = test_path().append("meshes/AnimatedMorphCube/AnimatedMorphCube.gltf").string(); desc.bakeAllSubmeshes = false; desc.submeshIndex = 0; diff --git a/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h b/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h index d92032b8..fdc5ef7b 100644 --- a/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h +++ b/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h @@ -34,6 +34,7 @@ class SpatialAdaptor : public TypedObjectAdaptor, public rotationType_{DEFAULT_VEC3_ROTATION_TYPE}, nodeBinding_{}, linksLifecycle_{sceneAdaptor->dispatcher()->registerOnLinksLifeCycle( + this->baseEditorObject(), [this](const core::LinkDescriptor& link) { raco::core::PropertyDescriptor rotation{this->editorObject(), std::vector{"rotation"}}; if (link.end == rotation) { diff --git a/components/libRamsesBase/include/ramses_adaptor/TextureSamplerAdaptor.h b/components/libRamsesBase/include/ramses_adaptor/TextureSamplerAdaptor.h index 5afbf0ec..96410aa5 100644 --- a/components/libRamsesBase/include/ramses_adaptor/TextureSamplerAdaptor.h +++ b/components/libRamsesBase/include/ramses_adaptor/TextureSamplerAdaptor.h @@ -33,7 +33,7 @@ class TextureSamplerAdaptor : public TypedObjectAdaptor subscriptions_; + std::array subscriptions_; ramses_base::RamsesTexture2D textureData_; static inline std::array, 2> fallbackTextureData_; diff --git a/components/libRamsesBase/include/ramses_base/Utils.h b/components/libRamsesBase/include/ramses_base/Utils.h index 107a9138..16881de3 100644 --- a/components/libRamsesBase/include/ramses_base/Utils.h +++ b/components/libRamsesBase/include/ramses_base/Utils.h @@ -12,6 +12,7 @@ #include "core/EngineInterface.h" #include "data_storage/Value.h" #include "ramses_base/LogicEngine.h" +#include "log_system/log.h" #include #include #include @@ -47,5 +48,6 @@ std::string getRamsesVersionString(); std::string getLogicEngineVersionString(); void enableLogicLoggerOutputToStdout(bool toStdOut); +void setRamsesAndLogicConsoleLogLevel(spdlog::level::level_enum level); }; // namespace raco::ramses_base diff --git a/components/libRamsesBase/src/ramses_adaptor/SceneAdaptor.cpp b/components/libRamsesBase/src/ramses_adaptor/SceneAdaptor.cpp index ed62d544..4e6b4d75 100644 --- a/components/libRamsesBase/src/ramses_adaptor/SceneAdaptor.cpp +++ b/components/libRamsesBase/src/ramses_adaptor/SceneAdaptor.cpp @@ -118,6 +118,7 @@ SceneAdaptor::SceneAdaptor(ramses::RamsesClient* client, ramses_base::LogicEngin adaptorStatusDirty_ = true; })), linksLifecycle_{dispatcher->registerOnLinksLifeCycle( + nullptr, [this](const core::LinkDescriptor& link) { createLink(link); }, [this](const core::LinkDescriptor& link) { removeLink(link); })}, linkValidityChangeSub_{dispatcher->registerOnLinkValidityChange( @@ -132,14 +133,15 @@ SceneAdaptor::SceneAdaptor(ramses::RamsesClient* client, ramses_base::LogicEngin dispatcher_->registerBulkChangeCallback([this](const core::SEditorObjectSet& changedObjects) { performBulkEngineUpdate(changedObjects); }); - const auto& instances{project_->instances()}; - core::SEditorObjectSet initialBulkUpdate(instances.begin(), instances.end()); - performBulkEngineUpdate(initialBulkUpdate); for (const SLink& link : project_->links()) { createLink(link->descriptor()); } + const auto& instances{project_->instances()}; + core::SEditorObjectSet initialBulkUpdate(instances.begin(), instances.end()); + performBulkEngineUpdate(initialBulkUpdate); + scene_->flush(); scene_->publish(); } diff --git a/components/libRamsesBase/src/ramses_adaptor/TextureSamplerAdaptor.cpp b/components/libRamsesBase/src/ramses_adaptor/TextureSamplerAdaptor.cpp index 1575a174..9a576d83 100644 --- a/components/libRamsesBase/src/ramses_adaptor/TextureSamplerAdaptor.cpp +++ b/components/libRamsesBase/src/ramses_adaptor/TextureSamplerAdaptor.cpp @@ -17,6 +17,7 @@ #include #include #include "lodepng.h" +#include "utils/FileUtils.h" namespace raco::ramses_adaptor { @@ -39,6 +40,12 @@ TextureSamplerAdaptor::TextureSamplerAdaptor(SceneAdaptor* sceneAdaptor, std::sh sceneAdaptor->dispatcher()->registerOn(core::ValueHandle{editorObject, &user_types::Texture::anisotropy_}, [this]() { tagDirty(); }), + sceneAdaptor->dispatcher()->registerOn(core::ValueHandle{editorObject, &user_types::Texture::flipTexture_}, [this]() { + tagDirty(); + }), + sceneAdaptor->dispatcher()->registerOn(core::ValueHandle{editorObject, &user_types::Texture::generateMipmaps_}, [this]() { + tagDirty(); + }), sceneAdaptor_->dispatcher()->registerOnPreviewDirty(editorObject, [this]() { tagDirty(); })} {} @@ -96,7 +103,8 @@ RamsesTexture2D TextureSamplerAdaptor::createTexture() { unsigned int height = 0; std::vector data; - const unsigned int ret = lodepng::decode(data, width, height, pngPath.c_str()); + auto pngData = raco::utils::file::readBinary(pngPath); + const unsigned int ret = lodepng::decode(data, width, height, pngData); if (ret != 0) { return nullptr; } diff --git a/components/libRamsesBase/src/ramses_base/BaseEngineBackend.cpp b/components/libRamsesBase/src/ramses_base/BaseEngineBackend.cpp index 1749678a..41a12a36 100644 --- a/components/libRamsesBase/src/ramses_base/BaseEngineBackend.cpp +++ b/components/libRamsesBase/src/ramses_base/BaseEngineBackend.cpp @@ -24,7 +24,9 @@ BaseEngineBackend::BaseEngineBackend( } BaseEngineBackend::~BaseEngineBackend() { - framework_.disconnect(); + if (framework_.isConnected()) { + framework_.disconnect(); + } } bool BaseEngineBackend::connect() { diff --git a/components/libRamsesBase/src/ramses_base/Utils.cpp b/components/libRamsesBase/src/ramses_base/Utils.cpp index 8e4c1984..96f6ebda 100644 --- a/components/libRamsesBase/src/ramses_base/Utils.cpp +++ b/components/libRamsesBase/src/ramses_base/Utils.cpp @@ -159,6 +159,7 @@ void fillLuaScriptInterface(std::vector &interfac bool parseLuaScript(LogicEngine &engine, const std::string &luaScript, const raco::data_storage::Table &modules, raco::core::PropertyInterfaceList &outInputs, raco::core::PropertyInterfaceList &outOutputs, std::string &outError) { rlogic::LuaConfig luaConfig = defaultLuaConfig(); std::vector tempModules; + bool containsBrokenModules = false; for (auto i = 0; i < modules.size(); ++i) { if (auto moduleRef = modules.get(i)->asRef()) { @@ -166,12 +167,15 @@ bool parseLuaScript(LogicEngine &engine, const std::string &luaScript, const rac auto tempModule = tempModules.emplace_back(engine.createLuaModule(moduleRef->as()->currentScriptContents_, tempConfig, "Stage::PreprocessModule::" + moduleRef->objectName())); if (tempModule) { luaConfig.addDependency(modules.name(i), *tempModule); + } else if (!containsBrokenModules) { + containsBrokenModules = true; } } } - auto script = engine.createLuaScript(luaScript, luaConfig, "Stage::PreprocessScript"); - if (script) { + const auto scriptName = "Stage::PreprocessScript"; + auto script = engine.createLuaScript(luaScript, luaConfig, scriptName); + if (!containsBrokenModules && script) { if (auto inputs = script->getInputs()) { fillLuaScriptInterface(outInputs, inputs); } @@ -186,7 +190,11 @@ bool parseLuaScript(LogicEngine &engine, const std::string &luaScript, const rac } return true; } else { - outError = engine.getErrors().at(0).message; + if (containsBrokenModules) { + outError = fmt::format("[{}] LuaScript can not be created because it contains invalid LuaScriptModules.", scriptName); + } else if (!script) { + outError = engine.getErrors().at(0).message; + } for (auto module : tempModules) { if (module) { engine.destroy(*module); @@ -233,4 +241,39 @@ void enableLogicLoggerOutputToStdout(bool enabled) { rlogic::Logger::SetDefaultLogging(enabled); } +void setRamsesAndLogicConsoleLogLevel(spdlog::level::level_enum level) { + using namespace spdlog::level; + + switch (level) { + case level_enum::trace: + rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Trace); + ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Trace); + return; + case level_enum::debug: + rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Debug); + ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Debug); + return; + case level_enum::info: + rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Info); + ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Info); + return; + case level_enum::warn: + rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Warn); + ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Warn); + return; + case level_enum::err: + rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Error); + ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Error); + return; + case level_enum::critical: + rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Fatal); + ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Fatal); + return; + case level_enum::off: + rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Off); + ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Off); + return; + } +} + } // namespace raco::ramses_base diff --git a/components/libRamsesBase/tests/AnimationAdaptor_test.cpp b/components/libRamsesBase/tests/AnimationAdaptor_test.cpp index 82dea29b..7c872cb0 100644 --- a/components/libRamsesBase/tests/AnimationAdaptor_test.cpp +++ b/components/libRamsesBase/tests/AnimationAdaptor_test.cpp @@ -46,7 +46,7 @@ TEST_F(AnimationAdaptorTest, animNode_Creation) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -64,7 +64,7 @@ TEST_F(AnimationAdaptorTest, animNode_Deletion) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -85,7 +85,7 @@ TEST_F(AnimationAdaptorTest, animNode_animName) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -106,7 +106,7 @@ TEST_F(AnimationAdaptorTest, prefab_noAnimNode) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -134,7 +134,7 @@ TEST_F(AnimationAdaptorTest, animNode_multiple_channels) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel1, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({animChannel2, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({animChannel3, &raco::user_types::AnimationChannel::uri_}, uriPath); @@ -168,7 +168,7 @@ TEST_F(AnimationAdaptorTest, afterSync_dataArrays_get_cleaned_up) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -197,7 +197,7 @@ TEST_F(AnimationAdaptorTest, afterSync_dataArrays_get_cleaned_up) { TEST_F(AnimationAdaptorTest, link_with_luascript_output) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = BOOL @@ -231,7 +231,7 @@ TEST_F(AnimationAdaptorTest, link_with_meshNode_mesh_changed) { auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -246,7 +246,7 @@ TEST_F(AnimationAdaptorTest, link_with_meshNode_mesh_changed) { commandInterface.addLink({anim, {"animationOutputs", "Ch0.Animation Sampler Name"}}, {meshNode, {"translation"}}); dispatch(); - commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, (cwd_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()); + commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, (test_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()); dispatch(); ASSERT_EQ(commandInterface.project()->links().size(), 1); @@ -260,7 +260,7 @@ TEST_F(AnimationAdaptorTest, link_with_meshNode_submesh_index_changed) { auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -289,7 +289,7 @@ TEST_F(AnimationAdaptorTest, link_with_meshNode_channel_data_changed_valid_type) auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -318,7 +318,7 @@ TEST_F(AnimationAdaptorTest, link_with_meshNode_channel_data_changed_invalid_typ auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -348,7 +348,7 @@ TEST_F(AnimationAdaptorTest, link_with_meshNode_channel_removed) { auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -385,7 +385,7 @@ TEST_F(AnimationAdaptorTest, anim_in_prefab_prefabinstance_link_inside_prefabins auto prefabInstance = context.createObject(PrefabInstance::typeDescription.typeName, "PrefabInstance"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -425,7 +425,7 @@ TEST_F(AnimationAdaptorTest, anim_in_prefab_prefabinstance_animation_gets_propag auto prefabInstance = context.createObject(PrefabInstance::typeDescription.typeName, "PrefabInstance"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -454,7 +454,7 @@ TEST_F(AnimationAdaptorTest, anim_in_prefab_prefabinstance_animation_pause_gets_ auto prefabInstance = context.createObject(PrefabInstance::typeDescription.typeName, "PrefabInstance"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -489,7 +489,7 @@ TEST_F(AnimationAdaptorTest, anim_in_prefab_prefabinstance_animation_stop_gets_p auto prefabInstance = context.createObject(PrefabInstance::typeDescription.typeName, "PrefabInstance"); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); diff --git a/components/libRamsesBase/tests/AnimationChannelAdaptor_test.cpp b/components/libRamsesBase/tests/AnimationChannelAdaptor_test.cpp index f9f72335..284f973d 100644 --- a/components/libRamsesBase/tests/AnimationChannelAdaptor_test.cpp +++ b/components/libRamsesBase/tests/AnimationChannelAdaptor_test.cpp @@ -30,7 +30,7 @@ TEST_F(AnimationChannelAdaptorTest, validAnim_validSampler_dataArrays) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -44,7 +44,7 @@ TEST_F(AnimationChannelAdaptorTest, validAnim_validSampler_dataArrays_rename_obj dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -61,7 +61,7 @@ TEST_F(AnimationChannelAdaptorTest, validAnim_validSampler_animAssigned_dataArra dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -82,7 +82,7 @@ TEST_F(AnimationChannelAdaptorTest, validAnim_invalidSampler_noDataArrays) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -97,7 +97,7 @@ TEST_F(AnimationChannelAdaptorTest, validAnim_invalidSampler_animAssigned_defaul dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -118,7 +118,7 @@ TEST_F(AnimationChannelAdaptorTest, invalidAnim_invalidSampler_noDataArrays) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "CesiumMilkTruck" / "CesiumMilkTruck.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -133,7 +133,7 @@ TEST_F(AnimationChannelAdaptorTest, noAnim_noDataArrays) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "Duck.glb").string()}; + std::string uriPath{(test_path() / "meshes" / "Duck.glb").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -145,7 +145,7 @@ TEST_F(AnimationChannelAdaptorTest, interpolationTest_dynamicDataArrays) { dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({animChannel, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); @@ -175,7 +175,7 @@ TEST_F(AnimationChannelAdaptorTest, mesh_baked_flag_true_anim_data_gets_imported commandInterface.set({mesh, {"bakeMeshes"}}, true); dispatch(); - std::string uriPath{(cwd_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "InterpolationTest" / "InterpolationTest.gltf").string()}; commandInterface.set({mesh, &raco::user_types::AnimationChannel::uri_}, uriPath); dispatch(); diff --git a/components/libRamsesBase/tests/LinkAdaptor_test.cpp b/components/libRamsesBase/tests/LinkAdaptor_test.cpp index ed94b107..b21e8a8c 100644 --- a/components/libRamsesBase/tests/LinkAdaptor_test.cpp +++ b/components/libRamsesBase/tests/LinkAdaptor_test.cpp @@ -29,7 +29,7 @@ class LinkAdaptorFixture : public RamsesBaseFixture<> {}; TEST_F(LinkAdaptorFixture, linkCreationOneLink) { const auto luaScript{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() IN.x = FLOAT OUT.translation = VEC3F @@ -38,7 +38,7 @@ function run() OUT.translation = { IN.x, 0.0, 0.0 } end )"); - context.set({luaScript, {"uri"}}, (cwd_path() / "lua_script.lua").string()); + context.set({luaScript, {"uri"}}, (test_path() / "lua_script.lua").string()); auto link = context.addLink({luaScript, {"luaOutputs", "translation"}}, {node, {"translation"}}); ASSERT_NO_FATAL_FAILURE(dispatch()); @@ -63,7 +63,7 @@ end TEST_F(LinkAdaptorFixture, linkWorksIfScriptContentChanges) { const auto luaScript{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() IN.x = FLOAT OUT.translation = VEC3F @@ -72,7 +72,7 @@ function run() OUT.translation = { IN.x, 0.0, 0.0 } end )"); - context.set({luaScript, {"uri"}}, (cwd_path() / "lua_script.lua").string()); + context.set({luaScript, {"uri"}}, (test_path() / "lua_script.lua").string()); auto link = context.addLink({luaScript, {"luaOutputs", "translation"}}, {node, {"translation"}}); ASSERT_NO_FATAL_FAILURE(dispatch()); @@ -83,7 +83,7 @@ end ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() IN.x = FLOAT OUT.translation = VEC3F @@ -110,7 +110,7 @@ end TEST_F(LinkAdaptorFixture, linkUnlinkLink) { const auto luaScript{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script.lua").string(), R"( function interface() IN.x = FLOAT OUT.translation = VEC3F @@ -119,7 +119,7 @@ function run() OUT.translation = { IN.x, 0.0, 0.0 } end )"); - context.set({luaScript, {"uri"}}, (cwd_path() / "lua_script.lua").string()); + context.set({luaScript, {"uri"}}, (test_path() / "lua_script.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); context.set({luaScript, {"luaInputs", "x"}}, 5.0); ASSERT_NO_FATAL_FAILURE(dispatch()); @@ -172,7 +172,7 @@ end TEST_F(LinkAdaptorFixture, linkStruct) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto luaScriptIn{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_in", "lua_script_in_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out.lua").string(), R"( function interface() IN.x = FLOAT OUT.a = { @@ -185,8 +185,8 @@ function run() OUT.a.b = IN.x end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out.lua").string()); - raco::utils::file::write((cwd_path() / "lua_script_in.lua").string(), R"( + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out.lua").string()); + raco::utils::file::write((test_path() / "lua_script_in.lua").string(), R"( function interface() OUT.a = FLOAT OUT.b = FLOAT @@ -200,7 +200,7 @@ function run() OUT.b = IN.a.b end )"); - context.set({luaScriptIn, {"uri"}}, (cwd_path() / "lua_script_in.lua").string()); + context.set({luaScriptIn, {"uri"}}, (test_path() / "lua_script_in.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -213,7 +213,7 @@ TEST_F(LinkAdaptorFixture, linkQuaternion) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out.lua").string(), R"( function interface() IN.x = VEC4F OUT.x = VEC4F @@ -222,7 +222,7 @@ function run() OUT.x = IN.x end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -249,7 +249,7 @@ TEST_F(LinkAdaptorFixture, linkEulerAfterQuaternion) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out.lua").string(), R"( function interface() IN.vec4 = VEC4F IN.vec3 = VEC3F @@ -261,7 +261,7 @@ function run() OUT.vec3 = IN.vec3 end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -295,7 +295,7 @@ TEST_F(LinkAdaptorFixture, linkQuaternionAfterEuler) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out.lua").string(), R"( function interface() IN.vec4 = VEC4F IN.vec3 = VEC3F @@ -307,7 +307,7 @@ function run() OUT.vec3 = IN.vec3 end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -341,7 +341,7 @@ TEST_F(LinkAdaptorFixture, linkQuaternionToEulerByURIChange) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out1.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1.lua").string(), R"( function interface() IN.vec = VEC4F OUT.vec = VEC4F @@ -351,7 +351,7 @@ function run() end )"); - raco::utils::file::write((cwd_path() / "lua_script_out2.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out2.lua").string(), R"( function interface() IN.vec = VEC3F OUT.vec = VEC3F @@ -360,7 +360,7 @@ function run() OUT.vec = IN.vec end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -368,7 +368,7 @@ end ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out2.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out2.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -380,7 +380,7 @@ TEST_F(LinkAdaptorFixture, linkEulerToQuaternionByURIChange) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out1.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1.lua").string(), R"( function interface() IN.vec = VEC4F OUT.vec = VEC4F @@ -390,7 +390,7 @@ function run() end )"); - raco::utils::file::write((cwd_path() / "lua_script_out2.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out2.lua").string(), R"( function interface() IN.vec = VEC3F OUT.vec = VEC3F @@ -399,7 +399,7 @@ function run() OUT.vec = IN.vec end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out2.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out2.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -407,7 +407,7 @@ end ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -419,7 +419,7 @@ TEST_F(LinkAdaptorFixture, linkQuaternionStaysAfterTranslationLinkRemoval) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out1.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1.lua").string(), R"( function interface() IN.vec = VEC4F OUT.vec = VEC4F @@ -431,7 +431,7 @@ function run() OUT.transl = IN.transl end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -440,7 +440,7 @@ end ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -457,7 +457,7 @@ TEST_F(LinkAdaptorFixture, linkQuaternionChangeToEulerAfterInvalidLinkIsValid) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out1.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1.lua").string(), R"( function interface() IN.vec = VEC4F OUT.vec = VEC4F @@ -469,7 +469,7 @@ end )"); - raco::utils::file::write((cwd_path() / "lua_script_out1b.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1b.lua").string(), R"( function interface() IN.vec_b = VEC4F OUT.vec_b = VEC4F @@ -481,7 +481,7 @@ end )"); - raco::utils::file::write((cwd_path() / "lua_script_out2.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out2.lua").string(), R"( function interface() IN.vec = VEC3F OUT.vec = VEC3F @@ -492,7 +492,7 @@ function run() end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -500,14 +500,14 @@ end ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1b.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1b.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); ASSERT_FALSE(commandInterface.project()->links()[0]->isValid()); auto nodeBinding = backend.logicEngine().findByName("node_NodeBinding"); ASSERT_NE(nodeBinding->getRotationType(), rlogic::ERotationType::Quaternion); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out2.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out2.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); ASSERT_TRUE(commandInterface.project()->links()[0]->isValid()); @@ -519,7 +519,7 @@ TEST_F(LinkAdaptorFixture, linkQuaternionChangeInvalidToValid) { const auto luaScriptOut{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script_out", "lua_script_out_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; - raco::utils::file::write((cwd_path() / "lua_script_out1.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1.lua").string(), R"( function interface() IN.vec = VEC4F OUT.vec = VEC4F @@ -531,7 +531,7 @@ end )"); - raco::utils::file::write((cwd_path() / "lua_script_out1b.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1b.lua").string(), R"( function interface() IN.vec_b = VEC4F OUT.vec_b = VEC4F @@ -544,7 +544,7 @@ end )"); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); @@ -552,14 +552,14 @@ end ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1b.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1b.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); ASSERT_FALSE(commandInterface.project()->links()[0]->isValid()); auto nodeBinding = backend.logicEngine().findByName("node_NodeBinding"); ASSERT_NE(nodeBinding->getRotationType(), rlogic::ERotationType::Quaternion); - context.set({luaScriptOut, {"uri"}}, (cwd_path() / "lua_script_out1.lua").string()); + context.set({luaScriptOut, {"uri"}}, (test_path() / "lua_script_out1.lua").string()); ASSERT_NO_FATAL_FAILURE(dispatch()); ASSERT_TRUE(backend.logicEngine().update()); ASSERT_TRUE(commandInterface.project()->links()[0]->isValid()); diff --git a/components/libRamsesBase/tests/LuaScriptAdaptor_test.cpp b/components/libRamsesBase/tests/LuaScriptAdaptor_test.cpp index 1da41e29..99ee89b7 100644 --- a/components/libRamsesBase/tests/LuaScriptAdaptor_test.cpp +++ b/components/libRamsesBase/tests/LuaScriptAdaptor_test.cpp @@ -39,7 +39,7 @@ TEST_F(LuaScriptAdaptorFixture, defaultConstruction) { TEST_F(LuaScriptAdaptorFixture, validScript) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() end @@ -58,7 +58,7 @@ end TEST_F(LuaScriptAdaptorFixture, nameChange) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() end @@ -89,7 +89,7 @@ end TEST_F(LuaScriptAdaptorFixture, inInt) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = INT @@ -110,7 +110,7 @@ end TEST_F(LuaScriptAdaptorFixture, inFloat) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = FLOAT @@ -131,7 +131,7 @@ end TEST_F(LuaScriptAdaptorFixture, inBool) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = BOOL @@ -152,7 +152,7 @@ end TEST_F(LuaScriptAdaptorFixture, inVec2f) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = VEC2F @@ -174,7 +174,7 @@ end TEST_F(LuaScriptAdaptorFixture, inStruct) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = { @@ -197,7 +197,7 @@ end TEST_F(LuaScriptAdaptorFixture, inNestedStruct) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() s = { x = FLOAT, y = FLOAT } @@ -221,7 +221,7 @@ end TEST_F(LuaScriptAdaptorFixture, inVec4f) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = VEC4F @@ -243,7 +243,7 @@ end TEST_F(LuaScriptAdaptorFixture, outInt) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = INT @@ -266,7 +266,7 @@ end TEST_F(LuaScriptAdaptorFixture, outFloat) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = FLOAT @@ -289,7 +289,7 @@ end TEST_F(LuaScriptAdaptorFixture, outBool) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = BOOL @@ -312,7 +312,7 @@ end TEST_F(LuaScriptAdaptorFixture, outVec2f) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = VEC2F @@ -336,7 +336,7 @@ end TEST_F(LuaScriptAdaptorFixture, outStruct) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = { @@ -364,7 +364,7 @@ end TEST_F(LuaScriptAdaptorFixture, outNestedStruct) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() s = { x = FLOAT, y = FLOAT } @@ -392,7 +392,7 @@ end TEST_F(LuaScriptAdaptorFixture, outVec4f) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = VEC4F @@ -416,7 +416,7 @@ end TEST_F(LuaScriptAdaptorFixture, keep_global_lua_state) { auto luaScript = create("LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = FLOAT @@ -449,7 +449,7 @@ TEST_F(LuaScriptAdaptorFixture, prefab_instance_top_level_script_engine_name_get auto prefab = create("Prefab"); auto prefabInst = create("PrefabInstance"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = FLOAT @@ -512,7 +512,7 @@ TEST_F(LuaScriptAdaptorFixture, prefab_instance_top_level_script_engine_name_get auto prefab = create("Prefab"); auto prefabInst = create("PrefabInstance"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = FLOAT @@ -550,7 +550,7 @@ TEST_F(LuaScriptAdaptorFixture, prefab_instance_top_level_script_engine_name_get auto prefab = create("Prefab"); auto prefabInst = create("PrefabInstance"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = FLOAT @@ -587,7 +587,7 @@ TEST_F(LuaScriptAdaptorFixture, prefab_instance_top_level_script_engine_name_get auto prefab = create("Prefab"); auto prefabInst = create("PrefabInstance"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.value = FLOAT diff --git a/components/libRamsesBase/tests/MeshAdaptor_test.cpp b/components/libRamsesBase/tests/MeshAdaptor_test.cpp index d4fa2246..16828587 100644 --- a/components/libRamsesBase/tests/MeshAdaptor_test.cpp +++ b/components/libRamsesBase/tests/MeshAdaptor_test.cpp @@ -17,7 +17,7 @@ class MeshAdaptorTest : public RamsesBaseFixture<> {}; TEST_F(MeshAdaptorTest, context_mesh_name_change) { auto node = context.createObject(raco::user_types::Mesh::typeDescription.typeName, "Mesh Name"); - context.set({node, {"uri"}}, cwd_path().append("meshes/Duck.glb").string()); + context.set({node, {"uri"}}, test_path().append("meshes/Duck.glb").string()); dispatch(); @@ -41,7 +41,7 @@ TEST_F(MeshAdaptorTest, context_mesh_name_change) { TEST_F(MeshAdaptorTest, gltf_without_meshes) { auto mesh = context.createObject(raco::user_types::Mesh::typeDescription.typeName, "Mesh Name"); - context.set({mesh, &raco::user_types::Mesh::uri_}, cwd_path().append("meshes/meshless.gltf").string()); + context.set({mesh, &raco::user_types::Mesh::uri_}, test_path().append("meshes/meshless.gltf").string()); dispatch(); @@ -53,7 +53,7 @@ TEST_F(MeshAdaptorTest, gltf_without_meshes) { TEST_F(MeshAdaptorTest, gltf_with_meshes_but_no_mesh_refs_baked) { auto mesh = context.createObject(raco::user_types::Mesh::typeDescription.typeName, "Mesh Name"); context.set({mesh, &raco::user_types::Mesh::bakeMeshes_}, true); - context.set({mesh, &raco::user_types::Mesh::uri_}, cwd_path().append("meshes/meshrefless.gltf").string()); + context.set({mesh, &raco::user_types::Mesh::uri_}, test_path().append("meshes/meshrefless.gltf").string()); dispatch(); @@ -65,7 +65,7 @@ TEST_F(MeshAdaptorTest, gltf_with_meshes_but_no_mesh_refs_baked) { TEST_F(MeshAdaptorTest, gltf_with_meshes_but_no_mesh_refs_unbaked) { auto mesh = context.createObject(raco::user_types::Mesh::typeDescription.typeName, "Mesh Name"); context.set({mesh, &raco::user_types::Mesh::bakeMeshes_}, false); - context.set({mesh, &raco::user_types::Mesh::uri_}, cwd_path().append("meshes/meshrefless.gltf").string()); + context.set({mesh, &raco::user_types::Mesh::uri_}, test_path().append("meshes/meshrefless.gltf").string()); dispatch(); diff --git a/components/libRamsesBase/tests/MeshNodeAdaptor_test.cpp b/components/libRamsesBase/tests/MeshNodeAdaptor_test.cpp index f7aadcbd..83d5afb2 100644 --- a/components/libRamsesBase/tests/MeshNodeAdaptor_test.cpp +++ b/components/libRamsesBase/tests/MeshNodeAdaptor_test.cpp @@ -171,8 +171,8 @@ TEST_F(MeshNodeAdaptorFixture, inContext_userType_ten_MeshNodes_withSameMaterial meshNodes[i] = create_meshnode(std::to_string(i), mesh, material); } - context.set({material, {"uriVertex"}}, cwd_path().append("shaders/basic.vert").string()); - context.set({material, {"uriFragment"}}, cwd_path().append("shaders/basic.frag").string()); + context.set({material, {"uriVertex"}}, test_path().append("shaders/basic.vert").string()); + context.set({material, {"uriFragment"}}, test_path().append("shaders/basic.frag").string()); dispatch(); context.deleteObjects({mesh, material}); @@ -203,8 +203,8 @@ TEST_F(MeshNodeAdaptorFixture, inContext_userType_MeshNode_dynamicMaterial_const EXPECT_STREQ(raco::ramses_adaptor::defaultEffectWithNormalsName, ramsesMeshNode->getAppearance()->getEffect().getName()); } - context.set({material, {"uriVertex"}}, cwd_path().append("shaders/basic.vert").string()); - context.set({material, {"uriFragment"}}, cwd_path().append("shaders/basic.frag").string()); + context.set({material, {"uriVertex"}}, test_path().append("shaders/basic.vert").string()); + context.set({material, {"uriFragment"}}, test_path().append("shaders/basic.frag").string()); dispatch(); auto meshNodes{select(*sceneContext.scene(), ramses::ERamsesObjectType::ERamsesObjectType_MeshNode)}; @@ -325,7 +325,7 @@ TEST_F(MeshNodeAdaptorFixture, inContext_userType_MeshNode_dynamicCreation_meshB dispatch(); context.set(ValueHandle{meshNode, {"mesh"}}, mesh); dispatch(); - context.set(ValueHandle{mesh, {"uri"}}, cwd_path().append("meshes/Duck.glb").string()); + context.set(ValueHandle{mesh, {"uri"}}, test_path().append("meshes/Duck.glb").string()); dispatch(); // TODO: Need a way to check actual mesh resources in ramses, we only check for no error @@ -339,7 +339,7 @@ TEST_F(MeshNodeAdaptorFixture, inContext_userType_MeshNode_dynamicCreation_meshN dispatch(); context.set(ValueHandle{meshNode, {"mesh"}}, mesh); dispatch(); - context.set(ValueHandle{mesh, {"uri"}}, cwd_path().append("meshes/Duck.glb").string()); + context.set(ValueHandle{mesh, {"uri"}}, test_path().append("meshes/Duck.glb").string()); dispatch(); // TODO: Need a way to check actual mesh resources in ramses, we only check for no error @@ -353,7 +353,7 @@ TEST_F(MeshNodeAdaptorFixture, inContext_user_type_MeshNode_meshDeletion_meshNod dispatch(); context.set(ValueHandle{meshNode, {"mesh"}}, mesh); dispatch(); - context.set(ValueHandle{mesh, {"uri"}}, cwd_path().append("meshes/Duck.glb").string()); + context.set(ValueHandle{mesh, {"uri"}}, test_path().append("meshes/Duck.glb").string()); dispatch(); context.deleteObjects({mesh}); @@ -371,7 +371,7 @@ TEST_F(MeshNodeAdaptorFixture, inContext_user_type_MeshNode_submeshSelection_wro dispatch(); context.set(ValueHandle{mesh, {"bakeMeshes"}}, false); dispatch(); - context.set(ValueHandle{mesh, {"uri"}}, cwd_path().append("meshes/Duck.glb").string()); + context.set(ValueHandle{mesh, {"uri"}}, test_path().append("meshes/Duck.glb").string()); dispatch(); context.set(ValueHandle{mesh, {"meshIndex"}}, 1); dispatch(); @@ -379,72 +379,5 @@ TEST_F(MeshNodeAdaptorFixture, inContext_user_type_MeshNode_submeshSelection_wro ASSERT_EQ(context.errors().getError(ValueHandle{mesh}).level(), core::ErrorLevel::ERROR); } -TEST_F(MeshNodeAdaptorFixture, inContext_user_type_MeshNode_submeshSelection_wrongSubmeshIndexCreatesErrorTooLow) { - auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); - dispatch(); - auto mesh = context.createObject(Mesh::typeDescription.typeName, "Mesh"); - dispatch(); - context.set(ValueHandle{meshNode, {"mesh"}}, mesh); - dispatch(); - context.set(ValueHandle{mesh, {"bakeMeshes"}}, false); - dispatch(); - context.set(ValueHandle{mesh, {"uri"}}, cwd_path().append("meshes/Duck.glb").string()); - dispatch(); - context.set(ValueHandle{mesh, {"meshIndex"}}, -1); - dispatch(); - - ASSERT_EQ(context.errors().getError(ValueHandle{mesh}).level(), core::ErrorLevel::ERROR); -} - -TEST_F(MeshNodeAdaptorFixture, inContext_user_type_MeshNode_submeshSelection_correctSubmeshIndexFixesError) { - auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); - dispatch(); - auto mesh = context.createObject(Mesh::typeDescription.typeName, "Mesh"); - dispatch(); - context.set(ValueHandle{meshNode, {"mesh"}}, mesh); - dispatch(); - context.set(ValueHandle{mesh, {"bakeMeshes"}}, false); - dispatch(); - context.set(ValueHandle{mesh, {"uri"}}, cwd_path().append("meshes/Duck.glb").string()); - dispatch(); - context.set(ValueHandle{mesh, {"meshIndex"}}, 1); - dispatch(); - context.set(ValueHandle{mesh, {"meshIndex"}}, 0); - dispatch(); - - ASSERT_EQ(context.errors().getError(ValueHandle{mesh}).level(), core::ErrorLevel::INFORMATION); -} - -TEST_F(MeshNodeAdaptorFixture, inContext_user_type_MeshNode_valid_file_with_no_meshes_unbaked_submesh_error) { - auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); - dispatch(); - auto mesh = context.createObject(Mesh::typeDescription.typeName, "Mesh"); - dispatch(); - context.set(ValueHandle{meshNode, &raco::user_types::MeshNode::mesh_}, mesh); - dispatch(); - context.set(ValueHandle{mesh, &raco::user_types::Mesh::bakeMeshes_}, false); - dispatch(); - context.set(ValueHandle{mesh, &raco::user_types::Mesh::uri_}, cwd_path().append("meshes/meshless.gltf").string()); - dispatch(); - ASSERT_TRUE(context.errors().hasError({mesh})); - ASSERT_EQ(context.errors().getError(ValueHandle{mesh}).level(), core::ErrorLevel::ERROR); -} - -TEST_F(MeshNodeAdaptorFixture, inContext_user_type_MeshNode_valid_file_with_no_meshes_baked_no_submesh_error) { - auto meshNode = context.createObject(MeshNode::typeDescription.typeName, "MeshNode"); - dispatch(); - auto mesh = context.createObject(Mesh::typeDescription.typeName, "Mesh"); - dispatch(); - context.set(ValueHandle{meshNode, &raco::user_types::MeshNode::mesh_}, mesh); - dispatch(); - context.set(ValueHandle{mesh, &raco::user_types::Mesh::bakeMeshes_}, false); - dispatch(); - context.set(ValueHandle{mesh, &raco::user_types::Mesh::uri_}, cwd_path().append("meshes/meshless.gltf").string()); - dispatch(); - context.set(ValueHandle{mesh, &raco::user_types::Mesh::bakeMeshes_}, true); - dispatch(); - ASSERT_TRUE(context.errors().hasError({mesh})); - ASSERT_EQ(context.errors().getError(ValueHandle{mesh}).level(), core::ErrorLevel::ERROR); -} \ No newline at end of file diff --git a/components/libRamsesBase/tests/OrthographicCameraAdaptor_test.cpp b/components/libRamsesBase/tests/OrthographicCameraAdaptor_test.cpp index 4c0c352c..dac09053 100644 --- a/components/libRamsesBase/tests/OrthographicCameraAdaptor_test.cpp +++ b/components/libRamsesBase/tests/OrthographicCameraAdaptor_test.cpp @@ -23,7 +23,7 @@ class OrthographicCameraAdaptorTest : public RamsesBaseFixture<> {}; TEST_F(OrthographicCameraAdaptorTest, quaternion_link_camera_still_active) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = VEC4F diff --git a/components/libRamsesBase/tests/PerspectiveCameraAdaptor_test.cpp b/components/libRamsesBase/tests/PerspectiveCameraAdaptor_test.cpp index f4da9676..47ed2e05 100644 --- a/components/libRamsesBase/tests/PerspectiveCameraAdaptor_test.cpp +++ b/components/libRamsesBase/tests/PerspectiveCameraAdaptor_test.cpp @@ -23,7 +23,7 @@ class PerspectiveCameraAdaptorTest : public RamsesBaseFixture<> {}; TEST_F(PerspectiveCameraAdaptorTest, quaternion_link_camera_still_active) { auto luaScript = context.createObject(LuaScript::typeDescription.typeName, "LuaScript Name"); - std::string uriPath{(cwd_path() / "script.lua").string()}; + std::string uriPath{(test_path() / "script.lua").string()}; raco::utils::file::write(uriPath, R"( function interface() IN.in_value = VEC4F diff --git a/components/libRamsesBase/tests/RamsesLogic_test.cpp b/components/libRamsesBase/tests/RamsesLogic_test.cpp index dcf95f12..0dc2d6b6 100644 --- a/components/libRamsesBase/tests/RamsesLogic_test.cpp +++ b/components/libRamsesBase/tests/RamsesLogic_test.cpp @@ -27,6 +27,38 @@ * Should be used to verify stability of dependency. */ + +TEST(ramses_logic, repeat_create_destroy_simple_script) { + rlogic::LogicEngine logicEngine; + + std::string scriptText = R"( +function interface() + OUT.out_float = FLOAT +end + +function run() +end +)"; + // logicengine bug: crashes in debug build with 10000 iterations although ok with 100 iterations + // TODO: remove the EXPECT_THROW once the logicengine bug has been fixed +#if (!defined(__linux__) && !defined(NDEBUG)) + EXPECT_THROW( + for (unsigned i = 0; i < 10000; i++) { + auto* script = logicEngine.createLuaScript(scriptText); + ASSERT_TRUE(script != nullptr); + ASSERT_TRUE(logicEngine.destroy(*script)); + }, + std::exception); +#else + for (unsigned i = 0; i < 10000; i++) { + auto* script = logicEngine.createLuaScript(scriptText); + ASSERT_TRUE(script != nullptr); + ASSERT_TRUE(logicEngine.destroy(*script)); + } +#endif +} + + TEST(ramses_logic, relink_scriptToScript) { rlogic::LogicEngine logicEngine; auto* outScript = logicEngine.createLuaScript(R"( diff --git a/components/libRamsesBase/tests/Resources_test.cpp b/components/libRamsesBase/tests/Resources_test.cpp index 088d16db..057ea945 100644 --- a/components/libRamsesBase/tests/Resources_test.cpp +++ b/components/libRamsesBase/tests/Resources_test.cpp @@ -21,7 +21,7 @@ class ResourcesAdaptorFixture : public RamsesBaseFixture<> {}; TEST_F(ResourcesAdaptorFixture, texture_name_change) { auto texture = create("texture name"); - context.set({texture, {"uri"}}, (cwd_path() / "images" / "DuckCM.png").string()); + context.set({texture, {"uri"}}, (test_path() / "images" / "DuckCM.png").string()); dispatch(); @@ -43,7 +43,7 @@ TEST_F(ResourcesAdaptorFixture, texture_name_change) { TEST_F(ResourcesAdaptorFixture, texture_info_box) { auto texture = create("texture name"); - context.set({texture, {"uri"}}, (cwd_path() / "images" / "DuckCM.png").string()); + context.set({texture, {"uri"}}, (test_path() / "images" / "DuckCM.png").string()); dispatch(); auto infoBoxError = context.errors().getError(raco::core::ValueHandle{texture}); @@ -54,12 +54,12 @@ TEST_F(ResourcesAdaptorFixture, texture_info_box) { TEST_F(ResourcesAdaptorFixture, cube_map_info_box) { auto cubemap = create("cube map name"); - context.set({cubemap, {"uriFront"}}, (cwd_path() / "images" / "DuckCM.png").string()); - context.set({cubemap, {"uriBack"}}, (cwd_path() / "images" / "DuckCM.png").string()); - context.set({cubemap, {"uriLeft"}}, (cwd_path() / "images" / "DuckCM.png").string()); - context.set({cubemap, {"uriRight"}}, (cwd_path() / "images" / "DuckCM.png").string()); - context.set({cubemap, {"uriTop"}}, (cwd_path() / "images" / "DuckCM.png").string()); - context.set({cubemap, {"uriBottom"}}, (cwd_path() / "images" / "DuckCM.png").string()); + context.set({cubemap, {"uriFront"}}, (test_path() / "images" / "DuckCM.png").string()); + context.set({cubemap, {"uriBack"}}, (test_path() / "images" / "DuckCM.png").string()); + context.set({cubemap, {"uriLeft"}}, (test_path() / "images" / "DuckCM.png").string()); + context.set({cubemap, {"uriRight"}}, (test_path() / "images" / "DuckCM.png").string()); + context.set({cubemap, {"uriTop"}}, (test_path() / "images" / "DuckCM.png").string()); + context.set({cubemap, {"uriBottom"}}, (test_path() / "images" / "DuckCM.png").string()); dispatch(); auto infoBoxError = context.errors().getError(raco::core::ValueHandle{cubemap}); diff --git a/components/libRamsesBase/tests/SceneContext_test.cpp b/components/libRamsesBase/tests/SceneContext_test.cpp index a453c823..5899fb89 100644 --- a/components/libRamsesBase/tests/SceneContext_test.cpp +++ b/components/libRamsesBase/tests/SceneContext_test.cpp @@ -79,9 +79,9 @@ TEST_F(SceneContextTest, construction_createMeshNodeWithMesh) { auto mesh = context.createObject(Mesh::typeDescription.typeName, "Mesh"); auto material = context.createObject(Material::typeDescription.typeName, "Material"); - context.set(raco::core::ValueHandle{mesh, {"uri"}}, (cwd_path() / "meshes/Duck.glb").string()); - context.set(raco::core::ValueHandle{material, {"uriVertex"}}, (cwd_path() / "shaders/simple_texture.vert").string()); - context.set(raco::core::ValueHandle{material, {"uriFragment"}}, (cwd_path() / "shaders/simple_texture.frag").string()); + context.set(raco::core::ValueHandle{mesh, {"uri"}}, (test_path() / "meshes/Duck.glb").string()); + context.set(raco::core::ValueHandle{material, {"uriVertex"}}, (test_path() / "shaders/simple_texture.vert").string()); + context.set(raco::core::ValueHandle{material, {"uriFragment"}}, (test_path() / "shaders/simple_texture.frag").string()); context.moveScenegraphChildren({meshNode}, node); context.set(raco::core::ValueHandle{meshNode, {"mesh"}}, mesh); @@ -111,7 +111,7 @@ TEST_F(SceneContextTest, construction_createOneMeshNodeWithMeshAndOneMeshNodeWit auto meshNode2 = context.createObject(MeshNode::typeDescription.typeName, "Mesh Node2"); auto mesh = context.createObject(Mesh::typeDescription.typeName, "Mesh"); - context.set(raco::core::ValueHandle{mesh, {"uri"}}, (cwd_path() / "meshes/Duck.glb").string()); + context.set(raco::core::ValueHandle{mesh, {"uri"}}, (test_path() / "meshes/Duck.glb").string()); context.set(raco::core::ValueHandle{meshNode, {"mesh"}}, mesh); @@ -137,7 +137,7 @@ TEST_F(SceneContextTest, construction_createMeshNodeWithMeshThenUnassignMesh) { auto mesh = context.createObject(Mesh::typeDescription.typeName, "Mesh"); auto material = context.createObject(Material::typeDescription.typeName, "Material"); - context.set(raco::core::ValueHandle{mesh, {"uri"}}, (cwd_path() / "meshes/Duck.glb").string()); + context.set(raco::core::ValueHandle{mesh, {"uri"}}, (test_path() / "meshes/Duck.glb").string()); context.moveScenegraphChildren({meshNode}, node); context.set(raco::core::ValueHandle{meshNode, {"mesh"}}, mesh); @@ -369,12 +369,12 @@ struct SceneContextParamTestFixture : public RamsesBaseFixture<::testing::TestWi } }; - std::filesystem::path cwd_path() const override { + raco::utils::u8path test_path() const override { std::string testCaseName{::testing::UnitTest::GetInstance()->current_test_info()->name()}; testCaseName = testCaseName.substr(0, testCaseName.find("#GetParam()")); std::replace(testCaseName.begin(), testCaseName.end(), '/', '\\'); - auto result(std::filesystem::current_path() / testCaseName); + auto result(raco::utils::u8path::current() / testCaseName); return result; } }; @@ -392,7 +392,7 @@ TEST_P(SceneContextParamTestFixture, contextCreationOrder_init) { auto mesh = objects.at(Mesh::typeDescription.typeName); auto material = objects.at(Material::typeDescription.typeName); - context.set(raco::core::ValueHandle{mesh, {"uri"}}, (cwd_path() / "meshes/Duck.glb").string()); + context.set(raco::core::ValueHandle{mesh, {"uri"}}, (test_path() / "meshes/Duck.glb").string()); context.moveScenegraphChildren({meshNode}, node); context.set(raco::core::ValueHandle{meshNode, {"mesh"}}, mesh); @@ -415,7 +415,7 @@ TEST_P(SceneContextParamTestFixture, contextCreationOrder_dispatch) { auto mesh = objects.at(Mesh::typeDescription.typeName); auto material = objects.at(Material::typeDescription.typeName); - context.set(raco::core::ValueHandle{mesh, {"uri"}}, (cwd_path() / "meshes/Duck.glb").string()); + context.set(raco::core::ValueHandle{mesh, {"uri"}}, (test_path() / "meshes/Duck.glb").string()); context.moveScenegraphChildren({meshNode}, node); context.set(raco::core::ValueHandle{meshNode, {"mesh"}}, mesh); diff --git a/datamodel/libCore/CMakeLists.txt b/datamodel/libCore/CMakeLists.txt index e0be0a00..f72a7056 100644 --- a/datamodel/libCore/CMakeLists.txt +++ b/datamodel/libCore/CMakeLists.txt @@ -25,7 +25,7 @@ add_library(libCore include/core/Queries_Tags.h src/Queries_Tags.cpp include/core/Consistency.h src/Consistency.cpp include/core/MeshCacheInterface.h - include/core/UserObjectFactoryInterface.h src/UserObjectFactoryInterface.cpp + include/core/UserObjectFactoryInterface.h include/core/Iterators.h src/Iterators.cpp include/core/Undo.h src/Undo.cpp include/core/EngineInterface.h @@ -38,9 +38,12 @@ add_library(libCore include/core/ExternalReferenceAnnotation.h include/core/ExtrefOperations.h src/ExtrefOperations.cpp include/core/SceneBackendInterface.h - include/core/RamsesProjectMigration.h src/RamsesProjectMigration.cpp + include/core/ProjectMigrationToV23.h src/ProjectMigrationToV23.cpp + include/core/ProjectMigration.h src/ProjectMigration.cpp + include/core/DynamicEditorObject.h + include/core/ProxyTypes.h src/ProxyTypes.cpp + include/core/ProxyObjectFactory.h src/ProxyObjectFactory.cpp include/core/Serialization.h src/Serialization.cpp - include/core/SerializationFunctions.h include/core/SerializationKeys.h include/core/CoreAnnotations.h @@ -67,6 +70,10 @@ PRIVATE raco::UserTypes ) +IF (MSVC) + # /bigobj is needed to compile UserObjectFactory.cpp + target_compile_options(libCore PRIVATE "/bigobj") +endif() add_library(raco::Core ALIAS libCore) diff --git a/datamodel/libCore/include/core/ChangeRecorder.h b/datamodel/libCore/include/core/ChangeRecorder.h index aa400474..9a8af921 100644 --- a/datamodel/libCore/include/core/ChangeRecorder.h +++ b/datamodel/libCore/include/core/ChangeRecorder.h @@ -107,7 +107,7 @@ class DataChangeRecorder : public DataChangeRecorderInterface { bool eraseLink(const LinkDescriptor& link); // Iterate through the saved links and insert the link end point objects, depending on which should be included, in the "objects" set. - void insertLinkEndPointObjects(bool includeLinkStart, bool includeLinkEnd, SEditorObjectSet& objects) const; + void insertLinkEndPointObjects(bool includeLinkStart, bool includeLinkEnd, SEditorObjectSet& objects, const SEditorObjectSet& excludeObjects) const; // Inserts a link or updates it when it is already contained in the linkMap_. void insertOrUpdateLink(const LinkDescriptor& link); diff --git a/datamodel/libCore/include/core/CommandInterface.h b/datamodel/libCore/include/core/CommandInterface.h index 7848c39f..81a04dba 100644 --- a/datamodel/libCore/include/core/CommandInterface.h +++ b/datamodel/libCore/include/core/CommandInterface.h @@ -15,10 +15,6 @@ #include #include -namespace raco::serialization { -struct DeserializationFactory; -} - namespace raco::core { struct MeshDescriptor; struct MeshScenegraph; @@ -52,6 +48,12 @@ class CommandInterface { void set(ValueHandle const& handle, std::vector const& value); void set(ValueHandle const& handle, Table const& value); void set(ValueHandle const& handle, SEditorObject const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); // Object creation/deletion SEditorObject createObject(std::string type, std::string name = std::string(), std::string id = std::string(), SEditorObject parent = nullptr); diff --git a/datamodel/libCore/include/core/Context.h b/datamodel/libCore/include/core/Context.h index 12a5b4a6..b2ecb824 100644 --- a/datamodel/libCore/include/core/Context.h +++ b/datamodel/libCore/include/core/Context.h @@ -16,7 +16,6 @@ #include "Link.h" namespace raco::serialization { -struct DeserializationFactory; struct ObjectsDeserialization; } // namespace raco::serialization @@ -68,6 +67,15 @@ class BaseContext { void set(ValueHandle const& handle, std::vector const& value); void set(ValueHandle const& handle, SEditorObject const& value); void set(ValueHandle const& handle, Table const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + void set(ValueHandle const& handle, std::array const& value); + // Set struct property to struct value. + // Identical types are dynamically enforced at runtime. + void set(ValueHandle const& handle, ClassWithReflectedMembers const& value); template void set(AnnotationValueHandle const& handle, T const& value); @@ -82,6 +90,8 @@ class BaseContext { // Remove all properties from Table void removeAllProperties(const ValueHandle &handle); + void swapProperties(const ValueHandle& handle, size_t index_1, size_t index_2); + // Object creation/deletion SEditorObject createObject(std::string type, std::string name = std::string(), std::string id = std::string()); @@ -149,7 +159,7 @@ class BaseContext { // Find and return the objects without parents within the serialized object set. // Note: the objects may still have parents in the origin project they were copied from. - static std::vector getTopLevelObjectsFromDeserializedObjects(serialization::ObjectsDeserialization& deserialization, UserObjectFactoryInterface* objectFactory, Project* project); + static std::vector getTopLevelObjectsFromDeserializedObjects(serialization::ObjectsDeserialization& deserialization, Project* project); // Initialize link validity flag during load. This does not update broken link errors and does not // generated change recorder entries. Use only during load to fix corrupt files. @@ -157,6 +167,7 @@ class BaseContext { private: friend class UndoStack; + friend class UndoHelpers; friend class FileChangeCallback; friend class PrefabOperations; friend class ExtrefOperations; @@ -174,7 +185,7 @@ class BaseContext { void removeReferencesTo(SEditorObjectSet const& objects); template - void callReferenceToThisHandlerForAllTableEntries(ValueHandle const& vh); + static void callReferenceToThisHandlerForAllTableEntries(ValueHandle const& vh); ValueTreeIterator erase(const ValueTreeIterator& it); diff --git a/datamodel/libCore/include/core/CoreAnnotations.h b/datamodel/libCore/include/core/CoreAnnotations.h index ef03e85d..133e947b 100644 --- a/datamodel/libCore/include/core/CoreAnnotations.h +++ b/datamodel/libCore/include/core/CoreAnnotations.h @@ -17,6 +17,10 @@ namespace raco::core { class URIAnnotation : public raco::data_storage::AnnotationBase { public: static inline const TypeDescriptor typeDescription = { "URIAnnotation", false }; + + // Filter for relative directory URIs that stay the same relative to the project (and do not get re-rooted), when the project file path changes. + static inline const std::string projectSubdirectoryFilter {"projectSubDir"}; + TypeDescriptor const& getTypeDescription() const override { return typeDescription; } @@ -40,6 +44,10 @@ class URIAnnotation : public raco::data_storage::AnnotationBase { return *filter_; } + bool isProjectSubdirectoryURI() const { + return filter_.asString() == projectSubdirectoryFilter; + } + raco::data_storage::Value filter_; }; diff --git a/datamodel/libCore/include/core/DynamicEditorObject.h b/datamodel/libCore/include/core/DynamicEditorObject.h new file mode 100644 index 00000000..a1674c50 --- /dev/null +++ b/datamodel/libCore/include/core/DynamicEditorObject.h @@ -0,0 +1,102 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "data_storage/Value.h" + +#include "core/EditorObject.h" + +#include +#include + +namespace raco::serialization::proxy { + +using raco::data_storage::ValueBase; + +class DynamicPropertyInterface { +public: + virtual ValueBase* addProperty(std::string const& name, raco::data_storage::PrimitiveType type) = 0; + virtual ValueBase* addProperty(std::string const& name, ValueBase* property, int index_before = -1) = 0; + virtual ValueBase* addProperty(std::string const& name, std::unique_ptr&& property, int index_before) = 0; + + virtual void removeProperty(std::string const& propertyName) = 0; + virtual std::unique_ptr extractProperty(std::string const& propertyName) = 0; +}; + +template +class DynamicPropertyMixin : public DynamicPropertyInterface { +public: + raco::data_storage::ValueBase* addProperty(std::string const& name, raco::data_storage::PrimitiveType type) override { + T& asT = static_cast(*this); + assert(asT.get(name) == nullptr); + auto prop = dynamicProperties_.addProperty(name, type); + asT.properties_.emplace_back(name, prop); + return prop; + } + + ValueBase* addProperty(std::string const& name, ValueBase* property, int index_before) override { + T& asT = static_cast(*this); + assert(asT.get(name) == nullptr); + auto prop = dynamicProperties_.addProperty(name, property, index_before); + asT.properties_.emplace_back(name, prop); + return prop; + } + + ValueBase* addProperty(std::string const& name, std::unique_ptr&& property, int index_before) override { + T& asT = static_cast(*this); + assert(asT.get(name) == nullptr); + auto prop = dynamicProperties_.addProperty(name, std::move(property), index_before); + asT.properties_.emplace_back(name, prop); + return prop; + } + + void removeProperty(std::string const& propertyName) override { + T& asT = static_cast(*this); + assert(asT.get(propertyName) != nullptr); + dynamicProperties_.removeProperty(propertyName); + auto it = std::find_if(asT.properties_.begin(), asT.properties_.end(), + [&propertyName](auto const& item) { + return item.first == propertyName; + }); + if (it != asT.properties_.end()) { + asT.properties_.erase(it); + } + } + + std::unique_ptr extractProperty(std::string const& propertyName) override { + T& asT = static_cast(*this); + auto clonedProp = asT.get(propertyName)->clone({}); + removeProperty(propertyName); + return clonedProp; + } + +protected: + raco::data_storage::Table dynamicProperties_; +}; + +class DynamicEditorObject : public raco::core::EditorObject, public DynamicPropertyMixin { +public: + DynamicEditorObject(std::string name = std::string(), std::string id = std::string()) + : EditorObject(name, id) { + } + + friend class DynamicPropertyMixin; +}; + +using SDynamicEditorObject = std::shared_ptr; + +class DynamicGenericStruct : public raco::data_storage::ClassWithReflectedMembers, public DynamicPropertyMixin { +public: + DynamicGenericStruct() : ClassWithReflectedMembers() {} + + friend class DynamicPropertyMixin; +}; + +} // namespace raco::serialization::proxy diff --git a/datamodel/libCore/include/core/EditorObject.h b/datamodel/libCore/include/core/EditorObject.h index cd045861..ef5fbf03 100644 --- a/datamodel/libCore/include/core/EditorObject.h +++ b/datamodel/libCore/include/core/EditorObject.h @@ -172,6 +172,8 @@ class EditorObject : public ClassWithReflectedMembers, public std::enable_shared static std::string normalizedObjectID(std::string const& id); + static std::string XorObjectIDs(std::string const& id1, std::string const& id2); + void fillPropertyDescription() { properties_.emplace_back("objectID", &objectID_); properties_.emplace_back("objectName", &objectName_); diff --git a/datamodel/libCore/include/core/Handles.h b/datamodel/libCore/include/core/Handles.h index 300cc72b..4abf554a 100644 --- a/datamodel/libCore/include/core/Handles.h +++ b/datamodel/libCore/include/core/Handles.h @@ -10,6 +10,7 @@ #pragma once #include "data_storage/Value.h" +#include "data_storage/BasicTypes.h" #include "core/PropertyDescriptor.h" #include @@ -128,7 +129,14 @@ class ValueHandle { int asInt() const; double asDouble() const; std::string asString() const; - SEditorObject asRef() const; + SEditorObject asRef() const; + const Vec2f& asVec2f() const; + const Vec3f& asVec3f() const; + const Vec4f& asVec4f() const; + const Vec2i& asVec2i() const; + const Vec3i& asVec3i() const; + const Vec4i& asVec4i() const; + template std::shared_ptr asTypedRef() const { diff --git a/datamodel/libCore/include/core/PathManager.h b/datamodel/libCore/include/core/PathManager.h index 8fef7911..6f621554 100644 --- a/datamodel/libCore/include/core/PathManager.h +++ b/datamodel/libCore/include/core/PathManager.h @@ -11,24 +11,31 @@ #include "utils/stdfilesystem.h" #include "data_storage/ReflectionInterface.h" +#include "utils/u8path.h" #include #include +#include namespace raco::components { class RaCoPreferences; } namespace raco::core { + struct PathManager { + using u8path = raco::utils::u8path; + static constexpr const char* DEFAULT_FILENAME = "Unnamed.rca"; - static constexpr const char* LOG_FILE_NAME = "RamsesComposer.log"; static constexpr const char* Q_LAYOUT_FILE_NAME = "layout.ini"; static constexpr const char* Q_PREFERENCES_FILE_NAME = "preferences.ini"; static constexpr const char* Q_RECENT_FILES_STORE_NAME = "recent_files.ini"; - static constexpr const char* DEFAULT_CONFIG_SUB_DIRECTORY = "configfiles"; + static constexpr const char* LOG_SUB_DIRECTORY = "logs"; + static constexpr const char* LEGACY_CONFIG_SUB_DIRECTORY = "configfiles"; static constexpr const char* DEFAULT_PROJECT_SUB_DIRECTORY = "projects"; + static constexpr const char* LOG_FILE_EDITOR_NAME = "RamsesComposer.log"; + static constexpr const char* LOG_FILE_HEADLESS_NAME = "RaCoHeadless.log"; enum class FolderTypeKeys { Invalid = 0, @@ -39,63 +46,59 @@ struct PathManager { Shader }; - static std::filesystem::path normal_path(const std::string& path); + static u8path legacyConfigDirectory(); - static void init(const std::string& executableDirectory); + static void init(const u8path& executableDirectory, const u8path& appDataDirectory); - static std::filesystem::path defaultBaseDirectory(); + static u8path defaultBaseDirectory(); - static std::string defaultConfigDirectory(); + static u8path logFileDirectory(); - static std::filesystem::path defaultResourceDirectory(); + static u8path defaultConfigDirectory(); - static std::string defaultProjectFallbackPath(); + static u8path defaultResourceDirectory(); - static std::string logFilePath(); + static u8path defaultProjectFallbackPath(); - static std::string layoutFilePath(); + static u8path logFilePath(); - static std::string recentFilesStorePath(); + static u8path logFileHeadlessName(); - static std::string preferenceFileLocation(); + static u8path logFileEditorName(); - static std::string constructRelativePath(const std::string& absolutePath, const std::string& basePath); - - // Construct absolute paths from base directory and relative or absolute file path. - // Absolute file paths are returned as is. - // Relative file paths are interpreted as relative to the dirPath argument. - static std::string constructAbsolutePath(const std::string& dirPath, const std::string& filePath); + static void migrateLegacyConfigDirectory(); + + static u8path layoutFilePath(); - static std::string rerootRelativePath(const std::string& relativePath, const std::string& oldPath, const std::string& newPath); + static u8path recentFilesStorePath(); - static std::string sanitizePath(const std::string& path); + static u8path preferenceFilePath(); + + static QSettings layoutSettings(); - static bool pathsShareSameRoot(const std::string& lhd, const std::string& rhd); + static QSettings recentFilesStoreSettings(); - static const std::string& getCachedPath(FolderTypeKeys Rekey, const std::string& fallbackPath = ""); + static QSettings preferenceSettings(); - static void setCachedPath(FolderTypeKeys key, const std::string& path); + static const u8path& getCachedPath(FolderTypeKeys key, const u8path& fallbackPath = {}); - static void setAllCachedPathRoots(const std::string& folder, - const std::string& imageSubdirectory, - const std::string& MeshSubdirectory, - const std::string& ScriptSubdirectory, - const std::string& ShaderSubdirectory); + static void setCachedPath(FolderTypeKeys key, const u8path& path); static FolderTypeKeys getCachedPathKeyCorrespondingToUserType(const raco::data_storage::ReflectionInterface::TypeDescriptor& type); private: friend class raco::components::RaCoPreferences; - static std::filesystem::path basePath_; + static u8path basePath_; + static u8path appDataBasePath_; // The default values for the subdirectories are set in RaCoPreferences::load - static inline std::map cachedPaths_ = { - {FolderTypeKeys::Project, ""}, - {FolderTypeKeys::Image, ""}, - {FolderTypeKeys::Mesh, ""}, - {FolderTypeKeys::Script, ""}, - {FolderTypeKeys::Shader, ""} + static inline std::map cachedPaths_ = { + {FolderTypeKeys::Project, {}}, + {FolderTypeKeys::Image, {}}, + {FolderTypeKeys::Mesh, {}}, + {FolderTypeKeys::Script, {}}, + {FolderTypeKeys::Shader, {}} }; }; diff --git a/datamodel/libCore/include/core/PrefabOperations.h b/datamodel/libCore/include/core/PrefabOperations.h index e6a3ac5d..332df48e 100644 --- a/datamodel/libCore/include/core/PrefabOperations.h +++ b/datamodel/libCore/include/core/PrefabOperations.h @@ -29,7 +29,7 @@ using SEditorObject = std::shared_ptr; class PrefabOperations { public: - static void globalPrefabUpdate(BaseContext& context, DataChangeRecorder& changes); + static void globalPrefabUpdate(BaseContext& context, DataChangeRecorder& changes, bool propagateMissingInterfaceProperties = false); static raco::user_types::SPrefabInstance findContainingPrefabInstance(SEditorObject object); @@ -38,7 +38,7 @@ class PrefabOperations { static void prefabUpdateOrderDepthFirstSearch(raco::user_types::SPrefab current, std::vector& order); private: - static void updatePrefabInstance(BaseContext& context, const raco::user_types::SPrefab& prefab, raco::user_types::SPrefabInstance instance, bool instanceDirty); + static void updatePrefabInstance(BaseContext& context, const raco::user_types::SPrefab& prefab, raco::user_types::SPrefabInstance instance, bool instanceDirty, bool propagateMissingInterfaceProperties); }; } // namespace raco::core \ No newline at end of file diff --git a/datamodel/libCore/include/core/RamsesProjectMigration.h b/datamodel/libCore/include/core/ProjectMigration.h similarity index 85% rename from datamodel/libCore/include/core/RamsesProjectMigration.h rename to datamodel/libCore/include/core/ProjectMigration.h index 1b98b3c3..169585f1 100644 --- a/datamodel/libCore/include/core/RamsesProjectMigration.h +++ b/datamodel/libCore/include/core/ProjectMigration.h @@ -9,11 +9,9 @@ */ #pragma once -#include -#include -#include +#include "core/Serialization.h" -namespace raco::core { +namespace raco::serialization { /** * History of versions: * 1: Initial @@ -51,7 +49,12 @@ namespace raco::core { * Links from Vec4f to Node::rotation are now allowed * 20: Added LuaScriptModule type as well as basic Lua module support * 21: Added mipmap flag to textures. + * 22: Added support for setting default resource folders per project + * 23: Serialization changes to support new-style migration code + * 24: Deterministics object IDs for PrefabInstance child objects */ -constexpr int RAMSES_PROJECT_FILE_VERSION = 21; -QJsonDocument migrateProject(const QJsonDocument& doc, std::unordered_map& migrationWarnings); -} // namespace raco::core +constexpr int RAMSES_PROJECT_FILE_VERSION = 24; + +void migrateProject(ProjectDeserializationInfoIR& deserializedIR); + +} // namespace raco::serialization diff --git a/utils/libUtils/include/utils/PathUtils.h b/datamodel/libCore/include/core/ProjectMigrationToV23.h similarity index 59% rename from utils/libUtils/include/utils/PathUtils.h rename to datamodel/libCore/include/core/ProjectMigrationToV23.h index 148be6b4..20d22a2f 100644 --- a/utils/libUtils/include/utils/PathUtils.h +++ b/datamodel/libCore/include/core/ProjectMigrationToV23.h @@ -9,16 +9,10 @@ */ #pragma once +#include +#include #include -namespace raco::utils::path { - -bool exists(const std::string& path); - -bool userHasReadAccess(const std::string& path); - -bool isExistingDirectory(const std::string& path); - -bool isExistingFile(const std::string& path); - -} // namespace raco::utils::path +namespace raco::serializationToV23 { +QJsonDocument migrateProjectToV23(const QJsonDocument& doc); +} // namespace raco::core diff --git a/datamodel/libCore/include/core/ProjectSettings.h b/datamodel/libCore/include/core/ProjectSettings.h index c7e8a6c8..3be52631 100644 --- a/datamodel/libCore/include/core/ProjectSettings.h +++ b/datamodel/libCore/include/core/ProjectSettings.h @@ -16,6 +16,59 @@ namespace raco::core { class ProjectSettings : public EditorObject { public: + class DefaultResourceDirectories : public ClassWithReflectedMembers { + public: + static inline const TypeDescriptor typeDescription = {"DefaultResourceDirectories", false}; + + TypeDescriptor const& getTypeDescription() const override { + return typeDescription; + } + + bool serializationRequired() const override { + return true; + } + + DefaultResourceDirectories(const DefaultResourceDirectories& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(), + imageSubdirectory_(other.imageSubdirectory_), + meshSubdirectory_(other.meshSubdirectory_), + scriptSubdirectory_(other.scriptSubdirectory_), + shaderSubdirectory_(other.shaderSubdirectory_) { + fillPropertyDescription(); + } + + DefaultResourceDirectories() : ClassWithReflectedMembers() { + fillPropertyDescription(); + } + + void fillPropertyDescription() { + properties_.emplace_back("imageSubdirectory", &imageSubdirectory_); + properties_.emplace_back("meshSubdirectory", &meshSubdirectory_); + properties_.emplace_back("scriptSubdirectory", &scriptSubdirectory_); + properties_.emplace_back("shaderSubdirectory", &shaderSubdirectory_); + } + + DefaultResourceDirectories& operator=(const DefaultResourceDirectories& other) { + imageSubdirectory_ = other.imageSubdirectory_; + meshSubdirectory_ = other.meshSubdirectory_; + scriptSubdirectory_ = other.scriptSubdirectory_; + shaderSubdirectory_ = other.shaderSubdirectory_; + return *this; + } + + void copyAnnotationData(const DefaultResourceDirectories& other) { + imageSubdirectory_.copyAnnotationData(other.imageSubdirectory_); + meshSubdirectory_.copyAnnotationData(other.meshSubdirectory_); + scriptSubdirectory_.copyAnnotationData(other.scriptSubdirectory_); + shaderSubdirectory_.copyAnnotationData(other.shaderSubdirectory_); + } + public: + Property imageSubdirectory_{{"images"}, {"Images"}, {URIAnnotation::projectSubdirectoryFilter}}; + Property meshSubdirectory_{{"meshes"}, {"Meshes"}, {URIAnnotation::projectSubdirectoryFilter}}; + Property scriptSubdirectory_{{"scripts"}, {"Scripts"}, {URIAnnotation::projectSubdirectoryFilter}}; + Property shaderSubdirectory_{{"shaders"}, {"Shaders"}, {URIAnnotation::projectSubdirectoryFilter}}; + }; + + static inline const TypeDescriptor typeDescription{"ProjectSettings", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; @@ -36,6 +89,7 @@ class ProjectSettings : public EditorObject { properties_.emplace_back("sceneId", &sceneId_); properties_.emplace_back("viewport", &viewport_); properties_.emplace_back("backgroundColor", &backgroundColor_); + properties_.emplace_back("defaultResourceFolders", &defaultResourceDirectories_); properties_.emplace_back("enableTimerFlag", &enableTimerFlag_); properties_.emplace_back("runTimer", &runTimer_); } @@ -44,6 +98,8 @@ class ProjectSettings : public EditorObject { Property viewport_{{{1440, 720}, 0, 4096}, {"Display Size"}}; Property backgroundColor_{{}, {"Display Background Color"}}; + Property defaultResourceDirectories_{{}, {"Default Resource Folders"}}; + // Properties related to timer running hack - remove these properties and all related code when proper animations have been implemented Property enableTimerFlag_{false, HiddenProperty()}; Property runTimer_{false, HiddenProperty()}; diff --git a/datamodel/libCore/include/core/ProxyObjectFactory.h b/datamodel/libCore/include/core/ProxyObjectFactory.h new file mode 100644 index 00000000..915b461d --- /dev/null +++ b/datamodel/libCore/include/core/ProxyObjectFactory.h @@ -0,0 +1,232 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "core/UserObjectFactoryInterface.h" +#include "core/ProxyTypes.h" + +#include "core/Link.h" +#include "user_types/EngineTypeAnnotation.h" + +namespace raco::serialization::proxy { + +using namespace raco::data_storage; + +using raco::user_types::EngineTypeAnnotation; +using raco::core::LinkStartAnnotation; +using raco::core::LinkEndAnnotation; +using raco::core::URIAnnotation; +using raco::core::EnumerationAnnotation; +using raco::core::ArraySemanticAnnotation; +using raco::core::HiddenProperty; +using raco::core::TagContainerAnnotation; +using raco::core::ExpectEmptyReference; +using raco::core::RenderableTagContainerAnnotation; + + +template +struct tuple_has_type {}; + +template +struct tuple_has_type> : std::disjunction...> { +}; + + +class ProxyObjectFactory : public raco::core::UserObjectFactoryInterface { +public: + // The ProxyObjectFactory creates all objects and properties that are deserialized. + // These are then further used as input to the migration code. + // After migration the proxy objects are converted to the real user types by the deserialization. + // Those are created using the UserObjectFactory. + // + // Please note: + // - The types used here are the proxy types from ProxyTypes.h and live in the namespace + // raco::serialization::proxy. They are therefore different types than the normally used types + // even if the class names are identical. + // - The object and property type maps initialized here must contain all types and properties + // appearing in old files to allow them to be deserialized before migration. This includes + // object and property types not used anymore. + + // Master list of all Property types that can be deserialized. + // Contains + // - dynamic properties = all properties that can occur in Tables. + // - static properties = properties declared as member variables EditorObject-derived classes + // Value does not appear here, even for pointer types derived from EditorObject. + using PropertyTypeMapType = std::tuple< + Property, + Property>, + + Property, + Property, + Property, + Property, + + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + Property, + + // EditorObject + Property, + Property, + Property, + + // Node + Property, + Property, + Property, + + // MeshNode + Property, + Property, + Property>, + + // ProjectSettings + Property, + Property, + Property, + Property, + Property, + Property, + + // BaseCamera + Property, + Property, + + // CameraViewport + Property, DisplayNameAnnotation, LinkEndAnnotation>, + + // PerspectiveCamera + Property, + + // PerspectiveFrustum + Property, LinkEndAnnotation>, + + // OrthographicCamera + Property, + + // RenderBuffer + Property, DisplayNameAnnotation>, + + // RenderPass + Property, + Property, + Property, + Property, + Property, + + // RenderLayer + Property, + Property, + Property, + + // RenderTarget + Property, + Property, + + // Texture + Property, + + // PrefabInstance + Property>; + + static ProxyObjectFactory& getInstance(); + + virtual SEditorObject createObject(const std::string& type, const std::string& name = std::string(), const std::string& id = std::string()) const override; + virtual data_storage::ValueBase* createValue(const std::string& type) const override; + + const std::map& getTypes() const override; + bool isUserCreatable(const std::string& type) const override; + + const std::map& getProperties() const; + + + std::shared_ptr createAnnotation(const std::string& type) const override; + + template + static ValueBase* staticCreateProperty(T defaultT, Args... params) { + static_assert(tuple_has_type, PropertyTypeMapType>::value == true); + return new Property(defaultT, params...); + } + +private: + ProxyObjectFactory(); + + using AnnotationCreationFunction = std::function()>; + + struct AnnotationDescriptor { + ReflectionInterface::TypeDescriptor description; + AnnotationCreationFunction createFunc; + }; + + template + static SEditorObject createObjectInternal(const std::string& name, const std::string& id); + template + static std::shared_ptr createAnnotationInternal(); + template + static data_storage::ValueBase* createValueInternal(); + + template + std::map makeTypeMap(); + template + std::map makePropertyMapTuple(std::tuple* dummy); + template + std::map makeAnnotationMap(); + + std::map types_; + // Annotations that can be dynmically added to / removed from ClassWithReflectedMembers + std::map annotations_; + std::map properties_; +}; + +} // namespace raco::serialization::proxy diff --git a/datamodel/libCore/include/core/ProxyTypes.h b/datamodel/libCore/include/core/ProxyTypes.h new file mode 100644 index 00000000..86e98fdf --- /dev/null +++ b/datamodel/libCore/include/core/ProxyTypes.h @@ -0,0 +1,163 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "core/DynamicEditorObject.h" + +namespace raco::serialization::proxy { + +using namespace raco::data_storage; + +template +class Proxy : public Base { +public: + Proxy(std::string name = std::string(), std::string id = std::string()) + : Base(name, id) { + } + + static inline const ReflectionInterface::TypeDescriptor typeDescription = {Name, true}; + ReflectionInterface::TypeDescriptor const& getTypeDescription() const override { + return typeDescription; + } +}; + +extern const char projectSettingsTypeName[]; +using ProjectSettings = Proxy; +using SProjectSettings = std::shared_ptr; + +extern const char meshTypeName[]; +using Mesh = Proxy; +using SMesh = std::shared_ptr; + +extern const char nodeTypeName[]; +using Node = Proxy; +using SNode = std::shared_ptr; + +extern const char meshNodeTypeName[]; +using MeshNode = Proxy; +using SMeshNode = std::shared_ptr; + +extern const char materialTypeName[]; +using Material = Proxy; +using SMaterial = std::shared_ptr; + +extern const char luaScriptTypeName[]; +using LuaScript = Proxy; +using SLuaScript = std::shared_ptr; + +extern const char luaScriptModuleTypeName[]; +using LuaScriptModule = Proxy; +using SLuaScriptModule = std::shared_ptr; + +extern const char animationTypeName[]; +using Animation = Proxy; +using SAnimation = std::shared_ptr; + +extern const char animationChannelTypeName[]; +using AnimationChannel = Proxy; +using SAnimationChannel = std::shared_ptr; + +extern const char textureSampler2DBaseTypeName[]; +using TextureSampler2DBase = Proxy; +using STextureSampler2DBase = std::shared_ptr; + +extern const char textureTypeName[]; +using Texture = Proxy; +using STexture = std::shared_ptr; + +// BaseTexture -> Texture +// BaseTexture -> TextureSampler2DBase -> Texture +// -> RenderBuffer + +extern const char cubeMapTypeName[]; +using CubeMap = Proxy; +using SCubeMap = std::shared_ptr; + +extern const char baseCameraTypeName[]; +using BaseCamera = Proxy; +using SBaseCamera = std::shared_ptr; + +extern const char perspectiveCameraTypeName[]; +using PerspectiveCamera = Proxy; +using SPerspectiveCamera = std::shared_ptr; + +extern const char orthographicCameraTypeName[]; +using OrthographicCamera = Proxy; +using SOrthographicCamera = std::shared_ptr; + +extern const char renderBufferTypeName[]; +using RenderBuffer = Proxy; +using SRenderBuffer = std::shared_ptr; + +extern const char renderLayerTypeName[]; +using RenderLayer = Proxy; +using SRenderLayer = std::shared_ptr; + +extern const char renderPassTypeName[]; +using RenderPass = Proxy; +using SRenderPass = std::shared_ptr; + +extern const char renderTargetTypeName[]; +using RenderTarget = Proxy; +using SRenderTarget = std::shared_ptr; + +extern const char prefabTypeName[]; +using Prefab = Proxy; +using SPrefab = std::shared_ptr; + +extern const char prefabInstanceTypeName[]; +using PrefabInstance = Proxy; +using SPrefabInstance = std::shared_ptr; + +template +class StructProxy : public DynamicGenericStruct { +public: + static inline const TypeDescriptor typeDescription = {Name, true}; + TypeDescriptor const& getTypeDescription() const override { + return typeDescription; + } + bool serializationRequired() const override { + return true; + } + + StructProxy() : DynamicGenericStruct() {} + + StructProxy(const StructProxy& other, std::function* translateRef = nullptr) { + + } + + StructProxy& operator=(const StructProxy& other) { + dynamicProperties_ = other.dynamicProperties_; + return *this; + } + + void copyAnnotationData(const StructProxy& other) { + for (size_t i = 0; i < dynamicProperties_.size(); i++) { + get(i)->copyAnnotationData(*other.get(i)); + } + } +}; + +extern const char blendmodeOptionsTypeName[]; +using BlendOptions = StructProxy; + +extern const char cameraViewportTypeName[]; +using CameraViewport = StructProxy; + +extern const char perspectiveFrustumTypeName[]; +using PerspectiveFrustum = StructProxy; + +extern const char orthographicFrustumTypeName[]; +using OrthographicFrustum = StructProxy; + +extern const char defaultResourceDirectoriesTypeName[]; +using DefaultResourceDirectories = StructProxy; + +} // namespace raco::serialization::proxy diff --git a/datamodel/libCore/include/core/Queries.h b/datamodel/libCore/include/core/Queries.h index 4dbe0d71..3d0fe20f 100644 --- a/datamodel/libCore/include/core/Queries.h +++ b/datamodel/libCore/include/core/Queries.h @@ -44,9 +44,11 @@ namespace Queries { SEditorObjectSet collectAllChildren(std::vector baseObjects); - std::vector filterForVisibleObjects(const std::vector& objects); std::vector filterForNotResource(const std::vector& objects); std::vector filterByTypeName(const std::vector& objects, const std::vector& typeNames); + std::vector filterForTopLevelObjectsByTypeName(const std::vector& objects, const std::vector& typeNames); + std::vector filterForVisibleTopLevelObjects(const std::vector& objects); + std::vector filterForDeleteableObjects(Project const& project, const std::vector& objects); std::vector filterForMoveableScenegraphChildren(Project const& project, const std::vector& objects, SEditorObject const& newParent); diff --git a/datamodel/libCore/include/core/Serialization.h b/datamodel/libCore/include/core/Serialization.h index f946b2f1..3ab56a57 100644 --- a/datamodel/libCore/include/core/Serialization.h +++ b/datamodel/libCore/include/core/Serialization.h @@ -9,10 +9,10 @@ */ #pragma once +#include "core/CoreAnnotations.h" #include "data_storage/BasicAnnotations.h" #include "data_storage/BasicTypes.h" #include "data_storage/Value.h" -#include "core/CoreAnnotations.h" #include #include @@ -23,11 +23,23 @@ #include #include +namespace raco::core { +class EditorObject; +using SEditorObject = std::shared_ptr; +class Link; +using SLink = std::shared_ptr; +} // namespace raco::core + +namespace raco::serialization::proxy { +class DynamicEditorObject; +using SDynamicEditorObject = std::shared_ptr; +} // namespace raco::serialization::proxy + namespace raco::serialization { +using raco::data_storage::ClassWithReflectedMembers; using raco::data_storage::PrimitiveType; using raco::data_storage::ReflectionInterface; -using raco::data_storage::ClassWithReflectedMembers; using raco::data_storage::ValueBase; using SReflectionInterface = std::shared_ptr; @@ -38,30 +50,20 @@ struct ExternalProjectInfo { bool operator==(const ExternalProjectInfo& lhs, const ExternalProjectInfo& rhs); -using ResolveReferencedId = std::function(const raco::data_storage::ValueBase& value)>; -std::string serializeObject(const SReflectionInterface& object, const std::string &projectPath, const ResolveReferencedId& resolveReferenceId); -std::string serializeObjects(const std::vector& objects, const std::vector& rootObjectIDs, const std::vector& links, const std::string& originFolder, const std::string& originFilename, const std::string& originProjectID, const std::string& originProjectName, const std::map& externalProjectsMap, const std::map& originFolders, const ResolveReferencedId& resolveReferenceId); -QJsonDocument serializeProject(const std::unordered_map>& fileVersions, const std::vector& instances, const std::vector& links, const std::map& externalProjectsMap, const ResolveReferencedId& resolveReferenceId); - -using UserTypeFactory = std::function(const std::string&)>; -using AnnotationFactory = std::function(const std::string&)>; -using ValueBaseFactory = std::function; -struct DeserializationFactory { - UserTypeFactory createUserType; - AnnotationFactory createAnnotation; - ValueBaseFactory createValueBase; -}; +std::string serializeObjects(const std::vector& objects, const std::vector& rootObjectIDs, const std::vector& links, const std::string& originFolder, const std::string& originFilename, const std::string& originProjectID, const std::string& originProjectName, const std::map& externalProjectsMap, const std::map& originFolders); + +QJsonDocument serializeProject(const std::unordered_map>& fileVersions, const std::vector& instances, const std::vector& links, const std::map& externalProjectsMap); using References = std::map; struct ObjectDeserialization { - SReflectionInterface object; + raco::core::SEditorObject object; References references; }; struct ObjectsDeserialization { - std::vector objects; + std::vector objects; // object ids of the top-level (no parents) objects std::set rootObjectIDs; - std::vector links; + std::vector links; References references; std::string originFolder; std::string originFileName; @@ -74,7 +76,7 @@ struct ObjectsDeserialization { // Contains base directory for relative paths for objects _not_ using the originFolder. // Maps object ID -> absolute folder. - // Needed because we can't figure out the correct path for non-extref objects which need to + // Needed because we can't figure out the correct path for non-extref objects which need to // use an external project path as origin folder. This concerns LuaScripts inside PrefabInstances // with an external reference Prefab when only the LuaScript but not the external reference // Prefab is included in the copied object set. @@ -85,37 +87,56 @@ struct DeserializedVersion { int major; int minor; int patch; +}; - void operator=(const QJsonArray& jsonArray) { - major = jsonArray[0].toInt(); - minor = jsonArray[1].toInt(); - patch = jsonArray[2].toInt(); - } +inline bool operator==(const DeserializedVersion& lhVersion, const DeserializedVersion& rhVersion) { + return lhVersion.major == rhVersion.major && lhVersion.minor == rhVersion.minor && lhVersion.patch == rhVersion.patch; }; -struct ProjectDeserializationInfo { +struct ProjectVersionInfo { DeserializedVersion raCoVersion; DeserializedVersion ramsesVersion; DeserializedVersion ramsesLogicEngineVersion; +}; + +template +struct GenericProjectDeserializationInfo { + int fileVersion = NO_VERSION; - ObjectsDeserialization objectsDeserialization; + ProjectVersionInfo versionInfo; + + std::vector objects; + std::vector links; + std::map externalProjectsMap; + + std::unordered_map migrationObjWarnings; + std::string currentPath; static constexpr int NO_VERSION = -1; }; -ObjectDeserialization deserializeObject(const std::string& json, const DeserializationFactory& factory); -ObjectsDeserialization deserializeObjects(const std::string& json, const DeserializationFactory& factory); +using ProjectDeserializationInfo = GenericProjectDeserializationInfo; +using ProjectDeserializationInfoIR = GenericProjectDeserializationInfo; + + int deserializeFileVersion(const QJsonDocument& document); -ProjectDeserializationInfo deserializeProjectVersionInfo(const QJsonDocument& document); -ProjectDeserializationInfo deserializeProject(const QJsonDocument& jsonDocument, const DeserializationFactory& factory); -ProjectDeserializationInfo deserializeProject(const std::string& json, const DeserializationFactory& factory); +ProjectVersionInfo deserializeProjectVersionInfo(const QJsonDocument& document); + +ObjectsDeserialization deserializeObjects(const std::string& json); + +ProjectDeserializationInfo deserializeProject(const QJsonDocument& jsonDocument, const std::string& filename); + +std::map> makeUserTypePropertyMap(); + +ProjectDeserializationInfoIR deserializeProjectToIR(const QJsonDocument& document, const std::string& filename); + +namespace test_helpers { + +std::string serializeObject(const SReflectionInterface& object, const std::string& projectPath = {}); -std::optional serializePropertyForMigration(const ValueBase& value, const ResolveReferencedId& resolveReferenceId, bool dynamicallyTyped); -References deserializePropertyForMigration(const QJsonValue& property, ValueBase& value, const DeserializationFactory& factory = {}); +ObjectDeserialization deserializeObject(const std::string& json); -SReflectionInterface deserializeTypedObject(const QJsonObject& jsonObject, const DeserializationFactory& factory, References& references); -QJsonObject serializeTypedObject(const ReflectionInterface& object, const ResolveReferencedId& resolveReferenceId); -void serializeExternalProjectsMap(QJsonObject& outContainer, const std::map& externalProjectsMap); +} // namespace test_helpers }; // namespace raco::serialization diff --git a/datamodel/libCore/include/core/SerializationFunctions.h b/datamodel/libCore/include/core/SerializationFunctions.h deleted file mode 100644 index e42155d3..00000000 --- a/datamodel/libCore/include/core/SerializationFunctions.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-License-Identifier: MPL-2.0 - * - * This file is part of Ramses Composer - * (see https://github.com/GENIVI/ramses-composer). - * - * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. - * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -#pragma once - -// Templated functions for de-/serialization for ease of use with EditorObject - -#include "Serialization.h" -#include "data_storage/Value.h" - -#include -#include - -namespace raco::serialization { - -// If you get a compile error for the function below, make sure that EditorObject.h is included before this file. -template -std::optional defaultIdResolver(const raco::data_storage::ValueBase& value) { - if (auto ref = value.asRef()) { - return ref->objectID(); - } else { - return {}; - } -} - -template -std::string serialize(const T& object, const std::string& projectPath = "", const ResolveReferencedId& resolveReferenceId = defaultIdResolver) { - return serializeObject(object, projectPath, resolveReferenceId); -} - -template -std::string serialize(const T& object, const std::vector& links, const std::string& projectPath = "", const ResolveReferencedId& resolveReferenceId = defaultIdResolver) { - return serializeObject(object, projectPath, resolveReferenceId); -} - -template -std::string serialize(const std::vector& objects, const std::vector& rootObjectIDs, const std::vector& links, const std::string& originFolder, const std::string& originFilename, const std::string& projectID, const std::string& originProjectName, const std::map& externalProjectsMap, const std::map& originFolders, const ResolveReferencedId& resolveReferenceId = defaultIdResolver) { - std::vector typeConversion{objects.begin(), objects.end()}; - return serializeObjects( - std::vector{objects.begin(), objects.end()}, - rootObjectIDs, - std::vector{links.begin(), links.end()}, - originFolder, originFilename, projectID, originProjectName, externalProjectsMap, originFolders, resolveReferenceId); -} - -} // namespace raco::serialization \ No newline at end of file diff --git a/datamodel/libCore/include/core/SerializationKeys.h b/datamodel/libCore/include/core/SerializationKeys.h index 3ffd19b9..1ee42b46 100644 --- a/datamodel/libCore/include/core/SerializationKeys.h +++ b/datamodel/libCore/include/core/SerializationKeys.h @@ -32,5 +32,7 @@ constexpr const char* EXTERNAL_PROJECT_PATH{"path"}; constexpr const char* EXTERNAL_PROJECT_NAME{"name"}; constexpr const char* ROOT_OBJECT_IDS{"rootObjectIDs"}; constexpr const char* OBJECT_ORIGIN_FOLDERS{"objectOriginFolders"}; +constexpr const char* USER_TYPE_PROP_MAP{"userTypePropMap"}; +constexpr const char* STRUCT_PROP_MAP{"structPropMap"}; } // namespace raco::serialization::keys diff --git a/datamodel/libCore/include/core/Undo.h b/datamodel/libCore/include/core/Undo.h index 2d01ff35..9282df9b 100644 --- a/datamodel/libCore/include/core/Undo.h +++ b/datamodel/libCore/include/core/Undo.h @@ -11,8 +11,8 @@ #include "core/Project.h" -#include #include +#include namespace raco::core { @@ -22,62 +22,73 @@ class DataChangeRecorder; class UserObjectFactoryInterface; using translateRefFunc = std::function; -using excludePropertyPredicateFunc = std::function; +using excludePropertyPredicateFunc = std::function; +class UndoHelpers { +public: + static void updateSingleValue(const ValueBase *src, ValueBase *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); -void updateSingleValue(const ValueBase *src, ValueBase *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); + static void callOnBeforeRemoveReferenceHandler(raco::data_storage::Table *dest, const size_t &index, raco::core::ValueHandle &destHandle); -void updateEditorObject(const EditorObject *src, SEditorObject dest, translateRefFunc translateRef, excludePropertyPredicateFunc excludeIf, UserObjectFactoryInterface &factory, DataChangeRecorder *outChanges, bool invokeHandler, bool updateObjectAnnotations = true); + static void updateEditorObject(const EditorObject *src, SEditorObject dest, translateRefFunc translateRef, excludePropertyPredicateFunc excludeIf, UserObjectFactoryInterface &factory, DataChangeRecorder *outChanges, bool invokeHandler, bool updateObjectAnnotations = true); + static void updateMissingTableProperties(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); + +private: + static void updateTableAsArray(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); + static void updateStruct(const ClassWithReflectedMembers *src, ClassWithReflectedMembers *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); + static void updateTableByName(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); +}; class UndoStack { public: - using Callback = std::function; + using Callback = std::function; - UndoStack(BaseContext *context, const Callback& onChange = []() {}); + UndoStack( + BaseContext *context, const Callback &onChange = []() {}); - // Add another undo stack entry. - void push(const std::string& description, std::string mergeId = std::string()); + // Add another undo stack entry. + void push(const std::string &description, std::string mergeId = std::string()); - // Number of entries on the undo stack - size_t size() const; - const std::string& description(size_t index) const; + // Number of entries on the undo stack + size_t size() const; + const std::string &description(size_t index) const; - // Get the current position of the undo stack pointer - size_t getIndex() const; + // Get the current position of the undo stack pointer + size_t getIndex() const; - // Jump backward or forward to any position in the undo stack. + // Jump backward or forward to any position in the undo stack. size_t setIndex(size_t newIndex, bool force = false); - // Go one entry backwards. - void undo(); + // Go one entry backwards. + void undo(); - // Go one entry forward. + // Go one entry forward. void redo(); - bool canUndo() const noexcept; - bool canRedo() const noexcept; + bool canUndo() const noexcept; + bool canRedo() const noexcept; void reset(); protected: - void saveProjectState(const Project *src, Project *dest, Project *ref, const DataChangeRecorder &changes, UserObjectFactoryInterface &factory); + void saveProjectState(const Project *src, Project *dest, Project *ref, const DataChangeRecorder &changes, UserObjectFactoryInterface &factory); void updateProjectState(const Project *src, Project *dest, const DataChangeRecorder &changes, UserObjectFactoryInterface &factory); - void restoreProjectState(Project *src, Project *dest, BaseContext &context, UserObjectFactoryInterface &factory); + void restoreProjectState(Project *src, Project *dest, BaseContext &context, UserObjectFactoryInterface &factory); bool canMerge(const DataChangeRecorder &changes); - BaseContext* context_; + BaseContext *context_; Callback onChange_; - struct Entry { + struct Entry { Entry(std::string description = std::string(), std::string mergeId = std::string()); std::string description; std::string mergeId; Project state; - }; + }; - std::vector> stack_; + std::vector> stack_; size_t index_ = 0; }; diff --git a/datamodel/libCore/include/core/UserObjectFactoryInterface.h b/datamodel/libCore/include/core/UserObjectFactoryInterface.h index 92a7e5a2..401d77ec 100644 --- a/datamodel/libCore/include/core/UserObjectFactoryInterface.h +++ b/datamodel/libCore/include/core/UserObjectFactoryInterface.h @@ -15,16 +15,10 @@ #include #include -namespace raco::serialization { -struct DeserializationFactory; -} - namespace raco::core { class UserObjectFactoryInterface { public: - static raco::serialization::DeserializationFactory deserializationFactory(UserObjectFactoryInterface* objectFactory); - using CreationFunction = std::function; using ValueCreationFunction = std::function; @@ -34,10 +28,10 @@ class UserObjectFactoryInterface { ValueCreationFunction createValueFunc; }; - virtual SEditorObject createObject(const std::string& type, const std::string& name = std::string(), const std::string& id = std::string()) = 0; - virtual data_storage::ValueBase* createValue(const std::string& type) = 0; + virtual SEditorObject createObject(const std::string& type, const std::string& name = std::string(), const std::string& id = std::string()) const = 0; + virtual data_storage::ValueBase* createValue(const std::string& type) const = 0; - virtual std::shared_ptr createAnnotation(const std::string& type) = 0; + virtual std::shared_ptr createAnnotation(const std::string& type) const = 0; virtual const std::map& getTypes() const = 0; /** diff --git a/datamodel/libCore/src/ChangeRecorder.cpp b/datamodel/libCore/src/ChangeRecorder.cpp index c91b832a..4040da43 100644 --- a/datamodel/libCore/src/ChangeRecorder.cpp +++ b/datamodel/libCore/src/ChangeRecorder.cpp @@ -47,14 +47,18 @@ bool DataChangeRecorder::LinkMap::eraseLink(const LinkDescriptor& link) { return false; } -void DataChangeRecorder::LinkMap::insertLinkEndPointObjects(bool includeLinkStart, bool includeLinkEnd, SEditorObjectSet& objects) const { +void DataChangeRecorder::LinkMap::insertLinkEndPointObjects(bool includeLinkStart, bool includeLinkEnd, SEditorObjectSet& objects, + const SEditorObjectSet& excludeObjects) const { for (auto const& [linkEndObjId, links] : linkMap_) { - if (includeLinkEnd) { + if (includeLinkEnd && + excludeObjects.find(links.begin()->end.object()) == excludeObjects.end()) { objects.insert(links.begin()->end.object()); } if (includeLinkStart) { for (const auto& link : links) { - objects.insert(link.start.object()); + if (excludeObjects.find(link.start.object()) == excludeObjects.end()) { + objects.insert(link.start.object()); + } } } } @@ -294,9 +298,9 @@ SEditorObjectSet DataChangeRecorder::getAllChangedObjects(bool includePreviewDir } if (includeLinkStart || includeLinkEnd) { - addedLinks_.insertLinkEndPointObjects(includeLinkStart, includeLinkEnd, objects); - changedValidityLinks_.insertLinkEndPointObjects(includeLinkStart, includeLinkEnd, objects); - removedLinks_.insertLinkEndPointObjects(includeLinkStart, includeLinkEnd, objects); + addedLinks_.insertLinkEndPointObjects(includeLinkStart, includeLinkEnd, objects, {}); + changedValidityLinks_.insertLinkEndPointObjects(includeLinkStart, includeLinkEnd, objects, {}); + removedLinks_.insertLinkEndPointObjects(includeLinkStart, includeLinkEnd, objects, deletedObjects_); } return objects; diff --git a/datamodel/libCore/src/CommandInterface.cpp b/datamodel/libCore/src/CommandInterface.cpp index a13dc4b1..eb2461fe 100644 --- a/datamodel/libCore/src/CommandInterface.cpp +++ b/datamodel/libCore/src/CommandInterface.cpp @@ -16,6 +16,7 @@ #include "core/Queries.h" #include "core/Undo.h" #include "core/UserObjectFactoryInterface.h" +#include "utils/u8path.h" #include @@ -78,7 +79,7 @@ void CommandInterface::set(ValueHandle const& handle, double const& value) { void CommandInterface::set(ValueHandle const& handle, std::string const& value) { if (handle) { - auto newValue = handle.query() ? PathManager::sanitizePath(value) : value; + auto newValue = handle.query() ? raco::utils::u8path::sanitizePathString(value) : value; if (handle.asString() != newValue) { context_->set(handle, newValue); @@ -116,6 +117,60 @@ void CommandInterface::set(ValueHandle const& handle, Table const& value) { } } +void CommandInterface::set(ValueHandle const& handle, std::array const& value) { + if (handle && handle.asVec2f() != value) { + context_->set(handle, value); + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Set property '{}' to ({}, {})", handle.getPropertyPath(), value[0], value[1]), + fmt::format("{}", handle.getPropertyPath(true))); + } +} + +void CommandInterface::set(ValueHandle const& handle, std::array const& value) { + if (handle && handle.asVec3f() != value) { + context_->set(handle, value); + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Set property '{}' to ({}, {}, {})", handle.getPropertyPath(), value[0], value[1], value[2]), + fmt::format("{}", handle.getPropertyPath(true))); + } +} + +void CommandInterface::set(ValueHandle const& handle, std::array const& value) { + if (handle && handle.asVec4f() != value) { + context_->set(handle, value); + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Set property '{}' to ({}, {}, {}, {})", handle.getPropertyPath(), value[0], value[1], value[2], value[3]), + fmt::format("{}", handle.getPropertyPath(true))); + } +} + +void CommandInterface::set(ValueHandle const& handle, std::array const& value) { + if (handle && handle.asVec2i() != value) { + context_->set(handle, value); + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Set property '{}' to ({}, {})", handle.getPropertyPath(), value[0], value[1]), + fmt::format("{}", handle.getPropertyPath(true))); + } +} + +void CommandInterface::set(ValueHandle const& handle, std::array const& value) { + if (handle && handle.asVec3i() != value) { + context_->set(handle, value); + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Set property '{}' to ({}, {}, {})", handle.getPropertyPath(), value[0], value[1], value[2]), + fmt::format("{}", handle.getPropertyPath(true))); + } +} + +void CommandInterface::set(ValueHandle const& handle, std::array const& value) { + if (handle && handle.asVec4i() != value) { + context_->set(handle, value); + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Set property '{}' to ({}, {}, {}, {})", handle.getPropertyPath(), value[0], value[1], value[2], value[3]), + fmt::format("{}", handle.getPropertyPath(true))); + } +} + SEditorObject CommandInterface::createObject(std::string type, std::string name, std::string id, SEditorObject parent) { auto types = context_->objectFactory()->getTypes(); if (types.find(type) != types.end()) { @@ -163,7 +218,7 @@ void CommandInterface::insertAssetScenegraph(const raco::core::MeshScenegraph& s context_->insertAssetScenegraph(scenegraph, absPath, parent); PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); undoStack_->push(fmt::format("Inserted assets from {}", absPath)); - PathManager::setCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh, std::filesystem::path(absPath).parent_path().generic_string()); + PathManager::setCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh, raco::utils::u8path(absPath).parent_path().string()); } std::string CommandInterface::copyObjects(const std::vector& objects, bool deepCopy) { diff --git a/datamodel/libCore/src/Context.cpp b/datamodel/libCore/src/Context.cpp index 72b7e46c..edd60e33 100644 --- a/datamodel/libCore/src/Context.cpp +++ b/datamodel/libCore/src/Context.cpp @@ -26,7 +26,6 @@ #include "core/CoreFormatter.h" #include "log_system/log.h" #include "core/Serialization.h" -#include "core/SerializationFunctions.h" #include "user_types/Animation.h" #include "user_types/AnimationChannel.h" #include "user_types/Mesh.h" @@ -179,6 +178,23 @@ void BaseContext::setT(ValueHandle const& handle, Table const& value) { changeMultiplexer_.recordValueChanged(handle); } +template <> +void BaseContext::setT(ValueHandle const& handle, ClassWithReflectedMembers const& value) { + ValueBase* v = handle.valueRef(); + + callReferenceToThisHandlerForAllTableEntries<&EditorObject::onBeforeRemoveReferenceToThis>(handle); + + v->setStruct(value); + + callReferenceToThisHandlerForAllTableEntries<&EditorObject::onAfterAddReferenceToThis>(handle); + + handle.object_->onAfterValueChanged(*this, handle); + + callReferencedObjectChangedHandlers(handle.object_); + + changeMultiplexer_.recordValueChanged(handle); +} + void BaseContext::set(ValueHandle const& handle, bool const& value) { setT(handle, value); } @@ -211,6 +227,34 @@ void BaseContext::set(ValueHandle const& handle, Table const& value) { setT(handle, value); } +void BaseContext::set(ValueHandle const& handle, std::array const& value) { + setT(handle, value); +} + +void BaseContext::set(ValueHandle const& handle, std::array const& value) { + setT(handle, value); +} + +void BaseContext::set(ValueHandle const& handle, std::array const& value) { + setT(handle, value); +} + +void BaseContext::set(ValueHandle const& handle, std::array const& value) { + setT(handle, value); +} + +void BaseContext::set(ValueHandle const& handle, std::array const& value) { + setT(handle, value); +} + +void BaseContext::set(ValueHandle const& handle, std::array const& value) { + setT(handle, value); +} + +void BaseContext::set(ValueHandle const& handle, ClassWithReflectedMembers const& value) { + setT(handle, value); +} + ValueBase* BaseContext::addProperty(const ValueHandle& handle, std::string name, std::unique_ptr&& newProperty, int indexBefore) { Table& table{handle.valueRef()->asTable()}; @@ -316,6 +360,16 @@ void BaseContext::removeAllProperties(const ValueHandle& handle) { } } +void BaseContext::swapProperties(const ValueHandle& handle, size_t index_1, size_t index_2) { + Table& table{handle.valueRef()->asTable()}; + table.swapProperties(index_1, index_2); + + callReferencedObjectChangedHandlers(handle.object_); + + changeMultiplexer_.recordValueChanged(handle); +} + + namespace { std::vector collectObjectsForCopyOrCutOperations(const std::vector& objects, bool deep) { SEditorObjectSet toCheck{objects.begin(), objects.end()}; @@ -368,7 +422,7 @@ std::string BaseContext::copyObjects(const std::vector& objects, auto allObjects{collectObjectsForCopyOrCutOperations(objects, deepCopy)}; auto rootObjectIDs{findRootObjectIDs(allObjects)}; auto originFolders{findOriginFolders(*project_, allObjects)}; - return raco::serialization::serialize(allObjects, rootObjectIDs, collectLinksForCopyOrCutOperation(*project_, allObjects), project_->currentFolder(), project_->currentFileName(), project_->projectID(), project_->projectName(), project_->externalProjectsMap(), originFolders).c_str(); + return raco::serialization::serializeObjects(allObjects, rootObjectIDs, collectLinksForCopyOrCutOperation(*project_, allObjects), project_->currentFolder(), project_->currentFileName(), project_->projectID(), project_->projectName(), project_->externalProjectsMap(), originFolders).c_str(); } std::string BaseContext::cutObjects(const std::vector& objects, bool deepCut) { @@ -376,7 +430,7 @@ std::string BaseContext::cutObjects(const std::vector& objects, b auto allLinks{collectLinksForCopyOrCutOperation(*project_, allObjects)}; auto rootObjectIDs{findRootObjectIDs(allObjects)}; auto originFolders{findOriginFolders(*project_, allObjects)}; - std::string serialization{raco::serialization::serialize(allObjects, rootObjectIDs, allLinks, project_->currentFolder(), project_->currentFileName(), project_->projectID(), project_->projectName(), project_->externalProjectsMap(), originFolders).c_str()}; + std::string serialization{raco::serialization::serializeObjects(allObjects, rootObjectIDs, allLinks, project_->currentFolder(), project_->currentFileName(), project_->projectID(), project_->projectName(), project_->externalProjectsMap(), originFolders).c_str()}; deleteObjects(Queries::filterForDeleteableObjects(*project_, allObjects)); return serialization; } @@ -394,11 +448,11 @@ void BaseContext::rerootRelativePaths(std::vector& newObjects, ra } else { originFolder = deserialization.originFolder; } - if (!originFolder.empty() && !uriPath.empty() && std::filesystem::path{uriPath}.is_relative()) { - if (PathManager::pathsShareSameRoot(originFolder, this->project()->currentPath())) { - property.valueRef()->set(PathManager::rerootRelativePath(uriPath, originFolder, this->project()->currentFolder())); + if (!originFolder.empty() && !uriPath.empty() && raco::utils::u8path(uriPath).is_relative()) { + if (raco::utils::u8path::areSharingSameRoot(originFolder, this->project()->currentPath())) { + property.valueRef()->set(raco::utils::u8path(uriPath).rerootRelativePath(originFolder, this->project()->currentFolder()).string()); } else { - property.valueRef()->set(PathManager::constructAbsolutePath(originFolder, uriPath)); + property.valueRef()->set(raco::utils::u8path(uriPath).normalizedAbsolutePath(originFolder).string()); } } } @@ -434,7 +488,7 @@ bool BaseContext::extrefPasteDiscardObject(SEditorObject editorObject, raco::ser originProjectID = *anno->projectID_; auto it = deserialization.externalProjectsMap.find(originProjectID); if (it != deserialization.externalProjectsMap.end()) { - originProjectPath = PathManager::constructAbsolutePath(deserialization.originFolder, it->second.path); + originProjectPath = raco::utils::u8path(it->second.path).normalizedAbsolutePath(deserialization.originFolder).string(); } else { throw ExtrefError("Paste: can't resolve external project path"); } @@ -466,7 +520,7 @@ void BaseContext::adjustExtrefAnnotationsForPaste(std::vector& ne auto it = deserialization.externalProjectsMap.find(extProjID); if (it != deserialization.externalProjectsMap.end()) { - project_->addExternalProjectMapping(extProjID, PathManager::constructAbsolutePath(deserialization.originFolder, it->second.path), it->second.name); + project_->addExternalProjectMapping(extProjID, raco::utils::u8path(it->second.path).normalizedAbsolutePath(deserialization.originFolder).string(), it->second.name); } else { throw ExtrefError("Paste: can't resolve external project path"); } @@ -481,8 +535,7 @@ void BaseContext::adjustExtrefAnnotationsForPaste(std::vector& ne void BaseContext::restoreReferences(const Project& project, std::vector& newObjects, raco::serialization::ObjectsDeserialization& deserialization) { std::map oldIdToEditorObject{}; - for (auto& object : newObjects) { - auto editorObject{std::dynamic_pointer_cast(object)}; + for (auto& editorObject : newObjects) { oldIdToEditorObject[editorObject->objectID()] = editorObject; } @@ -498,8 +551,7 @@ void BaseContext::restoreReferences(const Project& project, std::vector BaseContext::pasteObjects(const std::string& seralizedObjects, const SEditorObject& target, bool pasteAsExtref) { - auto deserialization{raco::serialization::deserializeObjects(seralizedObjects, - raco::user_types::UserObjectFactoryInterface::deserializationFactory(objectFactory_))}; + auto deserialization{raco::serialization::deserializeObjects(seralizedObjects)}; if (deserialization.objects.size() == 0) { return {}; @@ -520,9 +572,7 @@ std::vector BaseContext::pasteObjects(const std::string& seralize std::set discardedObjects{}; // Filter out objects that need to be discarded in paste - for (auto& object : deserialization.objects) { - auto editorObject{std::dynamic_pointer_cast(object)}; - + for (auto& editorObject : deserialization.objects) { if (pasteAsExtref && extrefPasteDiscardObject(editorObject, deserialization)) { discardedObjects.insert(editorObject->objectID()); } else { @@ -592,8 +642,7 @@ std::vector BaseContext::pasteObjects(const std::string& seralize } } - for (auto& i : deserialization.links) { - auto link{std::dynamic_pointer_cast(i)}; + for (auto& link : deserialization.links) { // Drop links if the start/end object doesn't exist, it violates prefab constraints or creates a loop. // Keep links if the property doesn't exist or the types don't match: these links are only (temporarily) invalid. if (*link->startObject_ && *link->endObject_ && @@ -851,7 +900,7 @@ void BaseContext::moveScenegraphChildren(std::vector const& objec } void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& scenegraph, const std::string& absPath, SEditorObject const& parent) { - auto relativeFilePath = PathManager::constructRelativePath(absPath, project()->currentFolder()); + auto relativeFilePath = raco::utils::u8path(absPath).normalizedRelativePath(project()->currentFolder()); std::vector meshScenegraphMeshes; std::vector meshScenegraphNodes; @@ -875,7 +924,7 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg continue; } - auto meshWithSameProperties = propertiesToMeshMap.find({false, static_cast(i), relativeFilePath}); + auto meshWithSameProperties = propertiesToMeshMap.find({false, static_cast(i), relativeFilePath.string()}); if (meshWithSameProperties == propertiesToMeshMap.end()) { LOG_DEBUG(log_system::CONTEXT, "Did not find existing local Mesh with same properties as asset mesh, creating one instead..."); auto ¤tSubmesh = meshScenegraphMeshes.emplace_back(createObject(raco::user_types::Mesh::typeDescription.typeName, *scenegraph.meshes[i])); @@ -883,7 +932,7 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg set(currentSubmeshHandle.get("bakeMeshes"), false); set(currentSubmeshHandle.get("meshIndex"), static_cast(i)); - set(currentSubmeshHandle.get("uri"), relativeFilePath); + set(currentSubmeshHandle.get("uri"), relativeFilePath.string()); } else { LOG_DEBUG(log_system::CONTEXT, "Found existing local Mesh {} with same properties as asset mesh, using this Mesh...", *scenegraph.meshes[i]); meshScenegraphMeshes.emplace_back(meshWithSameProperties->second); @@ -897,7 +946,7 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg return object->getParent() == nullptr; }); - auto meshPath = std::filesystem::path(relativeFilePath).filename().string(); + auto meshPath = relativeFilePath.filename().string(); meshPath = project_->findAvailableUniqueName(topLevelObjects.begin(), topLevelObjects.end(), nullptr, meshPath); auto sceneRootNode = createObject(raco::user_types::Node::typeDescription.typeName, meshPath); if (parent) { @@ -1006,7 +1055,7 @@ void BaseContext::insertAssetScenegraph(const raco::core::MeshScenegraph& sceneg for (auto samplerIndex = 0; samplerIndex < samplers.size(); ++samplerIndex) { auto& meshAnimSampler = scenegraph.animationSamplers.at(animIndex)[samplerIndex]; if (!meshAnimSampler.has_value()) { - LOG_DEBUG(log_system::CONTEXT, "Found disabled mesh animation sampler at index {}.{}, ignoring AnimationChannel creaton...", animIndex, samplerIndex); + LOG_DEBUG(log_system::CONTEXT, "Found disabled mesh animation sampler at index {}.{}, ignoring AnimationChannel creat�on...", animIndex, samplerIndex); sceneChannels[animIndex].emplace_back(nullptr); continue; } @@ -1123,28 +1172,22 @@ void BaseContext::removeLink(const PropertyDescriptor& end) { void BaseContext::updateExternalReferences(std::vector& pathStack) { ExtrefOperations::updateExternalObjects(*this, project(), *externalProjectsStore(), pathStack); - PrefabOperations::globalPrefabUpdate(*this, modelChanges()); + PrefabOperations::globalPrefabUpdate(*this, modelChanges(), true); } -std::vector BaseContext::getTopLevelObjectsFromDeserializedObjects(serialization::ObjectsDeserialization& deserialization, UserObjectFactoryInterface* objectFactory, Project* project) { - std::vector newObjects; +std::vector BaseContext::getTopLevelObjectsFromDeserializedObjects(serialization::ObjectsDeserialization& deserialization, Project* project) { SEditorObjectSet childrenSet; - for (auto& object : deserialization.objects) { - auto editorObject{std::dynamic_pointer_cast(object)}; - newObjects.emplace_back(editorObject); - } - - restoreReferences(*project, newObjects, deserialization); + restoreReferences(*project, deserialization.objects, deserialization); - for (const auto& obj : newObjects) { + for (const auto& obj : deserialization.objects) { for (const auto& objChild : obj->children_->asVector()) { childrenSet.emplace(objChild); } } std::vector topLevelObjects; - for (const auto& obj : newObjects) { + for (const auto& obj : deserialization.objects) { if (childrenSet.find(obj) == childrenSet.end()) { topLevelObjects.emplace_back(obj); } diff --git a/datamodel/libCore/src/EditorObject.cpp b/datamodel/libCore/src/EditorObject.cpp index 44454468..5ce3c919 100644 --- a/datamodel/libCore/src/EditorObject.cpp +++ b/datamodel/libCore/src/EditorObject.cpp @@ -27,7 +27,7 @@ using namespace raco::data_storage; EditorObject::EditorObject(std::string name, std::string id) : ClassWithReflectedMembers(), - objectName_{name, DisplayNameAnnotation("Object Name")}, + objectName_{name, DisplayNameAnnotation("Object Name")}, objectID_{normalizedObjectID(id), {}} { fillPropertyDescription(); @@ -75,24 +75,54 @@ std::string EditorObject::normalizedObjectID(std::string const& id) return id; } +std::string EditorObject::XorObjectIDs(std::string const& id1, std::string const& id2) { + QUuid qid1(QString::fromStdString(id1)); + QUuid qid2(QString::fromStdString(id2)); + + QUuid qid_result(qid1.data1 ^ qid2.data1, + qid1.data2 ^ qid2.data2, + qid1.data3 ^ qid2.data3, + qid1.data4[0] ^ qid2.data4[0], + qid1.data4[1] ^ qid2.data4[1], + qid1.data4[2] ^ qid2.data4[2], + qid1.data4[3] ^ qid2.data4[3], + qid1.data4[4] ^ qid2.data4[4], + qid1.data4[5] ^ qid2.data4[5], + qid1.data4[6] ^ qid2.data4[6], + qid1.data4[7] ^ qid2.data4[7]); + + return qid_result.toString(QUuid::WithoutBraces).toStdString(); +} + const std::set>& EditorObject::referencesToThis() const { return referencesToThis_; } -void EditorObject::onBeforeRemoveReferenceToThis(ValueHandle const& sourceReferenceProperty) const { - auto srcRootObject = sourceReferenceProperty.rootObject(); - - bool isReferenced = false; - for (auto const& prop : ValueTreeIteratorAdaptor(ValueHandle(srcRootObject))) { - if (!(prop == sourceReferenceProperty) && prop.type() == PrimitiveType::Ref) { - auto refValue = prop.asTypedRef(); - if (refValue && refValue.get() == this) { - isReferenced = true; - break; +namespace { +bool hasReferenceTo(const ReflectionInterface& src, SCEditorObject target, const ValueBase* exceptSourceValueBase) { + for (size_t index = 0; index < src.size(); index++) { + auto v = src.get(index); + if (v != exceptSourceValueBase) { + if (v->type() == PrimitiveType::Ref) { + auto vref = v->asRef(); + if (vref && vref == target) { + return true; + } + } else if (hasTypeSubstructure(v->type())) { + if (hasReferenceTo(v->getSubstructure(), target, exceptSourceValueBase)) { + return true; + } } } } - if (!isReferenced) { + return false; +} +} // namespace + +void EditorObject::onBeforeRemoveReferenceToThis(ValueHandle const& sourceReferenceProperty) const { + auto srcRootObject = sourceReferenceProperty.rootObject(); + + if (!hasReferenceTo(*srcRootObject, shared_from_this(), sourceReferenceProperty.constValueRef())) { referencesToThis_.erase(srcRootObject); } @@ -134,7 +164,7 @@ FileChangeMonitor::UniqueListener EditorObject::registerFileChangedHandler(BaseC void EditorObject::onAfterContextActivated(BaseContext& context) { for (size_t i = 0; i < size(); i++) { - if (get(i)->query()) { + if (auto anno = get(i)->query(); anno && !anno->isProjectSubdirectoryURI()) { auto propName = name(i); ValueHandle handle{shared_from_this(), {i}}; uriListeners_[propName] = registerFileChangedHandler(context, handle, [this, handle, &context]() { @@ -146,7 +176,7 @@ void EditorObject::onAfterContextActivated(BaseContext& context) { } void EditorObject::onAfterValueChanged(BaseContext& context, ValueHandle const& value) { - if (value.query()) { + if (auto anno = value.query(); anno && !anno->isProjectSubdirectoryURI()) { assert(value.depth() == 1); auto propName = value.getPropName(); uriListeners_[propName] = registerFileChangedHandler(context, value, [this, value, &context]() { diff --git a/datamodel/libCore/src/ExtrefOperations.cpp b/datamodel/libCore/src/ExtrefOperations.cpp index 040b9973..c7ab5699 100644 --- a/datamodel/libCore/src/ExtrefOperations.cpp +++ b/datamodel/libCore/src/ExtrefOperations.cpp @@ -247,7 +247,7 @@ void ExtrefOperations::updateExternalObjects(BaseContext& context, Project* proj auto extObj = item.second.obj; auto localObj = translateToLocal(extObj); - updateEditorObject( + UndoHelpers::updateEditorObject( extObj.get(), localObj, translateToLocal, [](const std::string&) { return false; }, *context.objectFactory(), &localChanges, true, false); } diff --git a/datamodel/libCore/src/Handles.cpp b/datamodel/libCore/src/Handles.cpp index 231799df..ca54e076 100644 --- a/datamodel/libCore/src/Handles.cpp +++ b/datamodel/libCore/src/Handles.cpp @@ -72,6 +72,7 @@ template<> double ValueHandle::as() const { return asDouble(); } + template <> std::string ValueHandle::as() const { return asString(); @@ -102,6 +103,36 @@ SEditorObject ValueHandle::asRef() const { return v->asRef(); } +const Vec2f& ValueHandle::asVec2f() const { + ValueBase* v = valueRef(); + return v->asVec2f(); +} + +const Vec3f& ValueHandle::asVec3f() const { + ValueBase* v = valueRef(); + return v->asVec3f(); +} + +const Vec4f& ValueHandle::asVec4f() const { + ValueBase* v = valueRef(); + return v->asVec4f(); +} + +const Vec2i& ValueHandle::asVec2i() const { + ValueBase* v = valueRef(); + return v->asVec2i(); +} + +const Vec3i& ValueHandle::asVec3i() const { + ValueBase* v = valueRef(); + return v->asVec3i(); +} + +const Vec4i& ValueHandle::asVec4i() const { + ValueBase* v = valueRef(); + return v->asVec4i(); +} + size_t ValueHandle::size() const { if (indices_.empty()) { return object_->size(); diff --git a/datamodel/libCore/src/PathManager.cpp b/datamodel/libCore/src/PathManager.cpp index 14bdc4fc..1a1c3ea6 100644 --- a/datamodel/libCore/src/PathManager.cpp +++ b/datamodel/libCore/src/PathManager.cpp @@ -8,7 +8,6 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "core/PathManager.h" -#include "utils/PathUtils.h" #include "user_types/AnimationChannel.h" #include "user_types/CubeMap.h" #include "user_types/LuaScript.h" @@ -19,83 +18,109 @@ #include #include +#include + namespace raco::core { -std::filesystem::path PathManager::basePath_; +using u8path = raco::utils::u8path; -std::filesystem::path PathManager::normal_path(const std::string& path) { - return raco_filesystem_compatibility::lexically_normal(std::filesystem::path(path)); -} +u8path PathManager::basePath_; +u8path PathManager::appDataBasePath_; -void PathManager::init(const std::string& executableDirectory) { - basePath_ = normal_path(executableDirectory).parent_path().parent_path(); +void PathManager::init(const u8path& executableDirectory, const u8path& appDataDirectory) { + basePath_ = executableDirectory.normalized().parent_path().parent_path(); + appDataBasePath_ = appDataDirectory.normalized(); + migrateLegacyConfigDirectory(); } -std::filesystem::path PathManager::defaultBaseDirectory() { +u8path PathManager::defaultBaseDirectory() { return basePath_; } -std::string PathManager::defaultConfigDirectory() { - return (defaultBaseDirectory() / DEFAULT_CONFIG_SUB_DIRECTORY).generic_string(); +u8path PathManager::defaultConfigDirectory() { + return appDataBasePath_; +} + +u8path PathManager::legacyConfigDirectory() { + return defaultBaseDirectory() / LEGACY_CONFIG_SUB_DIRECTORY; } -std::filesystem::path PathManager::defaultResourceDirectory() { +u8path PathManager::defaultResourceDirectory() { return defaultBaseDirectory() / DEFAULT_PROJECT_SUB_DIRECTORY; } -std::string PathManager::logFilePath() { - return (std::filesystem::path(defaultConfigDirectory()) / LOG_FILE_NAME).generic_string(); +u8path PathManager::logFileDirectory() { + return defaultConfigDirectory() / LOG_SUB_DIRECTORY; } -std::string PathManager::layoutFilePath() { - return (std::filesystem::path(defaultConfigDirectory()) / Q_LAYOUT_FILE_NAME).generic_string(); +u8path PathManager::logFileHeadlessName() { + return logFileDirectory() / LOG_FILE_HEADLESS_NAME; } -std::string PathManager::recentFilesStorePath() { - return (std::filesystem::path(defaultConfigDirectory()) / Q_RECENT_FILES_STORE_NAME).generic_string(); +u8path PathManager::logFileEditorName() { + return logFileDirectory() / LOG_FILE_EDITOR_NAME; } -std::string PathManager::preferenceFileLocation() { - return (std::filesystem::path(defaultConfigDirectory()) / Q_PREFERENCES_FILE_NAME).generic_string(); +u8path PathManager::layoutFilePath() { + return defaultConfigDirectory() / Q_LAYOUT_FILE_NAME; } -std::string PathManager::defaultProjectFallbackPath() { - return (defaultBaseDirectory() / DEFAULT_PROJECT_SUB_DIRECTORY).generic_string(); +u8path PathManager::recentFilesStorePath() { + return defaultConfigDirectory() / Q_RECENT_FILES_STORE_NAME; } -std::string PathManager::constructRelativePath(const std::string& absolutePath, const std::string& basePath) { - // use std::filesystem::proximate() call with std::error_code to prevent exceptions and return the input path instead. - std::error_code ec; - return std::filesystem::proximate(absolutePath, basePath, ec).generic_string(); +u8path PathManager::preferenceFilePath() { + return defaultConfigDirectory() / Q_PREFERENCES_FILE_NAME; } -std::string PathManager::constructAbsolutePath(const std::string& dirPath, const std::string& filePath) { - if (std::filesystem::path(filePath).is_absolute()) { - return raco_filesystem_compatibility::lexically_normal(std::filesystem::path(filePath)).generic_string(); +void PathManager::migrateLegacyConfigDirectory() { + auto configDir = defaultConfigDirectory(); + auto legacyConfigDir = legacyConfigDirectory(); + auto logDir = logFileDirectory(); + + // Check which config dirs exist and contain files + auto legacyConfigExistsAndHasFiles = legacyConfigDir.existsDirectory() && std::filesystem::directory_iterator(legacyConfigDir) != std::filesystem::directory_iterator{}; + auto newConfigExistsAndHasFiles = configDir.existsDirectory() && std::filesystem::directory_iterator(configDir) != std::filesystem::directory_iterator{}; + + if (legacyConfigExistsAndHasFiles && !newConfigExistsAndHasFiles) { + if (configDir.existsDirectory()) { + std::filesystem::remove(configDir); + } + std::filesystem::rename(legacyConfigDir, configDir); + std::filesystem::create_directories(logDir); + for(const auto& p : std::filesystem::directory_iterator(configDir)){ + if (u8path(p).existsFile() && p.path().extension().u8string() == ".log") { + std::filesystem::rename(p.path(), logDir / p.path().filename()); + } + } + } else { + std::filesystem::create_directories(configDir); + std::filesystem::create_directories(logDir); } - return raco_filesystem_compatibility::lexically_normal(std::filesystem::path(dirPath) / filePath).generic_string(); } -std::string PathManager::rerootRelativePath(const std::string& relativePath, const std::string& oldPath, const std::string& newPath) { - auto uriAbsolutePath = PathManager::constructAbsolutePath(oldPath, relativePath); - return PathManager::constructRelativePath(uriAbsolutePath, newPath); +u8path PathManager::defaultProjectFallbackPath() { + return defaultBaseDirectory() / DEFAULT_PROJECT_SUB_DIRECTORY; +} + +QSettings PathManager::layoutSettings() { + return QSettings(raco::core::PathManager::layoutFilePath().string().c_str(), QSettings::IniFormat); } -bool PathManager::pathsShareSameRoot(const std::string& lhd, const std::string& rhd) { - auto leftRoot = std::filesystem::path(lhd).root_name().generic_string(); - auto rightRoot = std::filesystem::path(rhd).root_name().generic_string(); - std::transform(leftRoot.begin(), leftRoot.end(), leftRoot.begin(), tolower); - std::transform(rightRoot.begin(), rightRoot.end(), rightRoot.begin(), tolower); +QSettings PathManager::recentFilesStoreSettings() { + return QSettings(raco::core::PathManager::recentFilesStorePath().string().c_str(), QSettings::IniFormat); +} - return leftRoot == rightRoot; +QSettings PathManager::preferenceSettings() { + return QSettings(raco::core::PathManager::preferenceFilePath().string().c_str(), QSettings::IniFormat); } -const std::string& PathManager::getCachedPath(FolderTypeKeys key, const std::string& fallbackPath) { +const u8path& PathManager::getCachedPath(FolderTypeKeys key, const u8path& fallbackPath) { auto pathIt = cachedPaths_.find(key); if (pathIt != cachedPaths_.end()) { auto& cachedPath = pathIt->second; - if (!raco::utils::path::isExistingDirectory(cachedPath) && !fallbackPath.empty()) { + if (!cachedPath.existsDirectory() && !fallbackPath.empty()) { return fallbackPath; } return cachedPath; @@ -104,25 +129,13 @@ const std::string& PathManager::getCachedPath(FolderTypeKeys key, const std::str return fallbackPath; } -void PathManager::setCachedPath(FolderTypeKeys key, const std::string& path) { +void PathManager::setCachedPath(FolderTypeKeys key, const u8path& path) { auto pathIt = cachedPaths_.find(key); if (pathIt != cachedPaths_.end()) { pathIt->second = path; } } -void PathManager::setAllCachedPathRoots(const std::string& folder, - const std::string& imageSubdirectory, - const std::string& MeshSubdirectory, - const std::string& ScriptSubdirectory, - const std::string& ShaderSubdirectory) { - setCachedPath(FolderTypeKeys::Project, folder); - setCachedPath(FolderTypeKeys::Image, folder + "/" + imageSubdirectory); - setCachedPath(FolderTypeKeys::Mesh, folder + "/" + MeshSubdirectory); - setCachedPath(FolderTypeKeys::Script, folder + "/" + ScriptSubdirectory); - setCachedPath(FolderTypeKeys::Shader, folder + "/" + ShaderSubdirectory); -} - PathManager::FolderTypeKeys PathManager::getCachedPathKeyCorrespondingToUserType(const raco::data_storage::ReflectionInterface::TypeDescriptor& type) { if (&type == &raco::user_types::CubeMap::typeDescription || &type == &raco::user_types::Texture::typeDescription) { return raco::core::PathManager::FolderTypeKeys::Image; @@ -140,21 +153,11 @@ PathManager::FolderTypeKeys PathManager::getCachedPathKeyCorrespondingToUserType return raco::core::PathManager::FolderTypeKeys::Shader; } - assert(false && "unknown user type found in URIEditor::getCachedPathKeyCorrespondingToUserType()"); - return raco::core::PathManager::FolderTypeKeys::Invalid; -} - -std::string PathManager::sanitizePath(const std::string& path) { - const auto trimmedStringLeft = std::find_if_not(path.begin(), path.end(), [](auto c) { return std::isspace(c); }); - const auto trimmedStringRight = std::find_if_not(path.rbegin(), path.rend(), [](auto c) { return std::isspace(c); }).base(); - - if (trimmedStringLeft >= trimmedStringRight) { - return std::string(); + if (&type == &raco::user_types::ProjectSettings::typeDescription) { + return raco::core::PathManager::FolderTypeKeys::Project; } - auto sanitizedPathString = raco_filesystem_compatibility::lexically_normal(std::filesystem::path(trimmedStringLeft, trimmedStringRight)).generic_string(); - - return sanitizedPathString; + assert(false && "unknown user type found in URIEditor::getCachedPathKeyCorrespondingToUserType()"); + return raco::core::PathManager::FolderTypeKeys::Invalid; } - } // namespace raco::core diff --git a/datamodel/libCore/src/PathQueries.cpp b/datamodel/libCore/src/PathQueries.cpp index 17cdad20..b77fc975 100644 --- a/datamodel/libCore/src/PathQueries.cpp +++ b/datamodel/libCore/src/PathQueries.cpp @@ -37,7 +37,7 @@ std::string baseFolderForRelativePath(const Project& project, SEditorObject obje if (!externalProjectID.empty()) { if (project.hasExternalProjectMapping(externalProjectID)) { auto projectPath = project.lookupExternalProjectPath(externalProjectID); - return std::filesystem::path(projectPath).parent_path().generic_string(); + return raco::utils::u8path(projectPath).parent_path().string(); } return std::string(); } @@ -53,13 +53,13 @@ std::string resolveUriPropertyToAbsolutePath(const Project& project, const Value if (!externalProjectID.empty()) { if (project.hasExternalProjectMapping(externalProjectID)) { auto projectPath = project.lookupExternalProjectPath(externalProjectID); - auto projectFolder = std::filesystem::path(projectPath).parent_path().generic_string(); - return PathManager::constructAbsolutePath(projectFolder, uriValue); + auto projectFolder = raco::utils::u8path(projectPath).parent_path().string(); + return raco::utils::u8path(uriValue).normalizedAbsolutePath(projectFolder).string(); } return std::string(); } - return PathManager::constructAbsolutePath(project.currentFolder(), uriValue); + return raco::utils::u8path(uriValue).normalizedAbsolutePath(project.currentFolder()).string(); } bool isPathRelativeToCurrentProject(const SEditorObject& object) { diff --git a/datamodel/libCore/src/PrefabOperations.cpp b/datamodel/libCore/src/PrefabOperations.cpp index b69dec73..f8816c15 100644 --- a/datamodel/libCore/src/PrefabOperations.cpp +++ b/datamodel/libCore/src/PrefabOperations.cpp @@ -80,7 +80,7 @@ SLink lookupLink(SLink srcLink, const std::map>& de // - selective update of single properties according to the changerecorder entries for the prefab subtree // - change recorder will be used as input for the dirty parts of the prefab and output for the changes in // the prefab instance and its children -void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& prefab, SPrefabInstance instance, bool instanceDirty) { +void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& prefab, SPrefabInstance instance, bool instanceDirty, bool propagateMissingInterfaceProperties) { using namespace raco::core; DataChangeRecorder localChanges; @@ -98,10 +98,10 @@ void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& std::map mapToPrefab; mapToInstance[prefab] = instance; mapToPrefab[instance] = prefab; - for (size_t index = 0; index < instance->mapToInstance_->size(); index++) { - const Table& item = instance->mapToInstance_->get(index)->asTable(); - auto prefabChild = item.get(0)->asRef(); - auto instChild = item.get(1)->asRef(); + + for (auto instChild : instanceChildren) { + auto prefabChildID = PrefabInstance::mapObjectIDFromInstance(instChild, prefab, instance); + auto prefabChild = context.project()->getInstanceByID(prefabChildID); mapToInstance[prefabChild] = instChild; mapToPrefab[instChild] = prefabChild; } @@ -133,7 +133,6 @@ void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& if (prefabIt == mapToPrefab.end() || prefabChildren.find(prefabIt->second) == prefabChildren.end()) { toRemove.emplace_back(instChild); - instance->removePrefabInstanceChild(context, prefabIt->second); mapToInstance.erase(prefabIt->second); mapToPrefab.erase(instChild); it = instanceChildren.erase(it); @@ -163,7 +162,8 @@ void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& context.project()->removeLink(instLink); localChanges.recordRemoveLink(instLink->descriptor()); - auto prefabEndObject = PrefabInstance::mapFromInstance(instEndObject, instance); + auto prefabEndObject = mapToPrefab[instEndObject]; + ValueHandle prefabEndHandle = ValueHandle::translatedHandle(ValueHandle(instLink->endProp()), prefabEndObject); if (prefabEndHandle) { allChangedValues.insert(prefabEndHandle); @@ -177,8 +177,8 @@ void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& for (auto prefabChild : prefabChildren) { auto it = mapToInstance.find(prefabChild); if (it == mapToInstance.end()) { - auto newInstChild = context.objectFactory()->createObject(prefabChild->getTypeDescription().typeName, prefabChild->objectName()); - instance->addChildMapping(context, prefabChild, newInstChild); + auto instChildID = PrefabInstance::mapObjectIDToInstance(prefabChild, prefab, instance); + auto newInstChild = context.objectFactory()->createObject(prefabChild->getTypeDescription().typeName, prefabChild->objectName(), instChildID); mapToInstance[prefabChild] = newInstChild; mapToPrefab[newInstChild] = prefabChild; context.project()->addInstance(newInstChild); @@ -190,7 +190,7 @@ void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& // Complete update of the the newly created objects for (auto [prefabChild, instChild] : createdObjects) { // Object IDs are never updated and the object name for newly created objects is already correct. - updateEditorObject( + UndoHelpers::updateEditorObject( prefabChild.get(), instChild, translateRefFunc, [](const std::string& propName) { return propName == "objectID" || propName == "mapToInstance"; @@ -202,7 +202,7 @@ void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& // Single property updates from model changes auto const& modelChanges = context.modelChanges().getChangedValues(); if (instanceDirty || context.modelChanges().hasValueChanged(ValueHandle(prefab, &EditorObject::children_))) { - updateSingleValue(&prefab->children_, &instance->children_, ValueHandle(instance, &EditorObject::children_), translateRefFunc, &localChanges, true); + UndoHelpers::updateSingleValue(&prefab->children_, &instance->children_, ValueHandle(instance, &EditorObject::children_), translateRefFunc, &localChanges, true); } for (const auto& [id, cont] : modelChanges) { @@ -213,16 +213,20 @@ void PrefabOperations::updatePrefabInstance(BaseContext& context, const SPrefab& if (std::find_if(createdObjects.begin(), createdObjects.end(), [prop](auto item) { return prop.rootObject() == item.first; }) == createdObjects.end()) { - if (prop.rootObject()->getParent() == prefab && - prop.rootObject()->as() && prop.depth() >= 1 && prop.getPropertyNamesVector()[0] == "luaInputs") { - continue; - } auto it = mapToInstance.find(prop.rootObject()); assert(it != mapToInstance.end()); auto inst = it->second; ValueHandle instProp = ValueHandle::translatedHandle(prop, inst); - updateSingleValue(prop.valueRef(), instProp.valueRef(), instProp, translateRefFunc, &localChanges, true); + + if (prop.rootObject()->getParent() == prefab && + prop.rootObject()->as() && prop.depth() >= 1 && prop.getPropertyNamesVector()[0] == "luaInputs") { + if (prop.depth() == 1 && propagateMissingInterfaceProperties) { + UndoHelpers::updateMissingTableProperties(&prop.valueRef()->asTable(), &instProp.valueRef()->asTable(), instProp, translateRefFunc, &localChanges, true); + } + } else { + UndoHelpers::updateSingleValue(prop.valueRef(), instProp.valueRef(), instProp, translateRefFunc, &localChanges, true); + } } } } @@ -316,7 +320,7 @@ bool prefabInstanceDirty(const DataChangeRecorder& changes, SPrefabInstance inst return changes.hasValueChanged(templateHandle); } -void PrefabOperations::globalPrefabUpdate(BaseContext& context, DataChangeRecorder& changes) { +void PrefabOperations::globalPrefabUpdate(BaseContext& context, DataChangeRecorder& changes, bool propagateMissingInterfaceProperties) { // Build prefab update order from dependency graph auto order = prefabUpdateOrder(*context.project()); @@ -333,7 +337,6 @@ void PrefabOperations::globalPrefabUpdate(BaseContext& context, DataChangeRecord if (std::find(context.project()->instances().begin(), context.project()->instances().end(), inst) != context.project()->instances().end()) { auto children = inst->children_->asVector(); context.deleteObjects(children); - context.removeAllProperties({inst, &PrefabInstance::mapToInstance_}); } } @@ -345,7 +348,7 @@ void PrefabOperations::globalPrefabUpdate(BaseContext& context, DataChangeRecord if (!findContainingPrefabInstance(inst->getParent())) { bool inst_dirty = prefabInstanceDirty(changes, inst); if (inst_dirty || prefab_dirty) { - updatePrefabInstance(context, prefab, inst, inst_dirty); + updatePrefabInstance(context, prefab, inst, inst_dirty, propagateMissingInterfaceProperties); } } } diff --git a/datamodel/libCore/src/Project.cpp b/datamodel/libCore/src/Project.cpp index dddd773c..b29e5db3 100644 --- a/datamodel/libCore/src/Project.cpp +++ b/datamodel/libCore/src/Project.cpp @@ -10,7 +10,7 @@ #include "core/Project.h" #include "core/PathManager.h" #include "core/ExternalReferenceAnnotation.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "core/CoreFormatter.h" #include "log_system/log.h" @@ -67,7 +67,7 @@ std::string Project::currentFolder() const { } std::string Project::currentPath() const { - return (std::filesystem::path(currentFolder()) / currentFileName()).generic_string(); + return (raco::utils::u8path(currentFolder()) / currentFileName()).string(); } std::string Project::currentFileName() const { @@ -75,12 +75,13 @@ std::string Project::currentFileName() const { } void Project::setCurrentPath(const std::string& newPath) { - if (utils::path::isExistingDirectory(newPath)) { + auto path = utils::u8path(newPath); + if (path.existsDirectory()) { folder_ = newPath; filename_.clear(); } else { - auto path = PathManager::normal_path(newPath); - folder_ = path.parent_path().generic_string(); + path = path.normalized(); + folder_ = path.parent_path().string(); filename_ = path.filename().string(); } } @@ -228,7 +229,7 @@ void Project::addExternalProjectMapping(const std::string& projectID, const std: throw ExtrefError("External reference project loop detected (based on project path)."); } - auto relPath = PathManager::constructRelativePath(absPath, currentFolder()); + auto relPath = raco::utils::u8path(absPath).normalizedRelativePath(currentFolder()).string(); auto it = externalProjectsMap_.find(projectID); if (it != externalProjectsMap_.end()) { @@ -269,7 +270,7 @@ bool Project::hasExternalProjectMapping(const std::string& projectID) const { std::string Project::lookupExternalProjectPath(const std::string& projectID) const { auto it = externalProjectsMap_.find(projectID); if (it != externalProjectsMap_.end()) { - return PathManager::constructAbsolutePath(currentFolder(), it->second.path); + return raco::utils::u8path(it->second.path).normalizedAbsolutePath(currentFolder()).string(); } return std::string(); } @@ -284,13 +285,13 @@ std::string Project::lookupExternalProjectName(const std::string& projectID) con void Project::rerootExternalProjectPaths(const std::string oldFolder, const std::string newFolder) { for (auto& item : externalProjectsMap_) { - item.second.path = PathManager::rerootRelativePath(item.second.path, oldFolder, newFolder); + item.second.path = raco::utils::u8path(item.second.path).rerootRelativePath(oldFolder, newFolder).string(); } } bool Project::usesExternalProjectByPath(const std::string& absPath) const { for (auto item : externalProjectsMap_) { - if (core::PathManager::constructAbsolutePath(currentFolder(), item.second.path) == absPath) { + if (raco::utils::u8path(item.second.path).normalizedAbsolutePath(currentFolder()) == absPath) { return true; } } diff --git a/datamodel/libCore/src/ProjectMigration.cpp b/datamodel/libCore/src/ProjectMigration.cpp new file mode 100644 index 00000000..3952effd --- /dev/null +++ b/datamodel/libCore/src/ProjectMigration.cpp @@ -0,0 +1,608 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "core/ProjectMigration.h" + +#include "core/CoreAnnotations.h" +#include "core/DynamicEditorObject.h" +#include "core/EngineInterface.h" +#include "core/ExternalReferenceAnnotation.h" +#include "core/Link.h" +#include "core/ProxyObjectFactory.h" +#include "core/ProxyTypes.h" + +#include "log_system/log.h" + +#include "user_types/EngineTypeAnnotation.h" + +#include + +#include "core/PathManager.h" +#include + +namespace raco::serialization { + +void linkReplaceEndIfMatching(raco::core::SLink& link, const std::string& oldProp, const std::vector& newEndProp) { + if (link->compareEndPropertyNames({oldProp})) { + link->endProp_->set(newEndProp); + } +}; + +template +raco::data_storage::ValueBase* createDynamicProperty_V11(raco::core::EnginePrimitive type) { + using namespace raco::serialization::proxy; + using namespace raco::data_storage; + using namespace raco::core; + + switch (type) { + case EnginePrimitive::Bool: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + case EnginePrimitive::Int32: + case EnginePrimitive::UInt16: + case EnginePrimitive::UInt32: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + case EnginePrimitive::Double: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + case EnginePrimitive::String: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + + case EnginePrimitive::Vec2f: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + case EnginePrimitive::Vec3f: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + case EnginePrimitive::Vec4f: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + + case EnginePrimitive::Vec2i: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + case EnginePrimitive::Vec3i: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + case EnginePrimitive::Vec4i: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + + case EnginePrimitive::Array: + case EnginePrimitive::Struct: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + + case EnginePrimitive::TextureSampler2D: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + + case EnginePrimitive::TextureSamplerCube: + return ProxyObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; + } + return nullptr; +} + +raco::data_storage::Table* findItemByValue(raco::data_storage::Table& table, raco::core::SEditorObject obj) { + for (size_t i{0}; i < table.size(); i++) { + raco::data_storage::Table& item = table.get(i)->asTable(); + if (item.get(1)->asRef() == obj) { + return &item; + } + } + return nullptr; +} + +void insertPrefabInstancesRecursive(raco::serialization::proxy::SEditorObject inst, std::vector& sortedInstances) { + if (std::find(sortedInstances.begin(), sortedInstances.end(), inst) == sortedInstances.end()) { + if (auto prefab = inst->get("template")->asRef()) { + for (auto prefabChild : raco::core::TreeIteratorAdaptor(prefab)) { + if (prefabChild->serializationTypeName() == "PrefabInstance") { + insertPrefabInstancesRecursive(prefabChild, sortedInstances); + } + } + } + sortedInstances.emplace_back(inst); + } +} + +// Limitations +// - Annotations and links are handled as static classes: +// the class definition is not supposed to change in any observable way: +// annotation name, property types and names, number of properties, serialiationRequired flag must not change. +// - If the need arises to migrate the annotationns we would need to create proxy types for them in the same +// way currently done for the Struct PrimitiveTypes. + +void migrateProject(ProjectDeserializationInfoIR& deserializedIR) { + using namespace raco::data_storage; + using namespace raco::serialization::proxy; + + if (deserializedIR.fileVersion < 2) { + auto settingsID = QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString(); + auto settings = std::make_shared("", settingsID); + deserializedIR.objects.emplace_back(settings); + } + + // File Version 10: cameras store viewport as four individual integers instead of a vec4i (for camera bindings). + if (deserializedIR.fileVersion < 10) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "PerspectiveCamera" || instanceType == "OrthographicCamera") { + auto& oldviewportprop = *dynObj->get("viewport"); + dynObj->addProperty("viewPortOffsetX", new data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i1_.asInt(), {-7680, 7680}, {"Viewport Offset X"}, {}}, -1); + dynObj->addProperty("viewPortOffsetY", new data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i2_.asInt(), {-7680, 7680}, {"Viewport Offset Y"}, {}}, -1); + dynObj->addProperty("viewPortWidth", new data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i3_.asInt(), {0, 7680}, {"Viewport Width"}, {}}, -1); + dynObj->addProperty("viewPortHeight", new data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i4_.asInt(), {0, 7680}, {"Viewport Height"}, {}}, -1); + dynObj->removeProperty("viewport"); + } + } + } + + // File Version 11: Added the viewport background color to the ProjectSettings. + if (deserializedIR.fileVersion < 11) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "ProjectSettings") { + dynObj->addProperty("backgroundColor", new data_storage::Property{{}, {"Display Background Color"}}, -1); + } + } + } + + // File version 12: + // Add 'private' property to material slot containers in MeshNodes. + // Rename 'depthfunction' -> 'depthFunction' in options container of meshnode material slot. + // Add LinkEndAnnotation to material uniform properties + if (deserializedIR.fileVersion < 12) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "MeshNode" && dynObj->hasProperty("materials")) { + auto* materials = &dynObj->get("materials")->asTable(); + + for (size_t i = 0; i < materials->size(); i++) { + Table& matCont = materials->get(i)->asTable(); + matCont.addProperty("private", new data_storage::Property(true, {"Private Material"}), 1); + Table& optionsCont = matCont.get("options")->asTable(); + optionsCont.renameProperty("depthfunction", "depthFunction"); + } + } + + if (instanceType == "Material" && dynObj->hasProperty("uniforms")) { + auto* uniforms = &dynObj->get("uniforms")->asTable(); + + for (size_t i = 0; i < uniforms->size(); i++) { + auto engineType = uniforms->get(i)->query()->type(); + if (raco::core::PropertyInterface::primitiveType(engineType) != PrimitiveType::Ref) { + auto newValue = createDynamicProperty_V11(engineType); + *newValue = *uniforms->get(i); + uniforms->replaceProperty(i, newValue); + } + } + } + } + } + + // File version 13: introduction of struct properties for camera viewport, frustum, and material/meshnode blend options + if (deserializedIR.fileVersion < 13) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "PerspectiveCamera" || instanceType == "OrthographicCamera") { + auto viewport = new Property{{}, {"Viewport"}, {}}; + (*viewport)->addProperty("offsetX", dynObj->extractProperty("viewPortOffsetX"), -1); + (*viewport)->addProperty("offsetY", dynObj->extractProperty("viewPortOffsetY"), -1); + (*viewport)->addProperty("width", dynObj->extractProperty("viewPortWidth"), -1); + (*viewport)->addProperty("height", dynObj->extractProperty("viewPortHeight"), -1); + dynObj->addProperty("viewport", viewport, -1); + } + + if (instanceType == "PerspectiveCamera") { + auto frustum = new Property{{}, {"Frustum"}, {}}; + (*frustum)->addProperty("nearPlane", dynObj->extractProperty("near"), -1); + (*frustum)->addProperty("farPlane", dynObj->extractProperty("far"), -1); + (*frustum)->addProperty("fieldOfView", dynObj->extractProperty("fov"), -1); + (*frustum)->addProperty("aspectRatio", dynObj->extractProperty("aspect"), -1); + dynObj->addProperty("frustum", frustum, -1); + } + + if (instanceType == "OrthographicCamera") { + auto frustum = new Property{{}, {"Frustum"}, {}}; + (*frustum)->addProperty("nearPlane", dynObj->extractProperty("near"), -1); + (*frustum)->addProperty("farPlane", dynObj->extractProperty("far"), -1); + (*frustum)->addProperty("leftPlane", dynObj->extractProperty("left"), -1); + (*frustum)->addProperty("rightPlane", dynObj->extractProperty("right"), -1); + (*frustum)->addProperty("bottomPlane", dynObj->extractProperty("bottom"), -1); + (*frustum)->addProperty("topPlane", dynObj->extractProperty("top"), -1); + dynObj->addProperty("frustum", frustum, -1); + } + + if (instanceType == "Material") { + auto options = new Property{{}, {"Options"}}; + (*options)->addProperty("blendOperationColor", dynObj->extractProperty("blendOperationColor"), -1); + (*options)->addProperty("blendOperationAlpha", dynObj->extractProperty("blendOperationAlpha"), -1); + (*options)->addProperty("blendFactorSrcColor", dynObj->extractProperty("blendFactorSrcColor"), -1); + (*options)->addProperty("blendFactorDestColor", dynObj->extractProperty("blendFactorDestColor"), -1); + (*options)->addProperty("blendFactorSrcAlpha", dynObj->extractProperty("blendFactorSrcAlpha"), -1); + (*options)->addProperty("blendFactorDestAlpha", dynObj->extractProperty("blendFactorDestAlpha"), -1); + (*options)->addProperty("blendColor", dynObj->extractProperty("blendColor"), -1); + (*options)->addProperty("depthwrite", dynObj->extractProperty("depthwrite"), -1); + (*options)->addProperty("depthFunction", dynObj->extractProperty("depthFunction"), -1); + (*options)->addProperty("cullmode", dynObj->extractProperty("cullmode"), -1); + dynObj->addProperty("options", options, -1); + } + + if (instanceType == "MeshNode" && dynObj->hasProperty("materials")) { + auto& materials = dynObj->get("materials")->asTable(); + + for (size_t i = 0; i < materials.size(); i++) { + Table& matCont = materials.get(i)->asTable(); + Table& optionsCont = matCont.get("options")->asTable(); + + auto options = new Property{{}, {"Options"}}; + (*options)->addProperty("blendOperationColor", optionsCont.get("blendOperationColor")->clone({}), -1); + (*options)->addProperty("blendOperationAlpha", optionsCont.get("blendOperationAlpha")->clone({}), -1); + (*options)->addProperty("blendFactorSrcColor", optionsCont.get("blendFactorSrcColor")->clone({}), -1); + (*options)->addProperty("blendFactorDestColor", optionsCont.get("blendFactorDestColor")->clone({}), -1); + (*options)->addProperty("blendFactorSrcAlpha", optionsCont.get("blendFactorSrcAlpha")->clone({}), -1); + (*options)->addProperty("blendFactorDestAlpha", optionsCont.get("blendFactorDestAlpha")->clone({}), -1); + (*options)->addProperty("blendColor", optionsCont.get("blendColor"), -1); + (*options)->addProperty("depthwrite", optionsCont.get("depthwrite"), -1); + (*options)->addProperty("depthFunction", optionsCont.get("depthFunction"), -1); + (*options)->addProperty("cullmode", optionsCont.get("cullmode"), -1); + + matCont.replaceProperty("options", options); + } + } + } + + for (auto& link : deserializedIR.links) { + // No need to check the object type of the endpoint since the property names alone are unique among top-level properties. + linkReplaceEndIfMatching(link, "viewPortOffsetX", {"viewport", "offsetX"}); + linkReplaceEndIfMatching(link, "viewPortOffsetY", {"viewport", "offsetY"}); + linkReplaceEndIfMatching(link, "viewPortWidth", {"viewport", "width"}); + linkReplaceEndIfMatching(link, "viewPortHeight", {"viewport", "height"}); + + linkReplaceEndIfMatching(link, "near", {"frustum", "nearPlane"}); + linkReplaceEndIfMatching(link, "far", {"frustum", "farPlane"}); + linkReplaceEndIfMatching(link, "fov", {"frustum", "fieldOfView"}); + linkReplaceEndIfMatching(link, "aspect", {"frustum", "aspectRatio"}); + linkReplaceEndIfMatching(link, "left", {"frustum", "leftPlane"}); + linkReplaceEndIfMatching(link, "right", {"frustum", "rightPlane"}); + linkReplaceEndIfMatching(link, "bottom", {"frustum", "bottomPlane"}); + linkReplaceEndIfMatching(link, "top", {"frustum", "topPlane"}); + } + } + + // 14 : Replaced "U/V Origin" enum with Texture flip flag + // Origin "Top Left"->flag enabled + if (deserializedIR.fileVersion < 14) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "Texture") { + constexpr int TEXTURE_ORIGIN_BOTTOM(0); + constexpr int TEXTURE_ORIGIN_TOP(1); + int oldValue = TEXTURE_ORIGIN_BOTTOM; + if (dynObj->hasProperty("origin")) { + oldValue = dynObj->get("origin")->asInt(); + dynObj->removeProperty("origin"); + } + + bool flipTexture = oldValue == TEXTURE_ORIGIN_TOP; + dynObj->addProperty("flipTexture", new Property{flipTexture, DisplayNameAnnotation("Flip U/V Origin")}, -1); + } + } + } + + // File version 15: offscreen rendering + // - changed texture uniform type for normal 2D textures from STexture -> STextureSampler2DBase + if (deserializedIR.fileVersion < 15) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + auto migrateUniforms = [](Table& uniforms) { + for (size_t i = 0; i < uniforms.size(); i++) { + auto engineType = uniforms.get(i)->query()->type(); + if (engineType == raco::core::EnginePrimitive::TextureSampler2D) { + auto newValue = ProxyObjectFactory::staticCreateProperty({}, {engineType}); + *newValue = uniforms.get(i)->asRef(); + uniforms.replaceProperty(i, newValue); + } + } + }; + + if (instanceType == "Material" && dynObj->hasProperty("uniforms")) { + auto& uniforms = dynObj->get("uniforms")->asTable(); + migrateUniforms(uniforms); + } + + if (instanceType == "MeshNode" && dynObj->hasProperty("materials")) { + auto* materials = &dynObj->get("materials")->asTable(); + + for (size_t i = 0; i < materials->size(); i++) { + Table& matCont = materials->get(i)->asTable(); + Table& uniformsCont = matCont.get("uniforms")->asTable(); + migrateUniforms(uniformsCont); + } + } + } + + // create default render setup + // - tag top-level Nodes with "render_main" tag + // - create default RenderLayer and RenderPass + + SDynamicEditorObject perspCamera; + SDynamicEditorObject orthoCamera; + + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "PerspectiveCamera") { + perspCamera = dynObj; + } + + if (instanceType == "OrthographicCamera") { + orthoCamera = dynObj; + } + } + + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "Node" || instanceType == "MeshNode" || instanceType == "PrefabInstance") { + if (!dynObj->getParent()) { + auto tags = new Property{{}, {}, {}, {"Tags"}}; + tags->set(std::vector({"render_main"})); + dynObj->addProperty("tags", tags, -1); + } + } + } + + SDynamicEditorObject camera = perspCamera ? perspCamera : orthoCamera; + + auto layerID = QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString(); + auto mainLayer = std::make_shared("MainRenderLayer", layerID); + auto renderableTagsProp = new Property{{}, {}, {"Renderable Tags"}}; + (*renderableTagsProp)->addProperty("render_main", std::make_unique>(0)); + mainLayer->addProperty("renderableTags", renderableTagsProp, -1); + mainLayer->addProperty("sortOrder", new Property{2, {"Render Order"}, raco::core::EngineEnumeration::RenderLayerOrder}, -1); + + auto passID = QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString(); + auto mainPass = std::make_shared("MainRenderPass", passID); + + ValueBase* cameraProp = new Property{{}, {"Camera"}}; + if (camera) { + *cameraProp = camera; + } + mainPass->addProperty("camera", cameraProp, -1); + + auto layer0Prop = new Property{{}, {"Layer 0"}}; + *layer0Prop = mainLayer; + mainPass->addProperty("layer0", layer0Prop, -1); + + deserializedIR.objects.emplace_back(mainLayer); + deserializedIR.objects.emplace_back(mainPass); + } + + if (deserializedIR.fileVersion < 16) { + std::map> objectsWithAffectedProperties; + + for (const auto& link : deserializedIR.links) { + if (*link->isValid_ == false) { + continue; + } + + auto linkEndObjID = (*link->endObject_)->objectID(); + auto linkEndProps = link->endPropertyNamesVector(); + + if (linkEndProps.size() == 1) { + auto endProp = linkEndProps[0]; + + objectsWithAffectedProperties[linkEndObjID][0] = objectsWithAffectedProperties[linkEndObjID][0] || endProp == "rotation"; + objectsWithAffectedProperties[linkEndObjID][1] = objectsWithAffectedProperties[linkEndObjID][1] || endProp == "scale"; + } + } + + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType != "Node" && instanceType != "MeshNode") { + continue; + } + + auto& scaleVec = dynObj->get("scale")->asVec3f(); + auto& rotationVec = dynObj->get("rotation")->asVec3f(); + auto objID = dynObj->objectID(); + + constexpr auto EPSILON = 0.0001; + + auto rotationNotZero = rotationVec.x.asDouble() > EPSILON || rotationVec.y.asDouble() > EPSILON || rotationVec.z.asDouble() > EPSILON; + auto scaleNotUniform = std::abs(scaleVec.x.asDouble() - scaleVec.y.asDouble()) > EPSILON || std::abs(scaleVec.x.asDouble() - scaleVec.z.asDouble()) > EPSILON; + auto rotationLinked = objectsWithAffectedProperties[objID][0]; + auto scaleLinked = objectsWithAffectedProperties[objID][1]; + + std::string warningText; + + if ((rotationNotZero || rotationLinked) && (scaleNotUniform || scaleLinked)) { + warningText = fmt::format("This node has {} scaling values and {} rotation values{}.", + scaleLinked ? "linked" : "non-uniform", rotationLinked ? "linked" : "non-zero", + scaleLinked || rotationLinked ? " which may lead to non-uniform scaling values and non-zero rotation values" : ""); + } + + if (!warningText.empty()) { + deserializedIR.migrationObjWarnings[objID] = + fmt::format( + "{}\n" + "Due to a new Ramses version that changed transformation order (previously: Translation -> Rotation -> Scale, now: Translation -> Scale -> Rotation), this node may be rendered differently from the previous version." + "\n\n" + "This message will disappear after saving & reloading the project or when a new warning/error for this Node pops up.", + warningText); + } + } + } + + if (deserializedIR.fileVersion < 17) { + for (const auto& dynObj : deserializedIR.objects) { + if (dynObj->serializationTypeName() == "RenderLayer" && dynObj->hasProperty("sortOrder")) { + auto& sortOrder = *dynObj->get("sortOrder"); + switch (sortOrder.asInt()) { + case 0: + sortOrder.asInt() = 0; + break; + case 1: + sortOrder.asInt() = 0; + break; + case 2: + sortOrder.asInt() = 1; + break; + } + } + } + } + + // File version 19: Changed ProjectSettings::backgroundColor from Vec3f to Vec4f + if (deserializedIR.fileVersion < 19) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "ProjectSettings") { + auto bgColor3 = dynObj->extractProperty("backgroundColor"); + auto bgColor3Vec = bgColor3->asVec3f(); + Vec4f bgColor4Vec; + bgColor4Vec.x = bgColor3Vec.x; + bgColor4Vec.y = bgColor3Vec.y; + bgColor4Vec.z = bgColor3Vec.z; + bgColor4Vec.w = 1.0; + dynObj->addProperty("backgroundColor", new Property{bgColor4Vec, {"Display Background Color"}}, -1); + } + } + } + + // File version 21: Added mipmap flag to textures + if (deserializedIR.fileVersion < 21) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "Texture") { + dynObj->addProperty("generateMipmaps", new Property{false, DisplayNameAnnotation("Generate Mipmaps")}, -1); + } + } + } + + // File version 22: Added support for setting default resource folders per project + if (deserializedIR.fileVersion < 22) { + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "ProjectSettings") { + // The old resource folder settings from the RaCoPreferences are transferred into the ProjectSettings + // We do not have access to the RaCoPreferences class here, however, we can just parse the ini file directly instead. + const std::string projectSubdirectoryFilter = "projectSubDir"; + auto settings = raco::core::PathManager::preferenceSettings(); + auto resourceFolders = new Property{{}, {"Default Resource Folders"}}; + (*resourceFolders)->addProperty("imageSubdirectory", new Property{settings.value("imageSubdirectory", "images").toString().toStdString(), {"Images"}, {projectSubdirectoryFilter}}, -1); + (*resourceFolders)->addProperty("meshSubdirectory", new Property{settings.value("meshSubdirectory", "meshes").toString().toStdString(), {"Meshes"}, {projectSubdirectoryFilter}}, -1); + (*resourceFolders)->addProperty("scriptSubdirectory", new Property{settings.value("scriptSubdirectory", "scripts").toString().toStdString(), {"Scripts"}, {projectSubdirectoryFilter}}, -1); + (*resourceFolders)->addProperty("shaderSubdirectory", new Property{settings.value("shaderSubdirectory", "shaders").toString().toStdString(), {"Shaders"}, {projectSubdirectoryFilter}}, -1); + dynObj->addProperty("defaultResourceFolders", resourceFolders, -1); + } + } + } + + // The following code repairs URIs which have been "rerooted" incorrectly during paste. + if (deserializedIR.fileVersion < 23) { + for (const auto& dynObj : deserializedIR.objects) { + auto findContainingPrefabInstance = [](SEditorObject object) -> SEditorObject { + SEditorObject current = object; + while (current) { + if (auto inst = current->as()) { + return inst; + } + current = current->getParent(); + } + return nullptr; + }; + + auto mapFromInstance = [](SEditorObject obj, SEditorObject instance) -> SEditorObject { + if (Table* item = findItemByValue(instance->get("mapToInstance")->asTable(), obj)) { + return item->get(0)->asRef(); + } + return nullptr; + }; + + if (auto prefabInst = findContainingPrefabInstance(dynObj)) { + if (auto prefab = prefabInst->get("template")->asRef()) { + if (prefab->query()) { + for (size_t propIndex = 0; propIndex < dynObj->size(); propIndex++) { + if (dynObj->get(propIndex)->query()) { + const auto& propName = dynObj->name(propIndex); + + SEditorObject prefabObj = dynObj; + while (auto currentPrefabInst = findContainingPrefabInstance(prefabObj)) { + // The "maptoInstance" property of the PrefabInstance is only filled for the outermost PrefabInstance if + // there are nested instances. So we need to search the topmost PrefabInstance first: + while (auto parentPrefabInst = findContainingPrefabInstance(currentPrefabInst->getParent())) { + currentPrefabInst = parentPrefabInst; + } + prefabObj = mapFromInstance(prefabObj, currentPrefabInst); + } + + auto& prefabInstPropValue = dynObj->get(propIndex)->asString(); + const auto& prefabPropValue = prefabObj->get(propName)->asString(); + if (prefabInstPropValue != prefabPropValue) { + LOG_WARNING(raco::log_system::DESERIALIZATION, "Rewrite URI property '{}.{}': '{}' -> '{}' in project '{}'", + dynObj->objectName(), propName, + prefabInstPropValue, prefabPropValue, + deserializedIR.currentPath); + prefabInstPropValue = prefabPropValue; + } + } + } + } + } + } + } + } + + // File version 24: Deterministics object IDs for PrefabInstance child objects + if (deserializedIR.fileVersion < 24) { + std::vector sortedInstances; + + for (const auto& dynObj : deserializedIR.objects) { + auto instanceType = dynObj->serializationTypeName(); + + if (instanceType == "PrefabInstance") { + insertPrefabInstancesRecursive(dynObj, sortedInstances); + } + } + + for (auto instance : sortedInstances) { + const Table& instMapTable = instance->get("mapToInstance")->asTable(); + + for (size_t index = 0; index < instMapTable.size(); index++) { + const Table& item = instMapTable.get(index)->asTable(); + auto prefabChild = item.get(0)->asRef(); + auto instChild = item.get(1)->asRef(); + auto instChildID = EditorObject::XorObjectIDs(prefabChild->objectID(), instance->objectID()); + instChild->objectID_ = instChildID; + } + + auto dynInst = std::dynamic_pointer_cast(instance); + dynInst->removeProperty("mapToInstance"); + } + } +} + +} // namespace raco::serialization \ No newline at end of file diff --git a/datamodel/libCore/src/ProjectMigrationToV23.cpp b/datamodel/libCore/src/ProjectMigrationToV23.cpp new file mode 100644 index 00000000..71aadcec --- /dev/null +++ b/datamodel/libCore/src/ProjectMigrationToV23.cpp @@ -0,0 +1,1968 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "core/ProjectMigrationToV23.h" + +#include "core/Serialization.h" +#include "core/SerializationKeys.h" + +#include + +using namespace raco::serialization; + +namespace { + +std::string structPropMap_V22 = + R"___({ + "BlendOptions": { + "blendColor": "Vec4f::DisplayNameAnnotation", + "blendFactorDestAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorDestColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "cullmode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthFunction": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthwrite": "Bool::DisplayNameAnnotation" + }, + "CameraViewport": { + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation" + }, + "DefaultResourceDirectories": { + "imageSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "meshSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "scriptSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "shaderSubdirectory": "String::DisplayNameAnnotation::URIAnnotation" + }, + "OrthographicFrustum": { + "bottomPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "leftPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "rightPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "topPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + }, + "PerspectiveFrustum": { + "aspectRatio": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "fieldOfView": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + } + })___"; + +std::string userTypePropMap_V22 = + R"___({ + "Animation": { + "animationChannels": "Table::DisplayNameAnnotation", + "animationOutputs": "Table::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "loop": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "play": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "rewindOnStop": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "AnimationChannel": { + "animationIndex": "Int::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "samplerIndex": "Int::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaModules": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "LuaScriptModule": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec4f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "defaultResourceFolders": "DefaultResourceDirectories::DisplayNameAnnotation", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "RenderBuffer": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "format": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "RenderLayer": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "invertMaterialFilter": "Bool::DisplayNameAnnotation::EnumerationAnnotation", + "materialFilterTags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "renderableTags": "Table::RenderableTagContainerAnnotation::DisplayNameAnnotation", + "sortOrder": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation" + }, + "RenderPass": { + "camera": "BaseCamera::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "clearColor": "Vec4f::DisplayNameAnnotation", + "enableClearColor": "Bool::DisplayNameAnnotation", + "enableClearDepth": "Bool::DisplayNameAnnotation", + "enableClearStencil": "Bool::DisplayNameAnnotation", + "enabled": "Bool::DisplayNameAnnotation", + "layer0": "RenderLayer::DisplayNameAnnotation", + "layer1": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer2": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer3": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer4": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer5": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer6": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer7": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "order": "Int::DisplayNameAnnotation", + "target": "RenderTarget::DisplayNameAnnotation::EmptyReferenceAllowable" + }, + "RenderTarget": { + "buffer0": "RenderBuffer::DisplayNameAnnotation", + "buffer1": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer2": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer3": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer4": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer5": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer6": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer7": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "generateMipmaps": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + })___"; + + +std::string structPropMap_V21 = + R"___({ + "BlendOptions": { + "blendColor": "Vec4f::DisplayNameAnnotation", + "blendFactorDestAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorDestColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "cullmode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthFunction": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthwrite": "Bool::DisplayNameAnnotation" + }, + "CameraViewport": { + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicFrustum": { + "bottomPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "leftPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "rightPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "topPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + }, + "PerspectiveFrustum": { + "aspectRatio": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "fieldOfView": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + } + })___"; + +std::string userTypePropMap_V21 = + R"___({ + "Animation": { + "animationChannels": "Table::DisplayNameAnnotation", + "animationOutputs": "Table::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "loop": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "play": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "rewindOnStop": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "AnimationChannel": { + "animationIndex": "Int::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "samplerIndex": "Int::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaModules": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "LuaScriptModule": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec4f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "RenderBuffer": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "format": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "RenderLayer": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "invertMaterialFilter": "Bool::DisplayNameAnnotation::EnumerationAnnotation", + "materialFilterTags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "renderableTags": "Table::RenderableTagContainerAnnotation::DisplayNameAnnotation", + "sortOrder": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation" + }, + "RenderPass": { + "camera": "BaseCamera::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "clearColor": "Vec4f::DisplayNameAnnotation", + "enableClearColor": "Bool::DisplayNameAnnotation", + "enableClearDepth": "Bool::DisplayNameAnnotation", + "enableClearStencil": "Bool::DisplayNameAnnotation", + "enabled": "Bool::DisplayNameAnnotation", + "layer0": "RenderLayer::DisplayNameAnnotation", + "layer1": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer2": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer3": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer4": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer5": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer6": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer7": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "order": "Int::DisplayNameAnnotation", + "target": "RenderTarget::DisplayNameAnnotation::EmptyReferenceAllowable" + }, + "RenderTarget": { + "buffer0": "RenderBuffer::DisplayNameAnnotation", + "buffer1": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer2": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer3": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer4": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer5": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer6": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer7": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "generateMipmaps": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + })___"; + + +std::string& structPropMap_V20 = structPropMap_V21; + +std::string userTypePropMap_V20 = + R"___({ + "Animation": { + "animationChannels": "Table::DisplayNameAnnotation", + "animationOutputs": "Table::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "loop": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "play": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "rewindOnStop": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "AnimationChannel": { + "animationIndex": "Int::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "samplerIndex": "Int::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaModules": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "LuaScriptModule": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec4f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "RenderBuffer": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "format": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "RenderLayer": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "invertMaterialFilter": "Bool::DisplayNameAnnotation::EnumerationAnnotation", + "materialFilterTags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "renderableTags": "Table::RenderableTagContainerAnnotation::DisplayNameAnnotation", + "sortOrder": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation" + }, + "RenderPass": { + "camera": "BaseCamera::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "clearColor": "Vec4f::DisplayNameAnnotation", + "enableClearColor": "Bool::DisplayNameAnnotation", + "enableClearDepth": "Bool::DisplayNameAnnotation", + "enableClearStencil": "Bool::DisplayNameAnnotation", + "enabled": "Bool::DisplayNameAnnotation", + "layer0": "RenderLayer::DisplayNameAnnotation", + "layer1": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer2": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer3": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer4": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer5": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer6": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer7": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "order": "Int::DisplayNameAnnotation", + "target": "RenderTarget::DisplayNameAnnotation::EmptyReferenceAllowable" + }, + "RenderTarget": { + "buffer0": "RenderBuffer::DisplayNameAnnotation", + "buffer1": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer2": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer3": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer4": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer5": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer6": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer7": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + })___"; + + +std::string& structPropMap_V19 = structPropMap_V20; + +std::string userTypePropMap_V19 = + R"___({ + "Animation": { + "animationChannels": "Table::DisplayNameAnnotation", + "animationOutputs": "Table::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "loop": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "play": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "rewindOnStop": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "AnimationChannel": { + "animationIndex": "Int::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "samplerIndex": "Int::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec4f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "RenderBuffer": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "format": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "RenderLayer": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "invertMaterialFilter": "Bool::DisplayNameAnnotation::EnumerationAnnotation", + "materialFilterTags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "renderableTags": "Table::RenderableTagContainerAnnotation::DisplayNameAnnotation", + "sortOrder": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation" + }, + "RenderPass": { + "camera": "BaseCamera::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "clearColor": "Vec4f::DisplayNameAnnotation", + "enableClearColor": "Bool::DisplayNameAnnotation", + "enableClearDepth": "Bool::DisplayNameAnnotation", + "enableClearStencil": "Bool::DisplayNameAnnotation", + "enabled": "Bool::DisplayNameAnnotation", + "layer0": "RenderLayer::DisplayNameAnnotation", + "layer1": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer2": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer3": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer4": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer5": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer6": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer7": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "order": "Int::DisplayNameAnnotation", + "target": "RenderTarget::DisplayNameAnnotation::EmptyReferenceAllowable" + }, + "RenderTarget": { + "buffer0": "RenderBuffer::DisplayNameAnnotation", + "buffer1": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer2": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer3": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer4": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer5": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer6": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer7": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } +})___"; + +std::string& structPropMap_V18 = structPropMap_V19; + +std::string userTypePropMap_V18 = + R"___({ + "Animation": { + "animationChannels": "Table::DisplayNameAnnotation", + "animationOutputs": "Table::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "loop": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "play": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "rewindOnStop": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "AnimationChannel": { + "animationIndex": "Int::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "samplerIndex": "Int::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec3f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "RenderBuffer": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "format": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "RenderLayer": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "invertMaterialFilter": "Bool::DisplayNameAnnotation::EnumerationAnnotation", + "materialFilterTags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "renderableTags": "Table::RenderableTagContainerAnnotation::DisplayNameAnnotation", + "sortOrder": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation" + }, + "RenderPass": { + "camera": "BaseCamera::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "clearColor": "Vec4f::DisplayNameAnnotation", + "enableClearColor": "Bool::DisplayNameAnnotation", + "enableClearDepth": "Bool::DisplayNameAnnotation", + "enableClearStencil": "Bool::DisplayNameAnnotation", + "enabled": "Bool::DisplayNameAnnotation", + "layer0": "RenderLayer::DisplayNameAnnotation", + "layer1": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer2": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer3": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer4": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer5": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer6": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer7": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "order": "Int::DisplayNameAnnotation", + "target": "RenderTarget::DisplayNameAnnotation::EmptyReferenceAllowable" + }, + "RenderTarget": { + "buffer0": "RenderBuffer::DisplayNameAnnotation", + "buffer1": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer2": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer3": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer4": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer5": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer6": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer7": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + })___"; + + +std::string& structPropMap_V14 = structPropMap_V18; + +std::string userTypePropMap_V14 = + R"___({ + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec3f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + } +)___"; + + +std::string structPropMap_V13 = structPropMap_V14; + +std::string userTypePropMap_V13 = + R"___({ + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec3f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "origin": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + } +)___"; + + +std::string structPropMap_V12 = "{}"; + +std::string userTypePropMap_V12 = + R"___({ + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "blendColor": "Vec4f::DisplayNameAnnotation", + "blendFactorDestAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorDestColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "cullmode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthFunction": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthwrite": "Bool::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "bottom": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "far": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "left": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "near": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "right": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "top": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortHeight": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortWidth": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "aspect": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "far": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "fov": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "near": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortHeight": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortWidth": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec3f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "origin": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + })___"; + +std::string structPropMap_V11 = "{}"; + +std::string& userTypePropMap_V11 = userTypePropMap_V12; + + +std::string structPropMap_V10 = "{}"; + +std::string userTypePropMap_V10 = + R"___({ + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "blendColor": "Vec4f::DisplayNameAnnotation", + "blendFactorDestAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorDestColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "cullmode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthFunction": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthwrite": "Bool::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "bottom": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "far": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "left": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "near": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "right": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "top": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortHeight": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortWidth": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "aspect": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "far": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "fov": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "near": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortHeight": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortOffsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "viewPortWidth": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "origin": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + })___"; + + +std::string structPropMap_V9 = "{}"; + +std::string userTypePropMap_V9 = + R"___({ + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "blendColor": "Vec4f::DisplayNameAnnotation", + "blendFactorDestAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorDestColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "cullmode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthFunction": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthwrite": "Bool::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "bottom": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "far": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "left": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "near": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "right": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "top": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "Vec4i::DisplayNameAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "aspect": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "far": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "fov": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "near": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "Vec4i::DisplayNameAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "origin": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + })___"; + + +} // namespace + +namespace raco::serializationToV23 { + +QJsonDocument migrateProjectToV23(const QJsonDocument& document) { + int const documentVersion = deserializeFileVersion(document); + + QJsonObject documentObject{document.object()}; + + // File Version 9: External references + if (documentVersion < 9) { + documentObject.insert(keys::EXTERNAL_PROJECTS, QJsonDocument::fromJson("{}").object()); + } + + if (documentVersion < 23) { + // V7 and V8 same as V9 + if (documentVersion < 10) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V9.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V9.c_str()).object()); + } else if (documentVersion < 11) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V10.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V10.c_str()).object()); + } else if (documentVersion < 12) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V11.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V11.c_str()).object()); + } else if (documentVersion < 13) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V12.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V12.c_str()).object()); + } else if (documentVersion < 14) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V13.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V13.c_str()).object()); + } else if (documentVersion < 15) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V14.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V14.c_str()).object()); + } else if (documentVersion < 19) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V18.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V18.c_str()).object()); + } else if (documentVersion < 20) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V19.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V19.c_str()).object()); + } else if (documentVersion < 21) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V20.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V20.c_str()).object()); + } else if (documentVersion < 22) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V21.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V21.c_str()).object()); + } else if (documentVersion < 23) { + documentObject.insert(keys::USER_TYPE_PROP_MAP, QJsonDocument::fromJson(userTypePropMap_V22.c_str()).object()); + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonDocument::fromJson(structPropMap_V22.c_str()).object()); + } + } + + QJsonDocument newDocument{documentObject}; + // for debugging: + //auto migratedJSON = QString(newDocument.toJson()).toStdString(); + return newDocument; +} + +} // namespace raco::core \ No newline at end of file diff --git a/datamodel/libCore/src/ProxyObjectFactory.cpp b/datamodel/libCore/src/ProxyObjectFactory.cpp new file mode 100644 index 00000000..9ed8dfb4 --- /dev/null +++ b/datamodel/libCore/src/ProxyObjectFactory.cpp @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "core/ProxyObjectFactory.h" + +#include "core/ProxyTypes.h" +#include "core/ExternalReferenceAnnotation.h" + +namespace raco::serialization::proxy { + + template + SEditorObject ProxyObjectFactory::createObjectInternal(const std::string& name, const std::string& id) { + return std::make_shared(name, id); + } + + template + std::shared_ptr ProxyObjectFactory::createAnnotationInternal() { + return std::make_shared(); + } + + template + data_storage::ValueBase* ProxyObjectFactory::createValueInternal() { + return new Value>(); + } + + template + std::map ProxyObjectFactory::makeTypeMap() { + return std::map{ + {Args::typeDescription.typeName, { Args::typeDescription, createObjectInternal, createValueInternal }}...}; + } + + template + constexpr std::pair> createTypeMapPair() { + return { TPropertyType().typeName(), []() { return new TPropertyType(); } }; + } + + template + std::map ProxyObjectFactory::makePropertyMapTuple(std::tuple* dummy) { + return std::map{ createTypeMapPair()...}; + } + + template + std::map ProxyObjectFactory::makeAnnotationMap() { + return std::map{ + {Args::typeDescription.typeName, { Args::typeDescription, createAnnotationInternal }}...}; + } + + + ProxyObjectFactory::ProxyObjectFactory() { + properties_ = makePropertyMapTuple(static_cast(nullptr)); + + // This contains proxy types defined in ProxyTypes.h from namespace raco::serialization::proxy + // Don't add the normal user_types here. + // Instead create a new proxy type in ProxyTypes.h and add that in the call below. + types_ = makeTypeMap< + ProjectSettings, + Animation, + AnimationChannel, + CubeMap, + Node, + MeshNode, + Mesh, + Material, + Prefab, + PrefabInstance, + OrthographicCamera, + PerspectiveCamera, + LuaScript, + LuaScriptModule, + Texture, + RenderBuffer, + RenderLayer, + RenderTarget, + RenderPass + >(); + + annotations_ = makeAnnotationMap< + raco::core::ExternalReferenceAnnotation + >(); + } + + ProxyObjectFactory& ProxyObjectFactory::getInstance() { + static ProxyObjectFactory* instance = nullptr; + if (!instance) { + instance = new ProxyObjectFactory(); + } + return *instance; + } + + SEditorObject ProxyObjectFactory::createObject(const std::string& type, const std::string& name, const std::string& id) const { + auto it = types_.find(type); + if (it != types_.end()) { + return it->second.createFunc(name, id); + } + + return SEditorObject(); + } + + std::shared_ptr ProxyObjectFactory::createAnnotation(const std::string& type) const { + auto it = annotations_.find(type); + if (it != annotations_.end()) { + return it->second.createFunc(); + } + return nullptr; + } + + data_storage::ValueBase* ProxyObjectFactory::createValue(const std::string& type) const { + { + auto it = types_.find(type); + if (it != types_.end()) { + return it->second.createValueFunc(); + } + } + { + auto it = properties_.find(type); + if (it != properties_.end()) { + return it->second(); + } + } + return new Value(); + } + + const std::map& ProxyObjectFactory::getTypes() const { + return types_; + } + + bool ProxyObjectFactory::isUserCreatable(const std::string& type) const { + return true; + } + + const std::map& ProxyObjectFactory::getProperties() const { + return properties_; + } + + } // namespace raco::user_types \ No newline at end of file diff --git a/datamodel/libCore/src/ProxyTypes.cpp b/datamodel/libCore/src/ProxyTypes.cpp new file mode 100644 index 00000000..96d71348 --- /dev/null +++ b/datamodel/libCore/src/ProxyTypes.cpp @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "core/ProxyTypes.h" + +namespace raco::serialization::proxy { + +const char projectSettingsTypeName[] = "ProjectSettings"; +const char meshTypeName[] = "Mesh"; +const char nodeTypeName[] = "Node"; +const char meshNodeTypeName[] = "MeshNode"; +const char materialTypeName[] = "Material"; +const char luaScriptTypeName[] = "LuaScript"; +const char luaScriptModuleTypeName[] = "LuaScriptModule"; +const char animationTypeName[] = "Animation"; +const char animationChannelTypeName[] = "AnimationChannel"; +const char textureSampler2DBaseTypeName[] = "TextureSampler2DBase"; +const char textureTypeName[] = "Texture"; +const char cubeMapTypeName[] = "CubeMap"; +const char baseCameraTypeName[] = "BaseCamera"; +const char perspectiveCameraTypeName[] = "PerspectiveCamera"; +const char orthographicCameraTypeName[] = "OrthographicCamera"; +const char renderBufferTypeName[] = "RenderBuffer"; +const char renderLayerTypeName[] = "RenderLayer"; +const char renderPassTypeName[] = "RenderPass"; +const char renderTargetTypeName[] = "RenderTarget"; +const char prefabTypeName[] = "Prefab"; +const char prefabInstanceTypeName[] = "PrefabInstance"; + +const char blendmodeOptionsTypeName[] = "BlendOptions"; +const char cameraViewportTypeName[] = "CameraViewport"; +const char perspectiveFrustumTypeName[] = "PerspectiveFrustum"; +const char orthographicFrustumTypeName[] = "OrthographicFrustum"; +const char defaultResourceDirectoriesTypeName[] = "DefaultResourceDirectories"; + +} diff --git a/datamodel/libCore/src/Queries.cpp b/datamodel/libCore/src/Queries.cpp index f7d11885..54febd75 100644 --- a/datamodel/libCore/src/Queries.cpp +++ b/datamodel/libCore/src/Queries.cpp @@ -760,12 +760,6 @@ bool Queries::linkWouldBeValid(const Project& project, const PropertyDescriptor& return isValidLinkStart(start) && isValidLinkEnd(end) && checkLinkCompatibleTypes(start, end) && linkWouldBeAllowed(project, start, end); } -std::vector Queries::filterForVisibleObjects(const std::vector& objects) { - std::vector result; - std::copy_if(objects.begin(), objects.end(), std::back_inserter(result), [](const auto& obj) { return Queries::isResource(obj) || Queries::isNotResource(obj); }); - return result; -} - std::vector Queries::filterForNotResource(const std::vector& objects) { std::vector result{}; std::copy_if(objects.begin(), objects.end(), std::back_inserter(result), Queries::isNotResource); @@ -781,6 +775,22 @@ std::vector Queries::filterByTypeName(const std::vector Queries::filterForTopLevelObjectsByTypeName(const std::vector& objects, const std::vector& typeNames) { + std::vector result{}; + std::copy_if(objects.begin(), objects.end(), std::back_inserter(result), + [&typeNames](const SEditorObject& object) { + return object->getParent() == nullptr && std::find(typeNames.begin(), typeNames.end(), object->getTypeDescription().typeName) != typeNames.end(); + }); + return result; +} + +std::vector Queries::filterForVisibleTopLevelObjects(const std::vector& objects) { + std::vector result; + std::copy_if(objects.begin(), objects.end(), std::back_inserter(result), [](const auto& obj) { return obj->getParent() == nullptr && (Queries::isResource(obj) || Queries::isNotResource(obj)); }); + return result; +} + + SEditorObjectSet Queries::collectAllChildren(std::vector baseObjects) { SEditorObjectSet children; for (auto obj : baseObjects) { diff --git a/datamodel/libCore/src/RamsesProjectMigration.cpp b/datamodel/libCore/src/RamsesProjectMigration.cpp deleted file mode 100644 index bf2f4192..00000000 --- a/datamodel/libCore/src/RamsesProjectMigration.cpp +++ /dev/null @@ -1,957 +0,0 @@ -/* - * SPDX-License-Identifier: MPL-2.0 - * - * This file is part of Ramses Composer - * (see https://github.com/GENIVI/ramses-composer). - * - * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. - * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -#include "core/RamsesProjectMigration.h" - -#include "core/Context.h" -#include "core/Link.h" -#include "core/Serialization.h" -#include "core/SerializationKeys.h" -#include "user_types/UserObjectFactory.h" - -#include "core/EngineInterface.h" -#include "user_types/EngineTypeAnnotation.h" - -#include "user_types/BaseCamera.h" -#include "user_types/DefaultValues.h" -#include "user_types/Enumerations.h" -#include "user_types/Material.h" -#include "user_types/OrthographicCamera.h" -#include "user_types/PerspectiveCamera.h" -#include "user_types/SyncTableWithEngineInterface.h" - -#include - -// Helper functions for migration - those are only implemented as far as they were needed. -// If you need more functionality, please add it. -namespace { - -std::string serializedProjectSetting() { - return - R"___({ - "properties" : { - "objectID" : ")___" + - QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString() + R"___(", - "objectName" : "", - "sceneId" : { - "annotations" : [ - { - "properties" : { - "max" : 1024, - "min" : 1 - }, - "typeName" : "RangeAnnotationInt" - } - ], - "value" : 123 - } - }, - "typeName" : "ProjectSettings" -})___"; -} - -std::string serializedRenderLayerV14(const std::string& layerID) { - return -R"---({ - "properties" : { - "invertMaterialFilter" : true, - "objectID" : ")---" + layerID + R"---(", - "objectName" : "MainRenderLayer", - "renderableTags" : { - "order" : ["render_main"], - "properties" : { - "render_main" : { - "typeName" : "Int", - "value" : 0 - } - } - }, - "sortOrder" : 2 - }, - "typeName" : "RenderLayer" -})---"; -} - -std::string serializedRenderPassV14(const std::string& layerID, const std::string& cameraID) { - std::string cameraIDValueString = cameraID.empty() ? "null" : ('\"' + cameraID + '\"'); - return - R"---({ - "properties" : { - "camera" : )---" + cameraIDValueString + R"---(, - "clearColor" : { - "w" : { - "annotations" : [ - { - "properties" : { - "max" : 1, - "min" : 0 - }, - "typeName" : "RangeAnnotationDouble" - } - ], - "value" : 0 - }, - "x" : { - "annotations" : [ - { - "properties" : { - "max" : 1, - "min" : 0 - }, - "typeName" : "RangeAnnotationDouble" - } - ], - "value" : 0 - }, - "y" : { - "annotations" : [ - { - "properties" : { - "max" : 1, - "min" : 0 - }, - "typeName" : "RangeAnnotationDouble" - } - ], - "value" : 0 - }, - "z" : { - "annotations" : [ - { - "properties" : { - "max" : 1, - "min" : 0 - }, - "typeName" : "RangeAnnotationDouble" - } - ], - "value" : 0 - } - }, - "enableClearColor" : true, - "enableClearDepth" : true, - "enableClearStencil" : true, - "enabled" : true, - "layer0" : ")---" + layerID + R"---(", - "layer1" : null, - "layer2" : null, - "layer3" : null, - "layer4" : null, - "layer5" : null, - "layer6" : null, - "layer7" : null, - "objectID" : ")---" + QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString() + R"---(", - "objectName" : "MainRenderPass", - "order" : 1, - "target" : null - }, - "typeName" : "RenderPass" -})---"; -} - - -// Helper object needed for readprop(...). Note that this clearly does not work for more complicated cases, so far (file version V9->V10) no calls to the deserializationFactory were needed. -// If you need a new object/property during migration, you need to make sure that you have the factory matching the version you are loading - and make sure you know what you are doing. -raco::serialization::DeserializationFactory deserializationFactoryV9() { - return raco::user_types::UserObjectFactoryInterface::deserializationFactory(nullptr); -} - -// Deserialization helper factory used from V10 onwards: -// This factory can create typed reference properties (e.g. texture uniforms) but can't create objects or annotations and -// should be sufficient to deserialize even complex properties. -raco::serialization::DeserializationFactory deserializationFactoryV10plus() { - auto objectFactory = &raco::user_types::UserObjectFactory::getInstance(); - return raco::serialization::DeserializationFactory{ - [objectFactory](const std::string& type) -> raco::serialization::SReflectionInterface { - if (type == raco::user_types::Link::typeDescription.typeName) { - return std::make_shared(); - } - return {}; - }, - {}, - [objectFactory](const std::string& type) { - return objectFactory->createValue(type); - }}; -} - -// Iterate over all instances in the JSON document. If the visitor returns true, the changes done to instanceproperties in the visitor will -// be stored in the JSON document. -void iterateInstances(QJsonObject& documentObject, std::function const& visitor) { - auto instances = documentObject[raco::serialization::keys::INSTANCES].toArray(); - for (QJsonValueRef instance : instances) { - auto o = instance.toObject(); - auto t = o[raco::serialization::keys::TYPENAME].toString(); - auto p = o[raco::serialization::keys::PROPERTIES].toObject(); - if (visitor(t, p)) { - o[raco::serialization::keys::PROPERTIES] = p; - instance = o; - } - } - documentObject[raco::serialization::keys::INSTANCES] = instances; -} - -// Read a property from a JSON block for the properties of a RaCo object. The passed in property parameter must have the exact -// type the property had when the migrated file version was saved, e. g. "data_storage::Property property;" -// Once the function returns, the value of the property can be extracted using property.as....(). -// This function requires deserializePropertyForMigration to be able to read the migrated file version (so if the code in deserializePropertyForMigration -// changes in a way that this is no longer possible, older file versions need to be migrated from with the original function). -template -raco::serialization::References readprop(raco::serialization::DeserializationFactory& deserializationFactory, QJsonObject const& instanceproperties, QStringView propname, PropertyType& property) { - auto const jsonprop = instanceproperties[propname]; - assert(!jsonprop.isUndefined()); - return raco::serialization::deserializePropertyForMigration(jsonprop, property, deserializationFactory); -} - -template -raco::serialization::References readprop(raco::serialization::DeserializationFactory& deserializationFactory, QJsonObject const& instanceproperties, QStringList propnames, PropertyType& property) { - QJsonValue v = instanceproperties; - for (auto index = 0; index < propnames.size(); index++) { - auto name = propnames[index]; - if (index > 0) { - v = v[raco::serialization::keys::PROPERTIES]; - } - v = v[name]; - } - if (!v.isUndefined()) { - return raco::serialization::deserializePropertyForMigration(v, property, deserializationFactory); - } - return {}; -} - -std::optional resolveRefIdFromReferences(const raco::serialization::References& references, const raco::data_storage::ValueBase& value) { - raco::data_storage::ValueBase* ptr = const_cast(&value); - auto it = references.find(ptr); - if (it != references.end()) { - return it->second; - } - return {}; -} - -// Add a property to a JSON block for the properties of a RaCo object. The passed in property parameter must have the exact -// type the property had in the class for the target file version, e. g. "data_storage::Property property;" -// This function requires serializePropertyForMigration to serialize the block consistent with the target file version (so if the code in serializePropertyForMigration -// changes in a way that this is no longer possible, older target file versions need to be migrated to with the original function). -template -void addprop(QJsonObject& instanceproperties, QStringView propname, PropertyType const& property, raco::serialization::References& references, bool dynamicallyTyped) { - auto v = raco::serialization::serializePropertyForMigration( - property, - [&references](const raco::data_storage::ValueBase& value) -> std::optional { - return resolveRefIdFromReferences(references, value); - }, - dynamicallyTyped); - assert(v.has_value()); - instanceproperties[propname] = *v; -} -template -void addprop(QJsonObject& instanceproperties, QStringView propname, PropertyType const& property, bool dynamicallyTyped = false) { - raco::serialization::References references; - addprop(instanceproperties, propname, property, references, dynamicallyTyped); -} - -QStringList intersperse(QStringList sl, QString separator) { - QStringList result; - for (auto index = 0; index < sl.size() - 1; index++) { - result.push_back(sl[index]); - result.push_back(separator); - } - result.push_back(sl.back()); - return result; -} - -template -void addprop_rec(QJsonObject& o, QStringList names, PropertyType const& property, bool dynamicallyTyped) { - if (names.size() == 1) { - addprop(o, names[0], property, dynamicallyTyped); - } else { - auto v = o[names[0]]; - auto vo = v.toObject(); - addprop_rec(vo, QStringList(++names.begin(), names.end()), property, dynamicallyTyped); - v = vo; - } -} - -template -void addprop(QJsonObject& instanceproperties, QStringList propnames, PropertyType const& property, bool dynamicallyTyped) { - addprop_rec(instanceproperties, intersperse(propnames, raco::serialization::keys::PROPERTIES), property, dynamicallyTyped); -} - -// Remove a property from a JSON block for the properties of a RaCo object. -void removeprop(QJsonObject& instanceproperties, QStringView propname) { - instanceproperties.remove(propname); -} - -void removeprop_rec(QJsonObject& o, QStringList names) { - if (names.size() == 1) { - o.remove(names[0]); - } else { - auto v = o[names[0]]; - auto vo = v.toObject(); - removeprop_rec(vo, QStringList(++names.begin(), names.end())); - v = vo; - } -} - -void removeprop(QJsonObject& instanceproperties, QStringList propnames) { - removeprop_rec(instanceproperties, intersperse(propnames, raco::serialization::keys::PROPERTIES)); -} - -template -raco::serialization::References extractprop(raco::serialization::DeserializationFactory& deserializationFactory, QJsonObject& instanceproperties, QStringView propname, PropertyType& property) { - if (instanceproperties.contains(propname)) { - auto refs = readprop(deserializationFactory, instanceproperties, propname, property); - removeprop(instanceproperties, propname); - return refs; - } - return {}; -} - -void linkEndPropPushFrontIfMatching(raco::core::SLink& link, const std::string& matching_prop, const std::string& prepend_prop) { - if (link->compareEndPropertyNames({matching_prop})) { - link->endProp_->set({prepend_prop, matching_prop}); - } -}; - -void linkReplaceEndIfMatching(raco::core::SLink& link, const std::string& oldProp, const std::vector& newEndProp) { - if (link->compareEndPropertyNames({oldProp})) { - link->endProp_->set(newEndProp); - } -}; - -} // namespace - -namespace raco::core { - -QJsonDocument migrateProject(const QJsonDocument& document, std::unordered_map& migrationWarnings) { - using namespace data_storage; - using namespace user_types; - - using LinkEndAnnotation = core::LinkEndAnnotation; - - int const documentVersion = raco::serialization::deserializeFileVersion(document); - - QJsonObject documentObject{document.object()}; - if (documentVersion < 2) { - auto instances = documentObject[raco::serialization::keys::INSTANCES].toArray(); - instances.push_back(QJsonDocument::fromJson(serializedProjectSetting().c_str()).object()); - documentObject[raco::serialization::keys::INSTANCES] = instances; - } - - // No file version number change for this change: added RangeAnnotation to MeshNode instanceCount property - if (documentVersion < 2) { - auto factory = deserializationFactoryV10plus(); - iterateInstances(documentObject, [&factory](QString const& instancetype, QJsonObject& instanceproperties) { - if (instancetype == "MeshNode") { - Property oldInstanceCount; - extractprop(factory, instanceproperties, u"instanceCount", oldInstanceCount); - - addprop(instanceproperties, u"instanceCount", Property>{*oldInstanceCount, DisplayNameAnnotation("Instance Count"), RangeAnnotation(1, 20)}); - - return true; - } - return false; - }); - } - - // File Version 3: added ProjectSettings viewport property - if (documentVersion < 3) { - iterateInstances(documentObject, [](QString const& instancetype, QJsonObject& instanceproperties) { - if (instancetype == "ProjectSettings") { - addprop(instanceproperties, u"viewport", Property{{{1440, 720}, 0, 4096}, {"Viewport"}}); - return true; - } - return false; - }); - } - - - // File Version 4..8: we might need to retroactively write the migration code for these - - // File Version 9: External references - if (documentVersion < 9) { - raco::serialization::serializeExternalProjectsMap(documentObject, {}); - } - - // Added without file version number change somewhere before V10. - if (documentVersion < 10) { - iterateInstances(documentObject, [](QString const& instancetype, QJsonObject& instanceproperties) { - if (instancetype == "ProjectSettings") { - addprop(instanceproperties, u"enableTimerFlag", Property{false, HiddenProperty()}); - addprop(instanceproperties, u"runTimer", Property{false, HiddenProperty()}); - return true; - } - return false; - }); - } - - - // File Version 10: cameras store viewport as four individual integers instead of a vec4i (for camera bindings). - if (documentVersion < 10) { - auto factory = deserializationFactoryV9(); - iterateInstances(documentObject, [&factory](QString const& instancetype, QJsonObject& instanceproperties) { - if (instancetype != "PerspectiveCamera" && instancetype != "OrthographicCamera") { - return false; - } - data_storage::Property oldviewportprop; - readprop(factory, instanceproperties, u"viewport", oldviewportprop); - removeprop(instanceproperties, u"viewport"); - - addprop(instanceproperties, u"viewPortOffsetX", data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i1_.asInt(), {-7680, 7680}, {"Viewport Offset X"}, {}}); - addprop(instanceproperties, u"viewPortOffsetY", data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i2_.asInt(), {-7680, 7680}, {"Viewport Offset Y"}, {}}); - addprop(instanceproperties, u"viewPortWidth", data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i3_.asInt(), {0, 7680}, {"Viewport Width"}, {}}); - addprop(instanceproperties, u"viewPortHeight", data_storage::Property, data_storage::DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asVec4i().i4_.asInt(), {0, 7680}, {"Viewport Height"}, {}}); - return true; - }); - } - - // File Version 11: Added the viewport background color to the ProjectSettings. - if (documentVersion < 11) { - iterateInstances(documentObject, [](QString const& instancetype, QJsonObject& instanceproperties) { - if (instancetype == "ProjectSettings") { - addprop(instanceproperties, u"backgroundColor", data_storage::Property{{}, {"Display Background Color"}}); - return true; - } - return false; - }); - } - - - // File version 12: - // Add 'private' property to material slot containers in MeshNodes. - // Rename 'depthfunction' -> 'depthFunction' in options container of meshnode material slot. - // Add LinkEndAnnotation to material uniform properties - if (documentVersion < 12) { - auto factory = deserializationFactoryV10plus(); - iterateInstances(documentObject, [&factory](const QString& instanceType, QJsonObject& instanceproperties) { - if (instanceType == "MeshNode" && instanceproperties.contains(u"materials")) { - Property materials; - - auto references = readprop(factory, instanceproperties, u"materials", materials); - removeprop(instanceproperties, u"materials"); - - for (size_t i = 0; i < materials->size(); i++) { - Table& matCont = materials->get(i)->asTable(); - matCont.addProperty("private", new data_storage::Property(true, {"Private Material"}), 1); - Table& optionsCont = matCont.get("options")->asTable(); - optionsCont.renameProperty("depthfunction", "depthFunction"); - } - - addprop(instanceproperties, u"materials", materials, references, false); - - return true; - } - - if (instanceType == "Material" && instanceproperties.contains(u"uniforms")) { - Property uniforms; - - auto references = readprop(factory, instanceproperties, u"uniforms", uniforms); - removeprop(instanceproperties, u"uniforms"); - - for (size_t i = 0; i < uniforms->size(); i++) { - auto engineType = uniforms->get(i)->query()->type(); - if (raco::core::PropertyInterface::primitiveType(engineType) != PrimitiveType::Ref) { - auto newValue = raco::user_types::createDynamicProperty(engineType); - *newValue = *uniforms->get(i); - uniforms->replaceProperty(i, newValue); - } - } - - addprop(instanceproperties, u"uniforms", uniforms, references, false); - return true; - } - - return false; - }); - } - - // File version 13: introduction of struct properties for camera viewport, frustum, and material/meshnode blend options - if (documentVersion < 13) { - auto factory = deserializationFactoryV10plus(); - iterateInstances(documentObject, [&factory](const QString& instanceType, QJsonObject& instanceproperties) { - bool changed = false; - if (instanceType == "PerspectiveCamera" || instanceType == "OrthographicCamera") { - Property, DisplayNameAnnotation, LinkEndAnnotation> offsetX{0, {-7680, 7680}, {"Viewport Offset X"}, {}}; - extractprop(factory, instanceproperties, u"viewPortOffsetX", offsetX); - - Property, DisplayNameAnnotation, LinkEndAnnotation> offsetY{0, {-7680, 7680}, {"Viewport Offset Y"}, {}}; - extractprop(factory, instanceproperties, u"viewPortOffsetY", offsetY); - - Property, DisplayNameAnnotation, LinkEndAnnotation> width{1440, {0, 7680}, {"Viewport Width"}, {}}; - extractprop(factory, instanceproperties, u"viewPortWidth", width); - - Property, DisplayNameAnnotation, LinkEndAnnotation> height{720, {0, 7680}, {"Viewport Height"}, {}}; - extractprop(factory, instanceproperties, u"viewPortHeight", height); - - Property viewport{{}, {"Viewport"}, {}}; - viewport->offsetX_.assign(offsetX, true); - viewport->offsetY_.assign(offsetY, true); - viewport->width_.assign(width, true); - viewport->height_.assign(height, true); - - addprop(instanceproperties, u"viewport", viewport); - - changed = true; - } - - if (instanceType == "PerspectiveCamera") { - Property, LinkEndAnnotation> near{0.1, DisplayNameAnnotation("Near Plane"), RangeAnnotation(0.1, 1.0), {}}; - extractprop(factory, instanceproperties, u"near", near); - - Property, LinkEndAnnotation> far{1000.0, DisplayNameAnnotation("Far Plane"), RangeAnnotation(100.0, 10000.0), {}}; - extractprop(factory, instanceproperties, u"far", far); - - Property, LinkEndAnnotation> fov{35.0, DisplayNameAnnotation("Field of View"), RangeAnnotation(10.0, 120.0), {}}; - extractprop(factory, instanceproperties, u"fov", fov); - - Property, LinkEndAnnotation> aspect{1440.0 / 720.0, DisplayNameAnnotation("Aspect"), RangeAnnotation(0.5, 4.0), {}}; - extractprop(factory, instanceproperties, u"aspect", aspect); - - Property frustum{{}, {"Frustum"}, {}}; - frustum->near_.assign(near, true); - frustum->far_.assign(far, true); - frustum->fov_.assign(fov, true); - frustum->aspect_.assign(aspect, true); - - addprop(instanceproperties, u"frustum", frustum); - - changed = true; - } - - if (instanceType == "OrthographicCamera") { - Property, LinkEndAnnotation> near{0.1, DisplayNameAnnotation("Near Plane"), RangeAnnotation(0.1, 1.0), {}}; - extractprop(factory, instanceproperties, u"near", near); - - Property, LinkEndAnnotation> far{1000.0, DisplayNameAnnotation("Far Plane"), RangeAnnotation(100.0, 10000.0), {}}; - extractprop(factory, instanceproperties, u"far", far); - - Property, LinkEndAnnotation> left{-10.0, DisplayNameAnnotation("Left Plane"), RangeAnnotation(-1000.0, 0.0), {}}; - extractprop(factory, instanceproperties, u"left", left); - - Property, LinkEndAnnotation> right{10.0, DisplayNameAnnotation("Right Plane"), RangeAnnotation(0.0, 1000.0), {}}; - extractprop(factory, instanceproperties, u"right", right); - - Property, LinkEndAnnotation> bottom{-10.0, DisplayNameAnnotation("Bottom Plane"), RangeAnnotation(-1000.0, 0.0), {}}; - extractprop(factory, instanceproperties, u"bottom", bottom); - - Property, LinkEndAnnotation> top{10.0, DisplayNameAnnotation("Top Plane"), RangeAnnotation(0.0, 1000.0), {}}; - extractprop(factory, instanceproperties, u"top", top); - - Property frustum{{}, {"Frustum"}, {}}; - frustum->near_.assign(near, true); - frustum->far_.assign(far, true); - frustum->left_.assign(left, true); - frustum->right_.assign(right, true); - frustum->bottom_.assign(bottom, true); - frustum->top_.assign(top, true); - - addprop(instanceproperties, u"frustum", frustum); - - changed = true; - } - - if (instanceType == "Material") { - Property depthwrite{true, DisplayNameAnnotation("Depth Write")}; - extractprop(factory, instanceproperties, u"depthwrite", depthwrite); - - Property depthFunction{DEFAULT_VALUE_MATERIAL_DEPTH_FUNCTION, DisplayNameAnnotation("Depth Function"), EnumerationAnnotation{EngineEnumeration::DepthFunction}}; - extractprop(factory, instanceproperties, u"depthFunction", depthFunction); - - Property cullmode{DEFAULT_VALUE_MATERIAL_CULL_MODE, DisplayNameAnnotation("Cull Mode"), EnumerationAnnotation{EngineEnumeration::CullMode}}; - extractprop(factory, instanceproperties, u"cullmode", cullmode); - - Property blendOperationColor{DEFAULT_VALUE_MATERIAL_BLEND_OPERATION_COLOR, {"Blend Operation Color"}, {EngineEnumeration::BlendOperation}}; - extractprop(factory, instanceproperties, u"blendOperationColor", blendOperationColor); - - Property blendOperationAlpha{DEFAULT_VALUE_MATERIAL_BLEND_OPERATION_ALPHA, {"Blend Operation Alpha"}, {EngineEnumeration::BlendOperation}}; - extractprop(factory, instanceproperties, u"blendOperationAlpha", blendOperationAlpha); - - Property blendFactorSrcColor{DEFAULT_VALUE_MATERIAL_BLEND_FACTOR_SRC_COLOR, {"Blend Factor Src Color"}, {EngineEnumeration::BlendFactor}}; - extractprop(factory, instanceproperties, u"blendFactorSrcColor", blendFactorSrcColor); - - Property blendFactorDestColor{DEFAULT_VALUE_MATERIAL_BLEND_FACTOR_DEST_COLOR, {"Blend Factor Dest Color"}, {EngineEnumeration::BlendFactor}}; - extractprop(factory, instanceproperties, u"blendFactorDestColor", blendFactorDestColor); - - Property blendFactorSrcAlpha{DEFAULT_VALUE_MATERIAL_BLEND_FACTOR_SRC_ALPHA, {"Blend Factor Src Alpha"}, {EngineEnumeration::BlendFactor}}; - extractprop(factory, instanceproperties, u"blendFactorSrcAlpha", blendFactorSrcAlpha); - - Property blendFactorDestAlpha{DEFAULT_VALUE_MATERIAL_BLEND_FACTOR_DEST_ALPHA, {"Blend Factor Dest Alpha"}, {EngineEnumeration::BlendFactor}}; - extractprop(factory, instanceproperties, u"blendFactorDestAlpha", blendFactorDestAlpha); - - Property blendColor{{}, {"Blend Color"}}; - extractprop(factory, instanceproperties, u"blendColor", blendColor); - - Property options{{}, {"Options"}}; - options->depthwrite_.assign(depthwrite, true); - options->depthFunction_.assign(depthFunction, true); - options->cullmode_.assign(cullmode, true); - options->blendOperationColor_.assign(blendOperationColor, true); - options->blendOperationAlpha_.assign(blendOperationAlpha, true); - options->blendFactorSrcColor_.assign(blendFactorSrcColor, true); - options->blendFactorDestColor_.assign(blendFactorDestColor, true); - options->blendFactorSrcAlpha_.assign(blendFactorSrcAlpha, true); - options->blendFactorDestAlpha_.assign(blendFactorDestAlpha, true); - options->blendColor_.assign(blendColor, true); - - addprop(instanceproperties, u"options", options); - - changed = true; - } - - if (instanceType == "MeshNode" && instanceproperties.contains(u"materials")) { - Property materials; - - auto references = readprop(factory, instanceproperties, u"materials", materials); - removeprop(instanceproperties, u"materials"); - - for (size_t i = 0; i < materials->size(); i++) { - Table& matCont = materials->get(i)->asTable(); - Table& optionsCont = matCont.get("options")->asTable(); - - auto options = new Property{{}, {"Options"}}; - (*options)->depthwrite_.assign(*optionsCont.get("depthwrite"), true); - (*options)->depthFunction_.assign(*optionsCont.get("depthFunction"), true); - (*options)->cullmode_.assign(*optionsCont.get("cullmode"), true); - (*options)->blendOperationColor_.assign(*optionsCont.get("blendOperationColor"), true); - (*options)->blendOperationAlpha_.assign(*optionsCont.get("blendOperationAlpha"), true); - (*options)->blendFactorSrcColor_.assign(*optionsCont.get("blendFactorSrcColor"), true); - (*options)->blendFactorDestColor_.assign(*optionsCont.get("blendFactorDestColor"), true); - (*options)->blendFactorSrcAlpha_.assign(*optionsCont.get("blendFactorSrcAlpha"), true); - (*options)->blendFactorDestAlpha_.assign(*optionsCont.get("blendFactorDestAlpha"), true); - (*options)->blendColor_.assign(*optionsCont.get("blendColor"), true); - - matCont.replaceProperty("options", options); - } - - addprop(instanceproperties, u"materials", materials, references, false); - - changed = true; - } - - return changed; - }); - - std::vector links; - auto inJsonLinks = documentObject[raco::serialization::keys::LINKS].toArray(); - raco::serialization::References references; - for (const auto& linkJson : inJsonLinks) { - links.push_back(std::dynamic_pointer_cast(raco::serialization::deserializeTypedObject(linkJson.toObject(), factory, references))); - } - - for (auto& link : links) { - // No need to check the object type of the endpoint since the property names alone are unique among top-level properties. - linkReplaceEndIfMatching(link, "viewPortOffsetX", {"viewport", "offsetX"}); - linkReplaceEndIfMatching(link, "viewPortOffsetY", {"viewport", "offsetY"}); - linkReplaceEndIfMatching(link, "viewPortWidth", {"viewport", "width"}); - linkReplaceEndIfMatching(link, "viewPortHeight", {"viewport", "height"}); - - linkReplaceEndIfMatching(link, "near", {"frustum", "nearPlane"}); - linkReplaceEndIfMatching(link, "far", {"frustum", "farPlane"}); - linkReplaceEndIfMatching(link, "fov", {"frustum", "fieldOfView"}); - linkReplaceEndIfMatching(link, "aspect", {"frustum", "aspectRatio"}); - linkReplaceEndIfMatching(link, "left", {"frustum", "leftPlane"}); - linkReplaceEndIfMatching(link, "right", {"frustum", "rightPlane"}); - linkReplaceEndIfMatching(link, "bottom", {"frustum", "bottomPlane"}); - linkReplaceEndIfMatching(link, "top", {"frustum", "topPlane"}); - } - - QJsonArray outJsonLinks{}; - for (const auto& link : links) { - outJsonLinks.push_back(raco::serialization::serializeTypedObject(*link.get(), - [&references](const raco::data_storage::ValueBase& value) -> std::optional { - return resolveRefIdFromReferences(references, value); - })); - } - - documentObject[raco::serialization::keys::LINKS] = outJsonLinks; - } - - if (documentVersion < 14) { - auto factory = deserializationFactoryV10plus(); - iterateInstances(documentObject, [&factory](const QString& instanceType, QJsonObject& instanceproperties) { - bool changed = false; - if (instanceType == "Texture") { - Property origin{ DEFAULT_VALUE_TEXTURE_ORIGIN_BOTTOM, DisplayNameAnnotation("U/V Origin"), EnumerationAnnotation{EngineEnumeration::TextureOrigin}}; - extractprop(factory, instanceproperties, u"origin", origin); - - Property flipTexture{false, DisplayNameAnnotation("Flip U/V Origin")}; - flipTexture = origin.asInt() == static_cast(raco::user_types::ETextureOrigin::Top); - - addprop(instanceproperties, u"flipTexture", flipTexture); - - changed = true; - } - - return changed; - }); - } - - // File version 15: offscreen rendering - // - changed texture uniform type for normal 2D textures from STexture -> STextureSampler2DBase - if (documentVersion < 15) { - auto factory = deserializationFactoryV10plus(); - - iterateInstances(documentObject, [&factory](const QString& instanceType, QJsonObject& instanceproperties) { - bool changed = false; - - auto migrateUniforms = [](Table& uniforms, raco::serialization::References& references) { - for (size_t i = 0; i < uniforms.size(); i++) { - auto engineType = uniforms.get(i)->query()->type(); - if (engineType == EnginePrimitive::TextureSampler2D) { - auto newValue = raco::user_types::createDynamicProperty<>(engineType); - auto oldValue = uniforms.get(i); - std::string valueObjectID; - if (references.find(oldValue) != references.end()) { - valueObjectID = references.at(uniforms.get(i)); - } - references.erase(oldValue); - uniforms.replaceProperty(i, newValue); - if (!valueObjectID.empty()) { - references[uniforms.get(i)] = valueObjectID; - } - } - } - }; - - if (instanceType == "Material" && instanceproperties.contains(u"uniforms")) { - Property uniforms; - auto references = readprop(factory, instanceproperties, u"uniforms", uniforms); - removeprop(instanceproperties, u"uniforms"); - - migrateUniforms(*uniforms, references); - - addprop(instanceproperties, u"uniforms", uniforms, references, false); - changed = true; - } - - auto sprops = QString(QJsonDocument(instanceproperties).toJson()).toStdString(); - - if (instanceType == "MeshNode" && instanceproperties.contains(u"materials")) { - Property materials; - auto references = readprop(factory, instanceproperties, u"materials", materials); - removeprop(instanceproperties, u"materials"); - - for (size_t i = 0; i < materials->size(); i++) { - Table& matCont = materials->get(i)->asTable(); - Table& uniformsCont = matCont.get("uniforms")->asTable(); - migrateUniforms(uniformsCont, references); - } - - addprop(instanceproperties, u"materials", materials, references, false); - changed = true; - } - - return changed; - }); - - // create default render setup - // - tag top-level Nodes with "render_main" tag - // - create default RenderLayer and RenderPass - - std::set childObjIds; - std::string perspCameraID; - std::string orthoCameraID; - iterateInstances(documentObject, [&factory, &childObjIds, &perspCameraID, &orthoCameraID](const QString& instanceType, QJsonObject& instanceproperties) { - if (instanceproperties.contains(u"children")) { - Property children{{}, {}, {}}; - auto references = readprop(factory, instanceproperties, u"children", children); - - for (size_t i = 0; i < children->size(); i++) { - auto vb = children->get(i); - auto it = references.find(vb); - if (it != references.end()) { - childObjIds.insert(it->second); - } - } - } - - if (instanceType == "PerspectiveCamera") { - Property objectID{std::string(), HiddenProperty()}; - readprop(factory, instanceproperties, u"objectID", objectID); - perspCameraID = *objectID; - } - - if (instanceType == "OrthographicCamera") { - Property objectID{std::string(), HiddenProperty()}; - readprop(factory, instanceproperties, u"objectID", objectID); - orthoCameraID = *objectID; - } - - return false; - }); - - iterateInstances(documentObject, [&factory, &childObjIds](const QString& instanceType, QJsonObject& instanceproperties) { - if (instanceType == "Node" || instanceType == "MeshNode" || instanceType == "PrefabInstance") { - Property objectID{std::string(), HiddenProperty()}; - readprop(factory, instanceproperties, u"objectID", objectID); - - if (childObjIds.find(*objectID) == childObjIds.end()) { - Property tags{{}, {}, {}, {"Tags"}}; - tags.set(std::vector({"render_main"})); - addprop(instanceproperties, u"tags", tags, false); - - return true; - } - } - - return false; - }); - - std::string cameraID = perspCameraID.empty() ? orthoCameraID : perspCameraID; - - auto instances = documentObject[raco::serialization::keys::INSTANCES].toArray(); - auto layerID = QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString(); - instances.push_back(QJsonDocument::fromJson(serializedRenderLayerV14(layerID).c_str()).object()); - instances.push_back(QJsonDocument::fromJson(serializedRenderPassV14(layerID, cameraID).c_str()).object()); - - documentObject[raco::serialization::keys::INSTANCES] = instances; - } - - if (documentVersion < 16) { - auto factory = deserializationFactoryV10plus(); - std::map> objectsWithAffectedProperties; - - auto inJsonLinks = documentObject[raco::serialization::keys::LINKS].toArray(); - for (const auto& linkJson : inJsonLinks) { - if (linkJson.toObject()[raco::serialization::keys::PROPERTIES].toObject()["isValid"].toBool() == false) { - continue; - } - - auto linkEndObjID = linkJson.toObject()[raco::serialization::keys::PROPERTIES].toObject()["endObject"].toString().toStdString(); - auto linkEndProps = linkJson.toObject()[raco::serialization::keys::PROPERTIES].toObject()["endProp"].toObject()[raco::serialization::keys::PROPERTIES].toArray(); - - if (linkEndProps.size() == 1) { - auto endProp = linkEndProps.first().toObject()["value"].toString(); - - objectsWithAffectedProperties[linkEndObjID][0] = objectsWithAffectedProperties[linkEndObjID][0] || endProp == "rotation"; - objectsWithAffectedProperties[linkEndObjID][1] = objectsWithAffectedProperties[linkEndObjID][1] || endProp == "scale"; - } - } - - iterateInstances(documentObject, [&migrationWarnings, &objectsWithAffectedProperties, &factory](const QString& instanceType, QJsonObject& instanceproperties) { - if (instanceType != "Node" && instanceType != "MeshNode") { - return false; - } - - auto changed = false; - Property scale{Vec3f(1.0, 0.1, 0.1, 100), DisplayNameAnnotation("Scaling"), {}}; - Property rotation{Vec3f(0.0, 5.0, -360.0, 360.0), DisplayNameAnnotation("Rotation"), {}}; - Property objID{{}}; - readprop(factory, instanceproperties, u"scale", scale); - readprop(factory, instanceproperties, u"rotation", rotation); - readprop(factory, instanceproperties, u"objectID", objID); - - constexpr auto EPSILON = 0.0001; - auto rotationVec = rotation.asVec3f(); - auto scaleVec = scale.asVec3f(); - - auto rotationNotZero = rotationVec.x.asDouble() > EPSILON || rotationVec.y.asDouble() > EPSILON || rotationVec.z.asDouble() > EPSILON; - auto scaleNotUniform = std::abs(scaleVec.x.asDouble() - scaleVec.y.asDouble()) > EPSILON || std::abs(scaleVec.x.asDouble() - scaleVec.z.asDouble()) > EPSILON; - auto rotationLinked = objectsWithAffectedProperties[objID.asString()][0]; - auto scaleLinked = objectsWithAffectedProperties[objID.asString()][1]; - - std::string warningText; - - if ((rotationNotZero || rotationLinked) && (scaleNotUniform || scaleLinked)) { - warningText = fmt::format("This node has {} scaling values and {} rotation values{}.", - scaleLinked ? "linked" : "non-uniform", rotationLinked ? "linked" : "non-zero", - scaleLinked || rotationLinked ? " which may lead to non-uniform scaling values and non-zero rotation values" : ""); - } - - if (!warningText.empty()) { - migrationWarnings[objID.asString()] = - fmt::format( - "{}\n" - "Due to a new Ramses version that changed transformation order (previously: Translation -> Rotation -> Scale, now: Translation -> Scale -> Rotation), this node may be rendered differently from the previous version." - "\n\n" - "This message will disappear after saving & reloading the project or when a new warning/error for this Node pops up.", - warningText); - - changed = true; - } - - return changed; - }); - } - - // File version 17: removed "Optimized" option from render layer sort options - if (documentVersion < 17) { - auto factory = deserializationFactoryV10plus(); - iterateInstances(documentObject, [&factory](const QString& instanceType, QJsonObject& instanceproperties) { - if (instanceType != "RenderLayer") { - return false; - } - Property sortOrder{1 }; // Neither the DisplayNameAnnotation nor the EnumerationAnnotation make a difference in the deserialization in this version - extractprop(factory, instanceproperties, u"sortOrder", sortOrder); - // Old enum: - // enum class ERenderLayerOrder { - // Optimized = 0, - // Manual = 0, - // SceneGraph - // }; - // New enum: - // enum class ERenderLayerOrder { - // Manual = 0, - // SceneGraph - // }; - // Optimized is mapped to Manual. - switch (sortOrder.asInt()) { - case 0: sortOrder.asInt() = 0; break; - case 1: sortOrder.asInt() = 0; break; - case 2: sortOrder.asInt() = 1; break; - } - addprop(instanceproperties, u"sortOrder", sortOrder); - return true; - }); - } - - // File version 19: Changed ProjectSettings::backgroundColor from Vec3f to Vec4f - if (documentVersion < 19) { - auto factory = deserializationFactoryV10plus(); - iterateInstances(documentObject, [&factory](const QString& instanceType, QJsonObject& instanceproperties) { - if (instanceType != "ProjectSettings") { - return false; - } - Property bgColor3; - extractprop(factory, instanceproperties, u"backgroundColor", bgColor3); - auto bgColor3Vec = bgColor3.asVec3f(); - Vec4f bgColor4Vec; - bgColor4Vec.x = bgColor3Vec.x.asDouble(); - bgColor4Vec.y = bgColor3Vec.y.asDouble(); - bgColor4Vec.z = bgColor3Vec.z.asDouble(); - bgColor4Vec.w = 1.0; - - addprop(instanceproperties, u"backgroundColor", Property { - bgColor4Vec, { "Display Background Color" } - }); - return true; - }); - } - - // File version 21: Added mipmap flag to textures - if (documentVersion < 21) { - auto factory = deserializationFactoryV10plus(); - iterateInstances(documentObject, [&factory](const QString& instanceType, QJsonObject& instanceproperties) { - if (instanceType != "Texture") { - return false; - } - Property generateMipMaps{false, DisplayNameAnnotation("Generate Mipmaps")}; - generateMipMaps = false; - - addprop(instanceproperties, u"generateMipmaps", generateMipMaps); - - return true; - }); - } - - QJsonDocument newDocument{documentObject}; - // for debugging: - //auto migratedJSON = QString(newDocument.toJson()).toStdString(); - return newDocument; -} - -} // namespace raco::core \ No newline at end of file diff --git a/datamodel/libCore/src/Serialization.cpp b/datamodel/libCore/src/Serialization.cpp index 2c7d1479..a10b9197 100644 --- a/datamodel/libCore/src/Serialization.cpp +++ b/datamodel/libCore/src/Serialization.cpp @@ -8,11 +8,21 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "core/Serialization.h" + +#include "core/DynamicEditorObject.h" +#include "core/EditorObject.h" +#include "core/Link.h" +#include "core/ProjectMigration.h" +#include "core/ProjectMigrationToV23.h" +#include "core/ProxyObjectFactory.h" #include "core/SerializationKeys.h" +#include "core/UserObjectFactoryInterface.h" +#include "user_types/UserObjectFactory.h" #include "data_storage/Table.h" -#include "utils/stdfilesystem.h" #include "log_system/log.h" +#include "utils/stdfilesystem.h" +#include "utils/u8path.h" #include #include @@ -20,6 +30,7 @@ #include using namespace raco::serialization; +using namespace raco::data_storage; namespace raco::serialization { @@ -27,17 +38,27 @@ bool operator==(const ExternalProjectInfo& lhs, const ExternalProjectInfo& rhs) return lhs.path == rhs.path && lhs.name == rhs.name; } -QJsonObject serializeTypedObject(const ReflectionInterface& object, const ResolveReferencedId& resolveReferenceId); +std::optional resolveReferenceId(const raco::data_storage::ValueBase& value) { + if (auto ref = value.asRef()) { + return ref->objectID(); + } else { + return {}; + } +} } // namespace raco::serialization namespace { +QJsonObject serializeTypedObject(const ReflectionInterface& object); + +SReflectionInterface deserializeTypedObject(const QJsonObject& jsonObject, const raco::core::UserObjectFactoryInterface& factory, References& references); + /** * Serialize a value base to it's primitive json counterpart. This function also maps references to the associated id via `resolveReferenceId`. * @return QJsonValue for the given `value`. Will be [QJsonValue::Null] if the `value` type cannot be mapped to a [QJsonValue] type. */ -QJsonValue serializePrimitiveValue(const ValueBase& value, const ResolveReferencedId& resolveReferenceId) { +QJsonValue serializePrimitiveValue(const ValueBase& value) { switch (value.type()) { case PrimitiveType::Bool: return QJsonValue{value.asBool()}; @@ -87,11 +108,11 @@ void deserializePrimitiveValue(const QJsonValue& jsonValue, ValueBase& value, Re } /** Serializations of annotations need to be handled separately. This is the only case where we require to serialize a typed object within a typed property. */ -std::optional serializeAnnotations(const std::vector& annotations, const ResolveReferencedId& resolveReferenceId, bool dynamicallyTyped) { +std::optional serializeAnnotations(const std::vector& annotations, bool dynamicallyTyped) { QJsonArray jsonArray{}; for (auto anno : annotations) { if (anno->serializationRequired() || dynamicallyTyped) { - jsonArray.push_back(serializeTypedObject(*anno, resolveReferenceId)); + jsonArray.push_back(serializeTypedObject(*anno)); } } if (jsonArray.size() > 0) { @@ -101,23 +122,23 @@ std::optional serializeAnnotations(const std::vector>& structPropTypesMap, bool dynamicallyTyped); +void deserializeArrayProperties(const QJsonArray& properties, ReflectionInterface& arrayInterface, References& references, const raco::core::UserObjectFactoryInterface& factory, const std::map>& structPropTypesMap, bool dynamicallyType); /** Deserializes result of `serializeAnnotations` from `annotations` into the annotations of the given `value`. */ -void deserializeAnnotations(const QJsonArray& annotations, const ValueBase& value, References& references, const DeserializationFactory& factory) { +void deserializeAnnotations(const QJsonArray& annotations, const ValueBase& value, References& references, const raco::core::UserObjectFactoryInterface& factory, const std::map>& structPropTypesMap) { for (const auto& annotation : annotations) { auto it = std::find_if(value.baseAnnotationPtrs().begin(), value.baseAnnotationPtrs().end(), [&annotation](const raco::data_storage::AnnotationBase* annoBase) { return annoBase->getTypeDescription().typeName == annotation[keys::TYPENAME].toString().toStdString(); }); - deserializeObjectProperties(annotation[keys::PROPERTIES].toObject(), **it, references, factory, false); + deserializeObjectProperties(annotation[keys::PROPERTIES].toObject(), **it, references, factory, structPropTypesMap, false); } } -std::optional serializeObjectAnnotations(const ClassWithReflectedMembers* object, const ResolveReferencedId& resolveReferenceId) { +std::optional serializeObjectAnnotations(const ClassWithReflectedMembers* object) { QJsonArray jsonArray{}; for (auto anno : object->annotations()) { - jsonArray.push_back(serializeTypedObject(*anno, resolveReferenceId)); + jsonArray.push_back(serializeTypedObject(*anno)); } if (jsonArray.size() > 0) { return jsonArray; @@ -126,31 +147,31 @@ std::optional serializeObjectAnnotations(const ClassWithReflectedMem } } -std::shared_ptr deserializeSingleObjectAnnotation(const QJsonObject& jsonObject, const DeserializationFactory& factory, References& references) { +std::shared_ptr deserializeSingleObjectAnnotation(const QJsonObject& jsonObject, const raco::core::UserObjectFactoryInterface& factory, const std::map>& structPropTypesMap, References& references) { auto object{factory.createAnnotation(jsonObject[keys::TYPENAME].toString().toStdString())}; - deserializeObjectProperties(jsonObject[keys::PROPERTIES].toObject(), *object.get(), references, factory, false); + deserializeObjectProperties(jsonObject[keys::PROPERTIES].toObject(), *object.get(), references, factory, structPropTypesMap, false); return object; } -void deserializeObjectAnnotations(const QJsonArray& annotations, ClassWithReflectedMembers* object, References& references, const DeserializationFactory& factory) { +void deserializeObjectAnnotations(const QJsonArray& annotations, ClassWithReflectedMembers* object, References& references, const raco::core::UserObjectFactoryInterface& factory, const std::map>& structPropTypesMap) { for (const auto& annotation : annotations) { - auto deserializedAnno = deserializeSingleObjectAnnotation(annotation.toObject(), factory, references); + auto deserializedAnno = deserializeSingleObjectAnnotation(annotation.toObject(), factory, structPropTypesMap, references); object->addAnnotation(deserializedAnno); } } -std::optional serializeObjectProperties(const ReflectionInterface& objectInterface, const ResolveReferencedId& resolveReferenceId, bool dynamicallyType); -std::optional serializeArrayProperties(const ReflectionInterface& arrayInterface, const ResolveReferencedId& resolveReferenceId, bool dynamicallyType); +std::optional serializeObjectProperties(const ReflectionInterface& objectInterface, bool dynamicallyType); +std::optional serializeArrayProperties(const ReflectionInterface& arrayInterface, bool dynamicallyType); /** * Main decision function for the seralization. Will call serializeArrayProperties, serializeObjectProperties or serializePrimitiveValue based on the type of `value`. * Further more compression will be applied based on how much information is needed for deseralization (e.g. `dynamicallyType`). * @return a QJsonValue which seralize the given `value`, based on the type of the `value` the returned QJsonValue can either be an Object, Array or an actual Value. */ -std::optional serializeValueBase(const ValueBase& value, const ResolveReferencedId& resolveReferenceId, bool dynamicallyTyped = false) { +std::optional serializeValueBase(const ValueBase& value, bool dynamicallyTyped = false) { bool childrenDynamicallyTyped{value.type() == PrimitiveType::Table}; bool valueIsClassType{hasTypeSubstructure(value.type())}; - auto annotations{serializeAnnotations(value.baseAnnotationPtrs(), resolveReferenceId, dynamicallyTyped)}; + auto annotations{serializeAnnotations(value.baseAnnotationPtrs(), dynamicallyTyped)}; if (dynamicallyTyped || annotations || childrenDynamicallyTyped) { // We need a object which holds typeName, properties/value and annotations. QJsonObject jsonObject{}; @@ -159,11 +180,11 @@ std::optional serializeValueBase(const ValueBase& value, const Resol if (valueIsClassType) { if (value.query()) { - if (auto properties{serializeArrayProperties(value.getSubstructure(), resolveReferenceId, childrenDynamicallyTyped)}) { + if (auto properties{serializeArrayProperties(value.getSubstructure(), childrenDynamicallyTyped)}) { jsonObject.insert(keys::PROPERTIES, properties.value()); } } else { - if (auto properties{serializeObjectProperties(value.getSubstructure(), resolveReferenceId, childrenDynamicallyTyped)}) { + if (auto properties{serializeObjectProperties(value.getSubstructure(), childrenDynamicallyTyped)}) { if (childrenDynamicallyTyped) { QJsonArray order{}; for (size_t i{0}; i < value.getSubstructure().size(); i++) { @@ -175,7 +196,7 @@ std::optional serializeValueBase(const ValueBase& value, const Resol } } } else { - jsonObject.insert(keys::VALUE, serializePrimitiveValue(value, resolveReferenceId)); + jsonObject.insert(keys::VALUE, serializePrimitiveValue(value)); } if (annotations) { jsonObject.insert(keys::ANNOTATIONS, annotations.value()); @@ -186,17 +207,32 @@ std::optional serializeValueBase(const ValueBase& value, const Resol } else if (valueIsClassType) { // We have a statically known class type which can be immediately seralized if (value.query()) { - return serializeArrayProperties(value.getSubstructure(), resolveReferenceId, false); + return serializeArrayProperties(value.getSubstructure(), false); } else { - return serializeObjectProperties(value.getSubstructure(), resolveReferenceId, false); + return serializeObjectProperties(value.getSubstructure(), false); } } else { // we have a primitive value which can be naivly serialized - return serializePrimitiveValue(value, resolveReferenceId); + return serializePrimitiveValue(value); + } +} + +void createMissingProperties(const std::map& propTypeMap, ReflectionInterface& object, const raco::core::UserObjectFactoryInterface& factory) { + auto intf = dynamic_cast(&object); + + for (const auto& [propertyName, typeName] : propTypeMap) { + if (!object.hasProperty(propertyName)) { + if (raco::data_storage::isPrimitiveTypeName(typeName)) { + intf->addProperty(propertyName, raco::data_storage::toPrimitiveType(typeName)); + } else { + // typeName: REF::Material + intf->addProperty(propertyName, factory.createValue(typeName)); + } + } } } -void createMissingProperties(const QJsonArray& order, const QJsonObject& jsonObject, raco::data_storage::Table& table, const DeserializationFactory& factory) { +void createMissingProperties(const QJsonArray& order, const QJsonObject& jsonObject, raco::data_storage::Table& table, const raco::core::UserObjectFactoryInterface& factory) { for (const auto& qPropertyName : order) { const std::string propertyName{qPropertyName.toString().toStdString()}; if (!table.hasProperty(propertyName)) { @@ -205,33 +241,31 @@ void createMissingProperties(const QJsonArray& order, const QJsonObject& jsonObj table.addProperty(propertyName, raco::data_storage::toPrimitiveType(typeName)); } else { // typeName: REF::Material - table.addProperty(propertyName, factory.createValueBase(typeName)); + table.addProperty(propertyName, factory.createValue(typeName)); } } } } -void createMissingProperties(const QJsonArray& jsonArray, raco::data_storage::Table& table, const DeserializationFactory& factory) { +void createMissingProperties(const QJsonArray& jsonArray, raco::data_storage::Table& table, const raco::core::UserObjectFactoryInterface& factory) { for (size_t i{0}; i < jsonArray.size(); i++) { if (!table[i]) { const std::string typeName{jsonArray[static_cast(i)].toObject()[keys::TYPENAME].toString().toStdString()}; if (raco::data_storage::isPrimitiveTypeName(typeName)) { table.addProperty(raco::data_storage::toPrimitiveType(typeName)); } else { - table.addProperty(factory.createValueBase(typeName)); + table.addProperty(factory.createValue(typeName)); } } } } /** Deserializes result of `serializeValueBase` from `property` into the given `value`. */ -void deserializeValueBase(const QJsonValue& property, ValueBase& value, References& references, const DeserializationFactory& factory, bool dynamicallyTyped = false) { +void deserializeValueBase(const QJsonValue& property, ValueBase& value, References& references, const raco::core::UserObjectFactoryInterface& factory, const std::map>& structPropTypesMap, bool dynamicallyTyped = false) { bool childrenDynamicallyTyped{value.type() == PrimitiveType::Table}; auto valueIsClassType{hasTypeSubstructure(value.type())}; auto hasAnnotations{property.isObject() && property.toObject().keys().contains(keys::ANNOTATIONS)}; - LOG_TRACE(raco::log_system::DESERIALIZATION, "{}, {}, {}", valueIsClassType, hasAnnotations, dynamicallyTyped); - if (dynamicallyTyped || hasAnnotations || childrenDynamicallyTyped) { auto propertyAsObject{property.toObject()}; if (valueIsClassType) { @@ -239,24 +273,31 @@ void deserializeValueBase(const QJsonValue& property, ValueBase& value, Referenc if (value.type() == PrimitiveType::Table) { createMissingProperties(propertyAsObject[keys::PROPERTIES].toArray(), value.asTable(), factory); } - deserializeArrayProperties(propertyAsObject[keys::PROPERTIES].toArray(), value.getSubstructure(), references, factory, childrenDynamicallyTyped); + deserializeArrayProperties(propertyAsObject[keys::PROPERTIES].toArray(), value.getSubstructure(), references, factory, structPropTypesMap, childrenDynamicallyTyped); } else { if (value.type() == PrimitiveType::Table) { createMissingProperties(propertyAsObject[keys::ORDER].toArray(), propertyAsObject[keys::PROPERTIES].toObject(), value.asTable(), factory); + } else if (value.type() == PrimitiveType::Struct) { + auto structTypeName = value.getSubstructure().getTypeDescription().typeName; + createMissingProperties(structPropTypesMap.at(structTypeName), value.getSubstructure(), factory); } - deserializeObjectProperties(propertyAsObject[keys::PROPERTIES].toObject(), value.getSubstructure(), references, factory, childrenDynamicallyTyped); + deserializeObjectProperties(propertyAsObject[keys::PROPERTIES].toObject(), value.getSubstructure(), references, factory, structPropTypesMap, childrenDynamicallyTyped); } } else { deserializePrimitiveValue(propertyAsObject[keys::VALUE], value, references); } if (hasAnnotations) { - deserializeAnnotations(propertyAsObject[keys::ANNOTATIONS].toArray(), value, references, factory); + deserializeAnnotations(propertyAsObject[keys::ANNOTATIONS].toArray(), value, references, factory, structPropTypesMap); } } else if (valueIsClassType) { if (property.isArray()) { - deserializeArrayProperties(property.toArray(), value.getSubstructure(), references, factory, false); + deserializeArrayProperties(property.toArray(), value.getSubstructure(), references, factory, structPropTypesMap, false); } else { - deserializeObjectProperties(property.toObject(), value.getSubstructure(), references, factory, false); + if (value.type() == PrimitiveType::Struct) { + auto structTypeName = value.getSubstructure().getTypeDescription().typeName; + createMissingProperties(structPropTypesMap.at(structTypeName), value.getSubstructure(), factory); + } + deserializeObjectProperties(property.toObject(), value.getSubstructure(), references, factory, structPropTypesMap, false); } } else { deserializePrimitiveValue(property, value, references); @@ -269,11 +310,11 @@ void deserializeValueBase(const QJsonValue& property, ValueBase& value, Referenc * * @return a QJsonArray which contains all properties which are accessable in the `interface`. Will return an empty optional if serialized array is empty. */ -std::optional serializeArrayProperties(const ReflectionInterface& arrayInterface, const ResolveReferencedId& resolveReferenceId, bool dynamicallyTyped = false) { +std::optional serializeArrayProperties(const ReflectionInterface& arrayInterface, bool dynamicallyTyped = false) { QJsonArray properties{}; for (size_t i{0}; i < arrayInterface.size(); i++) { const auto& property{arrayInterface.get(i)}; - if (auto child{serializeValueBase(*property, resolveReferenceId, dynamicallyTyped)}) { + if (auto child{serializeValueBase(*property, dynamicallyTyped)}) { properties.push_back(child.value()); } } @@ -285,9 +326,9 @@ std::optional serializeArrayProperties(const ReflectionInterface& ar } /** Deserializes result of `serializeArrayProperties` from `properties` into the given `interface`. */ -void deserializeArrayProperties(const QJsonArray& properties, ReflectionInterface& arrayInterface, References& references, const DeserializationFactory& factory, bool dynamicallyTyped = false) { +void deserializeArrayProperties(const QJsonArray& properties, ReflectionInterface& arrayInterface, References& references, const raco::core::UserObjectFactoryInterface& factory, const std::map>& structPropTypesMap, bool dynamicallyTyped = false) { for (size_t i{0}; i < properties.size(); i++) { - deserializeValueBase(properties[static_cast(i)].toObject(), *arrayInterface.get(i), references, factory, dynamicallyTyped); + deserializeValueBase(properties[static_cast(i)].toObject(), *arrayInterface.get(i), references, factory, structPropTypesMap, dynamicallyTyped); } } @@ -296,11 +337,11 @@ void deserializeArrayProperties(const QJsonArray& properties, ReflectionInterfac * { "objectID": "someID", "objectName": "someName", "translation": {...}, ... } * @return a QJsonObject which contains all properties which are accessable in the `interface`. Will return an empty optional if serialized object is empty. */ -std::optional serializeObjectProperties(const ReflectionInterface& propertiesInterface, const ResolveReferencedId& resolveReferenceId, bool dynamicallyTyped = false) { +std::optional serializeObjectProperties(const ReflectionInterface& propertiesInterface, bool dynamicallyTyped = false) { QJsonObject properties{}; for (size_t i{0}; i < propertiesInterface.size(); i++) { const auto& property{propertiesInterface.get(i)}; - if (auto serializedValue{serializeValueBase(*property, resolveReferenceId, dynamicallyTyped)}) { + if (auto serializedValue{serializeValueBase(*property, dynamicallyTyped)}) { properties.insert(propertiesInterface.name(i).c_str(), serializedValue.value()); } } @@ -312,39 +353,35 @@ std::optional serializeObjectProperties(const ReflectionInterface& } /** Deserializes result of `serializeObjectProperties` from `properties` into the given `interface`. */ -void deserializeObjectProperties(const QJsonObject& properties, ReflectionInterface& objectInterface, References& references, const DeserializationFactory& factory, bool dynamicallyTyped = false) { +void deserializeObjectProperties(const QJsonObject& properties, ReflectionInterface& objectInterface, References& references, const raco::core::UserObjectFactoryInterface& factory, const std::map>& structPropTypesMap, bool dynamicallyTyped = false) { for (const auto& qPropertyName : properties.keys()) { std::string name = qPropertyName.toStdString(); - LOG_TRACE(raco::log_system::DESERIALIZATION, "{}, {}", name, dynamicallyTyped); ValueBase& value = *objectInterface.get(objectInterface.index(name)); if (&value != nullptr) { - deserializeValueBase(properties[qPropertyName], value, references, factory, dynamicallyTyped); + deserializeValueBase(properties[qPropertyName], value, references, factory, structPropTypesMap, dynamicallyTyped); } else { LOG_WARNING(raco::log_system::DESERIALIZATION, "Dropping unsupported or deprecated property {}", name); } } } -} // namespace - -namespace raco::serialization { /** * We have some form of C++ Object. Either an EditorObject or an Annotation. * @return QJsonObject of form e.g.: { "typeName": "MeshNode", "properties": { ... } } */ -QJsonObject serializeTypedObject(const ReflectionInterface& object, const ResolveReferencedId& resolveReferenceId) { +QJsonObject serializeTypedObject(const ReflectionInterface& object) { QJsonObject jsonObject{}; jsonObject.insert(keys::TYPENAME, object.serializationTypeName().c_str()); auto cwrm = dynamic_cast(&object); if (cwrm) { - auto annotations{serializeObjectAnnotations(cwrm, resolveReferenceId)}; + auto annotations{serializeObjectAnnotations(cwrm)}; if (annotations) { jsonObject.insert(keys::ANNOTATIONS, annotations.value()); } } - if (auto properties{serializeObjectProperties(object, resolveReferenceId)}) { + if (auto properties{serializeObjectProperties(object)}) { jsonObject.insert(keys::PROPERTIES, properties.value()); } return jsonObject; @@ -355,30 +392,38 @@ QJsonObject serializeTypedObject(const ReflectionInterface& object, const Resolv * { "typeName": "MeshNode", "properties": { ... } } * @return an Object created by the `factory` for the given `jsonObject`. */ -SReflectionInterface deserializeTypedObject(const QJsonObject& jsonObject, const DeserializationFactory& factory, References& references) { - auto object{factory.createUserType(jsonObject[keys::TYPENAME].toString().toStdString())}; + +SReflectionInterface deserializeTypedObject(const QJsonObject& jsonObject, raco::core::UserObjectFactoryInterface& factory, References& references, const std::map>& typesPropTypesMap, const std::map>& structPropTypesMap) { + auto typeName = jsonObject[keys::TYPENAME].toString().toStdString(); + + SReflectionInterface object; + if (typeName == raco::core::Link::typeDescription.typeName) { + object = std::make_shared(); + } else { + object = factory.createObject(typeName); + } if (jsonObject.keys().contains(keys::ANNOTATIONS)) { deserializeObjectAnnotations(jsonObject[keys::ANNOTATIONS].toArray(), std::dynamic_pointer_cast(object).get(), - references, factory); + references, factory, structPropTypesMap); } - deserializeObjectProperties(jsonObject[keys::PROPERTIES].toObject(), *object.get(), references, factory); + if (typeName != raco::core::Link::typeDescription.typeName) { + createMissingProperties(typesPropTypesMap.at(typeName), *object, factory); + } + deserializeObjectProperties(jsonObject[keys::PROPERTIES].toObject(), *object, references, factory, structPropTypesMap); return object; } -} // namespace raco::serialization - -namespace { /** * Deserialize and log the values of version arrays that consist of the values [major.minor.patch]. * This includes Ramses, Logic Engine, and RaCo versions. * @return a QJsonArray holding the version array values. */ -QJsonArray deserializeVersionNumberArray(const QJsonDocument& document, const char* jsonVersionKey, const char* whichVersion) { - auto versionNums = QJsonArray{ProjectDeserializationInfo::NO_VERSION, ProjectDeserializationInfo::NO_VERSION, ProjectDeserializationInfo::NO_VERSION}; +DeserializedVersion deserializeVersionNumber(const QJsonDocument& document, const char* jsonVersionKey, const char* whichVersion) { + DeserializedVersion version = {ProjectDeserializationInfo::NO_VERSION, ProjectDeserializationInfo::NO_VERSION, ProjectDeserializationInfo::NO_VERSION}; if (document[jsonVersionKey].isUndefined()) { LOG_WARNING(raco::log_system::DESERIALIZATION, "{} version is not saved in project file", whichVersion); @@ -389,37 +434,21 @@ QJsonArray deserializeVersionNumberArray(const QJsonDocument& document, const ch } else if (deserializedVersionNums.size() > 3) { LOG_WARNING(raco::log_system::DESERIALIZATION, "{} version has not been saved correctly in project file - too many version values", whichVersion); } - - for (int i = 0; i < std::min(deserializedVersionNums.size(), versionNums.size()); ++i) { - versionNums[i] = deserializedVersionNums[i]; + + std::array versionNums {&version.major, &version.minor, &version.patch}; + for (int i = 0; i < std::min(static_cast(deserializedVersionNums.size()), versionNums.size()); ++i) { + *versionNums[i] = deserializedVersionNums[i].toInt(); } } - LOG_INFO(raco::log_system::DESERIALIZATION, "{} version from project file is {}.{}.{}", whichVersion, versionNums[0].toInt(), versionNums[1].toInt(), versionNums[2].toInt()); - return versionNums; + LOG_INFO(raco::log_system::DESERIALIZATION, "{} version from project file is {}.{}.{}", whichVersion, version.major, version.minor, version.patch); + return version; }; -} // namespace - -std::string raco::serialization::serializeObject(const SReflectionInterface& object, const std::string& originPath, const ResolveReferencedId& resolveReferenceId) { - return QJsonDocument{serializeTypedObject(*object.get(), resolveReferenceId)}.toJson().toStdString(); -} - -std::string ObjectsDeserialization::originPath() const { - return (std::filesystem::path(originFolder) / originFileName).generic_string(); -} - -ObjectDeserialization raco::serialization::deserializeObject(const std::string& json, const DeserializationFactory& factory) { - References references{}; - return { - deserializeTypedObject(QJsonDocument::fromJson(json.c_str()).object(), factory, references), - references}; -} - -void raco::serialization::serializeExternalProjectsMap(QJsonObject& outContainer, const std::map& externalProjectsMap) { +void serializeExternalProjectsMap(QJsonObject& outContainer, const std::map& externalProjectsMap) { QMap map; for (auto [id, info] : externalProjectsMap) { - auto qvinfo = QVariantMap({{keys::EXTERNAL_PROJECT_PATH, QString::fromStdString(info.path)}, + auto qvinfo = QVariantMap({{keys::EXTERNAL_PROJECT_PATH, QString::fromStdString(info.path)}, {keys::EXTERNAL_PROJECT_NAME, QString::fromStdString(info.name)}}); map[QString::fromStdString(id)] = QVariant(qvinfo); } @@ -448,43 +477,204 @@ void deserializeObjectOriginFolderMap(const QVariant& container, std::map> makeStructPropertyMap() { + auto& userFactory{raco::user_types::UserObjectFactory::getInstance()}; + + std::map> structPropTypesMap; + + for (const auto& [name, desc] : userFactory.getStructTypes()) { + auto obj = userFactory.createStruct(name); + + std::map propTypeMap; + for (size_t i = 0; i < obj->size(); i++) { + auto propName = obj->name(i); + auto propType = obj->get(i)->typeName(); + propTypeMap[propName] = propType; + } + structPropTypesMap[name] = propTypeMap; } - if (!container[keys::ORIGIN_FILENAME].isUndefined() && !container[keys::ORIGIN_FILENAME].toString().isEmpty()) { - result.originFileName = container[keys::ORIGIN_FILENAME].toString().toStdString(); + + return structPropTypesMap; +} + +QMap serializeUserTypePropertyMap(const std::map>& propTypeMap) { + QMap typesMap; + for (const auto& [userTypeName, propMap] : propTypeMap) { + QMap map; + for (const auto& [propName, propType] : propMap) { + map[QString::fromStdString(propName)] = QString::fromStdString(propType); + } + typesMap[QString::fromStdString(userTypeName)] = QVariant(map); } - if (!container[keys::ORIGIN_PROJECT_ID].isUndefined() && !container[keys::ORIGIN_PROJECT_ID].toString().isEmpty()) { - result.originProjectID = container[keys::ORIGIN_PROJECT_ID].toString().toStdString(); + return typesMap; +} + +std::map> deserializeUserTypePropertyMap(const QVariant& container) { + std::map> typesPropTypeMap; + + for (auto [name, propMap] : container.toMap().toStdMap()) { + auto qPropMap = propMap.toMap(); + std::map propTypeMap; + for (auto& [propName, propType] : qPropMap.toStdMap()) { + propTypeMap[propName.toStdString()] = propType.toString().toStdString(); + } + typesPropTypeMap[name.toStdString()] = propTypeMap; } - if (!container[keys::ORIGIN_PROJECT_NAME].isUndefined() && !container[keys::ORIGIN_PROJECT_NAME].toString().isEmpty()) { - result.originProjectName = container[keys::ORIGIN_PROJECT_NAME].toString().toStdString(); + return typesPropTypeMap; +} + +SReflectionInterface deserializeTypedObject(const QJsonObject& jsonObject, raco::core::UserObjectFactoryInterface& factory, References& references) { + return deserializeTypedObject(jsonObject, factory, references, makeUserTypePropertyMap(), makeStructPropertyMap()); +} + +using translateRefFunc = std::function; + +void convertObjectPropertiesIRToUser(const ReflectionInterface& dynObj, ReflectionInterface& userObj, translateRefFunc translateRef, raco::core::UserObjectFactoryInterface& factory); +void convertTablePropertiesIRToUser(const Table& src, Table& dest, translateRefFunc translateRef, raco::core::UserObjectFactoryInterface& factory); + +void convertPropertyAnnotationsIRToUser(const ValueBase& dynProp, ValueBase& userProp, translateRefFunc translateRef, raco::core::UserObjectFactoryInterface& factory, bool dynamicallyTyped) { + for (auto srcAnno : dynProp.baseAnnotationPtrs()) { + auto it = std::find_if(userProp.baseAnnotationPtrs().begin(), userProp.baseAnnotationPtrs().end(), [srcAnno](const raco::data_storage::AnnotationBase* destAnno) { + return destAnno->getTypeDescription().typeName == srcAnno->getTypeDescription().typeName; + }); + if (it != userProp.baseAnnotationPtrs().end() && + (dynamicallyTyped || srcAnno->serializationRequired())) { + convertObjectPropertiesIRToUser(*srcAnno, **it, translateRef, factory); + } } +} - deserializeExternalProjectsMap(container[keys::EXTERNAL_PROJECTS].toVariant(), result.externalProjectsMap); - deserializeObjectOriginFolderMap(container[keys::OBJECT_ORIGIN_FOLDERS].toVariant(), result.objectOriginFolders); +void convertValueBaseIRToUser(const ValueBase& dynProp, ValueBase& userProp, translateRefFunc translateRef, raco::core::UserObjectFactoryInterface& factory, bool dynamicallyTyped = false) { + if (userProp.type() == PrimitiveType::Table) { + convertTablePropertiesIRToUser(dynProp.asTable(), userProp.asTable(), translateRef, factory); + } else if (hasTypeSubstructure(userProp.type())) { + convertObjectPropertiesIRToUser(dynProp.getSubstructure(), userProp.getSubstructure(), translateRef, factory); + } else if (userProp.type() == PrimitiveType::Ref) { + userProp = translateRef(dynProp.asRef()); + } else { + // simple primitive type + userProp = dynProp; + } + convertPropertyAnnotationsIRToUser(dynProp, userProp, translateRef, factory, dynamicallyTyped); +} - auto rootObjectIDs = container[keys::ROOT_OBJECT_IDS].toVariant().toStringList(); - for (auto id : rootObjectIDs) { - result.rootObjectIDs.insert(id.toStdString()); +void convertTablePropertiesIRToUser(const Table& src, Table& dest, translateRefFunc translateRef, raco::core::UserObjectFactoryInterface& factory) { + for (size_t i = 0; i < src.size(); i++) { + auto propName = src.name(i); + auto typeName = src.get(i)->typeName(); + if (raco::data_storage::isPrimitiveTypeName(typeName)) { + dest.addProperty(propName, raco::data_storage::toPrimitiveType(typeName)); + } else { + dest.addProperty(propName, factory.createValue(typeName)); + } + convertValueBaseIRToUser(*src.get(i), *dest.get(i), translateRef, factory, true); } +} - for (const auto& objJson : container.value(keys::OBJECTS).toArray()) { - auto deserializeObject{deserializeTypedObject(objJson.toObject(), factory, result.references)}; - result.objects.push_back(deserializeObject); +void convertObjectPropertiesIRToUser(const ReflectionInterface& dynObj, ReflectionInterface& userObj, translateRefFunc translateRef, raco::core::UserObjectFactoryInterface& factory) { + for (size_t i = 0; i < dynObj.size(); i++) { + auto propName = dynObj.name(i); + convertValueBaseIRToUser(*dynObj.get(i), *userObj.get(propName), translateRef, factory); } - for (const auto& linkJson : container.value(keys::LINKS).toArray()) { - auto deserializeObject{deserializeTypedObject(linkJson.toObject(), factory, result.references)}; - result.links.push_back(deserializeObject); +} + +void convertObjectAnnotationsIRToUser(const ClassWithReflectedMembers& dynObj, ClassWithReflectedMembers& userObj, translateRefFunc translateRef, + raco::core::UserObjectFactoryInterface& factory) { + for (auto anno : dynObj.annotations()) { + auto userAnno = factory.createAnnotation(anno->getTypeDescription().typeName); + userObj.addAnnotation(userAnno); + + convertObjectPropertiesIRToUser(*anno, *userAnno, translateRef, factory); } +} + +ProjectDeserializationInfo ConvertFromIRToUserTypes(const ProjectDeserializationInfoIR& deserializedIR) { + auto& userFactory{raco::user_types::UserObjectFactory::getInstance()}; + + // dynamic -> static object translationB + ProjectDeserializationInfo result; + result.versionInfo = deserializedIR.versionInfo; + + result.externalProjectsMap = deserializedIR.externalProjectsMap; + + std::map instanceMap; + for (const auto& obj : deserializedIR.objects) { + auto dynObj = std::dynamic_pointer_cast(obj); + + auto objectID = *dynObj->objectID_; + auto typeName = dynObj->getTypeDescription().typeName; + + auto userObj = std::dynamic_pointer_cast(userFactory.createObject(typeName)); + result.objects.emplace_back(userObj); + + instanceMap[objectID] = userObj; + } + + auto translateRef = [&instanceMap](SEditorObject obj) -> SEditorObject { + if (obj) { + return instanceMap.at(obj->objectID()); + } + return nullptr; + }; + + for (const auto& irLink : deserializedIR.links) { + result.links.emplace_back(raco::core::Link::cloneLinkWithTranslation(std::dynamic_pointer_cast(irLink), translateRef)); + } + + for (const auto& obj : deserializedIR.objects) { + auto dynObj = std::dynamic_pointer_cast(obj); + auto userObj = instanceMap[dynObj->objectID()]; + + convertObjectPropertiesIRToUser(*dynObj, *userObj, translateRef, userFactory); + convertObjectAnnotationsIRToUser(*dynObj, *userObj, translateRef, userFactory); + } + return result; } +} // namespace + + +namespace raco::serialization { + +std::string test_helpers::serializeObject(const SReflectionInterface& object, const std::string& originPath) { + return QJsonDocument{serializeTypedObject(*object.get())}.toJson().toStdString(); +} -std::string raco::serialization::serializeObjects(const std::vector& objects, const std::vector& rootObjectIDs, const std::vector& links, const std::string& originFolder, const std::string& originFilename, const std::string& originProjectID, const std::string& originProjectName, const std::map& externalProjectsMap, const std::map& originFolders, const ResolveReferencedId& resolveReferenceId) { +std::string ObjectsDeserialization::originPath() const { + return (std::filesystem::path(originFolder) / originFileName).generic_string(); +} + +ObjectDeserialization test_helpers::deserializeObject(const std::string& json) { + auto& factory{user_types::UserObjectFactory::getInstance()}; + References references{}; + return { + std::dynamic_pointer_cast(deserializeTypedObject(QJsonDocument::fromJson(json.c_str()).object(), factory, references)), + references}; +} + +std::map> makeUserTypePropertyMap() { + auto& userFactory{user_types::UserObjectFactory::getInstance()}; + + std::map> typesPropTypesMap; + + for (const auto& [name, desc] : userFactory.getTypes()) { + auto obj = userFactory.createObject(name); + + std::map propTypeMap; + for (size_t i = 0; i < obj->size(); i++) { + auto propName = obj->name(i); + auto propType = obj->get(i)->typeName(); + propTypeMap[propName] = propType; + } + typesPropTypesMap[name] = propTypeMap; + } + + return typesPropTypesMap; +} + + +std::string serializeObjects(const std::vector& objects, const std::vector& rootObjectIDs, const std::vector& links, const std::string& originFolder, const std::string& originFilename, const std::string& originProjectID, const std::string& originProjectName, const std::map& externalProjectsMap, const std::map& originFolders) { QJsonObject result{}; if (!originFolder.empty()) { @@ -501,7 +691,6 @@ std::string raco::serialization::serializeObjects(const std::vector>& fileVersions, const std::vector& instances, const std::vector& links, - const std::map& externalProjectsMap, - const ResolveReferencedId& resolveReferenceId) { +ObjectsDeserialization deserializeObjects(const std::string& json) { + auto& factory{user_types::UserObjectFactory::getInstance()}; + ObjectsDeserialization result{}; + auto container{QJsonDocument::fromJson(json.c_str()).object()}; + if (!container[keys::ORIGIN_FOLDER].isUndefined() && !container[keys::ORIGIN_FOLDER].toString().isEmpty()) { + result.originFolder = container[keys::ORIGIN_FOLDER].toString().toStdString(); + } + if (!container[keys::ORIGIN_FILENAME].isUndefined() && !container[keys::ORIGIN_FILENAME].toString().isEmpty()) { + result.originFileName = container[keys::ORIGIN_FILENAME].toString().toStdString(); + } + if (!container[keys::ORIGIN_PROJECT_ID].isUndefined() && !container[keys::ORIGIN_PROJECT_ID].toString().isEmpty()) { + result.originProjectID = container[keys::ORIGIN_PROJECT_ID].toString().toStdString(); + } + if (!container[keys::ORIGIN_PROJECT_NAME].isUndefined() && !container[keys::ORIGIN_PROJECT_NAME].toString().isEmpty()) { + result.originProjectName = container[keys::ORIGIN_PROJECT_NAME].toString().toStdString(); + } + + deserializeExternalProjectsMap(container[keys::EXTERNAL_PROJECTS].toVariant(), result.externalProjectsMap); + deserializeObjectOriginFolderMap(container[keys::OBJECT_ORIGIN_FOLDERS].toVariant(), result.objectOriginFolders); + + auto rootObjectIDs = container[keys::ROOT_OBJECT_IDS].toVariant().toStringList(); + for (auto id : rootObjectIDs) { + result.rootObjectIDs.insert(id.toStdString()); + } + + for (const auto& objJson : container.value(keys::OBJECTS).toArray()) { + auto deserializedObject = std::dynamic_pointer_cast(deserializeTypedObject(objJson.toObject(), factory, result.references)); + result.objects.push_back(deserializedObject); + } + for (const auto& linkJson : container.value(keys::LINKS).toArray()) { + auto deserializedLink = std::dynamic_pointer_cast(deserializeTypedObject(linkJson.toObject(), factory, result.references)); + result.links.push_back(deserializedLink); + } + return result; +} + +QJsonDocument serializeProject(const std::unordered_map>& fileVersions, const std::vector& instances, const std::vector& links, + const std::map& externalProjectsMap) { QJsonObject container{}; auto ramsesVer = fileVersions.at(keys::RAMSES_VERSION); @@ -540,66 +764,96 @@ QJsonDocument raco::serialization::serializeProject(const std::unordered_map(deserializeTypedObject(instance.toObject(), factory, references, userPropTypeMap, structTypeMap)); + deserializedProjectInfo.objects.push_back(obj); } - const auto links = document[keys::LINKS].toArray(); - deserializedProjectInfo.objectsDeserialization.links.reserve(links.size()); - for (const auto& link : links) { - deserializedProjectInfo.objectsDeserialization.links.push_back(deserializeTypedObject(link.toObject(), factory, deserializedProjectInfo.objectsDeserialization.references)); + const auto links = migratedJson[keys::LINKS].toArray(); + deserializedProjectInfo.links.reserve(links.size()); + for (const auto& linkJson: links) { + auto link = std::dynamic_pointer_cast(deserializeTypedObject(linkJson.toObject(), factory, references, userPropTypeMap, structTypeMap)); + deserializedProjectInfo.links.push_back(link); } - return deserializedProjectInfo; -} -int raco::serialization::deserializeFileVersion(const QJsonDocument& document) { - return document.object()[keys::FILE_VERSION].toInt(); -} + // Restore references + std::map instanceMap; + for (auto& d : deserializedProjectInfo.objects) { + auto obj = std::dynamic_pointer_cast(d); + instanceMap[obj->objectID()] = obj; + } + for (const auto& pair : references) { + if (instanceMap.find(pair.second) != instanceMap.end()) { + *pair.first = instanceMap.at(pair.second); + } else { + LOG_WARNING(raco::log_system::DESERIALIZATION, "Load: referenced object not found: {}", pair.second); + } + } -ProjectDeserializationInfo raco::serialization::deserializeProject(const std::string& json, const DeserializationFactory& factory) { - return deserializeProject(QJsonDocument::fromJson(json.c_str()), factory); -} + for (const auto& obj : deserializedProjectInfo.objects) { + auto dynObj = std::dynamic_pointer_cast(obj); + dynObj->onAfterDeserialization(); + } + + deserializedProjectInfo.currentPath = filename; -std::optional raco::serialization::serializePropertyForMigration(const ValueBase& value, const ResolveReferencedId& resolveReferenceId, bool dynamicallyTyped) { - return serializeValueBase(value, resolveReferenceId, dynamicallyTyped); + return deserializedProjectInfo; } -References raco::serialization::deserializePropertyForMigration(const QJsonValue& property, ValueBase& value, const DeserializationFactory& factory) { - References references{}; - deserializeValueBase(property, value, references, factory); - return references; +ProjectDeserializationInfo deserializeProject(const QJsonDocument& document, const std::string& filename) { + auto deserializedIR{deserializeProjectToIR(document, filename)}; + + // run new migration code + migrateProject(deserializedIR); + + return ConvertFromIRToUserTypes(deserializedIR); } +} // namespace raco::serialization diff --git a/datamodel/libCore/src/Undo.cpp b/datamodel/libCore/src/Undo.cpp index bac78650..a83b98dd 100644 --- a/datamodel/libCore/src/Undo.cpp +++ b/datamodel/libCore/src/Undo.cpp @@ -13,6 +13,7 @@ #include "core/Context.h" #include "core/EditorObject.h" #include "core/ExternalReferenceAnnotation.h" +#include "core/Iterators.h" #include "core/Project.h" #include "core/UserObjectFactoryInterface.h" #include "core/Link.h" @@ -26,10 +27,7 @@ namespace raco::core { using namespace raco::data_storage; -void updateTableAsArray(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); -void updateTableByName(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler); - -void updateSingleValue(const ValueBase *src, ValueBase *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { +void UndoHelpers::updateSingleValue(const ValueBase *src, ValueBase *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { PrimitiveType type = src->type(); bool changed = false; if (type == PrimitiveType::Ref) { @@ -54,10 +52,12 @@ void updateSingleValue(const ValueBase *src, ValueBase *dest, ValueHandle destHa } else { updateTableByName(&src->asTable(), &dest->asTable(), destHandle, translateRef, outChanges, invokeHandler); } + } else if (type == PrimitiveType::Struct) { + assert(ValueBase::classesEqual(*src, *dest)); + updateStruct(&src->asStruct(), &dest->asStruct(), destHandle, translateRef, outChanges, invokeHandler); + // Assume annotation data doesn't contain Ref type properties. + dest->copyAnnotationData(*src); } else { - // TODO: there seems to be a loophole here: - // for PrimitiveType::Struct properties the references inside the struct are not translated; - // we currently don't use classes with reference properties in PrimitiveType::Struct properties changed = dest->assign(*src); // Assume annotation data doesn't contain Ref type properties. dest->copyAnnotationData(*src); @@ -67,23 +67,28 @@ void updateSingleValue(const ValueBase *src, ValueBase *dest, ValueHandle destHa } } +void UndoHelpers::callOnBeforeRemoveReferenceHandler(raco::data_storage::Table *dest, const size_t &index, raco::core::ValueHandle &destHandle) { + auto oldValue = dest->get(index); + if (oldValue->type() == PrimitiveType::Ref) { + if (auto oldObj = oldValue->asRef()) { + oldObj->onBeforeRemoveReferenceToThis(destHandle[index]); + } + } else if (hasTypeSubstructure(oldValue->type())) { + BaseContext::callReferenceToThisHandlerForAllTableEntries<&EditorObject::onBeforeRemoveReferenceToThis>(destHandle); + } +} + // Update of Tables with ArraySemanticAnnotation // - replace entire Table contents -void updateTableAsArray(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { +void UndoHelpers::updateTableAsArray(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { bool changed = false; if (ReflectionInterface::compare(*src, *dest, translateRef)) { return; } - if (invokeHandler) { + if (invokeHandler && destHandle) { for (size_t index{0}; index < dest->size(); index++) { - auto oldValue = dest->get(index); - if (oldValue->type() == PrimitiveType::Ref) { - auto oldObj = oldValue->asRef(); - if (oldObj && destHandle) { - oldObj->onBeforeRemoveReferenceToThis(destHandle[index]); - } - } + UndoHelpers::callOnBeforeRemoveReferenceHandler(dest, index, destHandle); } } if (dest->size() > 0) { @@ -101,9 +106,31 @@ void updateTableAsArray(const Table *src, Table *dest, ValueHandle destHandle, t } } +void UndoHelpers::updateStruct(const ClassWithReflectedMembers *src, ClassWithReflectedMembers *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { + for (size_t index{0}; index < src->size(); index++) { + std::string name = src->name(index); + assert(dest->hasProperty(name)); + UndoHelpers::updateSingleValue(src->get(name), dest->get(name), destHandle ? destHandle[index] : ValueHandle(), translateRef, outChanges, invokeHandler); + } +} + +void UndoHelpers::updateMissingTableProperties(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { + bool changed = false; + for (size_t index{0}; index < src->size(); index++) { + std::string name = src->name(index); + if (!dest->hasProperty(name)) { + dest->addProperty(name, src->get(name)->clone(&translateRef), index); + changed = true; + } + } + if (changed && outChanges && destHandle) { + outChanges->recordValueChanged(destHandle); + } +} + // Update of Tables without ArraySemanticAnnotation // - match properties by name and type and remove/add properties as necessary -void updateTableByName(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { +void UndoHelpers::updateTableByName(const Table *src, Table *dest, ValueHandle destHandle, translateRefFunc translateRef, DataChangeRecorder *outChanges, bool invokeHandler) { // Remove dest properties not present in src size_t index = 0; bool changed = false; @@ -111,13 +138,8 @@ void updateTableByName(const Table *src, Table *dest, ValueHandle destHandle, tr std::string name = dest->name(index); if (!src->hasProperty(name) || !ValueBase::classesEqual(*src->get(name), *dest->get(name))) { - auto oldValue = dest->get(index); - if (oldValue->type() == PrimitiveType::Ref) { - if (auto oldObj = oldValue->asRef()) { - if (invokeHandler && destHandle) { - oldObj->onBeforeRemoveReferenceToThis(destHandle[index]); - } - } + if (invokeHandler && destHandle) { + UndoHelpers::callOnBeforeRemoveReferenceHandler(dest, index, destHandle); } dest->removeProperty(index); @@ -131,9 +153,14 @@ void updateTableByName(const Table *src, Table *dest, ValueHandle destHandle, tr for (size_t index{0}; index < src->size(); index++) { std::string name = src->name(index); if (dest->hasProperty(name)) { - updateSingleValue(src->get(name), dest->get(name), destHandle ? destHandle[index] : ValueHandle(), translateRef, outChanges, invokeHandler); + auto destIndex = dest->index(name); + if (destIndex != index) { + dest->swapProperties(index, destIndex); + changed = true; + } + UndoHelpers::updateSingleValue(src->get(name), dest->get(name), destHandle ? destHandle[index] : ValueHandle(), translateRef, outChanges, invokeHandler); } else { - dest->addProperty(name, src->get(name)->clone(&translateRef)); + dest->addProperty(name, src->get(name)->clone(&translateRef), index); changed = true; } } @@ -142,7 +169,7 @@ void updateTableByName(const Table *src, Table *dest, ValueHandle destHandle, tr } } -void updateEditorObject(const EditorObject *src, SEditorObject dest, translateRefFunc translateRef, excludePropertyPredicateFunc excludeIf, UserObjectFactoryInterface &factory, DataChangeRecorder *outChanges, bool invokeHandler, bool updateObjectAnnotations) { +void UndoHelpers::updateEditorObject(const EditorObject *src, SEditorObject dest, translateRefFunc translateRef, excludePropertyPredicateFunc excludeIf, UserObjectFactoryInterface &factory, DataChangeRecorder *outChanges, bool invokeHandler, bool updateObjectAnnotations) { if (updateObjectAnnotations) { auto destAnnoCopy{dest->annotations()}; for (const auto &destAnno : destAnnoCopy) { @@ -164,7 +191,7 @@ void updateEditorObject(const EditorObject *src, SEditorObject dest, translateRe assert(destAnno->hasProperty(name)); assert(ValueBase::classesEqual(*destAnno->get(name), *srcAnno->get(name))); - updateSingleValue(srcAnno->get(name), destAnno->get(name), ValueHandle(), translateRef, outChanges, invokeHandler); + UndoHelpers::updateSingleValue(srcAnno->get(name), destAnno->get(name), ValueHandle(), translateRef, outChanges, invokeHandler); } } } @@ -175,7 +202,7 @@ void updateEditorObject(const EditorObject *src, SEditorObject dest, translateRe if (!excludeIf(name)) { assert(ValueBase::classesEqual(*dest->get(name), *src->get(name))); - updateSingleValue(src->get(name), dest->get(name), ValueHandle(dest, {index}), translateRef, outChanges, invokeHandler); + UndoHelpers::updateSingleValue(src->get(name), dest->get(name), ValueHandle(dest, {index}), translateRef, outChanges, invokeHandler); } } } @@ -214,7 +241,7 @@ void UndoStack::saveProjectState(const Project *src, Project *dest, Project *ref for (const auto &srcObj : dirtyObjects) { auto destObj = dest->getInstanceByID(srcObj->objectID()); - updateEditorObject( + UndoHelpers::updateEditorObject( srcObj.get(), destObj, translateRef, [](const std::string &) { return false; }, factory, nullptr, false); } @@ -242,7 +269,8 @@ void UndoStack::updateProjectState(const Project *src, Project *dest, const Data for (const auto &srcObj : dirtyObjects) { auto destObj = dest->getInstanceByID(srcObj->objectID()); - updateEditorObject(srcObj.get(), destObj, translateRef, [](const std::string &) { return false; }, factory, nullptr, false); + UndoHelpers::updateEditorObject( + srcObj.get(), destObj, translateRef, [](const std::string &) { return false; }, factory, nullptr, false); } // Update external project name map @@ -296,7 +324,8 @@ void UndoStack::restoreProjectState(Project *src, Project *dest, BaseContext &co // Update objects for (const auto &destObj : dest->instances()) { auto srcObj = src->getInstanceByID(destObj->objectID()); - updateEditorObject(srcObj.get(), destObj, translateRef, [](const std::string &) { return false; }, factory, &changes, true); + UndoHelpers::updateEditorObject( + srcObj.get(), destObj, translateRef, [](const std::string &) { return false; }, factory, &changes, true); } auto findExtref = [](const std::map>& changes) { diff --git a/datamodel/libCore/src/UserObjectFactoryInterface.cpp b/datamodel/libCore/src/UserObjectFactoryInterface.cpp deleted file mode 100644 index 1ea87914..00000000 --- a/datamodel/libCore/src/UserObjectFactoryInterface.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SPDX-License-Identifier: MPL-2.0 - * - * This file is part of Ramses Composer - * (see https://github.com/GENIVI/ramses-composer). - * - * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. - * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -#include "core/UserObjectFactoryInterface.h" -#include "core/Link.h" - -#include "core/Serialization.h" - -namespace raco::core { - -raco::serialization::DeserializationFactory UserObjectFactoryInterface::deserializationFactory(UserObjectFactoryInterface* objectFactory) { - return raco::serialization::DeserializationFactory{ - [objectFactory](const std::string& type) -> raco::serialization::SReflectionInterface { - if (type == Link::typeDescription.typeName) { - return std::make_shared(); - } - return objectFactory->createObject(type); - }, - [objectFactory](const std::string& type) { - return objectFactory->createAnnotation(type); - }, - [objectFactory](const std::string& type) { - return objectFactory->createValue(type); - }}; -} - -} // namespace raco::core diff --git a/datamodel/libCore/tests/CMakeLists.txt b/datamodel/libCore/tests/CMakeLists.txt index 6888cea4..efd09251 100644 --- a/datamodel/libCore/tests/CMakeLists.txt +++ b/datamodel/libCore/tests/CMakeLists.txt @@ -19,6 +19,7 @@ set(TEST_SOURCES Prefab_test.cpp ExternalReference_test.cpp ValueHandle_test.cpp + PathManager_test.cpp Queries_Tags_test.cpp ) @@ -119,6 +120,8 @@ raco_package_add_test_resouces( migrationTestData/V14c.rca migrationTestData/V16.rca migrationTestData/V18.rca + migrationTestData/V21.rca + migrationTestData/V23.rca migrationTestData/version-current.rca ) add_compile_definitions(libSerialization_test PRIVATE CMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/datamodel/libCore/tests/Context_test.cpp b/datamodel/libCore/tests/Context_test.cpp index 51b3d34d..3e19fb23 100644 --- a/datamodel/libCore/tests/Context_test.cpp +++ b/datamodel/libCore/tests/Context_test.cpp @@ -130,140 +130,6 @@ TEST_F(ContextTest, rendertarget_set_remove_multi_ref) { EXPECT_EQ(buffer->referencesToThis().size(), 0); } -TEST_F(ContextTest, add_remove_property_ref) { - const std::shared_ptr refTarget{new Foo()}; - const std::shared_ptr refSource{new ObjectWithTableProperty()}; - EXPECT_EQ(refTarget->referencesToThis().size(), 0); - - ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; - context.addProperty(tableHandle, "ref", std::make_unique>(refTarget)); - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - context.removeProperty(tableHandle, "ref"); - EXPECT_EQ(refTarget->referencesToThis().size(), 0); -} - -TEST_F(ContextTest, add_remove_multiple_property_ref) { - const std::shared_ptr refTarget{new Foo()}; - const std::shared_ptr refSource{new ObjectWithTableProperty()}; - EXPECT_EQ(refTarget->referencesToThis().size(), 0); - - ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; - context.addProperty(tableHandle, "ref1", std::make_unique>(refTarget)); - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - context.addProperty(tableHandle, "ref2", std::make_unique>(refTarget)); - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - context.removeProperty(tableHandle, "ref1"); - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - context.removeProperty(tableHandle, "ref2"); - EXPECT_EQ(refTarget->referencesToThis().size(), 0); -} - -TEST_F(ContextTest, add_remove_property_table_with_ref) { - const std::shared_ptr refTarget{new Foo()}; - const std::shared_ptr refSource{new ObjectWithTableProperty()}; - EXPECT_EQ(refTarget->referencesToThis().size(), 0); - - Table innerTable; - innerTable.addProperty("ref", std::make_unique>(refTarget)); - - const ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; - context.addProperty(tableHandle, "innerTable", std::make_unique>(innerTable)); - - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - context.removeProperty(tableHandle, "innerTable"); - EXPECT_EQ(refTarget->referencesToThis().size(), 0); -} - -TEST_F(ContextTest, add_remove_property_struct_with_ref) { - const std::shared_ptr refTarget{new Foo()}; - const std::shared_ptr refSource{new ObjectWithTableProperty()}; - EXPECT_EQ(refTarget->referencesToThis().size(), 0); - - Value* value{new Value()}; - (*value)->ref = refTarget; - - const ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; - context.addProperty(tableHandle, "foo", std::unique_ptr>(value)); - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - context.removeProperty(tableHandle, "foo"); - EXPECT_EQ(refTarget->referencesToThis().size(), 0); -} - -TEST_F(ContextTest, setTable_with_ref) { - const std::shared_ptr refTarget{new Foo()}; - const std::shared_ptr refSource{new ObjectWithTableProperty()}; - EXPECT_EQ(refTarget->referencesToThis().size(), 0); - - Table newTable; - newTable.addProperty("ref", std::make_unique>(refTarget)); - - const ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; - context.set(tableHandle, newTable); - - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - Table emptyTable; - context.set(tableHandle, emptyTable); - EXPECT_EQ(refTarget->referencesToThis().size(), 0); -} - -TEST_F(ContextTest, setTable_nested_table_with_ref) { - const std::shared_ptr refTarget{new Foo()}; - const std::shared_ptr refSource{new ObjectWithTableProperty()}; - EXPECT_EQ(refTarget->referencesToThis().size(), 0); - - Table innerTable; - innerTable.addProperty("ref", std::make_unique>(refTarget)); - - Table outerTable; - outerTable.addProperty("inner", std::make_unique>(innerTable)); - - const ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; - context.set(tableHandle, outerTable); - - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - Table emptyTable; - context.set(tableHandle, emptyTable); - EXPECT_EQ(refTarget->referencesToThis().size(), 0); -} - -TEST_F(ContextTest, setTable_nested_struct_with_ref) { - const std::shared_ptr refTarget{new Foo()}; - const std::shared_ptr refSource{new ObjectWithTableProperty()}; - EXPECT_EQ(refTarget->referencesToThis().size(), 0); - - Value* value{new Value()}; - (*value)->ref = refTarget; - - Table newTable; - newTable.addProperty("struct", std::unique_ptr>(value)); - - const ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; - context.set(tableHandle, newTable); - - ASSERT_EQ(refTarget->referencesToThis().size(), 1); - EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); - - Table emptyTable; - context.set(tableHandle, emptyTable); - EXPECT_EQ(refTarget->referencesToThis().size(), 0); -} - TEST_F(ContextTest, Prefab) { std::shared_ptr prefab{new Prefab("prefab")}; auto inst = std::make_shared("inst"); @@ -379,7 +245,7 @@ TEST_F(ContextTest, Mesh) { // Simple test of onAfterValueChanged handler: - auto duckPath = cwd_path().append("meshes/Duck.glb").generic_string(); + auto duckPath = test_path().append("meshes/Duck.glb").string(); context.set(m_uri, duckPath); EXPECT_EQ(m_uri.asString(), duckPath); EXPECT_EQ(mesh->materialNames(), std::vector({"material"})); @@ -401,7 +267,7 @@ TEST_F(ContextTest, MeshNode) { context.set(ValueHandle{meshnode, {"mesh"}}, m.rootObject()); - auto duckPath = cwd_path().append("meshes/Duck.glb").generic_string(); + auto duckPath = test_path().append("meshes/Duck.glb").string(); context.set(ValueHandle{mesh, {"uri"}}, duckPath); EXPECT_EQ(m_uri.asString(), duckPath); EXPECT_EQ(mesh->materialNames(), std::vector({ "material" })); @@ -566,7 +432,7 @@ TEST_F(ContextTest, material_sync_restore_cached_ref_adds_backpointer) { EXPECT_TRUE(ValueHandle(material, {"uniforms"})); EXPECT_FALSE(ValueHandle(material, {"uniforms", "u_Tex"})); - context.set({material, {"uriFragment"}}, (cwd_path() / "shaders/simple_texture.frag").string()); + context.set({material, {"uriFragment"}}, (test_path() / "shaders/simple_texture.frag").string()); EXPECT_TRUE(ValueHandle(material, {"uniforms", "u_Tex"})); context.deleteObjects({texture}); @@ -586,7 +452,7 @@ TEST_F(ContextTest, meshnode_uniform_sync_restore_cached_ref_adds_backpointer) { EXPECT_TRUE(ValueHandle(meshnode, {"materials", "material", "uniforms"})); EXPECT_FALSE(ValueHandle(meshnode, {"materials", "material", "uniforms", "u_Tex"})); - context.set({material, {"uriFragment"}}, (cwd_path() / "shaders/simple_texture.frag").string()); + context.set({material, {"uriFragment"}}, (test_path() / "shaders/simple_texture.frag").string()); EXPECT_TRUE(ValueHandle(meshnode, {"materials", "material", "uniforms", "u_Tex"})); context.deleteObjects({texture}); @@ -697,7 +563,7 @@ TEST_F(ContextTest, cutAndPasteObjectSimple) { TEST_F(ContextTest, copyAndPasteShallowSetsReferences) { auto meshNode = context.createObject(MeshNode::typeDescription.typeName); auto mesh = context.createObject(Mesh::typeDescription.typeName); - context.set({ mesh, {"uri"}}, (cwd_path() / "meshes" / "Duck.glb").string()); + context.set({ mesh, {"uri"}}, (test_path() / "meshes" / "Duck.glb").string()); context.set({ meshNode, {"mesh"}}, mesh); auto copyResult = context.pasteObjects(context.copyObjects({ meshNode })); auto meshNodeCopy = std::dynamic_pointer_cast(copyResult.at(0)); @@ -710,11 +576,11 @@ TEST_F(ContextTest, copyAndPasteShallowSetsReferences) { TEST_F(ContextTest, copy_paste_loses_uniforms) { auto meshnode = create("meshnode"); auto mesh = create("duck_mesh"); - context.set({mesh, {"uri"}}, (cwd_path() / "meshes" / "Duck.glb").string()); + context.set({mesh, {"uri"}}, (test_path() / "meshes" / "Duck.glb").string()); context.set({meshnode, {"mesh"}}, mesh); auto material = create("mat"); - context.set({material, {"uriVertex"}}, (cwd_path() / "shaders" / "basic.vert").string()); - context.set({material, {"uriFragment"}}, (cwd_path() / "shaders" / "basic.frag").string()); + context.set({material, {"uriVertex"}}, (test_path() / "shaders" / "basic.vert").string()); + context.set({material, {"uriFragment"}}, (test_path() / "shaders" / "basic.frag").string()); context.set(ValueHandle{meshnode}.get("materials")[0].get("material"), material); context.set(meshnode->getMaterialPrivateHandle(0), true); @@ -824,7 +690,7 @@ TEST_F(ContextTest, pasteInvalidJsonSchema) { } TEST_F(ContextTest, copyAndPasteKeepAbsolutePath) { - auto absoluteDuckPath{(cwd_path() / "testData" / "Duck.glb").generic_string()}; + auto absoluteDuckPath{(test_path() / "testData" / "Duck.glb").string()}; const auto sMesh{context.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; auto uri{absoluteDuckPath}; context.set({sMesh, {"uri"}}, uri); @@ -837,8 +703,8 @@ TEST_F(ContextTest, copyAndPasteKeepAbsolutePath) { } TEST_F(ContextTest, copyAndPasteTurnRelativePathFromDifferentDriveToAbsolute) { - context.project()->setCurrentPath((cwd_path() / "proj.file").generic_string()); - auto absoluteDuckPath{(cwd_path() / "testData" / "Duck.glb").generic_string()}; + context.project()->setCurrentPath((test_path() / "proj.file").string()); + auto absoluteDuckPath{(test_path() / "testData" / "Duck.glb").string()}; std::string relativeDuckPath{"testData/Duck.glb"}; const auto sMesh{context.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; context.set({sMesh, {"uri"}}, relativeDuckPath); @@ -851,31 +717,31 @@ TEST_F(ContextTest, copyAndPasteTurnRelativePathFromDifferentDriveToAbsolute) { } TEST_F(ContextTest, copyAndPasteRerootRelativePathHierarchyDown) { - context.project()->setCurrentPath((cwd_path() / "proj.file").string()); - auto relativeDuckPath{(cwd_path_relative() / "testData" / "Duck.glb").string()}; + context.project()->setCurrentPath((test_path() / "proj.file").string()); + auto relativeDuckPath{(test_relative_path() / "testData" / "Duck.glb").string()}; const auto sMesh{context.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; auto uri{relativeDuckPath}; context.set({sMesh, {"uri"}}, uri); auto clipboardContent = context.copyObjects({ sMesh }); - context.project()->setCurrentPath((cwd_path() / "newProject" / "proj.file").string()); + context.project()->setCurrentPath((test_path() / "newProject" / "proj.file").string()); auto pasteResult = context.pasteObjects(clipboardContent); - auto newRelativeDuckPath{std::filesystem::path("..") / std::filesystem::path(relativeDuckPath)}; + auto newRelativeDuckPath{raco::utils::u8path("..") / relativeDuckPath}; ASSERT_EQ(pasteResult.front()->get("uri")->asString(), newRelativeDuckPath); } TEST_F(ContextTest, copyAndPasteRerootRelativePathHierarchyUp) { - context.project()->setCurrentPath((cwd_path() / "newProject" / "proj.file").string()); - auto relativeDuckPath{(cwd_path_relative() / "testData" / "Duck.glb").generic_string()}; + context.project()->setCurrentPath((test_path() / "newProject" / "proj.file").string()); + auto relativeDuckPath{(test_relative_path() / "testData" / "Duck.glb").string()}; const auto sMesh{context.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; auto uri{relativeDuckPath}; context.set({sMesh, {"uri"}}, uri); auto clipboardContent = context.copyObjects({ sMesh }); - context.project()->setCurrentPath((cwd_path() / "proj.file").string()); + context.project()->setCurrentPath((test_path() / "proj.file").string()); auto pasteResult = context.pasteObjects(clipboardContent); - auto newRelativeDuckPath{"newProject" / std::filesystem::path(relativeDuckPath)}; + auto newRelativeDuckPath{raco::utils::u8path("newProject") / relativeDuckPath}; ASSERT_EQ(pasteResult.front()->get("uri")->asString(), newRelativeDuckPath); } diff --git a/datamodel/libCore/tests/Deserialization_test.cpp b/datamodel/libCore/tests/Deserialization_test.cpp index 3d7576a6..16b4d26e 100644 --- a/datamodel/libCore/tests/Deserialization_test.cpp +++ b/datamodel/libCore/tests/Deserialization_test.cpp @@ -17,6 +17,8 @@ #include "user_types/Material.h" #include "user_types/MeshNode.h" #include "user_types/Node.h" +#include "user_types/Texture.h" + #include "utils/FileUtils.h" #include @@ -24,28 +26,10 @@ using namespace raco::user_types; struct DeserializationTest : public TestEnvironmentCore { - raco::serialization::DeserializationFactory deserializationFactory() noexcept { - return {[this](const std::string& typeName) -> raco::serialization::SReflectionInterface { - if (typeName == raco::core::Link::typeDescription.typeName) { - return std::make_shared(); - } else { - return objectFactory()->createObject(typeName); - } - }, - [this](const std::string& type) { - return objectFactory()->createAnnotation(type); - }, - [this](const std::string& valueType) -> raco::data_storage::ValueBase* { - if (valueType == "LuaScript::DisplayNameAnnotation") { - return new Property({}, {}); - } - return objectFactory()->createValue(valueType); - }}; - } }; TEST_F(DeserializationTest, deserializeNode) { - auto result = raco::serialization::deserializeObject(raco::utils::file::read((cwd_path() / "expectations" / "Node.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject(raco::utils::file::read((test_path() / "expectations" / "Node.json").string())); ASSERT_EQ(raco::user_types::Node::typeDescription.typeName, result.object->getTypeDescription().typeName); SNode sNode{std::dynamic_pointer_cast(result.object)}; @@ -55,8 +39,8 @@ TEST_F(DeserializationTest, deserializeNode) { } TEST_F(DeserializationTest, deserializeNodeRotated) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "NodeRotated.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "NodeRotated.json").string())); ASSERT_EQ(raco::user_types::Node::typeDescription.typeName, result.object->getTypeDescription().typeName); SNode sNode{std::dynamic_pointer_cast(result.object)}; ASSERT_EQ(*sNode->rotation_->x, 90.0); @@ -65,8 +49,8 @@ TEST_F(DeserializationTest, deserializeNodeRotated) { } TEST_F(DeserializationTest, deserializeNodeWithAnnotations) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "NodeWithAnnotations.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "NodeWithAnnotations.json").string())); ASSERT_EQ(raco::user_types::Node::typeDescription.typeName, result.object->getTypeDescription().typeName); SNode sNode{std::dynamic_pointer_cast(result.object)}; auto anno = sNode->query(); @@ -75,8 +59,8 @@ TEST_F(DeserializationTest, deserializeNodeWithAnnotations) { } TEST_F(DeserializationTest, deserializeMeshNodeWithMesh) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "MeshNodeWithMesh.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "MeshNodeWithMesh.json").string())); ASSERT_EQ(raco::user_types::MeshNode::typeDescription.typeName, result.object->getTypeDescription().typeName); SMeshNode sMeshNode{std::dynamic_pointer_cast(result.object)}; ASSERT_EQ(1, result.references.size()); @@ -85,16 +69,16 @@ TEST_F(DeserializationTest, deserializeMeshNodeWithMesh) { } TEST_F(DeserializationTest, deserializeNodeWithMeshNode) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "NodeWithChildMeshNode.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "NodeWithChildMeshNode.json").string())); ASSERT_EQ(raco::user_types::Node::typeDescription.typeName, result.object->getTypeDescription().typeName); SNode sNode{std::dynamic_pointer_cast(result.object)}; ASSERT_EQ(1, result.references.size()); } TEST_F(DeserializationTest, deserializeMesh) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "Mesh.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "Mesh.json").string())); ASSERT_EQ(raco::user_types::Mesh::typeDescription.typeName, result.object->getTypeDescription().typeName); SMesh sMesh{std::dynamic_pointer_cast(result.object)}; ASSERT_EQ(0, result.references.size()); @@ -102,8 +86,8 @@ TEST_F(DeserializationTest, deserializeMesh) { } TEST_F(DeserializationTest, deserializeLuaScript) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScript.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "LuaScript.json").string())); ASSERT_EQ(raco::user_types::LuaScript::typeDescription.typeName, result.object->getTypeDescription().typeName); SLuaScript sLuaScript{std::dynamic_pointer_cast(result.object)}; ASSERT_EQ(0, result.references.size()); @@ -112,8 +96,8 @@ TEST_F(DeserializationTest, deserializeLuaScript) { } TEST_F(DeserializationTest, deserializeLuaScriptInStruct) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScriptInStruct.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "LuaScriptInStruct.json").string())); ASSERT_EQ(raco::user_types::LuaScript::typeDescription.typeName, result.object->getTypeDescription().typeName); SLuaScript sLuaScript{std::dynamic_pointer_cast(result.object)}; ASSERT_EQ(0, result.references.size()); @@ -124,27 +108,27 @@ TEST_F(DeserializationTest, deserializeLuaScriptInStruct) { } TEST_F(DeserializationTest, deserializeLuaScriptInSpecificPropNames) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScriptSpecificPropNames.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "LuaScriptSpecificPropNames.json").string())); ASSERT_EQ(raco::user_types::LuaScript::typeDescription.typeName, result.object->getTypeDescription().typeName); SLuaScript sLuaScript{std::dynamic_pointer_cast(result.object)}; } TEST_F(DeserializationTest, deserializeLuaScriptWithRefToUserTypeWithAnnotation) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScriptWithRefToUserTypeWithAnnotation.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "LuaScriptWithRefToUserTypeWithAnnotation.json").string())); ASSERT_EQ(raco::user_types::LuaScript::typeDescription.typeName, result.object->getTypeDescription().typeName); SLuaScript sLuaScript{std::dynamic_pointer_cast(result.object)}; auto* property{sLuaScript->luaInputs_->get(sLuaScript->luaInputs_->index("ref"))}; - ASSERT_EQ("LuaScript::DisplayNameAnnotation", property->typeName()); + ASSERT_EQ("Texture::EngineTypeAnnotation", property->typeName()); ASSERT_EQ(1, property->baseAnnotationPtrs().size()); - ASSERT_TRUE(property->dynamicQuery() != nullptr); - ASSERT_EQ("BLUBB", *property->query()->name_); + ASSERT_TRUE(property->dynamicQuery() != nullptr); + ASSERT_EQ(static_cast(raco::core::EnginePrimitive::TextureSampler2D), *property->query()->engineType_); } TEST_F(DeserializationTest, deserializeLuaScriptWithURI) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScriptWithURI.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "LuaScriptWithURI.json").string())); ASSERT_EQ(raco::user_types::LuaScript::typeDescription.typeName, result.object->getTypeDescription().typeName); SLuaScript sLuaScript{std::dynamic_pointer_cast(result.object)}; auto* property{sLuaScript->luaInputs_->get(sLuaScript->luaInputs_->index("uri"))}; @@ -154,8 +138,8 @@ TEST_F(DeserializationTest, deserializeLuaScriptWithURI) { } TEST_F(DeserializationTest, deserializeLuaScriptWithAnnotatedDouble) { - auto result = raco::serialization::deserializeObject( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScriptWithAnnotatedDouble.json").string()), deserializationFactory()); + auto result = raco::serialization::test_helpers::deserializeObject( + raco::utils::file::read((test_path() / "expectations" / "LuaScriptWithAnnotatedDouble.json").string())); ASSERT_EQ(raco::user_types::LuaScript::typeDescription.typeName, result.object->getTypeDescription().typeName); SLuaScript sLuaScript{std::dynamic_pointer_cast(result.object)}; @@ -171,15 +155,11 @@ TEST_F(DeserializationTest, deserializeVersionArray) { auto compareVersionValues = [this, &fakeProjectJSON](raco::serialization::DeserializedVersion&& expectedRamsesVer, raco::serialization::DeserializedVersion&& expectedLogicEngineVer, raco::serialization::DeserializedVersion&& expectedRaCoVer) { QJsonDocument fakeProjectJSONFile(fakeProjectJSON); - auto deserializedProjectJSON = raco::serialization::deserializeProject(fakeProjectJSONFile, deserializationFactory()); - - auto deserializedVersionsAreEqual = [](const auto& lhVersion, const auto& rhVersion) { - return lhVersion.major == rhVersion.major && lhVersion.minor == rhVersion.minor && lhVersion.patch == rhVersion.patch; - }; + auto versionInfo = raco::serialization::deserializeProjectVersionInfo(fakeProjectJSONFile); - ASSERT_TRUE(deserializedVersionsAreEqual(deserializedProjectJSON.ramsesVersion, expectedRamsesVer)); - ASSERT_TRUE(deserializedVersionsAreEqual(deserializedProjectJSON.ramsesLogicEngineVersion, expectedLogicEngineVer)); - ASSERT_TRUE(deserializedVersionsAreEqual(deserializedProjectJSON.raCoVersion, expectedRaCoVer)); + ASSERT_TRUE(versionInfo.ramsesVersion == expectedRamsesVer); + ASSERT_TRUE(versionInfo.ramsesLogicEngineVersion == expectedLogicEngineVer); + ASSERT_TRUE(versionInfo.raCoVersion == expectedRaCoVer); }; compareVersionValues( @@ -199,7 +179,7 @@ TEST_F(DeserializationTest, deserializeVersionArray) { TEST_F(DeserializationTest, deserializeObjects_luaScriptLinkedToNode_outputsAreDeserialized) { auto result = raco::serialization::deserializeObjects( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScriptLinkedToNode.json").string()), deserializationFactory()); + raco::utils::file::read((test_path() / "expectations" / "LuaScriptLinkedToNode.json").string())); raco::user_types::SLuaScript sScript{ raco::select(result.objects)}; ASSERT_EQ(3, sScript->luaOutputs_->size()); @@ -207,7 +187,7 @@ TEST_F(DeserializationTest, deserializeObjects_luaScriptLinkedToNode_outputsAreD TEST_F(DeserializationTest, deserializeObjects_luaScriptLinkedToNode) { auto result = raco::serialization::deserializeObjects( - raco::utils::file::read((cwd_path() / "expectations" / "LuaScriptLinkedToNode.json").string()), deserializationFactory()); + raco::utils::file::read((test_path() / "expectations" / "LuaScriptLinkedToNode.json").string())); std::vector objects{}; objects.reserve(result.objects.size()); diff --git a/datamodel/libCore/tests/ExternalReference_test.cpp b/datamodel/libCore/tests/ExternalReference_test.cpp index cafa22f9..714058da 100644 --- a/datamodel/libCore/tests/ExternalReference_test.cpp +++ b/datamodel/libCore/tests/ExternalReference_test.cpp @@ -72,20 +72,20 @@ class ExtrefTest : public RacoBaseTest<> { SAnimationChannel create_animationchannel(const std::string& name, const std::string& relpath) { auto channel = create(name); - cmd->set({channel, {"uri"}}, (cwd_path() / relpath).string()); + cmd->set({channel, {"uri"}}, (test_path() / relpath).string()); return channel; } SMesh create_mesh(const std::string &name, const std::string &relpath) { auto mesh = create(name); - cmd->set({mesh, {"uri"}}, (cwd_path() / relpath).string()); + cmd->set({mesh, {"uri"}}, (test_path() / relpath).string()); return mesh; } SMaterial create_material(const std::string &name, const std::string &relpathVertex, const std::string &relpathFragment) { auto material = create(name); - cmd->set({material, {"uriVertex"}}, (cwd_path() / relpathVertex).string()); - cmd->set({material, {"uriFragment"}}, (cwd_path() / relpathFragment).string()); + cmd->set({material, {"uriVertex"}}, (test_path() / relpathVertex).string()); + cmd->set({material, {"uriFragment"}}, (test_path() / relpathFragment).string()); return material; } @@ -97,7 +97,7 @@ class ExtrefTest : public RacoBaseTest<> { } void change_uri(SEditorObject obj, const std::string &newvalue) { - cmd->set({obj, {"uri"}}, (cwd_path() / newvalue).string()); + cmd->set({obj, {"uri"}}, (test_path() / newvalue).string()); } void rename_project(const std::string &newProjectName) { @@ -246,7 +246,7 @@ class ExtrefTest : public RacoBaseTest<> { }; TEST_F(ExtrefTest, normal_paste) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -263,7 +263,7 @@ TEST_F(ExtrefTest, normal_paste) { } TEST_F(ExtrefTest, duplicate_normal_paste) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -287,7 +287,7 @@ TEST_F(ExtrefTest, duplicate_normal_paste) { TEST_F(ExtrefTest, extref_paste_empty_projectname) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; auto base_id = setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -302,7 +302,7 @@ TEST_F(ExtrefTest, extref_paste_empty_projectname) { TEST_F(ExtrefTest, extref_paste_fail_renderpass) { - auto basePathName{(cwd_path() / "base.rca").string()}; + auto basePathName{(test_path() / "base.rca").string()}; setupBase(basePathName, [this]() { auto prefab = create("renderpass"); @@ -316,7 +316,7 @@ TEST_F(ExtrefTest, extref_paste_fail_renderpass) { } TEST_F(ExtrefTest, extref_paste_fail_existing_object_from_same_project) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -328,7 +328,7 @@ TEST_F(ExtrefTest, extref_paste_fail_existing_object_from_same_project) { } TEST_F(ExtrefTest, extref_paste_fail_deleted_object_from_same_project) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -344,7 +344,7 @@ TEST_F(ExtrefTest, extref_paste_fail_deleted_object_from_same_project) { } TEST_F(ExtrefTest, extref_paste_fail_existing_object_from_same_project_path) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -357,7 +357,7 @@ TEST_F(ExtrefTest, extref_paste_fail_existing_object_from_same_project_path) { } TEST_F(ExtrefTest, extref_paste_fail_deleted_object_from_same_project_path) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -375,8 +375,8 @@ TEST_F(ExtrefTest, extref_paste_fail_deleted_object_from_same_project_path) { } TEST_F(ExtrefTest, extref_paste_fail_from_filecopy) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto mesh = create("mesh"); @@ -406,7 +406,7 @@ TEST_F(ExtrefTest, extref_paste_fail_from_filecopy) { TEST_F(ExtrefTest, extref_paste) { - auto basePathName{(cwd_path() / "base.rcp").generic_string()}; + auto basePathName{(test_path() / "base.rcp").string()}; auto base_id = setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -423,8 +423,8 @@ TEST_F(ExtrefTest, extref_paste) { } TEST_F(ExtrefTest, extref_paste_duplicate_projname) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; auto base1_id = setupBase(basePathName1, [this]() { auto prefab = create("Prefab"); @@ -443,8 +443,8 @@ TEST_F(ExtrefTest, extref_paste_duplicate_projname) { } TEST_F(ExtrefTest, filecopy_paste_fail_same_object) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto mesh = create("mesh"); @@ -462,9 +462,9 @@ TEST_F(ExtrefTest, filecopy_paste_fail_same_object) { } TEST_F(ExtrefTest, filecopy_paste_fail_different_object) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; - auto compositePathName{(cwd_path() / "composite.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName1, [this]() { auto mesh = create("mesh"); @@ -484,8 +484,8 @@ TEST_F(ExtrefTest, filecopy_paste_fail_different_object) { } TEST_F(ExtrefTest, filecopy_paste_fail_new_object) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto mesh = create("mesh"); @@ -505,8 +505,8 @@ TEST_F(ExtrefTest, filecopy_paste_fail_new_object) { TEST_F(ExtrefTest, extref_paste_same_project_name_after_delete_with_undo) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; auto base_id1 = setupBase(basePathName1, [this]() { auto prefab = create("Prefab"); @@ -550,7 +550,7 @@ TEST_F(ExtrefTest, extref_paste_same_project_name_after_delete_with_undo) { } TEST_F(ExtrefTest, duplicate_extref_paste_discard_all) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("Prefab"); @@ -575,7 +575,7 @@ TEST_F(ExtrefTest, duplicate_extref_paste_discard_all) { } TEST_F(ExtrefTest, duplicate_extref_paste_discard_some) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto mesh = create("Mesh"); @@ -611,8 +611,8 @@ TEST_F(ExtrefTest, duplicate_extref_paste_discard_some) { } TEST_F(ExtrefTest, extref_projname_change_update) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -637,8 +637,8 @@ TEST_F(ExtrefTest, extref_projname_change_update) { } TEST_F(ExtrefTest, extref_projname_change_paste_more) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -668,8 +668,8 @@ TEST_F(ExtrefTest, extref_projname_change_paste_more) { } TEST_F(ExtrefTest, extref_can_delete_only_unused) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto mesh = create("mesh"); @@ -704,8 +704,8 @@ TEST_F(ExtrefTest, extref_can_delete_only_unused) { } TEST_F(ExtrefTest, extref_can_delete_only_unused_with_links) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto luaSource = create("luaSource"); @@ -739,8 +739,8 @@ end } TEST_F(ExtrefTest, extref_cant_move) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto mesh = create("mesh"); @@ -765,8 +765,8 @@ TEST_F(ExtrefTest, extref_cant_move) { } TEST_F(ExtrefTest, extref_cant_paste_into) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto material = create("material"); @@ -789,8 +789,8 @@ TEST_F(ExtrefTest, extref_cant_paste_into) { } TEST_F(ExtrefTest, prefab_update_create_child) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this](){ auto prefab = create("prefab"); @@ -817,8 +817,8 @@ TEST_F(ExtrefTest, prefab_update_create_child) { } TEST_F(ExtrefTest, prefab_update_delete_child) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -844,8 +844,8 @@ TEST_F(ExtrefTest, prefab_update_delete_child) { TEST_F(ExtrefTest, prefab_update_stop_using_child) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto left = create("prefab_left"); @@ -872,8 +872,8 @@ TEST_F(ExtrefTest, prefab_update_stop_using_child) { }); } TEST_F(ExtrefTest, prefab_update_inter_prefab_scenegraph_move) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto left = create("prefab_left"); @@ -906,8 +906,8 @@ TEST_F(ExtrefTest, prefab_update_inter_prefab_scenegraph_move) { } TEST_F(ExtrefTest, prefab_update_move_and_delete_parent) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -939,8 +939,8 @@ TEST_F(ExtrefTest, prefab_update_move_and_delete_parent) { } TEST_F(ExtrefTest, prefab_update_move_and_delete_parent_linked) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -988,8 +988,8 @@ end }); } TEST_F(ExtrefTest, update_losing_uniforms) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto mesh = create_mesh("mesh", "meshes/Duck.glb"); @@ -1024,7 +1024,7 @@ TEST_F(ExtrefTest, update_losing_uniforms) { updateBase(basePathName, [this]() { auto material = find("material"); - cmd->set({material, {"uriVertex"}}, (cwd_path() / "shaders/nosuchfile.vert").string()); + cmd->set({material, {"uriVertex"}}, (test_path() / "shaders/nosuchfile.vert").string()); }); updateComposite(compositePathName, [this]() { @@ -1045,8 +1045,8 @@ TEST_F(ExtrefTest, update_losing_uniforms) { } TEST_F(ExtrefTest, prefab_instance_update) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -1090,9 +1090,65 @@ TEST_F(ExtrefTest, prefab_instance_update) { }); } +TEST_F(ExtrefTest, prefab_instance_update_lua_new_property) { + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; + + TextFile scriptFile = makeFile("script.lua", R"( +function interface() + IN.i = INT +end +function run() +end +)"); + + setupBase(basePathName, [this, &scriptFile]() { + auto prefab = create("prefab"); + auto lua = create("prefab_lua", prefab); + cmd->set({lua, {"uri"}}, scriptFile); + cmd->set({lua, {"luaInputs", "i"}}, 7); + }); + + setupComposite(basePathName, compositePathName, {"prefab"}, [this]() { + auto prefab = findExt("prefab"); + auto lua = findExt("prefab_lua"); + ASSERT_EQ(prefab->children_->asVector(), std::vector({lua})); + + auto inst = create("inst"); + cmd->set({inst, {"template"}}, prefab); + auto inst_children = inst->children_->asVector(); + ASSERT_EQ(inst_children.size(), 1); + auto inst_lua = inst_children[0]->as(); + ASSERT_EQ(lua->luaInputs_->get("i")->asInt(), inst_lua->luaInputs_->get("i")->asInt()); + + cmd->set({inst_lua, {"luaInputs", "i"}}, 11); + }); + + raco::utils::file::write(scriptFile.path.string(), R"( +function interface() + IN.i = INT + IN.f = FLOAT +end +function run() +end +)"); + + updateBase(basePathName, [this]() { + auto lua = find("prefab_lua"); + cmd->set({lua, {"luaInputs", "f"}}, 13.0); + }); + + updateComposite(compositePathName, [this]() { + auto inst = find("inst"); + auto inst_lua = inst->children_->get(0)->asRef()->as(); + ASSERT_EQ(inst_lua->luaInputs_->get("i")->asInt(), 11); + ASSERT_EQ(inst_lua->luaInputs_->get("f")->asDouble(), 13.0); + }); +} + TEST_F(ExtrefTest, prefab_instance_lua_update_link) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -1135,8 +1191,8 @@ end } TEST_F(ExtrefTest, duplicate_link_paste_prefab) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -1164,7 +1220,7 @@ end } TEST_F(ExtrefTest, duplicate_link_paste_lua) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto start = create("start"); @@ -1194,7 +1250,7 @@ end } TEST_F(ExtrefTest, duplicate_link_paste_chained_lua) { - auto basePathName{(cwd_path() / "base.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; setupBase(basePathName, [this]() { auto start = create("start"); @@ -1231,9 +1287,9 @@ end } TEST_F(ExtrefTest, nesting_create) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto midPathName((cwd_path() / "mid.rcp").string()); - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto midPathName((test_path() / "mid.rcp").string()); + auto compositePathName{(test_path() / "composite.rcp").string()}; std::string base_id; std::string mid_id; @@ -1262,10 +1318,10 @@ TEST_F(ExtrefTest, nesting_create) { } TEST_F(ExtrefTest, filecopy_update_fail_nested_same_object) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; - auto midPathName((cwd_path() / "mid.rcp").string()); - auto compositePathName{(cwd_path() / "composite.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; + auto midPathName((test_path() / "mid.rcp").string()); + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName1, [this]() { auto mesh = create("mesh"); @@ -1300,10 +1356,10 @@ TEST_F(ExtrefTest, filecopy_update_fail_nested_same_object) { } TEST_F(ExtrefTest, filecopy_update_fail_nested_different_object) { - auto basePathName1{(cwd_path() / "base1.rcp").generic_string()}; - auto basePathName2{(cwd_path() / "base2.rcp").generic_string()}; - auto midPathName((cwd_path() / "mid.rcp").string()); - auto compositePathName{(cwd_path() / "composite.rcp").generic_string()}; + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; + auto midPathName((test_path() / "mid.rcp").string()); + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName1, [this]() { auto mesh = create("mesh"); @@ -1340,8 +1396,8 @@ TEST_F(ExtrefTest, filecopy_update_fail_nested_different_object) { TEST_F(ExtrefTest, nesting_create_loop_fail) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto prefab = create("prefab"); @@ -1358,9 +1414,9 @@ TEST_F(ExtrefTest, nesting_create_loop_fail) { } TEST_F(ExtrefTest, nested_shared_material_update) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto duckPathName((cwd_path() / "duck.rcp").string()); - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto duckPathName((test_path() / "duck.rcp").string()); + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto material = create_material("material", "shaders/basic.vert", "shaders/basic.frag"); @@ -1391,8 +1447,8 @@ TEST_F(ExtrefTest, nested_shared_material_update) { } TEST_F(ExtrefTest, meshnode_uniform_refs_private_material) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto mesh = create_mesh("mesh", "meshes/Duck.glb"); @@ -1412,8 +1468,8 @@ TEST_F(ExtrefTest, meshnode_uniform_refs_private_material) { } TEST_F(ExtrefTest, meshnode_uniform_refs_shared_material) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto mesh = create_mesh("mesh", "meshes/Duck.glb"); @@ -1441,9 +1497,9 @@ TEST_F(ExtrefTest, meshnode_uniform_refs_shared_material) { TEST_F(ExtrefTest, nested_shared_material_linked_update) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto duckPathName((cwd_path() / "duck.rcp").string()); - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto duckPathName((test_path() / "duck.rcp").string()); + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto material = create_material("material", "shaders/basic.vert", "shaders/basic.frag"); @@ -1489,8 +1545,8 @@ end } TEST_F(ExtrefTest, shared_material_stacked_lua_linked_update) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto material = create_material("material", "shaders/basic.vert", "shaders/basic.frag"); @@ -1539,10 +1595,10 @@ end } TEST_F(ExtrefTest, diamond_shared_material_linked_update) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto duckPathName((cwd_path() / "duck.rcp").string()); - auto quadPathName((cwd_path() / "quad.rcp").string()); - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto duckPathName((test_path() / "duck.rcp").string()); + auto quadPathName((test_path() / "quad.rcp").string()); + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto material = create_material("material", "shaders/basic.vert", "shaders/basic.frag"); @@ -1607,10 +1663,10 @@ end } TEST_F(ExtrefTest, diamond_shared_material_linked_move_lua) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto duckPathName((cwd_path() / "duck.rcp").string()); - auto quadPathName((cwd_path() / "quad.rcp").string()); - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto duckPathName((test_path() / "duck.rcp").string()); + auto quadPathName((test_path() / "quad.rcp").string()); + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { auto material = create_material("material", "shaders/basic.vert", "shaders/basic.frag"); @@ -1679,17 +1735,19 @@ end } TEST_F(ExtrefTest, saveas_reroot_uri_lua) { - auto basePathName{(cwd_path() / "base.rcp").generic_string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + + auto base_id = setupBase(basePathName, [this, basePathName]() { + project->setCurrentPath(basePathName); - auto base_id = setupBase(basePathName, [this]() { auto prefab = create("prefab"); auto lua = create("lua", prefab); cmd->set({lua, {"uri"}}, std::string("relativeURI")); }); - std::filesystem::create_directory((cwd_path() / "subdir")); + std::filesystem::create_directory((test_path() / "subdir")); setupGeneric([this, basePathName, base_id]() { - project->setCurrentPath((cwd_path() / "project.file").string()); + project->setCurrentPath((test_path() / "project.file").string()); pasteFromExt(basePathName, {"prefab"}, true); auto prefab = findExt("prefab"); @@ -1712,7 +1770,7 @@ TEST_F(ExtrefTest, saveas_reroot_uri_lua) { ASSERT_EQ(project->externalProjectsMap().at(base_id).path, "base.rcp"); - ASSERT_TRUE(app->activeRaCoProject().saveAs((cwd_path() / "subdir" / "project.file").string().c_str())); + ASSERT_TRUE(app->activeRaCoProject().saveAs((test_path() / "subdir" / "project.file").string().c_str())); EXPECT_EQ(*lua_prefab->uri_, std::string("relativeURI")); EXPECT_EQ(*lua_inst->uri_, std::string("relativeURI")); @@ -1723,16 +1781,18 @@ TEST_F(ExtrefTest, saveas_reroot_uri_lua) { } TEST_F(ExtrefTest, paste_reroot_lua_uri_dir_up) { - auto basePathName{(cwd_path() / "base.rcp").generic_string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + + auto base_id = setupBase(basePathName, [this, basePathName]() { + project->setCurrentPath(basePathName); - auto base_id = setupBase(basePathName, [this]() { auto prefab = create("prefab"); auto lua = create("lua", prefab); cmd->set({lua, {"uri"}}, std::string("relativeURI")); }); setupGeneric([this, basePathName, base_id]() { - project->setCurrentPath((cwd_path() / "subdir" / "project.file").string()); + project->setCurrentPath((test_path() / "subdir" / "project.file").string()); pasteFromExt(basePathName, {"prefab"}, true); auto prefab = findExt("prefab"); @@ -1760,17 +1820,19 @@ TEST_F(ExtrefTest, paste_reroot_lua_uri_dir_up) { } TEST_F(ExtrefTest, paste_reroot_lua_uri_dir_down) { - std::filesystem::create_directory((cwd_path() / "subdir")); - auto basePathName{(cwd_path() / "subdir" / "base.rcp").generic_string()}; + std::filesystem::create_directory((test_path() / "subdir")); + auto basePathName{(test_path() / "subdir" / "base.rcp").string()}; + + auto base_id = setupBase(basePathName, [this, basePathName]() { + project->setCurrentPath(basePathName); - auto base_id = setupBase(basePathName, [this]() { auto prefab = create("prefab"); auto lua = create("lua", prefab); cmd->set({lua, {"uri"}}, std::string("relativeURI")); }); setupGeneric([this, basePathName, base_id]() { - project->setCurrentPath((cwd_path() / "project.file").string()); + project->setCurrentPath((test_path() / "project.file").string()); pasteFromExt(basePathName, {"prefab"}, true); auto prefab = findExt("prefab"); @@ -1798,9 +1860,9 @@ TEST_F(ExtrefTest, paste_reroot_lua_uri_dir_down) { } TEST_F(ExtrefTest, paste_reroot_lua_uri_with_link_down) { - std::filesystem::create_directory((cwd_path() / "subdir")); - auto basePathName{(cwd_path() / "subdir" / "base.rcp").generic_string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + std::filesystem::create_directory((test_path() / "subdir")); + auto basePathName{(test_path() / "subdir" / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this, basePathName]() { project->setCurrentPath(basePathName); @@ -1809,7 +1871,7 @@ TEST_F(ExtrefTest, paste_reroot_lua_uri_with_link_down) { auto node = create("prefab_node", prefab); auto lua = create("prefab_lua", prefab); - raco::utils::file::write((cwd_path() / "subdir/script.lua").string(), R"( + raco::utils::file::write((test_path() / "subdir/script.lua").string(), R"( function interface() IN.v = VEC3F OUT.v = VEC3F @@ -1822,7 +1884,7 @@ end }); setupGeneric([this, basePathName]() { - project->setCurrentPath((cwd_path() / "project.file").string()); + project->setCurrentPath((test_path() / "project.file").string()); pasteFromExt(basePathName, {"prefab"}, true); auto prefab = findExt("prefab"); @@ -1853,8 +1915,8 @@ end } TEST_F(ExtrefTest, prefab_link_quaternion_in_prefab) { - auto basePathName1{(cwd_path() / "base.rcp").string()}; - auto basePathName2{(cwd_path() / "base2.rcp").string()}; + auto basePathName1{(test_path() / "base.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this, basePathName1]() { project->setCurrentPath(basePathName1); @@ -1863,7 +1925,7 @@ TEST_F(ExtrefTest, prefab_link_quaternion_in_prefab) { auto node = create("prefab_node", prefab); auto lua = create("prefab_lua", prefab); - raco::utils::file::write((cwd_path() / "script.lua").string(), R"( + raco::utils::file::write((test_path() / "script.lua").string(), R"( function interface() IN.v = VEC4F OUT.v = VEC4F @@ -1898,12 +1960,12 @@ end } TEST_F(ExtrefTest, animation_channel_data_gets_propagated) { - auto basePathName1{(cwd_path() / "base.rcp").string()}; - auto basePathName2{(cwd_path() / "base2.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName1{(test_path() / "base.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName1, [this]() { - auto animChannel = create_animationchannel("animCh", "meshse/InterpolationTest/InterpolationTest.gltf"); + auto animChannel = create_animationchannel("animCh", "meshes/InterpolationTest/InterpolationTest.gltf"); }); setupBase(basePathName2, [this, basePathName1]() { @@ -1919,8 +1981,8 @@ TEST_F(ExtrefTest, animation_channel_data_gets_propagated) { } TEST_F(ExtrefTest, animation_in_extref_prefab_gets_propagated) { - auto basePathName1{(cwd_path() / "base.rcp").string()}; - auto basePathName2{(cwd_path() / "base2.rcp").string()}; + auto basePathName1{(test_path() / "base.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto anim = create_animation("anim"); @@ -1930,7 +1992,7 @@ TEST_F(ExtrefTest, animation_in_extref_prefab_gets_propagated) { auto prefab = create("prefab"); cmd->moveScenegraphChildren({animNode}, prefab); - auto animChannel = create_animationchannel("animCh", "meshse/InterpolationTest/InterpolationTest.gltf"); + auto animChannel = create_animationchannel("animCh", "meshes/InterpolationTest/InterpolationTest.gltf"); cmd->set({anim, {"animationChannels", "Channel 0"}}, animChannel); }); @@ -1947,8 +2009,8 @@ TEST_F(ExtrefTest, animation_in_extref_prefab_gets_propagated) { } TEST_F(ExtrefTest, prefab_cut_deep_linked_does_not_delete_shared) { - auto basePathName{(cwd_path() / "base.rcp").string()}; - auto compositePathName{(cwd_path() / "composite.rcp").string()}; + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; setupBase(basePathName, [this]() { @@ -1981,18 +2043,18 @@ TEST_F(ExtrefTest, prefab_cut_deep_linked_does_not_delete_shared) { } TEST_F(ExtrefTest, module_gets_propagated) { - auto basePathName1{(cwd_path() / "base.rcp").string()}; - auto basePathName2{(cwd_path() / "base2.rcp").string()}; + auto basePathName1{(test_path() / "base.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto module = create("module"); - cmd->set({module, &LuaScriptModule::uri_}, (cwd_path() / "scripts" / "moduleDefinition.lua").string()); + cmd->set({module, &LuaScriptModule::uri_}, (test_path() / "scripts" / "moduleDefinition.lua").string()); }); setupBase(basePathName2, [this, basePathName1]() { ASSERT_TRUE(pasteFromExt(basePathName1, {"module"}, true)); auto lua = create("script"); - cmd->set({lua, &LuaScript::uri_}, (cwd_path() / "scripts" / "moduleDependency.lua").string()); + cmd->set({lua, &LuaScript::uri_}, (test_path() / "scripts" / "moduleDependency.lua").string()); auto module = findExt("module"); cmd->set({lua, {"luaModules", "coalas"}}, module); @@ -2003,8 +2065,8 @@ TEST_F(ExtrefTest, module_gets_propagated) { } TEST_F(ExtrefTest, prefab_instance_with_lua_and_module) { - auto basePathName1{(cwd_path() / "base.rcp").string()}; - auto basePathName2{(cwd_path() / "base2.rcp").string()}; + auto basePathName1{(test_path() / "base.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto prefab = create("prefab"); @@ -2012,11 +2074,11 @@ TEST_F(ExtrefTest, prefab_instance_with_lua_and_module) { cmd->set({inst, &PrefabInstance::template_}, prefab); auto lua = create("lua", prefab); - cmd->set({lua, &LuaScript::uri_}, (cwd_path() / "scripts/moduleDependency.lua").string()); + cmd->set({lua, &LuaScript::uri_}, (test_path() / "scripts/moduleDependency.lua").string()); cmd->moveScenegraphChildren({lua}, prefab); auto luaModule = create("luaModule"); - cmd->set({luaModule, &LuaScriptModule::uri_}, (cwd_path() / "scripts/moduleDefinition.lua").string()); + cmd->set({luaModule, &LuaScriptModule::uri_}, (test_path() / "scripts/moduleDefinition.lua").string()); cmd->set({lua, {"luaModules", "coalas"}}, luaModule); }); @@ -2032,8 +2094,8 @@ TEST_F(ExtrefTest, prefab_instance_with_lua_and_module) { }); } TEST_F(ExtrefTest, prefab_instance_update_lua_and_module_remove_module_ref) { - auto basePathName1{(cwd_path() / "base.rcp").string()}; - auto basePathName2{(cwd_path() / "base2.rcp").string()}; + auto basePathName1{(test_path() / "base.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto prefab = create("prefab"); @@ -2041,11 +2103,11 @@ TEST_F(ExtrefTest, prefab_instance_update_lua_and_module_remove_module_ref) { cmd->set({inst, &PrefabInstance::template_}, prefab); auto lua = create("lua", prefab); - cmd->set({lua, &LuaScript::uri_}, (cwd_path() / "scripts/moduleDependency.lua").string()); + cmd->set({lua, &LuaScript::uri_}, (test_path() / "scripts/moduleDependency.lua").string()); cmd->moveScenegraphChildren({lua}, prefab); auto luaModule = create("luaModule"); - cmd->set({luaModule, &LuaScriptModule::uri_}, (cwd_path() / "scripts/moduleDefinition.lua").string()); + cmd->set({luaModule, &LuaScriptModule::uri_}, (test_path() / "scripts/moduleDefinition.lua").string()); cmd->set({lua, {"luaModules", "coalas"}}, luaModule); }); @@ -2076,8 +2138,8 @@ TEST_F(ExtrefTest, prefab_instance_update_lua_and_module_remove_module_ref) { } TEST_F(ExtrefTest, prefab_instance_update_lua_and_module_change_lua_uri) { - auto basePathName1{(cwd_path() / "base.rcp").string()}; - auto basePathName2{(cwd_path() / "base2.rcp").string()}; + auto basePathName1{(test_path() / "base.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; setupBase(basePathName1, [this]() { auto prefab = create("prefab"); @@ -2085,11 +2147,11 @@ TEST_F(ExtrefTest, prefab_instance_update_lua_and_module_change_lua_uri) { cmd->set({inst, &PrefabInstance::template_}, prefab); auto lua = create("lua", prefab); - cmd->set({lua, &LuaScript::uri_}, (cwd_path() / "scripts/moduleDependency.lua").string()); + cmd->set({lua, &LuaScript::uri_}, (test_path() / "scripts/moduleDependency.lua").string()); cmd->moveScenegraphChildren({lua}, prefab); auto luaModule = create("luaModule"); - cmd->set({luaModule, &LuaScriptModule::uri_}, (cwd_path() / "scripts/moduleDefinition.lua").string()); + cmd->set({luaModule, &LuaScriptModule::uri_}, (test_path() / "scripts/moduleDefinition.lua").string()); cmd->set({lua, {"luaModules", "coalas"}}, luaModule); }); @@ -2106,7 +2168,7 @@ TEST_F(ExtrefTest, prefab_instance_update_lua_and_module_change_lua_uri) { updateBase(basePathName1, [this]() { auto lua = find("lua"); - cmd->set({lua, &LuaScript::uri_}, (cwd_path() / "scripts/types-scalar.lua").string()); + cmd->set({lua, &LuaScript::uri_}, (test_path() / "scripts/types-scalar.lua").string()); }); updateBase(basePathName2, [this]() { diff --git a/datamodel/libCore/tests/Link_test.cpp b/datamodel/libCore/tests/Link_test.cpp index 3423969a..9102f66b 100644 --- a/datamodel/libCore/tests/Link_test.cpp +++ b/datamodel/libCore/tests/Link_test.cpp @@ -42,7 +42,7 @@ using namespace raco::user_types; class LinkTest : public TestEnvironmentCore { public: void change_uri(SEditorObject obj, const std::string& newvalue) { - commandInterface.set({obj, {"uri"}}, (cwd_path() / newvalue).string()); + commandInterface.set({obj, {"uri"}}, (test_path() / newvalue).string()); } }; diff --git a/datamodel/libCore/tests/PathManager_test.cpp b/datamodel/libCore/tests/PathManager_test.cpp new file mode 100644 index 00000000..02e92fca --- /dev/null +++ b/datamodel/libCore/tests/PathManager_test.cpp @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "core/PathManager.h" + +#include "testing/RacoBaseTest.h" + +#include "gtest/gtest.h" + +using namespace raco::core; + +class PathManagerTest : public RacoBaseTest<> { +public: + raco::utils::u8path test_relative_path() const override { + return raco::utils::u8path{test_suite_name()} / test_case_name() / u8"滴滴启动纽交所退市 äüöß"; + } +}; + +TEST_F(PathManagerTest, MigrationOldConfigfilesDoIfNewNotPresent) { + auto programPath = test_path() / "App" / "bin"; + auto appDataPath = test_path() / "AppData"; + auto legacyConfigFiles = test_path() / "configfiles"; + + std::filesystem::remove_all(test_path()); + std::filesystem::create_directories(programPath); + std::filesystem::create_directories(legacyConfigFiles); + + auto layout = raco::utils::u8path(makeFile("configfiles/layout.ini", "")); + auto preferences = raco::utils::u8path(makeFile("configfiles/preferences.ini", "")); + auto log1 = raco::utils::u8path(makeFile("configfiles/log1.log", "")); + auto log2 = raco::utils::u8path(makeFile("configfiles/log2.log", "")); + + ASSERT_FALSE(appDataPath.existsDirectory()); + ASSERT_TRUE(legacyConfigFiles.existsDirectory()); + ASSERT_TRUE(layout.existsFile()); + ASSERT_TRUE(preferences.existsFile()); + ASSERT_TRUE(log1.existsFile()); + ASSERT_TRUE(log2.existsFile()); + + PathManager::init(programPath.string(), appDataPath.string()); + + ASSERT_FALSE(legacyConfigFiles.existsDirectory()); + ASSERT_FALSE(layout.existsFile()); + ASSERT_FALSE(preferences.existsFile()); + ASSERT_FALSE(log1.existsFile()); + ASSERT_FALSE(log2.existsFile()); + ASSERT_TRUE(appDataPath.existsDirectory()); + ASSERT_TRUE((appDataPath / "layout.ini").existsFile()); + ASSERT_TRUE((appDataPath / "preferences.ini").existsFile()); + ASSERT_TRUE((appDataPath / "logs/log1.log").existsFile()); + ASSERT_TRUE((appDataPath / "logs/log2.log").existsFile()); +} + +TEST_F(PathManagerTest, MigrationOldConfigfilesDoIfNewPresentEmptyFolder) { + auto programPath = test_path() / "App" / "bin"; + auto appDataPath = test_path() / "AppData"; + auto legacyConfigFiles = test_path() / "configfiles"; + + std::filesystem::remove_all(test_path()); + std::filesystem::create_directories(appDataPath); + std::filesystem::create_directories(programPath); + std::filesystem::create_directories(legacyConfigFiles); + + auto layout = raco::utils::u8path(makeFile("configfiles/layout.ini", "")); + auto preferences = raco::utils::u8path(makeFile("configfiles/preferences.ini", "")); + auto log1 = raco::utils::u8path(makeFile("configfiles/log1.log", "")); + auto log2 = raco::utils::u8path(makeFile("configfiles/log2.log", "")); + + ASSERT_TRUE(appDataPath.existsDirectory()); + ASSERT_TRUE(legacyConfigFiles.existsDirectory()); + ASSERT_TRUE(layout.existsFile()); + ASSERT_TRUE(preferences.existsFile()); + ASSERT_TRUE(log1.existsFile()); + ASSERT_TRUE(log2.existsFile()); + + PathManager::init(programPath.string(), appDataPath.string()); + + ASSERT_FALSE(legacyConfigFiles.existsDirectory()); + ASSERT_FALSE(layout.existsFile()); + ASSERT_FALSE(preferences.existsFile()); + ASSERT_FALSE(log1.existsFile()); + ASSERT_FALSE(log2.existsFile()); + + ASSERT_TRUE(appDataPath.existsDirectory()); + ASSERT_TRUE((appDataPath / "layout.ini").existsFile()); + ASSERT_TRUE((appDataPath / "preferences.ini").existsFile()); + ASSERT_TRUE((appDataPath / "logs/log1.log").existsFile()); + ASSERT_TRUE((appDataPath / "logs/log2.log").existsFile()); +} + +TEST_F(PathManagerTest, MigrationOldConfigfilesDoNotIfNewAlreadyPresent) { + auto programPath = test_path() / "App" / "bin"; + auto appDataPath = test_path() / "AppData"; + auto legacyConfigFiles = test_path() / "configfiles"; + + std::filesystem::remove_all(test_path()); + std::filesystem::create_directories(appDataPath); + std::filesystem::create_directories(programPath); + std::filesystem::create_directories(legacyConfigFiles); + + makeFile("AppData/custom.ini", ""); + + auto layout = raco::utils::u8path(makeFile("configfiles/layout.ini", "")); + auto preferences = raco::utils::u8path(makeFile("configfiles/preferences.ini", "")); + auto log1 = raco::utils::u8path(makeFile("configfiles/log1.log", "")); + auto log2 = raco::utils::u8path(makeFile("configfiles/log2.log", "")); + + ASSERT_TRUE(appDataPath.existsDirectory()); + ASSERT_TRUE(legacyConfigFiles.existsDirectory()); + ASSERT_TRUE(layout.existsFile()); + ASSERT_TRUE(preferences.existsFile()); + ASSERT_TRUE(log1.existsFile()); + ASSERT_TRUE(log2.existsFile()); + + PathManager::init(programPath.string(), appDataPath.string()); + + ASSERT_TRUE(appDataPath.existsDirectory()); + ASSERT_TRUE(legacyConfigFiles.existsDirectory()); + ASSERT_TRUE(layout.existsFile()); + ASSERT_TRUE(preferences.existsFile()); + ASSERT_TRUE(log1.existsFile()); + ASSERT_TRUE(log2.existsFile()); + + ASSERT_TRUE((appDataPath / "custom.ini").existsFile()); + ASSERT_FALSE((appDataPath / "layout.ini").existsFile()); + ASSERT_FALSE((appDataPath / "preferences.ini").existsFile()); + ASSERT_FALSE((appDataPath / "logs/log1.log").existsFile()); + ASSERT_FALSE((appDataPath / "logs/log2.log").existsFile()); +} \ No newline at end of file diff --git a/datamodel/libCore/tests/Prefab_test.cpp b/datamodel/libCore/tests/Prefab_test.cpp index f0aa13a1..e339a9c9 100644 --- a/datamodel/libCore/tests/Prefab_test.cpp +++ b/datamodel/libCore/tests/Prefab_test.cpp @@ -27,7 +27,7 @@ #include "user_types/MeshNode.h" #include "user_types/Node.h" #include "user_types/Prefab.h" -#include "user_types/PrefabInstance.h" +#include "user_types/Texture.h" #include "gtest/gtest.h" @@ -39,6 +39,68 @@ class PrefabTest : public TestEnvironmentCore { public: }; +TEST_F(PrefabTest, check_id_recreate_same_id) { + auto prefab = create("prefab"); + auto node = create("node", prefab); + auto inst = create_prefabInstance("inst", prefab); + + EXPECT_EQ(inst->children_->size(), 1); + auto inst_node_1 = inst->children_->asVector()[0]; + + commandInterface.set({inst, {"template"}}, SEditorObject{}); + EXPECT_EQ(inst->children_->size(), 0); + + commandInterface.set({inst, {"template"}}, prefab); + EXPECT_EQ(inst->children_->size(), 1); + auto inst_node_2 = inst->children_->asVector()[0]; + + EXPECT_FALSE(inst_node_1 == inst_node_2); + EXPECT_EQ(inst_node_1->objectID(), inst_node_2->objectID()); +} + +TEST_F(PrefabTest, check_id_different_inst) { + auto prefab = create("prefab"); + auto node = create("node", prefab); + auto inst_1 = create_prefabInstance("inst", prefab); + auto inst_2 = create_prefabInstance("inst", prefab); + + EXPECT_EQ(inst_1->children_->size(), 1); + auto inst_node_1 = inst_1->children_->asVector()[0]; + + EXPECT_EQ(inst_2->children_->size(), 1); + auto inst_node_2 = inst_2->children_->asVector()[0]; + + EXPECT_NE(inst_node_1->objectID(), inst_node_2->objectID()); +} + +TEST_F(PrefabTest, check_id_nesting) { + // prefab_2 inst_2 + // prefab inst_1 inst_3 + // node node_1 node_2 + + auto prefab = create("prefab"); + auto node = create("node", prefab); + + auto prefab_2 = create("prefab2"); + auto inst_1 = create_prefabInstance("inst", prefab, prefab_2); + + EXPECT_EQ(inst_1->children_->size(), 1); + auto node_1 = inst_1->children_->asVector()[0]; + + auto inst_2 = create_prefabInstance("inst", prefab_2); + EXPECT_EQ(inst_2->children_->size(), 1); + auto inst_3 = inst_2->children_->asVector()[0]; + EXPECT_EQ(inst_3->children_->size(), 1); + auto node_2 = inst_3->children_->asVector()[0]; + + EXPECT_EQ(node_1->objectID(), EditorObject::XorObjectIDs(node->objectID(), inst_1->objectID())); + + EXPECT_EQ(inst_3->objectID(), EditorObject::XorObjectIDs(inst_1->objectID(), inst_2->objectID())); + + EXPECT_EQ(node_2->objectID(), EditorObject::XorObjectIDs(node_1->objectID(), inst_2->objectID())); + EXPECT_EQ(node_2->objectID(), EditorObject::XorObjectIDs(node->objectID(), inst_3->objectID())); +} + TEST_F(PrefabTest, move_node_in) { auto prefab = create("prefab"); auto inst = create("inst"); @@ -175,8 +237,8 @@ TEST_F(PrefabTest, link_broken_inside_prefab_status_gets_propagated_to_instances auto luaPrefabNodeChild = create("luaPrefabNode"); commandInterface.moveScenegraphChildren({luaPrefabNodeChild}, node); - commandInterface.set({luaPrefabGlobal, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); - commandInterface.set({luaPrefabNodeChild, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set({luaPrefabGlobal, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); + commandInterface.set({luaPrefabNodeChild, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); commandInterface.addLink({luaPrefabGlobal, {"luaOutputs", "ovector3f"}} , {node, {"translation"}}); commandInterface.addLink({luaPrefabNodeChild, {"luaOutputs", "out_float"}}, {luaPrefabGlobal, {"luaInputs", "float"}}); @@ -187,13 +249,13 @@ TEST_F(PrefabTest, link_broken_inside_prefab_status_gets_propagated_to_instances {{inst_lua_prefab, {"luaOutputs", "ovector3f"}}, {inst_node, {"translation"}}}}}; checkLinks(refLinks); - commandInterface.set({luaPrefabNodeChild, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); - commandInterface.set({luaPrefabGlobal, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set({luaPrefabNodeChild, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); + commandInterface.set({luaPrefabGlobal, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); ASSERT_EQ(project.links().size(), 3); ASSERT_FALSE(std::all_of(project.links().begin(), project.links().end(), [](const auto link) { return link->isValid(); })); - commandInterface.set({luaPrefabGlobal, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); - commandInterface.set({luaPrefabNodeChild, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set({luaPrefabGlobal, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); + commandInterface.set({luaPrefabNodeChild, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); checkLinks(refLinks); } @@ -464,7 +526,7 @@ end TEST_F(PrefabTest, restore_cached_lua_prop_when_breaking_uri) { auto prefab = create("prefab"); auto lua = create("lua", prefab); - commandInterface.set({lua, {"uri"}}, (cwd_path() / "scripts/types-scalar.lua").string()); + commandInterface.set({lua, {"uri"}}, (test_path() / "scripts/types-scalar.lua").string()); auto inst = create("inst"); commandInterface.set({inst, {"template"}}, prefab); @@ -478,7 +540,7 @@ TEST_F(PrefabTest, restore_cached_lua_prop_when_breaking_uri) { ASSERT_EQ(ValueHandle(lua, {"luaInputs"}).size(), 0); ASSERT_EQ(ValueHandle(inst_lua, {"luaInputs"}).size(), 0); - commandInterface.set({lua, {"uri"}}, (cwd_path() / "scripts/types-scalar.lua").string()); + commandInterface.set({lua, {"uri"}}, (test_path() / "scripts/types-scalar.lua").string()); ASSERT_TRUE(ValueHandle(lua, {"luaInputs"}).hasProperty("float")); ASSERT_TRUE(ValueHandle(inst_lua, {"luaInputs"}).hasProperty("float")); @@ -544,7 +606,7 @@ TEST_F(PrefabTest, update_luascript_module_dependant_in_prefab_no_module) { commandInterface.set({inst, &PrefabInstance::template_}, prefab); auto lua = create("lua", prefab); - commandInterface.set({lua, &LuaScript::uri_}, (cwd_path() / "scripts/moduleDependency.lua").string()); + commandInterface.set({lua, &LuaScript::uri_}, (test_path() / "scripts/moduleDependency.lua").string()); commandInterface.moveScenegraphChildren({lua}, prefab); auto inst_lua = raco::select(inst->children_->asVector()); @@ -559,11 +621,11 @@ TEST_F(PrefabTest, update_luascript_module_dependant_in_prefab_add_module) { commandInterface.set({inst, &PrefabInstance::template_}, prefab); auto lua = create("lua", prefab); - commandInterface.set({lua, &LuaScript::uri_}, (cwd_path() / "scripts/moduleDependency.lua").string()); + commandInterface.set({lua, &LuaScript::uri_}, (test_path() / "scripts/moduleDependency.lua").string()); commandInterface.moveScenegraphChildren({lua}, prefab); auto luaModule = create("luaModule"); - commandInterface.set({luaModule, &LuaScriptModule::uri_}, (cwd_path() / "scripts/moduleDefinition.lua").string()); + commandInterface.set({luaModule, &LuaScriptModule::uri_}, (test_path() / "scripts/moduleDefinition.lua").string()); commandInterface.set({lua, {"luaModules", "coalas"}}, luaModule); @@ -579,11 +641,11 @@ TEST_F(PrefabTest, update_luascript_module_dependant_in_prefab_remove_module) { commandInterface.set({inst, &PrefabInstance::template_}, prefab); auto lua = create("lua", prefab); - commandInterface.set({lua, &LuaScript::uri_}, (cwd_path() / "scripts/moduleDependency.lua").string()); + commandInterface.set({lua, &LuaScript::uri_}, (test_path() / "scripts/moduleDependency.lua").string()); commandInterface.moveScenegraphChildren({lua}, prefab); auto luaModule = create("luaModule"); - commandInterface.set({luaModule, &LuaScriptModule::uri_}, (cwd_path() / "scripts/moduleDefinition.lua").string()); + commandInterface.set({luaModule, &LuaScriptModule::uri_}, (test_path() / "scripts/moduleDefinition.lua").string()); commandInterface.set({lua, {"luaModules", "coalas"}}, luaModule); commandInterface.set({lua, {"luaModules", "coalas"}}, SEditorObject{}); @@ -593,3 +655,173 @@ TEST_F(PrefabTest, update_luascript_module_dependant_in_prefab_remove_module) { ASSERT_TRUE(commandInterface.errors().hasError({lua})); ASSERT_TRUE(commandInterface.errors().hasError({inst_lua})); } + +TEST_F(PrefabTest, update_private_material_ref_change_with_overlapping_property_names) { + auto material1 = create("material1"); + auto material2 = create("material2"); + + + auto firstVertFile = makeFile("first.vert", R"( +#version 300 es +precision highp float; + +uniform mat4 uWorldViewProjectionMatrix; +uniform vec2 offset; +uniform vec2 flip; + +in vec3 a_Position; +in vec2 a_TextureCoordinate; + +out vec2 v_TextureCoordinate; + +void main() { + float coordsX = (flip.x == 0.0) ? (a_TextureCoordinate.x) : (1.0 - a_TextureCoordinate.x); + float coordsY = (flip.y == 0.0) ? (a_TextureCoordinate.y) : (1.0 - a_TextureCoordinate.y); + v_TextureCoordinate = vec2(coordsX + offset.x, coordsY - offset.y); + + gl_Position = uWorldViewProjectionMatrix * vec4(a_Position, 1.0); +} + +)"); + auto firstFragFile = makeFile("first.frag", R"( +#version 300 es +precision highp float; + +uniform float u_Opacity; +uniform sampler2D u_Tex; +uniform vec4 u_Color; +uniform float currentTile; +uniform float numberOfTiles; + + +in vec2 v_TextureCoordinate; +out vec4 FragColor; + +void main(){ + vec4 clr0 = texture(u_Tex, vec2(v_TextureCoordinate.x/numberOfTiles+(currentTile-1.0)*1.0/numberOfTiles, v_TextureCoordinate.y)); + FragColor.rgb = clr0.rgb * u_Color.rgb* u_Opacity; + FragColor.a = clr0.a * u_Opacity * u_Color.a; + +} +)"); + auto secondVertFile = makeFile("second.vert", R"( +#version 300 es +precision highp float; + +uniform mat4 uWorldViewProjectionMatrix; +uniform vec2 offset; +uniform vec2 flip; +uniform float numberOfTilesX; +uniform float numberOfTilesY; +uniform float currentTile; +uniform sampler2D u_Tex; + +in vec3 a_Position; +in vec2 a_TextureCoordinate; + +out vec2 v_TextureCoordinate; + +void main() { + float coordsX = (flip.x == 0.0) ? (a_TextureCoordinate.x) : (1.0 - a_TextureCoordinate.x); + float coordsY = (flip.y == 0.0) ? (1.0 - a_TextureCoordinate.y) : (a_TextureCoordinate.y); + float tileX = mod(currentTile-1.0,numberOfTilesX); + float tileY = numberOfTilesY-floor((currentTile-1.0)/numberOfTilesX)-1.0; + float tilingAppliedU = (tileX+coordsX)/numberOfTilesX; + float tilingAppliedV = 1.0 - ((tileY+coordsY)/numberOfTilesY); + v_TextureCoordinate = vec2(tilingAppliedU + offset.x, tilingAppliedV - offset.y); + + highp vec2 tiling = vec2(numberOfTilesX, numberOfTilesY); + vec2 texSize = vec2(textureSize(u_Tex,0)); + vec2 autoScale = texSize/tiling*a_Position.xy; + vec2 alignToRaster = vec2(abs(mod(autoScale.x,1.0)), abs(mod(autoScale.y,1.0))); // in case of centered but odd dimensions it will shift half a pixel to align back to the raster + vec3 adjustedPosition = vec3(autoScale-alignToRaster, a_Position.z); + gl_Position = uWorldViewProjectionMatrix * vec4(adjustedPosition, 1.0); +} +)"); + auto secondFragFile = makeFile("second.frag", R"( +#version 300 es +precision highp float; + +uniform float u_Opacity; +uniform sampler2D u_Tex; +uniform vec4 u_Color; + +in vec2 v_TextureCoordinate; + +out vec4 FragColor; + +void main(){ + vec4 clr0 = texture(u_Tex, v_TextureCoordinate); + if (clr0.a <= 0.0) + {discard;} + FragColor.rgb = clr0.rgb * u_Color.rgb; + FragColor.a = clr0.a * u_Opacity * u_Color.a; +} + +)"); + + commandInterface.set({material1, &Material::uriVertex_}, firstVertFile); + commandInterface.set({material1, &Material::uriFragment_}, firstFragFile); + commandInterface.set({material2, &Material::uriVertex_}, secondVertFile); + commandInterface.set({material2, &Material::uriFragment_}, secondFragFile); + + auto prefab = create("prefab"); + auto inst = create("inst"); + commandInterface.set({inst, &PrefabInstance::template_}, prefab); + + auto mesh = create("mesh"); + commandInterface.set({mesh, &Mesh::uri_}, (test_path() / "meshes" / "Duck.glb").string()); + + auto meshNode = create("meshNode"); + commandInterface.set({meshNode, &MeshNode::mesh_}, mesh); + commandInterface.set({meshNode, {"materials", "material", "material"}}, material1); + commandInterface.set({meshNode, {"materials", "material", "private"}}, true); + commandInterface.moveScenegraphChildren({meshNode}, prefab); + + auto texture = create("tex"); + commandInterface.set({meshNode, {"materials", "material", "material"}}, material2); + ASSERT_NO_THROW(commandInterface.set({meshNode, {"materials", "material", "uniforms", "u_Tex"}}, texture)); +} + +#ifdef NDEBUG +TEST_F(PrefabTest, prefab_performance_deletion_with_instance_1000_nodes) { + auto prefab = create("prefab"); + auto inst = create("inst"); + + for (auto i = 0; i < 1000; ++i) { + auto lua = create("lua", prefab); + commandInterface.set({lua, &LuaScript::uri_}, (test_path() / "scripts/types-scalar.lua").string()); + commandInterface.moveScenegraphChildren({lua}, prefab); + } + + commandInterface.set({inst, &PrefabInstance::template_}, prefab); + + assertOperationTimeIsBelow(700, [this, prefab]() { + commandInterface.deleteObjects({prefab}); + }); +} + + +TEST_F(PrefabTest, prefab_performance_deletion_with_instance_and_10_prefab_instance_children) { + auto prefab_to_delete = create("prefab"); + auto prefab_with_stuff = create("prefab2"); + auto inst = create("inst"); + + for (auto i = 0; i < 500; ++i) { + auto lua = create("lua", prefab_with_stuff); + commandInterface.set({lua, &LuaScript::uri_}, (test_path() / "scripts/types-scalar.lua").string()); + } + + for (auto i = 0; i < 10; ++i) { + auto delete_inst = create("inst", prefab_to_delete); + commandInterface.set({delete_inst, &PrefabInstance::template_}, prefab_with_stuff); + } + commandInterface.set({inst, &PrefabInstance::template_}, prefab_to_delete); + + + assertOperationTimeIsBelow(16000, [this, prefab_to_delete]() { + commandInterface.deleteObjects({prefab_to_delete}); + }); +} + +#endif \ No newline at end of file diff --git a/datamodel/libCore/tests/ProjectMigration_test.cpp b/datamodel/libCore/tests/ProjectMigration_test.cpp index 682ad3bd..2fa79ef5 100644 --- a/datamodel/libCore/tests/ProjectMigration_test.cpp +++ b/datamodel/libCore/tests/ProjectMigration_test.cpp @@ -9,24 +9,29 @@ */ #include "core/Queries.h" -#include "core/RamsesProjectMigration.h" +#include "core/DynamicEditorObject.h" +#include "core/ProjectMigration.h" +#include "core/ProjectMigrationToV23.h" +#include "core/ProxyObjectFactory.h" +#include "utils/u8path.h" #include "application/RaCoApplication.h" #include "application/RaCoProject.h" #include "core/Link.h" +#include "core/PathManager.h" -#include "ramses_adaptor/SceneBackend.h" #include "core/SerializationKeys.h" +#include "ramses_adaptor/SceneBackend.h" #include "user_types/Enumerations.h" +#include "user_types/Material.h" #include "user_types/MeshNode.h" #include "user_types/OrthographicCamera.h" #include "user_types/PerspectiveCamera.h" -#include "user_types/Material.h" -#include "user_types/Texture.h" #include "user_types/RenderBuffer.h" #include "user_types/RenderPass.h" +#include "user_types/Texture.h" #include "testing/TestEnvironmentCore.h" @@ -36,58 +41,66 @@ constexpr bool GENERATE_DIFF{false}; using namespace raco::core; +const char testName_old[] = "Test"; +const char testName_new[] = "Test"; + +static_assert(!std::is_same, raco::serialization::proxy::Proxy>::value); + struct MigrationTest : public TestEnvironmentCore { raco::ramses_base::HeadlessEngineBackend backend{}; raco::application::RaCoApplication application{backend}; + // Check if the property types coming out of the migration code agree with the types + // in the current version of the user types. + // Failure indicates missing migration code. + void checkPropertyTypes(const raco::serialization::ProjectDeserializationInfoIR& deserializedIR) { + auto userTypesPropMap = raco::serialization::makeUserTypePropertyMap(); + + for (const auto obj : deserializedIR.objects) { + const auto& typesMap = userTypesPropMap.at(obj->getTypeDescription().typeName); + + for (size_t i = 0; i < obj->size(); i++) { + auto propName = obj->name(i); + auto it = typesMap.find(propName); + ASSERT_TRUE(it != typesMap.end()); + EXPECT_EQ(it->second, obj->get(i)->typeName()); + } + } + } + std::unique_ptr loadAndCheckJson(QString filename, int* outFileVersion = nullptr) { QFile file{filename}; EXPECT_TRUE(file.open(QIODevice::ReadOnly | QIODevice::Text)); auto document{QJsonDocument::fromJson(file.readAll())}; file.close(); auto fileVersion{raco::serialization::deserializeFileVersion(document)}; - EXPECT_TRUE(fileVersion <= raco::core::RAMSES_PROJECT_FILE_VERSION); + EXPECT_TRUE(fileVersion <= raco::serialization::RAMSES_PROJECT_FILE_VERSION); if (outFileVersion) { *outFileVersion = fileVersion; } - auto projectInfo = raco::serialization::deserializeProjectVersionInfo(document); - std::unordered_map migrationObjWarnings; - auto migratedDoc{raco::core::migrateProject(document, migrationObjWarnings)}; - std::string migratedJson{migratedDoc.toJson().toStdString()}; + + // Perform deserialization to IR and migration by hand to check output of migration code: + auto deserializedIR{raco::serialization::deserializeProjectToIR(document, filename.toStdString())}; + raco::serialization::migrateProject(deserializedIR); + checkPropertyTypes(deserializedIR); std::vector pathStack; - auto racoproject = raco::application::RaCoProject::loadFromJson(migratedDoc, filename, &application, pathStack); - - std::unordered_map> currentVersions = { - {raco::serialization::keys::FILE_VERSION, {fileVersion}}, - {raco::serialization::keys::RAMSES_VERSION, {projectInfo.ramsesVersion.major, projectInfo.ramsesVersion.minor, projectInfo.ramsesVersion.patch}}, - {raco::serialization::keys::RAMSES_LOGIC_ENGINE_VERSION, {projectInfo.ramsesLogicEngineVersion.major, projectInfo.ramsesLogicEngineVersion.minor, projectInfo.ramsesLogicEngineVersion.patch}}, - {raco::serialization::keys::RAMSES_COMPOSER_VERSION, {projectInfo.raCoVersion.major, projectInfo.raCoVersion.minor, projectInfo.raCoVersion.patch}}}; - - auto serialized = racoproject->serializeProject(currentVersions); - auto serializedJson = serialized.toJson().toStdString(); - - if (GENERATE_DIFF) { - // This show a diff but is _very_ slow when run under the visual studio - EXPECT_EQ(migratedJson, serializedJson); - } else { - // alternatively use this to show failure but this doesn't show the diff - EXPECT_TRUE(migratedJson == serializedJson); - } + auto racoproject = raco::application::RaCoProject::loadFromFile(filename, &application, pathStack); + EXPECT_TRUE(racoproject != nullptr); return racoproject; } }; TEST_F(MigrationTest, migrate_from_V1) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V1.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V1.rca").string())); ASSERT_EQ(racoproject->project()->settings()->sceneId_.asInt(), 123); ASSERT_NE(racoproject->project()->settings()->objectID(), "b5535e97-4e60-4d72-99a9-b137b2ed52a5"); // this was the magic hardcoded ID originally used by the migration code. } TEST_F(MigrationTest, migrate_from_V9) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V9.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V9.rca").string())); auto p = std::dynamic_pointer_cast(raco::core::Queries::findByName(racoproject->project()->instances(), "PerspectiveCamera")); ASSERT_EQ(p->viewport_->offsetX_.asInt(), 1); @@ -103,7 +116,7 @@ TEST_F(MigrationTest, migrate_from_V9) { } TEST_F(MigrationTest, migrate_from_V10) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V10.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V10.rca").string())); auto meshnode = raco::core::Queries::findByName(racoproject->project()->instances(), "MeshNode")->as(); @@ -136,7 +149,7 @@ TEST_F(MigrationTest, migrate_from_V10) { } TEST_F(MigrationTest, migrate_from_V12) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V12.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V12.rca").string())); auto pcam = raco::core::Queries::findByName(racoproject->project()->instances(), "PerspectiveCamera")->as(); ASSERT_EQ(*pcam->viewport_->offsetX_, 1); @@ -144,20 +157,20 @@ TEST_F(MigrationTest, migrate_from_V12) { ASSERT_EQ(*pcam->viewport_->width_, 1441); ASSERT_EQ(*pcam->viewport_->height_, 721); - ASSERT_EQ(*pcam->frustum_->near_, 4.5); /// linked + ASSERT_EQ(*pcam->frustum_->near_, 4.5); /// linked ASSERT_EQ(*pcam->frustum_->far_, 1001.0); ASSERT_EQ(*pcam->frustum_->fov_, 36.0); ASSERT_EQ(*pcam->frustum_->aspect_, 3.0); auto ocam = raco::core::Queries::findByName(racoproject->project()->instances(), "OrthographicCamera")->as(); ASSERT_EQ(*ocam->viewport_->offsetX_, 2); - ASSERT_EQ(*ocam->viewport_->offsetY_, 3); // linked + ASSERT_EQ(*ocam->viewport_->offsetY_, 3); // linked ASSERT_EQ(*ocam->viewport_->width_, 1442); ASSERT_EQ(*ocam->viewport_->height_, 722); ASSERT_EQ(*ocam->frustum_->near_, 2.1); ASSERT_EQ(*ocam->frustum_->far_, 1002.0); - ASSERT_EQ(*ocam->frustum_->left_, 4.5); // linked + ASSERT_EQ(*ocam->frustum_->left_, 4.5); // linked ASSERT_EQ(*ocam->frustum_->right_, 12.0); ASSERT_EQ(*ocam->frustum_->bottom_, -8.0); ASSERT_EQ(*ocam->frustum_->top_, 12.0); @@ -202,23 +215,19 @@ TEST_F(MigrationTest, migrate_from_V12) { auto meshnode_no_mesh = raco::core::Queries::findByName(racoproject->project()->instances(), "meshnode_no_mesh")->as(); ASSERT_EQ(meshnode_no_mesh->materials_->size(), 0); - + auto meshnode_mesh_no_mat = raco::core::Queries::findByName(racoproject->project()->instances(), "meshnode_mesh_no_mat")->as(); ASSERT_EQ(meshnode_mesh_no_mat->materials_->size(), 1); auto lua = raco::core::Queries::findByName(racoproject->project()->instances(), "LuaScript")->as(); - checkLinks(*racoproject->project(), { - {{lua, {"luaOutputs", "int"}}, {pcam, {"viewport", "offsetY"}}}, - {{lua, {"luaOutputs", "float"}}, {pcam, {"frustum", "nearPlane"}}}, - {{lua, {"luaOutputs", "int"}}, {ocam, {"viewport", "offsetY"}}}, - {{lua, {"luaOutputs", "float"}}, {ocam, {"frustum", "leftPlane"}}}}); - + checkLinks(*racoproject->project(), {{{lua, {"luaOutputs", "int"}}, {pcam, {"viewport", "offsetY"}}}, + {{lua, {"luaOutputs", "float"}}, {pcam, {"frustum", "nearPlane"}}}, + {{lua, {"luaOutputs", "int"}}, {ocam, {"viewport", "offsetY"}}}, + {{lua, {"luaOutputs", "float"}}, {ocam, {"frustum", "leftPlane"}}}}); } TEST_F(MigrationTest, migrate_from_V13) { - std::vector pathStack; - - auto racoproject = raco::application::RaCoProject::loadFromFile(QString::fromStdString((cwd_path() / "migrationTestData" / "V13.rca").string()), &application, pathStack); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V13.rca").string())); auto textureNotFlipped = raco::core::Queries::findByName(racoproject->project()->instances(), "DuckTextureNotFlipped")->as(); ASSERT_FALSE(*textureNotFlipped->flipTexture_); @@ -228,7 +237,7 @@ TEST_F(MigrationTest, migrate_from_V13) { } TEST_F(MigrationTest, migrate_from_V14) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V14.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V14.rca").string())); auto camera = raco::core::Queries::findByName(racoproject->project()->instances(), "PerspectiveCamera")->as(); auto renderpass = raco::core::Queries::findByName(racoproject->project()->instances(), "MainRenderPass")->as(); @@ -260,7 +269,7 @@ TEST_F(MigrationTest, migrate_from_V14) { } TEST_F(MigrationTest, migrate_from_V14b) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V14b.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V14b.rca").string())); auto camera = raco::core::Queries::findByName(racoproject->project()->instances(), "OrthographicCamera")->as(); auto renderpass = raco::core::Queries::findByName(racoproject->project()->instances(), "MainRenderPass")->as(); @@ -292,7 +301,7 @@ TEST_F(MigrationTest, migrate_from_V14b) { } TEST_F(MigrationTest, migrate_from_V14c) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V14c.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V14c.rca").string())); auto renderpass = raco::core::Queries::findByName(racoproject->project()->instances(), "MainRenderPass")->as(); ASSERT_EQ(*renderpass->camera_, nullptr); @@ -323,7 +332,7 @@ TEST_F(MigrationTest, migrate_from_V14c) { } TEST_F(MigrationTest, migrate_from_V16) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V16.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V16.rca").string())); auto renderlayeropt = raco::core::Queries::findByName(racoproject->project()->instances(), "RenderLayerOptimized")->as(); ASSERT_EQ(renderlayeropt->sortOrder_.asInt(), static_cast(raco::user_types::ERenderLayerOrder::Manual)); @@ -334,7 +343,7 @@ TEST_F(MigrationTest, migrate_from_V16) { } TEST_F(MigrationTest, migrate_from_V18) { - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "V18.rca").string())); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V18.rca").string())); auto bgColor = racoproject->project()->settings()->backgroundColor_; ASSERT_EQ(bgColor->typeDescription.typeName, Vec4f::typeDescription.typeName); @@ -346,20 +355,78 @@ TEST_F(MigrationTest, migrate_from_V18) { ASSERT_EQ(bgColorVec4.w.asDouble(), 1.0); } +TEST_F(MigrationTest, migrate_from_V21) { + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V21.rca").string())); + + auto resourceFolders = racoproject->project()->settings()->defaultResourceDirectories_; + ASSERT_EQ(resourceFolders->typeDescription.typeName, ProjectSettings::DefaultResourceDirectories::typeDescription.typeName); + + ASSERT_EQ(resourceFolders->imageSubdirectory_.asString(), "images"); + ASSERT_EQ(resourceFolders->meshSubdirectory_.asString(), "meshes"); + ASSERT_EQ(resourceFolders->scriptSubdirectory_.asString(), "scripts"); + ASSERT_EQ(resourceFolders->shaderSubdirectory_.asString(), "shaders"); +} + +TEST_F(MigrationTest, migrate_from_V21_custom_paths) { + std::string imageSubdirectory = "imgs"; + std::string meshSubdirectory = "mshs"; + std::string scriptSubdirectory = "spts"; + std::string shaderSubdirectory = "shds"; + + auto preferencesFile = raco::core::PathManager::preferenceFilePath(); + if (preferencesFile.exists()) { + std::filesystem::remove(preferencesFile); + } + + { + // use scope to force saving QSettings when leaving the scope + QSettings settings(preferencesFile.string().c_str(), QSettings::IniFormat); + settings.setValue("imageSubdirectory", imageSubdirectory.c_str()); + settings.setValue("meshSubdirectory", meshSubdirectory.c_str()); + settings.setValue("scriptSubdirectory", scriptSubdirectory.c_str()); + settings.setValue("shaderSubdirectory", shaderSubdirectory.c_str()); + } + + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V21.rca").string())); + + auto resourceFolders = racoproject->project()->settings()->defaultResourceDirectories_; + ASSERT_EQ(resourceFolders->typeDescription.typeName, ProjectSettings::DefaultResourceDirectories::typeDescription.typeName); + + ASSERT_EQ(resourceFolders->imageSubdirectory_.asString(), imageSubdirectory); + ASSERT_EQ(resourceFolders->meshSubdirectory_.asString(), meshSubdirectory); + ASSERT_EQ(resourceFolders->scriptSubdirectory_.asString(), scriptSubdirectory); + ASSERT_EQ(resourceFolders->shaderSubdirectory_.asString(), shaderSubdirectory); + + if (preferencesFile.exists()) { + std::filesystem::remove(preferencesFile); + } +} + +TEST_F(MigrationTest, migrate_from_V23) { + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "V23.rca").string())); + + auto prefab = raco::core::Queries::findByName(racoproject->project()->instances(), "Prefab")->as(); + auto inst = raco::core::Queries::findByName(racoproject->project()->instances(), "PrefabInstance")->as(); + auto prefab_node = prefab->children_->asVector()[0]->as(); + auto inst_node = inst->children_->asVector()[0]->as(); + + EXPECT_EQ(inst_node->objectID(), EditorObject::XorObjectIDs(prefab_node->objectID(), inst->objectID())); +} + TEST_F(MigrationTest, migrate_from_current) { - // Check for changes in serialized JSON in newest version. + // Check for changes in serialized JSON in newest version. // Should detect changes in data model with missing migration code. // Also checks that all object types are present in file. - // - // The "version-current.rca" project needs to be updated when the data model has + // + // The "version-current.rca" project needs to be updated when the data model has // been changed in a way that changes the serialized JSON, e.g. // - static properties added // - annotations added to static properties // - added new object types int fileVersion; - auto racoproject = loadAndCheckJson(QString::fromStdString((cwd_path() / "migrationTestData" / "version-current.rca").string()), &fileVersion); - ASSERT_EQ(fileVersion, raco::core::RAMSES_PROJECT_FILE_VERSION); + auto racoproject = loadAndCheckJson(QString::fromStdString((test_path() / "migrationTestData" / "version-current.rca").string()), &fileVersion); + ASSERT_EQ(fileVersion, raco::serialization::RAMSES_PROJECT_FILE_VERSION); // check that all user types present in file auto& instances = racoproject->project()->instances(); @@ -370,4 +437,62 @@ TEST_F(MigrationTest, migrate_from_current) { }) != instances.end()); } } +TEST_F(MigrationTest, check_proxy_factory_has_all_objects_types) { + // Check that all types in the UserObjectFactory constructory via makeTypeMap call + // also have the corresponding proxy type added in the ProxyObjectFactory constructor. + // If this fails add the type in the ProxyObjectFactory constructor makeTypeMap call. + + auto& proxyFactory{raco::serialization::proxy::ProxyObjectFactory::getInstance()}; + auto& proxyTypeMap{proxyFactory.getTypes()}; + + for (auto& item : objectFactory()->getTypes()) { + auto name = item.first; + EXPECT_TRUE(proxyTypeMap.find(name) != proxyTypeMap.end()); + } +} + +TEST_F(MigrationTest, check_proxy_factory_has_all_dynamic_property_types) { + // Check that all dynamic properties contained in UserObjectFactory::PropertyTypeMapType + // have their corresponding properties added in ProxyObjectFactory::PropertyTypeMapType too. + // If this fails add the property in ProxyObjectFactory::PropertyTypeMapType. + + auto& proxyFactory{raco::serialization::proxy::ProxyObjectFactory::getInstance()}; + auto& userFactory{UserObjectFactory::getInstance()}; + auto& proxyProperties{proxyFactory.getProperties()}; + + for (auto& item : userFactory.getProperties()) { + auto name = item.first; + EXPECT_TRUE(proxyProperties.find(name) != proxyProperties.end()); + } +} +TEST_F(MigrationTest, check_proxy_factory_can_create_all_static_properties) { + // Check that the ProxyObjectFactory can create all statically known properties. + // If this fails add the failing property to the ProxyObjectFactory::PropertyTypeMapType. + + auto& proxyFactory{raco::serialization::proxy::ProxyObjectFactory::getInstance()}; + auto& userFactory{UserObjectFactory::getInstance()}; + + for (auto& item :userFactory.getTypes()) { + auto name = item.first; + auto object = objectFactory()->createObject(name); + ASSERT_TRUE(object != nullptr); + for (size_t index = 0; index < object->size(); index++) { + auto propTypeName = object->get(index)->typeName(); + auto proxyProperty = proxyFactory.createValue(propTypeName); + ASSERT_TRUE(proxyProperty != nullptr); + ASSERT_EQ(proxyProperty->typeName(), propTypeName); + } + } + for (auto& item : userFactory.getStructTypes()) { + auto name = item.first; + auto object = userFactory.createStruct(name); + ASSERT_TRUE(object != nullptr); + for (size_t index = 0; index < object->size(); index++) { + auto propTypeName = object->get(index)->typeName(); + auto proxyProperty = proxyFactory.createValue(propTypeName); + ASSERT_TRUE(proxyProperty != nullptr); + ASSERT_EQ(proxyProperty->typeName(), propTypeName); + } + } +} diff --git a/datamodel/libCore/tests/Queries_Tags_test.cpp b/datamodel/libCore/tests/Queries_Tags_test.cpp index 2f7f669d..058cac31 100644 --- a/datamodel/libCore/tests/Queries_Tags_test.cpp +++ b/datamodel/libCore/tests/Queries_Tags_test.cpp @@ -38,7 +38,7 @@ class QueriesTagTest : public TestEnvironmentCore { prefabInstance_ = commandInterface.createObject(PrefabInstance::typeDescription.typeName)->as(); meshnodeInPrefab_ = commandInterface.createObject(MeshNode::typeDescription.typeName)->as(); - commandInterface.set({mesh_, {"uri"}}, (cwd_path() / "meshes" / "Duck.glb").string()); + commandInterface.set({mesh_, {"uri"}}, (test_path() / "meshes" / "Duck.glb").string()); commandInterface.set({meshnode_, {"mesh"}}, mesh_); commandInterface.set({meshnodeInPrefab_, {"mesh"}}, mesh_); diff --git a/datamodel/libCore/tests/Serialization_test.cpp b/datamodel/libCore/tests/Serialization_test.cpp index 78939b45..349b6cee 100644 --- a/datamodel/libCore/tests/Serialization_test.cpp +++ b/datamodel/libCore/tests/Serialization_test.cpp @@ -18,8 +18,9 @@ #include "user_types/Material.h" #include "user_types/MeshNode.h" #include "user_types/Node.h" +#include "user_types/Texture.h" #include "utils/FileUtils.h" -#include "core/SerializationFunctions.h" +#include "utils/u8path.h" #include constexpr bool WRITE_RESULT{false}; @@ -27,9 +28,11 @@ constexpr bool WRITE_RESULT{false}; #define CMAKE_CURRENT_SOURCE_DIR "." #endif +using namespace raco::utils; + struct SerializationTest : public TestEnvironmentCore { void assertFileContentEqual(const std::string &filePath, const std::string &deserializedFileContent) { - auto expectedFileContent = raco::utils::file::read(filePath); + auto expectedFileContent = file::read(filePath); #if (defined(__linux__)) expectedFileContent.erase(std::remove(expectedFileContent.begin(), expectedFileContent.end(), '\r'), expectedFileContent.end()); @@ -42,10 +45,10 @@ struct SerializationTest : public TestEnvironmentCore { TEST_F(SerializationTest, serializeNode) { const auto sNode{std::make_shared("node", "node_id")}; sNode->scale_->z.staticQuery>().max_ = 100.0; - auto result = raco::serialization::serialize(sNode); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "Node.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sNode); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "Node.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "Node.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "Node.json").string(), result); } TEST_F(SerializationTest, serializeNodeRotated) { @@ -53,10 +56,10 @@ TEST_F(SerializationTest, serializeNodeRotated) { commandInterface.set({sNode, {"rotation", "x"}}, 90.0); commandInterface.set({sNode, {"rotation", "y"}}, -90.0); commandInterface.set({sNode, {"rotation", "z"}}, 180.0); - auto result = raco::serialization::serialize(sNode); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "NodeRotated.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sNode); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "NodeRotated.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "NodeRotated.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "NodeRotated.json").string(), result); } TEST_F(SerializationTest, serializeNodeWithAnnotations) { @@ -66,133 +69,133 @@ TEST_F(SerializationTest, serializeNodeWithAnnotations) { sNode->addAnnotation(anno); anno->projectID_ = "base_id"; - auto result = raco::serialization::serialize(sNode); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "NodeWithAnnotations.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sNode); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "NodeWithAnnotations.json").string(), result); - ASSERT_EQ(raco::utils::file::read((cwd_path() / "expectations" / "NodeWithAnnotations.json").string()), result); + ASSERT_EQ(file::read((test_path() / "expectations" / "NodeWithAnnotations.json").string()), result); } TEST_F(SerializationTest, serializeMeshNode) { const auto sMeshNode{std::make_shared("mesh_node", "mesh_node_id")}; - auto result = raco::serialization::serialize(sMeshNode); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshNode.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sMeshNode); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshNode.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "MeshNode.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "MeshNode.json").string(), result); } TEST_F(SerializationTest, serializeMeshNodeWithMesh) { const auto sMeshNode{commandInterface.createObject(raco::user_types::MeshNode::typeDescription.typeName, "mesh_node", "mesh_node_id")}; const auto sMesh{commandInterface.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; - auto uri{(cwd_path_relative() / "testData" / "duck.glb").string()}; + auto uri{(test_relative_path() / "testData" / "duck.glb").string()}; commandInterface.set({sMesh, {"uri"}}, uri); commandInterface.set({sMeshNode, {"mesh"}}, sMesh); - auto result = raco::serialization::serialize(sMeshNode); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshNodeWithMesh.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sMeshNode); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshNodeWithMesh.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "MeshNodeWithMesh.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "MeshNodeWithMesh.json").string(), result); } TEST_F(SerializationTest, serializeNodeWithChildMeshNode) { const auto sMeshNode{commandInterface.createObject(raco::user_types::MeshNode::typeDescription.typeName, "mesh_node", "mesh_node_id")}; const auto sNode{commandInterface.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; commandInterface.moveScenegraphChildren({sMeshNode}, sNode); - auto result = raco::serialization::serialize(sNode); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "NodeWithChildMeshNode.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sNode); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "NodeWithChildMeshNode.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "NodeWithChildMeshNode.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "NodeWithChildMeshNode.json").string(), result); } TEST_F(SerializationTest, serializeLuaScript) { const auto sLuaScript{commandInterface.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; - auto result = raco::serialization::serialize(sLuaScript); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScript.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sLuaScript); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScript.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScript.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScript.json").string(), result); } TEST_F(SerializationTest, serializeLuaScriptInFloat) { const auto sLuaScript{commandInterface.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; - auto uri { (cwd_path_relative() / "testData" / "in-float.lua").string() }; + auto uri { (test_relative_path() / "testData" / "in-float.lua").string() }; commandInterface.set(raco::core::ValueHandle{sLuaScript, {"uri"}}, uri); - auto result = raco::serialization::serialize(sLuaScript); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptInFloat.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sLuaScript); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptInFloat.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptInFloat.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptInFloat.json").string(), result); } TEST_F(SerializationTest, serializeLuaScriptInFloatArray) { const auto sLuaScript{commandInterface.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; - auto uri{(cwd_path_relative() / "testData" / "in-float-array.lua").string()}; + auto uri{(test_relative_path() / "testData" / "in-float-array.lua").string()}; commandInterface.set(raco::core::ValueHandle{sLuaScript, {"uri"}}, uri); - auto result = raco::serialization::serialize(sLuaScript); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptInFloatArray.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sLuaScript); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptInFloatArray.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptInFloatArray.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptInFloatArray.json").string(), result); } TEST_F(SerializationTest, serializeLuaScriptInStruct) { const auto sLuaScript{commandInterface.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; - auto uri{(cwd_path_relative() / "testData" / "in-struct.lua").string()}; + auto uri{(test_relative_path() / "testData" / "in-struct.lua").string()}; commandInterface.set(raco::core::ValueHandle{sLuaScript, {"uri"}}, uri); - auto result = raco::serialization::serialize(sLuaScript); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptInStruct.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sLuaScript); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptInStruct.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptInStruct.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptInStruct.json").string(), result); } TEST_F(SerializationTest, serializeLuaScriptInSpecificPropNames) { const auto sLuaScript{commandInterface.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; - auto uri{(cwd_path_relative() / "testData" / "in-specific-prop-names.lua").string()}; + auto uri{(test_relative_path() / "testData" / "in-specific-prop-names.lua").string()}; commandInterface.set(raco::core::ValueHandle{sLuaScript, {"uri"}}, uri); - auto result = raco::serialization::serialize(sLuaScript); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptSpecificPropNames.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sLuaScript); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptSpecificPropNames.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptSpecificPropNames.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptSpecificPropNames.json").string(), result); } TEST_F(SerializationTest, serializeMesh) { const auto sMesh{commandInterface.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; - auto uri{(cwd_path_relative() / "testData" / "duck.glb").string()}; + auto uri{(test_relative_path() / "testData" / "duck.glb").string()}; commandInterface.set({sMesh, {"uri"}}, uri); - auto result = raco::serialization::serialize(sMesh); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "Mesh.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sMesh); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "Mesh.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "Mesh.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "Mesh.json").string(), result); } TEST_F(SerializationTest, serializeMeshglTFSubmesh) { const auto sMesh{commandInterface.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; - auto uri{(cwd_path_relative() / "testData" / "ToyCar.gltf").string()}; + auto uri{(test_relative_path() / "testData" / "ToyCar.gltf").string()}; commandInterface.set({sMesh, {"uri"}}, uri); commandInterface.set({sMesh, {"bakeMeshes"}}, false); commandInterface.set({sMesh, {"meshIndex"}}, 2); - auto result = raco::serialization::serialize(sMesh); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshGLTFSubmesh.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sMesh); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshGLTFSubmesh.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "MeshGLTFSubmesh.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "MeshGLTFSubmesh.json").string(), result); } TEST_F(SerializationTest, serializeMeshglTFBakedSubmeshes) { const auto sMesh{commandInterface.createObject(raco::user_types::Mesh::typeDescription.typeName, "mesh", "mesh_id")}; - auto uri{(cwd_path_relative() / "testData" / "ToyCar.gltf").string()}; + auto uri{(test_relative_path() / "testData" / "ToyCar.gltf").string()}; commandInterface.set({sMesh, {"uri"}}, uri); commandInterface.set({sMesh, {"meshIndex"}}, 2); commandInterface.set({sMesh, {"bakeMeshes"}}, true); - auto result = raco::serialization::serialize(sMesh); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshGLTFBaked.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(sMesh); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "MeshGLTFBaked.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "MeshGLTFBaked.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "MeshGLTFBaked.json").string(), result); } TEST_F(SerializationTest, serializeLuaScriptWithRefToUserTypeWithAnnotation) { const auto editorObject{commandInterface.createObject(raco::user_types::LuaScript::typeDescription.typeName, "mesh", "mesh_id")}; raco::user_types::SLuaScript sLuaScript{std::dynamic_pointer_cast(editorObject)}; - sLuaScript->luaInputs_->addProperty("ref", new raco::data_storage::Property({}, {"BLUBB"})); + sLuaScript->luaInputs_->addProperty("ref", new raco::data_storage::Property({}, {raco::core::EnginePrimitive::TextureSampler2D})); - auto result = raco::serialization::serialize(editorObject); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithRefToUserTypeWithAnnotation.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(editorObject); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithRefToUserTypeWithAnnotation.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptWithRefToUserTypeWithAnnotation.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptWithRefToUserTypeWithAnnotation.json").string(), result); } TEST_F(SerializationTest, serializeLuaScriptWithURI) { @@ -200,10 +203,10 @@ TEST_F(SerializationTest, serializeLuaScriptWithURI) { raco::user_types::SLuaScript sLuaScript{std::dynamic_pointer_cast(editorObject)}; sLuaScript->luaInputs_->addProperty("uri", new raco::data_storage::Property("", {})); - auto result = raco::serialization::serialize(editorObject); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithURI.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(editorObject); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithURI.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptWithURI.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptWithURI.json").string(), result); } TEST_F(SerializationTest, serializeLuaScriptWithAnnotatedDouble) { @@ -211,10 +214,10 @@ TEST_F(SerializationTest, serializeLuaScriptWithAnnotatedDouble) { raco::user_types::SLuaScript sLuaScript{std::dynamic_pointer_cast(editorObject)}; sLuaScript->luaInputs_->addProperty("double", new raco::data_storage::Property>({}, {"Double"}, {-10.0, 10.0})); - auto result = raco::serialization::serialize(editorObject); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(editorObject); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); } TEST_F(SerializationTest, serializeNodeAndScript_withLink) { @@ -222,10 +225,10 @@ TEST_F(SerializationTest, serializeNodeAndScript_withLink) { raco::user_types::SLuaScript sLuaScript{std::dynamic_pointer_cast(editorObject)}; sLuaScript->luaInputs_->addProperty("double", new raco::data_storage::Property>({}, {"Double"}, {-10.0, 10.0})); - auto result = raco::serialization::serialize(editorObject); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); + auto result = raco::serialization::test_helpers::serializeObject(editorObject); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptWithAnnotatedDouble.json").string(), result); } TEST_F(SerializationTest, serializeObjects_luaScriptLinkedToNode) { @@ -233,11 +236,11 @@ TEST_F(SerializationTest, serializeObjects_luaScriptLinkedToNode) { std::map externalProjectsMap; std::map originFolders; - auto result = raco::serialization::serialize( + auto result = raco::serialization::serializeObjects( {std::get<0>(objs), std::get<1>(objs)}, {std::get<0>(objs)->objectID(), std::get<1>(objs)->objectID()}, {std::get<2>(objs)}, "", "", "", "", externalProjectsMap, originFolders); - if (WRITE_RESULT) raco::utils::file::write((std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptLinkedToNode.json").string(), result); + if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "LuaScriptLinkedToNode.json").string(), result); - assertFileContentEqual((cwd_path() / "expectations" / "LuaScriptLinkedToNode.json").string(), result); + assertFileContentEqual((test_path() / "expectations" / "LuaScriptLinkedToNode.json").string(), result); } diff --git a/datamodel/libCore/tests/Undo_test.cpp b/datamodel/libCore/tests/Undo_test.cpp index a79c59b2..481758cf 100644 --- a/datamodel/libCore/tests/Undo_test.cpp +++ b/datamodel/libCore/tests/Undo_test.cpp @@ -27,6 +27,8 @@ #include "user_types/Node.h" #include "user_types/Prefab.h" #include "user_types/PrefabInstance.h" +#include "user_types/RenderBuffer.h" +#include "user_types/RenderTarget.h" #include "gtest/gtest.h" @@ -39,6 +41,9 @@ using namespace raco::user_types; template class UndoTestT : public TestEnvironmentCoreT { public: + UndoTestT() : TestEnvironmentCoreT(&TestObjectFactory::getInstance()) { + } + template void checkSetValue(ValueHandle handle, const C& value) { C oldValue = handle.as(); @@ -406,7 +411,7 @@ TEST_F(UndoTest, copyAndPasteObjectSimple) { TEST_F(UndoTest, copyAndPasteShallowSetsReferences) { auto meshNode = create("meshnode"); auto mesh = create("mesh"); - commandInterface.set({mesh, {"uri"}}, (cwd_path() / "meshes" / "Duck.glb").string()); + commandInterface.set({mesh, {"uri"}}, (test_path() / "meshes" / "Duck.glb").string()); commandInterface.set({meshNode, {"mesh"}}, mesh); checkUndoRedo([this, meshNode]() { commandInterface.pasteObjects(context.copyObjects({meshNode})); }, @@ -441,16 +446,33 @@ TEST_F(UndoTest, deepCut) { }); } +TEST_F(UndoTest, no_change_records_for_deleted_objects) { + auto start = create_lua("start", "scripts/types-scalar.lua"); + + auto index = undoStack.getIndex(); + + auto end = create_lua("end", "scripts/types-scalar.lua"); + commandInterface.addLink(ValueHandle{start, {"luaOutputs", "ofloat"}}, ValueHandle{end, {"luaInputs", "float"}}); + + std::vector refLinks{{{{start, {"luaOutputs", "ofloat"}}, {end, {"luaInputs", "float"}}}}}; + checkLinks(refLinks); + + undoStack.setIndex(index); + + auto changedSet = recorder.getAllChangedObjects(); + EXPECT_TRUE(changedSet.find(end) == changedSet.end()); +} + TEST_F(UndoTest, link_broken_changed_output) { auto linkBase = create("script1"); - commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); auto linkRecipient = create("script2"); - commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); commandInterface.addLink(ValueHandle{linkBase, {"luaOutputs", "ofloat"}}, ValueHandle{linkRecipient, {"luaInputs", "in_float"}}); // link gets broken here - checkUndoRedo([this, linkBase]() { commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); }, + checkUndoRedo([this, linkBase]() { commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); }, [this]() { ASSERT_EQ(project.links().size(), 1); ASSERT_TRUE(project.links()[0]->isValid()); @@ -463,15 +485,15 @@ TEST_F(UndoTest, link_broken_changed_output) { TEST_F(UndoTest, link_broken_changed_input) { auto linkBase = create("script1"); - commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); auto linkRecipient = create("script2"); - commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); commandInterface.addLink(ValueHandle{linkBase, {"luaOutputs", "ofloat"}}, ValueHandle{linkRecipient, {"luaInputs", "in_float"}}); // link gets broken here - checkUndoRedo([this, linkRecipient]() { commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); }, + checkUndoRedo([this, linkRecipient]() { commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); }, [this]() { ASSERT_EQ(project.links().size(), 1); ASSERT_TRUE(project.links()[0]->isValid()); @@ -484,20 +506,20 @@ TEST_F(UndoTest, link_broken_changed_input) { TEST_F(UndoTest, link_broken_fix_link_with_correct_input) { auto linkBase = create("script1"); - commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); auto linkRecipient = create("script2"); - commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); commandInterface.addLink(ValueHandle{linkBase, {"luaOutputs", "ofloat"}}, ValueHandle{linkRecipient, {"luaInputs", "in_float"}}); // link gets broken here - commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); // Simulate user doing stuff after changing URI to prevent undo stack merging when changing URI again. create("Node"); - checkUndoRedo([this, linkRecipient]() { commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); }, + checkUndoRedo([this, linkRecipient]() { commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); }, [this]() { ASSERT_EQ(project.links().size(), 1); ASSERT_FALSE(project.links()[0]->isValid()); @@ -510,19 +532,19 @@ TEST_F(UndoTest, link_broken_fix_link_with_correct_input) { TEST_F(UndoTest, link_broken_fix_link_with_correct_output) { auto linkBase = create("script1"); - commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); auto linkRecipient = create("script2"); - commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); commandInterface.addLink(ValueHandle{linkBase, {"luaOutputs", "ofloat"}}, ValueHandle{linkRecipient, {"luaInputs", "in_float"}}); // link gets broken here - commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); // Simulate user doing stuff after changing URI to prevent undo stack merging when changing URI again. create("Node"); - checkUndoRedo([this, linkBase]() { commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); }, + checkUndoRedo([this, linkBase]() { commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); }, [this]() { ASSERT_EQ(project.links().size(), 1); ASSERT_FALSE(project.links()[0]->isValid()); @@ -535,15 +557,15 @@ TEST_F(UndoTest, link_broken_fix_link_with_correct_output) { TEST_F(UndoTest, link_input_changed_add_another_link) { auto linkBase = create("script1"); - commandInterface.set(ValueHandle{linkBase, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + commandInterface.set(ValueHandle{linkBase, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); auto linkRecipient = create("script2"); - commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/SimpleScript.lua").string()); + commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/SimpleScript.lua").string()); commandInterface.addLink(ValueHandle{linkBase, {"luaOutputs", "ofloat"}}, ValueHandle{linkRecipient, {"luaInputs", "in_float"}}); // link gets broken here - commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, cwd_path().append("scripts/types-scalar.lua").string()); + commandInterface.set(ValueHandle{linkRecipient, {"uri"}}, test_path().append("scripts/types-scalar.lua").string()); checkUndoRedo([this, linkBase, linkRecipient]() { commandInterface.addLink(ValueHandle{linkBase, {"luaOutputs", "ofloat"}}, ValueHandle{linkRecipient, {"luaInputs", "float"}}); }, [this]() { ASSERT_EQ(project.links().size(), 1); @@ -558,10 +580,10 @@ TEST_F(UndoTest, link_input_changed_add_another_link) { TEST_F(UndoTest, lua_module_added) { auto script = create("script"); - commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, cwd_path().append("scripts/moduleDependency.lua").string()); + commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, test_path().append("scripts/moduleDependency.lua").string()); auto module = create("module"); - commandInterface.set(ValueHandle{module, &raco::user_types::LuaScriptModule::uri_}, cwd_path().append("scripts/moduleDefinition.lua").string()); + commandInterface.set(ValueHandle{module, &raco::user_types::LuaScriptModule::uri_}, test_path().append("scripts/moduleDefinition.lua").string()); checkUndoRedo([this, script, module]() { commandInterface.set(ValueHandle{script, {"luaModules", "coalas"}}, module); @@ -580,13 +602,13 @@ TEST_F(UndoTest, lua_module_added) { TEST_F(UndoTest, lua_module_script_uri_changed) { auto script = create("script"); - commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, cwd_path().append("scripts/moduleDependency.lua").string()); + commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, test_path().append("scripts/moduleDependency.lua").string()); auto module = create("module"); - commandInterface.set(ValueHandle{module, &raco::user_types::LuaScriptModule::uri_}, cwd_path().append("scripts/moduleDefinition.lua").string()); + commandInterface.set(ValueHandle{module, &raco::user_types::LuaScriptModule::uri_}, test_path().append("scripts/moduleDefinition.lua").string()); commandInterface.set(ValueHandle{script, {"luaModules", "coalas"}}, module); - checkUndoRedo([this, script, module]() { commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, cwd_path().append("scripts/types-scalar.lua").string()); }, + checkUndoRedo([this, script, module]() { commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, test_path().append("scripts/types-scalar.lua").string()); }, [this, script]() { ASSERT_FALSE(commandInterface.errors().hasError({script})); auto coalasRef = ValueHandle{script, {"luaModules", "coalas"}}.asRef(); @@ -600,10 +622,10 @@ TEST_F(UndoTest, lua_module_script_uri_changed) { TEST_F(UndoTest, lua_module_script_module_made_invalid) { auto script = create("script"); - commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, cwd_path().append("scripts/moduleDependency.lua").string()); + commandInterface.set(ValueHandle{script, &raco::user_types::LuaScript::uri_}, test_path().append("scripts/moduleDependency.lua").string()); auto module = create("module"); - commandInterface.set(ValueHandle{module, &raco::user_types::LuaScriptModule::uri_}, cwd_path().append("scripts/moduleDefinition.lua").string()); + commandInterface.set(ValueHandle{module, &raco::user_types::LuaScriptModule::uri_}, test_path().append("scripts/moduleDefinition.lua").string()); commandInterface.set(ValueHandle{script, {"luaModules", "coalas"}}, module); checkUndoRedo([this, script, module]() { commandInterface.set(ValueHandle{module, &raco::user_types::LuaScriptModule::uri_}, std::string()); }, @@ -619,7 +641,7 @@ TEST_F(UndoTest, lua_module_script_module_made_invalid) { } TEST_F(UndoTest, link_quaternion_euler_change) { - raco::utils::file::write((cwd_path() / "lua_script_out1.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out1.lua").string(), R"( function interface() IN.vec = VEC4F OUT.vec = VEC4F @@ -629,7 +651,7 @@ function run() end )"); - raco::utils::file::write((cwd_path() / "lua_script_out2.lua").string(), R"( + raco::utils::file::write((test_path() / "lua_script_out2.lua").string(), R"( function interface() IN.vec = VEC3F OUT.vec = VEC3F @@ -639,13 +661,13 @@ function run() end )"); auto lua = create("script1"); - commandInterface.set(ValueHandle{lua, {"uri"}}, cwd_path().append("lua_script_out1.lua").string()); + commandInterface.set(ValueHandle{lua, {"uri"}}, test_path().append("lua_script_out1.lua").string()); auto node = create("node"); commandInterface.addLink(ValueHandle{lua, {"luaOutputs", "vec"}}, ValueHandle{node, {"rotation"}}); checkUndoRedo([this, lua, node]() { - commandInterface.set(ValueHandle{lua, {"uri"}}, cwd_path().append("lua_script_out2.lua").string()); + commandInterface.set(ValueHandle{lua, {"uri"}}, test_path().append("lua_script_out2.lua").string()); }, [this, node, lua]() { ASSERT_EQ(project.links().size(), 1); @@ -668,7 +690,7 @@ end TEST_F(UndoTest, mesh_asset_with_anims_import_multiple_undo_redo) { raco::core::MeshDescriptor desc; - desc.absPath = cwd_path().append("meshes/InterpolationTest/InterpolationTest.gltf").string(); + desc.absPath = test_path().append("meshes/InterpolationTest/InterpolationTest.gltf").string(); desc.bakeAllSubmeshes = false; auto scenegraph = commandInterface.meshCache()->getMeshScenegraph(desc); @@ -691,7 +713,7 @@ TEST_P(UndoTestWithIDParams, animation_with_animation_channel) { auto channelID = GetParam()[0]; auto animID = GetParam()[1]; - auto absPath = cwd_path().append("meshes/InterpolationTest/InterpolationTest.gltf").string(); + auto absPath = test_path().append("meshes/InterpolationTest/InterpolationTest.gltf").string(); checkJump([this, absPath, channelID, animID]() { auto channel = commandInterface.createObject(AnimationChannel::typeDescription.typeName, "channel", channelID); @@ -783,6 +805,59 @@ void main() { postCheck("alt_name"); } +TEST_F(UndoTest, meshnode_uniform_value_update) { + TextFile vertShader = makeFile("shader.vert", R"( +#version 300 es +precision mediump float; +in vec3 a_Position; +uniform mat4 u_MVPMatrix; +void main() { + float offset = float(gl_InstanceID) * 0.2; + gl_Position = u_MVPMatrix * vec4(a_Position.x + offset, a_Position.yz, 1.0); +} +)"); + + TextFile fragShader = makeFile("shader.frag", R"( +#version 300 es +precision mediump float; +out vec4 FragColor; +uniform float u_color; +void main() { +})"); + + std::string altFragShader = makeFile("altshader.frag", R"( +#version 300 es +precision mediump float; +out vec4 FragColor; +uniform float alt_name; +void main() { +})"); + + auto mesh = create_mesh("mesh", "meshes/Duck.glb"); + + auto material = commandInterface.createObject(Material::typeDescription.typeName, "material", "CCC"); + auto meshnode = commandInterface.createObject(MeshNode::typeDescription.typeName, "meshnode", "BBB")->as(); + commandInterface.set({meshnode, {"mesh"}}, mesh); + commandInterface.set({meshnode, {"materials", "material", "material"}}, material); + commandInterface.set(meshnode->getMaterialPrivateHandle(0), true); + commandInterface.set({material, &Material::uriVertex_}, vertShader); + commandInterface.set({material, &Material::uriFragment_}, fragShader); + + size_t preIndex = undoStack.getIndex(); + + commandInterface.set(raco::core::ValueHandle{meshnode, {"materials", "material", "uniforms", "u_color"}}, 5.0); + + size_t postIndex = undoStack.getIndex(); + + undoStack.setIndex(preIndex); + commandInterface.set({material, &Material::uriFragment_}, altFragShader); + commandInterface.set({material, &Material::uriFragment_}, fragShader); + + undoStack.setIndex(preIndex); + undoStack.setIndex(postIndex); + ASSERT_NO_THROW((raco::core::ValueHandle{meshnode, {"materials", "material", "uniforms", "u_color"}}.asDouble())); +} + #if (!defined(__linux__)) // awaitPreviewDirty does not work in Linux as expected. See RAOS-692 @@ -998,4 +1073,348 @@ end ASSERT_FALSE(static_cast(ValueHandle(sprop))); } -#endif \ No newline at end of file +#endif + +TEST_F(UndoTest, add_remove_property_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + context.addProperty(tableHandle, "ref", std::make_unique>(refTarget)); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + context.removeProperty(tableHandle, "ref"); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, add_remove_multiple_property_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; + + checkUndoRedoMultiStep<4>( + {[this, tableHandle, refTarget]() { + context.addProperty(tableHandle, "ref1", std::make_unique>(refTarget)); + this->undoStack.push("step 1"); + }, + [this, tableHandle, refTarget]() { + context.addProperty(tableHandle, "ref2", std::make_unique>(refTarget)); + this->undoStack.push("step 2"); + }, + [this, tableHandle]() { + context.removeProperty(tableHandle, "ref1"); + this->undoStack.push("step 3"); + }, + [this, tableHandle]() { + context.removeProperty(tableHandle, "ref2"); + this->undoStack.push("step 4"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, add_remove_property_table_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + Table innerTable; + innerTable.addProperty("ref", std::make_unique>(refTarget)); + context.addProperty(tableHandle, "innerTable", std::make_unique>(innerTable)); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + context.removeProperty(tableHandle, "innerTable"); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, add_remove_property_struct_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + Value* value{new Value()}; + (*value)->ref = refTarget; + context.addProperty(tableHandle, "innerStruct", std::unique_ptr>(value)); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + context.removeProperty(tableHandle, "innerStruct"); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, setTable_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + Table newTable; + newTable.addProperty("ref", std::make_unique>(refTarget)); + context.set(tableHandle, newTable); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + Table emptyTable; + context.set(tableHandle, emptyTable); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, setArray_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle arrayHandle{refSource, &ObjectWithTableProperty::array_}; + + checkUndoRedoMultiStep<2>( + {[this, arrayHandle, refTarget]() { + Table newTable; + newTable.addProperty("ref", std::make_unique>(refTarget)); + context.set(arrayHandle, newTable); + this->undoStack.push("step 1"); + }, + [this, arrayHandle]() { + Table emptyTable; + context.set(arrayHandle, emptyTable); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, setStruct_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + const ValueHandle structHandle{refSource, &ObjectWithStructProperty::s_}; + + checkUndoRedoMultiStep<2>( + {[this, structHandle, refTarget]() { + StructWithRef newStruct; + newStruct.ref = refTarget; + + context.set(structHandle, newStruct); + this->undoStack.push("step 1"); + }, + [this, structHandle]() { + StructWithRef emptyStruct; + context.set(structHandle, emptyStruct); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, setTable_nested_table_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + Table innerTable; + innerTable.addProperty("ref", std::make_unique>(refTarget)); + + Table outerTable; + outerTable.addProperty("inner", std::make_unique>(innerTable)); + + context.set(tableHandle, outerTable); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + Table emptyTable; + context.set(tableHandle, emptyTable); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, setArray_nested_table_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::array_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + Table innerTable; + innerTable.addProperty("ref", std::make_unique>(refTarget)); + + Table outerTable; + outerTable.addProperty("inner", std::make_unique>(innerTable)); + + context.set(tableHandle, outerTable); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + Table emptyTable; + context.set(tableHandle, emptyTable); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, setTable_nested_struct_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::t_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + Value* value{new Value()}; + (*value)->ref = refTarget; + + Table newTable; + newTable.addProperty("struct", std::unique_ptr>(value)); + + context.set(tableHandle, newTable); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + Table emptyTable; + context.set(tableHandle, emptyTable); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} + +TEST_F(UndoTest, setArray_nested_struct_with_ref) { + auto refTarget = create("foo"); + auto refSource = create("sourceObject"); + ValueHandle tableHandle{refSource, &ObjectWithTableProperty::array_}; + + checkUndoRedoMultiStep<2>( + {[this, tableHandle, refTarget]() { + Value* value{new Value()}; + (*value)->ref = refTarget; + + Table newTable; + newTable.addProperty("struct", std::unique_ptr>(value)); + + context.set(tableHandle, newTable); + this->undoStack.push("step 1"); + }, + [this, tableHandle]() { + Table emptyTable; + context.set(tableHandle, emptyTable); + this->undoStack.push("step 2"); + }}, + {[this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }, + [this, refTarget, refSource]() { + ASSERT_EQ(refTarget->referencesToThis().size(), 1); + EXPECT_EQ(refTarget->referencesToThis().begin()->lock(), refSource); + }, + [this, refTarget]() { + EXPECT_EQ(refTarget->referencesToThis().size(), 0); + }}); +} diff --git a/datamodel/libCore/tests/expectations/LuaScriptWithRefToUserTypeWithAnnotation.json b/datamodel/libCore/tests/expectations/LuaScriptWithRefToUserTypeWithAnnotation.json index d07842bf..58eeb1dd 100644 --- a/datamodel/libCore/tests/expectations/LuaScriptWithRefToUserTypeWithAnnotation.json +++ b/datamodel/libCore/tests/expectations/LuaScriptWithRefToUserTypeWithAnnotation.json @@ -9,12 +9,12 @@ "annotations": [ { "properties": { - "name": "BLUBB" + "engineType": 15 }, - "typeName": "DisplayNameAnnotation" + "typeName": "EngineTypeAnnotation" } ], - "typeName": "LuaScript::DisplayNameAnnotation", + "typeName": "Texture::EngineTypeAnnotation", "value": null } } diff --git a/datamodel/libCore/tests/migrationTestData/V21.rca b/datamodel/libCore/tests/migrationTestData/V21.rca new file mode 100644 index 00000000..69e61af7 --- /dev/null +++ b/datamodel/libCore/tests/migrationTestData/V21.rca @@ -0,0 +1,708 @@ +{ + "externalProjects": { + }, + "fileVersion": 21, + "instances": [ + { + "properties": { + "instanceCount": { + "annotations": [ + { + "properties": { + "max": 20, + "min": 1 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1 + }, + "mesh": null, + "objectID": "3e1cd74a-319d-47f9-939e-d9bd318c334e", + "objectName": "MeshNode", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "visible": true + }, + "typeName": "MeshNode" + }, + { + "properties": { + "children": { + "properties": [ + { + "typeName": "Ref", + "value": "3e1cd74a-319d-47f9-939e-d9bd318c334e" + } + ] + }, + "objectID": "eb49804f-0526-43a7-b873-7946ec5ba3d2", + "objectName": "Node", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "tags": { + "properties": [ + { + "typeName": "String", + "value": "render_main" + } + ] + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "visible": true + }, + "typeName": "Node" + }, + { + "properties": { + "frustum": { + "aspectRatio": { + "annotations": [ + { + "properties": { + "max": 4, + "min": 0.5 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 2 + }, + "farPlane": { + "annotations": [ + { + "properties": { + "max": 10000, + "min": 100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1000 + }, + "fieldOfView": { + "annotations": [ + { + "properties": { + "max": 120, + "min": 10 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 35 + }, + "nearPlane": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0.1 + } + }, + "objectID": "f570de7d-a1f9-4b6d-bee5-6c735fcb46b1", + "objectName": "PerspectiveCamera", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 10 + } + }, + "viewport": { + "height": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 720 + }, + "offsetX": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": -7680 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 0 + }, + "offsetY": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": -7680 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 0 + }, + "width": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1440 + } + }, + "visible": true + }, + "typeName": "PerspectiveCamera" + }, + { + "properties": { + "camera": "f570de7d-a1f9-4b6d-bee5-6c735fcb46b1", + "clearColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "enableClearColor": true, + "enableClearDepth": true, + "enableClearStencil": true, + "enabled": true, + "layer0": "b4beda7e-6d46-4d97-bfa0-eb2cbb91528d", + "layer1": null, + "layer2": null, + "layer3": null, + "layer4": null, + "layer5": null, + "layer6": null, + "layer7": null, + "objectID": "0057248c-bba4-4551-a8f3-8a50955836fe", + "objectName": "MainRenderPass", + "order": 1, + "target": null + }, + "typeName": "RenderPass" + }, + { + "properties": { + "invertMaterialFilter": true, + "objectID": "b4beda7e-6d46-4d97-bfa0-eb2cbb91528d", + "objectName": "MainRenderLayer", + "renderableTags": { + "order": [ + "render_main" + ], + "properties": { + "render_main": { + "typeName": "Int", + "value": 0 + } + } + }, + "sortOrder": 0 + }, + "typeName": "RenderLayer" + }, + { + "properties": { + "backgroundColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "enableTimerFlag": false, + "objectID": "74d0daad-8696-4586-be9f-d878b40d07b8", + "objectName": "V19", + "runTimer": false, + "sceneId": { + "annotations": [ + { + "properties": { + "max": 1024, + "min": 1 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 123 + }, + "viewport": { + "i1": { + "annotations": [ + { + "properties": { + "max": 4096, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1440 + }, + "i2": { + "annotations": [ + { + "properties": { + "max": 4096, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 720 + } + } + }, + "typeName": "ProjectSettings" + } + ], + "links": [ + ], + "logicEngineVersion": [ + 0, + 12, + 0 + ], + "racoVersion": [ + 0, + 10, + 0 + ], + "ramsesVersion": [ + 27, + 0, + 113 + ] +} diff --git a/datamodel/libCore/tests/migrationTestData/V23.rca b/datamodel/libCore/tests/migrationTestData/V23.rca new file mode 100644 index 00000000..63869c66 --- /dev/null +++ b/datamodel/libCore/tests/migrationTestData/V23.rca @@ -0,0 +1,1109 @@ +{ + "externalProjects": { + }, + "fileVersion": 23, + "instances": [ + { + "properties": { + "frustum": { + "aspectRatio": { + "annotations": [ + { + "properties": { + "max": 4, + "min": 0.5 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 2 + }, + "farPlane": { + "annotations": [ + { + "properties": { + "max": 10000, + "min": 100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1000 + }, + "fieldOfView": { + "annotations": [ + { + "properties": { + "max": 120, + "min": 10 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 35 + }, + "nearPlane": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0.1 + } + }, + "objectID": "73164748-df0d-409e-afa6-760589bda7ab", + "objectName": "PerspectiveCamera", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 10 + } + }, + "viewport": { + "height": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 720 + }, + "offsetX": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": -7680 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 0 + }, + "offsetY": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": -7680 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 0 + }, + "width": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1440 + } + }, + "visible": true + }, + "typeName": "PerspectiveCamera" + }, + { + "properties": { + "camera": "73164748-df0d-409e-afa6-760589bda7ab", + "clearColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "enableClearColor": true, + "enableClearDepth": true, + "enableClearStencil": true, + "enabled": true, + "layer0": "0a3c09f8-20d2-4926-93b8-366804f7efba", + "layer1": null, + "layer2": null, + "layer3": null, + "layer4": null, + "layer5": null, + "layer6": null, + "layer7": null, + "objectID": "1f20316b-cb22-421d-b20b-b06bc78b7d67", + "objectName": "MainRenderPass", + "order": 1, + "target": null + }, + "typeName": "RenderPass" + }, + { + "properties": { + "invertMaterialFilter": true, + "objectID": "0a3c09f8-20d2-4926-93b8-366804f7efba", + "objectName": "MainRenderLayer", + "renderableTags": { + "order": [ + "render_main" + ], + "properties": { + "render_main": { + "typeName": "Int", + "value": 0 + } + } + }, + "sortOrder": 0 + }, + "typeName": "RenderLayer" + }, + { + "properties": { + "backgroundColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "defaultResourceFolders": { + "imageSubdirectory": "images", + "meshSubdirectory": "meshes", + "scriptSubdirectory": "scripts", + "shaderSubdirectory": "shaders" + }, + "enableTimerFlag": false, + "objectID": "ef2fd911-39d5-449b-842c-2c8724ee907e", + "objectName": "V23", + "runTimer": false, + "sceneId": { + "annotations": [ + { + "properties": { + "max": 1024, + "min": 1 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 123 + }, + "viewport": { + "i1": { + "annotations": [ + { + "properties": { + "max": 4096, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1440 + }, + "i2": { + "annotations": [ + { + "properties": { + "max": 4096, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 720 + } + } + }, + "typeName": "ProjectSettings" + }, + { + "properties": { + "children": { + "properties": [ + { + "typeName": "Ref", + "value": "e1b00b50-1e98-4937-9045-3b225e34e9a9" + } + ] + }, + "objectID": "3fdaffd0-f451-4f0a-a8f7-8763d31f9ea5", + "objectName": "Prefab" + }, + "typeName": "Prefab" + }, + { + "properties": { + "objectID": "e1b00b50-1e98-4937-9045-3b225e34e9a9", + "objectName": "Node", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "visible": true + }, + "typeName": "Node" + }, + { + "properties": { + "children": { + "properties": [ + { + "typeName": "Ref", + "value": "9d5cf488-816b-4120-8d7c-fa8bd3d33d22" + } + ] + }, + "mapToInstance": { + "properties": [ + { + "order": [ + "prefabChild", + "instChild" + ], + "properties": { + "instChild": { + "typeName": "Ref", + "value": "9d5cf488-816b-4120-8d7c-fa8bd3d33d22" + }, + "prefabChild": { + "typeName": "Ref", + "value": "e1b00b50-1e98-4937-9045-3b225e34e9a9" + } + }, + "typeName": "Table" + } + ] + }, + "objectID": "a398d381-507f-441a-9595-3d2eae23214d", + "objectName": "PrefabInstance", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "template": "3fdaffd0-f451-4f0a-a8f7-8763d31f9ea5", + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "visible": true + }, + "typeName": "PrefabInstance" + }, + { + "properties": { + "objectID": "9d5cf488-816b-4120-8d7c-fa8bd3d33d22", + "objectName": "Node", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "visible": true + }, + "typeName": "Node" + } + ], + "links": [ + ], + "logicEngineVersion": [ + 0, + 13, + 0 + ], + "racoVersion": [ + 0, + 11, + 1 + ], + "ramsesVersion": [ + 27, + 0, + 114 + ], + "structPropMap": { + "BlendOptions": { + "blendColor": "Vec4f::DisplayNameAnnotation", + "blendFactorDestAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorDestColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "cullmode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthFunction": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthwrite": "Bool::DisplayNameAnnotation" + }, + "CameraViewport": { + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation" + }, + "DefaultResourceDirectories": { + "imageSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "meshSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "scriptSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "shaderSubdirectory": "String::DisplayNameAnnotation::URIAnnotation" + }, + "OrthographicFrustum": { + "bottomPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "leftPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "rightPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "topPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + }, + "PerspectiveFrustum": { + "aspectRatio": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "fieldOfView": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + } + }, + "userTypePropMap": { + "Animation": { + "animationChannels": "Table::DisplayNameAnnotation", + "animationOutputs": "Table::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "loop": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "play": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "rewindOnStop": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "AnimationChannel": { + "animationIndex": "Int::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "samplerIndex": "Int::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaModules": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "LuaScriptModule": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "mapToInstance": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec4f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "defaultResourceFolders": "DefaultResourceDirectories::DisplayNameAnnotation", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "RenderBuffer": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "format": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "RenderLayer": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "invertMaterialFilter": "Bool::DisplayNameAnnotation::EnumerationAnnotation", + "materialFilterTags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "renderableTags": "Table::RenderableTagContainerAnnotation::DisplayNameAnnotation", + "sortOrder": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation" + }, + "RenderPass": { + "camera": "BaseCamera::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "clearColor": "Vec4f::DisplayNameAnnotation", + "enableClearColor": "Bool::DisplayNameAnnotation", + "enableClearDepth": "Bool::DisplayNameAnnotation", + "enableClearStencil": "Bool::DisplayNameAnnotation", + "enabled": "Bool::DisplayNameAnnotation", + "layer0": "RenderLayer::DisplayNameAnnotation", + "layer1": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer2": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer3": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer4": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer5": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer6": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer7": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "order": "Int::DisplayNameAnnotation", + "target": "RenderTarget::DisplayNameAnnotation::EmptyReferenceAllowable" + }, + "RenderTarget": { + "buffer0": "RenderBuffer::DisplayNameAnnotation", + "buffer1": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer2": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer3": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer4": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer5": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer6": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer7": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "generateMipmaps": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + } +} diff --git a/datamodel/libCore/tests/migrationTestData/version-current.rca b/datamodel/libCore/tests/migrationTestData/version-current.rca index 2d8494c4..34b26ac6 100644 --- a/datamodel/libCore/tests/migrationTestData/version-current.rca +++ b/datamodel/libCore/tests/migrationTestData/version-current.rca @@ -1,7 +1,7 @@ { "externalProjects": { }, - "fileVersion": 21, + "fileVersion": 24, "instances": [ { "properties": { @@ -672,6 +672,12 @@ "value": 0 } }, + "defaultResourceFolders": { + "imageSubdirectory": "images", + "meshSubdirectory": "meshes", + "scriptSubdirectory": "scripts", + "shaderSubdirectory": "shaders" + }, "enableTimerFlag": false, "objectID": "71454add-eb56-4288-9057-825539914bed", "objectName": "test-offscreen-tex-uniform-migration-material", @@ -1880,17 +1886,273 @@ ], "logicEngineVersion": [ 0, - 12, + 13, 0 ], "racoVersion": [ 0, - 10, - 0 + 11, + 1 ], "ramsesVersion": [ 27, 0, - 113 - ] + 114 + ], + "structPropMap": { + "BlendOptions": { + "blendColor": "Vec4f::DisplayNameAnnotation", + "blendFactorDestAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorDestColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendFactorSrcColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationAlpha": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "blendOperationColor": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "cullmode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthFunction": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "depthwrite": "Bool::DisplayNameAnnotation" + }, + "CameraViewport": { + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetX": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "offsetY": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation::LinkEndAnnotation" + }, + "DefaultResourceDirectories": { + "imageSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "meshSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "scriptSubdirectory": "String::DisplayNameAnnotation::URIAnnotation", + "shaderSubdirectory": "String::DisplayNameAnnotation::URIAnnotation" + }, + "OrthographicFrustum": { + "bottomPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "leftPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "rightPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "topPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + }, + "PerspectiveFrustum": { + "aspectRatio": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "fieldOfView": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", + "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + } + }, + "userTypePropMap": { + "Animation": { + "animationChannels": "Table::DisplayNameAnnotation", + "animationOutputs": "Table::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "loop": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "play": "Bool::DisplayNameAnnotation::LinkEndAnnotation", + "rewindOnStop": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "AnimationChannel": { + "animationIndex": "Int::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "samplerIndex": "Int::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "CubeMap": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "LuaScript": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "luaInputs": "Table::DisplayNameAnnotation", + "luaModules": "Table::DisplayNameAnnotation", + "luaOutputs": "Table::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "LuaScriptModule": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Material": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "options": "BlendOptions::DisplayNameAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "uniforms": "Table::DisplayNameAnnotation", + "uriDefines": "String::URIAnnotation::DisplayNameAnnotation", + "uriFragment": "String::URIAnnotation::DisplayNameAnnotation", + "uriGeometry": "String::URIAnnotation::DisplayNameAnnotation", + "uriVertex": "String::URIAnnotation::DisplayNameAnnotation" + }, + "Mesh": { + "bakeMeshes": "Bool::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "materialNames": "Table::ArraySemanticAnnotation::HiddenProperty", + "meshIndex": "Int::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation" + }, + "MeshNode": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "instanceCount": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "materials": "Table::DisplayNameAnnotation", + "mesh": "Mesh::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Node": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "OrthographicCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "OrthographicFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "PerspectiveCamera": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "frustum": "PerspectiveFrustum::DisplayNameAnnotation::LinkEndAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "viewport": "CameraViewport::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "Prefab": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "PrefabInstance": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "rotation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "scale": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "template": "Prefab::DisplayNameAnnotation", + "translation": "Vec3f::DisplayNameAnnotation::LinkEndAnnotation", + "visible": "Bool::DisplayNameAnnotation::LinkEndAnnotation" + }, + "ProjectSettings": { + "backgroundColor": "Vec4f::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "defaultResourceFolders": "DefaultResourceDirectories::DisplayNameAnnotation", + "enableTimerFlag": "Bool::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "runTimer": "Bool::HiddenProperty", + "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "viewport": "Vec2i::DisplayNameAnnotation" + }, + "RenderBuffer": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "format": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "height": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "width": "Int::RangeAnnotationInt::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + }, + "RenderLayer": { + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "invertMaterialFilter": "Bool::DisplayNameAnnotation::EnumerationAnnotation", + "materialFilterTags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "renderableTags": "Table::RenderableTagContainerAnnotation::DisplayNameAnnotation", + "sortOrder": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "tags": "Table::ArraySemanticAnnotation::TagContainerAnnotation::DisplayNameAnnotation" + }, + "RenderPass": { + "camera": "BaseCamera::DisplayNameAnnotation", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "clearColor": "Vec4f::DisplayNameAnnotation", + "enableClearColor": "Bool::DisplayNameAnnotation", + "enableClearDepth": "Bool::DisplayNameAnnotation", + "enableClearStencil": "Bool::DisplayNameAnnotation", + "enabled": "Bool::DisplayNameAnnotation", + "layer0": "RenderLayer::DisplayNameAnnotation", + "layer1": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer2": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer3": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer4": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer5": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer6": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "layer7": "RenderLayer::DisplayNameAnnotation::EmptyReferenceAllowable", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "order": "Int::DisplayNameAnnotation", + "target": "RenderTarget::DisplayNameAnnotation::EmptyReferenceAllowable" + }, + "RenderTarget": { + "buffer0": "RenderBuffer::DisplayNameAnnotation", + "buffer1": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer2": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer3": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer4": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer5": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer6": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "buffer7": "RenderBuffer::DisplayNameAnnotation::EmptyReferenceAllowable", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation" + }, + "Texture": { + "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "flipTexture": "Bool::DisplayNameAnnotation", + "generateMipmaps": "Bool::DisplayNameAnnotation", + "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "objectID": "String::HiddenProperty", + "objectName": "String::DisplayNameAnnotation", + "uri": "String::URIAnnotation::DisplayNameAnnotation", + "wrapUMode": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "wrapVMode": "Int::DisplayNameAnnotation::EnumerationAnnotation" + } + } } diff --git a/datamodel/libDataStorage/include/data_storage/BasicTypes.h b/datamodel/libDataStorage/include/data_storage/BasicTypes.h index 9ba91b66..29004304 100644 --- a/datamodel/libDataStorage/include/data_storage/BasicTypes.h +++ b/datamodel/libDataStorage/include/data_storage/BasicTypes.h @@ -9,9 +9,9 @@ */ #pragma once +#include "BasicAnnotations.h" #include "ReflectionInterface.h" #include "Value.h" -#include "BasicAnnotations.h" #include @@ -19,34 +19,49 @@ namespace raco::data_storage { class Vec2f : public ClassWithReflectedMembers { public: - static inline const TypeDescriptor typeDescription = { "Vec2f", false }; + static inline const TypeDescriptor typeDescription = {"Vec2f", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; } bool serializationRequired() const override { return true; } - Vec2f(const Vec2f& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y) {} + Vec2f(const Vec2f& other, std::function* translateRef = nullptr) + : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y) {} - Vec2f(double defaultValue = 0.0, double step = 0.1, double min = 0.0, double max = 1.0) : - ClassWithReflectedMembers(getProperties()), - x{ defaultValue, DisplayNameAnnotation{ "X" }, RangeAnnotation(min, max) }, - y{ defaultValue, DisplayNameAnnotation{ "Y" }, RangeAnnotation(min, max) } - {} + Vec2f(double defaultValue = 0.0, double step = 0.1, double min = 0.0, double max = 1.0) + : ClassWithReflectedMembers(getProperties()), + x{defaultValue, DisplayNameAnnotation{"X"}, RangeAnnotation(min, max)}, + y{defaultValue, DisplayNameAnnotation{"Y"}, RangeAnnotation(min, max)} {} Vec2f& operator=(const Vec2f& other) { x = other.x; y = other.y; return *this; } - + + Vec2f& operator=(const std::array& value) { + x = value[0]; + y = value[1]; + return *this; + } + + friend bool operator==(const Vec2f& left, const std::array& right) { + return left.x.asDouble() == right[0] && + left.y.asDouble() == right[1]; + } + + friend bool operator!=(const Vec2f& left, const std::array& right) { + return !(left == right); + } + void copyAnnotationData(const Vec2f& other) { x.copyAnnotationData(other.x); y.copyAnnotationData(other.y); } std::vector> getProperties() { - return { {"x", &x}, {"y", &y} }; + return {{"x", &x}, {"y", &y}}; } public: @@ -54,24 +69,23 @@ class Vec2f : public ClassWithReflectedMembers { Property> y; }; - class Vec3f : public ClassWithReflectedMembers { public: - static inline const TypeDescriptor typeDescription = { "Vec3f", false }; + static inline const TypeDescriptor typeDescription = {"Vec3f", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; } bool serializationRequired() const override { return true; } - Vec3f(const Vec3f& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y), z(other.z) {} + Vec3f(const Vec3f& other, std::function* translateRef = nullptr) + : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y), z(other.z) {} - Vec3f(double defaultValue = 0.0, double step = 0.1, double min = 0.0, double max = 1.0) : - ClassWithReflectedMembers(getProperties()), - x{ defaultValue, DisplayNameAnnotation{ "X" }, RangeAnnotation(min, max) }, - y{ defaultValue, DisplayNameAnnotation{ "Y" }, RangeAnnotation(min, max) }, - z{ defaultValue, DisplayNameAnnotation{ "Z" }, RangeAnnotation(min, max) } - {} + Vec3f(double defaultValue = 0.0, double step = 0.1, double min = 0.0, double max = 1.0) + : ClassWithReflectedMembers(getProperties()), + x{defaultValue, DisplayNameAnnotation{"X"}, RangeAnnotation(min, max)}, + y{defaultValue, DisplayNameAnnotation{"Y"}, RangeAnnotation(min, max)}, + z{defaultValue, DisplayNameAnnotation{"Z"}, RangeAnnotation(min, max)} {} Vec3f& operator=(const Vec3f& other) { x = other.x; @@ -79,7 +93,24 @@ class Vec3f : public ClassWithReflectedMembers { z = other.z; return *this; } - + + Vec3f& operator=(const std::array& value) { + x = value[0]; + y = value[1]; + z = value[2]; + return *this; + } + + friend bool operator==(const Vec3f& left, const std::array& right) { + return left.x.asDouble() == right[0] && + left.y.asDouble() == right[1] && + left.z.asDouble() == right[2]; + } + + friend bool operator!=(const Vec3f& left, const std::array& right) { + return !(left == right); + } + void copyAnnotationData(const Vec3f& other) { x.copyAnnotationData(other.x); y.copyAnnotationData(other.y); @@ -87,7 +118,7 @@ class Vec3f : public ClassWithReflectedMembers { } std::vector> getProperties() { - return { {"x", &x}, {"y", &y}, {"z", &z} }; + return {{"x", &x}, {"y", &y}, {"z", &z}}; } public: @@ -96,25 +127,24 @@ class Vec3f : public ClassWithReflectedMembers { Property> z; }; - class Vec4f : public ClassWithReflectedMembers { public: - static inline const TypeDescriptor typeDescription = { "Vec4f", false }; + static inline const TypeDescriptor typeDescription = {"Vec4f", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; } bool serializationRequired() const override { return true; } - Vec4f(const Vec4f& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y), z(other.z), w(other.w) {} + Vec4f(const Vec4f& other, std::function* translateRef = nullptr) + : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y), z(other.z), w(other.w) {} - Vec4f(double defaultValue = 0.0, double step = 0.1, double min = 0.0, double max = 1.0) : - ClassWithReflectedMembers(getProperties()), - x{ defaultValue, DisplayNameAnnotation{ "X" }, RangeAnnotation(min, max) }, - y{ defaultValue, DisplayNameAnnotation{ "Y" }, RangeAnnotation(min, max) }, - z{ defaultValue, DisplayNameAnnotation{ "Z" }, RangeAnnotation(min, max) }, - w{ defaultValue, DisplayNameAnnotation{ "W" }, RangeAnnotation(min, max) } - {} + Vec4f(double defaultValue = 0.0, double step = 0.1, double min = 0.0, double max = 1.0) + : ClassWithReflectedMembers(getProperties()), + x{defaultValue, DisplayNameAnnotation{"X"}, RangeAnnotation(min, max)}, + y{defaultValue, DisplayNameAnnotation{"Y"}, RangeAnnotation(min, max)}, + z{defaultValue, DisplayNameAnnotation{"Z"}, RangeAnnotation(min, max)}, + w{defaultValue, DisplayNameAnnotation{"W"}, RangeAnnotation(min, max)} {} Vec4f& operator=(const Vec4f& other) { x = other.x; @@ -124,15 +154,34 @@ class Vec4f : public ClassWithReflectedMembers { return *this; } + Vec4f& operator=(const std::array& value) { + x = value[0]; + y = value[1]; + z = value[2]; + w = value[3]; + return *this; + } + + friend bool operator==(const Vec4f& left, const std::array& right) { + return left.x.asDouble() == right[0] && + left.y.asDouble() == right[1] && + left.z.asDouble() == right[2] && + left.w.asDouble() == right[3]; + } + + friend bool operator!=(const Vec4f& left, const std::array& right) { + return !(left == right); + } + void copyAnnotationData(const Vec4f& other) { x.copyAnnotationData(other.x); y.copyAnnotationData(other.y); z.copyAnnotationData(other.z); w.copyAnnotationData(other.w); } - + std::vector> getProperties() { - return { {"x", &x}, {"y", &y}, {"z", &z}, {"w", &w } }; + return {{"x", &x}, {"y", &y}, {"z", &z}, {"w", &w}}; } Property> x; @@ -141,27 +190,27 @@ class Vec4f : public ClassWithReflectedMembers { Property> w; }; - class Vec2i : public ClassWithReflectedMembers { public: - static inline const TypeDescriptor typeDescription = { "Vec2i", false }; + static inline const TypeDescriptor typeDescription = {"Vec2i", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; } bool serializationRequired() const override { return true; } - Vec2i(const Vec2i& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_) {} + Vec2i(const Vec2i& other, std::function* translateRef = nullptr) + : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_) {} - Vec2i(int defaultValue = 0, int step = 1, int min = 0, int max = 1) : - ClassWithReflectedMembers(getProperties()), - i1_{ defaultValue, DisplayNameAnnotation{ "i1" }, RangeAnnotation(min, max) }, - i2_{ defaultValue, DisplayNameAnnotation{ "i2" }, RangeAnnotation(min, max) } - {} + Vec2i(int defaultValue = 0, int step = 1, int min = 0, int max = 1) + : ClassWithReflectedMembers(getProperties()), + i1_{defaultValue, DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, + i2_{defaultValue, DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)} {} - Vec2i(std::array values, int min, int max) : ClassWithReflectedMembers(getProperties()), - i1_{values[0], DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, - i2_{values[1], DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)} {} + Vec2i(std::array values, int min, int max) + : ClassWithReflectedMembers(getProperties()), + i1_{values[0], DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, + i2_{values[1], DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)} {} Vec2i& operator=(const Vec2i& other) { i1_ = other.i1_; @@ -169,13 +218,28 @@ class Vec2i : public ClassWithReflectedMembers { return *this; } + Vec2i& operator=(const std::array& value) { + i1_ = value[0]; + i2_ = value[1]; + return *this; + } + + friend bool operator==(const Vec2i& left, const std::array& right) { + return left.i1_.asInt() == right[0] && + left.i2_.asInt() == right[1]; + } + + friend bool operator!=(const Vec2i& left, const std::array& right) { + return !(left == right); + } + void copyAnnotationData(const Vec2i& other) { i1_.copyAnnotationData(other.i1_); i2_.copyAnnotationData(other.i2_); } std::vector> getProperties() { - return { {"i1", &i1_}, {"i2", &i2_} }; + return {{"i1", &i1_}, {"i2", &i2_}}; } Property> i1_; @@ -184,21 +248,21 @@ class Vec2i : public ClassWithReflectedMembers { class Vec3i : public ClassWithReflectedMembers { public: - static inline const TypeDescriptor typeDescription = { "Vec3i", false }; + static inline const TypeDescriptor typeDescription = {"Vec3i", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; } bool serializationRequired() const override { return true; } - Vec3i(const Vec3i& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_), i3_(other.i3_) {} + Vec3i(const Vec3i& other, std::function* translateRef = nullptr) + : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_), i3_(other.i3_) {} - Vec3i(int defaultValue = 0, int step = 1, int min = 0, int max = 1) : - ClassWithReflectedMembers(getProperties()), - i1_{ defaultValue, DisplayNameAnnotation{ "i1" }, RangeAnnotation(min, max) }, - i2_{ defaultValue, DisplayNameAnnotation{ "i2" }, RangeAnnotation(min, max) }, - i3_{ defaultValue, DisplayNameAnnotation{ "i3" }, RangeAnnotation(min, max) } - {} + Vec3i(int defaultValue = 0, int step = 1, int min = 0, int max = 1) + : ClassWithReflectedMembers(getProperties()), + i1_{defaultValue, DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, + i2_{defaultValue, DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)}, + i3_{defaultValue, DisplayNameAnnotation{"i3"}, RangeAnnotation(min, max)} {} Vec3i& operator=(const Vec3i& other) { i1_ = other.i1_; @@ -207,6 +271,23 @@ class Vec3i : public ClassWithReflectedMembers { return *this; } + Vec3i& operator=(const std::array& value) { + i1_ = value[0]; + i2_ = value[1]; + i3_ = value[2]; + return *this; + } + + friend bool operator==(const Vec3i& left, const std::array& right) { + return left.i1_.asInt() == right[0] && + left.i2_.asInt() == right[1] && + left.i3_.asInt() == right[2]; + } + + friend bool operator!=(const Vec3i& left, const std::array& right) { + return !(left == right); + } + void copyAnnotationData(const Vec3i& other) { i1_.copyAnnotationData(other.i1_); i2_.copyAnnotationData(other.i2_); @@ -214,7 +295,7 @@ class Vec3i : public ClassWithReflectedMembers { } std::vector> getProperties() { - return { {"i1", &i1_}, {"i2", &i2_}, {"i3", &i3_} }; + return {{"i1", &i1_}, {"i2", &i2_}, {"i3", &i3_}}; } Property> i1_; Property> i2_; @@ -223,7 +304,7 @@ class Vec3i : public ClassWithReflectedMembers { class Vec4i : public ClassWithReflectedMembers { public: - static inline const TypeDescriptor typeDescription = { "Vec4i", false }; + static inline const TypeDescriptor typeDescription = {"Vec4i", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; } @@ -232,19 +313,19 @@ class Vec4i : public ClassWithReflectedMembers { } Vec4i(const Vec4i& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_), i3_(other.i3_), i4_(other.i4_) {} - Vec4i(int defaultValue = 0, int step = 1, int min = 0, int max = 1) : - ClassWithReflectedMembers(getProperties()), - i1_{ defaultValue, DisplayNameAnnotation{ "i1" }, RangeAnnotation(min, max) }, - i2_{ defaultValue, DisplayNameAnnotation{ "i2" }, RangeAnnotation(min, max) }, - i3_{ defaultValue, DisplayNameAnnotation{ "i3" }, RangeAnnotation(min, max) }, - i4_{ defaultValue, DisplayNameAnnotation{ "i4" }, RangeAnnotation(min, max) } - {} + Vec4i(int defaultValue = 0, int step = 1, int min = 0, int max = 1) + : ClassWithReflectedMembers(getProperties()), + i1_{defaultValue, DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, + i2_{defaultValue, DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)}, + i3_{defaultValue, DisplayNameAnnotation{"i3"}, RangeAnnotation(min, max)}, + i4_{defaultValue, DisplayNameAnnotation{"i4"}, RangeAnnotation(min, max)} {} - Vec4i(std::array values, int min, int max) : ClassWithReflectedMembers(getProperties()), - i1_{values[0], DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, - i2_{values[1], DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)}, - i3_{values[2], DisplayNameAnnotation{"i3"}, RangeAnnotation(min, max)}, - i4_{values[3], DisplayNameAnnotation{"i4"}, RangeAnnotation(min, max)} {} + Vec4i(std::array values, int min, int max) + : ClassWithReflectedMembers(getProperties()), + i1_{values[0], DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, + i2_{values[1], DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)}, + i3_{values[2], DisplayNameAnnotation{"i3"}, RangeAnnotation(min, max)}, + i4_{values[3], DisplayNameAnnotation{"i4"}, RangeAnnotation(min, max)} {} Vec4i& operator=(const Vec4i& other) { i1_ = other.i1_; @@ -253,7 +334,26 @@ class Vec4i : public ClassWithReflectedMembers { i4_ = other.i4_; return *this; } - + + Vec4i& operator=(const std::array& value) { + i1_ = value[0]; + i2_ = value[1]; + i3_ = value[2]; + i4_ = value[3]; + return *this; + } + + friend bool operator==(const Vec4i& left, const std::array& right) { + return left.i1_.asInt() == right[0] && + left.i2_.asInt() == right[1] && + left.i3_.asInt() == right[2] && + left.i4_.asInt() == right[3]; + } + + friend bool operator!=(const Vec4i& left, const std::array& right) { + return !(left == right); + } + void copyAnnotationData(const Vec4i& other) { i1_.copyAnnotationData(other.i1_); i2_.copyAnnotationData(other.i2_); @@ -262,7 +362,7 @@ class Vec4i : public ClassWithReflectedMembers { } std::vector> getProperties() { - return { {"i1", &i1_}, {"i2", &i2_}, {"i3", &i3_}, {"i4", &i4_} }; + return {{"i1", &i1_}, {"i2", &i2_}, {"i3", &i3_}, {"i4", &i4_}}; } Property> i1_; @@ -270,5 +370,4 @@ class Vec4i : public ClassWithReflectedMembers { Property> i3_; Property> i4_; }; - -} \ No newline at end of file +} // namespace raco::data_storage \ No newline at end of file diff --git a/datamodel/libDataStorage/include/data_storage/Table.h b/datamodel/libDataStorage/include/data_storage/Table.h index 7a2a17e8..4cfc3723 100644 --- a/datamodel/libDataStorage/include/data_storage/Table.h +++ b/datamodel/libDataStorage/include/data_storage/Table.h @@ -74,6 +74,8 @@ class Table : public ReflectionInterface { void replaceProperty(size_t index, ValueBase* property); void replaceProperty(const std::string& name, ValueBase* property); + void swapProperties(size_t index_1, size_t index_2); + // Remove all properties. void clear(); diff --git a/datamodel/libDataStorage/include/data_storage/Value.h b/datamodel/libDataStorage/include/data_storage/Value.h index cf7049f3..c01ea417 100644 --- a/datamodel/libDataStorage/include/data_storage/Value.h +++ b/datamodel/libDataStorage/include/data_storage/Value.h @@ -209,6 +209,7 @@ class ValueBase { // Get reference to generic base class for Struct type values. // Will not work for vector (Vec2f etc) types or Tables. + virtual ClassWithReflectedMembers& asStruct() = 0; virtual const ClassWithReflectedMembers& asStruct() const = 0; // Set Struct type values. @@ -460,6 +461,12 @@ class Value : public ValueBase { value_ = std::static_pointer_cast(v); } } + virtual ClassWithReflectedMembers& asStruct() override { + if constexpr (primitiveType() == PrimitiveType::Struct) { + return value_; + } + throw std::runtime_error("type mismatch"); + } virtual const ClassWithReflectedMembers& asStruct() const override { if constexpr (primitiveType() == PrimitiveType::Struct) { return value_; diff --git a/datamodel/libDataStorage/src/Table.cpp b/datamodel/libDataStorage/src/Table.cpp index 0c3c544f..ae93d678 100644 --- a/datamodel/libDataStorage/src/Table.cpp +++ b/datamodel/libDataStorage/src/Table.cpp @@ -170,6 +170,12 @@ void Table::replaceProperty(const std::string& name, ValueBase* property) { } } +void Table::swapProperties(size_t index_1, size_t index_2) { + if (index_1 < properties_.size() && index_2 < properties_.size() && index_1 != index_2) { + std::swap(properties_[index_1], properties_[index_2]); + } +} + void Table::clear() { properties_.clear(); } diff --git a/datamodel/libDataStorage/src/Value.cpp b/datamodel/libDataStorage/src/Value.cpp index dc3bb309..c9bd6f4f 100644 --- a/datamodel/libDataStorage/src/Value.cpp +++ b/datamodel/libDataStorage/src/Value.cpp @@ -13,13 +13,13 @@ #include "data_storage/BasicTypes.h" #include "data_storage/Table.h" -#include #include +#include namespace raco::data_storage { -static std::map& primitiveTypeName() { - static std::map primitiveTypeNameMap { +static std::map& primitiveTypeName() { + static std::map primitiveTypeNameMap{ {PrimitiveType::Bool, "Bool"}, {PrimitiveType::Int, "Int"}, {PrimitiveType::Double, "Double"}, @@ -33,8 +33,7 @@ static std::map& primitiveTypeName() { {PrimitiveType::Vec4f, "Vec4f"}, {PrimitiveType::Vec2i, "Vec2i"}, {PrimitiveType::Vec3i, "Vec3i"}, - {PrimitiveType::Vec4i, "Vec4i"} - }; + {PrimitiveType::Vec4i, "Vec4i"}}; return primitiveTypeNameMap; }; @@ -43,120 +42,118 @@ std::string getTypeName(PrimitiveType type) { } bool isPrimitiveTypeName(const std::string& type) { - for (const auto& [ key, value] : primitiveTypeName()) { + for (const auto& [key, value] : primitiveTypeName()) { if (type == value) return true; } return false; } PrimitiveType toPrimitiveType(const std::string& type) { - for (const auto& [ key, value] : primitiveTypeName()) { + for (const auto& [key, value] : primitiveTypeName()) { if (type == value) return key; } return PrimitiveType::Bool; } -std::unique_ptr ValueBase::create(PrimitiveType type) -{ +std::unique_ptr ValueBase::create(PrimitiveType type) { // The code below forces instantiation of the Value classes with the allowed templated arguments; // Other types as template arguments will lead to linker errors with missing functions. switch (type) { - case PrimitiveType::Bool: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::Int: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::Double: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::String: - return std::unique_ptr(new Value()); - break; - - case PrimitiveType::Ref: - return std::unique_ptr(new Value()); - break; - - case PrimitiveType::Table: - return std::unique_ptr(new Value
()); - break; - - case PrimitiveType::Vec2f: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::Vec3f: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::Vec4f: - return std::unique_ptr(new Value()); - break; - - case PrimitiveType::Vec2i: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::Vec3i: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::Vec4i: - return std::unique_ptr(new Value()); - break; - case PrimitiveType::Struct: - throw std::runtime_error("ValueBase::create can't create generic Value"); - break; + case PrimitiveType::Bool: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::Int: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::Double: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::String: + return std::unique_ptr(new Value()); + break; + + case PrimitiveType::Ref: + return std::unique_ptr(new Value()); + break; + + case PrimitiveType::Table: + return std::unique_ptr(new Value
()); + break; + + case PrimitiveType::Vec2f: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::Vec3f: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::Vec4f: + return std::unique_ptr(new Value()); + break; + + case PrimitiveType::Vec2i: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::Vec3i: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::Vec4i: + return std::unique_ptr(new Value()); + break; + case PrimitiveType::Struct: + throw std::runtime_error("ValueBase::create can't create generic Value"); + break; } return std::unique_ptr(); } - -template<> +template <> bool& ValueBase::as() { return asBool(); } -template<> +template <> int& ValueBase::as() { return asInt(); } -template<> +template <> double& ValueBase::as() { return asDouble(); } -template<> +template <> std::string& ValueBase::as() { return asString(); } -template<> +template <> Table& ValueBase::as
() { return asTable(); } -template<> +template <> Vec2f& ValueBase::as() { return asVec2f(); } -template<> +template <> Vec3f& ValueBase::as() { return asVec3f(); } -template<> +template <> Vec4f& ValueBase::as() { return asVec4f(); } -template<> +template <> Vec2i& ValueBase::as() { return asVec2i(); } -template<> +template <> Vec3i& ValueBase::as() { return asVec3i(); } -template<> +template <> Vec4i& ValueBase::as() { return asVec4i(); } @@ -231,7 +228,7 @@ ValueBase& ValueBase::operator=(double value) { return *this; } -ValueBase& ValueBase::operator=(std::string const & value) { +ValueBase& ValueBase::operator=(std::string const& value) { asString() = value; return *this; } @@ -241,7 +238,7 @@ ValueBase& ValueBase::operator=(SEditorObject value) { return *this; } -template +template ValueBase& ValueBase::set(T const& value) { as() = value; return *this; @@ -252,11 +249,48 @@ template ValueBase& ValueBase::set(int const& value); template ValueBase& ValueBase::set(double const& value); template ValueBase& ValueBase::set(std::string const& value); -template<> ValueBase& ValueBase::set>(std::vector const& value) { +template <> +ValueBase& ValueBase::set>(std::vector const& value) { asTable().set(value); return *this; } +template <> +ValueBase& ValueBase::set>(std::array const& value) { + as() = value; + return *this; +} + +template <> +ValueBase& ValueBase::set>(std::array const& value) { + as() = value; + return *this; +} + +template <> +ValueBase& ValueBase::set>(std::array const& value) { + as() = value; + return *this; +} + +template <> +ValueBase& ValueBase::set>(std::array const& value) { + as() = value; + return *this; +} + +template <> +ValueBase& ValueBase::set>(std::array const& value) { + as() = value; + return *this; +} + +template <> +ValueBase& ValueBase::set>(std::array const& value) { + as() = value; + return *this; +} + template <> ValueBase& ValueBase::set
(Table const& value) { asTable() = value; @@ -271,7 +305,7 @@ std::unique_ptr ValueBase::from(double value) { return std::make_unique>(value); } -template +template void primitiveCopyAnnotationData(T& dest, const T& src) { } @@ -281,7 +315,8 @@ template void primitiveCopyAnnotationData(double& dest, const double& sr template void primitiveCopyAnnotationData(std::string& dest, const std::string& src); template void primitiveCopyAnnotationData
(Table& dest, const Table& src); -template<> void primitiveCopyAnnotationData(Vec2f& dest, const Vec2f& src) { +template <> +void primitiveCopyAnnotationData(Vec2f& dest, const Vec2f& src) { dest.copyAnnotationData(src); } @@ -308,4 +343,4 @@ void primitiveCopyAnnotationData(Vec4i& dest, const Vec4i& src) { dest.copyAnnotationData(src); } -} \ No newline at end of file +} // namespace raco::data_storage \ No newline at end of file diff --git a/datamodel/libTesting/include/testing/MockUserTypes.h b/datamodel/libTesting/include/testing/MockUserTypes.h index c9bbd6ac..10941dc0 100644 --- a/datamodel/libTesting/include/testing/MockUserTypes.h +++ b/datamodel/libTesting/include/testing/MockUserTypes.h @@ -110,12 +110,10 @@ class Foo : public EditorObject { fillPropertyDescription(); } - Foo() : EditorObject() { + Foo(std::string name = {}, std::string id = {}) : EditorObject(name, id) { fillPropertyDescription(); } - - void fillPropertyDescription() { properties_.emplace_back("flag", &b_); properties_.emplace_back("i", &i_); @@ -151,23 +149,54 @@ class Foo : public EditorObject { class ObjectWithTableProperty : public EditorObject { public: - static inline const TypeDescriptor typeDescription = {"Foo", false}; + static inline const TypeDescriptor typeDescription = {"ObjectWithTableProperty", false}; TypeDescriptor const& getTypeDescription() const override { return typeDescription; } - ObjectWithTableProperty(ObjectWithTableProperty const& other) : EditorObject(other), t_(other.t_) { + ObjectWithTableProperty(ObjectWithTableProperty const& other) + : EditorObject(other), + t_(other.t_), + array_(other.array_) { fillPropertyDescription(); } - ObjectWithTableProperty() : EditorObject() { + ObjectWithTableProperty(std::string name = {}, std::string id = {}) : EditorObject(name, id) { fillPropertyDescription(); } void fillPropertyDescription() { properties_.emplace_back("t", &t_); + properties_.emplace_back("array", &array_); } Value
t_{}; + Property array_{}; +}; + +class ObjectWithStructProperty : public EditorObject { +public: + static inline const TypeDescriptor typeDescription = {"ObjectWithStructProperty", false}; + TypeDescriptor const& getTypeDescription() const override { + return typeDescription; + } + + ObjectWithStructProperty(ObjectWithStructProperty const& other) : EditorObject(other), s_(other.s_) { + fillPropertyDescription(); + } + + ObjectWithStructProperty(std::string name = {}, std::string id = {}) : EditorObject(name, id) { + fillPropertyDescription(); + } + + ObjectWithStructProperty() : EditorObject() { + fillPropertyDescription(); + } + + void fillPropertyDescription() { + properties_.emplace_back("s", &s_); + } + + Value s_{}; }; } \ No newline at end of file diff --git a/datamodel/libTesting/include/testing/RacoBaseTest.h b/datamodel/libTesting/include/testing/RacoBaseTest.h index 96e78d09..be8c32c5 100644 --- a/datamodel/libTesting/include/testing/RacoBaseTest.h +++ b/datamodel/libTesting/include/testing/RacoBaseTest.h @@ -11,8 +11,10 @@ #include "utils/stdfilesystem.h" #include "utils/FileUtils.h" +#include "utils/u8path.h" #include "core/Context.h" #include "core/CommandInterface.h" +#include "core/PathManager.h" #include "core/Undo.h" #include @@ -20,11 +22,6 @@ template class RacoBaseTest : public BaseClass { public: - virtual std::string cwd() const { - return cwd_path().u8string(); - } - - virtual std::string test_case_name() const { return ::testing::UnitTest::GetInstance()->current_test_info()->name(); } @@ -33,12 +30,12 @@ class RacoBaseTest : public BaseClass { return ::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name(); } - virtual std::filesystem::path cwd_path_relative() const { - return std::filesystem::path{test_suite_name()} / test_case_name(); + virtual raco::utils::u8path test_relative_path() const { + return raco::utils::u8path{test_suite_name()} / test_case_name(); } - virtual std::filesystem::path cwd_path() const { - return (std::filesystem::current_path() / cwd_path_relative()); + virtual raco::utils::u8path test_path() const { + return raco::utils::u8path::current() / test_relative_path(); } void checkUndoRedo(raco::core::CommandInterface& cmd, std::function operation, std::function preCheck, std::function postCheck) { @@ -76,11 +73,17 @@ class RacoBaseTest : public BaseClass { protected: virtual void SetUp() override { - if (std::filesystem::exists(cwd())) { + if (std::filesystem::exists(test_path())) { // Debugging case: if we debug and kill the test before complition the test directory will not be cleaned by TearDown - std::filesystem::remove_all(cwd()); + std::filesystem::remove_all(test_path()); } - std::filesystem::create_directories(cwd()); + std::filesystem::create_directories(test_path()); + + // Setup fake directory hierachy to simulate different directories configfiles can live in. + auto programPath = test_path() / "App" / "bin"; + auto appDataPath = test_path() / "AppData"; + raco::core::PathManager::init(programPath.string(), appDataPath.string()); + #ifdef RACO_LOCAL_TEST_RESOURCES_FILE_LIST std::string input{RACO_LOCAL_TEST_RESOURCES_FILE_LIST}; std::stringstream ss{input}; @@ -88,9 +91,9 @@ class RacoBaseTest : public BaseClass { // Convention Fileseparator: '!' while (std::getline(ss, fileName, '!')) { - const std::filesystem::path from{std::filesystem::path{RACO_LOCAL_TEST_RESOURCES_SOURCE_DIRECTORY}.append(fileName)}; - const std::filesystem::path to{cwd_path().append(fileName)}; - std::filesystem::path toWithoutFilename{to}; + const raco::utils::u8path from{raco::utils::u8path{RACO_LOCAL_TEST_RESOURCES_SOURCE_DIRECTORY}.append(fileName)}; + const raco::utils::u8path to{test_path().append(fileName)}; + raco::utils::u8path toWithoutFilename{to}; toWithoutFilename.remove_filename(); std::filesystem::create_directories(toWithoutFilename); std::filesystem::copy(from, to); @@ -99,12 +102,12 @@ class RacoBaseTest : public BaseClass { } virtual void TearDown() override { - std::filesystem::remove_all(cwd()); + std::filesystem::remove_all(test_path()); } struct TextFile { TextFile(RacoBaseTest& test, std::string fileName, std::string contents) { - path = test.cwd_path() / fileName; + path = test.test_path() / fileName; raco::utils::file::write(path.string(), contents); } @@ -112,7 +115,7 @@ class RacoBaseTest : public BaseClass { return path.string(); } - std::filesystem::path path; + raco::utils::u8path path; }; TextFile makeFile(std::string fileName, std::string contents) { diff --git a/datamodel/libTesting/include/testing/TestEnvironmentCore.h b/datamodel/libTesting/include/testing/TestEnvironmentCore.h index 97c6e34d..26a6ac64 100644 --- a/datamodel/libTesting/include/testing/TestEnvironmentCore.h +++ b/datamodel/libTesting/include/testing/TestEnvironmentCore.h @@ -32,11 +32,16 @@ #include "user_types/Material.h" #include "user_types/LuaScript.h" #include "user_types/RenderLayer.h" +#include "user_types/UserObjectFactory.h" +#include "user_types/PrefabInstance.h" + +#include "testing/MockUserTypes.h" #include #include "utils/stdfilesystem.h" +#include #include inline void clearQEventLoop() { @@ -55,6 +60,23 @@ class TestUndoStack : public raco::core::UndoStack { } }; +class TestObjectFactory : public raco::user_types::UserObjectFactory { +public: + TestObjectFactory() : UserObjectFactory() {} + + static TestObjectFactory& getInstance() { + static TestObjectFactory* instance = nullptr; + if (!instance) { + instance = new TestObjectFactory(); + instance->addType(); + instance->addType(); + instance->addType(); + } + return *instance; + } +}; + + template struct TestEnvironmentCoreT : public RacoBaseTest { using BaseContext = raco::core::BaseContext; @@ -66,7 +88,7 @@ struct TestEnvironmentCoreT : public RacoBaseTest { using DataChangeRecorderInterface = raco::core::DataChangeRecorderInterface; UserObjectFactoryInterface* objectFactory() { - return &UserObjectFactory::getInstance(); + return context.objectFactory(); }; template @@ -90,20 +112,20 @@ struct TestEnvironmentCoreT : public RacoBaseTest { return obj; } - raco::user_types::SMesh create_mesh(const std::string &name, const std::string &relpath) { + raco::user_types::SMesh create_mesh(const std::string& name, const std::string& relpath) { auto mesh = create(name); - commandInterface.set({mesh, {"uri"}}, (RacoBaseTest::cwd_path() / relpath).string()); + commandInterface.set({mesh, {"uri"}}, (RacoBaseTest::test_path() / relpath).string()); return mesh; } - raco::user_types::SMaterial create_material(const std::string &name, const std::string &relpathVertex, const std::string &relpathFragment) { + raco::user_types::SMaterial create_material(const std::string& name, const std::string& relpathVertex, const std::string& relpathFragment) { auto material = create(name); - commandInterface.set({material, {"uriVertex"}}, (RacoBaseTest::cwd_path() / relpathVertex).string()); - commandInterface.set({material, {"uriFragment"}}, (RacoBaseTest::cwd_path() / relpathFragment).string()); + commandInterface.set({material, {"uriVertex"}}, (RacoBaseTest::test_path() / relpathVertex).string()); + commandInterface.set({material, {"uriFragment"}}, (RacoBaseTest::test_path() / relpathFragment).string()); return material; } - raco::user_types::SMeshNode create_meshnode(const std::string &name, raco::user_types::SMesh mesh, raco::user_types::SMaterial material, raco::user_types::SEditorObject parent = nullptr) { + raco::user_types::SMeshNode create_meshnode(const std::string& name, raco::user_types::SMesh mesh, raco::user_types::SMaterial material, raco::user_types::SEditorObject parent = nullptr) { auto meshnode = create(name, parent); commandInterface.set({meshnode, {"mesh"}}, mesh); commandInterface.set({meshnode, {"materials", "material", "material"}}, material); @@ -112,7 +134,7 @@ struct TestEnvironmentCoreT : public RacoBaseTest { raco::user_types::SLuaScript create_lua(raco::core::CommandInterface& cmd, const std::string& name, const std::string& relpath, raco::core::SEditorObject parent = nullptr) { auto lua = create(cmd, name, parent); - cmd.set({lua, {"uri"}}, (RacoBaseTest::cwd_path() / relpath).string()); + cmd.set({lua, {"uri"}}, (RacoBaseTest::test_path() / relpath).string()); return lua; } @@ -132,9 +154,9 @@ struct TestEnvironmentCoreT : public RacoBaseTest { } raco::user_types::SRenderLayer create_layer(const std::string& name, - const std::vector& tags = {}, - const std::vector>& renderables = {}, - const std::vector& matFilterTags = {}, + const std::vector& tags = {}, + const std::vector>& renderables = {}, + const std::vector& matFilterTags = {}, bool invertFilter = true) { auto layer = create(name, nullptr, tags); for (int index = 0; index < renderables.size(); index++) { @@ -148,6 +170,11 @@ struct TestEnvironmentCoreT : public RacoBaseTest { return layer; } + raco::user_types::SPrefabInstance create_prefabInstance(const std::string& name, raco::user_types::SPrefab prefab, raco::user_types::SEditorObject parent = nullptr) { + auto inst = create(name, parent); + commandInterface.set({inst, {"template"}}, prefab); + return inst; + } void checkUndoRedo(std::function operation, std::function preCheck, std::function postCheck) { @@ -168,7 +195,7 @@ struct TestEnvironmentCoreT : public RacoBaseTest { void checkLinks(Project& project, const std::vector& refLinks) { EXPECT_EQ(refLinks.size(), project.links().size()); - for (const auto &refLink : refLinks) { + for (const auto& refLink : refLinks) { auto projectLink = raco::core::Queries::getLink(project, refLink.endProp()); EXPECT_TRUE(projectLink && projectLink->startProp() == refLink.startProp() && projectLink->isValid() == refLink.isValid()); } @@ -178,8 +205,24 @@ struct TestEnvironmentCoreT : public RacoBaseTest { checkLinks(project, refLinks); } - - TestEnvironmentCoreT() : fileChangeMonitor(std::make_unique()), meshCache() { + void assertOperationTimeIsBelow(testing::TimeInMillis maxMs, const std::function& operation) { + auto startTime = std::chrono::steady_clock::now(); + operation(); + auto endTime = std::chrono::steady_clock::now(); + auto opTimeMs = std::chrono::duration_cast(endTime - startTime).count(); + ASSERT_LE(opTimeMs, maxMs) << "Operation took longer than allowed boundary of " << maxMs << " ms\nActual operation duration: " << opTimeMs << " ms"; + } + + TestEnvironmentCoreT(UserObjectFactoryInterface* objectFactory = &UserObjectFactory::getInstance()) + : backend{}, + fileChangeMonitor{std::make_unique()}, + meshCache{}, + project{}, + recorder{}, + errors{&recorder}, + context{&project, backend.coreInterface(), objectFactory, &recorder, &errors}, + undoStack{&context}, + commandInterface{&context, &undoStack} { spdlog::drop_all(); raco::log_system::init(); clearQEventLoop(); @@ -193,16 +236,17 @@ struct TestEnvironmentCoreT : public RacoBaseTest { clearQEventLoop(); } - raco::ramses_base::HeadlessEngineBackend backend{}; + + raco::ramses_base::HeadlessEngineBackend backend; // FileChangeMonitor needs to be destroyed after the project (and with them all FileListeners have been cleaned up) std::unique_ptr fileChangeMonitor; raco::components::MeshCacheImpl meshCache; - Project project{}; - raco::core::DataChangeRecorder recorder{}; - Errors errors{&recorder}; - BaseContext context{&project, backend.coreInterface(), objectFactory(), &recorder, &errors}; - TestUndoStack undoStack{&context}; - raco::core::CommandInterface commandInterface{&context, &undoStack}; + Project project; + raco::core::DataChangeRecorder recorder; + Errors errors; + BaseContext context; + TestUndoStack undoStack; + raco::core::CommandInterface commandInterface; }; using TestEnvironmentCore = TestEnvironmentCoreT<>; diff --git a/datamodel/libTesting/include/testing/TestUtil.h b/datamodel/libTesting/include/testing/TestUtil.h index a190b73f..d847273d 100644 --- a/datamodel/libTesting/include/testing/TestUtil.h +++ b/datamodel/libTesting/include/testing/TestUtil.h @@ -34,7 +34,7 @@ inline std::shared_ptr select(const std::vector& vec) { } template -inline auto createLinkedScene(ContextOrCommandInterface& context, const std::filesystem::path& path) { +inline auto createLinkedScene(ContextOrCommandInterface& context, const raco::utils::u8path& path) { const auto luaScript{context.createObject(raco::user_types::LuaScript::typeDescription.typeName, "lua_script", "lua_script_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; raco::utils::file::write((path / "lua_script.lua").string(), R"( @@ -53,11 +53,11 @@ end } inline auto createLinkedScene(TestEnvironmentCore& env) { - return createLinkedScene(env.commandInterface, env.cwd_path_relative()); + return createLinkedScene(env.commandInterface, env.test_relative_path()); } template -inline auto createAnimatedScene(ContextOrCommandInterface& context, const std::filesystem::path& path) { +inline auto createAnimatedScene(ContextOrCommandInterface& context, const raco::utils::u8path& path) { const auto anim{context.createObject(raco::user_types::Animation::typeDescription.typeName, "anim", "anim_id")}; const auto animChannel{context.createObject(raco::user_types::AnimationChannel::typeDescription.typeName, "anim_ch", "anim_ch_id")}; const auto node{context.createObject(raco::user_types::Node::typeDescription.typeName, "node", "node_id")}; diff --git a/datamodel/libUserTypes/CMakeLists.txt b/datamodel/libUserTypes/CMakeLists.txt index 5645b5cf..60fef30d 100644 --- a/datamodel/libUserTypes/CMakeLists.txt +++ b/datamodel/libUserTypes/CMakeLists.txt @@ -59,6 +59,11 @@ PRIVATE add_library(raco::UserTypes ALIAS libUserTypes) +IF (MSVC) + # /bigobj is needed to compile UserObjectFactory.cpp + target_compile_options(libUserTypes PRIVATE "/bigobj") +endif() + if(PACKAGE_TESTS) add_subdirectory(tests) endif() \ No newline at end of file diff --git a/datamodel/libUserTypes/include/user_types/BaseCamera.h b/datamodel/libUserTypes/include/user_types/BaseCamera.h index 35585fb3..14465388 100644 --- a/datamodel/libUserTypes/include/user_types/BaseCamera.h +++ b/datamodel/libUserTypes/include/user_types/BaseCamera.h @@ -64,7 +64,6 @@ class CameraViewport : public ClassWithReflectedMembers { }; class BaseCamera : public Node { - Property step_; public: static inline const TypeDescriptor typeDescription = {"BaseCamera", false}; diff --git a/datamodel/libUserTypes/include/user_types/MeshNode.h b/datamodel/libUserTypes/include/user_types/MeshNode.h index b282f1f6..b169c443 100644 --- a/datamodel/libUserTypes/include/user_types/MeshNode.h +++ b/datamodel/libUserTypes/include/user_types/MeshNode.h @@ -71,6 +71,7 @@ class MeshNode : public Node { void createMaterialSlot(std::string const& name); void updateMaterialSlots(BaseContext& context, std::vector const& materialNames); + void updatePrivateMaterialOptions(BaseContext& context, const raco::user_types::BlendOptions* src, ValueHandle& destOptions); void updateUniformContainer(BaseContext& context, const std::string& materialName, const Table* src, ValueHandle& destUniforms); void checkMeshMaterialAttributMatch(BaseContext& context); diff --git a/datamodel/libUserTypes/include/user_types/PrefabInstance.h b/datamodel/libUserTypes/include/user_types/PrefabInstance.h index 3cf49bd2..6fa357ac 100644 --- a/datamodel/libUserTypes/include/user_types/PrefabInstance.h +++ b/datamodel/libUserTypes/include/user_types/PrefabInstance.h @@ -38,20 +38,12 @@ class PrefabInstance : public Node { void fillPropertyDescription() { properties_.emplace_back("template", &template_); - properties_.emplace_back("mapToInstance", &mapToInstance_); } Property template_{nullptr, DisplayNameAnnotation("Prefab Template")}; - static SEditorObject mapToInstance(SEditorObject obj, SPrefab prefab, SPrefabInstance instance); - static SEditorObject mapFromInstance(SEditorObject obj, SPrefabInstance instance); - - void removePrefabInstanceChild(BaseContext& context, const SEditorObject& prefabChild); - - void addChildMapping(BaseContext& context, const SEditorObject& prefabChild, const SEditorObject& instanceChild); - - // Maps from Prefab children objects -> PrefabInstance children - Property mapToInstance_; + static std::string mapObjectIDToInstance(SEditorObject obj, SPrefab prefab, SPrefabInstance instance); + static std::string mapObjectIDFromInstance(SEditorObject obj, SPrefab prefab, SPrefabInstance instance); }; } \ No newline at end of file diff --git a/datamodel/libUserTypes/include/user_types/UserObjectFactory.h b/datamodel/libUserTypes/include/user_types/UserObjectFactory.h index bbe75e36..8757d66b 100644 --- a/datamodel/libUserTypes/include/user_types/UserObjectFactory.h +++ b/datamodel/libUserTypes/include/user_types/UserObjectFactory.h @@ -101,16 +101,29 @@ class UserObjectFactory : public raco::core::UserObjectFactoryInterface { Property, Property>; + using StructCreationFunction = std::function()>; + + struct StructDescriptor { + ReflectionInterface::TypeDescriptor description; + StructCreationFunction createFunc; + }; + + static UserObjectFactory& getInstance(); - virtual SEditorObject createObject(const std::string& type, const std::string& name = std::string(), const std::string& id = std::string()) override; - virtual data_storage::ValueBase* createValue(const std::string& type) override; + virtual SEditorObject createObject(const std::string& type, const std::string& name = std::string(), const std::string& id = std::string()) const override; + virtual data_storage::ValueBase* createValue(const std::string& type) const override; const std::map& getTypes() const override; bool isUserCreatable(const std::string& type) const override; + + const std::map& getProperties() const; + + const std::map& getStructTypes() const; - std::shared_ptr createAnnotation(const std::string& type) override; + std::shared_ptr createAnnotation(const std::string& type) const override; + std::shared_ptr createStruct(const std::string& type) const; template static ValueBase* staticCreateProperty(T defaultT, Args... params) { @@ -118,8 +131,15 @@ class UserObjectFactory : public raco::core::UserObjectFactoryInterface { return new Property(defaultT, params...); } -private: +protected: + template + void addType() { + types_[T::typeDescription.typeName] = {T::typeDescription, createObjectInternal, createValueInternal}; + } + UserObjectFactory(); + +private: using AnnotationCreationFunction = std::function()>; @@ -129,23 +149,33 @@ class UserObjectFactory : public raco::core::UserObjectFactoryInterface { }; template - static SEditorObject createObjectInternal(const std::string& name, const std::string& id); + static SEditorObject createObjectInternal(const std::string& name, const std::string& id) { + return std::make_shared(name, id); + } + template static std::shared_ptr createAnnotationInternal(); template - static data_storage::ValueBase* createValueInternal(); + static std::shared_ptr createStructInternal(); + template + static data_storage::ValueBase* createValueInternal() { + return new Value>(); + } template std::map makeTypeMap(); template - std::map makePropertyMap(); + std::map makePropertyMapTuple(std::tuple* dummy); template std::map makeAnnotationMap(); + template + std::map makeStructTypeMap(); std::map types_; // Annotations that can be dynmically added to / removed from ClassWithReflectedMembers std::map annotations_; std::map properties_; + std::map structTypes_; }; } \ No newline at end of file diff --git a/datamodel/libUserTypes/src/LuaScript.cpp b/datamodel/libUserTypes/src/LuaScript.cpp index 42178d73..6bfb93b3 100644 --- a/datamodel/libUserTypes/src/LuaScript.cpp +++ b/datamodel/libUserTypes/src/LuaScript.cpp @@ -47,12 +47,24 @@ void LuaScript::updateFromExternalFile(BaseContext& context) { PropertyInterfaceList inputs{}; PropertyInterfaceList outputs{}; std::string error{}; - context.errors().removeError({shared_from_this()}); + context.errors().removeAll({shared_from_this()}); syncLuaModules(context, luaScript, error); - if (error.empty() && !luaScript.empty()) { - context.engineInterface().parseLuaScript(luaScript, luaModules_.asTable(), inputs, outputs, error); + const auto& moduleTable = luaModules_.asTable(); + if (error.empty()) { + context.engineInterface().parseLuaScript(luaScript, moduleTable, inputs, outputs, error); + } + + if (!error.empty()) { + for (auto i = 0; i < moduleTable.size(); ++i) { + const auto& moduleName = moduleTable.name(i); + auto module = ValueHandle{shared_from_this(), {"luaModules", moduleName}}; + auto moduleRef = module.asRef(); + if (context.errors().hasError(moduleRef) || (moduleRef && moduleRef->get("uri")->asString().empty())) { + context.errors().addError(raco::core::ErrorCategory::GENERAL, raco::core::ErrorLevel::ERROR, module, fmt::format("Invalid LuaScriptModule '{}' assigned.", moduleRef->objectName())); + } + } } if (validateURI(context, {shared_from_this(), &LuaScript::uri_})) { diff --git a/datamodel/libUserTypes/src/LuaScriptModule.cpp b/datamodel/libUserTypes/src/LuaScriptModule.cpp index bff39781..4b704142 100644 --- a/datamodel/libUserTypes/src/LuaScriptModule.cpp +++ b/datamodel/libUserTypes/src/LuaScriptModule.cpp @@ -22,9 +22,8 @@ void LuaScriptModule::updateFromExternalFile(BaseContext& context) { std::string luaScript = utils::file::read(PathQueries::resolveUriPropertyToAbsolutePath(*context.project(), {shared_from_this(), &LuaScriptModule::uri_})); std::string error; - if (!luaScript.empty()) { - context.engineInterface().parseLuaScriptModule(luaScript, error); - } + + context.engineInterface().parseLuaScriptModule(luaScript, error); context.errors().removeError({shared_from_this()}); if (validateURI(context, {shared_from_this(), &LuaScriptModule::uri_})) { diff --git a/datamodel/libUserTypes/src/Material.cpp b/datamodel/libUserTypes/src/Material.cpp index e4ba4555..c84584e0 100644 --- a/datamodel/libUserTypes/src/Material.cpp +++ b/datamodel/libUserTypes/src/Material.cpp @@ -26,10 +26,12 @@ const PropertyInterfaceList& Material::attributes() const { void Material::updateFromExternalFile(BaseContext& context) { context.errors().removeError(ValueHandle{shared_from_this()}); - if (uriGeometry_.asString().empty() || validateURI(context, ValueHandle{shared_from_this(), &Material::uriGeometry_})) { + bool isUriGeometryValid = false; + if (uriGeometry_.asString().empty() || (isUriGeometryValid = validateURI(context, ValueHandle{shared_from_this(), &Material::uriGeometry_}))) { context.errors().removeError(ValueHandle{shared_from_this(), &Material::uriGeometry_}); } - if (uriDefines_.asString().empty() || validateURI(context, ValueHandle{shared_from_this(), &Material::uriDefines_})) { + bool isUriDefinesValid = false; + if (uriDefines_.asString().empty() || (isUriDefinesValid = validateURI(context, ValueHandle{shared_from_this(), &Material::uriDefines_}))) { context.errors().removeError(ValueHandle{shared_from_this(), &Material::uriDefines_}); } @@ -38,16 +40,33 @@ void Material::updateFromExternalFile(BaseContext& context) { if (validateURIs(context, ValueHandle{shared_from_this(), &Material::uriFragment_}, ValueHandle{ shared_from_this(), &Material::uriVertex_})) { std::string vertexShader{raco::utils::file::read(PathQueries::resolveUriPropertyToAbsolutePath(*context.project(), {shared_from_this(), &Material::uriVertex_}))}; - std::string geometryShader{raco::utils::file::read(PathQueries::resolveUriPropertyToAbsolutePath(*context.project(), {shared_from_this(), &Material::uriGeometry_}))}; std::string fragmentShader{raco::utils::file::read(PathQueries::resolveUriPropertyToAbsolutePath(*context.project(), {shared_from_this(), &Material::uriFragment_}))}; - std::string shaderDefines{raco::utils::file::read(PathQueries::resolveUriPropertyToAbsolutePath(*context.project(), {shared_from_this(), &Material::uriDefines_}))}; - if (!vertexShader.empty() && !fragmentShader.empty()) { - std::string error{}; - isShaderValid_ = context.engineInterface().parseShader(vertexShader, geometryShader, fragmentShader, shaderDefines, uniforms, attributes_, error); - if (error.size() > 0) { - context.errors().addError(ErrorCategory::PARSE_ERROR, ErrorLevel::ERROR, ValueHandle{shared_from_this()}, error); + + std::string geometryShader = ""; + if (isUriGeometryValid) { + geometryShader = {raco::utils::file::read(PathQueries::resolveUriPropertyToAbsolutePath(*context.project(), {shared_from_this(), &Material::uriGeometry_}))}; + // ramses treats the empty string as no geometry shader should be loaded and only shows errors if the stringcontains at least a single character. + // We want to display errors. We want to display errors when the file load was successful but the file is empty. + if (geometryShader == "") { + geometryShader = " "; } } + + std::string shaderDefines = ""; + if (isUriDefinesValid) { + shaderDefines = {raco::utils::file::read(PathQueries::resolveUriPropertyToAbsolutePath(*context.project(), {shared_from_this(), &Material::uriDefines_}))}; + // ramses treats the empty string as no shader defines should be loaded and only shows errors if the string contains at least a single charcater. + // We want to display errors when the file load was successful but the file is empty. + if (shaderDefines == "") { + shaderDefines = " "; + } + } + + std::string error{}; + isShaderValid_ = context.engineInterface().parseShader(vertexShader, geometryShader, fragmentShader, shaderDefines, uniforms, attributes_, error); + if (error.size() > 0) { + context.errors().addError(ErrorCategory::PARSE_ERROR, ErrorLevel::ERROR, ValueHandle{shared_from_this()}, error); + } } if (!isShaderValid_) { attributes_.clear(); diff --git a/datamodel/libUserTypes/src/MeshNode.cpp b/datamodel/libUserTypes/src/MeshNode.cpp index e2eb41a1..b3492e12 100644 --- a/datamodel/libUserTypes/src/MeshNode.cpp +++ b/datamodel/libUserTypes/src/MeshNode.cpp @@ -196,6 +196,11 @@ void MeshNode::updateUniformContainer(BaseContext& context, const std::string& m *newValue = *src->get(i); } } + } else { + auto destIndex = dest.index(name); + if (destIndex != i) { + context.swapProperties(destUniforms, i, destIndex); + } } } } else { @@ -342,6 +347,9 @@ void MeshNode::onAfterValueChanged(BaseContext& context, ValueHandle const& valu Table* materialUniforms = nullptr; if (material && value.asBool()) { materialUniforms = &*material->uniforms_; + + auto optionsHandle = value.parent().get("options"); + context.set(optionsHandle, *material->options_); } updateUniformContainer(context, materialName, materialUniforms, uniformsHandle); diff --git a/datamodel/libUserTypes/src/PrefabInstance.cpp b/datamodel/libUserTypes/src/PrefabInstance.cpp index a8d58cd1..54c83ec0 100644 --- a/datamodel/libUserTypes/src/PrefabInstance.cpp +++ b/datamodel/libUserTypes/src/PrefabInstance.cpp @@ -8,78 +8,21 @@ * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "user_types/PrefabInstance.h" -#include "core/Context.h" -#include "core/Handles.h" namespace raco::user_types { -Table* findItem(Table& table, SEditorObject obj) { - for (size_t i{0}; i < table.size(); i++) { - Table& item = table.get(i)->asTable(); - if (item.get(0)->asRef() == obj) { - return &item; - } - } - return nullptr; -} - -Table* findItemByValue(Table& table, SEditorObject obj) { - for (size_t i{0}; i < table.size(); i++) { - Table& item = table.get(i)->asTable(); - if (item.get(1)->asRef() == obj) { - return &item; - } - } - return nullptr; -} - -int findItemIndex(Table& table, SEditorObject obj) { - for (size_t i{0}; i < table.size(); i++) { - Table& item = table.get(i)->asTable(); - if (item.get(0)->asRef() == obj) { - return static_cast(i); - } - } - return -1; -} - -SEditorObject PrefabInstance::mapToInstance(SEditorObject obj, SPrefab prefab, SPrefabInstance instance) { +std::string PrefabInstance::mapObjectIDToInstance(SEditorObject obj, SPrefab prefab, SPrefabInstance instance) { if (obj == prefab) { - return instance; + return instance->objectID(); } - if (Table* item = findItem(*(instance->mapToInstance_) ,obj)) { - return item->get(1)->asRef(); - } - return nullptr; + return EditorObject::XorObjectIDs(obj->objectID(), instance->objectID()); } -SEditorObject PrefabInstance::mapFromInstance(SEditorObject obj, SPrefabInstance instance) { - if (Table* item = findItemByValue(*(instance->mapToInstance_), obj)) { - return item->get(0)->asRef(); - } - return nullptr; -} - - -void PrefabInstance::removePrefabInstanceChild(BaseContext& context, const SEditorObject& prefabChild) { - int index = findItemIndex(*mapToInstance_, prefabChild); - if (index != -1) { - context.removeProperty({shared_from_this(), &PrefabInstance::mapToInstance_} , index); +std::string PrefabInstance::mapObjectIDFromInstance(SEditorObject obj, SPrefab prefab, SPrefabInstance instance) { + if (obj == instance) { + return prefab->objectID(); } + return EditorObject::XorObjectIDs(obj->objectID(), instance->objectID()); } -void PrefabInstance::addChildMapping(BaseContext& context, const SEditorObject& prefabChild, const SEditorObject& instanceChild) { - if (Table* item = findItem(*mapToInstance_, prefabChild)) { - *item->get(1) = instanceChild; - } else { - auto newItem = mapToInstance_->addProperty(PrimitiveType::Table); - auto key = newItem->asTable().addProperty("prefabChild", PrimitiveType::Ref); - auto val = newItem->asTable().addProperty("instChild", PrimitiveType::Ref); - *key = prefabChild; - *val = instanceChild; - } - context.changeMultiplexer().recordValueChanged(ValueHandle(shared_from_this(), &PrefabInstance::mapToInstance_)); -} - - } // namespace raco::user_types diff --git a/datamodel/libUserTypes/src/UserObjectFactory.cpp b/datamodel/libUserTypes/src/UserObjectFactory.cpp index 532e943c..09d4f166 100644 --- a/datamodel/libUserTypes/src/UserObjectFactory.cpp +++ b/datamodel/libUserTypes/src/UserObjectFactory.cpp @@ -10,6 +10,7 @@ #include "user_types/UserObjectFactory.h" #include "core/ExternalReferenceAnnotation.h" +#include "core/ProjectSettings.h" #include "user_types/Animation.h" #include "user_types/AnimationChannel.h" @@ -35,21 +36,17 @@ namespace raco::user_types { -template -SEditorObject UserObjectFactory::createObjectInternal(const std::string& name, const std::string& id) { - return std::make_shared(name, id); -} - template std::shared_ptr UserObjectFactory::createAnnotationInternal() { return std::make_shared(); } template -data_storage::ValueBase* UserObjectFactory::createValueInternal() { - return new Value>(); +std::shared_ptr UserObjectFactory::createStructInternal() { + return std::make_shared(); } + template std::map UserObjectFactory::makeTypeMap() { return std::map{ @@ -62,7 +59,7 @@ constexpr std::pair> createTypeMapP } template -std::map makePropertyMapTuple(std::tuple *dummy) { +std::map UserObjectFactory::makePropertyMapTuple(std::tuple* dummy) { return std::map{ createTypeMapPair()...}; } @@ -72,6 +69,11 @@ std::map UserObjectFactory {Args::typeDescription.typeName, {Args::typeDescription, createAnnotationInternal}}...}; } +template +std::map UserObjectFactory::makeStructTypeMap() { + return std::map{ + {Args::typeDescription.typeName, {Args::typeDescription, createStructInternal}}...}; +} UserObjectFactory::UserObjectFactory() { properties_ = makePropertyMapTuple(static_cast(nullptr)); @@ -101,6 +103,12 @@ UserObjectFactory::UserObjectFactory() { annotations_ = makeAnnotationMap< ExternalReferenceAnnotation >(); + + structTypes_ = makeStructTypeMap(); } UserObjectFactory& UserObjectFactory::getInstance() { @@ -111,7 +119,7 @@ UserObjectFactory& UserObjectFactory::getInstance() { return *instance; } -SEditorObject UserObjectFactory::createObject(const std::string& type, const std::string& name, const std::string& id) { +SEditorObject UserObjectFactory::createObject(const std::string& type, const std::string& name, const std::string& id) const { auto it = types_.find(type); if (it != types_.end()) { return it->second.createFunc(name, id); @@ -120,7 +128,7 @@ SEditorObject UserObjectFactory::createObject(const std::string& type, const std return SEditorObject(); } -std::shared_ptr UserObjectFactory::createAnnotation(const std::string& type) { +std::shared_ptr UserObjectFactory::createAnnotation(const std::string& type) const { auto it = annotations_.find(type); if (it != annotations_.end()) { return it->second.createFunc(); @@ -128,7 +136,15 @@ std::shared_ptr UserObjectFactory::createAnnotation(const std::s return nullptr; } -data_storage::ValueBase* UserObjectFactory::createValue(const std::string& type) { +std::shared_ptr UserObjectFactory::createStruct(const std::string& type) const { + auto it = structTypes_.find(type); + if (it != structTypes_.end()) { + return it->second.createFunc(); + } + return {}; +} + +data_storage::ValueBase* UserObjectFactory::createValue(const std::string& type) const { { auto it = types_.find(type); if (it != types_.end()) { @@ -152,4 +168,12 @@ bool UserObjectFactory::isUserCreatable(const std::string& type) const { return type != core::ProjectSettings::typeDescription.typeName; } +const std::map& UserObjectFactory::getStructTypes() const { + return structTypes_; +} + +const std::map& UserObjectFactory::getProperties() const { + return properties_; +} + } // namespace raco::user_types \ No newline at end of file diff --git a/datamodel/libUserTypes/src/Validation.h b/datamodel/libUserTypes/src/Validation.h index ab83e920..5a1ed422 100644 --- a/datamodel/libUserTypes/src/Validation.h +++ b/datamodel/libUserTypes/src/Validation.h @@ -13,7 +13,7 @@ #include "core/ErrorItem.h" #include "core/Handles.h" #include "utils/FileUtils.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "core/PathManager.h" #include "core/PathQueries.h" #include "core/Project.h" @@ -30,7 +30,7 @@ inline bool validateURI(raco::core::BaseContext& context, const raco::core::Valu if (handle.asString().empty()) { context.errors().addError(ErrorCategory::FILESYSTEM_ERROR, ErrorLevel::WARNING, handle, "Empty URI."); return false; - } else if (!raco::utils::path::exists(uriPath)) { + } else if (!raco::utils::u8path(uriPath).exists()) { context.errors().addError(ErrorCategory::FILESYSTEM_ERROR, ErrorLevel::ERROR, handle, "File not found."); return false; } else { diff --git a/datamodel/libUserTypes/tests/AnimationChannel_test.cpp b/datamodel/libUserTypes/tests/AnimationChannel_test.cpp index 3827a71f..42f59bf7 100644 --- a/datamodel/libUserTypes/tests/AnimationChannel_test.cpp +++ b/datamodel/libUserTypes/tests/AnimationChannel_test.cpp @@ -24,7 +24,7 @@ TEST_F(AnimationChannelTest, URI_setValidURI) { auto animChannel{commandInterface.createObject(AnimationChannel::typeDescription.typeName)}; ValueHandle m{animChannel}; ValueHandle m_uri{m.get("uri")}; - auto animChannelPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").generic_string(); + auto animChannelPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); commandInterface.set(m_uri, animChannelPath); ASSERT_EQ(m_uri.asString(), animChannelPath); } @@ -39,7 +39,7 @@ TEST_F(AnimationChannelTest, URI_setInvalidURI_error) { TEST_F(AnimationChannelTest, URI_setValidURI_noError) { auto animChannel{commandInterface.createObject(AnimationChannel::typeDescription.typeName)}; ValueHandle uriHandle{animChannel, {"uri"}}; - auto animChannelPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").generic_string(); + auto animChannelPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); commandInterface.set(uriHandle, animChannelPath); ASSERT_FALSE(commandInterface.errors().hasError(uriHandle)); @@ -48,7 +48,7 @@ TEST_F(AnimationChannelTest, URI_setValidURI_noError) { TEST_F(AnimationChannelTest, URI_setValidURI_noAnims) { auto animChannel{commandInterface.createObject(AnimationChannel::typeDescription.typeName)}; ValueHandle uriHandle{animChannel, {"uri"}}; - auto animChannelPath = cwd_path().append("meshes/Duck.glb").generic_string(); + auto animChannelPath = test_path().append("meshes/Duck.glb").string(); commandInterface.set(uriHandle, animChannelPath); ASSERT_TRUE(commandInterface.errors().hasError(uriHandle)); @@ -61,7 +61,7 @@ TEST_F(AnimationChannelTest, Inputs_setInvalidAnimationIndex_error) { ValueHandle uriHandle{animChannel, {"uri"}}; ValueHandle animIndexHandle{animChannel, {"animationIndex"}}; ValueHandle samplerIndexHandle{animChannel, {"samplerIndex"}}; - auto animChannelPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").generic_string(); + auto animChannelPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); commandInterface.set(uriHandle, animChannelPath); ASSERT_TRUE(commandInterface.errors().hasError(animHandle)); @@ -99,7 +99,7 @@ TEST_F(AnimationChannelTest, Inputs_setInvalidSamplerIndex_noError) { ValueHandle uriHandle{animChannel, {"uri"}}; ValueHandle animIndexHandle{animChannel, {"animationIndex"}}; ValueHandle samplerIndexHandle{animChannel, {"samplerIndex"}}; - auto animChannelPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").generic_string(); + auto animChannelPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); commandInterface.set(uriHandle, animChannelPath); ASSERT_TRUE(commandInterface.errors().hasError(animHandle)); @@ -135,7 +135,7 @@ TEST_F(AnimationChannelTest, invalidAnim_weights_supported) { auto animChannel{commandInterface.createObject(AnimationChannel::typeDescription.typeName)}; ValueHandle uriHandle{animChannel, {"uri"}}; - std::string uriPath{(cwd_path() / "meshes" / "AnimatedMorphCube" / "AnimatedMorphCube.gltf").string()}; + std::string uriPath{(test_path() / "meshes" / "AnimatedMorphCube" / "AnimatedMorphCube.gltf").string()}; commandInterface.set({animChannel, {"uri"}}, uriPath); ValueHandle samplerIndexHandle{animChannel, {"samplerIndex"}}; @@ -151,7 +151,7 @@ TEST_F(AnimationChannelTest, validAnim_madeInvalid) { auto animChannel = create("anim_channel"); ValueHandle uriHandle{animChannel, &raco::user_types::AnimationChannel::uri_}; - auto animChannelPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").generic_string(); + auto animChannelPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); commandInterface.set(uriHandle, animChannelPath); ASSERT_FALSE(commandInterface.errors().hasError(uriHandle)); diff --git a/datamodel/libUserTypes/tests/Animation_test.cpp b/datamodel/libUserTypes/tests/Animation_test.cpp index 55f80319..8a005132 100644 --- a/datamodel/libUserTypes/tests/Animation_test.cpp +++ b/datamodel/libUserTypes/tests/Animation_test.cpp @@ -40,7 +40,7 @@ TEST_F(AnimationTest, emptyAnimChannelsErrors) { ASSERT_FALSE(commandInterface.errors().hasError({anim, {"animationChannels", "Channel 3"}})); ValueHandle uriHandle{animChannel, {"uri"}}; - auto animChannelPath = cwd_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").generic_string(); + auto animChannelPath = test_path().append("meshes/CesiumMilkTruck/CesiumMilkTruck.gltf").string(); commandInterface.set(uriHandle, animChannelPath); ASSERT_FALSE(commandInterface.errors().hasError({anim, {"animationChannels", "Channel 0"}})); diff --git a/datamodel/libUserTypes/tests/CMakeLists.txt b/datamodel/libUserTypes/tests/CMakeLists.txt index aacef0e7..39ea02cd 100644 --- a/datamodel/libUserTypes/tests/CMakeLists.txt +++ b/datamodel/libUserTypes/tests/CMakeLists.txt @@ -13,9 +13,13 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h set(TEST_SOURCES Animation_test.cpp AnimationChannel_test.cpp + DefaultValues_test.cpp + Material_test.cpp + MeshNode_test.cpp + LuaScript_test.cpp + LuaScriptModule_test.cpp LuaScript_test.cpp LuaScriptModule_test.cpp - DefaultValues_test.cpp ) set(TEST_LIBRARIES raco::UserTypes @@ -35,9 +39,12 @@ raco_package_add_test_resouces( meshes/CesiumMilkTruck/CesiumMilkTruck.gltf meshes/CesiumMilkTruck/CesiumMilkTruck_data.bin meshes/Duck.glb + meshes/meshless.gltf scripts/array.lua scripts/compile-error.lua scripts/moduleDefinition.lua scripts/moduleDependency.lua scripts/struct.lua + shaders/basic.vert + shaders/basic.frag ) diff --git a/datamodel/libUserTypes/tests/LuaScriptModule_test.cpp b/datamodel/libUserTypes/tests/LuaScriptModule_test.cpp index c3908e30..e83e5bb0 100644 --- a/datamodel/libUserTypes/tests/LuaScriptModule_test.cpp +++ b/datamodel/libUserTypes/tests/LuaScriptModule_test.cpp @@ -22,8 +22,8 @@ TEST_F(LuaScriptModuleTest, URI_setValidURI) { auto module{commandInterface.createObject(LuaScriptModule::typeDescription.typeName)}; ValueHandle m{module}; ValueHandle m_uri{m.get("uri")}; - commandInterface.set(m_uri, cwd_path().append("scripts/moduleDefinition.lua").string()); - ASSERT_EQ(m_uri.asString(), cwd_path().append("scripts/moduleDefinition.lua").generic_string()); + commandInterface.set(m_uri, test_path().append("scripts/moduleDefinition.lua").string()); + ASSERT_EQ(m_uri.asString(), test_path().append("scripts/moduleDefinition.lua").string()); } TEST_F(LuaScriptModuleTest, URI_emptyURI_error) { @@ -46,7 +46,7 @@ TEST_F(LuaScriptModuleTest, URI_setInvalidURI_error) { TEST_F(LuaScriptModuleTest, URI_setValidURI_noError) { auto module{commandInterface.createObject(LuaScriptModule::typeDescription.typeName)}; ValueHandle uriHandle{module, {"uri"}}; - commandInterface.set(uriHandle, cwd_path().append("scripts/moduleDefinition.lua").string()); + commandInterface.set(uriHandle, test_path().append("scripts/moduleDefinition.lua").string()); ASSERT_FALSE(commandInterface.errors().hasError(uriHandle)); ASSERT_FALSE(commandInterface.errors().hasError(module)); @@ -55,7 +55,21 @@ TEST_F(LuaScriptModuleTest, URI_setValidURI_noError) { TEST_F(LuaScriptModuleTest, URI_setValidURI_noModules_error) { auto module{commandInterface.createObject(LuaScriptModule::typeDescription.typeName)}; ValueHandle uriHandle{module, {"uri"}}; - commandInterface.set(uriHandle, cwd_path().append("scripts/struct.lua").string()); + commandInterface.set(uriHandle, test_path().append("scripts/struct.lua").string()); ASSERT_TRUE(commandInterface.errors().hasError(module)); +} + +TEST_F(LuaScriptModuleTest, table_missing_error) { + auto script{commandInterface.createObject(LuaScriptModule::typeDescription.typeName)}; + TextFile scriptWhitespaceOnlyFile = makeFile("script1.lua", " "); + TextFile scriptEmptyFile = makeFile("script2.lua", ""); + + ValueHandle uriHandle{ValueHandle{script, {"uri"}}}; + commandInterface.set(uriHandle, scriptWhitespaceOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(script)); + ASSERT_EQ(commandInterface.errors().getError(script).message(), "[Stage::PreprocessModule] Error while loading module. Module script must return a table!"); + commandInterface.set(uriHandle, scriptEmptyFile); + ASSERT_TRUE(commandInterface.errors().hasError(script)); + ASSERT_EQ(commandInterface.errors().getError(script).message(), "[Stage::PreprocessModule] Error while loading module. Module script must return a table!"); } \ No newline at end of file diff --git a/datamodel/libUserTypes/tests/LuaScript_test.cpp b/datamodel/libUserTypes/tests/LuaScript_test.cpp index c2a7f62c..5bb2284e 100644 --- a/datamodel/libUserTypes/tests/LuaScript_test.cpp +++ b/datamodel/libUserTypes/tests/LuaScript_test.cpp @@ -22,7 +22,7 @@ TEST_F(LuaScriptTest, URI_setValidURI) { auto script{commandInterface.createObject(LuaScript::typeDescription.typeName)}; ValueHandle m{script}; ValueHandle m_uri{m.get("uri")}; - auto scriptPath = cwd_path().append("scripts/struct.lua").generic_string(); + auto scriptPath = test_path().append("scripts/struct.lua").string(); commandInterface.set(m_uri, scriptPath); EXPECT_EQ(m_uri.asString(), scriptPath); } @@ -36,7 +36,7 @@ TEST_F(LuaScriptTest, URI_setInvalidURI_error) { TEST_F(LuaScriptTest, URI_setValidURI_noError) { auto script{commandInterface.createObject(LuaScript::typeDescription.typeName)}; ValueHandle uriHandle{ script, { "uri" }}; - commandInterface.set(uriHandle, cwd_path().append("scripts/struct.lua").string()); + commandInterface.set(uriHandle, test_path().append("scripts/struct.lua").string()); EXPECT_FALSE(commandInterface.errors().hasError(uriHandle)); } @@ -44,7 +44,7 @@ TEST_F(LuaScriptTest, URI_setValidURI_compileError) { auto script{commandInterface.createObject(LuaScript::typeDescription.typeName)}; ValueHandle uriHandle{ script, { "uri" }}; - commandInterface.set(uriHandle, cwd_path().append("scripts/compile-error.lua").string()); + commandInterface.set(uriHandle, test_path().append("scripts/compile-error.lua").string()); EXPECT_TRUE(commandInterface.errors().hasError(script)); EXPECT_FALSE(commandInterface.errors().hasError(uriHandle)); @@ -52,7 +52,7 @@ TEST_F(LuaScriptTest, URI_setValidURI_compileError) { TEST_F(LuaScriptTest, URI_setValidURI_trimFront) { auto script{commandInterface.createObject(LuaScript::typeDescription.typeName)}; - const auto FILE_NAME = cwd_path().append("scripts/compile-error.lua").generic_string(); + const auto FILE_NAME = test_path().append("scripts/compile-error.lua").string(); ValueHandle uriHandle{ValueHandle{script, {"uri"}}}; commandInterface.set(uriHandle, " " + FILE_NAME); EXPECT_EQ(uriHandle.asString(), FILE_NAME); @@ -61,7 +61,7 @@ TEST_F(LuaScriptTest, URI_setValidURI_trimFront) { TEST_F(LuaScriptTest, URI_setValidURI_trimBack) { auto script{commandInterface.createObject(LuaScript::typeDescription.typeName)}; - const auto FILE_NAME = cwd_path().append("scripts/compile-error.lua").generic_string(); + const auto FILE_NAME = test_path().append("scripts/compile-error.lua").string(); ValueHandle uriHandle{ValueHandle{script, {"uri"}}}; commandInterface.set(uriHandle, FILE_NAME + " "); EXPECT_EQ(uriHandle.asString(), FILE_NAME); @@ -100,7 +100,7 @@ TEST_F(LuaScriptTest, inputs_are_correctly_built) { auto script{commandInterface.createObject(LuaScript::typeDescription.typeName)}; ValueHandle s{script}; ValueHandle uri{s.get("uri")}; - auto scriptPath = cwd_path().append("scripts/struct.lua").string(); + auto scriptPath = test_path().append("scripts/struct.lua").string(); commandInterface.set(uri, scriptPath); ValueHandle luaInputs{s.get("luaInputs")}; @@ -115,6 +115,34 @@ TEST_F(LuaScriptTest, inputs_are_correctly_built) { EXPECT_EQ(PrimitiveType::Double, structInput[1].type()); } +TEST_F(LuaScriptTest, errorInterfaceMissing) { + auto script{commandInterface.createObject(LuaScript::typeDescription.typeName)}; + TextFile scriptRunOnlyFile = makeFile("script1.lua", R"( +function run() +end +)"); + TextFile scriptInterfaceOnlyFile = makeFile("script2.lua", R"( +function interface() +end +)"); + TextFile scriptWhitespaceOnlyFile = makeFile("script3.lua", " "); + TextFile scriptEmptyFile = makeFile("script4.lua", ""); + + ValueHandle uriHandle{ValueHandle{script, {"uri"}}}; + commandInterface.set(uriHandle, scriptRunOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(script)); + ASSERT_EQ(commandInterface.errors().getError(script).message(), "[Stage::PreprocessScript] No 'interface' function defined!"); + commandInterface.set(uriHandle, scriptInterfaceOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(script)); + ASSERT_EQ(commandInterface.errors().getError(script).message(), "[Stage::PreprocessScript] No 'run' function defined!"); + commandInterface.set(uriHandle, scriptWhitespaceOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(script)); + ASSERT_EQ(commandInterface.errors().getError(script).message(), "[Stage::PreprocessScript] No 'interface' function defined!"); + commandInterface.set(uriHandle, scriptEmptyFile); + ASSERT_TRUE(commandInterface.errors().hasError(script)); + ASSERT_EQ(commandInterface.errors().getError(script).message(), "[Stage::PreprocessScript] No 'interface' function defined!"); +} + TEST_F(LuaScriptTest, arrayIsCorrectlyBuilt) { auto script = create("foo"); TextFile scriptFile = makeFile("script.lua", R"( @@ -187,15 +215,15 @@ end TEST_F(LuaScriptTest, restore_cached_struct_member) { auto lua = create("script"); - commandInterface.set({lua, {"uri"}}, (cwd_path() / "scripts/struct.lua").string()); + commandInterface.set({lua, {"uri"}}, (test_path() / "scripts/struct.lua").string()); commandInterface.set({lua, {"luaInputs", "struct", "a"}}, 2.0); ValueHandle inputs{lua, {"luaInputs"}}; - commandInterface.set({lua, {"uri"}}, (cwd_path() / "scripts/nosuchfile.lua").string()); + commandInterface.set({lua, {"uri"}}, (test_path() / "scripts/nosuchfile.lua").string()); ASSERT_EQ(inputs.size(), 0); - commandInterface.set({lua, {"uri"}}, (cwd_path() / "scripts/struct.lua").string()); + commandInterface.set({lua, {"uri"}}, (test_path() / "scripts/struct.lua").string()); ASSERT_EQ(inputs.size(), 1); ValueHandle in_struct = inputs.get("struct"); ASSERT_TRUE(in_struct); @@ -255,10 +283,11 @@ TEST_F(LuaScriptTest, modules_in_uri_are_rejected) { ValueHandle s{newScript}; ValueHandle uri{s.get("uri")}; - commandInterface.set(s.get("uri"), cwd_path().append("scripts/moduleDependency.lua").string()); + commandInterface.set(s.get("uri"), test_path().append("scripts/moduleDefinition.lua").string()); ASSERT_TRUE(commandInterface.errors().hasError({newScript})); ASSERT_EQ(commandInterface.errors().getError({newScript}).level(), raco::core::ErrorLevel::ERROR); + ASSERT_EQ(newScript->luaModules_->size(), 0); } @@ -558,3 +587,202 @@ end ASSERT_FALSE(commandInterface.errors().hasError({newScript})); ASSERT_EQ(newScript->luaModules_.asTable().size(), 0); } + +TEST_F(LuaScriptTest, module_error_messages_invalid_assigned) { + auto moduleInvalid = create("invalid"); + auto newScript = create("script"); + ValueHandle s{newScript}; + ValueHandle scriptUri{s.get("uri")}; + + auto scriptFile = makeFile("script.lua", R"( +modules("test", "coalas") + +function interface() +end + +function run() +end +)"); + + auto moduleInvalidFile = makeFile("moduleInvalid.lua", R"( +local what = {} +error; +return what +)"); + + commandInterface.set(scriptUri, scriptFile); + commandInterface.set(raco::core::ValueHandle{moduleInvalid, &raco::user_types::LuaScriptModule::uri_}, moduleInvalidFile); + commandInterface.set(s.get("luaModules").get("coalas"), moduleInvalid); + + ASSERT_TRUE(commandInterface.errors().hasError({newScript})); + ASSERT_EQ(commandInterface.errors().getError({newScript}).message(), "[Stage::PreprocessScript] LuaScript can not be created because it contains invalid LuaScriptModules."); + + ASSERT_TRUE(commandInterface.errors().hasError(s.get("luaModules").get("coalas"))); + ASSERT_EQ(commandInterface.errors().getError(s.get("luaModules").get("coalas")).level(), raco::core::ErrorLevel::ERROR); + ASSERT_EQ(commandInterface.errors().getError(s.get("luaModules").get("coalas")).message(), "Invalid LuaScriptModule 'invalid' assigned."); + + ASSERT_FALSE(commandInterface.errors().hasError(s.get("luaModules").get("test"))); +} + +TEST_F(LuaScriptTest, module_error_messages_two_invalid_assigned) { + auto moduleInvalid1 = create("invalid1"); + auto moduleInvalid2 = create("invalid2"); + auto newScript = create("script"); + ValueHandle s{newScript}; + ValueHandle scriptUri{s.get("uri")}; + + auto scriptFile = makeFile("script.lua", R"( +modules("test", "coalas") + +function interface() +end + +function run() +end +)"); + + auto moduleInvalidFile = makeFile("moduleInvalid.lua", R"( +local what = {} +error; +return what +)"); + + commandInterface.set(scriptUri, scriptFile); + commandInterface.set(raco::core::ValueHandle{moduleInvalid1, &raco::user_types::LuaScriptModule::uri_}, moduleInvalidFile); + commandInterface.set(raco::core::ValueHandle{moduleInvalid2, &raco::user_types::LuaScriptModule::uri_}, moduleInvalidFile); + commandInterface.set(s.get("luaModules").get("coalas"), moduleInvalid1); + commandInterface.set(s.get("luaModules").get("test"), moduleInvalid2); + + ASSERT_TRUE(commandInterface.errors().hasError({newScript})); + ASSERT_EQ(commandInterface.errors().getError({newScript}).message(), "[Stage::PreprocessScript] LuaScript can not be created because it contains invalid LuaScriptModules."); + + ASSERT_TRUE(commandInterface.errors().hasError(s.get("luaModules").get("coalas"))); + ASSERT_EQ(commandInterface.errors().getError(s.get("luaModules").get("coalas")).level(), raco::core::ErrorLevel::ERROR); + ASSERT_EQ(commandInterface.errors().getError(s.get("luaModules").get("coalas")).message(), "Invalid LuaScriptModule 'invalid1' assigned."); + + ASSERT_TRUE(commandInterface.errors().hasError(s.get("luaModules").get("test"))); + ASSERT_EQ(commandInterface.errors().getError(s.get("luaModules").get("test")).level(), raco::core::ErrorLevel::ERROR); + ASSERT_EQ(commandInterface.errors().getError(s.get("luaModules").get("test")).message(), "Invalid LuaScriptModule 'invalid2' assigned."); +} + +TEST_F(LuaScriptTest, module_error_messages_invalid_then_valid) { + auto moduleValid = create("valid"); + auto moduleInvalid = create("invalid"); + auto newScript = create("script"); + ValueHandle s{newScript}; + ValueHandle scriptUri{s.get("uri")}; + + auto scriptFile = makeFile("script.lua", R"( +modules("test", "coalas") + +function interface() +end + +function run() +end +)"); + + auto moduleInvalidFile = makeFile("moduleInvalid.lua", R"( +local what = {} +error; +return what +)"); + + auto moduleValidFile = makeFile("moduleValid.lua", R"( +local coalaModule = {} + +return coalaModule +)"); + + commandInterface.set(scriptUri, scriptFile); + commandInterface.set(raco::core::ValueHandle{moduleValid, &raco::user_types::LuaScriptModule::uri_}, moduleValidFile); + commandInterface.set(raco::core::ValueHandle{moduleInvalid, &raco::user_types::LuaScriptModule::uri_}, moduleInvalidFile); + + commandInterface.set(s.get("luaModules").get("coalas"), moduleValid); + commandInterface.set(s.get("luaModules").get("test"), moduleValid); + + // make invalid by switching LuaScriptModule + commandInterface.set(s.get("luaModules").get("coalas"), moduleInvalid); + + ASSERT_TRUE(commandInterface.errors().hasError({newScript})); + ASSERT_EQ(commandInterface.errors().getError({newScript}).message(), "[Stage::PreprocessScript] LuaScript can not be created because it contains invalid LuaScriptModules."); + ASSERT_TRUE(commandInterface.errors().hasError(s.get("luaModules").get("coalas"))); + ASSERT_FALSE(commandInterface.errors().hasError(s.get("luaModules").get("test"))); + + // make invalid by changing URIs + commandInterface.set(raco::core::ValueHandle{moduleValid, &raco::user_types::LuaScriptModule::uri_}, moduleInvalidFile); + + ASSERT_TRUE(commandInterface.errors().hasError({newScript})); + ASSERT_EQ(commandInterface.errors().getError({newScript}).message(), "[Stage::PreprocessScript] LuaScript can not be created because it contains invalid LuaScriptModules."); + ASSERT_TRUE(commandInterface.errors().hasError(s.get("luaModules").get("test"))); + ASSERT_TRUE(commandInterface.errors().hasError(s.get("luaModules").get("coalas"))); +} + +TEST_F(LuaScriptTest, module_error_messages_invalid_then_empty) { + auto moduleValid = create("valid"); + auto moduleInvalid = create("invalid"); + auto newScript = create("script"); + ValueHandle s{newScript}; + ValueHandle scriptUri{s.get("uri")}; + + auto scriptFile = makeFile("script.lua", R"( +modules("test", "coalas") + +function interface() +end + +function run() +end +)"); + + auto moduleInvalidFile = makeFile("moduleInvalid.lua", R"( +local what = {} +error; +return what +)"); + + auto moduleValidFile = makeFile("moduleValid.lua", R"( +local coalaModule = {} + +return coalaModule +)"); + + commandInterface.set(scriptUri, scriptFile); + commandInterface.set(raco::core::ValueHandle{moduleValid, &raco::user_types::LuaScriptModule::uri_}, moduleValidFile); + commandInterface.set(raco::core::ValueHandle{moduleInvalid, &raco::user_types::LuaScriptModule::uri_}, moduleInvalidFile); + + commandInterface.set(s.get("luaModules").get("coalas"), moduleInvalid); + commandInterface.set(s.get("luaModules").get("test"), moduleValid); + + commandInterface.set(s.get("luaModules").get("coalas"), SEditorObject()); + + ASSERT_TRUE(commandInterface.errors().hasError({newScript})); + ASSERT_NE(commandInterface.errors().getError({newScript}).message(), "[Stage::PreprocessScript] LuaScript can not be created because it contains invalid LuaScriptModules."); + ASSERT_FALSE(commandInterface.errors().hasError(s.get("luaModules").get("coalas"))); + ASSERT_FALSE(commandInterface.errors().hasError(s.get("luaModules").get("test"))); +} + +TEST_F(LuaScriptTest, module_error_messages_no_module_uri) { + auto moduleInvalid = create("invalid"); + auto newScript = create("script"); + ValueHandle s{newScript}; + ValueHandle scriptUri{s.get("uri")}; + + auto scriptFile = makeFile("script.lua", R"( +modules("test") + +function interface() +end + +function run() +end +)"); + + commandInterface.set(scriptUri, scriptFile); + + commandInterface.set(s.get("luaModules").get("test"), moduleInvalid); + + ASSERT_TRUE(commandInterface.errors().hasError({newScript})); + ASSERT_EQ(commandInterface.errors().getError({newScript}).message(), "[Stage::PreprocessScript] LuaScript can not be created because it contains invalid LuaScriptModules."); + ASSERT_TRUE(commandInterface.errors().hasError(s.get("luaModules").get("test"))); +} \ No newline at end of file diff --git a/datamodel/libUserTypes/tests/Material_test.cpp b/datamodel/libUserTypes/tests/Material_test.cpp new file mode 100644 index 00000000..65ee7dc3 --- /dev/null +++ b/datamodel/libUserTypes/tests/Material_test.cpp @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "testing/TestEnvironmentCore.h" + +#include "user_types/Material.h" +#include "utils/FileUtils.h" +#include + +using namespace raco::core; +using namespace raco::user_types; + +class MaterialTest : public TestEnvironmentCore { +public: + std::string shaderWhitespaceOnlyFile; + std::string shaderEmptyFile; + + SEditorObject material; + + ValueHandle vertexUriHandle; + ValueHandle geometryUriHandle; + ValueHandle fragmentUriHandle; + ValueHandle definesUriHandle; + + void SetUp() { + TestEnvironmentCore::SetUp(); + shaderWhitespaceOnlyFile = makeFile("whitespace.glsl", " "); + shaderEmptyFile = makeFile("empty.glsl", ""); + + material = commandInterface.createObject(Material::typeDescription.typeName); + + vertexUriHandle = {material, &Material::uriVertex_}; + fragmentUriHandle = {material, &Material::uriFragment_}; + geometryUriHandle = {material, &Material::uriGeometry_}; + definesUriHandle = {material, &Material::uriDefines_}; + } +}; + +TEST_F(MaterialTest, validShader) { + commandInterface.set(vertexUriHandle, (test_path() / "shaders/basic.vert").string()); + commandInterface.set(fragmentUriHandle, (test_path() / "shaders/basic.frag").string()); + ASSERT_FALSE(commandInterface.errors().hasError(material)); + ASSERT_FALSE(commandInterface.errors().hasError(vertexUriHandle)); + ASSERT_FALSE(commandInterface.errors().hasError(fragmentUriHandle)); +} + +TEST_F(MaterialTest, errorEmptyVertexShader) { + commandInterface.set(fragmentUriHandle, (test_path() / "shaders/basic.frag").string()); + commandInterface.set(vertexUriHandle, shaderEmptyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] Shader Program Linker Error:\nERROR: Linking vertex stage: Missing entry point: Each stage requires one entry point\n\n"); + commandInterface.set(vertexUriHandle, shaderWhitespaceOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] Shader Program Linker Error:\nERROR: Linking vertex stage: Missing entry point: Each stage requires one entry point\n\n"); +} + +TEST_F(MaterialTest, errorEmptyFragmentShader) { + commandInterface.set(vertexUriHandle, (test_path() / "shaders/basic.vert").string()); + commandInterface.set(fragmentUriHandle, shaderEmptyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] Shader Program Linker Error:\nERROR: Linking fragment stage: Missing entry point: Each stage requires one entry point\n\n"); + commandInterface.set(fragmentUriHandle, shaderWhitespaceOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] Shader Program Linker Error:\nERROR: Linking fragment stage: Missing entry point: Each stage requires one entry point\n\n"); +} + +TEST_F(MaterialTest, errorEmptyGeometryShader) { + commandInterface.set(vertexUriHandle, (test_path() / "shaders/basic.vert").string()); + commandInterface.set(fragmentUriHandle, (test_path() / "shaders/basic.frag").string()); + ASSERT_FALSE(commandInterface.errors().hasError(material)); + + commandInterface.set(geometryUriHandle, shaderEmptyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] geometry shader Shader Parsing Error:\nERROR: #version: geometry shaders require es profile with version 310 or non-es profile with version 150 or above\nERROR: 0:1: '' : array size must be a positive integer\nERROR: 0:1: '' : compilation terminated \nINTERNAL ERROR: Unable to parse built-ins\nERROR: 1 compilation errors. No code generated.\n\n\n"); + commandInterface.set(geometryUriHandle, shaderWhitespaceOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] geometry shader Shader Parsing Error:\nERROR: #version: geometry shaders require es profile with version 310 or non-es profile with version 150 or above\nERROR: 0:1: '' : array size must be a positive integer\nERROR: 0:1: '' : compilation terminated \nINTERNAL ERROR: Unable to parse built-ins\nERROR: 1 compilation errors. No code generated.\n\n\n"); +} + +TEST_F(MaterialTest, errorEmptyDefines) { + commandInterface.set(vertexUriHandle, (test_path() / "shaders/basic.vert").string()); + commandInterface.set(fragmentUriHandle, (test_path() / "shaders/basic.frag").string()); + ASSERT_FALSE(commandInterface.errors().hasError(material)); + + commandInterface.set(definesUriHandle, shaderEmptyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] vertex shader Shader Parsing Error:\nERROR: 2:1: '#define' : must be followed by macro name \nERROR: 2:1: '' : compilation terminated \nERROR: 2 compilation errors. No code generated.\n\n\n"); + commandInterface.set(definesUriHandle, shaderWhitespaceOnlyFile); + ASSERT_TRUE(commandInterface.errors().hasError(material)); + ASSERT_EQ(commandInterface.errors().getError(material).message(), "[GLSL Compiler] vertex shader Shader Parsing Error:\nERROR: 2:1: '#define' : must be followed by macro name \nERROR: 2:1: '' : compilation terminated \nERROR: 2 compilation errors. No code generated.\n\n\n"); +} \ No newline at end of file diff --git a/datamodel/libUserTypes/tests/MeshNode_test.cpp b/datamodel/libUserTypes/tests/MeshNode_test.cpp new file mode 100644 index 00000000..a76ac4cc --- /dev/null +++ b/datamodel/libUserTypes/tests/MeshNode_test.cpp @@ -0,0 +1,250 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "testing/TestEnvironmentCore.h" +#include "user_types/MeshNode.h" +#include + +using namespace raco::core; +using namespace raco::user_types; + +class MeshNodeTest : public TestEnvironmentCore { +protected: + void setMaterialOptions(raco::user_types::SMaterial& material) { + commandInterface.set({material, {"options", "blendOperationColor"}}, 3); + commandInterface.set({material, {"options", "blendOperationAlpha"}}, 2); + commandInterface.set({material, {"options", "blendFactorSrcColor"}}, 8); + commandInterface.set({material, {"options", "blendFactorDestColor"}}, 10); + commandInterface.set({material, {"options", "blendFactorSrcAlpha"}}, 5); + commandInterface.set({material, {"options", "blendFactorDestAlpha"}}, 4); + commandInterface.set({material, {"options", "blendColor", "x"}}, 0.2); + commandInterface.set({material, {"options", "blendColor", "y"}}, 0.4); + commandInterface.set({material, {"options", "blendColor", "z"}}, 0.6); + commandInterface.set({material, {"options", "blendColor", "w"}}, 0.8); + commandInterface.set({material, {"options", "depthwrite"}}, false); + commandInterface.set({material, {"options", "depthFunction"}}, 0); + commandInterface.set({material, {"options", "cullmode"}}, 1); + } +}; + +TEST_F(MeshNodeTest, submeshSelection_wrongSubmeshIndexCreatesErrorTooLow) { + auto meshNode = commandInterface.createObject(MeshNode::typeDescription.typeName, "MeshNode"); + auto mesh = commandInterface.createObject(Mesh::typeDescription.typeName, "Mesh"); + + commandInterface.set(ValueHandle{meshNode, {"mesh"}}, mesh); + commandInterface.set(ValueHandle{mesh, {"bakeMeshes"}}, false); + commandInterface.set(ValueHandle{mesh, {"uri"}}, test_path().append("meshes/Duck.glb").string()); + commandInterface.set(ValueHandle{mesh, {"meshIndex"}}, -1); + ASSERT_EQ(commandInterface.errors().getError(ValueHandle{mesh}).level(), raco::core::ErrorLevel::ERROR); +} + +TEST_F(MeshNodeTest, submeshSelection_correctSubmeshIndexFixesError) { + auto meshNode = commandInterface.createObject(MeshNode::typeDescription.typeName, "MeshNode"); + auto mesh = commandInterface.createObject(Mesh::typeDescription.typeName, "Mesh"); + + commandInterface.set(ValueHandle{meshNode, {"mesh"}}, mesh); + commandInterface.set(ValueHandle{mesh, {"bakeMeshes"}}, false); + commandInterface.set(ValueHandle{mesh, {"uri"}}, test_path().append("meshes/Duck.glb").string()); + commandInterface.set(ValueHandle{mesh, {"meshIndex"}}, 1); + commandInterface.set(ValueHandle{mesh, {"meshIndex"}}, 0); + + ASSERT_EQ(commandInterface.errors().getError(ValueHandle{mesh}).level(), raco::core::ErrorLevel::INFORMATION); +} + +TEST_F(MeshNodeTest, valid_file_with_no_meshes_unbaked_submesh_error) { + auto meshNode = commandInterface.createObject(MeshNode::typeDescription.typeName, "MeshNode"); + auto mesh = commandInterface.createObject(Mesh::typeDescription.typeName, "Mesh"); + + commandInterface.set(ValueHandle{meshNode, &raco::user_types::MeshNode::mesh_}, mesh); + commandInterface.set(ValueHandle{mesh, &raco::user_types::Mesh::bakeMeshes_}, false); + commandInterface.set(ValueHandle{mesh, &raco::user_types::Mesh::uri_}, test_path().append("meshes/meshless.gltf").string()); + + ASSERT_TRUE(commandInterface.errors().hasError({mesh})); + ASSERT_EQ(commandInterface.errors().getError(ValueHandle{mesh}).level(), raco::core::ErrorLevel::ERROR); +} + +TEST_F(MeshNodeTest, valid_file_with_no_meshes_baked_no_submesh_error) { + auto meshNode = commandInterface.createObject(MeshNode::typeDescription.typeName, "MeshNode"); + auto mesh = commandInterface.createObject(Mesh::typeDescription.typeName, "Mesh"); + + commandInterface.set(ValueHandle{meshNode, &raco::user_types::MeshNode::mesh_}, mesh); + commandInterface.set(ValueHandle{mesh, &raco::user_types::Mesh::bakeMeshes_}, false); + commandInterface.set(ValueHandle{mesh, &raco::user_types::Mesh::uri_}, test_path().append("meshes/meshless.gltf").string()); + commandInterface.set(ValueHandle{mesh, &raco::user_types::Mesh::bakeMeshes_}, true); + + ASSERT_TRUE(commandInterface.errors().hasError({mesh})); + ASSERT_EQ(commandInterface.errors().getError(ValueHandle{mesh}).level(), raco::core::ErrorLevel::ERROR); +} + +TEST_F(MeshNodeTest, private_material_options_get_taken_over_from_shared_material) { + auto material = create_material("Material", "shaders/basic.vert", "shaders/basic.frag"); + auto mesh = create_mesh("Mesh", "meshes/Duck.glb"); + auto meshNode = create_meshnode("MeshNode", mesh, material); + + setMaterialOptions(material); + + commandInterface.set(meshNode->getMaterialPrivateHandle(0), true); + + auto privateMaterialOptionsHandle = meshNode->getMaterialOptionsHandle(0); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationColor").asInt(), 3); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationAlpha").asInt(), 2); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcColor").asInt(), 8); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestColor").asInt(), 10); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcAlpha").asInt(), 5); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestAlpha").asInt(), 4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("x").asDouble(), 0.2); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("y").asDouble(), 0.4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("z").asDouble(), 0.6); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("w").asDouble(), 0.8); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthwrite").asBool(), false); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthFunction").asInt(), 0); + ASSERT_EQ(privateMaterialOptionsHandle.get("cullmode").asInt(), 1); +} + +TEST_F(MeshNodeTest, private_material_new_options_get_taken_from_shared_material_after_reprivatizing) { + auto material = create_material("Material", "shaders/basic.vert", "shaders/basic.frag"); + auto mesh = create_mesh("Mesh", "meshes/Duck.glb"); + auto meshNode = create_meshnode("MeshNode", mesh, material); + + setMaterialOptions(material); + + commandInterface.set(meshNode->getMaterialPrivateHandle(0), true); + + auto privateMaterialOptionsHandle = meshNode->getMaterialOptionsHandle(0); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationColor").asInt(), 3); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationAlpha").asInt(), 2); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcColor").asInt(), 8); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestColor").asInt(), 10); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcAlpha").asInt(), 5); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestAlpha").asInt(), 4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("x").asDouble(), 0.2); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("y").asDouble(), 0.4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("z").asDouble(), 0.6); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("w").asDouble(), 0.8); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthwrite").asBool(), false); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthFunction").asInt(), 0); + ASSERT_EQ(privateMaterialOptionsHandle.get("cullmode").asInt(), 1); + + + commandInterface.set(meshNode->getMaterialPrivateHandle(0), false); + + commandInterface.set({material, {"options", "blendOperationColor"}}, 4); + commandInterface.set({material, {"options", "blendOperationAlpha"}}, 1); + commandInterface.set({material, {"options", "blendFactorSrcColor"}}, 3); + commandInterface.set({material, {"options", "blendFactorDestColor"}}, 5); + commandInterface.set({material, {"options", "blendFactorSrcAlpha"}}, 1); + commandInterface.set({material, {"options", "blendFactorDestAlpha"}}, 0); + commandInterface.set({material, {"options", "blendColor", "x"}}, 0.1); + commandInterface.set({material, {"options", "blendColor", "y"}}, 0.3); + commandInterface.set({material, {"options", "blendColor", "z"}}, 0.5); + commandInterface.set({material, {"options", "blendColor", "w"}}, 0.6); + commandInterface.set({material, {"options", "depthwrite"}}, true); + commandInterface.set({material, {"options", "depthFunction"}}, 1); + commandInterface.set({material, {"options", "cullmode"}}, 0); + + commandInterface.set(meshNode->getMaterialPrivateHandle(0), true); + + privateMaterialOptionsHandle = meshNode->getMaterialOptionsHandle(0); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationColor").asInt(), 4); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationAlpha").asInt(), 1); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcColor").asInt(), 3); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestColor").asInt(), 5); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcAlpha").asInt(), 1); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestAlpha").asInt(), 0); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("x").asDouble(), 0.1); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("y").asDouble(), 0.3); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("z").asDouble(), 0.5); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("w").asDouble(), 0.6); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthwrite").asBool(), true); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthFunction").asInt(), 1); + ASSERT_EQ(privateMaterialOptionsHandle.get("cullmode").asInt(), 0); +} + +TEST_F(MeshNodeTest, private_material_options_get_overwritten_from_shared_material_after_reprivatizing) { + auto material = create_material("Material", "shaders/basic.vert", "shaders/basic.frag"); + auto mesh = create_mesh("Mesh", "meshes/Duck.glb"); + auto meshNode = create_meshnode("MeshNode", mesh, material); + + setMaterialOptions(material); + + commandInterface.set(meshNode->getMaterialPrivateHandle(0), true); + + auto privateMaterialOptionsHandle = meshNode->getMaterialOptionsHandle(0); + + commandInterface.set(privateMaterialOptionsHandle.get("blendOperationColor"), 4); + commandInterface.set(privateMaterialOptionsHandle.get("blendOperationAlpha"), 1); + commandInterface.set(privateMaterialOptionsHandle.get("blendFactorSrcColor"), 3); + commandInterface.set(privateMaterialOptionsHandle.get("blendFactorDestColor"), 5); + commandInterface.set(privateMaterialOptionsHandle.get("blendFactorSrcAlpha"), 1); + commandInterface.set(privateMaterialOptionsHandle.get("blendFactorDestAlpha"), 0); + commandInterface.set(privateMaterialOptionsHandle.get("blendColor").get("x"), 0.1); + commandInterface.set(privateMaterialOptionsHandle.get("blendColor").get("y"), 0.3); + commandInterface.set(privateMaterialOptionsHandle.get("blendColor").get("z"), 0.5); + commandInterface.set(privateMaterialOptionsHandle.get("blendColor").get("w"), 0.6); + commandInterface.set(privateMaterialOptionsHandle.get("depthwrite"), true); + commandInterface.set(privateMaterialOptionsHandle.get("depthFunction"), 1); + commandInterface.set(privateMaterialOptionsHandle.get("cullmode"), 0); + + commandInterface.set(meshNode->getMaterialPrivateHandle(0), false); + commandInterface.set(meshNode->getMaterialPrivateHandle(0), true); + + privateMaterialOptionsHandle = meshNode->getMaterialOptionsHandle(0); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationColor").asInt(), 3); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationAlpha").asInt(), 2); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcColor").asInt(), 8); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestColor").asInt(), 10); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcAlpha").asInt(), 5); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestAlpha").asInt(), 4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("x").asDouble(), 0.2); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("y").asDouble(), 0.4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("z").asDouble(), 0.6); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("w").asDouble(), 0.8); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthwrite").asBool(), false); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthFunction").asInt(), 0); + ASSERT_EQ(privateMaterialOptionsHandle.get("cullmode").asInt(), 1); +} + +TEST_F(MeshNodeTest, private_material_options_do_not_get_updated_from_shared_material_to_active_private_material) { + auto material = create_material("Material", "shaders/basic.vert", "shaders/basic.frag"); + auto mesh = create_mesh("Mesh", "meshes/Duck.glb"); + auto meshNode = create_meshnode("MeshNode", mesh, material); + + setMaterialOptions(material); + + commandInterface.set(meshNode->getMaterialPrivateHandle(0), true); + + commandInterface.set({material, {"options", "blendOperationColor"}}, 4); + commandInterface.set({material, {"options", "blendOperationAlpha"}}, 1); + commandInterface.set({material, {"options", "blendFactorSrcColor"}}, 3); + commandInterface.set({material, {"options", "blendFactorDestColor"}}, 5); + commandInterface.set({material, {"options", "blendFactorSrcAlpha"}}, 1); + commandInterface.set({material, {"options", "blendFactorDestAlpha"}}, 0); + commandInterface.set({material, {"options", "blendColor", "x"}}, 0.1); + commandInterface.set({material, {"options", "blendColor", "y"}}, 0.3); + commandInterface.set({material, {"options", "blendColor", "z"}}, 0.5); + commandInterface.set({material, {"options", "blendColor", "w"}}, 0.6); + commandInterface.set({material, {"options", "depthwrite"}}, true); + commandInterface.set({material, {"options", "depthFunction"}}, 1); + commandInterface.set({material, {"options", "cullmode"}}, 0); + + auto privateMaterialOptionsHandle = meshNode->getMaterialOptionsHandle(0); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationColor").asInt(), 3); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendOperationAlpha").asInt(), 2); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcColor").asInt(), 8); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestColor").asInt(), 10); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorSrcAlpha").asInt(), 5); + ASSERT_EQ(privateMaterialOptionsHandle.get("blendFactorDestAlpha").asInt(), 4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("x").asDouble(), 0.2); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("y").asDouble(), 0.4); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("z").asDouble(), 0.6); + ASSERT_DOUBLE_EQ(privateMaterialOptionsHandle.get("blendColor").get("w").asDouble(), 0.8); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthwrite").asBool(), false); + ASSERT_EQ(privateMaterialOptionsHandle.get("depthFunction").asInt(), 0); + ASSERT_EQ(privateMaterialOptionsHandle.get("cullmode").asInt(), 1); +} \ No newline at end of file diff --git a/gui/libCommonWidgets/include/common_widgets/PreferencesView.h b/gui/libCommonWidgets/include/common_widgets/PreferencesView.h index 0f07cddc..4be74d3f 100644 --- a/gui/libCommonWidgets/include/common_widgets/PreferencesView.h +++ b/gui/libCommonWidgets/include/common_widgets/PreferencesView.h @@ -36,10 +36,6 @@ class PreferencesView final : public QDialog { private: QLineEdit* userProjectEdit_; - QLineEdit* imageEdit_; - QLineEdit* meshEdit_; - QLineEdit* scriptEdit_; - QLineEdit* shaderEdit_; }; } // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/src/ExportDialog.cpp b/gui/libCommonWidgets/src/ExportDialog.cpp index 766631d4..9b8e7115 100644 --- a/gui/libCommonWidgets/src/ExportDialog.cpp +++ b/gui/libCommonWidgets/src/ExportDialog.cpp @@ -13,6 +13,7 @@ #include "core/PathManager.h" #include "core/SceneBackendInterface.h" #include "utils/stdfilesystem.h" +#include "utils/u8path.h" #include #include @@ -82,17 +83,16 @@ ExportDialog::ExportDialog(const application::RaCoApplication* application, QWid compressEdit_->setChecked(true); if (application_->activeProjectPath().size() > 0) { - std::filesystem::path projectPath(application_->activeProjectPath()); - std::filesystem::path ramsesPath(projectPath); + auto projectPath = raco::utils::u8path(application_->activeProjectPath()); + auto ramsesPath = projectPath; ramsesPath.replace_extension(raco::names::FILE_EXTENSION_RAMSES_EXPORT); - std::filesystem::path logicPath(projectPath); + auto logicPath = projectPath; logicPath.replace_extension(raco::names::FILE_EXTENSION_LOGIC_EXPORT); pathEdit_->setText(QString::fromStdString(application_->activeProjectFolder())); - ramsesEdit_->setText(QString::fromStdString(ramsesPath.generic_string())); - logicEdit_->setText(QString::fromStdString(logicPath.generic_string())); + ramsesEdit_->setText(QString::fromStdString(ramsesPath.string())); + logicEdit_->setText(QString::fromStdString(logicPath.string())); } else { - std::error_code ec; - pathEdit_->setText(QString::fromStdString(std::filesystem::current_path(ec).generic_string())); + pathEdit_->setText(QString::fromStdString(raco::utils::u8path::current().string())); ramsesEdit_->setText(QString("unknown.").append(raco::names::FILE_EXTENSION_RAMSES_EXPORT)); logicEdit_->setText(QString("unknown.").append(raco::names::FILE_EXTENSION_LOGIC_EXPORT)); } @@ -149,21 +149,14 @@ ExportDialog::ExportDialog(const application::RaCoApplication* application, QWid void ExportDialog::exportProject() { std::string error; - std::filesystem::path dir{pathEdit_->text().toStdString()}; - std::filesystem::path ramsesFilePath = ramsesEdit_->text().toStdString(); - if (ramsesFilePath.is_relative()) { - ramsesFilePath = raco::core::PathManager::constructAbsolutePath(dir.string(), ramsesFilePath.string()); - } - - std::filesystem::path rlogicFilePath = logicEdit_->text().toStdString(); - if (rlogicFilePath.is_relative()) { - rlogicFilePath = raco::core::PathManager::constructAbsolutePath(dir.string(), rlogicFilePath.string()); - } + auto dir = pathEdit_->text().toStdString(); + auto ramsesFilePath = raco::utils::u8path(ramsesEdit_->text().toStdString()).normalizedAbsolutePath(dir); + auto rlogicFilePath = raco::utils::u8path(logicEdit_->text().toStdString()).normalizedAbsolutePath(dir); if (application_->exportProject(application_->activeRaCoProject(), - ramsesFilePath.generic_string(), - rlogicFilePath.generic_string(), + ramsesFilePath.string(), + rlogicFilePath.string(), compressEdit_->isChecked(), error)) { accept(); } else { diff --git a/gui/libCommonWidgets/src/PreferencesView.cpp b/gui/libCommonWidgets/src/PreferencesView.cpp index c95d9ca7..de55537e 100644 --- a/gui/libCommonWidgets/src/PreferencesView.cpp +++ b/gui/libCommonWidgets/src/PreferencesView.cpp @@ -9,10 +9,11 @@ */ #include "common_widgets/PreferencesView.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "common_widgets/PropertyBrowserButton.h" #include "core/PathManager.h" +#include "log_system/log.h" #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include @@ -59,26 +61,6 @@ PreferencesView::PreferencesView(QWidget* parent) : QDialog{parent} { }); } - imageEdit_ = new QLineEdit{this}; - formLayout->addRow("Image subdirectory", imageEdit_); - imageEdit_->setText(RaCoPreferences::instance().imageSubdirectory); - QObject::connect(imageEdit_, &QLineEdit::textChanged, this, [this](auto) { Q_EMIT dirtyChanged(dirty()); }); - - meshEdit_ = new QLineEdit{this}; - formLayout->addRow("Mesh subdirectory", meshEdit_); - meshEdit_->setText(RaCoPreferences::instance().meshSubdirectory); - QObject::connect(meshEdit_, &QLineEdit::textChanged, this, [this](auto) { Q_EMIT dirtyChanged(dirty()); }); - - scriptEdit_ = new QLineEdit{this}; - formLayout->addRow("Script subdirectory", scriptEdit_); - scriptEdit_->setText(RaCoPreferences::instance().scriptSubdirectory); - QObject::connect(scriptEdit_, &QLineEdit::textChanged, this, [this](auto) { Q_EMIT dirtyChanged(dirty()); }); - - shaderEdit_ = new QLineEdit{this}; - formLayout->addRow("Shader subdirectory", shaderEdit_); - shaderEdit_->setText(RaCoPreferences::instance().shaderSubdirectory); - QObject::connect(shaderEdit_, &QLineEdit::textChanged, this, [this](auto) { Q_EMIT dirtyChanged(dirty()); }); - auto buttonBox = new QDialogButtonBox{this}; auto cancelButton{new QPushButton{"Close", buttonBox}}; QObject::connect(cancelButton, &QPushButton::clicked, this, &PreferencesView::close); @@ -92,23 +74,19 @@ PreferencesView::PreferencesView(QWidget* parent) : QDialog{parent} { } void PreferencesView::save() { - if (raco::utils::path::isExistingDirectory(userProjectEdit_->text().toStdString())) { + if (raco::utils::u8path(userProjectEdit_->text().toStdString()).existsDirectory()) { RaCoPreferences::instance().userProjectsDirectory = userProjectEdit_->text(); } - RaCoPreferences::instance().imageSubdirectory = imageEdit_->text(); - RaCoPreferences::instance().meshSubdirectory = meshEdit_->text(); - RaCoPreferences::instance().scriptSubdirectory = scriptEdit_->text(); - RaCoPreferences::instance().shaderSubdirectory = shaderEdit_->text(); - RaCoPreferences::instance().save(); + if (!RaCoPreferences::instance().save()) { + LOG_ERROR(raco::log_system::COMMON, "Saving settings failed: {}", raco::core::PathManager::preferenceFilePath().string()); + QMessageBox::critical(this, "Saving settings failed", QString("Settings could not be saved. Check whether the application can write to its config directory.\nFile: ") + + QString::fromStdString(raco::core::PathManager::preferenceFilePath().string())); + } Q_EMIT dirtyChanged(false); } bool PreferencesView::dirty() { - return RaCoPreferences::instance().userProjectsDirectory != userProjectEdit_->text() || - RaCoPreferences::instance().imageSubdirectory != imageEdit_->text() || - RaCoPreferences::instance().meshSubdirectory != meshEdit_->text() || - RaCoPreferences::instance().scriptSubdirectory != scriptEdit_->text() || - RaCoPreferences::instance().shaderSubdirectory != shaderEdit_->text(); + return RaCoPreferences::instance().userProjectsDirectory != userProjectEdit_->text(); } } // namespace raco::common_widgets diff --git a/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h b/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h index b9c3b330..3f1aefc2 100644 --- a/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h +++ b/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h @@ -45,6 +45,7 @@ class ObjectTreeView : public QTreeView { QModelIndexList getSelectedIndices(bool sorted = false) const; QModelIndex getSelectedInsertionTargetIndex() const; + void collapseRecusively(const QModelIndex &index); Q_SIGNALS: void dockSelectionFocusRequested(ObjectTreeView *focusTree); @@ -60,6 +61,8 @@ public Q_SLOTS: void shortcutDelete(); void selectObject(const QString &objectID); void expandAllParentsOfObject(const QString &objectID); + void expanded(const QModelIndex &index); + void collapsed(const QModelIndex &index); protected: static inline auto SELECTION_MODE = QItemSelectionModel::Select | QItemSelectionModel::Rows; @@ -75,8 +78,9 @@ public Q_SLOTS: void dragMoveEvent(QDragMoveEvent *event) override; void mousePressEvent(QMouseEvent *event) override; - SEditorObject indexToSEditorObject(const QModelIndex &index) const; - QModelIndex indexFromObjectID(const std::string &id) const; + std::vector indicesToSEditorObjects(const QModelIndexList &index) const; + std::string indexToTreeNodeID(const QModelIndex &index) const; + QModelIndex indexFromTreeNodeID(const std::string &id) const; protected Q_SLOTS: void restoreItemExpansionStates(); diff --git a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeNode.h b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeNode.h index 97db58b3..d5c6549c 100644 --- a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeNode.h +++ b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeNode.h @@ -13,14 +13,24 @@ namespace raco::object_tree::model { +enum class ObjectTreeNodeType { + EditorObject, + ExternalProject, + ExtRefGroup, + Root +}; + class ObjectTreeNode { public: - explicit ObjectTreeNode(core::SEditorObject obj = core::SEditorObject(), ObjectTreeNode *parent = nullptr); + explicit ObjectTreeNode(core::SEditorObject obj, ObjectTreeNode *parent); + explicit ObjectTreeNode(ObjectTreeNodeType type, ObjectTreeNode *parent); + ~ObjectTreeNode(); ObjectTreeNode *getParent(); size_t childCount() const; void addChild(ObjectTreeNode *child); + void addChildFront(ObjectTreeNode *child); ptrdiff_t row() const; std::vector getChildren(); @@ -29,8 +39,19 @@ class ObjectTreeNode { void setParent(ObjectTreeNode *parent); + ObjectTreeNodeType getType() const; + std::string getID() const; + std::string getDisplayName() const; + std::string getDisplayType() const; + std::string getExternalProjectName() const; + std::string getExternalProjectPath() const; + void setBelongsToExternalProject(const std::string &path, const std::string &name); + protected: - ObjectTreeNode *parent_; + ObjectTreeNode *parent_; + ObjectTreeNodeType type_; + std::string externalProjectPath_ = ""; + std::string externalProjectName_ = ""; std::vector children_; core::SEditorObject representedObject_; }; diff --git a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h index 83094472..171f756e 100644 --- a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h +++ b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h @@ -36,13 +36,10 @@ Traversal through the entire tree is guaranteed by the iterateThroughTree() meth namespace raco::object_tree::model { -using ObjectFilterFunc = std::function(const std::vector&)>; -using ObjectTreeBuildFunc = std::function&)>; - class ObjectTreeViewDefaultModel : public QAbstractItemModel { DEBUG_INSTANCE_COUNTER(ObjectTreeViewDefaultModel); Q_OBJECT - + public: enum ColumnIndex { COLUMNINDEX_NAME, @@ -51,7 +48,8 @@ class ObjectTreeViewDefaultModel : public QAbstractItemModel { COLUMNINDEX_COLUMN_COUNT }; - ObjectTreeViewDefaultModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectStore, const std::vector &allowedCreatableUserTypes = {}); + ObjectTreeViewDefaultModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectStore, const std::vector &allowedCreatableUserTypes, + bool groupExternalReferences = false); int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; @@ -71,16 +69,12 @@ class ObjectTreeViewDefaultModel : public QAbstractItemModel { Qt::DropActions supportedDropActions() const override; virtual void buildObjectTree(); - virtual void setUpTreeModificationFunctions(); void iterateThroughTree(std::function nodeFunc, QModelIndex& currentIndex); ObjectTreeNode* indexToTreeNode(const QModelIndex& index) const; core::SEditorObject indexToSEditorObject(const QModelIndex& index) const; std::vector indicesToSEditorObjects(const QModelIndexList& indices) const; - QModelIndex indexFromObjectID(const std::string& id) const; - - void setProjectObjectFilterFunction(const ObjectFilterFunc& func); - void setTreeBuildingFunction(const ObjectTreeBuildFunc& func); + QModelIndex indexFromTreeNodeID(const std::string& id) const; core::UserObjectFactoryInterface* objectFactory(); core::Project* project() const; @@ -128,12 +122,14 @@ public Q_SLOTS: std::unordered_map> lifeCycleSubscriptions_; components::Subscription afterDispatchSubscription_; components::Subscription extProjectChangedSubscription_; + bool groupExternalReferences_; // The dirty flag is set if the tree needs to be rebuilt. See afterDispatchSubscription_ member variable usage. bool dirty_ = false; - ObjectFilterFunc objectFilterFunc_; - ObjectTreeBuildFunc treeBuildFunc_; + virtual std::vector filterForTopLevelObjects(const std::vector& objects) const; + virtual void setNodeExternalProjectInfo(ObjectTreeNode* node) const; + void constructTreeUnderNode(ObjectTreeNode* rootNode, const std::vector& children, bool groupExternalReferences); void resetInvisibleRootNode(); void updateTreeIndexes(); diff --git a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h index ae7b300d..860b0b06 100644 --- a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h +++ b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h @@ -18,27 +18,6 @@ class ObjectTreeViewExternalProjectModel : public ObjectTreeViewDefaultModel { Q_OBJECT public: - class ProjectNode : public raco::core::EditorObject { - public: - static inline const TypeDescriptor typeDescription{"Project", false}; - TypeDescriptor const& getTypeDescription() const override { - return typeDescription; - } - - ProjectNode(ProjectNode const& other) : EditorObject(other) { - fillPropertyDescription(); - } - - ProjectNode(const std::string& name, const std::string& id = std::string()) : EditorObject(name, id) { - fillPropertyDescription(); - } - - ProjectNode() : ProjectNode("Main") {} - - void fillPropertyDescription() { - } - }; - ObjectTreeViewExternalProjectModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectsStoreInterface); QVariant data(const QModelIndex& index, int role) const override; @@ -54,7 +33,7 @@ class ObjectTreeViewExternalProjectModel : public ObjectTreeViewDefaultModel { bool isObjectAllowedIntoIndex(const QModelIndex& index, const core::SEditorObject& obj) const override; bool canPasteIntoIndex(const QModelIndex& index, const std::vector& objects, const std::set& sourceProjectTopLevelObjectIds, bool asExtRef = false) const override; - + public Q_SLOTS: size_t deleteObjectsAtIndices(const QModelIndexList& index) override; void copyObjectsAtIndices(const QModelIndexList& indices, bool deepCopy) override; @@ -62,14 +41,14 @@ public Q_SLOTS: bool pasteObjectAtIndex(const QModelIndex& index, bool pasteAsExtref = false, std::string* outError = nullptr, const std::string& serializedObjects = RaCoClipboard::get()) override; protected: + void setNodeExternalProjectInfo(ObjectTreeNode* node) const override; void buildObjectTree() override; + std::vector filterForTopLevelObjects(const std::vector& objects) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; QMimeData* mimeData(const QModelIndexList& indexes) const override; - - std::string getOriginProjectPathOfSelectedIndex(const QModelIndex& index) const; - components::Subscription projectChangedSubscription_; }; diff --git a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewPrefabModel.h b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewPrefabModel.h index 7c400968..e7702e6b 100644 --- a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewPrefabModel.h +++ b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewPrefabModel.h @@ -20,8 +20,12 @@ class ObjectTreeViewPrefabModel : public ObjectTreeViewDefaultModel { public: ObjectTreeViewPrefabModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectsStore, const std::vector& allowedCreatableUserTypes = {}); + QVariant data(const QModelIndex& index, int role) const override; bool isObjectAllowedIntoIndex(const QModelIndex& index, const core::SEditorObject& obj) const override; std::vector typesAllowedIntoIndex(const QModelIndex& index) const override; + +protected: + std::vector filterForTopLevelObjects(const std::vector& objects) const override; }; } // namespace raco::object_tree::model \ No newline at end of file diff --git a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewResourceModel.h b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewResourceModel.h index d09a1522..8b897d91 100644 --- a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewResourceModel.h +++ b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewResourceModel.h @@ -18,6 +18,7 @@ class ObjectTreeViewResourceModel : public ObjectTreeViewDefaultModel { DEBUG_INSTANCE_COUNTER(ObjectTreeViewResourceModel); Q_OBJECT + public: ObjectTreeViewResourceModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectStore, const std::vector& allowedCreatableUserTypes = {}); diff --git a/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp b/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp index b043d7ca..9da6b23c 100644 --- a/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp +++ b/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp @@ -22,10 +22,11 @@ #include "object_tree_view_model/ObjectTreeViewExternalProjectModel.h" #include "object_tree_view_model/ObjectTreeViewPrefabModel.h" #include "object_tree_view_model/ObjectTreeViewResourceModel.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include #include +#include #include #include #include @@ -58,15 +59,9 @@ ObjectTreeView::ObjectTreeView(const QString &viewTitle, ObjectTreeViewDefaultMo setTextElideMode(treeModel_->textElideMode()); - connect(this, &ObjectTreeView::customContextMenuRequested, this, &ObjectTreeView::showContextMenu); - connect(this, &ObjectTreeView::expanded, [this](const QModelIndex &index) { - auto editorObj = indexToSEditorObject(index); - expandedItemIDs_.insert(editorObj->objectID()); - }); - connect(this, &ObjectTreeView::collapsed, [this](const QModelIndex &index) { - auto editorObj = indexToSEditorObject(index); - expandedItemIDs_.erase(editorObj->objectID()); - }); + connect(this, &QTreeView::customContextMenuRequested, this, &ObjectTreeView::showContextMenu); + connect(this, &QTreeView::expanded, this, &ObjectTreeView::expanded); + connect(this, &QTreeView::collapsed, this, &ObjectTreeView::collapsed); connect(this->selectionModel(), &QItemSelectionModel::selectionChanged, [this](const auto &selectedItemList, const auto &deselectedItemList) { if (auto externalProjectModel = (dynamic_cast(treeModel_))) { @@ -74,14 +69,14 @@ ObjectTreeView::ObjectTreeView(const QString &viewTitle, ObjectTreeViewDefaultMo return; } - for (const auto &selectedItemIndex : selectedItemList.indexes()) { - auto selObj = indexToSEditorObject(selectedItemIndex); + auto selectedObjects = indicesToSEditorObjects(selectedItemList.indexes()); + for (const auto &selObj : selectedObjects) { selectedItemIDs_.emplace(selObj->objectID()); } - for (const auto &deselectedItem : deselectedItemList.indexes()) { - auto selObj = indexToSEditorObject(deselectedItem); - selectedItemIDs_.erase(selObj->objectID()); + auto deselectedObjects = indicesToSEditorObjects(deselectedItemList.indexes()); + for (const auto &deselObj : deselectedObjects) { + selectedItemIDs_.erase(deselObj->objectID()); } Q_EMIT newObjectTreeItemsSelected(getSelectedHandles()); @@ -99,14 +94,9 @@ ObjectTreeView::ObjectTreeView(const QString &viewTitle, ObjectTreeViewDefaultMo } std::set ObjectTreeView::getSelectedHandles() const { - std::set handles; - - for (const auto &selectedItemIndex : selectionModel()->selectedIndexes()) { - auto selObj = indexToSEditorObject(selectedItemIndex); - handles.emplace(selObj); - } + auto selectedObjects = indicesToSEditorObjects(selectionModel()->selectedIndexes()); - return handles; + return std::set(selectedObjects.begin(), selectedObjects.end()); } void ObjectTreeView::globalCopyCallback() { @@ -135,7 +125,7 @@ void ObjectTreeView::selectObject(const QString &objectID) { return; } - auto objectIndex = indexFromObjectID(objectID.toStdString()); + auto objectIndex = indexFromTreeNodeID(objectID.toStdString()); if (objectIndex.isValid()) { resetSelection(); selectionModel()->select(objectIndex, SELECTION_MODE); @@ -144,12 +134,36 @@ void ObjectTreeView::selectObject(const QString &objectID) { } void ObjectTreeView::expandAllParentsOfObject(const QString &objectID) { - auto objectIndex = indexFromObjectID(objectID.toStdString()); + auto objectIndex = indexFromTreeNodeID(objectID.toStdString()); if (objectIndex.isValid()) { expandAllParentsOfObject(objectIndex); } } +void ObjectTreeView::expanded(const QModelIndex &index) { + expandedItemIDs_.insert(indexToTreeNodeID(index)); + + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::KeyboardModifier::ShiftModifier)) { + expandRecursively(index); + } +} + +void ObjectTreeView::collapsed(const QModelIndex &index) { + expandedItemIDs_.erase(indexToTreeNodeID(index)); + + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::KeyboardModifier::ShiftModifier)) { + collapseRecusively(index); + } +} + +void ObjectTreeView::collapseRecusively(const QModelIndex& index) { + collapse(index); + + for (int i = 0; i < index.model()->rowCount(index); ++i) { + collapseRecusively(index.model()->index(i, 0, index)); + } +} + void ObjectTreeView::cut() { auto selectedIndices = getSelectedIndices(true); if (!selectedIndices.isEmpty()) { @@ -160,6 +174,8 @@ void ObjectTreeView::cut() { void ObjectTreeView::globalPasteCallback(const QModelIndex &index, bool asExtRef) { if (canPasteIntoIndex(index, asExtRef)) { treeModel_->pasteObjectAtIndex(index, asExtRef); + } else if (canPasteIntoIndex({}, asExtRef)) { + treeModel_->pasteObjectAtIndex({}, asExtRef); } } @@ -237,7 +253,7 @@ QMenu* ObjectTreeView::createCustomContextMenu(const QPoint &p) { auto actionImport = treeViewMenu->addAction("Import glTF Assets...", [this, insertionTargetIndex]() { auto sceneFolder = raco::core::PathManager::getCachedPath(raco::core::PathManager::FolderTypeKeys::Mesh, treeModel_->project()->currentFolder()); - auto file = QFileDialog::getOpenFileName(this, "Load Asset File", QString::fromStdString(sceneFolder), "glTF files (*.gltf *.glb)"); + auto file = QFileDialog::getOpenFileName(this, "Load Asset File", QString::fromStdString(sceneFolder.string()), "glTF files (*.gltf *.glb)"); if (!file.isEmpty()) { treeModel_->importMeshScenegraph(file, insertionTargetIndex); } @@ -357,16 +373,28 @@ void ObjectTreeView::mousePressEvent(QMouseEvent *event) { } } -core::SEditorObject ObjectTreeView::indexToSEditorObject(const QModelIndex &index) const { +std::vector ObjectTreeView::indicesToSEditorObjects(const QModelIndexList &indices) const { + QModelIndexList itemIndices; + if (proxyModel_) { + for (const auto &index : indices) { + itemIndices.append(proxyModel_->mapToSource(index)); + } + } else { + itemIndices = indices; + } + return treeModel_->indicesToSEditorObjects(itemIndices); +} + +std::string ObjectTreeView::indexToTreeNodeID(const QModelIndex &index) const { auto itemIndex = index; if (proxyModel_) { itemIndex = proxyModel_->mapToSource(index); } - return treeModel_->indexToSEditorObject(itemIndex); + return treeModel_->indexToTreeNode(itemIndex)->getID(); } -QModelIndex ObjectTreeView::indexFromObjectID(const std::string &id) const { - auto index = treeModel_->indexFromObjectID(id); +QModelIndex ObjectTreeView::indexFromTreeNodeID(const std::string &id) const { + auto index = treeModel_->indexFromTreeNodeID(id); if (proxyModel_) { index = proxyModel_->mapFromSource(index); } @@ -419,7 +447,7 @@ QModelIndex ObjectTreeView::getSelectedInsertionTargetIndex() const { void ObjectTreeView::restoreItemExpansionStates() { for (const auto &expandedObjectID : expandedItemIDs_) { - auto expandedObjectIndex = indexFromObjectID(expandedObjectID); + auto expandedObjectIndex = indexFromTreeNodeID(expandedObjectID); if (expandedObjectIndex.isValid()) { blockSignals(true); expand(expandedObjectIndex); @@ -435,7 +463,7 @@ void ObjectTreeView::restoreItemSelectionStates() { auto selectionIt = selectedItemIDs_.begin(); while (selectionIt != selectedItemIDs_.end()) { const auto &selectionID = *selectionIt; - auto selectedObjectIndex = indexFromObjectID(selectionID); + auto selectedObjectIndex = indexFromTreeNodeID(selectionID); if (selectedObjectIndex.isValid()) { selectionModel()->select(selectedObjectIndex, SELECTION_MODE); selectedObjects.emplace_back(selectedObjectIndex); diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeNode.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeNode.cpp index 525c9ad5..e881b3f2 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeNode.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeNode.cpp @@ -10,6 +10,8 @@ #include "object_tree_view_model/ObjectTreeNode.h" #include +#include +#include namespace raco::object_tree::model { @@ -17,12 +19,25 @@ using namespace raco::core; ObjectTreeNode::ObjectTreeNode(SEditorObject obj, ObjectTreeNode* parent) : parent_{parent}, + type_{ObjectTreeNodeType::EditorObject}, representedObject_{obj} { if (parent_) { parent_->addChild(this); } } +ObjectTreeNode::ObjectTreeNode(ObjectTreeNodeType type, ObjectTreeNode* parent) + : parent_{parent}, + type_{type}, + representedObject_{nullptr} { + assert(type != ObjectTreeNodeType::EditorObject); + + if (parent_) { + assert(type != ObjectTreeNodeType::Root); + parent_->addChild(this); + } +} + ObjectTreeNode::~ObjectTreeNode() { for (auto child : children_) { delete child; @@ -39,7 +54,12 @@ size_t ObjectTreeNode::childCount() const { void ObjectTreeNode::addChild(ObjectTreeNode* child) { child->setParent(this); - children_.emplace_back(child); + children_.push_back(child); +} + +void ObjectTreeNode::addChildFront(ObjectTreeNode* child) { + child->setParent(this); + children_.insert(children_.begin(), child); } ptrdiff_t ObjectTreeNode::row() const { @@ -64,6 +84,62 @@ ObjectTreeNode* ObjectTreeNode::getChild(int row) { return nullptr; } +ObjectTreeNodeType ObjectTreeNode::getType() const { + return type_; +} + +std::string ObjectTreeNode::getDisplayName() const { + switch (type_) { + case ObjectTreeNodeType::EditorObject: + return representedObject_->objectName(); + case ObjectTreeNodeType::ExternalProject: + return externalProjectPath_; + case ObjectTreeNodeType::ExtRefGroup: + return "External References"; + default: + return ""; + } +} +std::string ObjectTreeNode::getDisplayType() const { + switch (type_) { + case ObjectTreeNodeType::EditorObject: + return representedObject_->getTypeDescription().typeName; + case ObjectTreeNodeType::ExternalProject: + return "Project"; + default: + return ""; + } +} + +std::string ObjectTreeNode::getExternalProjectPath() const { + return externalProjectPath_; +} + +std::string ObjectTreeNode::getExternalProjectName() const { + return externalProjectName_; +} + +void ObjectTreeNode::setBelongsToExternalProject(const std::string& path, const std::string& name) { + assert(type_ == ObjectTreeNodeType::EditorObject || type_ == ObjectTreeNodeType::ExternalProject); + externalProjectPath_ = path; + externalProjectName_ = name; +} + +std::string ObjectTreeNode::getID() const { + switch (type_) { + case ObjectTreeNodeType::EditorObject: + return representedObject_->objectID(); + case ObjectTreeNodeType::ExternalProject: + return externalProjectPath_; + case ObjectTreeNodeType::ExtRefGroup: + return "External References"; + case ObjectTreeNodeType::Root: + return "Root"; + default: + return "INVALID"; + } +} + SEditorObject ObjectTreeNode::getRepresentedObject() const { return representedObject_; } diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp index f5bb6ba7..b801881f 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp @@ -38,13 +38,17 @@ namespace raco::object_tree::model { using namespace raco::core; using namespace raco::style; -ObjectTreeViewDefaultModel::ObjectTreeViewDefaultModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectStore, const std::vector& allowedCreatableUserTypes) +ObjectTreeViewDefaultModel::ObjectTreeViewDefaultModel(raco::core::CommandInterface* commandInterface, + components::SDataChangeDispatcher dispatcher, + core::ExternalProjectsStoreInterface* externalProjectStore, + const std::vector& allowedCreatableUserTypes, + bool groupExternalReferences) : dispatcher_{dispatcher}, commandInterface_{commandInterface}, externalProjectStore_{externalProjectStore}, - allowedUserCreatableUserTypes_(allowedCreatableUserTypes) { + allowedUserCreatableUserTypes_(allowedCreatableUserTypes), + groupExternalReferences_(groupExternalReferences) { resetInvisibleRootNode(); - ObjectTreeViewDefaultModel::setUpTreeModificationFunctions(); lifeCycleSubscriptions_["objectLifecycle"].emplace_back(dispatcher_->registerOnObjectsLifeCycle( [this](auto sEditorObject) { dirty_ = true; }, @@ -75,46 +79,43 @@ int ObjectTreeViewDefaultModel::columnCount(const QModelIndex& parent) const { return COLUMNINDEX_COLUMN_COUNT; } + QVariant ObjectTreeViewDefaultModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } - switch (auto editorObj = indexToSEditorObject(index); role) { + auto treeNode = indexToTreeNode(index); + + switch (role) { case Qt::ItemDataRole::DecorationRole: { - switch (index.column()) { - case COLUMNINDEX_NAME: - if (editorObj->query() && editorObj->as()) { - return QVariant(Icons::icon(typeIconMap.at("ExtrefPrefab"))); - } else { - auto itr = typeIconMap.find(editorObj->getTypeDescription().typeName); - if (itr == typeIconMap.end()) - return QVariant(); - return QVariant(Icons::icon(itr->second)); - } + + auto editorObj = treeNode->getRepresentedObject(); + if (editorObj && index.column() == COLUMNINDEX_NAME) { + auto itr = typeIconMap.find(editorObj->getTypeDescription().typeName); + if (itr == typeIconMap.end()) + return QVariant(); + return QVariant(Icons::icon(itr->second)); } - return QVariant(QIcon()); + return QVariant(); } case Qt::ForegroundRole: { - if (editorObj->query()) { + auto editorObj = treeNode->getRepresentedObject(); + if (editorObj && editorObj->query() || treeNode->getType() == ObjectTreeNodeType::ExtRefGroup) { return QVariant(Colors::color(Colormap::externalReference)); - } else if (Queries::isReadOnly(editorObj)) { + } else if (editorObj && Queries::isReadOnly(editorObj)) { return QVariant(Colors::color(Colormap::textDisabled)); - } else { - return QVariant(Colors::color(Colormap::text)); } + return QVariant(Colors::color(Colormap::text)); } case Qt::ItemDataRole::DisplayRole: { switch (index.column()) { case COLUMNINDEX_NAME: - return QVariant(QString::fromStdString(editorObj->objectName())); + return QVariant(QString::fromStdString(treeNode->getDisplayName())); case COLUMNINDEX_TYPE: - return QVariant(QString::fromStdString(editorObj->getTypeDescription().typeName)); + return QVariant(QString::fromStdString(treeNode->getDisplayType())); case COLUMNINDEX_PROJECT: { - if (auto extrefAnno = editorObj->query()) { - return QVariant(QString::fromStdString(project()->lookupExternalProjectName(*extrefAnno->projectID_))); - } - return QVariant(); + return QVariant(QString::fromStdString(treeNode->getExternalProjectName())); } } } @@ -167,8 +168,10 @@ bool ObjectTreeViewDefaultModel::canDropMimeData(const QMimeData* data, Qt::Drop auto originPath = getOriginPathFromMimeData(data); auto droppingFromOtherProject = originPath != project()->currentPath(); - auto droppingAsExternalReference = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::KeyboardModifier::AltModifier); - if (droppingAsExternalReference && parent.isValid()) { + auto parentType = indexToTreeNode(parent)->getType(); + auto droppingAsExternalReference = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::KeyboardModifier::AltModifier) || + parentType == ObjectTreeNodeType::ExtRefGroup; + if (droppingAsExternalReference && (!droppingFromOtherProject || parentType == ObjectTreeNodeType::EditorObject)) { return false; } @@ -227,11 +230,12 @@ QMimeData* raco::object_tree::model::ObjectTreeViewDefaultModel::generateMimeDat LOG_TRACE(log_system::OBJECT_TREE_VIEW, "Start - Creating mime data of size {}", indexes.size()); stream << QString::fromStdString(originPath); for (const auto& index : indexes) { - if (index.isValid() && index.column() == COLUMNINDEX_NAME) { - auto obj = indexToSEditorObject(index); - // Object ID - stream << QString::fromStdString(obj->objectID()); - LOG_TRACE(log_system::OBJECT_TREE_VIEW, "Add - {}", obj->objectID()); + if (index.column() == COLUMNINDEX_NAME) { + if (auto obj = indexToSEditorObject(index)) { + // Object ID + stream << QString::fromStdString(obj->objectID()); + LOG_TRACE(log_system::OBJECT_TREE_VIEW, "Add - {}", obj->objectID()); + } } } LOG_TRACE(log_system::OBJECT_TREE_VIEW, "End - Creating mime data"); @@ -273,10 +277,6 @@ bool ObjectTreeViewDefaultModel::dropMimeData(const QMimeData* data, Qt::DropAct auto movedItemIDs = decodeMimeData(data); if (mimeDataContainsLocalInstances) { - SEditorObject parentObj; - if (parent.isValid()) { - parentObj = indexToSEditorObject(parent); - } std::vector objs; for (const auto& movedItemID : movedItemIDs) { if (auto childObj = project()->getInstanceByID(movedItemID.toStdString())) { @@ -284,7 +284,7 @@ bool ObjectTreeViewDefaultModel::dropMimeData(const QMimeData* data, Qt::DropAct } } - moveScenegraphChildren(objs, parentObj, row); + moveScenegraphChildren(objs, indexToSEditorObject(parent), row); } else { auto originCommandInterface = externalProjectStore_->getExternalProjectCommandInterface(originPath); std::vector objs; @@ -296,7 +296,8 @@ bool ObjectTreeViewDefaultModel::dropMimeData(const QMimeData* data, Qt::DropAct auto serializedObjects = originCommandInterface->copyObjects(objs, true); auto pressedKeys = QGuiApplication::queryKeyboardModifiers(); - pasteObjectAtIndex(parent, pressedKeys.testFlag(Qt::KeyboardModifier::AltModifier), nullptr, serializedObjects); + auto pasteAsExtRef = pressedKeys.testFlag(Qt::KeyboardModifier::AltModifier) || indexToTreeNode(parent)->getType() == ObjectTreeNodeType::ExtRefGroup; + pasteObjectAtIndex(parent, pasteAsExtRef, nullptr, serializedObjects); } return true; @@ -305,7 +306,7 @@ bool ObjectTreeViewDefaultModel::dropMimeData(const QMimeData* data, Qt::DropAct Qt::ItemFlags ObjectTreeViewDefaultModel::flags(const QModelIndex& index) const { Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); - if (index.isValid()) { + if (auto obj = indexToSEditorObject(index); obj && !obj->query()) { return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; } else { return Qt::ItemIsDropEnabled | defaultFlags; @@ -339,72 +340,70 @@ void ObjectTreeViewDefaultModel::buildObjectTree() { })); } - auto& allEditorObjects = project()->instances(); - auto filteredEditorObjects = objectFilterFunc_(allEditorObjects); + auto filteredEditorObjects = filterForTopLevelObjects(project()->instances()); beginResetModel(); resetInvisibleRootNode(); - treeBuildFunc_(invisibleRootNode_.get(), filteredEditorObjects); + constructTreeUnderNode(invisibleRootNode_.get(), filteredEditorObjects, groupExternalReferences_); updateTreeIndexes(); endResetModel(); } -void ObjectTreeViewDefaultModel::setUpTreeModificationFunctions() { - setProjectObjectFilterFunction([](const auto& editorObjVec) { - return editorObjVec; - }); +void ObjectTreeViewDefaultModel::setNodeExternalProjectInfo(ObjectTreeNode* node) const { + if (auto obj = node->getRepresentedObject()) { + if (auto extrefAnno = obj->query()) { + node->setBelongsToExternalProject( + project()->lookupExternalProjectPath(*extrefAnno->projectID_), + project()->lookupExternalProjectName(*extrefAnno->projectID_)); + } + } +} - setTreeBuildingFunction([this](auto* rootPtr, const auto& filteredEditorObjVec) { - std::unordered_map nodeCache; - std::vector sceneGraphNodes(filteredEditorObjVec.size()); +void ObjectTreeViewDefaultModel::constructTreeUnderNode(ObjectTreeNode* rootNode, const std::vector& children, bool groupExternalReferences) { - for (auto i = 0U; i < sceneGraphNodes.size(); ++i) { - auto currentEditorObj = filteredEditorObjVec[i]; - sceneGraphNodes[i] = new ObjectTreeNode(filteredEditorObjVec[i]); - nodeCache[currentEditorObj->objectID()] = sceneGraphNodes[i]; - } + auto rootObject = rootNode->getRepresentedObject(); + auto extrefParent = groupExternalReferences ? nullptr : rootNode; - for (auto i = 0U; i < sceneGraphNodes.size(); ++i) { - auto currentObj = filteredEditorObjVec[i]; - auto node = nodeCache[currentObj->objectID()]; + for (const auto& obj : children) { + auto parentNode = rootNode; - if (!currentObj->getParent()) { - rootPtr->addChild(node); - } - Property objChildren = currentObj->children_; - auto childrenVec = objChildren->asVector(); - for (const auto& childObj : childrenVec) { - node->addChild(nodeCache[childObj->objectID()]); + if (obj->query()) { + if (!extrefParent) { + extrefParent = new ObjectTreeNode(ObjectTreeNodeType::ExtRefGroup, nullptr); + rootNode->addChildFront(extrefParent); } + parentNode = extrefParent; } - }); + + auto node = new ObjectTreeNode(obj, parentNode); + setNodeExternalProjectInfo(node); + constructTreeUnderNode(node, obj->children_->asVector(), false); + } +} + +std::vector ObjectTreeViewDefaultModel::filterForTopLevelObjects(const std::vector& objects) const { + return Queries::filterForTopLevelObjectsByTypeName(objects, allowedUserCreatableUserTypes_); } SEditorObject ObjectTreeViewDefaultModel::createNewObject(const EditorObject::TypeDescriptor& typeDesc, const std::string& nodeName, const QModelIndex& parent) { + SEditorObject parentObj = indexToSEditorObject(parent); + std::vector nodes; - if (!parent.isValid()) { - std::copy_if(project()->instances().begin(), project()->instances().end(), std::back_inserter(nodes), [](const SEditorObject& obj) { return obj->getParent() == nullptr; }); - } else { - std::copy_if(project()->instances().begin(), project()->instances().end(), std::back_inserter(nodes), [this, parent](const SEditorObject& obj) { - if (parent.isValid()) { - return obj->getParent() == indexToSEditorObject(parent); - } else { - return false; - } - }); - } - auto name = project()->findAvailableUniqueName(nodes.begin(), nodes.end(), nullptr, nodeName.empty() ? raco::components::Naming::format(typeDesc.typeName) : nodeName); + std::copy_if(project()->instances().begin(), project()->instances().end(), std::back_inserter(nodes), [this, parentObj](const SEditorObject& obj) { + return obj->getParent() == parentObj; + }); - auto newObj = commandInterface_->createObject(typeDesc.typeName, name, std::string(), parent.isValid() ? indexToSEditorObject(parent) : nullptr); + auto name = project()->findAvailableUniqueName(nodes.begin(), nodes.end(), nullptr, nodeName.empty() ? raco::components::Naming::format(typeDesc.typeName) : nodeName); + auto newObj = commandInterface_->createObject(typeDesc.typeName, name, std::string(), parent.isValid() ? parentObj : nullptr); return newObj; } bool ObjectTreeViewDefaultModel::canCopyAtIndices(const QModelIndexList& indices) const { for (const auto& index : indices) { - if (index.isValid()) { + if (indexToSEditorObject(index)) { return true; } } @@ -416,26 +415,26 @@ bool ObjectTreeViewDefaultModel::canDeleteAtIndices(const QModelIndexList& indic } bool ObjectTreeViewDefaultModel::isObjectAllowedIntoIndex(const QModelIndex& index, const core::SEditorObject& obj) const { - if (index.isValid() && !core::Queries::canPasteIntoObject(*commandInterface_->project(), indexToSEditorObject(index))) { + if (auto parentObj = indexToSEditorObject(index); parentObj && !core::Queries::canPasteIntoObject(*commandInterface_->project(), parentObj)) { return false; - } else { - auto types = typesAllowedIntoIndex(index); - - return std::find(types.begin(), types.end(), obj->getTypeDescription().typeName) != types.end(); } + + auto types = typesAllowedIntoIndex(index); + + return std::find(types.begin(), types.end(), obj->getTypeDescription().typeName) != types.end(); } std::pair, std::set> ObjectTreeViewDefaultModel::getObjectsAndRootIdsFromClipboardString(const std::string& serializedObjs) const { - auto deserialization{raco::serialization::deserializeObjects(serializedObjs, - raco::user_types::UserObjectFactoryInterface::deserializationFactory(commandInterface_->objectFactory()))}; - auto objects = BaseContext::getTopLevelObjectsFromDeserializedObjects(deserialization, commandInterface_->objectFactory(), project()); + auto deserialization{raco::serialization::deserializeObjects(serializedObjs)}; + auto objects = BaseContext::getTopLevelObjectsFromDeserializedObjects(deserialization, project()); return {objects, deserialization.rootObjectIDs}; } bool ObjectTreeViewDefaultModel::canPasteIntoIndex(const QModelIndex& index, const std::vector& objects, const std::set& sourceProjectTopLevelObjectIds, bool asExtRef) const { if (asExtRef) { - if (index.isValid()) { + // Only allow top level extref pasting + if (indexToSEditorObject(index)) { return false; } @@ -501,7 +500,7 @@ void ObjectTreeViewDefaultModel::importMeshScenegraph(const QString& filePath, c meshDesc.absPath = filePath.toStdString(); meshDesc.bakeAllSubmeshes = false; - auto selectedObject = selectedIndex.isValid() ? indexToSEditorObject(selectedIndex) : nullptr; + auto selectedObject = indexToSEditorObject(selectedIndex); if (auto sceneGraph = commandInterface_->meshCache()->getMeshScenegraph(meshDesc)) { auto importStatus = raco::common_widgets::MeshAssetImportDialog(*sceneGraph, nullptr).exec(); @@ -547,17 +546,14 @@ SEditorObject ObjectTreeViewDefaultModel::indexToSEditorObject(const QModelIndex std::vector ObjectTreeViewDefaultModel::indicesToSEditorObjects(const QModelIndexList& indices) const { std::vector objects; for (const auto& index : indices) { - if (index.isValid()) { - auto obj = indexToSEditorObject(index); - if (obj) { - objects.push_back(indexToSEditorObject(index)); - } + if (auto obj = indexToSEditorObject(index); obj) { + objects.push_back(obj); } } return objects; } -QModelIndex ObjectTreeViewDefaultModel::indexFromObjectID(const std::string& id) const { +QModelIndex ObjectTreeViewDefaultModel::indexFromTreeNodeID(const std::string& id) const { auto cachedID = indexes_.find(id); if (cachedID != indexes_.end()) { return cachedID->second; @@ -566,22 +562,14 @@ QModelIndex ObjectTreeViewDefaultModel::indexFromObjectID(const std::string& id) return QModelIndex(); } -void ObjectTreeViewDefaultModel::setProjectObjectFilterFunction(const ObjectFilterFunc& func) { - objectFilterFunc_ = func; -} - -void ObjectTreeViewDefaultModel::setTreeBuildingFunction(const ObjectTreeBuildFunc& func) { - treeBuildFunc_ = func; -} - void ObjectTreeViewDefaultModel::resetInvisibleRootNode() { - invisibleRootNode_ = std::make_unique(nullptr); + invisibleRootNode_ = std::make_unique(ObjectTreeNodeType::Root, nullptr); } void raco::object_tree::model::ObjectTreeViewDefaultModel::updateTreeIndexes() { indexes_.clear(); iterateThroughTree([&](const auto& modelIndex) { - indexes_[indexToSEditorObject(modelIndex)->objectID()] = modelIndex; + indexes_[indexToTreeNode(modelIndex)->getID()] = modelIndex; }, invisibleRootIndex_); } @@ -614,7 +602,7 @@ bool ObjectTreeViewDefaultModel::isIndexAboveInHierachyOrPosition(QModelIndex le } std::vector raco::object_tree::model::ObjectTreeViewDefaultModel::typesAllowedIntoIndex(const QModelIndex& index) const { - if (index.isValid() && !core::Queries::canPasteIntoObject(*commandInterface_->project(), indexToSEditorObject(index))) { + if (!core::Queries::canPasteIntoObject(*commandInterface_->project(), indexToSEditorObject(index))) { return {}; } else { return allowedUserCreatableUserTypes_; diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp index 304b7b97..ec8e4f33 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp @@ -14,13 +14,15 @@ #include "core/Project.h" #include "core/CommandInterface.h" +#include "core/Queries.h" +#include "core/ExternalReferenceAnnotation.h" #include namespace raco::object_tree::model { ObjectTreeViewExternalProjectModel::ObjectTreeViewExternalProjectModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectsStore) - : ObjectTreeViewDefaultModel(commandInterface, dispatcher, externalProjectsStore) { + : ObjectTreeViewDefaultModel(commandInterface, dispatcher, externalProjectsStore, {}, true) { // don't rebuild tree when creating/deleting local objects lifeCycleSubscriptions_.clear(); @@ -32,18 +34,21 @@ QVariant ObjectTreeViewExternalProjectModel::data(const QModelIndex& index, int return QVariant(); } - auto editorObj = indexToSEditorObject(index); - if (editorObj->as()) { + auto treeNode = indexToTreeNode(index); + if (treeNode->getType() == ObjectTreeNodeType::ExternalProject) { if (role == Qt::ForegroundRole) { - if (commandInterface_->project()->usesExternalProjectByPath(editorObj->objectName())) { + if (commandInterface_->project()->usesExternalProjectByPath(indexToTreeNode(index)->getExternalProjectPath())) { return QVariant(raco::style::Colors::color(raco::style::Colormap::externalReference)); + } else { + return QVariant(raco::style::Colors::color(raco::style::Colormap::text)); } + } if (role == Qt::ItemDataRole::DecorationRole && index.column() == COLUMNINDEX_NAME) { bool failed = false; - auto originPath = getOriginProjectPathOfSelectedIndex(index); + auto originPath = treeNode->getExternalProjectPath(); auto* originCommandInterface = externalProjectStore_->getExternalProjectCommandInterface(originPath); if (originCommandInterface) { failed = originCommandInterface->project()->externalReferenceUpdateFailed(); @@ -57,19 +62,10 @@ QVariant ObjectTreeViewExternalProjectModel::data(const QModelIndex& index, int } } - if (role == Qt::ItemDataRole::DisplayRole && index.column() == COLUMNINDEX_PROJECT) { - auto originPath = getOriginProjectPathOfSelectedIndex(index); - auto* originCommandInterface = externalProjectStore_->getExternalProjectCommandInterface(originPath); - if (originCommandInterface) { - auto editorObj = indexToSEditorObject(index); - return QVariant(QString::fromStdString(originCommandInterface->project()->getProjectNameForObject(editorObj))); - } - return QVariant(QString()); - } return ObjectTreeViewDefaultModel::data(index, role); } -void raco::object_tree::model::ObjectTreeViewExternalProjectModel::addProject(const QString& projectPath) { +void ObjectTreeViewExternalProjectModel::addProject(const QString& projectPath) { std::vector stack; stack.emplace_back(commandInterface_->project()->currentPath()); if (externalProjectStore_->addExternalProject(projectPath.toStdString(), stack)) { @@ -77,10 +73,10 @@ void raco::object_tree::model::ObjectTreeViewExternalProjectModel::addProject(co } } -void raco::object_tree::model::ObjectTreeViewExternalProjectModel::removeProjectsAtIndices(const QModelIndexList& indices) { +void ObjectTreeViewExternalProjectModel::removeProjectsAtIndices(const QModelIndexList& indices) { std::set projectsToRemove; for (const auto& index : indices) { - projectsToRemove.emplace(getOriginProjectPathOfSelectedIndex(index)); + projectsToRemove.emplace(indexToTreeNode(index)->getExternalProjectPath()); } for (const auto& projectPath : projectsToRemove) { @@ -88,7 +84,7 @@ void raco::object_tree::model::ObjectTreeViewExternalProjectModel::removeProject } } -bool raco::object_tree::model::ObjectTreeViewExternalProjectModel::canRemoveProjectsAtIndices(const QModelIndexList& indices) { +bool ObjectTreeViewExternalProjectModel::canRemoveProjectsAtIndices(const QModelIndexList& indices) { if (indices.isEmpty()) { return false; } @@ -98,7 +94,7 @@ bool raco::object_tree::model::ObjectTreeViewExternalProjectModel::canRemoveProj return false; } - auto projectPath = getOriginProjectPathOfSelectedIndex(index); + auto projectPath = indexToTreeNode(index)->getExternalProjectPath(); if (!externalProjectStore_->canRemoveExternalProject(projectPath)) { return false; } @@ -113,15 +109,14 @@ void ObjectTreeViewExternalProjectModel::copyObjectsAtIndices(const QModelIndexL std::vector objects; for (const auto& index : indices) { - auto object = indexToSEditorObject(index); - if (&object->getTypeDescription() != &ProjectNode::typeDescription) { - - objects.push_back(indexToSEditorObject(index)); + auto treeNode = indexToTreeNode(index); + auto object = treeNode->getRepresentedObject(); + if (object) { + objects.push_back(object); // canCopyAtIndices already enforces that we only copy objects from the same project, so we can just take the project path from the first index if (!commandInterface) { - const auto& index = indices.front(); - auto originPath = getOriginProjectPathOfSelectedIndex(index); + auto originPath = treeNode->getExternalProjectPath(); commandInterface = externalProjectStore_->getExternalProjectCommandInterface(originPath); } } @@ -132,17 +127,41 @@ void ObjectTreeViewExternalProjectModel::copyObjectsAtIndices(const QModelIndexL } } -void raco::object_tree::model::ObjectTreeViewExternalProjectModel::buildObjectTree() { +void ObjectTreeViewExternalProjectModel::setNodeExternalProjectInfo(ObjectTreeNode* node) const { + auto parent = node; + + while (parent->getRepresentedObject() || parent->getType() == ObjectTreeNodeType::ExtRefGroup) { + parent = parent->getParent(); + } + + assert(parent->getType() == ObjectTreeNodeType::ExternalProject); + + auto projectPath = parent->getExternalProjectPath(); + auto projectName = parent->getExternalProjectName(); + if (auto obj = node->getRepresentedObject()) { + if (auto extrefAnno = obj->query()) { + if (auto originCommandInterface = externalProjectStore_->getExternalProjectCommandInterface(projectPath)) { + auto project = originCommandInterface->project(); + // Keep the project path from the root project, even in case of extrefs. + // The utility functions in Context ensure proper copy paste behaviour for these. + projectName = project->lookupExternalProjectName(*extrefAnno->projectID_); + } + } + } + node->setBelongsToExternalProject(projectPath, projectName); +} + +void ObjectTreeViewExternalProjectModel::buildObjectTree() { beginResetModel(); resetInvisibleRootNode(); for (const auto& [projectPath, commandInterface] : externalProjectStore_->allExternalProjects()) { - auto projectObj = std::make_shared(projectPath, projectPath); - auto projectRootNode = new ObjectTreeNode(projectObj); - invisibleRootNode_->addChild(projectRootNode); + auto projectRootNode = new ObjectTreeNode(ObjectTreeNodeType::ExternalProject, invisibleRootNode_.get()); + auto projectName = commandInterface->project()->projectName(); + projectRootNode->setBelongsToExternalProject(projectPath, projectName); if (commandInterface) { - auto filteredExternalProjectObjects = objectFilterFunc_(commandInterface->project()->instances()); - ObjectTreeViewDefaultModel::treeBuildFunc_(projectRootNode, filteredExternalProjectObjects); + auto filteredExternalProjectObjects = filterForTopLevelObjects(commandInterface->project()->instances()); + constructTreeUnderNode(projectRootNode, filteredExternalProjectObjects, groupExternalReferences_); } } updateTreeIndexes(); @@ -151,27 +170,27 @@ void raco::object_tree::model::ObjectTreeViewExternalProjectModel::buildObjectTr dirty_ = false; } +std::vector ObjectTreeViewExternalProjectModel::filterForTopLevelObjects(const std::vector& objects) const { + return raco::core::Queries::filterForVisibleTopLevelObjects(objects); +} + Qt::ItemFlags ObjectTreeViewExternalProjectModel::flags(const QModelIndex& index) const { Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); - return Qt::ItemIsDragEnabled | defaultFlags; -} - -QMimeData* ObjectTreeViewExternalProjectModel::mimeData(const QModelIndexList& indexes) const { - auto originPath = getOriginProjectPathOfSelectedIndex(indexes.front()); - return ObjectTreeViewDefaultModel::generateMimeData(indexes, originPath); + if (auto obj = indexToSEditorObject(index)) { + return Qt::ItemIsDragEnabled | defaultFlags; + } else { + return defaultFlags; + } } -std::string ObjectTreeViewExternalProjectModel::getOriginProjectPathOfSelectedIndex(const QModelIndex& index) const { - auto obj = indexToTreeNode(index); - - while (obj->getRepresentedObject()->getTypeDescription().typeName != ProjectNode::typeDescription.typeName) { - obj = obj->getParent(); +QMimeData* ObjectTreeViewExternalProjectModel::mimeData(const QModelIndexList& indices) const { + if (!canCopyAtIndices(indices)) { + return nullptr; } - assert(obj->getRepresentedObject() != invisibleRootNode_->getRepresentedObject()); - - return obj->getRepresentedObject()->objectName(); + auto originPath = indexToTreeNode(indices.front())->getExternalProjectPath(); + return ObjectTreeViewDefaultModel::generateMimeData(indices, originPath); } Qt::TextElideMode ObjectTreeViewExternalProjectModel::textElideMode() const { @@ -183,11 +202,11 @@ bool ObjectTreeViewExternalProjectModel::canCopyAtIndices(const QModelIndexList& bool atLeastOneCopyableItem = false; std::string originPath; for (const auto& index : indices) { - if (index.isValid() && indexToSEditorObject(index)->as() == nullptr) { + if (indexToSEditorObject(index)) { atLeastOneCopyableItem = true; - // Make sure all items belong to the same external project - auto indexOriginPath = getOriginProjectPathOfSelectedIndex(index); + // Make sure all items are (or are an extref) in the same external project + auto indexOriginPath = indexToTreeNode(index)->getParent()->getExternalProjectPath(); if (originPath == "") { originPath = indexOriginPath; } else if (indexOriginPath != originPath) { @@ -208,6 +227,7 @@ bool ObjectTreeViewExternalProjectModel::canPasteIntoIndex(const QModelIndex& in return false; } + bool ObjectTreeViewExternalProjectModel::isObjectAllowedIntoIndex(const QModelIndex& index, const core::SEditorObject& obj) const { return false; } diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp index 8c35e3e5..6a03ba59 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp @@ -14,17 +14,31 @@ #include "core/Queries.h" #include "user_types/Prefab.h" #include "user_types/PrefabInstance.h" +#include "style/Icons.h" namespace raco::object_tree::model { ObjectTreeViewPrefabModel::ObjectTreeViewPrefabModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectsStore, const std::vector& allowedCreatableUserTypes) - : ObjectTreeViewDefaultModel(commandInterface, dispatcher, externalProjectsStore, allowedCreatableUserTypes) { + : ObjectTreeViewDefaultModel(commandInterface, dispatcher, externalProjectsStore, allowedCreatableUserTypes, true) { +} + +QVariant ObjectTreeViewPrefabModel::data(const QModelIndex& index, int role) const { + auto treeNode = indexToTreeNode(index); + + if (role == Qt::ItemDataRole::DecorationRole && index.column() == COLUMNINDEX_NAME) { + auto editorObj = indexToSEditorObject(index); + if (editorObj && editorObj->query() && editorObj->as()) { + return QVariant(raco::style::Icons::icon(typeIconMap.at("ExtrefPrefab"))); + } + } + + return ObjectTreeViewDefaultModel::data(index, role); } std::vector ObjectTreeViewPrefabModel::typesAllowedIntoIndex(const QModelIndex& index) const { auto prefabType = raco::user_types::Prefab::typeDescription.typeName; - if (index.isValid()) { + if (indexToSEditorObject(index)) { auto types = ObjectTreeViewDefaultModel::typesAllowedIntoIndex(index); auto prefabIndex = std::find(types.begin(), types.end(), prefabType); @@ -38,6 +52,10 @@ std::vector ObjectTreeViewPrefabModel::typesAllowedIntoIndex(const } } +std::vector ObjectTreeViewPrefabModel::filterForTopLevelObjects(const std::vector& objects) const { + return raco::core::Queries::filterForTopLevelObjectsByTypeName(objects, {raco::user_types::Prefab::typeDescription.typeName}); +} + bool ObjectTreeViewPrefabModel::isObjectAllowedIntoIndex(const QModelIndex& index, const core::SEditorObject& obj) const { if (index.isValid()) { @@ -57,6 +75,4 @@ bool ObjectTreeViewPrefabModel::isObjectAllowedIntoIndex(const QModelIndex& inde } } - - } // namespace raco::object_tree::model \ No newline at end of file diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewResourceModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewResourceModel.cpp index 0b43a7e6..5294a21f 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewResourceModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewResourceModel.cpp @@ -13,12 +13,15 @@ #include "core/CommandInterface.h" #include "core/Queries.h" #include "core/Serialization.h" +#include "core/Project.h" +#include "core/Queries.h" +#include "core/ExternalReferenceAnnotation.h" #include "user_types/UserObjectFactory.h" namespace raco::object_tree::model { ObjectTreeViewResourceModel::ObjectTreeViewResourceModel(raco::core::CommandInterface* commandInterface, components::SDataChangeDispatcher dispatcher, core::ExternalProjectsStoreInterface* externalProjectStore, const std::vector& allowedCreatableUserTypes) - : ObjectTreeViewDefaultModel(commandInterface, dispatcher, externalProjectStore, allowedCreatableUserTypes){} + : ObjectTreeViewDefaultModel(commandInterface, dispatcher, externalProjectStore, allowedCreatableUserTypes, true) {} bool ObjectTreeViewResourceModel::pasteObjectAtIndex(const QModelIndex& index, bool pasteAsExtref, std::string* outError, const std::string& serializedObjects) { // ignore index: resources always get pasted at top level. @@ -27,7 +30,7 @@ bool ObjectTreeViewResourceModel::pasteObjectAtIndex(const QModelIndex& index, b bool ObjectTreeViewResourceModel::isObjectAllowedIntoIndex(const QModelIndex& index, const core::SEditorObject& obj) const { // Only allow root level pasting here, thus only invalid indices are ok. - return !index.isValid() && ObjectTreeViewDefaultModel::isObjectAllowedIntoIndex(index, obj); + return !indexToSEditorObject(index) && ObjectTreeViewDefaultModel::isObjectAllowedIntoIndex(index, obj); } std::vector ObjectTreeViewResourceModel::typesAllowedIntoIndex(const QModelIndex& index) const { @@ -36,6 +39,4 @@ std::vector ObjectTreeViewResourceModel::typesAllowedIntoIndex(cons return ObjectTreeViewDefaultModel::typesAllowedIntoIndex(topLevel); } - - } // namespace raco::object_tree::model \ No newline at end of file diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewTopLevelSortProxyModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewTopLevelSortProxyModel.cpp index 5615cc64..64d16007 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewTopLevelSortProxyModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewTopLevelSortProxyModel.cpp @@ -10,11 +10,21 @@ #include "object_tree_view_model/ObjectTreeViewTopLevelSortProxyModel.h" +#include "object_tree_view_model/ObjectTreeNode.h" namespace raco::object_tree::model { bool ObjectTreeViewTopLevelSortFilterProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { - if (!source_left.parent().isValid() || !source_right.parent().isValid()) { + + // The external reference grouping element should always be on top of the list + if (static_cast(source_left.internalPointer())->getType() == ObjectTreeNodeType::ExtRefGroup) { + return true; + } else if (static_cast(source_right.internalPointer())->getType() == ObjectTreeNodeType::ExtRefGroup) { + return false; + } + + // Only compare items that are on the same level and only sort items that are below the root node or the ExtRefGroup + if (source_left.parent() == source_right.parent() && (!source_left.parent().isValid() || static_cast(source_left.parent().internalPointer())->getType() == ObjectTreeNodeType::ExtRefGroup)) { return QSortFilterProxyModel::lessThan(source_left, source_right); } diff --git a/gui/libObjectTree/tests/ObjectTreeNode_test.cpp b/gui/libObjectTree/tests/ObjectTreeNode_test.cpp index df51e5e3..f056553f 100644 --- a/gui/libObjectTree/tests/ObjectTreeNode_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeNode_test.cpp @@ -17,7 +17,7 @@ using namespace raco::object_tree::model; using namespace raco::core; TEST(ObjectTreeNodeTest, StructureChildGetsAdded) { - auto parent = new ObjectTreeNode; + auto parent = new ObjectTreeNode(ObjectTreeNodeType::Root, nullptr); auto child = new ObjectTreeNode(SEditorObject(), parent); ASSERT_EQ(parent->childCount(), 1); @@ -34,7 +34,7 @@ TEST(ObjectTreeNodeTest, StructureParentGetsDeleted) { std::array nodes; - auto *rootNode = nodes[0] = new ObjectTreeNode; + auto *rootNode = nodes[0] = new ObjectTreeNode(ObjectTreeNodeType::Root, nullptr); for (auto i = 1; i < NODE_AMOUNT; ++i) { nodes[i] = new ObjectTreeNode(SEditorObject(), nodes[i - 1]); } diff --git a/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp index 2ec2e5ab..3da08100 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp @@ -233,12 +233,12 @@ TEST_F(ObjectTreeViewDefaultModelTest, TreeBuildingNastyNesting) { TEST_F(ObjectTreeViewDefaultModelTest, SceneGraphMoveCreateTwoNodes) { nodeNames_ = {"rootNode", "childNode"}; - auto wrongItemIndex = viewModel_->indexFromObjectID("nothing"); + auto wrongItemIndex = viewModel_->indexFromTreeNodeID("nothing"); ASSERT_FALSE(wrongItemIndex.isValid()); auto createdNodes = createNodes(Node::typeDescription.typeName, nodeNames_); - auto rootIndex = viewModel_->indexFromObjectID(createdNodes.front()->objectID()); - auto childIndex = viewModel_->indexFromObjectID(createdNodes.back()->objectID()); + auto rootIndex = viewModel_->indexFromTreeNodeID(createdNodes.front()->objectID()); + auto childIndex = viewModel_->indexFromTreeNodeID(createdNodes.back()->objectID()); ASSERT_EQ(rootIndex.row(), 0); ASSERT_FALSE(rootIndex.parent().isValid()); @@ -257,8 +257,8 @@ TEST_F(ObjectTreeViewDefaultModelTest, SceneGraphMoveCreateParentAndChild) { moveScenegraphChildren({childNode}, rootNode); ASSERT_EQ(childNode->getParent(), rootNode); - auto rootIndex = viewModel_->indexFromObjectID(rootNode->objectID()); - auto childIndex = viewModel_->indexFromObjectID(childNode->objectID()); + auto rootIndex = viewModel_->indexFromTreeNodeID(rootNode->objectID()); + auto childIndex = viewModel_->indexFromTreeNodeID(childNode->objectID()); ASSERT_EQ(rootIndex.row(), 0); ASSERT_FALSE(rootIndex.parent().isValid()); @@ -279,7 +279,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, SceneGraphMoveDontAllowMovingObjectIntoIt auto rootNode = createdNodes.front(); moveScenegraphChildren({rootNode}, rootNode); - auto rootIndex = viewModel_->indexFromObjectID(createdNodes.front()->objectID()); + auto rootIndex = viewModel_->indexFromTreeNodeID(createdNodes.front()->objectID()); ASSERT_EQ(rootNode->getParent(), nullptr); ASSERT_EQ(rootIndex.row(), 0); @@ -298,8 +298,8 @@ TEST_F(ObjectTreeViewDefaultModelTest, SceneGraphMoveDontAllowMovingParentIntoCh ASSERT_EQ(childNode->getParent(), rootNode); moveScenegraphChildren({rootNode}, childNode); - auto rootIndex = viewModel_->indexFromObjectID(createdNodes.front()->objectID()); - auto childIndex = viewModel_->indexFromObjectID(createdNodes.back()->objectID()); + auto rootIndex = viewModel_->indexFromTreeNodeID(createdNodes.front()->objectID()); + auto childIndex = viewModel_->indexFromTreeNodeID(createdNodes.back()->objectID()); ASSERT_EQ(childNode->getParent(), rootNode); ASSERT_EQ(rootNode->getParent(), nullptr); @@ -324,8 +324,8 @@ TEST_F(ObjectTreeViewDefaultModelTest, SceneGraphMoveParentMidAndChildProperHier for (size_t i = 1; i < createdNodes.size(); ++i) { ASSERT_EQ(createdNodes[i]->getParent(), createdNodes[i - 1]); - auto currentNodeIndex = viewModel_->indexFromObjectID(createdNodes[i]->objectID()); - auto parentNodeIndex = viewModel_->indexFromObjectID(createdNodes[i - 1]->objectID()); + auto currentNodeIndex = viewModel_->indexFromTreeNodeID(createdNodes[i]->objectID()); + auto parentNodeIndex = viewModel_->indexFromTreeNodeID(createdNodes[i - 1]->objectID()); ASSERT_EQ(currentNodeIndex.parent(), parentNodeIndex); } } @@ -349,8 +349,8 @@ TEST_F(ObjectTreeViewDefaultModelTest, SceneGraphMoveParentMidAndChildDontMovePa for (size_t i = 1; i < createdNodes.size(); ++i) { ASSERT_EQ(createdNodes[i]->getParent(), createdNodes[i - 1]); - auto currentNodeIndex = viewModel_->indexFromObjectID(createdNodes[i]->objectID()); - auto parentNodeIndex = viewModel_->indexFromObjectID(createdNodes[i - 1]->objectID()); + auto currentNodeIndex = viewModel_->indexFromTreeNodeID(createdNodes[i]->objectID()); + auto parentNodeIndex = viewModel_->indexFromTreeNodeID(createdNodes[i - 1]->objectID()); ASSERT_EQ(currentNodeIndex.parent(), parentNodeIndex); } } @@ -404,7 +404,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, SceneGraphMoveParentChildrenDifferentMovi auto rootChildren = rootNode->children_->asVector(); ASSERT_EQ(rootChildren.size(), childAmount); - auto rootTreeNode = viewModel_->indexToTreeNode(viewModel_->indexFromObjectID(rootNode->objectID())); + auto rootTreeNode = viewModel_->indexToTreeNode(viewModel_->indexFromTreeNodeID(rootNode->objectID())); ASSERT_EQ(rootTreeNode->childCount(), childAmount); for (int i = 1; i <= childAmount; ++i) { @@ -498,12 +498,12 @@ TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionJustOneObject) { ASSERT_EQ(project.instances().size(), 1); - auto justAnIndex = viewModel_->indexFromObjectID(justANode->objectID()); + auto justAnIndex = viewModel_->indexFromTreeNodeID(justANode->objectID()); ASSERT_EQ(deleteObjectsAtIndices({justAnIndex}), 1); ASSERT_TRUE(project.instances().empty()); - justAnIndex = viewModel_->indexFromObjectID(justANode->objectID()); + justAnIndex = viewModel_->indexFromTreeNodeID(justANode->objectID()); ASSERT_FALSE(justAnIndex.isValid()); } @@ -516,14 +516,14 @@ TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionChildNode) { auto childNode = createdNodes.back(); moveScenegraphChildren({childNode}, parentNode); - auto childIndex = viewModel_->indexFromObjectID(childNode->objectID()); + auto childIndex = viewModel_->indexFromTreeNodeID(childNode->objectID()); ASSERT_EQ(deleteObjectsAtIndices({childIndex}), 1); ASSERT_EQ(project.instances().size(), 1); - auto parentIndex = viewModel_->indexFromObjectID(parentNode->objectID()); + auto parentIndex = viewModel_->indexFromTreeNodeID(parentNode->objectID()); ASSERT_EQ(viewModel_->indexToTreeNode(parentIndex)->childCount(), 0); - childIndex = viewModel_->indexFromObjectID(childNode->objectID()); + childIndex = viewModel_->indexFromTreeNodeID(childNode->objectID()); ASSERT_FALSE(childIndex.isValid()); } @@ -540,13 +540,13 @@ TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionParentNode) { moveScenegraphChildren({childNode1}, parentNode); moveScenegraphChildren({childNode2}, parentNode); - auto parentIndex = viewModel_->indexFromObjectID(parentNode->objectID()); + auto parentIndex = viewModel_->indexFromTreeNodeID(parentNode->objectID()); ASSERT_EQ(deleteObjectsAtIndices({parentIndex}), 3); ASSERT_TRUE(project.instances().empty()); - ASSERT_FALSE(viewModel_->indexFromObjectID(parentNode->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode1->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode2->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(parentNode->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode1->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode2->objectID()).isValid()); } TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionMidNode) { @@ -564,14 +564,14 @@ TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionMidNode) { moveScenegraphChildren({childNode1}, midNode); moveScenegraphChildren({childNode2}, midNode); - auto midIndex = viewModel_->indexFromObjectID(midNode->objectID()); + auto midIndex = viewModel_->indexFromTreeNodeID(midNode->objectID()); ASSERT_EQ(deleteObjectsAtIndices({midIndex}), 3); ASSERT_EQ(project.instances().size(), 1); - ASSERT_TRUE(viewModel_->indexFromObjectID(parentNode->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(midNode->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode1->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode2->objectID()).isValid()); + ASSERT_TRUE(viewModel_->indexFromTreeNodeID(parentNode->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(midNode->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode1->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode2->objectID()).isValid()); } @@ -587,14 +587,14 @@ TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionMultiSelectionIncludingPare moveScenegraphChildren({childNode1}, parentNode); moveScenegraphChildren({childNode2}, parentNode); - auto parentIndex = viewModel_->indexFromObjectID(parentNode->objectID()); - auto child1Index = viewModel_->indexFromObjectID(childNode1->objectID()); + auto parentIndex = viewModel_->indexFromTreeNodeID(parentNode->objectID()); + auto child1Index = viewModel_->indexFromTreeNodeID(childNode1->objectID()); ASSERT_EQ(deleteObjectsAtIndices({parentIndex, child1Index}), 3); ASSERT_TRUE(project.instances().empty()); - ASSERT_FALSE(viewModel_->indexFromObjectID(parentNode->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode1->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode2->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(parentNode->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode1->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode2->objectID()).isValid()); } TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionMultiSelectionExcludingParent) { @@ -609,14 +609,14 @@ TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionMultiSelectionExcludingPare moveScenegraphChildren({childNode1}, parentNode); moveScenegraphChildren({childNode2}, parentNode); - auto child1Index = viewModel_->indexFromObjectID(childNode1->objectID()); - auto child2Index = viewModel_->indexFromObjectID(childNode2->objectID()); + auto child1Index = viewModel_->indexFromTreeNodeID(childNode1->objectID()); + auto child2Index = viewModel_->indexFromTreeNodeID(childNode2->objectID()); ASSERT_EQ(deleteObjectsAtIndices({child1Index, child2Index}), 2); ASSERT_TRUE(project.instances().size() == 1); - ASSERT_TRUE(viewModel_->indexFromObjectID(parentNode->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode1->objectID()).isValid()); - ASSERT_FALSE(viewModel_->indexFromObjectID(childNode2->objectID()).isValid()); + ASSERT_TRUE(viewModel_->indexFromTreeNodeID(parentNode->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode1->objectID()).isValid()); + ASSERT_FALSE(viewModel_->indexFromTreeNodeID(childNode2->objectID()).isValid()); } TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionMultiSelectionIncludingPrefabInstanceChild) { @@ -631,34 +631,34 @@ TEST_F(ObjectTreeViewDefaultModelTest, ObjectDeletionMultiSelectionIncludingPref viewModel_->buildObjectTree(); auto prefabInstanceChild = prefabInstance->children_->asVector()[0]->as(); - auto nodeIndex = viewModel_->indexFromObjectID(node->objectID()); - auto nodeChildIndex = viewModel_->indexFromObjectID(nodeChild->objectID()); - auto prefabInstanceIndex = viewModel_->indexFromObjectID(prefabInstance->objectID()); - auto prefabInstanceChildIndex = viewModel_->indexFromObjectID(prefabInstanceChild->objectID()); + auto nodeIndex = viewModel_->indexFromTreeNodeID(node->objectID()); + auto nodeChildIndex = viewModel_->indexFromTreeNodeID(nodeChild->objectID()); + auto prefabInstanceIndex = viewModel_->indexFromTreeNodeID(prefabInstance->objectID()); + auto prefabInstanceChildIndex = viewModel_->indexFromTreeNodeID(prefabInstanceChild->objectID()); ASSERT_EQ(deleteObjectsAtIndices({prefabInstanceChildIndex}), 0); - nodeIndex = viewModel_->indexFromObjectID(node->objectID()); - nodeChildIndex = viewModel_->indexFromObjectID(nodeChild->objectID()); - prefabInstanceIndex = viewModel_->indexFromObjectID(prefabInstance->objectID()); - prefabInstanceChildIndex = viewModel_->indexFromObjectID(prefabInstanceChild->objectID()); + nodeIndex = viewModel_->indexFromTreeNodeID(node->objectID()); + nodeChildIndex = viewModel_->indexFromTreeNodeID(nodeChild->objectID()); + prefabInstanceIndex = viewModel_->indexFromTreeNodeID(prefabInstance->objectID()); + prefabInstanceChildIndex = viewModel_->indexFromTreeNodeID(prefabInstanceChild->objectID()); ASSERT_TRUE(nodeIndex.isValid()); ASSERT_TRUE(nodeChildIndex.isValid()); ASSERT_TRUE(prefabInstanceIndex.isValid()); ASSERT_TRUE(prefabInstanceChildIndex.isValid()); ASSERT_EQ(deleteObjectsAtIndices({prefabInstanceChildIndex, nodeIndex}), 2); - nodeIndex = viewModel_->indexFromObjectID(node->objectID()); - nodeChildIndex = viewModel_->indexFromObjectID(nodeChild->objectID()); - prefabInstanceIndex = viewModel_->indexFromObjectID(prefabInstance->objectID()); - prefabInstanceChildIndex = viewModel_->indexFromObjectID(prefabInstanceChild->objectID()); + nodeIndex = viewModel_->indexFromTreeNodeID(node->objectID()); + nodeChildIndex = viewModel_->indexFromTreeNodeID(nodeChild->objectID()); + prefabInstanceIndex = viewModel_->indexFromTreeNodeID(prefabInstance->objectID()); + prefabInstanceChildIndex = viewModel_->indexFromTreeNodeID(prefabInstanceChild->objectID()); ASSERT_FALSE(nodeIndex.isValid()); ASSERT_FALSE(nodeChildIndex.isValid()); ASSERT_TRUE(prefabInstanceIndex.isValid()); ASSERT_TRUE(prefabInstanceChildIndex.isValid()); ASSERT_EQ(deleteObjectsAtIndices({prefabInstanceIndex, prefabInstanceChildIndex}), 2); - prefabInstanceIndex = viewModel_->indexFromObjectID(prefabInstance->objectID()); - prefabInstanceChildIndex = viewModel_->indexFromObjectID(prefabInstanceChild->objectID()); + prefabInstanceIndex = viewModel_->indexFromTreeNodeID(prefabInstance->objectID()); + prefabInstanceChildIndex = viewModel_->indexFromTreeNodeID(prefabInstanceChild->objectID()); ASSERT_FALSE(prefabInstanceIndex.isValid()); ASSERT_FALSE(prefabInstanceChildIndex.isValid()); } @@ -685,15 +685,15 @@ TEST_F(ObjectTreeViewDefaultModelTest, TypesAllowedIntoIndexInvalidParent) { auto luaScript = createNodes(LuaScript::typeDescription.typeName, {"luaScript"}).front(); auto animation = createNodes(Animation::typeDescription.typeName, {"animation"}).front(); - ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(prefabInstance->objectID())).empty()); - ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(luaScript->objectID())).empty()); - ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(animation->objectID())).empty()); + ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(prefabInstance->objectID())).empty()); + ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(luaScript->objectID())).empty()); + ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(animation->objectID())).empty()); } TEST_F(ObjectTreeViewDefaultModelTest, TypesAllowedIntoIndexNode) { auto node = createNodes(Node::typeDescription.typeName, {"node"}).front(); - auto allowedTypes = viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(node->objectID())); + auto allowedTypes = viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(node->objectID())); std::vector allowedTypesAssert {Animation::typeDescription.typeName, Node::typeDescription.typeName, MeshNode::typeDescription.typeName, @@ -724,7 +724,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsCheckAllSceneGraphObjectCombin auto newObj = viewModel_->objectFactory()->createObject(typeName); if (Queries::isNotResource(newObj) && typeName != Prefab::typeDescription.typeName) { for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { - auto sceneObjIndex = viewModel_->indexFromObjectID(sceneGraphNodeInScene->objectID()); + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); auto pastingSomethingUnderPrefabInstance = sceneGraphNodeInScene->as(); auto pastingSomethingUnderLuaScript = sceneGraphNodeInScene->as(); auto pastingSomethingUnderAnimaton = sceneGraphNodeInScene->as(); @@ -755,7 +755,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsResourcesAreNotAllowedUnderSce auto newObj = viewModel_->objectFactory()->createObject(typeName); if (Queries::isResource(newObj)) { for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { - auto sceneObjIndex = viewModel_->indexFromObjectID(sceneGraphNodeInScene->objectID()); + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(sceneObjIndex, newObj)); } } @@ -769,7 +769,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsPrefabsAreNotAllowed) { ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex({}, prefab)); for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { - auto sceneObjIndex = viewModel_->indexFromObjectID(sceneGraphNodeInScene->objectID()); + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(sceneObjIndex, prefab)); } } @@ -781,7 +781,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsCheckPrefabInstanceCombination ASSERT_TRUE(viewModel_->isObjectAllowedIntoIndex({}, prefabInstance)); for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { - auto sceneObjIndex = viewModel_->indexFromObjectID(sceneGraphNodeInScene->objectID()); + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); auto pastingPrefabInstanceUnderPrefabInstance = sceneGraphNodeInScene->as(); auto pastingPrefabInstanceUnderLuaScript = sceneGraphNodeInScene->as(); auto pastingPrefabInstanceUnderAnimation = sceneGraphNodeInScene->as(); @@ -800,9 +800,9 @@ TEST_F(ObjectTreeViewDefaultModelTest, CanCopyAtIndicesMultiSelection) { auto childNode1 = createdNodes[1]; auto parentNode2 = createdNodes[2]; moveScenegraphChildren({childNode1}, parentNode1); - auto parent1Index = viewModel_->indexFromObjectID(parentNode1->objectID()); - auto child1Index = viewModel_->indexFromObjectID(childNode1->objectID()); - auto parent2Index = viewModel_->indexFromObjectID(parentNode2->objectID()); + auto parent1Index = viewModel_->indexFromTreeNodeID(parentNode1->objectID()); + auto child1Index = viewModel_->indexFromTreeNodeID(childNode1->objectID()); + auto parent2Index = viewModel_->indexFromTreeNodeID(parentNode2->objectID()); ASSERT_FALSE(viewModel_->canCopyAtIndices({})); ASSERT_FALSE(viewModel_->canCopyAtIndices({{}})); @@ -826,10 +826,10 @@ TEST_F(ObjectTreeViewDefaultModelTest, CanDeleteAtIndicesMultiSelection) { viewModel_->buildObjectTree(); auto prefabInstanceChild = prefabInstance->children_->asVector()[0]->as(); - auto nodeIndex = viewModel_->indexFromObjectID(node->objectID()); - auto nodeChildIndex = viewModel_->indexFromObjectID(nodeChild->objectID()); - auto prefabInstanceIndex = viewModel_->indexFromObjectID(prefabInstance->objectID()); - auto prefabInstanceChildIndex = viewModel_->indexFromObjectID(prefabInstanceChild->objectID()); + auto nodeIndex = viewModel_->indexFromTreeNodeID(node->objectID()); + auto nodeChildIndex = viewModel_->indexFromTreeNodeID(nodeChild->objectID()); + auto prefabInstanceIndex = viewModel_->indexFromTreeNodeID(prefabInstance->objectID()); + auto prefabInstanceChildIndex = viewModel_->indexFromTreeNodeID(prefabInstanceChild->objectID()); ASSERT_FALSE(viewModel_->canDeleteAtIndices({})); ASSERT_FALSE(viewModel_->canDeleteAtIndices({{}})); @@ -899,7 +899,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsChildLuaScriptIsNotAllowedAsEx auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(cutObjs); ASSERT_FALSE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds, true)); - ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromObjectID("NodelocalParent"), parsedObjs, sourceProjectTopLevelObjectIds, true)); + ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID("NodelocalParent"), parsedObjs, sourceProjectTopLevelObjectIds, true)); } TEST_F(ObjectTreeViewDefaultModelTest, PasteLuaScriptAsExtRefNotAllowedAsChild) { @@ -912,7 +912,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, PasteLuaScriptAsExtRefNotAllowedAsChild) dataChangeDispatcher_->dispatch(recorder.release()); auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(cutObjs); - ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromObjectID("NodelocalParent"), parsedObjs, sourceProjectTopLevelObjectIds, true)); + ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID("NodelocalParent"), parsedObjs, sourceProjectTopLevelObjectIds, true)); ASSERT_TRUE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds, true)); } diff --git a/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp index 5fda2cd6..2a7eea8c 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp @@ -45,7 +45,6 @@ class ExposedObjectTreeViewExternalProjectModel : public model::ObjectTreeViewEx class ObjectTreeViewExternalProjectModelTest : public ObjectTreeViewDefaultModelTest { protected: - static inline int NEW_PROJECT_NODE_AMOUNT{0}; raco::ramses_base::HeadlessEngineBackend backend{}; raco::ramses_base::HeadlessEngineBackend otherBackend{}; @@ -55,7 +54,6 @@ class ObjectTreeViewExternalProjectModelTest : public ObjectTreeViewDefaultModel void generateExternalProject(const std::vector &instances, std::string projectPath = "projectPath.rca") { application.externalProjectsStore_.externalProjects_[projectPath] = raco::application::RaCoProject::createNew(&otherApplication); - NEW_PROJECT_NODE_AMOUNT = raco::core::Queries::filterForVisibleObjects(application.externalProjectsStore_.externalProjects_[projectPath]->project()->instances()).size(); auto project = application.externalProjectsStore_.externalProjects_[projectPath]->project(); for (const auto &instance : instances) { @@ -79,8 +77,8 @@ TEST_F(ObjectTreeViewExternalProjectModelTest, LoadingProjectEmpty) { auto rootNode = externalProjectModel.getInvisibleRootNode(); ASSERT_EQ(rootNode->childCount(), 1); - ASSERT_EQ(rootNode->getChildren().front()->getRepresentedObject()->objectName(), projectPath); - ASSERT_EQ(rootNode->getChildren().front()->childCount(), NEW_PROJECT_NODE_AMOUNT); + ASSERT_EQ(rootNode->getChildren().front()->getExternalProjectPath(), projectPath); + ASSERT_EQ(rootNode->getChildren().front()->childCount(), 4); } TEST_F(ObjectTreeViewExternalProjectModelTest, LoadingProjectTenTopLevelNodes) { @@ -96,7 +94,7 @@ TEST_F(ObjectTreeViewExternalProjectModelTest, LoadingProjectTenTopLevelNodes) { auto rootNode = externalProjectModel.getInvisibleRootNode(); ASSERT_EQ(rootNode->childCount(), 1); - ASSERT_EQ(rootNode->getChildren().front()->childCount(), CHILD_NODE_AMOUNT + NEW_PROJECT_NODE_AMOUNT); + ASSERT_EQ(rootNode->getChildren().front()->childCount(), CHILD_NODE_AMOUNT + 4); } TEST_F(ObjectTreeViewExternalProjectModelTest, LoadingProjectHierarchyParentsCreatedFirst) { @@ -182,10 +180,10 @@ TEST_F(ObjectTreeViewExternalProjectModelTest, CanCopyAtIndicesMultiSelection) { externalProjectModel.triggerObjectTreeRebuilding(); - auto project1Index = externalProjectModel.indexFromObjectID(project1Path); - auto project1NodeIndex = externalProjectModel.indexFromObjectID(project1Node->objectID()); - auto project2Index = externalProjectModel.indexFromObjectID(project2Path); - auto project2NodeIndex = externalProjectModel.indexFromObjectID(project2Node->objectID()); + auto project1Index = externalProjectModel.indexFromTreeNodeID(project1Path); + auto project1NodeIndex = externalProjectModel.indexFromTreeNodeID(project1Node->objectID()); + auto project2Index = externalProjectModel.indexFromTreeNodeID(project2Path); + auto project2NodeIndex = externalProjectModel.indexFromTreeNodeID(project2Node->objectID()); ASSERT_TRUE(project1Index.isValid()); ASSERT_TRUE(project1NodeIndex.isValid()); @@ -214,8 +212,8 @@ TEST_F(ObjectTreeViewExternalProjectModelTest, CanDeleteAtIndicesNever) { externalProjectModel.triggerObjectTreeRebuilding(); - auto project1Index = externalProjectModel.indexFromObjectID(project1Path); - auto project1NodeIndex = externalProjectModel.indexFromObjectID(project1Node->objectID()); + auto project1Index = externalProjectModel.indexFromTreeNodeID(project1Path); + auto project1NodeIndex = externalProjectModel.indexFromTreeNodeID(project1Node->objectID()); ASSERT_FALSE(externalProjectModel.canDeleteAtIndices({})); ASSERT_FALSE(externalProjectModel.canDeleteAtIndices({{}})); diff --git a/gui/libObjectTree/tests/ObjectTreeViewMultipleModels_test.h b/gui/libObjectTree/tests/ObjectTreeViewMultipleModels_test.h index 3d7a8beb..593136b3 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewMultipleModels_test.h +++ b/gui/libObjectTree/tests/ObjectTreeViewMultipleModels_test.h @@ -24,24 +24,6 @@ class ObjectTreeViewMultipleModelsTest : public ObjectTreeViewDefaultModelTest { } void SetUp() override { - viewModel_->setProjectObjectFilterFunction([](const std::vector& objects) -> std::vector { - return raco::core::Queries::filterByTypeName(objects, {raco::user_types::Node::typeDescription.typeName}); - }); - - prefabModel_.setProjectObjectFilterFunction([](const std::vector& objects) -> std::vector { - std::vector result{}; - std::copy_if(objects.begin(), objects.end(), std::back_inserter(result), - [](const raco::core::SEditorObject& object) { - for (auto parent = object; parent; parent = parent->getParent()) { - if (parent->getTypeDescription().typeName == raco::user_types::Prefab::typeDescription.typeName) { - return true; - } - } - return false; - }); - return result; - }); - prefab_ = createNodes(raco::user_types::Prefab::typeDescription.typeName, {"Prefab"}).front(); } }; diff --git a/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp index 8ec88141..608e0fd8 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp @@ -50,9 +50,9 @@ TEST_F(ObjectTreeViewPrefabModelTest, TypesAllowedIntoIndexInvalidParent) { moveScenegraphChildren({luaScript}, prefab); moveScenegraphChildren({animation}, prefab); - ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(prefabInstance->objectID())).empty()); - ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(luaScript->objectID())).empty()); - ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(animation->objectID())).empty()); + ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(prefabInstance->objectID())).empty()); + ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(luaScript->objectID())).empty()); + ASSERT_TRUE(viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(animation->objectID())).empty()); } TEST_F(ObjectTreeViewPrefabModelTest, TypesAllowedIntoIndexNode) { @@ -60,7 +60,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, TypesAllowedIntoIndexNode) { auto node = createNodes(Node::typeDescription.typeName, {"node"}).front(); moveScenegraphChildren({node}, prefab); - auto allowedTypes = viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(node->objectID())); + auto allowedTypes = viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(node->objectID())); std::vector allowedTypesAssert {Animation::typeDescription.typeName, Node::typeDescription.typeName, MeshNode::typeDescription.typeName, @@ -92,7 +92,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsResourcesAreNotAllowedUnderPref for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); if (raco::core::Queries::isResource(newObj)) { - ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(viewModel_->indexFromObjectID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName), newObj)); + ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(viewModel_->indexFromTreeNodeID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName), newObj)); } } } @@ -112,7 +112,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsCheckSceneGraphObjectsOnTopLeve TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsCheckExternalSceneGraphObjectsUnderPrefab) { auto prefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}); - auto prefabIndex = viewModel_->indexFromObjectID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName); + auto prefabIndex = viewModel_->indexFromTreeNodeID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName); for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); @@ -128,7 +128,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsCheckExternalSceneGraphObjectsU TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsPrefabInTreeViewIsNotMovable) { auto prefabs = createNodes(Prefab::typeDescription.typeName, {"prefab1", "prefab2"}); - auto prefabIndex = viewModel_->indexFromObjectID(Prefab::typeDescription.typeName + "prefab1"); + auto prefabIndex = viewModel_->indexFromTreeNodeID(Prefab::typeDescription.typeName + "prefab1"); ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(prefabIndex, prefabs[1])); } @@ -159,7 +159,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsDeepCopiedSceneGraphWithResourc dataChangeDispatcher_->dispatch(recorder.release()); auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(copiedObjs); - ASSERT_TRUE(viewModel_->canPasteIntoIndex(viewModel_->indexFromObjectID(prefab->objectID()), parsedObjs, sourceProjectTopLevelObjectIds)); + ASSERT_TRUE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID(prefab->objectID()), parsedObjs, sourceProjectTopLevelObjectIds)); } TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsDeepCopiedPrefabInstanceWithPrefabIsAllowed) { @@ -209,16 +209,43 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsDeepCopiedPrefabIsAllowedUnderP dataChangeDispatcher_->dispatch(recorder.release()); auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(copiedObjs); - ASSERT_TRUE(viewModel_->canPasteIntoIndex(viewModel_->indexFromObjectID("prefab2"), parsedObjs, sourceProjectTopLevelObjectIds)); + ASSERT_TRUE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID("prefab2"), parsedObjs, sourceProjectTopLevelObjectIds)); } TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsNothingIsAllowedUnderExtRef) { auto extRefPrefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}).front(); extRefPrefab->addAnnotation(std::make_shared("differentProject")); - auto extRefPrefabIndex = viewModel_->indexFromObjectID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName); + + viewModel_->buildObjectTree(); + auto extRefPrefabIndex = viewModel_->indexFromTreeNodeID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName); for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(extRefPrefabIndex, newObj)); } + + auto extRefGroupIndex = viewModel_->index(0, 0); + ASSERT_EQ(viewModel_->indexToTreeNode(extRefGroupIndex)->getType(), raco::object_tree::model::ObjectTreeNodeType::ExtRefGroup); + ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(extRefGroupIndex, viewModel_->objectFactory()->createObject(Node::typeDescription.typeName))); + ASSERT_TRUE(viewModel_->isObjectAllowedIntoIndex(extRefGroupIndex, viewModel_->objectFactory()->createObject(Prefab::typeDescription.typeName))); } + +TEST_F(ObjectTreeViewPrefabModelTest, CanNotDoAnythingButPasteWithExtRefGroup) { + auto extRefPrefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}).front(); + extRefPrefab->addAnnotation(std::make_shared("differentProject")); + + viewModel_->buildObjectTree(); + auto extRefGroupIndex = viewModel_->index(0, 0); + ASSERT_EQ(viewModel_->indexToTreeNode(extRefGroupIndex)->getType(), raco::object_tree::model::ObjectTreeNodeType::ExtRefGroup); + + ASSERT_FALSE(viewModel_->canDeleteAtIndices({extRefGroupIndex})); + ASSERT_FALSE(viewModel_->canCopyAtIndices({extRefGroupIndex})); + + dataChangeDispatcher_->dispatch(recorder.release()); + auto copiedObjs = commandInterface.copyObjects({extRefPrefab}); + dataChangeDispatcher_->dispatch(recorder.release()); + + auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(copiedObjs); + ASSERT_TRUE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds)); + ASSERT_TRUE(viewModel_->canPasteIntoIndex(extRefGroupIndex, parsedObjs, sourceProjectTopLevelObjectIds)); +} \ No newline at end of file diff --git a/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp index 7f5c97ae..3e4cafdd 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp @@ -13,7 +13,6 @@ #include "core/ExternalReferenceAnnotation.h" #include "core/Queries.h" #include "object_tree_view_model/ObjectTreeViewResourceModel.h" -#include "core/SerializationFunctions.h" #include "user_types/AnimationChannel.h" #include "user_types/LuaScriptModule.h" #include "user_types/RenderBuffer.h" @@ -66,7 +65,7 @@ TEST_F(ObjectTreeViewResourceModelTest, TypesAllowedIntoIndexAnyTypeBehavesLikeE for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto item = createNodes(typeName, {typeName}).front(); - auto allowedTypes = viewModel_->typesAllowedIntoIndex(viewModel_->indexFromObjectID(item->objectID())); + auto allowedTypes = viewModel_->typesAllowedIntoIndex(viewModel_->indexFromTreeNodeID(item->objectID())); ASSERT_EQ(allowedTypes.size(), allowedTypesEmptyIndex.size()); for (int i = 0; i < allowedTypes.size(); ++i) { @@ -105,7 +104,7 @@ TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsResourcesAreAllowedWithResour auto newObj = viewModel_->objectFactory()->createObject(typeName); if (raco::core::Queries::isResource(newObj)) { for (const auto &resourceInScene : allResources) { - ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(viewModel_->indexFromObjectID(resourceInScene->objectID()), newObj)); + ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(viewModel_->indexFromTreeNodeID(resourceInScene->objectID()), newObj)); ASSERT_TRUE(viewModel_->isObjectAllowedIntoIndex({}, newObj)); } } @@ -139,12 +138,14 @@ TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsSceneGraphObjectsAreNotAllowe TEST_F(ObjectTreeViewResourceModelTest, PastePastingMaterialsUnderMaterialCreatesMaterialOnTopLevel) { createNodes(Material::typeDescription.typeName, {Material::typeDescription.typeName}).front(); - auto materialIndex = viewModel_->indexFromObjectID(Material::typeDescription.typeName + Material::typeDescription.typeName); + auto materialIndex = viewModel_->indexFromTreeNodeID(Material::typeDescription.typeName + Material::typeDescription.typeName); auto resourceChild = createNodes(Mesh::typeDescription.typeName, {Mesh::typeDescription.typeName}).front(); auto cutObjs = commandInterface.cutObjects({resourceChild}, false); dataChangeDispatcher_->dispatch(recorder.release()); + materialIndex = viewModel_->indexFromTreeNodeID(Material::typeDescription.typeName + Material::typeDescription.typeName); + auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(cutObjs); ASSERT_FALSE(viewModel_->canPasteIntoIndex(materialIndex, parsedObjs, sourceProjectTopLevelObjectIds)); ASSERT_TRUE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds)); @@ -152,7 +153,7 @@ TEST_F(ObjectTreeViewResourceModelTest, PastePastingMaterialsUnderMaterialCreate viewModel_->pasteObjectAtIndex(materialIndex, false, nullptr, cutObjs); dataChangeDispatcher_->dispatch(recorder.release()); - materialIndex = viewModel_->indexFromObjectID(Material::typeDescription.typeName + Material::typeDescription.typeName); + materialIndex = viewModel_->indexFromTreeNodeID(Material::typeDescription.typeName + Material::typeDescription.typeName); ASSERT_TRUE(materialIndex.isValid()); ASSERT_EQ(viewModel_->indexToTreeNode(materialIndex)->childCount(), 0); ASSERT_EQ(viewModel_->project()->instances().size(), 2); @@ -189,12 +190,35 @@ TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsDeepCopiedPrefabInstanceWithP TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsNoSceneGraphObjectsAreAllowedUnderExtRef) { auto extRefMesh = createNodes(Mesh::typeDescription.typeName, {Mesh::typeDescription.typeName}).front(); extRefMesh->addAnnotation(std::make_shared("differentProject")); - auto extRefMeshIndex = viewModel_->indexFromObjectID(Mesh::typeDescription.typeName + Mesh::typeDescription.typeName); + + viewModel_->buildObjectTree(); + auto extRefMeshIndex = viewModel_->indexFromTreeNodeID(Mesh::typeDescription.typeName + Mesh::typeDescription.typeName); + auto extRefGroupIndex = viewModel_->index(0, 0); + ASSERT_EQ(viewModel_->indexToTreeNode(extRefGroupIndex)->getType(), raco::object_tree::model::ObjectTreeNodeType::ExtRefGroup); for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); - if (!raco::core::Queries::isResource(newObj)) { - ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(extRefMeshIndex, newObj)); - } + ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(extRefMeshIndex, newObj)); + ASSERT_EQ(viewModel_->isObjectAllowedIntoIndex(extRefGroupIndex, newObj), raco::core::Queries::isResource(newObj)); } +} + +TEST_F(ObjectTreeViewResourceModelTest, CanNotDoAnythingButPasteWithExtRefGroup) { + auto extRefMesh = createNodes(Mesh::typeDescription.typeName, {Mesh::typeDescription.typeName}).front(); + extRefMesh->addAnnotation(std::make_shared("differentProject")); + + viewModel_->buildObjectTree(); + auto extRefGroupIndex = viewModel_->index(0, 0); + ASSERT_EQ(viewModel_->indexToTreeNode(extRefGroupIndex)->getType(), raco::object_tree::model::ObjectTreeNodeType::ExtRefGroup); + + ASSERT_FALSE(viewModel_->canDeleteAtIndices({extRefGroupIndex})); + ASSERT_FALSE(viewModel_->canCopyAtIndices({extRefGroupIndex})); + + dataChangeDispatcher_->dispatch(recorder.release()); + auto copiedObjs = commandInterface.copyObjects({extRefMesh}); + dataChangeDispatcher_->dispatch(recorder.release()); + + auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(copiedObjs); + ASSERT_TRUE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds)); + ASSERT_TRUE(viewModel_->canPasteIntoIndex(extRefGroupIndex, parsedObjs, sourceProjectTopLevelObjectIds)); } \ No newline at end of file diff --git a/gui/libPropertyBrowser/CMakeLists.txt b/gui/libPropertyBrowser/CMakeLists.txt index 22ae8179..6240f62c 100644 --- a/gui/libPropertyBrowser/CMakeLists.txt +++ b/gui/libPropertyBrowser/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(libPropertyBrowser include/property_browser/editors/EnumerationEditor.h src/editors/EnumerationEditor.cpp include/property_browser/editors/IntEditor.h src/editors/IntEditor.cpp include/property_browser/editors/LinkEditor.h src/editors/LinkEditor.cpp + include/property_browser/editors/PropertyEditor.h src/editors/PropertyEditor.cpp include/property_browser/editors/RefEditor.h src/editors/RefEditor.cpp include/property_browser/editors/StringEditor.h src/editors/StringEditor.cpp include/property_browser/editors/TagContainerEditor.h src/editors/TagContainerEditor.cpp @@ -63,6 +64,7 @@ PUBLIC Qt5::Widgets PRIVATE raco::Style + lua::lua ) add_library(raco::PropertyBrowser ALIAS libPropertyBrowser) diff --git a/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h b/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h index 3523c42e..10a451ea 100644 --- a/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h +++ b/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h @@ -72,8 +72,10 @@ class PropertyBrowserItem final : public QObject { bool hasCollapsedParent() const noexcept; /** Local expanded flag, doesn't take into account if ancestors are collapsed or not. */ bool expanded() const noexcept; + void setExpanded(bool expanded) noexcept; + void setExpandedRecursively(bool expanded) noexcept; bool showChildren() const; - bool showControl() const; + void requestNextSiblingFocus(); const core::ErrorItem& error() const; @@ -81,12 +83,13 @@ class PropertyBrowserItem final : public QObject { void markForDeletion(); + bool canBeChosenByColorPicker() const; + Q_SIGNALS: void linkStateChanged(raco::core::Queries::LinkState state); void linkTextChanged(const QString& text); void childrenChangedOrCollapsedChildChanged(); void showChildrenChanged(bool show); - void showControlChanged(bool show); void valueChanged(raco::core::ValueHandle& v); void errorChanged(raco::core::ValueHandle& v); void displayNameChanged(const QString& string); @@ -95,9 +98,6 @@ class PropertyBrowserItem final : public QObject { void editableChanged(bool editable); void widgetRequestFocus(); -public Q_SLOTS: - void toggleExpanded() noexcept; - protected: Q_SLOT void updateLinkState() noexcept; diff --git a/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h b/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h index 8c995c6a..ed03f966 100644 --- a/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h +++ b/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h @@ -39,7 +39,7 @@ public Q_SLOTS: void playStructureChangeAnimation(); void setLabelAreaWidth(int offset); void updateChildrenContainer(); - void updatePropertyControl(); + protected: void paintEvent(QPaintEvent* event) override; int getLabelAreaWidthHint() const; diff --git a/gui/libPropertyBrowser/include/property_browser/WidgetFactory.h b/gui/libPropertyBrowser/include/property_browser/WidgetFactory.h index f452d64d..d496ca5c 100644 --- a/gui/libPropertyBrowser/include/property_browser/WidgetFactory.h +++ b/gui/libPropertyBrowser/include/property_browser/WidgetFactory.h @@ -16,6 +16,7 @@ namespace raco::property_browser { class PropertyBrowserItem; class LinkEditor; +class PropertyEditor; /** * Helper factory for create property related widgets (e.g. Label Widget, Control Widget, Link Widget). @@ -23,8 +24,8 @@ class LinkEditor; */ class WidgetFactory { public: - static QWidget* createPropertyControl(PropertyBrowserItem* item, QWidget* parent = nullptr); + static PropertyEditor* createPropertyEditor(PropertyBrowserItem* item, QWidget* parent = nullptr); static QLabel* createPropertyLabel(PropertyBrowserItem* item, QWidget* parent = nullptr); static LinkEditor* createLinkControl(PropertyBrowserItem* item, QWidget* parent = nullptr); }; -} // namespace raco::property_browser +} // namespace raco::property_browser \ No newline at end of file diff --git a/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h b/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h index 278c092a..b698b34c 100644 --- a/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h +++ b/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h @@ -32,6 +32,9 @@ struct SpinBoxTraits { typedef QSpinBox BaseType; }; +template +std::optional evaluateLuaExpression(QString expression); + template class InternalSpinBox : public SpinBoxTraits::BaseType { public: @@ -42,19 +45,13 @@ class InternalSpinBox : public SpinBoxTraits::BaseType { virtual ~InternalSpinBox() {} QValidator::State validate(QString& input, int& pos) const override { - if (SpinBoxTraits::BaseType::validate(input, pos) != QValidator::Acceptable - && !tryGetValueFromText(input).has_value()) { - return QValidator::Intermediate; - } else { - return QValidator::Acceptable; - } + return QValidator::Acceptable; } QString textFromValue(T value) const { return SpinBoxTraits::BaseType::textFromValue(value); } - T valueFromText(const QString& text) const { return tryGetValueFromText(text).value_or(SpinBoxTraits::BaseType::value()); } @@ -78,7 +75,7 @@ class InternalSpinBox : public SpinBoxTraits::BaseType { } std::optional tryGetValueFromText(const QString& text) const { - return SpinBoxTraits::BaseType::valueFromText(text); + return evaluateLuaExpression(text); } }; @@ -87,32 +84,6 @@ inline QString InternalSpinBox::textFromValue(double value) const { return QLocale(QLocale::C).toString(value, 'f', QLocale::FloatingPointShortest); }; -template <> -inline std::optional InternalSpinBox::tryGetValueFromText(const QString& text) const { - bool ok = false; - QString textToInterpret = text; - - if (textToInterpret.size() > 0 && text.at(0) == '.') { - textToInterpret.insert(0, '0'); - } - double ret = QLocale::system().toDouble(textToInterpret, &ok); - if (!ok) { - ret = QLocale(QLocale::C).toDouble(textToInterpret, &ok); - } - return ok ? ret : std::optional{}; -}; - -template <> -inline std::optional InternalSpinBox::tryGetValueFromText(const QString& text) const { - bool ok = false; - - int ret = QLocale::system().toInt(text, &ok); - if (!ok) { - ret = QLocale(QLocale::C).toInt(text, &ok); - } - return ok ? ret : std::optional{}; -}; - template class SpinBox : public QWidget { public: diff --git a/gui/libPropertyBrowser/include/property_browser/editors/BoolEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/BoolEditor.h index 3be1e456..c5d7cbb5 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/BoolEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/BoolEditor.h @@ -9,7 +9,7 @@ */ #pragma once -#include +#include "PropertyEditor.h" class QCheckBox; @@ -17,7 +17,7 @@ namespace raco::property_browser { class PropertyBrowserItem; -class BoolEditor final : public QWidget { +class BoolEditor final : public PropertyEditor { public: explicit BoolEditor( PropertyBrowserItem* item, diff --git a/gui/libPropertyBrowser/include/property_browser/editors/DoubleEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/DoubleEditor.h index f7eabc18..90dfada8 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/DoubleEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/DoubleEditor.h @@ -9,7 +9,7 @@ */ #pragma once -#include +#include "PropertyEditor.h" class QStackedWidget; @@ -17,7 +17,7 @@ namespace raco::property_browser { class PropertyBrowserItem; -class DoubleEditor final : public QWidget { +class DoubleEditor final : public PropertyEditor { public: explicit DoubleEditor( PropertyBrowserItem* item, diff --git a/gui/libPropertyBrowser/include/property_browser/editors/EnumerationEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/EnumerationEditor.h index 5308fd9b..c01c1b8a 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/EnumerationEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/EnumerationEditor.h @@ -9,14 +9,14 @@ */ #pragma once -#include +#include "PropertyEditor.h" class QComboBox; namespace raco::property_browser { class PropertyBrowserItem; -class EnumerationEditor final : public QWidget { +class EnumerationEditor final : public PropertyEditor { public: explicit EnumerationEditor(PropertyBrowserItem* item, QWidget* parent); diff --git a/gui/libPropertyBrowser/include/property_browser/editors/IntEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/IntEditor.h index f5ff0440..5326b1ba 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/IntEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/IntEditor.h @@ -9,7 +9,7 @@ */ #pragma once -#include +#include "PropertyEditor.h" class QStackedWidget; @@ -17,7 +17,7 @@ namespace raco::property_browser { class PropertyBrowserItem; -class IntEditor final : public QWidget { +class IntEditor final : public PropertyEditor { public: explicit IntEditor( PropertyBrowserItem* item, diff --git a/gui/libPropertyBrowser/include/property_browser/editors/PropertyEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/PropertyEditor.h new file mode 100644 index 00000000..1fe266eb --- /dev/null +++ b/gui/libPropertyBrowser/include/property_browser/editors/PropertyEditor.h @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include + +namespace raco::property_browser { + +class PropertyBrowserItem; + +class PropertyEditor : public QWidget { +public: + explicit PropertyEditor( + PropertyBrowserItem* item, + QWidget* parent = nullptr); + +protected: + PropertyBrowserItem* item_; +}; + +} // namespace raco::property_browser diff --git a/gui/libPropertyBrowser/include/property_browser/editors/RefEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/RefEditor.h index acfefcfc..48008222 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/RefEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/RefEditor.h @@ -11,7 +11,7 @@ #include "property_browser/PropertyBrowserRef.h" -#include +#include "PropertyEditor.h" class QPushButton; class QComboBox; @@ -19,7 +19,7 @@ class QComboBox; namespace raco::property_browser { class PropertyBrowserItem; -class RefEditor final : public QWidget { +class RefEditor final : public PropertyEditor { Q_OBJECT Q_PROPERTY(bool unexpectedEmptyReference READ unexpectedEmptyReference); @@ -32,11 +32,8 @@ class RefEditor final : public QWidget { protected: Q_SLOT void updateItems(const PropertyBrowserRef::ComboBoxItems& items); - void changeEvent(QEvent* event) override; - bool emptyReference_ = false; - PropertyBrowserItem* item_{}; PropertyBrowserRef* ref_{nullptr}; QComboBox* comboBox_{nullptr}; QPushButton* goToRefObjectButton_{nullptr}; diff --git a/gui/libPropertyBrowser/include/property_browser/editors/StringEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/StringEditor.h index a0cd5d16..8b2821f3 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/StringEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/StringEditor.h @@ -10,6 +10,8 @@ #pragma once +#include "PropertyEditor.h" + #include #include #include @@ -37,7 +39,7 @@ class StringEditorLineEdit : public QLineEdit { void focusNextRequested(); }; -class StringEditor : public QWidget { +class StringEditor : public PropertyEditor { Q_OBJECT Q_PROPERTY(bool updatedInBackground READ updatedInBackground); Q_PROPERTY(int errorLevel READ errorLevel); @@ -52,7 +54,7 @@ public Q_SLOTS: protected: bool editingStartedByUser(); - void updateErrorState(raco::property_browser::PropertyBrowserItem* item); + void updateErrorState(); bool updatedInBackground_ = false; core::ErrorLevel errorLevel_{core::ErrorLevel::NONE}; diff --git a/gui/libPropertyBrowser/include/property_browser/editors/TagContainerEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/TagContainerEditor.h index b8173b7b..92f8be58 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/TagContainerEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/TagContainerEditor.h @@ -11,7 +11,7 @@ #include "components/DataChangeDispatcher.h" -#include +#include "PropertyEditor.h" #include @@ -24,7 +24,7 @@ enum class TagType; class PropertyBrowserItem; class TagDataCache; -class TagContainerEditor final : public QWidget { +class TagContainerEditor final : public PropertyEditor { Q_OBJECT public: explicit TagContainerEditor(PropertyBrowserItem* item, QWidget* parent); @@ -39,7 +39,6 @@ class TagContainerEditor final : public QWidget { bool showRenderedBy() const; TagType tagType_{}; - PropertyBrowserItem* item_{}; QPushButton* editButton_{}; QListWidget* tagList_{}; QLabel* renderedBy_ {}; diff --git a/gui/libPropertyBrowser/include/property_browser/editors/URIEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/URIEditor.h index 2eee621b..c259e160 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/URIEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/URIEditor.h @@ -38,7 +38,6 @@ class URIEditor : public StringEditor { void updateFileEditButton(); void updateURILineEditString(); - PropertyBrowserItem* currentItem_; QPushButton* editButton_; }; diff --git a/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h index dd349835..7ff379b3 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h @@ -9,18 +9,122 @@ */ #pragma once +#include "PropertyEditor.h" +#include "common_widgets/PropertyBrowserButton.h" #include "core/Queries.h" -#include "property_browser/controls/SpinBox.h" +#include "data_storage/BasicTypes.h" #include "property_browser/PropertyBrowserItem.h" #include "property_browser/PropertyBrowserLayouts.h" +#include "property_browser/controls/SpinBox.h" #include "style/Colors.h" +#include "style/Icons.h" +#include "style/RaCoStyle.h" +#include +#include +#include #include -#include -#include #include +#include namespace raco::property_browser { +template +class VecNTEditorColorPickerButton : public QPushButton { +public: + VecNTEditorColorPickerButton(PropertyBrowserItem* item, QWidget* parent) : item_(item), QPushButton("", parent) { + QObject::connect(this, &QPushButton::clicked, [this]() { this->showColorPicker(); }); + + setFlat(true); + + setToolTip("Show Color Picker"); + setFixedHeight(26); + setFixedWidth(26); + setProperty("slimButton", true); + } + + void paintEvent(QPaintEvent* event) override { + QPushButton::paintEvent(event); + + QPainter painter{this}; + + QPalette pal{palette()}; + + painter.setRenderHint(QPainter::Antialiasing); + auto backgroundBrush = pal.base(); + backgroundBrush.setColor(colorFromItem()); + auto pen = QPen(pal.windowText(), 2); + painter.setBrush(backgroundBrush); + painter.setPen(pen); + painter.drawEllipse(QRect(rect().x() + 4 , rect().y() + 4, 18, 18)); + } + +protected: + PropertyBrowserItem* item_; + + QColor colorFromItem() const; + void setColorToItem(QColor color); + + void showColorPicker() { + QColorDialog::ColorDialogOptions options; + + if (N > 3) { + options = QColorDialog::ColorDialogOption::ShowAlphaChannel; + } + + auto currentColor = colorFromItem(); + + QColor color = QColorDialog::getColor(currentColor, this, "Select Color", options); + + if (color.isValid()) { + setColorToItem(color); + } + } +}; + +inline double clampColorComponent(double value) { + if (value > 1.0) { + return 1.0; + } else if (value < 0.0) { + return 0.0; + } else { + return value; + } +}; + +template <> +inline QColor VecNTEditorColorPickerButton<3>::colorFromItem() const { + auto r = item_->children().at(0)->valueHandle().as(); + auto g = item_->children().at(1)->valueHandle().as(); + auto b = item_->children().at(2)->valueHandle().as(); + return QColor::fromRgbF( + clampColorComponent(r), + clampColorComponent(g), + clampColorComponent(b)); +}; + +template <> +inline QColor VecNTEditorColorPickerButton<4>::colorFromItem() const { + auto r = item_->children().at(0)->valueHandle().as(); + auto g = item_->children().at(1)->valueHandle().as(); + auto b = item_->children().at(2)->valueHandle().as(); + auto a = item_->children().at(3)->valueHandle().as(); + return QColor::fromRgbF( + clampColorComponent(r), + clampColorComponent(g), + clampColorComponent(b), + clampColorComponent(a)); +}; + +template <> +inline void VecNTEditorColorPickerButton<3>::setColorToItem(QColor color) { + item_->set(std::array{color.redF(), color.greenF(), color.blueF()}); +}; + +template <> +inline void VecNTEditorColorPickerButton<4>::setColorToItem(QColor color) { + item_->set(std::array{color.redF(), color.greenF(), color.blueF(), color.alphaF()}); +}; + template struct VecNTEditorTraits { typedef IntSpinBox SpinBoxType; @@ -37,56 +141,111 @@ struct VecNTEditorTraits { }; template -class VecNTEditor final : public QWidget { +class VecNTEditor final : public PropertyEditor { typedef typename VecNTEditorTraits::SpinBoxType SpinBoxType; public: explicit VecNTEditor( PropertyBrowserItem* item, QWidget* parent = nullptr) - : QWidget{parent} { + : PropertyEditor(item, parent) { static_assert(std::is_floating_point::value || std::is_integral::value, "VecNTEditor requires floating point or integral type"); auto* layout = new PropertyBrowserGridLayout{this}; for (int i = 0; i < N; i++) { spinboxes_[i].reset(new SpinBoxType{this}); - if (auto rangeAnnotation = item->children().at(i)->query>()) { + if (auto rangeAnnotation = item->children().at(i)->query>()) { spinboxes_[i]->setRange(*rangeAnnotation->min_, *rangeAnnotation->max_); } spinboxes_[i]->setValue(item->children().at(i)->valueHandle().as()); - QObject::connect(spinboxes_[i].get(), &SpinBoxType::valueChanged, item, [item, i](T value) { item->children().at(i)->set(value); }); - QObject::connect(item->children().at(i), &PropertyBrowserItem::valueChanged, spinboxes_[i].get(), [this, i](raco::core::ValueHandle& handle) { - spinboxes_[i]->setValue(handle.as()); + QObject::connect(spinboxes_[i].get(), &SpinBoxType::valueChanged, this, [this, item, i](T value) { + item->children().at(i)->set(value); + updateColorPicker(); }); + QObject::connect(item->children().at(i), &PropertyBrowserItem::valueChanged, this, &VecNTEditor::updateSpinBoxesAndColorPicker); if (i < N - 1) { int nextSpinboxIndex = i + 1; QObject::connect(spinboxes_[i].get(), &SpinBoxType::focusNextRequested, this, [this, nextSpinboxIndex]() { spinboxes_[nextSpinboxIndex]->setFocus(); - }); + }); } layout->addWidget(spinboxes_[i].get(), 0, i); } - QObject::connect(item, &PropertyBrowserItem::childrenChanged, item, [this](const QList& children) { + QObject::connect(item, &PropertyBrowserItem::childrenChanged, this, &VecNTEditor::updateSpinBoxesAndColorPicker); + QObject::connect(item, &PropertyBrowserItem::childrenChanged, this, [this](const QList& children) { for (int i = 0; i < N; i++) { - spinboxes_[i]->setValue(children.at(i)->valueHandle().as()); + QObject::connect(children.at(i), &PropertyBrowserItem::valueChanged, this, &VecNTEditor::updateSpinBoxesAndColorPicker); } - }); + }); + + setupColorPicker(item); + QObject::connect(item, &PropertyBrowserItem::expandedChanged, this, &VecNTEditor::setExpandedMode); + setExpandedMode(item->expanded()); } + public Q_SLOTS: - void setEnabled(bool val) { - for(auto* child : layout()->children()) { - if(auto* widget { dynamic_cast(child)}) { + void setEnabled(bool val) { + for (auto* child : layout()->children()) { + if (auto* widget{dynamic_cast(child)}) { widget->setEnabled(val); } } } - protected: + virtual void setExpandedMode(bool expanded) { + for (int i = 0; i < N; i++) { + spinboxes_[i]->setVisible(!expanded); + } + if (colorPickerButton_) { + colorPickerButton_->setVisible(expanded); + } + } + + void updateSpinBoxesAndColorPicker() { + for (int i = 0; i < N; i++) { + spinboxes_[i]->setValue(item_->children().at(i)->valueHandle().as()); + } + updateColorPicker(); + } + +protected: + QPushButton* colorPickerButton_ = nullptr; std::array, N> spinboxes_; + + void setupColorPicker(PropertyBrowserItem* item); + void updateColorPicker() { + if (colorPickerButton_) { + auto* button = static_cast*>(colorPickerButton_); + button->update(); + } + } }; +template +void VecNTEditor::setupColorPicker(PropertyBrowserItem* item) { + // Don't do anything, only 3 and 4 double vectors are supported. +} + +template <> +inline void VecNTEditor::setupColorPicker(PropertyBrowserItem* item) { + if (item->canBeChosenByColorPicker()) { + auto* layout = static_cast(this->layout()); + colorPickerButton_ = new VecNTEditorColorPickerButton<3>(item, this); + layout->addWidget(colorPickerButton_, 0, 0, Qt::AlignRight); + } +} + +template <> +inline void VecNTEditor::setupColorPicker(PropertyBrowserItem* item) { + if (item->canBeChosenByColorPicker()) { + auto* layout = static_cast(this->layout()); + colorPickerButton_ = new VecNTEditorColorPickerButton<4>(item, this); + layout->addWidget(colorPickerButton_, 0, 0, Qt::AlignRight); + } +} + using Vec2fEditor = VecNTEditor; using Vec3fEditor = VecNTEditor; using Vec4fEditor = VecNTEditor; diff --git a/gui/libPropertyBrowser/src/PropertyBrowserItem.cpp b/gui/libPropertyBrowser/src/PropertyBrowserItem.cpp index 88d8019e..99d6b742 100644 --- a/gui/libPropertyBrowser/src/PropertyBrowserItem.cpp +++ b/gui/libPropertyBrowser/src/PropertyBrowserItem.cpp @@ -69,7 +69,9 @@ PropertyBrowserItem::PropertyBrowserItem( }); } - linkLifecycleSub_ = dispatcher_->registerOnLinksLifeCycle([this](const core::LinkDescriptor& link) { + linkLifecycleSub_ = dispatcher_->registerOnLinksLifeCycle( + valueHandle_.rootObject(), + [this](const core::LinkDescriptor& link) { if (valueHandle_) { auto endHandle = core::ValueHandle(link.end); if (endHandle && endHandle == valueHandle_) { @@ -207,10 +209,6 @@ bool PropertyBrowserItem::showChildren() const { return expandable() && children_.size() > 0 && expanded_; } -bool PropertyBrowserItem::showControl() const { - return !expandable() || !expanded_ || children_.size() == 0; -} - void PropertyBrowserItem::requestNextSiblingFocus() { auto children = &parentItem()->children(); int index = children->indexOf(this); @@ -284,13 +282,25 @@ bool PropertyBrowserItem::expanded() const noexcept { return expanded_; } -void PropertyBrowserItem::toggleExpanded() noexcept { +void PropertyBrowserItem::setExpanded(bool expanded) noexcept { assert(expandable()); - expanded_ = !expanded_; - // notify the view state accordingly - Q_EMIT expandedChanged(expanded_); - Q_EMIT showChildrenChanged(showChildren()); - Q_EMIT showControlChanged(showControl()); + + if (expanded_ != expanded) { + expanded_ = expanded; + + Q_EMIT expandedChanged(expanded_); + Q_EMIT showChildrenChanged(showChildren()); + } +} + +void PropertyBrowserItem::setExpandedRecursively(bool expanded) noexcept { + if (expandable()) { + setExpanded(expanded); + + for (const auto& child : children_) { + child->setExpandedRecursively(expanded); + } + } } void PropertyBrowserItem::createChildren() { @@ -345,11 +355,31 @@ void PropertyBrowserItem::syncChildrenWithValueHandle() { // notify the view state accordingly if (children_.size() <= 1) { Q_EMIT expandedChanged(expanded()); - Q_EMIT showControlChanged(showControl()); Q_EMIT showChildrenChanged(showChildren()); } } +bool PropertyBrowserItem::canBeChosenByColorPicker() const { + + if(valueHandle_.isObject() || !(valueHandle_.type() == PrimitiveType::Vec3f || valueHandle_.type() == PrimitiveType::Vec4f)) { + return false; + } + + const auto rootTypeRef = &valueHandle_.rootObject()->getTypeDescription(); + + if (!(rootTypeRef == &raco::user_types::ProjectSettings::typeDescription || + rootTypeRef == &raco::user_types::LuaScript::typeDescription || + rootTypeRef == &raco::user_types::MeshNode::typeDescription || + rootTypeRef == &raco::user_types::Material::typeDescription)) { + return false; + } + + if(valueHandle_.isRefToProp(&raco::user_types::Node::translation_) || valueHandle_.isRefToProp(&raco::user_types::Node::rotation_) || valueHandle_.isRefToProp(&raco::user_types::Node::scale_)) { + return false; + } + + return true; +} bool PropertyBrowserItem::getDefaultExpandedFromValueHandleType() const { if (valueHandle_.isObject()) { diff --git a/gui/libPropertyBrowser/src/PropertySubtreeView.cpp b/gui/libPropertyBrowser/src/PropertySubtreeView.cpp index ecf2f415..2a19a791 100644 --- a/gui/libPropertyBrowser/src/PropertySubtreeView.cpp +++ b/gui/libPropertyBrowser/src/PropertySubtreeView.cpp @@ -10,6 +10,7 @@ #include "property_browser/PropertySubtreeView.h" #include "ErrorBox.h" +#include "property_browser/editors/PropertyEditor.h" #include "core/CoreFormatter.h" #include "log_system/log.h" #include "property_browser/PropertyBrowserLayouts.h" @@ -70,16 +71,12 @@ PropertySubtreeView::PropertySubtreeView(PropertyBrowserModel* model, PropertyBr auto* linkControl = WidgetFactory::createLinkControl(item, labelContainer); - propertyControl_ = WidgetFactory::createPropertyControl(item, labelContainer); - propertyControl_->setEnabled(item->editable()); - propertyControl_->setVisible(item->showControl()); - QObject::connect(item, &PropertyBrowserItem::editableChanged, propertyControl_, &QWidget::setEnabled); + propertyControl_ = WidgetFactory::createPropertyEditor(item, labelContainer); labelLayout->addWidget(button_, 0); labelLayout->addWidget(label_, 0); labelLayout->addWidget(linkControl, 1); - QObject::connect(item, &PropertyBrowserItem::showControlChanged, this, [this](bool show) { propertyControl_->setVisible(show); }); linkControl->setControl(propertyControl_); label_->setEnabled(item->editable()); QObject::connect(item, &PropertyBrowserItem::editableChanged, label_, &QWidget::setEnabled); @@ -127,12 +124,6 @@ void PropertySubtreeView::updateError() { } } -void PropertySubtreeView::updatePropertyControl() { - if (propertyControl_) { - propertyControl_->setVisible(!item_->expanded() || item_->size() == 0); - } -} - void PropertySubtreeView::collectTabWidgets(QObject* item, QWidgetList& tabWidgets) { if (auto itemWidget = qobject_cast(item)) { if (itemWidget->focusPolicy() & Qt::TabFocus) { diff --git a/gui/libPropertyBrowser/src/WidgetFactory.cpp b/gui/libPropertyBrowser/src/WidgetFactory.cpp index 6ebe3809..f158e82e 100644 --- a/gui/libPropertyBrowser/src/WidgetFactory.cpp +++ b/gui/libPropertyBrowser/src/WidgetFactory.cpp @@ -36,7 +36,7 @@ QLabel* WidgetFactory::createPropertyLabel(PropertyBrowserItem* item, QWidget* p return label; } -QWidget* WidgetFactory::createPropertyControl(PropertyBrowserItem* item, QWidget* parent) { +PropertyEditor* WidgetFactory::createPropertyEditor(PropertyBrowserItem* item, QWidget* parent) { using PrimitiveType = core::PrimitiveType; switch (item->type()) { @@ -74,10 +74,10 @@ QWidget* WidgetFactory::createPropertyControl(PropertyBrowserItem* item, QWidget if (item->query() || item->query()) { return new TagContainerEditor{ item, parent }; } - return new QWidget{ parent }; + return new PropertyEditor{ item, parent }; default: // used for group headlines - return new QWidget{parent}; + return new PropertyEditor{item, parent}; }; } diff --git a/gui/libPropertyBrowser/src/controls/ExpandButton.cpp b/gui/libPropertyBrowser/src/controls/ExpandButton.cpp index f7209289..80b03604 100644 --- a/gui/libPropertyBrowser/src/controls/ExpandButton.cpp +++ b/gui/libPropertyBrowser/src/controls/ExpandButton.cpp @@ -9,6 +9,7 @@ */ #include "property_browser/controls/ExpandButton.h" +#include #include #include @@ -45,7 +46,14 @@ ExpandControlButton::ExpandControlButton(PropertyBrowserItem* item, QWidget* par } }); - QObject::connect(this, &ExpandControlButton::clicked, item, &PropertyBrowserItem::toggleExpanded); + QObject::connect(this, &ExpandControlButton::clicked, this, [this, item]() { + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::KeyboardModifier::ShiftModifier)) { + item->setExpandedRecursively(!item->expanded()); + } else { + item->setExpanded(!item->expanded()); + } + }); + QObject::connect(item, &PropertyBrowserItem::expandedChanged, this, &ExpandControlButton::updateIcon); updateIcon(item->expanded()); } diff --git a/gui/libPropertyBrowser/src/controls/SpinBox.cpp b/gui/libPropertyBrowser/src/controls/SpinBox.cpp index 84d8f263..7b6d314e 100644 --- a/gui/libPropertyBrowser/src/controls/SpinBox.cpp +++ b/gui/libPropertyBrowser/src/controls/SpinBox.cpp @@ -7,10 +7,19 @@ * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + #include "property_browser/controls/SpinBox.h" +#include "log_system/log.h" + +#include "lualib.h" +#include "lauxlib.h" +#include "lapi.h" + namespace raco::property_browser { +const int EVALUATE_LUA_EXECUTION_LIMIT = 1000; + raco::property_browser::IntSpinBox::IntSpinBox(QWidget* parent) : SpinBox{parent} {} void raco::property_browser::IntSpinBox::emitValueChanged(int value) { @@ -41,4 +50,37 @@ void raco::property_browser::DoubleSpinBox::emitFocusNextRequested() { Q_EMIT focusNextRequested(); } + +template +std::optional evaluateLuaExpression(QString expression) { + // Support german language decimal seperators by just replacing them with dots. + // This can invalidate fancy lua expressions, but these should not be used anyway by users. + expression.replace(',', '.'); + + lua_State* l = lua_open(); + + // Limit number of instructions executed, to avoid infinite loops hanging the program. + lua_sethook( + l, [](lua_State* l, lua_Debug* d) { luaL_error(l, "Maximum instruction excecution limit exceeded."); }, LUA_MASKCOUNT, EVALUATE_LUA_EXECUTION_LIMIT); + + if (!luaL_dostring(l, ("return " + expression.toStdString()).c_str())) { + T result; + if constexpr (std::is_same::value) { + result = lua_tonumber(l, -1); + } else { + result = lua_tointeger(l, -1); + } + lua_close(l); + return result; + } else { + LOG_INFO(raco::log_system::PROPERTY_BROWSER, "Could not evaluate Lua Expression: {}", lua_tostring(l, -1)); + lua_close(l); + return {}; + } +}; + +template std::optional evaluateLuaExpression(QString expression); + +template std::optional evaluateLuaExpression(QString expression); + } // namespace raco::property_browser \ No newline at end of file diff --git a/gui/libPropertyBrowser/src/editors/BoolEditor.cpp b/gui/libPropertyBrowser/src/editors/BoolEditor.cpp index 3fd60879..8c9e81b4 100644 --- a/gui/libPropertyBrowser/src/editors/BoolEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/BoolEditor.cpp @@ -20,7 +20,8 @@ namespace raco::property_browser { BoolEditor::BoolEditor( PropertyBrowserItem* item, QWidget* parent) - : QWidget{parent} { + : PropertyEditor(item, parent) { + auto* layout{new PropertyBrowserHBoxLayout{this}}; checkBox_ = new QCheckBox{this}; QObject::connect(checkBox_, &QCheckBox::clicked, item, [item](bool checked) { item->set(checked); }); diff --git a/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp b/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp index 6a796ceb..bcb83b98 100644 --- a/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp @@ -24,7 +24,7 @@ namespace raco::property_browser { DoubleEditor::DoubleEditor( PropertyBrowserItem* item, - QWidget* parent) : QWidget{parent} { + QWidget* parent) : PropertyEditor(item, parent) { auto* layout = new PropertyBrowserGridLayout{this}; stack_ = new QStackedWidget{this}; diff --git a/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp b/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp index eb7836a1..1c76678d 100644 --- a/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp @@ -22,7 +22,7 @@ namespace raco::property_browser { -EnumerationEditor::EnumerationEditor(PropertyBrowserItem* item, QWidget* parent) : QWidget{parent} { +EnumerationEditor::EnumerationEditor(PropertyBrowserItem* item, QWidget* parent) : PropertyEditor(item, parent) { auto* layout{new PropertyBrowserGridLayout{this}}; comboBox_ = new QComboBox(this); comboBox_->setFocusPolicy(Qt::StrongFocus); diff --git a/gui/libPropertyBrowser/src/editors/IntEditor.cpp b/gui/libPropertyBrowser/src/editors/IntEditor.cpp index ddc7bdb3..e70eaee7 100644 --- a/gui/libPropertyBrowser/src/editors/IntEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/IntEditor.cpp @@ -25,7 +25,7 @@ namespace raco::property_browser { IntEditor::IntEditor( PropertyBrowserItem* item, - QWidget* parent) : QWidget{parent} { + QWidget* parent) : PropertyEditor(item, parent) { auto* layout = new PropertyBrowserGridLayout{this}; stack_ = new QStackedWidget{this}; diff --git a/gui/libPropertyBrowser/src/editors/LinkEditor.cpp b/gui/libPropertyBrowser/src/editors/LinkEditor.cpp index 532996be..bf605c26 100644 --- a/gui/libPropertyBrowser/src/editors/LinkEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/LinkEditor.cpp @@ -82,10 +82,6 @@ class LinkEditorPopup : public QDialog { QObject::connect(&list_, &LinkStartSearchView::clicked, this, [this, item](const QModelIndex& index) { item->setLink(list_.handleFromIndex(index)); close(); }); QObject::connect(&list_, &LinkStartSearchView::activated, this, [this, item](const QModelIndex& index) { item->setLink(list_.handleFromIndex(index)); close(); }); - if (isLinked) { - search_.setText(item->linkText().c_str()); - } - // center horizontally on link button and keep on screen search_.setMinimumWidth(500); updateGeometry(); @@ -192,7 +188,7 @@ void LinkEditor::setLinkState(const LinkState& linkstate) { linkButton_->setDisabled(linkstate.readonly || !linkstate.validLinkTarget); if (linkstate.current == CurrentLinkState::PARENT_LINKED) { - linkButton_->setIcon(Icons::icon(Pixmap::parent_is_linked)); + linkButton_->setIcon(Icons::icon(Pixmap::parentIsLinked)); } else { if (linkstate.validLinkTarget) { switch (linkstate.current) { @@ -205,7 +201,7 @@ void LinkEditor::setLinkState(const LinkState& linkstate) { break; case CurrentLinkState::BROKEN: - linkButton_->setIcon(Icons::icon(Pixmap::link_broken)); + linkButton_->setIcon(Icons::icon(Pixmap::linkBroken)); break; } diff --git a/gui/libPropertyBrowser/src/editors/PropertyEditor.cpp b/gui/libPropertyBrowser/src/editors/PropertyEditor.cpp new file mode 100644 index 00000000..e6c9d6f2 --- /dev/null +++ b/gui/libPropertyBrowser/src/editors/PropertyEditor.cpp @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "property_browser/editors/PropertyEditor.h" +#include "property_browser/PropertyBrowserItem.h" + +namespace raco::property_browser { + +PropertyEditor::PropertyEditor( + PropertyBrowserItem* item, + QWidget* parent) + : QWidget{parent}, item_{item} { + + setEnabled(item->editable()); + QObject::connect(item, &PropertyBrowserItem::editableChanged, this, &QWidget::setEnabled); +} + +} // namespace raco::property_browser diff --git a/gui/libPropertyBrowser/src/editors/RefEditor.cpp b/gui/libPropertyBrowser/src/editors/RefEditor.cpp index 9dd97c37..7d5d7bb3 100644 --- a/gui/libPropertyBrowser/src/editors/RefEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/RefEditor.cpp @@ -31,8 +31,7 @@ using namespace raco::style; RefEditor::RefEditor( PropertyBrowserItem* item, QWidget* parent) - : QWidget{parent}, - item_{item}, + : PropertyEditor(item, parent), ref_{item->refItem()} { auto* layout{new raco::common_widgets::NoContentMarginsLayout{this}}; comboBox_ = new QComboBox{this}; @@ -40,7 +39,7 @@ RefEditor::RefEditor( comboBox_->installEventFilter(new MouseWheelGuard()); layout->addWidget(comboBox_); - goToRefObjectButton_ = new raco::common_widgets::PropertyBrowserButton(raco::style::Icons::icon(raco::style::Pixmap::go_to, this), "", this); + goToRefObjectButton_ = new raco::common_widgets::PropertyBrowserButton(raco::style::Icons::icon(raco::style::Pixmap::goTo, this), "", this); QObject::connect(goToRefObjectButton_, &QPushButton::clicked, [this, item]() { item->model()->Q_EMIT objectSelectionRequested(ref_->items().at(ref_->currentIndex()).second); @@ -59,6 +58,16 @@ RefEditor::RefEditor( comboBox_->setFocus(); }); updateItems(ref_->items()); + + // Override the enabled behaviour of the parent class, so that the goto button can remain enabled even though the rest of the control gets disabled. + setEnabled(true); + comboBox_->setEnabled(item->editable()); + goToRefObjectButton_->setEnabled(!emptyReference_); + QObject::disconnect(item, &PropertyBrowserItem::editableChanged, this, &QWidget::setEnabled); + QObject::connect(item, &PropertyBrowserItem::editableChanged, this, [this]() { + comboBox_->setEnabled(item_->editable()); + goToRefObjectButton_->setEnabled(!emptyReference_); + }); } void RefEditor::updateItems(const PropertyBrowserRef::ComboBoxItems& items) { @@ -71,19 +80,6 @@ void RefEditor::updateItems(const PropertyBrowserRef::ComboBoxItems& items) { QObject::connect(comboBox_, qOverload(&QComboBox::activated), ref_, &PropertyBrowserRef::setIndex); } -void RefEditor::changeEvent(QEvent* event) { - QWidget::changeEvent(event); - if (event->type() == QEvent::EnabledChange) { - // retroactively set the RefEditor enabled at all times and only disable/enable the Ref combo box - // because QWidgets have to be enabled for their child widgets to be enabled and we want to enable the goto button at all times - auto enabled = this->isEnabled(); - this->setEnabled(true); - - comboBox_->setEnabled(enabled); - goToRefObjectButton_->setEnabled(!emptyReference_); - } -} - bool RefEditor::unexpectedEmptyReference() const noexcept { return emptyReference_ && !item_->valueHandle().query(); } diff --git a/gui/libPropertyBrowser/src/editors/StringEditor.cpp b/gui/libPropertyBrowser/src/editors/StringEditor.cpp index f9f6b09e..4e53e818 100644 --- a/gui/libPropertyBrowser/src/editors/StringEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/StringEditor.cpp @@ -29,20 +29,20 @@ namespace raco::property_browser { StringEditor::StringEditor(PropertyBrowserItem* item, QWidget* parent) - : QWidget{parent} { + : PropertyEditor(item, parent) { this->setLayout(new raco::common_widgets::NoContentMarginsLayout(this)); lineEdit_ = new StringEditorLineEdit(this); layout()->addWidget(lineEdit_); // connect item to QLineEdit - QObject::connect(lineEdit_, &QLineEdit::editingFinished, item, [this, item]() { item->set(lineEdit_->text().toStdString()); }); - QObject::connect(item, &PropertyBrowserItem::valueChanged, this, [this, item](core::ValueHandle & handle) { + QObject::connect(lineEdit_, &QLineEdit::editingFinished, item, [this]() { item_->set(lineEdit_->text().toStdString()); }); + QObject::connect(item, &PropertyBrowserItem::valueChanged, this, [this](core::ValueHandle & handle) { lineEdit_->setText(handle.asString().c_str()); - this->updateErrorState(item); + this->updateErrorState(); lineEdit_->update(); }); - QObject::connect(item, &PropertyBrowserItem::errorChanged, this, [this, item](core::ValueHandle& handle) { - this->updateErrorState(item); + QObject::connect(item, &PropertyBrowserItem::errorChanged, this, [this](core::ValueHandle& handle) { + this->updateErrorState(); }); QObject::connect(item, &PropertyBrowserItem::widgetRequestFocus, this, [this]() { lineEdit_->setFocus(); @@ -62,10 +62,10 @@ StringEditor::StringEditor(PropertyBrowserItem* item, QWidget* parent) }); } -void StringEditor::updateErrorState(raco::property_browser::PropertyBrowserItem* item) { - if (item->hasError()) { - errorLevel_ = item->error().level(); - lineEdit_->setToolTip(item->error().message().c_str()); +void StringEditor::updateErrorState() { + if (item_->hasError()) { + errorLevel_ = item_->error().level(); + lineEdit_->setToolTip(item_->error().message().c_str()); } else { errorLevel_ = core::ErrorLevel::NONE; lineEdit_->setToolTip({}); diff --git a/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp b/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp index abddff2e..b01a2127 100644 --- a/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp @@ -30,12 +30,12 @@ namespace raco::property_browser { -TagContainerEditor::TagContainerEditor(PropertyBrowserItem* item, QWidget* parent) : QWidget{parent}, item_{item} { +TagContainerEditor::TagContainerEditor(PropertyBrowserItem* item, QWidget* parent) : PropertyEditor(item, parent) { auto* layout{new PropertyBrowserGridLayout{this}}; - if (&item_->valueHandle().rootObject()->getTypeDescription() == &user_types::Material::typeDescription) { + if (&item->valueHandle().rootObject()->getTypeDescription() == &user_types::Material::typeDescription) { tagType_ = TagType::MaterialTags; - } else if (&item_->valueHandle().rootObject()->getTypeDescription() == &user_types::RenderLayer::typeDescription) { + } else if (&item->valueHandle().rootObject()->getTypeDescription() == &user_types::RenderLayer::typeDescription) { // TODO: what is a good way of checking if the ValueHandle points to a specific known C++ class member? // Would be nice to be able to write something like if(item->valueHandle().refersTo(&user_types::RenderLayer::renderableTags_))? // Checking the property name hides the dependency of this code to the property? @@ -57,7 +57,7 @@ TagContainerEditor::TagContainerEditor(PropertyBrowserItem* item, QWidget* paren editButton_ = new QPushButton{this}; editButton_->setFlat(true); editButton_->setProperty("slimButton", true); - editButton_->setIcon(style::Icons::icon(style::Pixmap::open_in_new)); + editButton_->setIcon(style::Icons::icon(style::Pixmap::openInNew)); layout->addWidget(editButton_, 0, 0, Qt::AlignTop); diff --git a/gui/libPropertyBrowser/src/editors/URIEditor.cpp b/gui/libPropertyBrowser/src/editors/URIEditor.cpp index 7258dfd8..dcc9c4b7 100644 --- a/gui/libPropertyBrowser/src/editors/URIEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/URIEditor.cpp @@ -25,7 +25,7 @@ #include "style/Icons.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "core/PathQueries.h" #include "core/Project.h" #include "property_browser/PropertyBrowserItem.h" @@ -34,31 +34,41 @@ namespace raco::property_browser { using PathManager = core::PathManager; -URIEditor::URIEditor(PropertyBrowserItem* item, QWidget* parent) : StringEditor(item, parent), currentItem_(item) { +URIEditor::URIEditor(PropertyBrowserItem* item, QWidget* parent) : StringEditor(item, parent) { auto* loadFileButton = new raco::common_widgets::PropertyBrowserButton(" ... ", this); - auto uriAnno = item->query(); - auto filter = *uriAnno->filter_; - QObject::connect(loadFileButton, &QPushButton::clicked, [this, filter]() { + QObject::connect(loadFileButton, &QPushButton::clicked, [this]() { + auto uriAnno = item_->query(); std::string cachedPath; - auto projectAbsPath = currentItem_->project()->currentFolder(); - auto cachedPathKey = raco::core::PathManager::getCachedPathKeyCorrespondingToUserType(currentItem_->valueHandle().rootObject()->getTypeDescription()); + auto projectAbsPath = item_->project()->currentFolder(); + auto cachedPathKey = raco::core::PathManager::getCachedPathKeyCorrespondingToUserType(item_->valueHandle().rootObject()->getTypeDescription()); if (fileExists()) { - auto fileInfo = QFileInfo(QString::fromStdString(PathManager::constructAbsolutePath(projectAbsPath, lineEdit_->text().toStdString()))); + auto fileInfo = QFileInfo(QString::fromStdString(raco::utils::u8path(lineEdit_->text().toStdString()).normalizedAbsolutePath(projectAbsPath).string())); QDir uriDir = fileInfo.absoluteDir(); cachedPath = uriDir.absolutePath().toStdString(); } else { - cachedPath = raco::core::PathManager::getCachedPath(cachedPathKey); + cachedPath = raco::core::PathManager::getCachedPath(cachedPathKey).string(); } - QFileDialog* dialog = new QFileDialog(this, tr("Choose URI"), QString::fromStdString(raco::utils::path::isExistingDirectory(cachedPath) ? cachedPath : projectAbsPath), tr(filter.c_str())); + auto fileFilter = QString(); + auto fileMode = QFileDialog::FileMode::Directory; + if (!uriAnno->isProjectSubdirectoryURI()) { + fileFilter = uriAnno->filter_.asString().c_str(); + fileMode = QFileDialog::FileMode::AnyFile; + } + + QFileDialog* dialog = new QFileDialog(this, tr("Choose URI"), QString::fromStdString(raco::utils::u8path(cachedPath).existsDirectory() ? cachedPath : projectAbsPath), fileFilter); + if (uriAnno->isProjectSubdirectoryURI()) { + dialog->setOption(QFileDialog::ShowDirsOnly, true); + } + dialog->setFileMode(fileMode); connect(dialog, &QFileDialog::fileSelected, [this, cachedPathKey](const QString& file) { auto oldUriWasAbsolute = pathIsAbsolute(); if (oldUriWasAbsolute) { - currentItem_->set(file.toStdString()); + item_->set(file.toStdString()); } else { - currentItem_->set(PathManager::constructRelativePath(file.toStdString(), currentItem_->project()->currentFolder())); + item_->set(raco::utils::u8path(file.toStdString()).normalizedRelativePath(item_->project()->currentFolder()).string()); } QDir dir = QFileInfo(file).absoluteDir(); @@ -68,7 +78,7 @@ URIEditor::URIEditor(PropertyBrowserItem* item, QWidget* parent) : StringEditor( }); layout()->addWidget(loadFileButton); - editButton_ = new raco::common_widgets::PropertyBrowserButton(raco::style::Icons::icon(raco::style::Pixmap::open_in_new, this), "", this); + editButton_ = new raco::common_widgets::PropertyBrowserButton(raco::style::Icons::icon(raco::style::Pixmap::openInNew, this), "", this); editButton_->setMaximumWidth(raco::common_widgets::PropertyBrowserButton::MAXIMUM_WIDTH_PX + 5); editButton_->setEnabled(fileExists()); connect(editButton_, &QPushButton::clicked, [this]() { @@ -79,20 +89,29 @@ URIEditor::URIEditor(PropertyBrowserItem* item, QWidget* parent) : StringEditor( lineEdit_->setContextMenuPolicy(Qt::CustomContextMenu); connect(lineEdit_, &QLineEdit::customContextMenuRequested, [this, item](const QPoint& p) { showCustomLineEditContextMenu(p, item); }); - connect(currentItem_, &PropertyBrowserItem::valueChanged, this, [this, item](core::ValueHandle& handle) { + connect(item_, &PropertyBrowserItem::valueChanged, this, [this, item](core::ValueHandle& handle) { updateURILineEditString(); updateFileEditButton(); }); updateFileEditButton(); + + // Override the enabled behaviour of the parent class, so that the open with button can remain enabled even though the rest of the control gets disabled. + setEnabled(true); + loadFileButton->setEnabled(item->editable()); + lineEdit_->setEnabled(item->editable()); + QObject::disconnect(item, &PropertyBrowserItem::editableChanged, this, &QWidget::setEnabled); + QObject::connect(item, &PropertyBrowserItem::editableChanged, this, [loadFileButton, this]() { + loadFileButton->setEnabled(item_->editable()); + lineEdit_->setEnabled(item_->editable()); + }); } void URIEditor::showCustomLineEditContextMenu(const QPoint& p, PropertyBrowserItem* item) { QMenu* contextMenu = lineEdit_->createStandardContextMenu(); QAction* absoluteRelativeURISwitch = new QAction("Use Absolute Path", this); - auto currentPath = std::filesystem::path(lineEdit_->text().toStdString()); - auto URIisOnDifferentPartition = pathIsAbsolute() && !PathManager::pathsShareSameRoot(lineEdit_->text().toStdString(), item->project()->currentPath()); + auto URIisOnDifferentPartition = pathIsAbsolute() && !raco::utils::u8path::areSharingSameRoot(lineEdit_->text().toStdString(), item->project()->currentPath()); absoluteRelativeURISwitch->setDisabled(URIisOnDifferentPartition); absoluteRelativeURISwitch->setCheckable(true); absoluteRelativeURISwitch->setChecked(pathIsAbsolute() || URIisOnDifferentPartition); @@ -111,46 +130,46 @@ void URIEditor::updateFileEditButton() { } void URIEditor::updateURILineEditString() { - lineEdit_->setText(QString::fromStdString(currentItem_->valueHandle().asString())); + lineEdit_->setText(QString::fromStdString(item_->valueHandle().asString())); } void URIEditor::updateURIValueHandle() { std::string newPathString{""}; - if (!currentItem_->valueHandle().asString().empty()) { + if (!item_->valueHandle().asString().empty()) { newPathString = pathIsAbsolute() ? createAbsolutePath() : createRelativePath(); } - currentItem_->set(newPathString); + item_->set(newPathString); } bool URIEditor::pathIsAbsolute() { - return std::filesystem::path(currentItem_->valueHandle().asString()).is_absolute(); + return raco::utils::u8path(item_->valueHandle().asString()).is_absolute(); } void URIEditor::switchAbsoluteRelativePath() { - if (currentItem_->valueHandle().asString().empty()) { + if (item_->valueHandle().asString().empty()) { return; } - currentItem_->set(pathIsAbsolute() ? createRelativePath() : createAbsolutePath()); + item_->set(pathIsAbsolute() ? createRelativePath() : createAbsolutePath()); } bool URIEditor::fileExists() { - if (currentItem_->valueHandle().asString().empty()) { + if (item_->valueHandle().asString().empty()) { return false; } - auto itemAbsolutePath = pathIsAbsolute() ? currentItem_->valueHandle().asString() : createAbsolutePath(); - return raco::utils::path::exists(itemAbsolutePath); + auto itemAbsolutePath = pathIsAbsolute() ? item_->valueHandle().asString() : createAbsolutePath(); + return raco::utils::u8path(itemAbsolutePath).exists(); } std::string URIEditor::createAbsolutePath() { - return core::PathQueries::resolveUriPropertyToAbsolutePath(*currentItem_->project(), currentItem_->valueHandle()); + return core::PathQueries::resolveUriPropertyToAbsolutePath(*item_->project(), item_->valueHandle()); } std::string URIEditor::createRelativePath() { - auto itemPath = currentItem_->valueHandle().asString(); - auto projectAbsPath = currentItem_->project()->currentFolder(); - return PathManager::constructRelativePath(itemPath, projectAbsPath); + auto itemPath = item_->valueHandle().asString(); + auto projectAbsPath = item_->project()->currentFolder(); + return raco::utils::u8path(itemPath).normalizedRelativePath(projectAbsPath).string(); } } // namespace raco::property_browser diff --git a/gui/libPropertyBrowser/tests/CMakeLists.txt b/gui/libPropertyBrowser/tests/CMakeLists.txt index 6a636f3e..4ebc32e8 100644 --- a/gui/libPropertyBrowser/tests/CMakeLists.txt +++ b/gui/libPropertyBrowser/tests/CMakeLists.txt @@ -15,9 +15,10 @@ set(TEST_SOURCES PropertyBrowserItem_test.cpp EditorTestFixture.h + EnumerationEditor_test.cpp PrimitiveEditorsDataChange_test.cpp + SpinBox_test.cpp URIEditor_test.cpp - EnumerationEditor_test.cpp ) set(TEST_LIBRARIES raco::PropertyBrowser diff --git a/gui/libPropertyBrowser/tests/PrimitiveEditorsDataChange_test.cpp b/gui/libPropertyBrowser/tests/PrimitiveEditorsDataChange_test.cpp index 36fa0527..58ec997a 100644 --- a/gui/libPropertyBrowser/tests/PrimitiveEditorsDataChange_test.cpp +++ b/gui/libPropertyBrowser/tests/PrimitiveEditorsDataChange_test.cpp @@ -24,7 +24,6 @@ #include "user_types/Material.h" #include "user_types/MeshNode.h" #include "user_types/Node.h" -#include using raco::core::CommandInterface; using raco::core::ValueHandle; @@ -40,12 +39,12 @@ struct TestParam { }; struct PrimitiveEditorDataChangeFixture : public EditorTestFixtureT<::testing::TestWithParam> { - std::filesystem::path cwd_path() const override { + raco::utils::u8path test_path() const override { std::string testCaseName{::testing::UnitTest::GetInstance()->current_test_info()->name()}; testCaseName = testCaseName.substr(0, testCaseName.find("#GetParam()")); std::replace(testCaseName.begin(), testCaseName.end(), '/', '\\'); - auto result(std::filesystem::current_path() / testCaseName); + auto result(raco::utils::u8path::current() / testCaseName); return result; } struct PrintToStringParamName { diff --git a/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp b/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp index e3d1a80a..64ae47aa 100644 --- a/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp +++ b/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp @@ -109,7 +109,7 @@ TEST(PropertyBrowserItem, structuralModification_inCollapsedStructure_emits_chil PropertyBrowserItem itemUnderTest{propertyHandle, data.dispatcher, &data.commandInterface, nullptr}; PropertyBrowserItem* childItem{itemUnderTest.children().at(0)}; - itemUnderTest.toggleExpanded(); + itemUnderTest.setExpanded(false); EXPECT_EQ(itemUnderTest.expanded(), false); QSignalSpy spy{&itemUnderTest, &PropertyBrowserItem::childrenChangedOrCollapsedChildChanged}; @@ -129,7 +129,7 @@ TEST(PropertyBrowserItem, setExpanded_influence_showChildren_ifItemHasChildren) EXPECT_EQ(itemUnderTest.expanded(), true); EXPECT_EQ(itemUnderTest.showChildren(), true); - itemUnderTest.toggleExpanded(); + itemUnderTest.setExpanded(false); EXPECT_EQ(itemUnderTest.expanded(), false); EXPECT_EQ(itemUnderTest.showChildren(), false); @@ -144,41 +144,43 @@ TEST(PropertyBrowserItem, setExpanded_doesnt_influence_showChildren_ifItemHasNoC EXPECT_EQ(itemUnderTest.expanded(), true); EXPECT_EQ(itemUnderTest.showChildren(), false); - itemUnderTest.toggleExpanded(); + itemUnderTest.setExpanded(false); EXPECT_EQ(itemUnderTest.expanded(), false); EXPECT_EQ(itemUnderTest.showChildren(), false); } -TEST(PropertyBrowserItem, setExpanded_influence_showControl_ifItemHasChildren) { +TEST(PropertyBrowserItem, setExpandedRecursively) { PropertyBrowserItemTestHelper data{}; - const ValueHandle propertyHandle{data.valueHandle.get("table")}; - data.addPropertyTo("table", PrimitiveType::Double); + const ValueHandle tableHandle{data.valueHandle.get("table")}; + data.addPropertyTo("table", PrimitiveType::Vec3f, "vec"); + const ValueHandle vecHandle{data.valueHandle.get("table").get("vec")}; - PropertyBrowserItem itemUnderTest{propertyHandle, data.dispatcher, &data.commandInterface, nullptr}; + PropertyBrowserItem tableItem{tableHandle, data.dispatcher, &data.commandInterface, nullptr}; + PropertyBrowserItem * vecItem = tableItem.children().front(); - EXPECT_EQ(itemUnderTest.expanded(), true); - EXPECT_EQ(itemUnderTest.showControl(), false); + EXPECT_EQ(tableItem.expanded(), true); + EXPECT_EQ(vecItem->expanded(), true); - itemUnderTest.toggleExpanded(); + tableItem.setExpanded(false); - EXPECT_EQ(itemUnderTest.expanded(), false); - EXPECT_EQ(itemUnderTest.showControl(), true); -} + EXPECT_EQ(tableItem.expanded(), false); + EXPECT_EQ(vecItem->expanded(), true); -TEST(PropertyBrowserItem, setExpanded_doesnt_influence_showControl_ifItemHasNoChildren) { - PropertyBrowserItemTestHelper data{}; - const ValueHandle propertyHandle{data.valueHandle.get("table")}; + tableItem.setExpanded(true); - PropertyBrowserItem itemUnderTest{propertyHandle, data.dispatcher, &data.commandInterface, nullptr}; + EXPECT_EQ(tableItem.expanded(), true); + EXPECT_EQ(vecItem->expanded(), true); - EXPECT_EQ(itemUnderTest.expanded(), true); - EXPECT_EQ(itemUnderTest.showControl(), true); + tableItem.setExpandedRecursively(false); - itemUnderTest.toggleExpanded(); + EXPECT_EQ(tableItem.expanded(), false); + EXPECT_EQ(vecItem->expanded(), false); - EXPECT_EQ(itemUnderTest.expanded(), false); - EXPECT_EQ(itemUnderTest.showControl(), true); + tableItem.setExpandedRecursively(true); + + EXPECT_EQ(tableItem.expanded(), true); + EXPECT_EQ(vecItem->expanded(), true); } } // namespace raco::property_browser diff --git a/gui/libPropertyBrowser/tests/SpinBox_test.cpp b/gui/libPropertyBrowser/tests/SpinBox_test.cpp new file mode 100644 index 00000000..2596e459 --- /dev/null +++ b/gui/libPropertyBrowser/tests/SpinBox_test.cpp @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include "testing/TestEnvironmentCore.h" +#include "property_browser/controls/SpinBox.h" + +class SpinBoxFixture : public TestEnvironmentCore {}; + +using namespace raco::property_browser; + +TEST_F(SpinBoxFixture, evaluateLuaExpressionDouble) { + ASSERT_DOUBLE_EQ(12.345, evaluateLuaExpression("12.345").value_or(0)); + ASSERT_DOUBLE_EQ(-12.345, evaluateLuaExpression("-12.345").value_or(0)); + ASSERT_DOUBLE_EQ(12.345, evaluateLuaExpression("12,345").value_or(0)); + ASSERT_DOUBLE_EQ(-12.345, evaluateLuaExpression("-12,345").value_or(0)); + ASSERT_DOUBLE_EQ(0.345, evaluateLuaExpression(".345").value_or(0)); + ASSERT_DOUBLE_EQ(-0.345, evaluateLuaExpression("-.345").value_or(0)); + ASSERT_DOUBLE_EQ(-0.345 * 2, evaluateLuaExpression("-.345 * 2").value_or(0)); + ASSERT_DOUBLE_EQ(4 + -0.345 * 2, evaluateLuaExpression("4 + -.345 * 2").value_or(0)); + ASSERT_DOUBLE_EQ(1 - -0.345 / 2, evaluateLuaExpression("1 - -.345 / 2 ").value_or(0)); + ASSERT_DOUBLE_EQ(-0.345 * -0.345, evaluateLuaExpression("(-.345) ^ 2").value_or(0)); + + // Forbidden elements are thousand seperators, invalid numeric expressions and function calls to the standard library. + ASSERT_FALSE(evaluateLuaExpression("1.000,0").has_value()); + ASSERT_FALSE(evaluateLuaExpression("1,000.0").has_value()); + ASSERT_FALSE(evaluateLuaExpression("1abc").has_value()); + ASSERT_FALSE(evaluateLuaExpression("print('TEST') or 15").has_value()); + ASSERT_FALSE(evaluateLuaExpression("math.sin(1.2)").has_value()); + + // Limitation: Lua is not type safe, so strings are valid numbers. + ASSERT_DOUBLE_EQ(1.02, evaluateLuaExpression("'1.0' .. '2'").value_or(0)); + + // As an undocumented feature, it is still possible to use lambda calculus to compute arbitary expressions. Since "," is always replaced by ".", function currying (e.g. max 1 argument per function) is required. + // To demonstrate computation capabilities, here we compute PI using the Nilakantha series. + ASSERT_NEAR(M_PI, evaluateLuaExpression("(function(f) return 3 + f(f)(2) end)(function (f) return function(n) if (n < 100) then return 4 / (n * (n + 1) * (n + 2)) * -((-1)^(n/2)) + f(f)(n + 2) else return 0 end end end)").value_or(0), 0.0001); + + // If the user enters an infinite loop, program excecution should be aborted without a result after some time. + ASSERT_FALSE(evaluateLuaExpression("(function() while(true)do end end)()").has_value()); +} + +TEST_F(SpinBoxFixture, evaluateLuaExpressionInteger) { + ASSERT_EQ(12, evaluateLuaExpression("12").value_or(0)); + ASSERT_EQ(-12, evaluateLuaExpression("-12").value_or(0)); + ASSERT_EQ(3 + 4 * 5, evaluateLuaExpression("3 + 4 * 5").value_or(0)); + ASSERT_EQ(3 - 45 % 6, evaluateLuaExpression("3 - 45 % 6").value_or(0)); + + // Rounding will aways happen by truncation to integers. + ASSERT_EQ(12, evaluateLuaExpression("12.345").value_or(0)); + ASSERT_EQ(-12, evaluateLuaExpression("-12.345").value_or(0)); + ASSERT_EQ(12, evaluateLuaExpression("12.567").value_or(0)); + ASSERT_EQ(-12, evaluateLuaExpression("-12.567").value_or(0)); + + // Rounding happens after computation, integer division needs to happen explicitly. + ASSERT_EQ((int)(1.0 / 2.0 * 4.0), evaluateLuaExpression("1 / 2 * 4").value_or(0)); + ASSERT_EQ(1 / 2 * 4, evaluateLuaExpression("1 // 2 * 4").value_or(0)); + + // Forbidden elements are thousand seperators, invalid numeric expressions and function calls to the standard library. + ASSERT_FALSE(evaluateLuaExpression("1.000,0").has_value()); + ASSERT_FALSE(evaluateLuaExpression("1,000.0").has_value()); + ASSERT_FALSE(evaluateLuaExpression("1abc").has_value()); + ASSERT_FALSE(evaluateLuaExpression("print('TEST') or 15").has_value()); + ASSERT_FALSE(evaluateLuaExpression("math.sin(1.2)").has_value()); + + // Limitation: Lua is not type safe, so strings are valid numbers. + ASSERT_EQ(12, evaluateLuaExpression("'1' .. '2'").value_or(0)); + + // If the user enters an infinite loop, program excecution should be aborted without a result after some time. + ASSERT_FALSE(evaluateLuaExpression("(function() while(true)do end end)()").has_value()); +} \ No newline at end of file diff --git a/gui/libPropertyBrowser/tests/URIEditor_test.cpp b/gui/libPropertyBrowser/tests/URIEditor_test.cpp index 010db9d2..c0eb767e 100644 --- a/gui/libPropertyBrowser/tests/URIEditor_test.cpp +++ b/gui/libPropertyBrowser/tests/URIEditor_test.cpp @@ -40,9 +40,9 @@ class URIEditorTest : public EditorTestFixture { ValueHandle propertyHandle{data.valueHandle.get("uri")}; PropertyBrowserItem propertyBrowserItem{propertyHandle, dataChangeDispatcher, &commandInterface, &model}; ExposedURIEditor uriEditor{&propertyBrowserItem}; - std::string projectPath{(cwd_path() / "project" / "projectFileName").generic_string()}; - std::string absoluteMeshPath{(cwd_path() / "meshes" / "Duck.glb").generic_string()}; - std::string relativeMeshPath{PathManager::constructRelativePath(absoluteMeshPath, std::filesystem::path(projectPath).parent_path().string())}; + std::string projectPath{(test_path() / "project" / "projectFileName").string()}; + std::string absoluteMeshPath{(test_path() / "meshes" / "Duck.glb").string()}; + std::string relativeMeshPath{raco::utils::u8path(absoluteMeshPath).normalizedRelativePath(raco::utils::u8path(projectPath).parent_path()).string()}; URIEditorTest() { project.setCurrentPath(projectPath); @@ -85,7 +85,7 @@ TEST_F(URIEditorTest, InstantiationIsDefaultRelative) { } TEST_F(URIEditorTest, ModificationAddNonExistentPath) { - propertyBrowserItem.set((cwd_path() / "THISSHOULDNOTEXIST.txt").string()); + propertyBrowserItem.set((test_path() / "THISSHOULDNOTEXIST.txt").string()); ASSERT_TRUE(propertyBrowserItem.hasError()); ASSERT_EQ(propertyBrowserItem.error().level(), ErrorLevel::ERROR); @@ -151,12 +151,12 @@ TEST_F(URIEditorTest, ModificationChangeRelativeToAbsolutePathByUserAction) { } TEST_F(URIEditorTest, ModificationRerootRelativePath) { - std::string newProjectPath{(cwd_path() / "project" / "projectSubFolder" / "projectFileName").generic_string()}; - std::string newRelativeMeshPath{PathManager::constructRelativePath(absoluteMeshPath, std::filesystem::path(newProjectPath).parent_path().string())}; + auto newProjectPath = test_path() / "project" / "projectSubFolder" / "projectFileName"; + auto newRelativeMeshPath = raco::utils::u8path(absoluteMeshPath).normalizedRelativePath(newProjectPath.parent_path()); setLineEditText(relativeMeshPath); - propertyBrowserItem.set(PathManager::rerootRelativePath(relativeMeshPath, projectPath, newProjectPath)); + propertyBrowserItem.set(raco::utils::u8path(relativeMeshPath).rerootRelativePath(projectPath, newProjectPath).string()); valueChanged(); ASSERT_EQ(uriEditor.getLineEdit()->text().toStdString(), newRelativeMeshPath); diff --git a/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h b/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h index 839bd505..7724a464 100644 --- a/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h +++ b/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h @@ -15,8 +15,10 @@ #include #include + namespace raco::ramses_widgets { + class PreviewContentWidget final : public QWidget { Q_OBJECT Q_DISABLE_COPY(PreviewContentWidget) @@ -26,6 +28,7 @@ class PreviewContentWidget final : public QWidget { ramses::sceneId_t getSceneId(); void setSceneId(ramses::sceneId_t id); void setBackgroundColor(data_storage::Vec4f backgroundColor); + void setFilteringMode(PreviewFilteringMode mode); void commit(); public Q_SLOTS: @@ -36,6 +39,7 @@ public Q_SLOTS: const QSize viewportSize, const QSize virtualSize, const QSize targetSize); + Q_SIGNALS: void newMousePosition(const QPoint globalPosition); diff --git a/gui/libRamsesWidgets/include/ramses_widgets/PreviewFramebufferScene.h b/gui/libRamsesWidgets/include/ramses_widgets/PreviewFramebufferScene.h index df5000e3..c5a7e19d 100644 --- a/gui/libRamsesWidgets/include/ramses_widgets/PreviewFramebufferScene.h +++ b/gui/libRamsesWidgets/include/ramses_widgets/PreviewFramebufferScene.h @@ -15,6 +15,11 @@ namespace raco::ramses_widgets { +enum class PreviewFilteringMode { + NearestNeighbor, + Linear +}; + class PreviewFramebufferScene final { Q_DISABLE_COPY(PreviewFramebufferScene); @@ -22,7 +27,7 @@ class PreviewFramebufferScene final { explicit PreviewFramebufferScene(ramses::RamsesClient& client, ramses::sceneId_t sceneId); ramses::sceneId_t getSceneId() const; - ramses::dataConsumerId_t setupFramebufferTexture(RendererBackend& backend, const QSize& size); + ramses::dataConsumerId_t setupFramebufferTexture(RendererBackend& backend, const QSize& size, PreviewFilteringMode filteringMode); void setViewport(const QPoint& viewportPosition, const QSize& viewportSize, const QSize& virtualSize); private: diff --git a/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h b/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h index b05073b3..f6162a87 100644 --- a/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h +++ b/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h @@ -38,6 +38,7 @@ class RamsesPreviewWindow final { QSize targetSize{0, 0}; QSize virtualSize{0, 0}; QColor backgroundColor{}; + PreviewFilteringMode filteringMode{PreviewFilteringMode::NearestNeighbor}; }; explicit RamsesPreviewWindow( diff --git a/gui/libRamsesWidgets/src/PreviewContentWidget.cpp b/gui/libRamsesWidgets/src/PreviewContentWidget.cpp index cd742ac2..fb91e057 100644 --- a/gui/libRamsesWidgets/src/PreviewContentWidget.cpp +++ b/gui/libRamsesWidgets/src/PreviewContentWidget.cpp @@ -93,6 +93,14 @@ void PreviewContentWidget::setViewportRect( update(); } +void PreviewContentWidget::setFilteringMode(PreviewFilteringMode filteringMode) { + if (ramsesPreview_) { + ramsesPreview_->state().filteringMode = filteringMode; + update(); + } +} + + void PreviewContentWidget::commit() { ramsesPreview_->commit(); } diff --git a/gui/libRamsesWidgets/src/PreviewFramebufferScene.cpp b/gui/libRamsesWidgets/src/PreviewFramebufferScene.cpp index 4a045918..b2346499 100644 --- a/gui/libRamsesWidgets/src/PreviewFramebufferScene.cpp +++ b/gui/libRamsesWidgets/src/PreviewFramebufferScene.cpp @@ -132,8 +132,13 @@ ramses::sceneId_t PreviewFramebufferScene::getSceneId() const { return scene_->getSceneId(); } -ramses::dataConsumerId_t PreviewFramebufferScene::setupFramebufferTexture(RendererBackend& backend, const QSize& size) { - if (framebufferTexture_ && framebufferTexture_->getWidth() == size.width() && framebufferTexture_->getHeight() == size.height()) { +ramses::dataConsumerId_t PreviewFramebufferScene::setupFramebufferTexture(RendererBackend& backend, const QSize& size, PreviewFilteringMode filteringMode) { + ramses::ETextureSamplingMethod samplingMethod = ramses::ETextureSamplingMethod_Nearest; + if (filteringMode == PreviewFilteringMode::Linear) { + samplingMethod = ramses::ETextureSamplingMethod_Linear; + } + + if (framebufferTexture_ && framebufferTexture_->getWidth() == size.width() && framebufferTexture_->getHeight() == size.height() && sampler_->getMagSamplingMethod() == samplingMethod && sampler_->getMinSamplingMethod() == samplingMethod) { return framebufferSampleId_; } @@ -147,7 +152,9 @@ ramses::dataConsumerId_t PreviewFramebufferScene::setupFramebufferTexture(Render const ramses::TextureSwizzle textureSwizzle{}; framebufferTexture_ = ramsesTexture2D(scene_.get(), ramses::ETextureFormat::RGBA8, size.width(), size.height(), 1, &mipData, false, textureSwizzle, ramses::ResourceCacheFlag_DoNotCache, "framebuffer texture"); - sampler_ = ramsesTextureSampler(scene_.get(), ramses::ETextureAddressMode_Clamp, ramses::ETextureAddressMode_Clamp, ramses::ETextureSamplingMethod_Nearest, ramses::ETextureSamplingMethod_Nearest, framebufferTexture_.get(), 1, "framebuffer sampler"); + + + sampler_ = ramsesTextureSampler(scene_.get(), ramses::ETextureAddressMode_Clamp, ramses::ETextureAddressMode_Clamp, samplingMethod, samplingMethod, framebufferTexture_.get(), 1, "framebuffer sampler"); (*appearance_)->setInputTexture(texUniformInput, *sampler_.get()); scene_->flush(); diff --git a/gui/libRamsesWidgets/src/PreviewMainWindow.cpp b/gui/libRamsesWidgets/src/PreviewMainWindow.cpp index fe1cc7a7..47e9969e 100644 --- a/gui/libRamsesWidgets/src/PreviewMainWindow.cpp +++ b/gui/libRamsesWidgets/src/PreviewMainWindow.cpp @@ -70,6 +70,11 @@ PreviewMainWindow::PreviewMainWindow(RendererBackend& rendererBackend, raco::ram sizeMenu->addAction(ui_->actionSetSizeModeHorizontalFit); sizeMenu->addAction(ui_->actionSetSizeModeBestFit); sizeMenu->addAction(ui_->actionSetSizeModeOriginalFit); + ui_->actionSetSizeModeOff->setCheckable(true); + ui_->actionSetSizeModeVerticalFit->setCheckable(true); + ui_->actionSetSizeModeHorizontalFit->setCheckable(true); + ui_->actionSetSizeModeBestFit->setCheckable(true); + ui_->actionSetSizeModeOriginalFit->setCheckable(true); connect(ui_->actionSetSizeModeOff, &QAction::triggered, scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingOff); connect(ui_->actionSetSizeModeVerticalFit, &QAction::triggered, scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingVerticalFit); connect(ui_->actionSetSizeModeHorizontalFit, &QAction::triggered, scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingHorizontalFit); @@ -78,27 +83,62 @@ PreviewMainWindow::PreviewMainWindow(RendererBackend& rendererBackend, raco::ram auto* sizeMenuButton = new QToolButton{this}; sizeMenuButton->setMenu(sizeMenu); sizeMenuButton->setPopupMode(QToolButton::InstantPopup); - connect(scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingChanged, [=](PreviewScrollAreaWidget::AutoSizing mode) { + connect(scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingChanged, [this, sizeMenuButton](PreviewScrollAreaWidget::AutoSizing mode) { + auto action = ui_->actionSetSizeModeOff; switch (mode) { - case PreviewScrollAreaWidget::AutoSizing::OFF: - sizeMenuButton->setDefaultAction(ui_->actionSetSizeModeOff); - break; case PreviewScrollAreaWidget::AutoSizing::VERTICAL_FIT: - sizeMenuButton->setDefaultAction(ui_->actionSetSizeModeVerticalFit); + action = ui_->actionSetSizeModeVerticalFit; break; case PreviewScrollAreaWidget::AutoSizing::HORIZONTAL_FIT: - sizeMenuButton->setDefaultAction(ui_->actionSetSizeModeHorizontalFit); + action = ui_->actionSetSizeModeHorizontalFit; break; case PreviewScrollAreaWidget::AutoSizing::BEST_FIT: - sizeMenuButton->setDefaultAction(ui_->actionSetSizeModeBestFit); + action = ui_->actionSetSizeModeBestFit; break; case PreviewScrollAreaWidget::AutoSizing::ORIGINAL_FIT: - sizeMenuButton->setDefaultAction(ui_->actionSetSizeModeOriginalFit); + action = ui_->actionSetSizeModeOriginalFit; break; }; + sizeMenuButton->setText(action->text()); + ui_->actionSetSizeModeOff->setChecked(false); + ui_->actionSetSizeModeVerticalFit->setChecked(false); + ui_->actionSetSizeModeHorizontalFit->setChecked(false); + ui_->actionSetSizeModeBestFit->setChecked(false); + ui_->actionSetSizeModeOriginalFit->setChecked(false); + action->setChecked(true); }); ui_->toolBar->insertWidget(ui_->actionSelectSizeMode, sizeMenuButton); } + // Filtering mode tool button + { + auto* filteringMenu = new QMenu{this}; + filteringMenu->addAction(ui_->actionSetFilteringModeNearestNeighbor); + filteringMenu->addAction(ui_->actionSetFilteringModeLinear); + ui_->actionSetFilteringModeNearestNeighbor->setCheckable(true); + ui_->actionSetFilteringModeNearestNeighbor->setChecked(true); + + + auto* filteringMenuButton = new QToolButton{this}; + filteringMenuButton->setMenu(filteringMenu); + filteringMenuButton->setPopupMode(QToolButton::InstantPopup); + + ui_->actionSetFilteringModeLinear->setCheckable(true); + filteringMenuButton->setText(ui_->actionSetFilteringModeNearestNeighbor->text()); + + connect(ui_->actionSetFilteringModeNearestNeighbor, &QAction::triggered, this, [this, filteringMenuButton]() { + previewWidget_->setFilteringMode(PreviewFilteringMode::NearestNeighbor); + filteringMenuButton->setText(ui_->actionSetFilteringModeNearestNeighbor->text()); + ui_->actionSetFilteringModeNearestNeighbor->setChecked(true); + ui_->actionSetFilteringModeLinear->setChecked(false); + }); + connect(ui_->actionSetFilteringModeLinear, &QAction::triggered, this, [this, filteringMenuButton]() { + previewWidget_->setFilteringMode(PreviewFilteringMode::Linear); + filteringMenuButton->setText(ui_->actionSetFilteringModeLinear->text()); + ui_->actionSetFilteringModeNearestNeighbor->setChecked(false); + ui_->actionSetFilteringModeLinear->setChecked(true); + }); + ui_->toolBar->insertWidget(ui_->actionSelectFilteringMode, filteringMenuButton); + } } PreviewMainWindow::~PreviewMainWindow() { diff --git a/gui/libRamsesWidgets/src/PreviewMainWindow.ui b/gui/libRamsesWidgets/src/PreviewMainWindow.ui index ff1a3f81..18d6db1e 100644 --- a/gui/libRamsesWidgets/src/PreviewMainWindow.ui +++ b/gui/libRamsesWidgets/src/PreviewMainWindow.ui @@ -1,95 +1,97 @@ - PreviewMainWindow - - - - 0 - 0 - 800 - 600 - - - - - - - false - - - - - - None - - - None - - - - - Scene 1 - - - Scene 1 - - - - - Scene 2 - - - Scene 2 - - - - - Scene Truck - - - Scene Truck - - - - - - Off - - - Off - - - - - Vertical fit - - - Vertical fit - - - - - Horizontal fit - - - Horizontal fit - - - - - Best fit - - - Best fit - - - - - Original fit - - - Original fit - - - + PreviewMainWindow + + + + 0 + 0 + 800 + 600 + + + + Qt::NoContextMenu + + + + + Qt::NoContextMenu + + + + + Qt::NoContextMenu + + + false + + + TopToolBarArea + + + false + + + + + + Manual zoom + + + Manual zoom + + + + + Vertical fit + + + Vertical fit + + + + + Horizontal fit + + + Horizontal fit + + + + + Best fit + + + Best fit + + + + + Original fit + + + Original fit + + + + + + Nearest filtering + + + Nearest filtering + + + + + Linear filtering + + + Linear filtering + + + + + diff --git a/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp b/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp index d971c05a..eafae48f 100644 --- a/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp +++ b/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp @@ -56,7 +56,7 @@ void reduceAndWaitSceneState( const ramses::sceneId_t sceneId) { auto& eventHandler = backend.eventHandler(); auto& sceneControlAPI = *backend.renderer().getSceneControlAPI(); - if (framebufferScene && framebufferScene->getSceneId().isValid()) { + if (framebufferScene && framebufferScene->getSceneId().isValid() && eventHandler.sceneState(framebufferScene->getSceneId()) > state) { sceneControlAPI.setSceneState(framebufferScene->getSceneId(), state); } if (sceneId.isValid() && eventHandler.sceneState(sceneId) > state) { @@ -102,7 +102,7 @@ RamsesPreviewWindow::State& RamsesPreviewWindow::state() { } void RamsesPreviewWindow::commit() { - if (!displayId_.isValid() || next_.viewportSize != current_.viewportSize || next_.sceneId != current_.sceneId || next_.targetSize != current_.targetSize) { + if (!displayId_.isValid() || next_.viewportSize != current_.viewportSize || next_.sceneId != current_.sceneId || next_.targetSize != current_.targetSize || next_.filteringMode != current_.filteringMode) { // Unload current scenes reduceAndWaitSceneState(rendererBackend_, (displayId_.isValid()) ? ramses::RendererSceneState::Available : ramses::RendererSceneState::Unavailable, framebufferScene_, current_.sceneId); @@ -157,7 +157,8 @@ void RamsesPreviewWindow::commit() { // but an offscreen render buffer created with RamsesRenderer::createOffscreenBuffer to avoid creating the // offscreen render buffer in the scene we eventually export (and in fact for Ramses this offscreen render buffer is // the framebuffer - that we use it later on to blit it into our preview makes for the Ramses scene no difference). - const ramses::dataConsumerId_t dataConsumerId = framebufferScene_->setupFramebufferTexture(rendererBackend_, next_.targetSize); + const ramses::dataConsumerId_t dataConsumerId = framebufferScene_->setupFramebufferTexture(rendererBackend_, next_.targetSize, next_.filteringMode); + current_.filteringMode = next_.filteringMode; offscreenBufferId_ = rendererBackend_.renderer().createOffscreenBuffer(displayId_, next_.targetSize.width(), next_.targetSize.height()); rendererBackend_.renderer().flush(); rendererBackend_.eventHandler().waitForOffscreenBufferCreation(offscreenBufferId_); diff --git a/gui/libRamsesWidgets/src/RendererBackend.cpp b/gui/libRamsesWidgets/src/RendererBackend.cpp index 97bf16a6..7d743c0c 100644 --- a/gui/libRamsesWidgets/src/RendererBackend.cpp +++ b/gui/libRamsesWidgets/src/RendererBackend.cpp @@ -46,6 +46,7 @@ RendererBackend::RendererBackend(const std::string& frameworkArgs) } RendererBackend::~RendererBackend() { + framework().disconnect(); } ramses::RamsesRenderer& RendererBackend::renderer() const { diff --git a/gui/libStyle/include/style/Icons.h b/gui/libStyle/include/style/Icons.h index 77c2bb22..2fa26bbc 100644 --- a/gui/libStyle/include/style/Icons.h +++ b/gui/libStyle/include/style/Icons.h @@ -29,9 +29,9 @@ enum class Pixmap { linkable, linked, - parent_is_linked, + parentIsLinked, unlinkable, - link_broken, + linkBroken, warning, error, @@ -39,8 +39,8 @@ enum class Pixmap { close, undock, menu, - open_in_new, - go_to, + openInNew, + goTo, typeNode, typeCamera, diff --git a/gui/libStyle/src/Icons.cpp b/gui/libStyle/src/Icons.cpp index 1acc8a41..6ed3c03d 100644 --- a/gui/libStyle/src/Icons.cpp +++ b/gui/libStyle/src/Icons.cpp @@ -21,16 +21,16 @@ Icons& Icons::instance() { {Pixmap::collapsed, QPixmap{":collapsedIcon"}}, {Pixmap::linkable, QPixmap{":linkableIcon"}}, {Pixmap::linked, QPixmap{":linkedIcon"}}, - {Pixmap::parent_is_linked, QPixmap{":parentLinkedIcon"}}, + {Pixmap::parentIsLinked, QPixmap{":parentLinkedIcon"}}, {Pixmap::unlinkable, QPixmap{":unlinkableIcon"}}, - {Pixmap::link_broken, QPixmap{":linkBrokenIcon"}}, + {Pixmap::linkBroken, QPixmap{":linkBrokenIcon"}}, {Pixmap::locked, QPixmap{":lockedIcon"}}, {Pixmap::unlocked, QPixmap{":unlockedIcon"}}, {Pixmap::close, QPixmap{":closeIcon"}}, {Pixmap::undock, QPixmap{":undockIcon"}}, {Pixmap::menu, QPixmap{":menuIcon"}}, - {Pixmap::open_in_new, QPixmap{":openInNewIcon"}}, - {Pixmap::go_to, QPixmap{":gotoIcon"}}, + {Pixmap::openInNew, QPixmap{":openInNewIcon"}}, + {Pixmap::goTo, QPixmap{":gotoIcon"}}, {Pixmap::increment, QPixmap{":incrementIcon"}}, {Pixmap::decrement, QPixmap{":decrementIcon"}}, {Pixmap::warning, QPixmap{":warningIcon"}}, diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 0492f92f..5d5a55e2 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -51,6 +51,7 @@ set(RESOURCE_FILES shaders/default.vert shaders/simple_texture.frag shaders/simple_texture.vert + example_scene.rca ) foreach(relpath ${RESOURCE_FILES}) diff --git a/resources/example_scene.rca b/resources/example_scene.rca new file mode 100644 index 00000000..c2e46e7e --- /dev/null +++ b/resources/example_scene.rca @@ -0,0 +1,966 @@ +{ + "externalProjects": { + }, + "fileVersion": 22, + "instances": [ + { + "properties": { + "frustum": { + "aspectRatio": { + "annotations": [ + { + "properties": { + "max": 4, + "min": 0.5 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 2 + }, + "farPlane": { + "annotations": [ + { + "properties": { + "max": 10000, + "min": 100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1000 + }, + "fieldOfView": { + "annotations": [ + { + "properties": { + "max": 120, + "min": 10 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 35 + }, + "nearPlane": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0.1 + } + }, + "objectID": "4e2412c6-52c5-42d0-8746-17c42bb903c0", + "objectName": "PerspectiveCamera", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 10 + } + }, + "viewport": { + "height": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 720 + }, + "offsetX": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": -7680 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 0 + }, + "offsetY": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": -7680 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 0 + }, + "width": { + "annotations": [ + { + "properties": { + "max": 7680, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1440 + } + }, + "visible": true + }, + "typeName": "PerspectiveCamera" + }, + { + "properties": { + "camera": "4e2412c6-52c5-42d0-8746-17c42bb903c0", + "clearColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "enableClearColor": true, + "enableClearDepth": true, + "enableClearStencil": true, + "enabled": true, + "layer0": "261a7740-ca6a-4b3e-99f6-3299fa95e35c", + "layer1": null, + "layer2": null, + "layer3": null, + "layer4": null, + "layer5": null, + "layer6": null, + "layer7": null, + "objectID": "2f113aa5-64b5-4216-b3ce-c4e2ab4c570e", + "objectName": "MainRenderPass", + "order": 1, + "target": null + }, + "typeName": "RenderPass" + }, + { + "properties": { + "invertMaterialFilter": true, + "objectID": "261a7740-ca6a-4b3e-99f6-3299fa95e35c", + "objectName": "MainRenderLayer", + "renderableTags": { + "order": [ + "render_main" + ], + "properties": { + "render_main": { + "typeName": "Int", + "value": 0 + } + } + }, + "sortOrder": 0 + }, + "typeName": "RenderLayer" + }, + { + "properties": { + "backgroundColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "defaultResourceFolders": { + "imageSubdirectory": "images", + "meshSubdirectory": "meshes", + "scriptSubdirectory": "scripts", + "shaderSubdirectory": "shaders" + }, + "enableTimerFlag": false, + "objectID": "c4732350-9a9b-4da4-8b5e-c8d65ce5757e", + "objectName": "example_scene", + "runTimer": false, + "sceneId": { + "annotations": [ + { + "properties": { + "max": 1024, + "min": 1 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 123 + }, + "viewport": { + "i1": { + "annotations": [ + { + "properties": { + "max": 4096, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1440 + }, + "i2": { + "annotations": [ + { + "properties": { + "max": 4096, + "min": 0 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 720 + } + } + }, + "typeName": "ProjectSettings" + }, + { + "properties": { + "bakeMeshes": true, + "materialNames": { + "properties": [ + { + "typeName": "String", + "value": "material" + } + ] + }, + "meshIndex": 0, + "objectID": "f62b29a4-87ea-4f2c-9558-e7637134dd82", + "objectName": "DuckMesh", + "uri": "meshes/Duck.glb" + }, + "typeName": "Mesh" + }, + { + "properties": { + "objectID": "bee9db1d-a133-4306-9e80-54a2491f28b5", + "objectName": "DuckMaterial", + "options": { + "blendColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "blendFactorDestAlpha": 1, + "blendFactorDestColor": 3, + "blendFactorSrcAlpha": 1, + "blendFactorSrcColor": 2, + "blendOperationAlpha": 0, + "blendOperationColor": 0, + "cullmode": 2, + "depthFunction": 4, + "depthwrite": true + }, + "uniforms": { + "order": [ + "u_Tex" + ], + "properties": { + "u_Tex": { + "annotations": [ + { + "properties": { + "engineType": 15 + }, + "typeName": "EngineTypeAnnotation" + } + ], + "typeName": "TextureSampler2DBase::EngineTypeAnnotation", + "value": "c9f1cd84-8eb6-4140-8f6e-e850a5622e20" + } + } + }, + "uriDefines": "", + "uriFragment": "shaders/simple_texture.frag", + "uriGeometry": "", + "uriVertex": "shaders/simple_texture.vert" + }, + "typeName": "Material" + }, + { + "properties": { + "anisotropy": { + "annotations": [ + { + "properties": { + "max": 32000, + "min": 1 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1 + }, + "flipTexture": false, + "generateMipmaps": false, + "magSamplingMethod": 0, + "minSamplingMethod": 0, + "objectID": "c9f1cd84-8eb6-4140-8f6e-e850a5622e20", + "objectName": "DuckTexture", + "uri": "images/DuckCM.png", + "wrapUMode": 0, + "wrapVMode": 0 + }, + "typeName": "Texture" + }, + { + "properties": { + "children": { + "properties": [ + { + "typeName": "Ref", + "value": "db79d496-7f6c-44f6-8c32-91f8830643e8" + } + ] + }, + "objectID": "87085c5f-8ac5-432a-944a-cf5bea48bcc1", + "objectName": "DuckNode", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 1 + } + }, + "tags": { + "properties": [ + { + "typeName": "String", + "value": "render_main" + } + ] + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "visible": true + }, + "typeName": "Node" + }, + { + "properties": { + "instanceCount": { + "annotations": [ + { + "properties": { + "max": 20, + "min": 1 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1 + }, + "materials": { + "order": [ + "material" + ], + "properties": { + "material": { + "order": [ + "material", + "private", + "options", + "uniforms" + ], + "properties": { + "material": { + "typeName": "Material", + "value": "bee9db1d-a133-4306-9e80-54a2491f28b5" + }, + "options": { + "annotations": [ + { + "properties": { + "name": "Options" + }, + "typeName": "DisplayNameAnnotation" + } + ], + "properties": { + "blendColor": { + "w": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "x": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 1, + "min": 0 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "blendFactorDestAlpha": 1, + "blendFactorDestColor": 3, + "blendFactorSrcAlpha": 1, + "blendFactorSrcColor": 2, + "blendOperationAlpha": 0, + "blendOperationColor": 0, + "cullmode": 2, + "depthFunction": 4, + "depthwrite": true + }, + "typeName": "BlendOptions::DisplayNameAnnotation" + }, + "private": { + "annotations": [ + { + "properties": { + "name": "Private Material" + }, + "typeName": "DisplayNameAnnotation" + } + ], + "typeName": "Bool::DisplayNameAnnotation", + "value": false + }, + "uniforms": { + "typeName": "Table" + } + }, + "typeName": "Table" + } + } + }, + "mesh": "f62b29a4-87ea-4f2c-9558-e7637134dd82", + "objectID": "db79d496-7f6c-44f6-8c32-91f8830643e8", + "objectName": "DuckMeshNode", + "rotation": { + "x": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": -160 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 360, + "min": -360 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "scale": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 2 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 2 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": 0.1 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 2 + } + }, + "tags": { + "properties": [ + { + "typeName": "String", + "value": "render_main" + } + ] + }, + "translation": { + "x": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + }, + "y": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": -1.7 + }, + "z": { + "annotations": [ + { + "properties": { + "max": 100, + "min": -100 + }, + "typeName": "RangeAnnotationDouble" + } + ], + "value": 0 + } + }, + "visible": true + }, + "typeName": "MeshNode" + } + ], + "links": [ + ], + "logicEngineVersion": [ + 0, + 13, + 0 + ], + "racoVersion": [ + 0, + 11, + 1 + ], + "ramsesVersion": [ + 27, + 0, + 114 + ] +} diff --git a/styles/icons.qrc b/styles/icons.qrc index c9106120..e05ebdc7 100644 --- a/styles/icons.qrc +++ b/styles/icons.qrc @@ -39,4 +39,4 @@ _Default/icons/baseline_crop_free_white_18dp.png _Default/icons/baseline_view_module_white_18dp.png - + \ No newline at end of file diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 90fb4dbf..1dd7a138 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -1,3 +1,4 @@ +set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE) add_subdirectory(spdlog/) set_target_properties(spdlog PROPERTIES FOLDER third_party) @@ -110,3 +111,47 @@ endif() add_ramses_logic_target(ramses-logic-shared-lib-client-only ramses-shared-lib-client-only) add_library(raco::ramses-logic-lib-client-only ALIAS ramses-logic-shared-lib-client-only) add_library(raco::ramses-lib-client-only ALIAS ramses-shared-lib-client-only) + + +add_subdirectory(ramses-logic/tools/ramses-logic-viewer) +folderize_target(ramses-logic-viewer "third_party/ramses-logic") +target_include_directories(ramses-logic-viewer PRIVATE + ramses-logic/lib +) + +# unlink original "rlogic::ramses-logic" targets from ramses-logic-viewer to make it work with our own ramses and ramses-logic .dlls +get_target_property(VIEWER_LINKED_LIBS ramses-logic-viewer LINK_LIBRARIES) +list(REMOVE_ITEM VIEWER_LINKED_LIBS rlogic::ramses-logic rlogic::ramses-logic-static) +set_property(TARGET ramses-logic-viewer PROPERTY LINK_LIBRARIES ${VIEWER_LINKED_LIBS}) +set_property(TARGET ramses-logic-viewer PROPERTY CXX_STANDARD 17) + +target_link_libraries(ramses-logic-viewer PRIVATE + raco::ramses-lib + raco::ramses-logic-lib-client-only +) + +IF (MSVC) + # /bigobj is needed to compile LogicViewer.cpp + target_compile_options(ramses-logic-viewer PRIVATE "/bigobj") +endif() + +if (CMAKE_SYSTEM_NAME STREQUAL Linux) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8) + target_compile_definitions(ramses-logic-viewer PRIVATE RLOGIC_STD_FILESYSTEM_EXPERIMENTAL) + endif() + + find_program(LINUXDEPLOYQT linuxdeployqt) + if(EXISTS "${LINUXDEPLOYQT}") + add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/ramses-logic-viewer + COMMAND ${CMAKE_COMMAND} -E rm -f "$/ramses-logic-viewer.sh" + COMMAND ${CMAKE_COMMAND} -D TARGET_FILE=$/ramses-logic-viewer -D ROOT_DIR=${CMAKE_SOURCE_DIR} -P "${CMAKE_SOURCE_DIR}/ubuntustartscript.cmake" + COMMAND chmod +x "$/ramses-logic-viewer.sh" + DEPENDS ramses-logic-viewer RaCoEditor + ) + + add_custom_target(generate_ramses_logic_viewer_launch_script ALL + DEPENDS ${CMAKE_BINARY_DIR}/ramses-logic-viewer + ) + endif() +endif() + diff --git a/third_party/ramses-logic b/third_party/ramses-logic index c4f05291..1df2d5d7 160000 --- a/third_party/ramses-logic +++ b/third_party/ramses-logic @@ -1 +1 @@ -Subproject commit c4f05291c50719d0b56c919af4cf0d5b0bfdaf37 +Subproject commit 1df2d5d7ed1adda2248cf6ac5849c62e26a67635 diff --git a/utils/libLogSystem/include/log_system/log.h b/utils/libLogSystem/include/log_system/log.h index 13f20de2..9869d8b6 100644 --- a/utils/libLogSystem/include/log_system/log.h +++ b/utils/libLogSystem/include/log_system/log.h @@ -13,6 +13,7 @@ #define SPDLOG_ACTIVE_LEVEL 0 #include #include +#include namespace raco::log_system { @@ -36,10 +37,14 @@ constexpr const char* DESERIALIZATION{"DESERIALIZATION"}; constexpr const char* PROJECT{"PROJECT"}; constexpr const char* MESH_LOADER{"MESH_LOADER"}; -void init(const char* fileName = nullptr); +constexpr size_t MAX_LOG_FILE_SIZE_BYTES = 10 * 1024 * 1024; +constexpr size_t MAX_LOG_FILE_AMOUNT = 25; + +void init(const spdlog::filename_t& logFileName = SPDLOG_FILENAME_T("")); void deinit(); void registerSink(const SinkPtr sink); void unregisterSink(const SinkPtr sink); +void setConsoleLogLevel(spdlog::level::level_enum level); LoggerPtr get(const char* category); } // namespace raco::log_system diff --git a/utils/libLogSystem/src/log.cpp b/utils/libLogSystem/src/log.cpp index d48bcaf9..befdcc1e 100644 --- a/utils/libLogSystem/src/log.cpp +++ b/utils/libLogSystem/src/log.cpp @@ -10,16 +10,19 @@ #include "log_system/log.h" #include -#include #include #include +#include #include +#include +#include namespace raco::log_system { bool initialized{false}; -std::shared_ptr multiplexSink = std::make_shared(); +std::shared_ptr multiplexSink; +std::shared_ptr consoleSink; std::vector sinks{}; std::shared_ptr makeLogger(const char* name, spdlog::level::level_enum level = spdlog::level::level_enum::trace) { @@ -37,21 +40,28 @@ void unregisterSink(const spdlog::sink_ptr sink) { multiplexSink->remove_sink(sink); } -void init(const char* logFile) { +void setConsoleLogLevel(spdlog::level::level_enum level) { + consoleSink->set_level(level); +} + +void init(const spdlog::filename_t& logFileName) { if (initialized) { LOG_WARNING(LOGGING, "log_system already initialized - call has no effect."); return; } - sinks = { - std::make_shared(), - multiplexSink}; - if (logFile) { + + multiplexSink = std::make_shared(); + consoleSink = std::make_shared(); + sinks = {consoleSink, multiplexSink}; + + if (!logFileName.empty()) { try { - sinks.push_back(std::make_shared(logFile)); + sinks.push_back(std::make_shared(logFileName, MAX_LOG_FILE_SIZE_BYTES, MAX_LOG_FILE_AMOUNT, false)); } catch (const spdlog::spdlog_ex& ex) { LOG_ERROR(LOGGING, "Log file initialization failed: {}\n", ex.what()); } } + // Resize stdout buffer const auto stdoutBufferSize = 50000; setvbuf(stdout, nullptr, _IOFBF, stdoutBufferSize); @@ -75,9 +85,18 @@ void init(const char* logFile) { spdlog::register_logger(makeLogger(MESH_LOADER)); spdlog::set_default_logger(spdlog::get(DEFAULT)); spdlog::set_pattern("%^[%L] [%D %T:%f] [%n] [%s:%#] [%!] %v"); + +#if _WIN64 + SetConsoleOutputCP(CP_UTF8); +#endif + initialized = true; - LOG_INFO_IF(LOGGING, logFile != nullptr, "log_system initialized logFile: {}", logFile); - LOG_INFO_IF(LOGGING, logFile == nullptr, "log_system initialized"); + +#if defined(_WIN32) + LOG_INFO(LOGGING, "log_system initialized logFileName: {}", std::wstring_convert, wchar_t>().to_bytes(logFileName)); +#else + LOG_INFO(LOGGING, "log_system initialized logFileName: {}", logFileName); +#endif } void deinit() { diff --git a/utils/libUtils/CMakeLists.txt b/utils/libUtils/CMakeLists.txt index a4c77a30..0177db70 100644 --- a/utils/libUtils/CMakeLists.txt +++ b/utils/libUtils/CMakeLists.txt @@ -12,7 +12,8 @@ add_library(libUtils include/utils/CrashDump.h src/CrashDump.cpp include/utils/FileUtils.h src/FileUtils.cpp include/utils/MathUtils.h src/MathUtils.cpp - include/utils/PathUtils.h src/PathUtils.cpp + include/utils/u8path.h src/u8path.cpp + include/utils/stdfilesystem.h ) target_include_directories(libUtils PUBLIC include/) enable_warnings_as_errors(libUtils) @@ -29,3 +30,7 @@ PUBLIC raco::LogSystem ) add_library(raco::Utils ALIAS libUtils) + +if(PACKAGE_TESTS) + add_subdirectory(tests) +endif() \ No newline at end of file diff --git a/utils/libUtils/include/utils/FileUtils.h b/utils/libUtils/include/utils/FileUtils.h index 076e0933..19b49d71 100644 --- a/utils/libUtils/include/utils/FileUtils.h +++ b/utils/libUtils/include/utils/FileUtils.h @@ -11,12 +11,12 @@ #include #include +#include "u8path.h" namespace raco::utils::file { -using Path = std::string; -std::string read(const Path& path); -std::vector readBinary(const Path& path); -void write(const Path& path, const std::string& content); +std::string read(const u8path& path); +std::vector readBinary(const u8path& path); +void write(const u8path& path, const std::string& content); } // namespace raco::utils::file diff --git a/utils/libUtils/include/utils/u8path.h b/utils/libUtils/include/utils/u8path.h new file mode 100644 index 00000000..a1dc2f43 --- /dev/null +++ b/utils/libUtils/include/utils/u8path.h @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include "stdfilesystem.h" +#include + +namespace raco::utils { + +// Wrapper class around std::filesystem::path +// Extends its functionality and ensures, that only std::filesystem::u8path is ever used as constructor, to avoid issues with non-ASCII letters. +class u8path { + using path = std::filesystem::path; + +private: + path path_; + +public: + u8path(); + u8path(const path& path); + u8path(const std::string& path); + u8path(const char* path); + + // Operations to append and concat paths, ensuring that strings added are interpreted as UTF-8 + + friend u8path operator/(const u8path& lhs, const path& rhs); + friend u8path operator/(const u8path& lhs, const std::string& rhs); + friend u8path operator/(const u8path& lhs, const char* rhs); + u8path& operator/=(const u8path& p); + u8path& append(const std::string& source); + u8path& concat(const std::string& source); + + friend bool operator==(const u8path& lhs, const u8path& rhs); + friend bool operator!=(const u8path& lhs, const u8path& rhs); + + // Allow implicit casting to to the internal std::filesystem::path, in order to conveniently pass this as parameter to IO functions. + // On Linux, ifstream and ofstream struggle to use this implicit cast correctly for some reason. + // Thus, internalPath() is an explicit way to access the underlying std::filesystem::path + + operator path() const { return path_; } + const path& internalPath() const; + + // Limit the ways ways to get std::string from this std::filesystem::path, both are unicode-aware. + + std::string string() const; + std::wstring wstring() const; + + // Convenience methods to extend std::filesystem::path functionality + + bool exists() const; + bool existsDirectory() const; + bool existsFile() const; + bool userHasReadAccess() const; + + u8path normalized() const; + // Construct relative path from relative or absolute file path. + // Absolute file paths are returned normalized and relative to the basePath argument. + // Relative file paths are returned normalized. + u8path normalizedRelativePath(const u8path& basePath) const; + // Construct absolute paths from relative or absolute file path. + // Absolute file paths are returned normalized. + // Relative file paths are returned normalized and absolute using the basePath. + u8path normalizedAbsolutePath(const u8path& basePath) const; + + u8path rerootRelativePath(const u8path& oldBasePath, const u8path& newBasePath); + + static bool areSharingSameRoot(const u8path& lhd, const u8path& rhd); + static std::string sanitizePathString(const std::string& path); + static u8path current(); + + // Wrapper functions for std::filesystem::path functionality + + bool empty() const; + u8path parent_path() const; + u8path extension() const; + u8path stem() const; + u8path root_path() const; + u8path root_name() const; + u8path filename() const; + bool is_absolute() const; + bool is_relative() const; + u8path& replace_extension(const u8path& extension); + u8path& replace_filename(const u8path& extension); + u8path& remove_filename(); + +}; + +} // namespace raco::utils::path diff --git a/utils/libUtils/src/FileUtils.cpp b/utils/libUtils/src/FileUtils.cpp index 2b6abcf8..e374fea1 100644 --- a/utils/libUtils/src/FileUtils.cpp +++ b/utils/libUtils/src/FileUtils.cpp @@ -9,7 +9,7 @@ */ #include "utils/FileUtils.h" -#include "utils/PathUtils.h" +#include "utils/u8path.h" #include "utils/stdfilesystem.h" #include "log_system/log.h" @@ -19,41 +19,41 @@ namespace raco::utils::file { -std::string read(const Path& path) { - if (raco::utils::path::isExistingFile(path)) { - std::ifstream in{path, std::ifstream::in}; +std::string read(const u8path& path) { + if (path.existsFile()) { + + std::ifstream in{path.internalPath(), std::ifstream::in}; std::stringstream ss{}; ss << in.rdbuf(); in.close(); return ss.str(); } else { if (!path.empty()) { - LOG_WARNING("UTILS", "file not found: {}", path); + LOG_WARNING("UTILS", "file not found: {}", path.string()); } return {}; } } -std::vector readBinary(const Path& path) { - if (raco::utils::path::isExistingFile(path)) { - std::ifstream in{path, std::ifstream::in | std::ifstream::binary}; - auto buffer = std::vector{std::istream_iterator(in), std::istream_iterator()}; +std::vector readBinary(const u8path& path) { + if (path.existsFile()) { + std::ifstream in{path.internalPath(), std::ifstream::in | std::ifstream::binary}; + std::vector buffer(std::istreambuf_iterator(in), {}); in.close(); - return buffer; + return buffer; } else { if (!path.empty()) { - LOG_WARNING("UTILS", "file not found: {}", path); + LOG_WARNING("UTILS", "file not found: {}", path.string()); } return {}; } } -void write(const Path& path, const std::string& content) { - std::filesystem::path p{path}; - if (!raco::utils::path::isExistingDirectory(p.parent_path().generic_string())) { - std::filesystem::create_directory(p.parent_path()); - } - std::ofstream out{p, std::ifstream::out}; +void write(const u8path& path, const std::string& content) { + if (!path.parent_path().existsDirectory()) { + std::filesystem::create_directory(path.parent_path()); + } + std::ofstream out{path.internalPath(), std::ifstream::out}; out << content; out.close(); } diff --git a/utils/libUtils/src/PathUtils.cpp b/utils/libUtils/src/PathUtils.cpp deleted file mode 100644 index ce78fbf6..00000000 --- a/utils/libUtils/src/PathUtils.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SPDX-License-Identifier: MPL-2.0 - * - * This file is part of Ramses Composer - * (see https://github.com/GENIVI/ramses-composer). - * - * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. - * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -#include "utils/PathUtils.h" - -#include "utils/stdfilesystem.h" -#include - -namespace raco::utils::path { - -bool exists(const std::string& path) { - std::error_code ec; - auto status = std::filesystem::status(path, ec); - if (!ec) { - return std::filesystem::exists(status); - } - - return false; -} - -bool userHasReadAccess(const std::string& path) { - std::ifstream stream(path); - return stream.good(); -} - -bool isExistingDirectory(const std::string& path) { - return exists(path) && std::filesystem::is_directory(path); -} - -bool isExistingFile(const std::string& path) { - return exists(path) && !std::filesystem::is_directory(path); -} - -} // namespace raco::utils::path diff --git a/utils/libUtils/src/u8path.cpp b/utils/libUtils/src/u8path.cpp new file mode 100644 index 00000000..22d3f222 --- /dev/null +++ b/utils/libUtils/src/u8path.cpp @@ -0,0 +1,200 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "utils/u8path.h" + +#include "utils/stdfilesystem.h" +#include +#include + +namespace raco::utils { + +u8path::u8path() : path_() {} + +u8path::u8path(const path& path) : path_(path) {} + +u8path::u8path(const std::string& path) : path_(std::filesystem::u8path(path)) {} + +u8path::u8path(const char* path) : path_(std::filesystem::u8path(path)) {} + +u8path& u8path::operator/=(const u8path& p) { + path_ /= p; + return *this; +} + +u8path& u8path::append(const std::string& source) { + path_ /= u8path(source); + return *this; +} + +u8path& u8path::concat(const std::string& source) { + path_ += u8path(source); + return *this; +} + +u8path operator/(const u8path& lhs, const std::filesystem::path& rhs) { + return u8path(lhs.path_ / rhs); +} + +u8path operator/(const u8path& lhs, const std::string& rhs) { + return lhs / u8path(rhs); +} + +u8path operator/(const u8path& lhs, const char* rhs) { + return lhs / u8path(rhs); +} + +bool operator==(const u8path& lhs, const u8path& rhs) { + return lhs.path_ == rhs.path_; +} + +bool operator!=(const u8path& lhs, const u8path& rhs) { + return lhs.path_ != rhs.path_; +} + + const std::filesystem::path& u8path::internalPath() const { + return path_; +} + + std::string u8path::string() const { + return path_.generic_u8string(); +} + + std::wstring u8path::wstring() const { + return path_.generic_wstring(); +} + + bool u8path::empty() const { + return path_.empty(); +} + + bool u8path::exists() const { + std::error_code ec; + auto status = std::filesystem::status(path_, ec); + if (!ec) { + return std::filesystem::exists(status); + } + + return false; +} + + bool u8path::existsDirectory() const { + return exists() && std::filesystem::is_directory(path_); +} + + bool u8path::existsFile() const { + return exists() && !is_directory(path_); + } + + bool u8path::userHasReadAccess() const { + std::ifstream stream(path_); + return stream.good(); + } + + u8path u8path::normalized() const { + return raco_filesystem_compatibility::lexically_normal(path_); + } + + u8path u8path::normalizedRelativePath(const u8path& basePath) const { + if (is_relative()) { + return normalized(); + } else { + // use proximate() call with std::error_code to prevent exceptions and return the input path instead. + std::error_code ec; + + return std::filesystem::proximate(path_, basePath.path_, ec); + } +} + +u8path u8path::normalizedAbsolutePath(const u8path& basePath) const { + if (is_absolute()) { + return normalized(); + } else { + return (basePath / path_).normalized(); + } +} + +u8path u8path::rerootRelativePath(const u8path& oldBasePath, const u8path& newBasePath) { + return normalizedAbsolutePath(oldBasePath).normalizedRelativePath(newBasePath); +} + +bool u8path::areSharingSameRoot(const u8path& lhd, const u8path& rhd) { + auto leftRoot = lhd.root_name().string(); + auto rightRoot = rhd.root_name().string(); + std::transform(leftRoot.begin(), leftRoot.end(), leftRoot.begin(), tolower); + std::transform(rightRoot.begin(), rightRoot.end(), rightRoot.begin(), tolower); + + return leftRoot == rightRoot; +} + +std::string u8path::sanitizePathString(const std::string& path) { + const auto whitespaceCharacters = " \t\n\r\f\v"; + const auto trimmedStringLeft = path.find_first_not_of(whitespaceCharacters); + const auto trimmedStringRight = path.find_last_not_of(whitespaceCharacters); + + if (trimmedStringLeft >= trimmedStringRight) { + return {}; + } + + const auto trimmedString = path.substr(trimmedStringLeft, trimmedStringRight - trimmedStringLeft + 1); + return u8path(trimmedString).normalized().string(); +} + +u8path u8path::current() { + return std::filesystem::current_path(); +} + + u8path u8path::parent_path() const { + return path_.parent_path(); +} + + u8path u8path::extension() const { + return path_.extension(); +} + + u8path u8path::stem() const { + return path_.stem(); +} + + u8path u8path::root_path() const { + return path_.root_path(); +} + + u8path u8path::root_name() const { + return path_.root_name(); +} + + u8path u8path::filename() const { + return path_.filename(); +} + + bool u8path::is_absolute() const { + return path_.is_absolute(); +} + + bool u8path::is_relative() const { + return path_.is_relative(); +} + + u8path& u8path::replace_extension(const u8path& extension) { + path_.replace_extension(extension.path_); + return *this; +} + + u8path& u8path::replace_filename(const u8path& extension) { + path_.replace_filename(extension.path_); + return *this; +} + + u8path& u8path::remove_filename() { + path_.remove_filename(); + return *this; +} + +} // namespace raco::utils::path diff --git a/utils/libUtils/tests/CMakeLists.txt b/utils/libUtils/tests/CMakeLists.txt new file mode 100644 index 00000000..0c077d27 --- /dev/null +++ b/utils/libUtils/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +#[[ +SPDX-License-Identifier: MPL-2.0 + +This file is part of Ramses Composer +(see https://github.com/GENIVI/ramses-composer). + +This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +]] + +set(TEST_SOURCES + FileUtils_test.cpp + u8path_test.cpp +) + +set(TEST_LIBRARIES + raco::Utils +) + +raco_package_add_test( + libUtils_test + "${TEST_SOURCES}" + "${TEST_LIBRARIES}" + ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/utils/libUtils/tests/FileUtils_test.cpp b/utils/libUtils/tests/FileUtils_test.cpp new file mode 100644 index 00000000..17a1da8f --- /dev/null +++ b/utils/libUtils/tests/FileUtils_test.cpp @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "gtest/gtest.h" + +#include "utils/FileUtils.h" +#include "utils/u8path.h" +#include "utils/stdfilesystem.h" + +#include + +using namespace raco::utils; + +class FileUtilsTest : public testing::Test { +public: + virtual std::string test_case_name() const { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); + } + + virtual std::string test_suite_name() const { + return ::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name(); + } + + virtual u8path test_relative_path() const { + return u8path{test_suite_name()} / test_case_name(); + } + + virtual u8path test_path() const { + return (std::filesystem::current_path() / test_relative_path()); + } + + std::vector testFileNames = { + test_path() / "test", + test_path() / u8"äöüÄÖÜáàÁÀÇß", + test_path() / u8"滴滴启动纽交所退市", + test_path() / u8"滴滴启动纽" / u8"äöüÄÖÜáàÁÀÇß" + }; + + std::string testFileContents = u8"test\täöüÄÖÜáàÁÀÇß 滴滴启动纽交所退市 🐌🌝👍🏼t"; + +protected: + virtual void SetUp() override { + if (std::filesystem::exists(test_path())) { + // Debugging case: if we debug and kill the test before complition the test directory will not be cleaned by TearDown + std::filesystem::remove_all(test_path()); + } + std::filesystem::create_directories(test_path()); + } + + virtual void TearDown() override { + std::filesystem::remove_all(test_path()); + } +}; + +TEST_F(FileUtilsTest, writeReadFileTest) { + ASSERT_TRUE(test_path().exists()); + + std::vector testFileContentsBinary(testFileContents.begin(), testFileContents.end()); + + for (const auto& testFileName : testFileNames) { + file::write(testFileName, testFileContents); + ASSERT_EQ(file::read(testFileName), testFileContents); + + auto readBinaryData = file::readBinary(testFileName); + ASSERT_EQ(readBinaryData.size(), testFileContentsBinary.size()); + for (int i = 0; i < readBinaryData.size(); ++i) { + ASSERT_EQ(readBinaryData[i], testFileContentsBinary[i]); + } + } +} \ No newline at end of file diff --git a/utils/libUtils/tests/u8path_test.cpp b/utils/libUtils/tests/u8path_test.cpp new file mode 100644 index 00000000..e4344235 --- /dev/null +++ b/utils/libUtils/tests/u8path_test.cpp @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of Ramses Composer + * (see https://github.com/GENIVI/ramses-composer). + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "gtest/gtest.h" + +#include "utils/FileUtils.h" +#include "utils/u8path.h" +#include "utils/stdfilesystem.h" + +#include + +using namespace raco::utils; + +class u8pathTest : public testing::Test { +public: + virtual std::string test_case_name() const { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); + } + + virtual std::string test_suite_name() const { + return ::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name(); + } + + virtual u8path test_relative_path() const { + return u8path{test_suite_name()} / test_case_name(); + } + + virtual u8path test_path() const { + return std::filesystem::current_path() / test_relative_path(); + } + + std::vector testFileNames = { + test_path() / "test", + test_path() / u8"äöüÄÖÜáàÁÀÇß", + test_path() / u8"滴滴启动纽交所退市" + }; + +protected: + virtual void SetUp() override { + if (std::filesystem::exists(test_path())) { + // Debugging case: if we debug and kill the test before complition the test directory will not be cleaned by TearDown + std::filesystem::remove_all(test_path()); + } + std::filesystem::create_directories(test_path()); + } + + virtual void TearDown() override { + std::filesystem::remove_all(test_path()); + } +}; + +TEST_F(u8pathTest, existsFileTest) { + ASSERT_TRUE(test_path().exists()); + + for (const auto& testFileName : testFileNames) { + ASSERT_FALSE(testFileName.exists()); + ASSERT_FALSE(testFileName.userHasReadAccess()); + ASSERT_FALSE(testFileName.existsFile()); + ASSERT_FALSE(testFileName.existsDirectory()); + + file::write(testFileName.string(), ""); + + ASSERT_TRUE(testFileName.exists()); + ASSERT_TRUE(testFileName.userHasReadAccess()); + ASSERT_TRUE(testFileName.existsFile()); + ASSERT_FALSE(testFileName.existsDirectory()); + } +} + +TEST_F(u8pathTest, existsDirTest) { + ASSERT_TRUE(test_path().exists()); + + for (const auto& testDirName : testFileNames) { + auto testDirSubfileName = testDirName / "test"; + + ASSERT_FALSE(testDirName.exists()); + ASSERT_FALSE(testDirSubfileName.exists()); + ASSERT_FALSE(testDirSubfileName.userHasReadAccess()); + ASSERT_FALSE(testDirName.existsFile()); + ASSERT_FALSE(testDirName.existsDirectory()); + ASSERT_FALSE(testDirSubfileName.existsFile()); + ASSERT_FALSE(testDirSubfileName.existsDirectory()); + + file::write(testDirSubfileName.string(), ""); + + ASSERT_TRUE(testDirName.exists()); + ASSERT_TRUE(testDirSubfileName.exists()); + ASSERT_TRUE(testDirSubfileName.userHasReadAccess()); + ASSERT_FALSE(testDirName.existsFile()); + ASSERT_TRUE(testDirName.existsDirectory()); + ASSERT_TRUE(testDirSubfileName.existsFile()); + ASSERT_FALSE(testDirSubfileName.existsDirectory()); + } +} \ No newline at end of file