diff --git a/.gitmodules b/.gitmodules index 630bb133..424967a1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ [submodule "third_party/tinygltf"] path = third_party/tinygltf url = https://github.com/syoyo/tinygltf.git +[submodule "third_party/zip"] + path = third_party/zip + url = https://github.com/kuba--/zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 853bd2ed..97dc2a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,34 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h ### Known Issues --> +## [0.13.0] Compressed project files, cubemap extensions, log view +* **File version number has changed. Files saved with RaCo 0.13.0 cannot be opened by previous versions.** + +### Added +* Added custom CubeMap mipmap support. +* Added optional automatic zipping of project files. + * With automatic zipping enabled, projects will be saved as ZIP archives that still use the .rca file extension and contain the project JSON file. + * This option can be enabled/disabled in the Project Settings (disabled by default). +* Added command line argument "--outlogfile" to headless Ramses Composer for changing the log file path. +* New log view allows observing Ramses Composer log output within the application. +* The ramses logic datatype INT64 is now supported. +* Added object duplication feature via context menu or shortcut. + +### Changes +* Removed upper limit for glTF mesh TEXCOORD and COLOR attributes. +* Export dialog now displays Ramses Composer scene errors if any are present. +* Ramses Logic log output now appears in Ramses Composer log files. +* Ramses Composer now begins a new log file every time the application is launched. +* The "Field of View" property in the perspective camera has been renamed to "Vert. Field of View". + +### Fixes +* Fixed dropping of links from external reference to project local objects when loading a project. +* Fixed crash when dragging around "Scene Id" property value. +* Fixed problem with saving preferences if the entered directory does not exist. +* Improved support for High DPI screens. +* Fixed losing the property values of PrefabInstance interface scripts when pasting PrefabInstances. + + ## [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.** @@ -35,8 +63,9 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h * 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. + * Currently allowed operations: addition (+), substraction (-), multiplication (*), division (/), modulo (%), exponentiation (^) and changing precedence using parentheses. * Results are calculated immediately and the mathematical expression is not stored. +* The property browser now indicates lua datatypes with tooltips on property labels and with a label in the link editor popup. ### Changes * Update from ramses-logic 0.13.0 to ramses-logic 0.14.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 44fba4c7..86b2d67a 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.12.0) +project(RaCoOS VERSION 0.13.0) SET(RACO_RELEASE_DIRECTORY ${CMAKE_BINARY_DIR}/release) @@ -87,10 +87,17 @@ macro(deploy_qt tgt) # Also add the Qt licensing file and our licensing file. # We are making use of the add_custom_command feature for build event commands that "COMMAND" statements with an empty COMMAND string are completely ignored. add_custom_command(TARGET ${tgt} POST_BUILD - COMMAND "$<$:${RACO_QT_WINDEPLOY_PATH}/windeployqt.exe>" --debug --no-libraries --no-opengl-sw --no-system-d3d-compiler --no-svg --no-translations --no-compiler-runtime "$" - COMMAND "${RACO_QT_WINDEPLOY_PATH}/windeployqt.exe" "$<$:--no-plugins>" --no-opengl-sw --no-system-d3d-compiler --no-svg --no-translations --no-compiler-runtime "$" - # Qt deploys the folder imageformats which we do not need - createReadHandlerHelper in C:\Qt\5.15.2\Src\qtbase\src\gui\image\qimagereader.cpp contains hardcoded support for the extensions png, bmp, dib, xpm, xbm, pbm, pbmraw, pgm, pgmraw, ppm and ppmraw (which is more than we need). - COMMAND ${CMAKE_COMMAND} -E rm -fr "$/imageformats" + COMMAND "$<$:${RACO_QT_WINDEPLOY_PATH}/windeployqt.exe>" --debug --no-libraries --no-opengl-sw --no-system-d3d-compiler --svg --no-translations --no-compiler-runtime "$" + COMMAND "${RACO_QT_WINDEPLOY_PATH}/windeployqt.exe" "$<$:>" --no-opengl-sw --no-system-d3d-compiler --svg --no-translations --no-compiler-runtime "$" + # Qt deploys the folder imageformats which we do not need except for svg support - createReadHandlerHelper in C:\Qt\5.15.2\Src\qtbase\src\gui\image\qimagereader.cpp contains hardcoded support for the extensions png, bmp, dib, xpm, xbm, pbm, pbmraw, pgm, pgmraw, ppm and ppmraw (which is more than we need). + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qgifd.dll" "$/imageformats/qgif.dll" + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qicnsd.dll" "$/imageformats/qicns.dll" + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qicod.dll" "$/imageformats/qico.dll" + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qjpegd.dll" "$/imageformats/qjpeg.dll" + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qtiffd.dll" "$/imageformats/qtiff.dll" + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qtgad.dll" "$/imageformats/qtga.dll" + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qwbmpd.dll" "$/imageformats/qwbmp.dll" + COMMAND ${CMAKE_COMMAND} -E rm -f "$/imageformats/qwebpd.dll" "$/imageformats/qwebp.dll" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/third_party/qtsharedlibs_license.txt" "$/qtsharedlibs_license.txt" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/LICENSE.txt" "$/LICENSE.txt" ) diff --git a/EditorApp/main.cpp b/EditorApp/main.cpp index f91ad338..c574138c 100644 --- a/EditorApp/main.cpp +++ b/EditorApp/main.cpp @@ -44,6 +44,13 @@ int main(int argc, char *argv[]) { QCoreApplication::setApplicationName("Ramses Composer"); QCoreApplication::setApplicationVersion(RACO_OSS_VERSION); + // Enable Qt's virtualized coordinate system, which makes qt pixel size different from physical pixel size depending on the scale factor set in the operating system. + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + // By default, Qt will round the scale factor to the closest integer in some contexts. Disable rounding, since it produces invalid font sizes for Windows 125%, 150%, 175% etc. scaling mode. + QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); + // Also, we need support for high resolution icons on scale factors greater than one. + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); + // 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); @@ -83,10 +90,7 @@ int main(int argc, char *argv[]) { // force use of style palette, required on Linux a.setPalette(a.style()->standardPalette()); - QStringList argList{}; - for (int i{0}; i < argc; i++) - argList << argv[i]; - parser.process(argList); + parser.process(QCoreApplication::arguments()); bool noDumpFiles = parser.isSet(noDumpFileCheckOption); raco::utils::crashdump::installCrashDumpHandler(noDumpFiles); @@ -140,5 +144,6 @@ int main(int argc, char *argv[]) { MainWindow w{&app, &rendererBackend}; w.show(); + return a.exec(); } diff --git a/EditorApp/mainwindow.cpp b/EditorApp/mainwindow.cpp index b479f3e0..67ff4d6f 100644 --- a/EditorApp/mainwindow.cpp +++ b/EditorApp/mainwindow.cpp @@ -16,6 +16,7 @@ #include "SavedLayoutsDialog.h" #include "common_widgets/ErrorView.h" #include "common_widgets/ExportDialog.h" +#include "common_widgets/LogView.h" #include "common_widgets/MeshAssetImportDialog.h" #include "common_widgets/PreferencesView.h" #include "common_widgets/UndoView.h" @@ -25,7 +26,7 @@ #include "core/PathManager.h" #include "core/Project.h" #include "core/Queries.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include "data_storage/Value.h" #include "log_system/log.h" #include "object_tree_view/ObjectTreeDock.h" @@ -129,7 +130,7 @@ ads::CDockWidget* createDockWidget(const QString& title, QWidget* parent) { ads::CDockAreaWidget* createAndAddPreview(MainWindow* mainWindow, const char* dockObjName, RaCoDockManager* dockManager, raco::ramses_widgets::RendererBackend& rendererBackend, raco::application::RaCoApplication* application) { const auto& viewport = application->activeRaCoProject().project()->settings()->viewport_; - const auto& backgroundColor = application->activeRaCoProject().project()->settings()->backgroundColor_.asVec4f(); + const auto& backgroundColor = *application->activeRaCoProject().project()->settings()->backgroundColor_; auto* previewWidget = new raco::ramses_widgets::PreviewMainWindow{rendererBackend, application->sceneBackendImpl(), {*viewport->i1_, *viewport->i2_}, application->activeRaCoProject().project(), application->dataChangeDispatcher()}; QObject::connect(mainWindow, &MainWindow::viewportChanged, previewWidget, &raco::ramses_widgets::PreviewMainWindow::setViewport); previewWidget->displayScene(application->sceneBackendImpl()->currentSceneId(), backgroundColor); @@ -258,8 +259,8 @@ ads::CDockAreaWidget* createAndAddUndoView(raco::application::RaCoApplication* a return dockManager->addDockWidget(ads::BottomDockWidgetArea, dock, dockArea); } -ads::CDockAreaWidget* createAndAddErrorView(MainWindow* mainWindow, raco::application::RaCoApplication* application, const char* dockObjName, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager, ads::CDockAreaWidget* dockArea = nullptr) { - auto* errorView = new raco::common_widgets::ErrorView(application->activeRaCoProject().commandInterface(), application->dataChangeDispatcher()); +ads::CDockAreaWidget* createAndAddErrorView(MainWindow* mainWindow, raco::application::RaCoApplication* application, const char* dockObjName, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager, raco::common_widgets::LogViewModel* logViewModel, ads::CDockAreaWidget* dockArea = nullptr) { + auto* errorView = new raco::common_widgets::ErrorView(application->activeRaCoProject().commandInterface(), application->dataChangeDispatcher(), true, logViewModel); QObject::connect(errorView, &raco::common_widgets::ErrorView::objectSelectionRequested, &treeDockManager, &raco::object_tree::view::ObjectTreeDockManager::selectObjectAcrossAllTreeDocks); auto* dock = createDockWidget(MainWindow::DockWidgetTypes::ERROR_VIEW, mainWindow); dock->setWidget(errorView); @@ -267,6 +268,14 @@ ads::CDockAreaWidget* createAndAddErrorView(MainWindow* mainWindow, raco::applic return dockManager->addDockWidget(ads::BottomDockWidgetArea, dock, dockArea); } +ads::CDockAreaWidget* createAndAddLogView(MainWindow* mainWindow, raco::application::RaCoApplication* application, const char* dockObjName, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager, raco::common_widgets::LogViewModel* logViewModel, ads::CDockAreaWidget* dockArea = nullptr) { + auto* logView = new raco::common_widgets::LogView(logViewModel); + auto* dock = createDockWidget(MainWindow::DockWidgetTypes::LOG_VIEW, mainWindow); + dock->setWidget(logView); + dock->setObjectName(dockObjName); + return dockManager->addDockWidget(ads::BottomDockWidgetArea, dock, dockArea); +} + void createInitialWidgets(MainWindow* mainWindow, raco::ramses_widgets::RendererBackend& rendererBackend, raco::application::RaCoApplication* application, RaCoDockManager* dockManager, raco::object_tree::view::ObjectTreeDockManager& treeDockManager) { createAndAddPreview(mainWindow, "defaultPreview", dockManager, rendererBackend, application); @@ -283,8 +292,8 @@ void createInitialWidgets(MainWindow* mainWindow, raco::ramses_widgets::Renderer MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco::ramses_widgets::RendererBackend* rendererBackend, QWidget* parent) : QMainWindow(parent), - rendererBackend_{rendererBackend}, - racoApplication_{racoApplication} { + rendererBackend_(rendererBackend), + racoApplication_(racoApplication) { // Setup the UI from the QtCreator file mainwindow.ui ui = new Ui::MainWindow(); ui->setupUi(this); @@ -295,6 +304,8 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco setWindowIcon(QIcon(":applicationLogo")); resize(QGuiApplication::screenAt(this->pos())->size() * 0.85); + logViewModel_ = new raco::common_widgets::LogViewModel(this); + // Shortcuts { @@ -336,7 +347,7 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco openProject(); }); QObject::connect(ui->actionExport, &QAction::triggered, this, [this]() { - auto dialog = new raco::common_widgets::ExportDialog(racoApplication_, this); + auto dialog = new raco::common_widgets::ExportDialog(racoApplication_, logViewModel_, this); dialog->exec(); }); QObject::connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::close); @@ -357,7 +368,8 @@ MainWindow::MainWindow(raco::application::RaCoApplication* racoApplication, raco QObject::connect(ui->actionNewResourcesTree, &QAction::triggered, [this]() { createAndAddResourceTree(this, EditorObject::normalizedObjectID("").c_str(), dockManager_, treeDockManager_, racoApplication_, nullptr); }); QObject::connect(ui->actionNewPrefabTree, &QAction::triggered, [this]() { createAndAddPrefabTree(this, EditorObject::normalizedObjectID("").c_str(), dockManager_, treeDockManager_, racoApplication_, nullptr); }); QObject::connect(ui->actionNewUndoView, &QAction::triggered, [this]() { createAndAddUndoView(racoApplication_, EditorObject::normalizedObjectID("").c_str(), &racoApplication_->activeRaCoProject(), this, dockManager_); }); - QObject::connect(ui->actionNewErrorView, &QAction::triggered, [this]() { createAndAddErrorView(this, racoApplication_, EditorObject::normalizedObjectID("").c_str(), dockManager_, treeDockManager_); }); + QObject::connect(ui->actionNewErrorView, &QAction::triggered, [this]() { createAndAddErrorView(this, racoApplication_, EditorObject::normalizedObjectID("").c_str(), dockManager_, treeDockManager_, logViewModel_); }); + QObject::connect(ui->actionNewLogView, &QAction::triggered, [this]() { createAndAddLogView(this, racoApplication_, EditorObject::normalizedObjectID("").c_str(), dockManager_, treeDockManager_, logViewModel_); }); QObject::connect(ui->actionRestoreDefaultLayout, &QAction::triggered, [this](){ resetDockManager(); createInitialWidgets(this, *rendererBackend_, racoApplication_, dockManager_, treeDockManager_); @@ -428,7 +440,7 @@ void MainWindow::timerEvent(QTimerEvent* event) { racoApplication_->doOneLoop(); const auto& viewport = racoApplication_->activeRaCoProject().project()->settings()->viewport_; - const auto& backgroundColor = racoApplication_->activeRaCoProject().project()->settings()->backgroundColor_.asVec4f(); + const auto& backgroundColor = *racoApplication_->activeRaCoProject().project()->settings()->backgroundColor_; Q_EMIT viewportChanged({*viewport->i1_, *viewport->i2_}); @@ -498,6 +510,7 @@ void MainWindow::openProject(const QString& file) { // Don't create a new DockManager right away - making QMessageBoxes pop up messes up state restoring // (see https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/315) delete dockManager_; + logViewModel_->clear(); try { racoApplication_->switchActiveRaCoProject(file); @@ -570,6 +583,7 @@ bool MainWindow::saveAsActiveProject() { if (newPath.isEmpty()) { return false; } + if (!newPath.endsWith(".rca")) newPath += ".rca"; if (racoApplication_->activeRaCoProject().saveAs(newPath, setProjectName)) { recentFileMenu_->addRecentFile(racoApplication_->activeProjectPath().c_str()); @@ -684,7 +698,9 @@ void MainWindow::regenerateLayoutDocks(const RaCoDockManager::LayoutDocks& docks } else if (savedDockType == DockWidgetTypes::UNDO_STACK) { createAndAddUndoView(racoApplication_, dockNameCString, &racoApplication_->activeRaCoProject(), this, dockManager_); } else if (savedDockType == DockWidgetTypes::ERROR_VIEW) { - createAndAddErrorView(this, racoApplication_, dockNameCString, dockManager_, treeDockManager_); + createAndAddErrorView(this, racoApplication_, dockNameCString, dockManager_, treeDockManager_, logViewModel_); + } else if (savedDockType == DockWidgetTypes::LOG_VIEW) { + createAndAddLogView(this, racoApplication_, dockNameCString, dockManager_, treeDockManager_, logViewModel_); } else { assert(false && "Unknown Dock Type detected"); } diff --git a/EditorApp/mainwindow.h b/EditorApp/mainwindow.h index 73124f3f..73430972 100644 --- a/EditorApp/mainwindow.h +++ b/EditorApp/mainwindow.h @@ -13,6 +13,7 @@ #include "common_widgets/TimingsWidget.h" #include "object_tree_view/ObjectTreeDockManager.h" #include "ramses_widgets/RendererBackend.h" +#include "common_widgets/log_model/LogViewModel.h" #include #include @@ -47,6 +48,7 @@ class MainWindow : public QMainWindow { static inline const char* SCENE_GRAPH{"Scene Graph"}; static inline const char* UNDO_STACK{"Undo Stack"}; static inline const char* ERROR_VIEW{"Error View"}; + static inline const char* LOG_VIEW{"Log View"}; }; explicit MainWindow( @@ -95,6 +97,7 @@ protected Q_SLOTS: raco::object_tree::view::ObjectTreeDockManager treeDockManager_; raco::common_widgets::TimingsModel timingsModel_{this}; QMetaObject::Connection activeProjectFileConnection_; + raco::common_widgets::LogViewModel* logViewModel_; int renderTimerId_ = 0; }; diff --git a/EditorApp/mainwindow.ui b/EditorApp/mainwindow.ui index dd3bcf40..f6a89a11 100644 --- a/EditorApp/mainwindow.ui +++ b/EditorApp/mainwindow.ui @@ -73,6 +73,7 @@ + @@ -227,6 +228,11 @@ Save Current Layout... + + + New Log View + + diff --git a/HeadlessApp/main.cpp b/HeadlessApp/main.cpp index dbb51bca..46b96723 100644 --- a/HeadlessApp/main.cpp +++ b/HeadlessApp/main.cpp @@ -115,21 +115,24 @@ int main(int argc, char* argv[]) { << "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" - ); + "6"); + QCommandLineOption logFileOutputOption( + QStringList() << "o" + << "outlogfile", + "File name to write log file to.", + "log-file-name", + ""); parser.addOption(loadProjectAction); parser.addOption(exportProjectAction); parser.addOption(compressExportAction); parser.addOption(noDumpFileCheckOption); parser.addOption(logLevelOption); + parser.addOption(logFileOutputOption); // application must be instantiated before parsing command line QCoreApplication a(argc, argv); - QStringList argList{}; - for (int i{0}; i < argc; i++) - argList << argv[i]; - parser.process(argList); + parser.process(QCoreApplication::arguments()); bool noDumpFiles = parser.isSet(noDumpFileCheckOption); raco::utils::crashdump::installCrashDumpHandler(noDumpFiles); @@ -137,12 +140,33 @@ int main(int argc, char* argv[]) { auto appDataPath = raco::utils::u8path(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString()).parent_path() / "RamsesComposer"; raco::core::PathManager::init(QCoreApplication::applicationDirPath().toStdString(), appDataPath); - 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)); + auto customLogFileName = parser.value(logFileOutputOption).toStdString(); + auto logFilePath = raco::utils::u8path(customLogFileName); + bool customLogFileNameFailed = false; + + if (!customLogFileName.empty()) { + if (!logFilePath.parent_path().empty() && logFilePath.parent_path() != logFilePath && !logFilePath.parent_path().exists()) { + std::filesystem::create_directories(logFilePath.parent_path()); + + if (!logFilePath.parent_path().exists()) { + logFilePath = raco::core::PathManager::logFileHeadlessName(); + customLogFileNameFailed = true; + } + } + } else { + logFilePath = raco::core::PathManager::logFileHeadlessName(); + } + + raco::log_system::init(logFilePath.internalPath().native()); + + if (customLogFileNameFailed) { + LOG_ERROR(raco::log_system::LOGGING, "Could not create log file at: " + customLogFileName + ". Using default location instead: " + logFilePath.string()); + } + raco::log_system::setConsoleLogLevel(logLevel); - raco::ramses_base::setRamsesAndLogicConsoleLogLevel(logLevel); + raco::ramses_base::setRamsesLogLevel(logLevel); + raco::ramses_base::setLogicLogLevel(logLevel); QString projectFile{}; if (parser.isSet(loadProjectAction)) { diff --git a/README.md b/README.md index d32905ce..dfd75d69 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ Ramses Composer uses a number of third party libraries: * RAMSES logic * spdlog * tinygltf +* zip Their source code and respective licenses can be found in the ```third_party/``` folder. diff --git a/components/libApplication/src/RaCoApplication.cpp b/components/libApplication/src/RaCoApplication.cpp index df6db622..42597cfd 100644 --- a/components/libApplication/src/RaCoApplication.cpp +++ b/components/libApplication/src/RaCoApplication.cpp @@ -37,7 +37,7 @@ RaCoApplication::RaCoApplication(ramses_base::BaseEngineBackend& engine, const Q dataChangeDispatcherEngine_{std::make_shared()}, scenesBackend_{new ramses_adaptor::SceneBackend(&engine, dataChangeDispatcherEngine_)}, externalProjectsStore_(this) { - ramses_base::enableLogicLoggerOutputToStdout(false); + ramses_base::installLogicLogHandler(); // Preferences need to be initalized before we have a fist initial project raco::components::RaCoPreferences::init(); std::vector stack; diff --git a/components/libApplication/src/RaCoProject.cpp b/components/libApplication/src/RaCoProject.cpp index a354305d..6c3b838a 100644 --- a/components/libApplication/src/RaCoProject.cpp +++ b/components/libApplication/src/RaCoProject.cpp @@ -36,6 +36,7 @@ #include "user_types/RenderTarget.h" #include "user_types/RenderPass.h" #include "utils/FileUtils.h" +#include "utils/ZipUtils.h" #include "utils/u8path.h" #include "core/CoreFormatter.h" #include "utils/stdfilesystem.h" @@ -198,7 +199,7 @@ RaCoProject::~RaCoProject() { } std::unique_ptr RaCoProject::createNew(RaCoApplication* app) { - LOG_INFO(raco::log_system::PROJECT, ""); + LOG_INFO(raco::log_system::PROJECT, "Creating new project."); Project p{}; p.setCurrentPath(components::RaCoPreferences::instance().userProjectsDirectory.toStdString()); @@ -229,7 +230,7 @@ std::unique_ptr RaCoProject::createNew(RaCoApplication* app) { result->context_->addProperty({sRenderLayer, &user_types::RenderLayer::renderableTags_}, "render_main", std::make_unique>(0)); result->context_->set({sNode, &user_types::Node::tags_}, std::vector({"render_main"})); - result->context_->set({sCamera, &user_types::Node::translation_, &data_storage::Vec3f::z}, 10.0); + result->context_->set({sCamera, &user_types::Node::translation_, &Vec3f::z}, 10.0); result->context_->moveScenegraphChildren({sMeshNode}, sNode); result->undoStack_.reset(); result->context_->changeMultiplexer().reset(); @@ -249,15 +250,33 @@ std::unique_ptr RaCoProject::loadFromFile(const QString& filename, } QFile file{filename}; - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + if (!file.open(QIODevice::ReadOnly)) { LOG_WARNING(raco::log_system::PROJECT, "Can't read file {}", filename.toLatin1()); return {}; } - auto document{QJsonDocument::fromJson(file.readAll())}; + + auto fileContents = file.readAll(); + file.close(); + + if (fileContents.size() < 4) { + LOG_WARNING(raco::log_system::PROJECT, "File {} has invalid content", filename.toLatin1()); + return {}; + } + + if (raco::utils::zip::isZipFile({fileContents.begin(), fileContents.begin() + 4})) { + auto unzippedProj = raco::utils::zip::zipToProject(fileContents, fileContents.size()); + + if (unzippedProj.success) { + fileContents = QByteArray(unzippedProj.payload.c_str()); + } else { + throw std::runtime_error(fmt::format("Can't read zipped file {}:\n{}", filename.toLatin1(), unzippedProj.payload)); + } + } + + auto document{QJsonDocument::fromJson(fileContents)}; if (document.isNull()) { throw std::runtime_error("Loading JSON file resulted in a null document object"); } - file.close(); auto fileVersion{raco::serialization::deserializeFileVersion(document)}; if (fileVersion > raco::serialization::RAMSES_PROJECT_FILE_VERSION) { @@ -322,7 +341,11 @@ bool RaCoProject::save() { const auto path(project_.currentPath()); LOG_INFO(raco::log_system::PROJECT, "Saving project to {}", path); QFile file{path.c_str()}; - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + auto settings = project_.settings(); + auto saveAsZip = *settings->saveAsZip_; + auto flags = (saveAsZip) ? QIODevice::WriteOnly : QIODevice::WriteOnly | QIODevice::Text; + + if (!file.open(flags)) { LOG_ERROR(raco::log_system::PROJECT, "Saving project failed: Could not open file for writing: {} FileError {} {}", path, file.error(), file.errorString().toStdString()); return false; } @@ -335,7 +358,21 @@ bool RaCoProject::save() { {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}}}; - file.write(serializeProject(currentVersions).toJson()); + auto projectFileData = serializeProject(currentVersions).toJson(); + + if (saveAsZip) { + auto zippedFile = (raco::utils::zip::projectToZip(projectFileData.constData(), (project_.currentFileName() + ".json").c_str())); + if (zippedFile.empty()) { + LOG_ERROR(raco::log_system::PROJECT, "Saving project failed: Error while zipping project file"); + return false; + } + + projectFileData.resize(zippedFile.size()); + + std::memcpy(projectFileData.data(), zippedFile.data(), zippedFile.size()); + } + + file.write(projectFileData); if (!file.flush() || file.error() != QFile::FileError::NoError) { LOG_ERROR(raco::log_system::PROJECT, "Saving project failed: Could not write to disk: FileError {} {}", file.error(), file.errorString().toStdString()); file.close(); diff --git a/components/libApplication/tests/RaCoProject_test.cpp b/components/libApplication/tests/RaCoProject_test.cpp index cd6c69eb..177777ed 100644 --- a/components/libApplication/tests/RaCoProject_test.cpp +++ b/components/libApplication/tests/RaCoProject_test.cpp @@ -643,6 +643,29 @@ TEST_F(RaCoProjectFixture, loadingBrokenJSONFileThrowsException) { ASSERT_THROW(app.switchActiveRaCoProject(QString::fromStdString(jsonPath)), std::runtime_error); } +TEST_F(RaCoProjectFixture, saveLoadAsZip) { + { + RaCoApplication app{backend}; + 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")}; + + app.activeRaCoProject().commandInterface()->addLink({lua, {"luaOutputs", "translation"}}, {nodeRotEuler, {"translation"}}); + app.activeRaCoProject().commandInterface()->addLink({lua, {"luaOutputs", "translation"}}, {nodeRotQuat, {"translation"}}); + app.activeRaCoProject().commandInterface()->addLink({lua, {"luaOutputs", "rotation3"}}, {nodeRotEuler, {"rotation"}}); + app.activeRaCoProject().commandInterface()->addLink({lua, {"luaOutputs", "rotation4"}}, {nodeRotQuat, {"rotation"}}); + app.activeRaCoProject().commandInterface()->set({app.activeRaCoProject().project()->settings(), &raco::user_types::ProjectSettings::saveAsZip_}, true); + + app.activeRaCoProject().saveAs((test_path() / "project.rcp").string().c_str()); + } + { + RaCoApplication app{backend, (test_path() / "project.rcp").string().c_str()}; + ASSERT_EQ(10, app.activeRaCoProject().project()->instances().size()); + ASSERT_EQ(5, app.activeRaCoProject().project()->links().size()); + } +} + TEST_F(RaCoProjectFixture, saveLoadRotationLinksGetReinstated) { { RaCoApplication app{backend}; diff --git a/components/libMeshLoader/include/mesh_loader/glTFMesh.h b/components/libMeshLoader/include/mesh_loader/glTFMesh.h index 7483624c..4c284749 100644 --- a/components/libMeshLoader/include/mesh_loader/glTFMesh.h +++ b/components/libMeshLoader/include/mesh_loader/glTFMesh.h @@ -52,9 +52,6 @@ class glTFMesh : public raco::core::MeshData { std::vector data; }; - constexpr static inline auto MAX_NUMBER_TEXTURECOORDS = 2; - constexpr static inline auto MAX_NUMBER_COLORS = 2; - void loadPrimitiveData(const tinygltf::Primitive& primitive, const tinygltf::Model& scene, std::vector& vertexBuffer, std::vector& normalBuffer, std::vector& tangentBuffer, std::vector& bitangentBuffer, std::vector>& uvBuffers, std::vector>& colorBuffers, glm::dmat4* rootTrafoMatrix = nullptr); diff --git a/components/libMeshLoader/src/glTFMesh.cpp b/components/libMeshLoader/src/glTFMesh.cpp index c5cb688a..8aeac300 100644 --- a/components/libMeshLoader/src/glTFMesh.cpp +++ b/components/libMeshLoader/src/glTFMesh.cpp @@ -33,8 +33,8 @@ glTFMesh::glTFMesh(const tinygltf::Model &scene, const core::MeshScenegraph &sce std::vector normalBuffer{}; std::vector tangentBuffer{}; std::vector bitangentBuffer{}; - std::vector> uvBuffers(MAX_NUMBER_TEXTURECOORDS); - std::vector> colorBuffers(MAX_NUMBER_COLORS); + std::vector> uvBuffers; + std::vector> colorBuffers; std::vector flattenedPrimitiveList; @@ -214,8 +214,8 @@ void glTFMesh::loadPrimitiveData(const tinygltf::Primitive &primitive, const tin std::optional normalData; std::optional tangentData; - std::array, MAX_NUMBER_TEXTURECOORDS> bufferTexCoordDatas; - std::array, MAX_NUMBER_COLORS> bufferColorDatas; + std::vector> bufferTexCoordDatas; + std::vector> bufferColorDatas; if (primitive.attributes.find("NORMAL") != primitive.attributes.end()) { normalData.emplace(scene, primitive.attributes.at("NORMAL"), std::set{TINYGLTF_COMPONENT_TYPE_FLOAT}); @@ -225,17 +225,21 @@ void glTFMesh::loadPrimitiveData(const tinygltf::Primitive &primitive, const tin } } - for (auto uvChannel = 0; uvChannel < MAX_NUMBER_TEXTURECOORDS; ++uvChannel) { + for (auto uvChannel = 0; uvChannel < std::numeric_limits::max(); ++uvChannel) { auto texCoordName = fmt::format("TEXCOORD_{}", uvChannel); if (primitive.attributes.find(texCoordName) != primitive.attributes.end()) { - bufferTexCoordDatas[uvChannel].emplace(scene, primitive.attributes.at(texCoordName), std::set{TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE, TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT}); + bufferTexCoordDatas.emplace_back().emplace(scene, primitive.attributes.at(texCoordName), std::set{TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE, TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT}); + } else { + break; } } - for (auto colorChannel = 0; colorChannel < MAX_NUMBER_COLORS; ++colorChannel) { + for (auto colorChannel = 0; colorChannel < std::numeric_limits::max(); ++colorChannel) { auto colorName = fmt::format("COLOR_{}", colorChannel); if (primitive.attributes.find(colorName) != primitive.attributes.end()) { - bufferColorDatas[colorChannel].emplace(scene,primitive.attributes.at(colorName), std::set{TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE, TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT}); + bufferColorDatas.emplace_back().emplace(scene, primitive.attributes.at(colorName), std::set{TINYGLTF_COMPONENT_TYPE_FLOAT, TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE, TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT}); + } else { + break; } } @@ -281,7 +285,8 @@ void glTFMesh::loadPrimitiveData(const tinygltf::Primitive &primitive, const tin } } - for (auto uvChannel = 0; uvChannel < MAX_NUMBER_TEXTURECOORDS; ++uvChannel) { + uvBuffers.resize(bufferTexCoordDatas.size()); + for (auto uvChannel = 0; uvChannel < bufferTexCoordDatas.size(); ++uvChannel) { if (bufferTexCoordDatas[uvChannel]) { std::vector &uvBuffer{uvBuffers[uvChannel]}; auto textureData = bufferTexCoordDatas[uvChannel]->getDataAt(vertexIndex); @@ -291,7 +296,8 @@ void glTFMesh::loadPrimitiveData(const tinygltf::Primitive &primitive, const tin } } - for (auto colorChannel = 0; colorChannel < MAX_NUMBER_COLORS; ++colorChannel) { + colorBuffers.resize(bufferColorDatas.size()); + for (auto colorChannel = 0; colorChannel < bufferColorDatas.size(); ++colorChannel) { if (bufferColorDatas[colorChannel]) { std::vector &colorBuffer{colorBuffers[colorChannel]}; std::vector colorData(4, 1.0F); diff --git a/components/libMeshLoader/tests/CMakeLists.txt b/components/libMeshLoader/tests/CMakeLists.txt index 56b9d31b..f7b77ee7 100644 --- a/components/libMeshLoader/tests/CMakeLists.txt +++ b/components/libMeshLoader/tests/CMakeLists.txt @@ -31,4 +31,7 @@ raco_package_add_test_resouces( meshes/CesiumMilkTruck/CesiumMilkTruck.gltf meshes/CesiumMilkTruck/CesiumMilkTruck.png meshes/CesiumMilkTruck/CesiumMilkTruck_data.bin + meshes/MosquitoInAmber/MosquitoInAmber.gltf + meshes/MosquitoInAmber/MosquitoInAmber.bin + meshes/MultipleVCols/multiple_VCols.gltf ) diff --git a/components/libMeshLoader/tests/FileLoader_test.cpp b/components/libMeshLoader/tests/FileLoader_test.cpp index 8b65c03d..f56ed25b 100644 --- a/components/libMeshLoader/tests/FileLoader_test.cpp +++ b/components/libMeshLoader/tests/FileLoader_test.cpp @@ -150,4 +150,35 @@ TEST_F(MeshLoaderTest, glTFWithTangentsAndBitangents) { ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_TANGENT), -1); ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_BITANGENT), -1); +} + + +TEST_F(MeshLoaderTest, glTFWithMultipleTexCoords) { + core::MeshDescriptor desc; + desc.absPath = test_path().append("meshes/MosquitoInAmber/MosquitoInAmber.gltf").string(); + desc.bakeAllSubmeshes = false; + desc.submeshIndex = 1; + + mesh_loader::glTFFileLoader fileloader(desc.absPath); + auto mesh = fileloader.loadMesh(desc); + + ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_UVMAP), -1); + ASSERT_EQ(mesh->attribIndex(mesh->ATTRIBUTE_UVMAP + std::to_string(0)), -1); + ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_UVMAP + std::to_string(1)), -1); + ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_UVMAP + std::to_string(2)), -1); +} + +TEST_F(MeshLoaderTest, glTFWithMultipleColors) { + core::MeshDescriptor desc; + desc.absPath = test_path().append("meshes/MultipleVCols/multiple_VCols.gltf").string(); + desc.bakeAllSubmeshes = true; + desc.submeshIndex = 1; + + mesh_loader::glTFFileLoader fileloader(desc.absPath); + auto mesh = fileloader.loadMesh(desc); + + ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_COLOR), -1); + ASSERT_EQ(mesh->attribIndex(mesh->ATTRIBUTE_COLOR + std::to_string(0)), -1); + ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_COLOR + std::to_string(1)), -1); + ASSERT_NE(mesh->attribIndex(mesh->ATTRIBUTE_COLOR + std::to_string(2)), -1); } \ No newline at end of file diff --git a/components/libRamsesBase/include/ramses_adaptor/CubeMapAdaptor.h b/components/libRamsesBase/include/ramses_adaptor/CubeMapAdaptor.h index feaa586d..2df256e5 100644 --- a/components/libRamsesBase/include/ramses_adaptor/CubeMapAdaptor.h +++ b/components/libRamsesBase/include/ramses_adaptor/CubeMapAdaptor.h @@ -30,8 +30,10 @@ class CubeMapAdaptor : public TypedObjectAdaptor subscriptions_; + std::array subscriptions_; raco::ramses_base::RamsesTextureCube textureData_; + + std::map> generateMipmapData(core::Errors* errors, int level, int& width, int& height); }; }; // namespace raco::ramses_adaptor \ No newline at end of file diff --git a/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h b/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h index fdc5ef7b..da87b8f9 100644 --- a/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h +++ b/components/libRamsesBase/include/ramses_adaptor/NodeAdaptor.h @@ -198,7 +198,7 @@ class SpatialAdaptor : public TypedObjectAdaptor, public if (linkDescriptor.isValid) { auto startHandle = raco::core::ValueHandle{linkDescriptor.start}; - if (startHandle && startHandle.type() == raco::data_storage::PrimitiveType::Vec4f) { + if (startHandle && startHandle.isVec4f()) { return rlogic::ERotationType::Quaternion; } } diff --git a/components/libRamsesBase/include/ramses_adaptor/utilities.h b/components/libRamsesBase/include/ramses_adaptor/utilities.h index 3f18dcdc..c70cc990 100644 --- a/components/libRamsesBase/include/ramses_adaptor/utilities.h +++ b/components/libRamsesBase/include/ramses_adaptor/utilities.h @@ -10,6 +10,7 @@ #pragma once +#include "components/DataChangeDispatcher.h" #include "core/CoreFormatter.h" #include "core/EditorObject.h" #include "core/Handles.h" @@ -18,14 +19,13 @@ #include "log_system/log.h" #include "ramses_adaptor/BuildOptions.h" #include "ramses_base/RamsesHandles.h" -#include "components/DataChangeDispatcher.h" #include "user_types/Material.h" #include "user_types/Node.h" #include "utils/MathUtils.h" +#include #include #include -#include #include namespace raco::ramses_adaptor { @@ -41,7 +41,7 @@ static constexpr const char* defaultVertexShader = }"; static constexpr const char* defaultVertexShaderWithNormals = -R"( + R"( #version 300 es precision mediump float; in vec3 a_Position; @@ -65,7 +65,7 @@ static constexpr const char* defaultFragmentShader = }"; static constexpr const char* defaultFragmentShaderWithNormals = -R"( + R"( #version 300 es precision mediump float; in float lambertian; @@ -88,7 +88,6 @@ static constexpr const char* defaultAnimationChannelName = "raco::ramses_adaptor static constexpr const char* defaultAnimationChannelTimestampsName = "raco::ramses_adaptor::DefaultAnimationTimestamps"; static constexpr const char* defaultAnimationChannelKeyframesName = "raco::ramses_adaptor::DefaultAnimationKeyframes"; - struct Vec3f { float x, y, z; bool operator==(const Vec3f& other) const { @@ -204,27 +203,29 @@ inline bool setLuaInputInEngine(rlogic::Property* property, const core::ValueHan case PrimitiveType::Int: success = property->set(valueHandle.as()); break; + case PrimitiveType::Int64: + success = property->set(valueHandle.as()); + break; case PrimitiveType::Bool: success = property->set(valueHandle.as()); break; - case PrimitiveType::Vec2f: - success = property->set(rlogic::vec2f{valueHandle[0].as(), valueHandle[1].as()}); - break; - case PrimitiveType::Vec3f: - success = property->set(rlogic::vec3f{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as()}); - break; - case PrimitiveType::Vec4f: - success = property->set(rlogic::vec4f{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as(), valueHandle[3].as()}); - break; - case PrimitiveType::Vec2i: - success = property->set(rlogic::vec2i{valueHandle[0].as(), valueHandle[1].as()}); - break; - case PrimitiveType::Vec3i: - success = property->set(rlogic::vec3i{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as()}); - break; - case PrimitiveType::Vec4i: - success = property->set(rlogic::vec4i{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as(), valueHandle[3].as()}); + case PrimitiveType::Struct: { + auto typeDesc = &valueHandle.constValueRef()->asStruct().getTypeDescription(); + if (typeDesc == &core::Vec2f::typeDescription) { + success = property->set(rlogic::vec2f{valueHandle[0].as(), valueHandle[1].as()}); + } else if (typeDesc == &core::Vec3f::typeDescription) { + success = property->set(rlogic::vec3f{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as()}); + } else if (typeDesc == &core::Vec4f::typeDescription) { + success = property->set(rlogic::vec4f{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as(), valueHandle[3].as()}); + } else if (typeDesc == &core::Vec2i::typeDescription) { + success = property->set(rlogic::vec2i{valueHandle[0].as(), valueHandle[1].as()}); + } else if (typeDesc == &core::Vec3i::typeDescription) { + success = property->set(rlogic::vec3i{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as()}); + } else if (typeDesc == &core::Vec4i::typeDescription) { + success = property->set(rlogic::vec4i{valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as(), valueHandle[3].as()}); + } break; + } case PrimitiveType::String: success = property->set(valueHandle.as()); break; @@ -255,7 +256,7 @@ class ReadFromEngineManager { } static void setVec2f(const core::ValueHandle& handle, double x, double y, core::DataChangeRecorder& recorder) { - raco::data_storage::Vec2f& v = handle.valueRef()->asVec2f(); + raco::core::Vec2f& v = dynamic_cast(handle.valueRef()->asStruct()); if (*v.x != x) { v.x = x; @@ -268,7 +269,7 @@ class ReadFromEngineManager { } static void setVec3f(const core::ValueHandle& handle, double x, double y, double z, core::DataChangeRecorder& recorder) { - raco::data_storage::Vec3f& v = handle.valueRef()->asVec3f(); + raco::core::Vec3f& v = dynamic_cast(handle.valueRef()->asStruct()); if (*v.x != x) { v.x = x; @@ -285,7 +286,7 @@ class ReadFromEngineManager { } static void setVec4f(const core::ValueHandle& handle, double x, double y, double z, double w, core::DataChangeRecorder& recorder) { - raco::data_storage::Vec4f& v = handle.valueRef()->asVec4f(); + raco::core::Vec4f& v = dynamic_cast(handle.valueRef()->asStruct()); if (*v.x != x) { v.x = x; @@ -306,7 +307,7 @@ class ReadFromEngineManager { } static void setVec2i(const core::ValueHandle& handle, int x, int y, core::DataChangeRecorder& recorder) { - raco::data_storage::Vec2i& v = handle.valueRef()->asVec2i(); + raco::core::Vec2i& v = dynamic_cast(handle.valueRef()->asStruct()); if (*v.i1_ != x) { v.i1_ = x; @@ -319,7 +320,7 @@ class ReadFromEngineManager { } static void setVec3i(const core::ValueHandle& handle, int x, int y, int z, core::DataChangeRecorder& recorder) { - raco::data_storage::Vec3i& v = handle.valueRef()->asVec3i(); + raco::core::Vec3i& v = dynamic_cast(handle.valueRef()->asStruct()); if (*v.i1_ != x) { v.i1_ = x; @@ -336,7 +337,7 @@ class ReadFromEngineManager { } static void setVec4i(const core::ValueHandle& handle, int x, int y, int z, int w, core::DataChangeRecorder& recorder) { - raco::data_storage::Vec4i& v = handle.valueRef()->asVec4i(); + raco::core::Vec4i& v = dynamic_cast(handle.valueRef()->asStruct()); if (*v.i1_ != x) { v.i1_ = x; @@ -357,13 +358,25 @@ class ReadFromEngineManager { } }; +void getLuaOutputFromEngine(const rlogic::Property& property, const core::ValueHandle& valueHandle, core::DataChangeRecorder& recorder); + +inline void getComplexLuaOutputFromEngine(const rlogic::Property& property, const core::ValueHandle& valueHandle, core::DataChangeRecorder& recorder) { + for (size_t i{0}; i < valueHandle.size(); i++) { + if (property.getType() == rlogic::EPropertyType::Array) { + getLuaOutputFromEngine(*property.getChild(i), valueHandle[i], recorder); + } else { + getLuaOutputFromEngine(*property.getChild(valueHandle[i].getPropName()), valueHandle[i], recorder); + } + } +} + inline void getLuaOutputFromEngine(const rlogic::Property& property, const core::ValueHandle& valueHandle, core::DataChangeRecorder& recorder) { // Don't spam the log with constant messages: //LOG_TRACE(log_system::RAMSES_ADAPTOR, "{} => {}", fmt::ptr(&property), valueHandle); using core::PrimitiveType; // read quaternion rotation data - if (valueHandle.type() == PrimitiveType::Vec3f && property.getType() == rlogic::EPropertyType::Vec4f) { + if (valueHandle.isVec3f() && property.getType() == rlogic::EPropertyType::Vec4f) { auto [x, y, z, w] = property.get().value(); auto [eulerX, eulerY, eulerZ] = raco::utils::math::quaternionToXYZDegrees(x, y, z, w); ReadFromEngineManager::setVec3f(valueHandle, eulerX, eulerY, eulerZ, recorder); @@ -379,55 +392,47 @@ inline void getLuaOutputFromEngine(const rlogic::Property& property, const core: ReadFromEngineManager::setValueFromEngineValue(valueHandle, property.get().value(), recorder); break; } - case PrimitiveType::Bool: { - ReadFromEngineManager::setValueFromEngineValue(valueHandle, property.get().value(), recorder); - break; - } - case PrimitiveType::Vec2f: { - auto [x, y] = property.get().value(); - ReadFromEngineManager::setVec2f(valueHandle, x, y, recorder); + case PrimitiveType::Int64: { + ReadFromEngineManager::setValueFromEngineValue(valueHandle, property.get().value(), recorder); break; } - case PrimitiveType::Vec3f: { - auto [x, y, z] = property.get().value(); - ReadFromEngineManager::setVec3f(valueHandle, x, y, z, recorder); - break; - } - case PrimitiveType::Vec4f: { - auto [x, y, z, w] = property.get().value(); - ReadFromEngineManager::setVec4f(valueHandle, x, y, z, w, recorder); - break; - } - case PrimitiveType::Vec2i: { - auto [i1, i2] = property.get().value(); - ReadFromEngineManager::setVec2i(valueHandle, i1, i2, recorder); - break; - } - case PrimitiveType::Vec3i: { - auto [i1, i2, i3] = property.get().value(); - ReadFromEngineManager::setVec3i(valueHandle, i1, i2, i3, recorder); - break; - } - case PrimitiveType::Vec4i: { - auto [i1, i2, i3, i4] = property.get().value(); - ReadFromEngineManager::setVec4i(valueHandle, i1, i2, i3, i4, recorder); + case PrimitiveType::Bool: { + ReadFromEngineManager::setValueFromEngineValue(valueHandle, property.get().value(), recorder); break; } case PrimitiveType::String: { ReadFromEngineManager::setValueFromEngineValue(valueHandle, property.get().value(), recorder); break; } - case PrimitiveType::Table: - case PrimitiveType::Struct: { - for (size_t i{0}; i < valueHandle.size(); i++) { - if (property.getType() == rlogic::EPropertyType::Array) { - getLuaOutputFromEngine(*property.getChild(i), valueHandle[i], recorder); - } else { - getLuaOutputFromEngine(*property.getChild(valueHandle[i].getPropName()), valueHandle[i], recorder); - } + case PrimitiveType::Struct: { + auto typeDesc = &valueHandle.constValueRef()->asStruct().getTypeDescription(); + if (typeDesc == &core::Vec2f::typeDescription) { + auto [x, y] = property.get().value(); + ReadFromEngineManager::setVec2f(valueHandle, x, y, recorder); + } else if (typeDesc == &core::Vec3f::typeDescription) { + auto [x, y, z] = property.get().value(); + ReadFromEngineManager::setVec3f(valueHandle, x, y, z, recorder); + } else if (typeDesc == &core::Vec4f::typeDescription) { + auto [x, y, z, w] = property.get().value(); + ReadFromEngineManager::setVec4f(valueHandle, x, y, z, w, recorder); + } else if (typeDesc == &core::Vec2i::typeDescription) { + auto [i1, i2] = property.get().value(); + ReadFromEngineManager::setVec2i(valueHandle, i1, i2, recorder); + } else if (typeDesc == &core::Vec3i::typeDescription) { + auto [i1, i2, i3] = property.get().value(); + ReadFromEngineManager::setVec3i(valueHandle, i1, i2, i3, recorder); + } else if (typeDesc == &core::Vec4i::typeDescription) { + auto [i1, i2, i3, i4] = property.get().value(); + ReadFromEngineManager::setVec4i(valueHandle, i1, i2, i3, i4, recorder); + } else { + getComplexLuaOutputFromEngine(property, valueHandle, recorder); } break; } + case PrimitiveType::Table: { + getComplexLuaOutputFromEngine(property, valueHandle, recorder); + break; + } } } @@ -444,24 +449,24 @@ inline void setUniform(ramses::Appearance* appearance, const core::ValueHandle& case PrimitiveType::Int: appearance->setInputValueInt32(input, static_cast(valueHandle.as())); break; - case PrimitiveType::Vec2f: - appearance->setInputValueVector2f(input, valueHandle[0].as(), valueHandle[1].as()); - break; - case PrimitiveType::Vec3f: - appearance->setInputValueVector3f(input, valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as()); - break; - case PrimitiveType::Vec4f: - appearance->setInputValueVector4f(input, valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as(), valueHandle[3].as()); - break; - case PrimitiveType::Vec2i: - appearance->setInputValueVector2i(input, static_cast(valueHandle[0].as()), static_cast(valueHandle[1].as())); - break; - case PrimitiveType::Vec3i: - appearance->setInputValueVector3i(input, static_cast(valueHandle[0].as()), static_cast(valueHandle[1].as()), static_cast(valueHandle[2].as())); - break; - case PrimitiveType::Vec4i: - appearance->setInputValueVector4i(input, static_cast(valueHandle[0].as()), static_cast(valueHandle[1].as()), static_cast(valueHandle[2].as()), static_cast(valueHandle[3].as())); + + case PrimitiveType::Struct: { + auto typeDesc = &valueHandle.constValueRef()->asStruct().getTypeDescription(); + if (typeDesc == &core::Vec2f::typeDescription) { + appearance->setInputValueVector2f(input, valueHandle[0].as(), valueHandle[1].as()); + } else if (typeDesc == &core::Vec3f::typeDescription) { + appearance->setInputValueVector3f(input, valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as()); + } else if (typeDesc == &core::Vec4f::typeDescription) { + appearance->setInputValueVector4f(input, valueHandle[0].as(), valueHandle[1].as(), valueHandle[2].as(), valueHandle[3].as()); + } else if (typeDesc == &core::Vec2i::typeDescription) { + appearance->setInputValueVector2i(input, static_cast(valueHandle[0].as()), static_cast(valueHandle[1].as())); + } else if (typeDesc == &core::Vec3i::typeDescription) { + appearance->setInputValueVector3i(input, static_cast(valueHandle[0].as()), static_cast(valueHandle[1].as()), static_cast(valueHandle[2].as())); + } else if (typeDesc == &core::Vec4i::typeDescription) { + appearance->setInputValueVector4i(input, static_cast(valueHandle[0].as()), static_cast(valueHandle[1].as()), static_cast(valueHandle[2].as()), static_cast(valueHandle[3].as())); + } break; + } default: break; } diff --git a/components/libRamsesBase/include/ramses_base/CoreInterfaceImpl.h b/components/libRamsesBase/include/ramses_base/CoreInterfaceImpl.h index 0988662d..36f7de4b 100644 --- a/components/libRamsesBase/include/ramses_base/CoreInterfaceImpl.h +++ b/components/libRamsesBase/include/ramses_base/CoreInterfaceImpl.h @@ -23,6 +23,7 @@ class CoreInterfaceImpl final : public raco::core::EngineInterface { bool parseLuaScriptModule(const std::string& luaScriptModule, std::string& outError) override; bool extractLuaDependencies(const std::string& luaScript, std::vector& moduleList, std::string& outError) override; const std::map& enumerationDescription(raco::core::EngineEnumeration type) const override; + std::string luaNameForPrimitiveType(raco::core::EnginePrimitive engineType) const override; private: BaseEngineBackend* backend_; diff --git a/components/libRamsesBase/include/ramses_base/Utils.h b/components/libRamsesBase/include/ramses_base/Utils.h index 16881de3..eb71ac95 100644 --- a/components/libRamsesBase/include/ramses_base/Utils.h +++ b/components/libRamsesBase/include/ramses_base/Utils.h @@ -47,7 +47,8 @@ rlogic::RamsesLogicVersion getLogicEngineVersion(); std::string getRamsesVersionString(); std::string getLogicEngineVersionString(); -void enableLogicLoggerOutputToStdout(bool toStdOut); -void setRamsesAndLogicConsoleLogLevel(spdlog::level::level_enum level); +void installLogicLogHandler(); +void setRamsesLogLevel(spdlog::level::level_enum level); +void setLogicLogLevel(spdlog::level::level_enum level); }; // namespace raco::ramses_base diff --git a/components/libRamsesBase/src/ramses_adaptor/CubeMapAdaptor.cpp b/components/libRamsesBase/src/ramses_adaptor/CubeMapAdaptor.cpp index ec977410..8a038131 100644 --- a/components/libRamsesBase/src/ramses_adaptor/CubeMapAdaptor.cpp +++ b/components/libRamsesBase/src/ramses_adaptor/CubeMapAdaptor.cpp @@ -39,55 +39,50 @@ CubeMapAdaptor::CubeMapAdaptor(SceneAdaptor* sceneAdaptor, std::shared_ptrdispatcher()->registerOn(core::ValueHandle{editorObject, &user_types::CubeMap::anisotropy_}, [this]() { tagDirty(); }), + sceneAdaptor->dispatcher()->registerOn(core::ValueHandle{editorObject, &user_types::CubeMap::generateMipmaps_}, [this]() { + tagDirty(); + }), + sceneAdaptor->dispatcher()->registerOn(core::ValueHandle{editorObject, &user_types::CubeMap::mipmapLevel_}, [this]() { + tagDirty(); + }), sceneAdaptor_->dispatcher()->registerOnPreviewDirty(editorObject, [this]() { tagDirty(); })} {} raco::ramses_base::RamsesTextureCube CubeMapAdaptor::createTexture(core::Errors* errors) { - std::map> data; - unsigned int width = -1; - unsigned int height = -1; + if (*editorObject()->mipmapLevel_ < 1 || *editorObject()->mipmapLevel_ > 4) { + return fallbackCube(); + } - // check all URIs for error conditions to show them in the UI - bool allImagesOk = true; - for (const auto& propName : {"uriFront", "uriBack", "uriLeft", "uriRight", "uriTop", "uriBottom"}) { - std::string uri = editorObject()->get(propName)->asString(); - if (!uri.empty()) { - unsigned int curWidth; - unsigned int curHeight; - if (0 == lodepng::decode(data[propName], curWidth, curHeight, raco::core::PathQueries::resolveUriPropertyToAbsolutePath(sceneAdaptor_->project(), {editorObject(), {propName}}))) { - if (curWidth != curHeight) { - LOG_ERROR(raco::log_system::RAMSES_ADAPTOR, "CubeMap '{}': non-square image '{}' for '{}'", editorObject()->objectName(), uri, propName); - errors->addError(core::ErrorCategory::PARSE_ERROR, core::ErrorLevel::ERROR, {editorObject()->shared_from_this(), {propName}}, - "None-square image " + std::to_string(curWidth) + "x" + std::to_string(curHeight)); - allImagesOk = false; - } else { - if (width == -1) { - width = curWidth; - height = curHeight; - } - if (width != curWidth || height != curHeight) { - LOG_ERROR(raco::log_system::RAMSES_ADAPTOR, "CubeMap '{}': incompatible image sizes", editorObject()->objectName()); - errors->addError(core::ErrorCategory::PARSE_ERROR, core::ErrorLevel::ERROR, {editorObject()->shared_from_this(), {propName}}, - "Incompatible image size " + std::to_string(curWidth) + "x" + std::to_string(curHeight) + ", expected is " + std::to_string(width) + "x" + std::to_string(height)); - allImagesOk = false; - } else { - errors->removeError({editorObject()->shared_from_this(), {propName}}); - } - } - } else { - LOG_ERROR(raco::log_system::RAMSES_ADAPTOR, "CubeMap '{}': Couldn't load png file from '{}'", editorObject()->objectName(), uri); - errors->addError(core::ErrorCategory::PARSE_ERROR, core::ErrorLevel::ERROR, {editorObject()->shared_from_this(), {propName}}, "Image file could not be loaded."); - allImagesOk = false; - } - } else { - allImagesOk = false; + int width = -1; + int height = -1; + + std::vector>> rawMipDatas; + std::vector mipDatas; + + auto mipMapsOk = true; + for (int level = 1; level <= *editorObject()->mipmapLevel_; ++level) { + auto& mipData = rawMipDatas.emplace_back(generateMipmapData(errors, level, width, height)); + if (mipData.empty()) { + mipMapsOk = false; } } - if (!allImagesOk) { + + if (!mipMapsOk) { return fallbackCube(); } + for (auto& mipData : rawMipDatas) { + // Order: +X, -X, +Y, -Y, +Z, -Z + mipDatas.emplace_back(ramses::CubeMipLevelData(static_cast(mipData["uriRight"].size()), + mipData["uriRight"].data(), + mipData["uriLeft"].data(), + mipData["uriTop"].data(), + mipData["uriBottom"].data(), + mipData["uriFront"].data(), + mipData["uriBack"].data())); + } + std::string infoText = "CubeMap information\n\n"; infoText += fmt::format("Width: {} px\n", width); @@ -98,18 +93,8 @@ raco::ramses_base::RamsesTextureCube CubeMapAdaptor::createTexture(core::Errors* infoText += "Format: RGBA8"; errors->addError(core::ErrorCategory::GENERAL, core::ErrorLevel::INFORMATION, {editorObject()->shared_from_this()}, infoText); - - // Order: +x, -X, +Y, -Y, +Z, -Z - ramses::CubeMipLevelData mipData = ramses::CubeMipLevelData((uint32_t)data["uriRight"].size(), - data["uriRight"].data(), - data["uriLeft"].data(), - data["uriTop"].data(), - data["uriBottom"].data(), - data["uriFront"].data(), - data["uriBack"].data()); - - return raco::ramses_base::ramsesTextureCube(sceneAdaptor_->scene(), ramses::ETextureFormat::RGBA8, width, 1u, &mipData, false, {}, ramses::ResourceCacheFlag_DoNotCache); + return raco::ramses_base::ramsesTextureCube(sceneAdaptor_->scene(), ramses::ETextureFormat::RGBA8, width, *editorObject()->mipmapLevel_, mipDatas.data(), *editorObject()->generateMipmaps_, {}, ramses::ResourceCacheFlag_DoNotCache); } raco::ramses_base::RamsesTextureCube CubeMapAdaptor::fallbackCube() { @@ -130,14 +115,72 @@ raco::ramses_base::RamsesTextureCube CubeMapAdaptor::fallbackCube() { data["uriFront"].data(), data["uriBack"].data()); - return raco::ramses_base::ramsesTextureCube(sceneAdaptor_->scene(), ramses::ETextureFormat::RGBA8, TextureSamplerAdaptor::FALLBACK_TEXTURE_SIZE_PX, 1u, &mipData, false, {}, ramses::ResourceCacheFlag_DoNotCache); + return raco::ramses_base::ramsesTextureCube(sceneAdaptor_->scene(), ramses::ETextureFormat::RGBA8, TextureSamplerAdaptor::FALLBACK_TEXTURE_SIZE_PX, 1u, &mipData, *editorObject()->generateMipmaps_, {}, ramses::ResourceCacheFlag_DoNotCache); } std::string CubeMapAdaptor::createDefaultTextureDataName() { return this->editorObject()->objectName() + "_TextureCube"; } +std::map> CubeMapAdaptor::generateMipmapData(core::Errors* errors, int level, int& width, int& height) { + std::map> data; + auto mipmapOk = true; + + auto getUriPropName = [](const auto& propName, int level) { + if (level > 1) { + return fmt::format("level{}{}", level, propName); + } + return propName; + }; + + for (std::string propName : {"uriRight", "uriLeft", "uriTop", "uriBottom", "uriFront", "uriBack", }) { + std::string uri = editorObject()->get(getUriPropName(propName, level))->asString(); + if (!uri.empty()) { + unsigned int curWidth; + unsigned int curHeight; + if (0 == lodepng::decode(data[propName], curWidth, curHeight, raco::core::PathQueries::resolveUriPropertyToAbsolutePath(sceneAdaptor_->project(), {editorObject(), {getUriPropName(propName, level)}}))) { + if (curWidth != curHeight) { + LOG_ERROR(raco::log_system::RAMSES_ADAPTOR, "CubeMap '{}': non-square image '{}' for '{}'", editorObject()->objectName(), uri, getUriPropName(propName, level)); + errors->addError(core::ErrorCategory::PARSE_ERROR, core::ErrorLevel::ERROR, {editorObject()->shared_from_this(), {getUriPropName(propName, level)}}, + fmt::format("Non-square image size {}x{}", curWidth, curHeight)); + mipmapOk = false; + } else { + if (level == 1 && width == -1) { + width = curWidth; + height = curHeight; + } + + if ((level == 1 && (width != curWidth || height != curHeight)) || (curWidth != width * std::pow(0.5, level - 1) || curHeight != height * std::pow(0.5, level - 1))) { + LOG_ERROR(raco::log_system::RAMSES_ADAPTOR, "CubeMap '{}': incompatible image sizes", editorObject()->objectName()); + auto errorMsg = (width == -1) + ? "Level 1 mipmap not defined" + : fmt::format("Incompatible image size {}x{}, expected is {}x{}", curWidth, curHeight, static_cast(width * std::pow(0.5, level - 1)), static_cast(height * std::pow(0.5, level - 1))); + errors->addError(core::ErrorCategory::PARSE_ERROR, core::ErrorLevel::ERROR, {editorObject()->shared_from_this(), {getUriPropName(propName, level)}}, errorMsg); + mipmapOk = false; + } else { + errors->removeError({editorObject()->shared_from_this(), {getUriPropName(propName, level)}}); + } + } + } else { + LOG_ERROR(raco::log_system::RAMSES_ADAPTOR, "CubeMap '{}': Couldn't load png file from '{}'", editorObject()->objectName(), uri); + errors->addError(core::ErrorCategory::PARSE_ERROR, core::ErrorLevel::ERROR, {editorObject()->shared_from_this(), {getUriPropName(propName, level)}}, "Image file could not be loaded."); + mipmapOk = false; + } + } else { + mipmapOk = false; + } + } + + if (!mipmapOk) { + data.clear(); + } + + return data; +} + bool CubeMapAdaptor::sync(core::Errors* errors) { + errors->removeError({editorObject()->shared_from_this()}); + textureData_.reset(); textureData_ = createTexture(errors); diff --git a/components/libRamsesBase/src/ramses_adaptor/RenderBufferAdaptor.cpp b/components/libRamsesBase/src/ramses_adaptor/RenderBufferAdaptor.cpp index 79a4686e..3bad8870 100644 --- a/components/libRamsesBase/src/ramses_adaptor/RenderBufferAdaptor.cpp +++ b/components/libRamsesBase/src/ramses_adaptor/RenderBufferAdaptor.cpp @@ -10,7 +10,7 @@ #include "ramses_adaptor/RenderBufferAdaptor.h" #include "ramses_base/RamsesHandles.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" namespace raco::ramses_adaptor { @@ -64,8 +64,8 @@ bool RenderBufferAdaptor::sync(core::Errors* errors) { ramses::ERenderBufferFormat format = static_cast(*editorObject()->format_); ramses::ERenderBufferType type = bufferTypeFromFormat.at(format); - const auto& widthRange = editorObject()->width_.staticQuery>(); - const auto& heightRange = editorObject()->height_.staticQuery>(); + const auto& widthRange = editorObject()->width_.staticQuery>(); + const auto& heightRange = editorObject()->height_.staticQuery>(); buffer_ = raco::ramses_base::ramsesRenderBuffer(sceneAdaptor_->scene(), std::min(std::max(*widthRange.min_, *editorObject()->width_), *widthRange.max_), diff --git a/components/libRamsesBase/src/ramses_base/CoreInterfaceImpl.cpp b/components/libRamsesBase/src/ramses_base/CoreInterfaceImpl.cpp index 7c0e9456..ce899d15 100644 --- a/components/libRamsesBase/src/ramses_base/CoreInterfaceImpl.cpp +++ b/components/libRamsesBase/src/ramses_base/CoreInterfaceImpl.cpp @@ -22,7 +22,7 @@ bool CoreInterfaceImpl::parseShader(const std::string& vertexShader, const std:: return raco::ramses_base::parseShaderText(backend_->internalScene(), vertexShader, geometryShader, fragmentShader, shaderDefines, outUniforms, outAttributes, outError); } -bool CoreInterfaceImpl::parseLuaScript(const std::string& luaScript, const raco::data_storage::Table &modules, raco::core::PropertyInterfaceList& outInputs, raco::core::PropertyInterfaceList& outOutputs, std::string& outError) { +bool CoreInterfaceImpl::parseLuaScript(const std::string& luaScript, const raco::data_storage::Table& modules, raco::core::PropertyInterfaceList& outInputs, raco::core::PropertyInterfaceList& outOutputs, std::string& outError) { return raco::ramses_base::parseLuaScript(backend_->logicEngine(), luaScript, modules, outInputs, outOutputs, outError); } @@ -76,4 +76,27 @@ const std::map& CoreInterfaceImpl::enumerationDescription(raco } } +std::string CoreInterfaceImpl::luaNameForPrimitiveType(raco::core::EnginePrimitive engineType) const { + static const std::unordered_map nameMap = + {{raco::core::EnginePrimitive::Bool, "BOOL"}, + {raco::core::EnginePrimitive::Int32, "INT32"}, + {raco::core::EnginePrimitive::Int64, "INT64"}, + {raco::core::EnginePrimitive::Double, "FLOAT"}, + {raco::core::EnginePrimitive::String, "STRING"}, + {raco::core::EnginePrimitive::Vec2f, "VEC2F"}, + {raco::core::EnginePrimitive::Vec3f, "VEC3F"}, + {raco::core::EnginePrimitive::Vec4f, "VEC4F"}, + {raco::core::EnginePrimitive::Vec2i, "VEC2I"}, + {raco::core::EnginePrimitive::Vec3i, "VEC3I"}, + {raco::core::EnginePrimitive::Vec4i, "VEC4I"}, + {raco::core::EnginePrimitive::Struct, "STRUCT"}, + {raco::core::EnginePrimitive::Array, "ARRAY"}}; + + auto it = nameMap.find(engineType); + if (it != nameMap.end()) { + return it->second; + } + return "Unknown Type"; +} + } // namespace raco::ramses_base \ No newline at end of file diff --git a/components/libRamsesBase/src/ramses_base/Utils.cpp b/components/libRamsesBase/src/ramses_base/Utils.cpp index 96f6ebda..1e9b9b6d 100644 --- a/components/libRamsesBase/src/ramses_base/Utils.cpp +++ b/components/libRamsesBase/src/ramses_base/Utils.cpp @@ -134,6 +134,7 @@ void fillLuaScriptInterface(std::vector &interfac {rlogic::EPropertyType::Vec3f, raco::core::EnginePrimitive::Vec3f}, {rlogic::EPropertyType::Vec4f, raco::core::EnginePrimitive::Vec4f}, {rlogic::EPropertyType::Int32, raco::core::EnginePrimitive::Int32}, + {rlogic::EPropertyType::Int64, raco::core::EnginePrimitive::Int64}, {rlogic::EPropertyType::Vec2i, raco::core::EnginePrimitive::Vec2i}, {rlogic::EPropertyType::Vec3i, raco::core::EnginePrimitive::Vec3i}, {rlogic::EPropertyType::Vec4i, raco::core::EnginePrimitive::Vec4i}, @@ -237,43 +238,105 @@ std::string getLogicEngineVersionString() { return std::string(getLogicEngineVersion().string); } -void enableLogicLoggerOutputToStdout(bool enabled) { - rlogic::Logger::SetDefaultLogging(enabled); +rlogic::ELogMessageType toLogicLogLevel(spdlog::level::level_enum level) { + using namespace spdlog::level; + using namespace rlogic; + + switch (level) { + case level_enum::trace: + return ELogMessageType::Trace; + case level_enum::debug: + return ELogMessageType::Debug; + case level_enum::info: + return ELogMessageType::Info; + case level_enum::warn: + return ELogMessageType::Warn; + case level_enum::err: + return ELogMessageType::Error; + case level_enum::critical: + return ELogMessageType::Fatal; + default: + return ELogMessageType::Off; + } } -void setRamsesAndLogicConsoleLogLevel(spdlog::level::level_enum level) { +ramses::ELogLevel toRamsesLogLevel(spdlog::level::level_enum level) { using namespace spdlog::level; + using namespace ramses; switch (level) { case level_enum::trace: - rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Trace); - ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Trace); - return; + return ELogLevel::Trace; case level_enum::debug: - rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Debug); - ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Debug); - return; + return ELogLevel::Debug; case level_enum::info: - rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Info); - ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Info); - return; + return ELogLevel::Info; case level_enum::warn: - rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Warn); - ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Warn); - return; + return ELogLevel::Warn; case level_enum::err: - rlogic::Logger::SetLogVerbosityLimit(rlogic::ELogMessageType::Error); - ramses::RamsesFramework::SetConsoleLogLevel(ramses::ELogLevel::Error); - return; + return ELogLevel::Error; 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; + return ELogLevel::Fatal; + default: + return ELogLevel::Off; + } +} + +spdlog::level::level_enum toSpdLogLevel(ramses::ELogLevel level) { + using namespace spdlog::level; + using namespace ramses; + + switch (level) { + case ELogLevel::Trace: + return level_enum::trace; + case ELogLevel::Debug: + return level_enum::debug; + case ELogLevel::Info: + return level_enum::info; + case ELogLevel::Warn: + return level_enum::warn; + case ELogLevel::Error: + return level_enum::err; + case ELogLevel::Fatal: + return level_enum::critical; + default: + return level_enum::off; + } +} + +spdlog::level::level_enum toSpdLogLevel(rlogic::ELogMessageType level) { + using namespace spdlog::level; + using namespace rlogic; + + switch (level) { + case ELogMessageType::Trace: + return level_enum::trace; + case ELogMessageType::Debug: + return level_enum::debug; + case ELogMessageType::Info: + return level_enum::info; + case ELogMessageType::Warn: + return level_enum::warn; + case ELogMessageType::Error: + return level_enum::err; + case ELogMessageType::Fatal: + return level_enum::critical; + default: + return level_enum::off; } } +void installLogicLogHandler() { + rlogic::Logger::SetDefaultLogging(false); + rlogic::Logger::SetLogHandler([](rlogic::ELogMessageType level, std::string_view message) { SPDLOG_LOGGER_CALL(raco::log_system::get(raco::log_system::RAMSES_LOGIC), toSpdLogLevel(level), message); }); +} + +void setRamsesLogLevel(spdlog::level::level_enum level) { + ramses::RamsesFramework::SetConsoleLogLevel(toRamsesLogLevel(level)); +} + +void setLogicLogLevel(spdlog::level::level_enum level) { + rlogic::Logger::SetLogVerbosityLimit(toLogicLogLevel(level)); +} + } // namespace raco::ramses_base diff --git a/components/libRamsesBase/tests/CMakeLists.txt b/components/libRamsesBase/tests/CMakeLists.txt index b66a23fd..aaf7364d 100644 --- a/components/libRamsesBase/tests/CMakeLists.txt +++ b/components/libRamsesBase/tests/CMakeLists.txt @@ -13,6 +13,7 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h set(TEST_SOURCES AnimationAdaptor_test.cpp AnimationChannelAdaptor_test.cpp + CubeMapAdaptor_test.cpp RamsesBaseFixture.h LinkAdaptor_test.cpp LuaScriptAdaptor_test.cpp @@ -43,7 +44,11 @@ raco_package_add_headless_test( ) raco_package_add_test_resouces( libRamsesBase_test "${CMAKE_SOURCE_DIR}/resources" + images/blue_1024.png images/DuckCM.png + images/green_512.png + images/red_128.png + images/yellow_256.png shaders/basic.frag shaders/basic.vert shaders/simple_texture.frag diff --git a/components/libRamsesBase/tests/CubeMapAdaptor_test.cpp b/components/libRamsesBase/tests/CubeMapAdaptor_test.cpp new file mode 100644 index 00000000..f42b53c2 --- /dev/null +++ b/components/libRamsesBase/tests/CubeMapAdaptor_test.cpp @@ -0,0 +1,192 @@ +/* + * 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 "RamsesBaseFixture.h" +#include "ramses_adaptor/CubeMapAdaptor.h" + +class CubeMapAdaptorFixture : public RamsesBaseFixture<> {}; + +TEST_F(CubeMapAdaptorFixture, cubeMapGenerationAtMultipleLevels) { + auto cubeMap = create("Cubemap"); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriBack_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriBottom_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriFront_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriLeft_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriRight_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriTop_}, (test_path() / "images" / "blue_1024.png").string()); + + dispatch(); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriBack_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriBottom_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriFront_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriLeft_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriRight_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriTop_}, (test_path() / "images" / "green_512.png").string()); + + dispatch(); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriBack_}, (test_path() / "images" / "yellow_256.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriBottom_}, (test_path() / "images" / "yellow_256.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriFront_}, (test_path() / "images" / "yellow_256.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriLeft_}, (test_path() / "images" / "yellow_256.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriRight_}, (test_path() / "images" / "yellow_256.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriTop_}, (test_path() / "images" / "yellow_256.png").string()); + + dispatch(); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriBack_}, (test_path() / "images" / "red_128.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriBottom_}, (test_path() / "images" / "red_128.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriFront_}, (test_path() / "images" / "red_128.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriLeft_}, (test_path() / "images" / "red_128.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriRight_}, (test_path() / "images" / "red_128.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriTop_}, (test_path() / "images" / "red_128.png").string()); + + dispatch(); + + auto cubeMapStuff{select(*sceneContext.scene(), ramses::ERamsesObjectType::ERamsesObjectType_TextureSampler)}; + ASSERT_EQ(cubeMapStuff.size(), 1); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 2); + dispatch(); + cubeMapStuff = select(*sceneContext.scene(), ramses::ERamsesObjectType::ERamsesObjectType_TextureSampler); + ASSERT_EQ(cubeMapStuff.size(), 1); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 3); + dispatch(); + cubeMapStuff = select(*sceneContext.scene(), ramses::ERamsesObjectType::ERamsesObjectType_TextureSampler); + ASSERT_EQ(cubeMapStuff.size(), 1); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 4); + dispatch(); + cubeMapStuff = select(*sceneContext.scene(), ramses::ERamsesObjectType::ERamsesObjectType_TextureSampler); + ASSERT_EQ(cubeMapStuff.size(), 1); +} + +TEST_F(CubeMapAdaptorFixture, wrongMipMapLevelImageSizes) { + auto cubeMap = create("Cubemap"); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 4); + dispatch(); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriBack_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriBottom_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriFront_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriLeft_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriRight_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriTop_}, (test_path() / "images" / "blue_1024.png").string()); + dispatch(); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriBack_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriBottom_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriFront_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriLeft_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriRight_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriTop_}, (test_path() / "images" / "blue_1024.png").string()); + dispatch(); + + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriBack_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriBottom_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriFront_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriLeft_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriRight_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriTop_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriBack_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriBottom_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriFront_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriLeft_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriRight_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level3uriTop_}, (test_path() / "images" / "blue_1024.png").string()); + dispatch(); + + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriBack_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriBottom_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriFront_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriLeft_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriRight_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriTop_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriBack_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriBottom_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriFront_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriLeft_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriRight_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level4uriTop_}, (test_path() / "images" / "blue_1024.png").string()); + dispatch(); + + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriBack_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriBottom_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriFront_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriLeft_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriRight_})); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriTop_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 3); + dispatch(); + + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriBack_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriBottom_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriFront_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriLeft_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriRight_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level4uriTop_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 2); + dispatch(); + + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriBack_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriBottom_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriFront_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriLeft_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriRight_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level3uriTop_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 1); + dispatch(); + + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriBack_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriBottom_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriFront_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriLeft_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriRight_})); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::level2uriTop_})); +} + +TEST_F(CubeMapAdaptorFixture, ramsesAutoMipMapGenerationWarningPersistsAfterChangingURI) { + auto cubeMap = create("Cubemap"); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::generateMipmaps_}, true); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 2); + dispatch(); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriBack_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriBottom_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriFront_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriLeft_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriRight_}, (test_path() / "images" / "blue_1024.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::uriTop_}, (test_path() / "images" / "blue_1024.png").string()); + + dispatch(); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriBack_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriBottom_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriFront_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriLeft_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriRight_}, (test_path() / "images" / "green_512.png").string()); + commandInterface.set({cubeMap, &raco::user_types::CubeMap::level2uriTop_}, (test_path() / "images" / "green_512.png").string()); + + dispatch(); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); +} \ No newline at end of file diff --git a/datamodel/libCore/CMakeLists.txt b/datamodel/libCore/CMakeLists.txt index f72a7056..300c05a3 100644 --- a/datamodel/libCore/CMakeLists.txt +++ b/datamodel/libCore/CMakeLists.txt @@ -46,6 +46,8 @@ add_library(libCore include/core/Serialization.h src/Serialization.cpp include/core/SerializationKeys.h include/core/CoreAnnotations.h + include/core/BasicAnnotations.h + include/core/BasicTypes.h include/core/CoreFormatter.h include/core/ProjectSettings.h diff --git a/datamodel/libDataStorage/include/data_storage/BasicAnnotations.h b/datamodel/libCore/include/core/BasicAnnotations.h similarity index 80% rename from datamodel/libDataStorage/include/data_storage/BasicAnnotations.h rename to datamodel/libCore/include/core/BasicAnnotations.h index 48a12083..d4235901 100644 --- a/datamodel/libDataStorage/include/data_storage/BasicAnnotations.h +++ b/datamodel/libCore/include/core/BasicAnnotations.h @@ -9,13 +9,13 @@ */ #pragma once -#include "AnnotationBase.h" -#include "Value.h" +#include "data_storage/AnnotationBase.h" +#include "data_storage/Value.h" -namespace raco::data_storage { +namespace raco::core { template -class RangeAnnotation : public AnnotationBase { +class RangeAnnotation : public raco::data_storage::AnnotationBase { public: static const TypeDescriptor typeDescription; TypeDescriptor const& getTypeDescription() const override { @@ -46,10 +46,10 @@ class RangeAnnotation : public AnnotationBase { return *max_; } - Value min_, max_; + raco::data_storage::Value min_, max_; }; -class DisplayNameAnnotation : public AnnotationBase { +class DisplayNameAnnotation : public raco::data_storage::AnnotationBase { public: static inline const TypeDescriptor typeDescription = { "DisplayNameAnnotation", false }; TypeDescriptor const& getTypeDescription() const override { @@ -70,13 +70,13 @@ class DisplayNameAnnotation : public AnnotationBase { return *this; } - Value name_; + raco::data_storage::Value name_; }; -template<> inline const raco::data_storage::ReflectionInterface::TypeDescriptor raco::data_storage::RangeAnnotation::typeDescription { +template<> inline const raco::data_storage::ReflectionInterface::TypeDescriptor raco::core::RangeAnnotation::typeDescription { "RangeAnnotationDouble", false }; -template<> inline const raco::data_storage::ReflectionInterface::TypeDescriptor raco::data_storage::RangeAnnotation::typeDescription { +template<> inline const raco::data_storage::ReflectionInterface::TypeDescriptor raco::core::RangeAnnotation::typeDescription { "RangeAnnotationInt", false }; diff --git a/datamodel/libDataStorage/include/data_storage/BasicTypes.h b/datamodel/libCore/include/core/BasicTypes.h similarity index 89% rename from datamodel/libDataStorage/include/data_storage/BasicTypes.h rename to datamodel/libCore/include/core/BasicTypes.h index 29004304..997c0e6b 100644 --- a/datamodel/libDataStorage/include/data_storage/BasicTypes.h +++ b/datamodel/libCore/include/core/BasicTypes.h @@ -10,14 +10,18 @@ #pragma once #include "BasicAnnotations.h" -#include "ReflectionInterface.h" -#include "Value.h" +#include "data_storage/ReflectionInterface.h" +#include "data_storage/Value.h" #include -namespace raco::data_storage { +namespace raco::core { -class Vec2f : public ClassWithReflectedMembers { +using raco::data_storage::StructBase; +using raco::data_storage::ValueBase; +using raco::data_storage::Property; + +class Vec2f : public StructBase { public: static inline const TypeDescriptor typeDescription = {"Vec2f", false}; TypeDescriptor const& getTypeDescription() const override { @@ -27,10 +31,10 @@ class Vec2f : public ClassWithReflectedMembers { return true; } Vec2f(const Vec2f& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y) {} + : StructBase(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()), + : StructBase(getProperties()), x{defaultValue, DisplayNameAnnotation{"X"}, RangeAnnotation(min, max)}, y{defaultValue, DisplayNameAnnotation{"Y"}, RangeAnnotation(min, max)} {} @@ -69,7 +73,7 @@ class Vec2f : public ClassWithReflectedMembers { Property> y; }; -class Vec3f : public ClassWithReflectedMembers { +class Vec3f : public StructBase { public: static inline const TypeDescriptor typeDescription = {"Vec3f", false}; TypeDescriptor const& getTypeDescription() const override { @@ -79,10 +83,10 @@ class Vec3f : public ClassWithReflectedMembers { return true; } Vec3f(const Vec3f& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y), z(other.z) {} + : StructBase(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()), + : StructBase(getProperties()), x{defaultValue, DisplayNameAnnotation{"X"}, RangeAnnotation(min, max)}, y{defaultValue, DisplayNameAnnotation{"Y"}, RangeAnnotation(min, max)}, z{defaultValue, DisplayNameAnnotation{"Z"}, RangeAnnotation(min, max)} {} @@ -127,7 +131,7 @@ class Vec3f : public ClassWithReflectedMembers { Property> z; }; -class Vec4f : public ClassWithReflectedMembers { +class Vec4f : public StructBase { public: static inline const TypeDescriptor typeDescription = {"Vec4f", false}; TypeDescriptor const& getTypeDescription() const override { @@ -137,10 +141,10 @@ class Vec4f : public ClassWithReflectedMembers { return true; } Vec4f(const Vec4f& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), x(other.x), y(other.y), z(other.z), w(other.w) {} + : StructBase(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()), + : StructBase(getProperties()), x{defaultValue, DisplayNameAnnotation{"X"}, RangeAnnotation(min, max)}, y{defaultValue, DisplayNameAnnotation{"Y"}, RangeAnnotation(min, max)}, z{defaultValue, DisplayNameAnnotation{"Z"}, RangeAnnotation(min, max)}, @@ -190,7 +194,7 @@ class Vec4f : public ClassWithReflectedMembers { Property> w; }; -class Vec2i : public ClassWithReflectedMembers { +class Vec2i : public StructBase { public: static inline const TypeDescriptor typeDescription = {"Vec2i", false}; TypeDescriptor const& getTypeDescription() const override { @@ -200,15 +204,15 @@ class Vec2i : public ClassWithReflectedMembers { return true; } Vec2i(const Vec2i& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_) {} + : StructBase(getProperties()), i1_(other.i1_), i2_(other.i2_) {} Vec2i(int defaultValue = 0, int step = 1, int min = 0, int max = 1) - : ClassWithReflectedMembers(getProperties()), + : StructBase(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()), + : StructBase(getProperties()), i1_{values[0], DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, i2_{values[1], DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)} {} @@ -246,7 +250,7 @@ class Vec2i : public ClassWithReflectedMembers { Property> i2_; }; -class Vec3i : public ClassWithReflectedMembers { +class Vec3i : public StructBase { public: static inline const TypeDescriptor typeDescription = {"Vec3i", false}; TypeDescriptor const& getTypeDescription() const override { @@ -256,10 +260,10 @@ class Vec3i : public ClassWithReflectedMembers { return true; } Vec3i(const Vec3i& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_), i3_(other.i3_) {} + : StructBase(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()), + : StructBase(getProperties()), i1_{defaultValue, DisplayNameAnnotation{"i1"}, RangeAnnotation(min, max)}, i2_{defaultValue, DisplayNameAnnotation{"i2"}, RangeAnnotation(min, max)}, i3_{defaultValue, DisplayNameAnnotation{"i3"}, RangeAnnotation(min, max)} {} @@ -302,7 +306,7 @@ class Vec3i : public ClassWithReflectedMembers { Property> i3_; }; -class Vec4i : public ClassWithReflectedMembers { +class Vec4i : public StructBase { public: static inline const TypeDescriptor typeDescription = {"Vec4i", false}; TypeDescriptor const& getTypeDescription() const override { @@ -311,17 +315,17 @@ class Vec4i : public ClassWithReflectedMembers { bool serializationRequired() const override { return true; } - Vec4i(const Vec4i& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), i1_(other.i1_), i2_(other.i2_), i3_(other.i3_), i4_(other.i4_) {} + Vec4i(const Vec4i& other, std::function* translateRef = nullptr) : StructBase(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()), + : StructBase(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()), + : StructBase(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)}, diff --git a/datamodel/libCore/include/core/CommandInterface.h b/datamodel/libCore/include/core/CommandInterface.h index 81a04dba..04ea2948 100644 --- a/datamodel/libCore/include/core/CommandInterface.h +++ b/datamodel/libCore/include/core/CommandInterface.h @@ -43,6 +43,7 @@ class CommandInterface { // Basic property changes void set(ValueHandle const& handle, bool const& value); void set(ValueHandle const& handle, int const& value); + void set(ValueHandle const& handle, int64_t const& value); void set(ValueHandle const& handle, double const& value); void set(ValueHandle const& handle, std::string const& value); void set(ValueHandle const& handle, std::vector const& value); @@ -94,6 +95,14 @@ class CommandInterface { */ std::vector pasteObjects(const std::string& val, SEditorObject const& target = {}, bool pasteAsExtref = false, bool* outSuccess = nullptr, std::string* outError = nullptr); + /** + * Creates a duplicate of the #objects in the scene. + * The duplicated objects will essentially copy & paste the #objects on the same scenegraph hierarchy level. + * @param objects vector of objects to be duplicated. + * @return std::vector of all successfully duplicated objects + */ + std::vector duplicateObjects(const std::vector& objects); + // Link operations SLink addLink(const ValueHandle& start, const ValueHandle& end); void removeLink(const PropertyDescriptor& end); diff --git a/datamodel/libCore/include/core/Context.h b/datamodel/libCore/include/core/Context.h index b2ecb824..2d535dda 100644 --- a/datamodel/libCore/include/core/Context.h +++ b/datamodel/libCore/include/core/Context.h @@ -61,6 +61,7 @@ class BaseContext { // Basic property changes void set(ValueHandle const& handle, bool const& value); void set(ValueHandle const& handle, int const& value); + void set(ValueHandle const& handle, int64_t const& value); void set(ValueHandle const& handle, double const& value); void set(ValueHandle const& handle, std::string const& value); void set(ValueHandle const& handle, char const* value); // avoid cast of char const* to bool @@ -75,7 +76,7 @@ class BaseContext { 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); + void set(ValueHandle const& handle, StructBase const& value); template void set(AnnotationValueHandle const& handle, T const& value); diff --git a/datamodel/libCore/include/core/CoreFormatter.h b/datamodel/libCore/include/core/CoreFormatter.h index 407e1968..4de6d2d6 100644 --- a/datamodel/libCore/include/core/CoreFormatter.h +++ b/datamodel/libCore/include/core/CoreFormatter.h @@ -32,6 +32,9 @@ struct fmt::formatter : formatter : formatter::format(name, ctx); @@ -73,6 +61,7 @@ struct fmt::formatter : formatter { {raco::core::EnginePrimitive::Undefined, "Undefined"}, {raco::core::EnginePrimitive::Bool, "Bool"}, {raco::core::EnginePrimitive::Int32, "Int"}, + {raco::core::EnginePrimitive::Int64, "Int"}, {raco::core::EnginePrimitive::UInt16, "Int"}, {raco::core::EnginePrimitive::UInt32, "Int"}, {raco::core::EnginePrimitive::Double, "Float"}, diff --git a/datamodel/libCore/include/core/DynamicEditorObject.h b/datamodel/libCore/include/core/DynamicEditorObject.h index a1674c50..6eaf14f3 100644 --- a/datamodel/libCore/include/core/DynamicEditorObject.h +++ b/datamodel/libCore/include/core/DynamicEditorObject.h @@ -27,6 +27,7 @@ class DynamicPropertyInterface { virtual ValueBase* addProperty(std::string const& name, std::unique_ptr&& property, int index_before) = 0; virtual void removeProperty(std::string const& propertyName) = 0; + virtual void removeAllProperties() = 0; virtual std::unique_ptr extractProperty(std::string const& propertyName) = 0; }; @@ -70,6 +71,12 @@ class DynamicPropertyMixin : public DynamicPropertyInterface { } } + void removeAllProperties() override { + T& asT = static_cast(*this); + dynamicProperties_.clear(); + asT.properties_.clear(); + } + std::unique_ptr extractProperty(std::string const& propertyName) override { T& asT = static_cast(*this); auto clonedProp = asT.get(propertyName)->clone({}); @@ -92,9 +99,9 @@ class DynamicEditorObject : public raco::core::EditorObject, public DynamicPrope using SDynamicEditorObject = std::shared_ptr; -class DynamicGenericStruct : public raco::data_storage::ClassWithReflectedMembers, public DynamicPropertyMixin { +class DynamicGenericStruct : public raco::data_storage::StructBase, public DynamicPropertyMixin { public: - DynamicGenericStruct() : ClassWithReflectedMembers() {} + DynamicGenericStruct() : StructBase() {} friend class DynamicPropertyMixin; }; diff --git a/datamodel/libCore/include/core/EditorObject.h b/datamodel/libCore/include/core/EditorObject.h index ef5fbf03..d0f550c7 100644 --- a/datamodel/libCore/include/core/EditorObject.h +++ b/datamodel/libCore/include/core/EditorObject.h @@ -12,7 +12,7 @@ #include "data_storage/ReflectionInterface.h" #include "data_storage/Value.h" #include "data_storage/Table.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "core/CoreAnnotations.h" #include "core/FileChangeMonitor.h" diff --git a/datamodel/libCore/include/core/EngineInterface.h b/datamodel/libCore/include/core/EngineInterface.h index cf0e0d77..08d870dc 100644 --- a/datamodel/libCore/include/core/EngineInterface.h +++ b/datamodel/libCore/include/core/EngineInterface.h @@ -9,7 +9,7 @@ */ #pragma once -#include "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include #include #include @@ -51,7 +51,9 @@ enum class EnginePrimitive { Array, TextureSampler2D, TextureSampler3D, - TextureSamplerCube + TextureSamplerCube, + // Types added later, in the bottom of the enum to avoid file format changing + Int64 }; struct PropertyInterface; @@ -67,16 +69,17 @@ struct PropertyInterface { static std::map typeMap = { {EnginePrimitive::Bool, data_storage::PrimitiveType::Bool}, {EnginePrimitive::Int32, data_storage::PrimitiveType::Int}, + {EnginePrimitive::Int64, data_storage::PrimitiveType::Int64}, {EnginePrimitive::UInt16, data_storage::PrimitiveType::Int}, {EnginePrimitive::UInt32, data_storage::PrimitiveType::Int}, {EnginePrimitive::Double, data_storage::PrimitiveType::Double}, {EnginePrimitive::String, data_storage::PrimitiveType::String}, - {EnginePrimitive::Vec2f, data_storage::PrimitiveType::Vec2f}, - {EnginePrimitive::Vec3f, data_storage::PrimitiveType::Vec3f}, - {EnginePrimitive::Vec4f, data_storage::PrimitiveType::Vec4f}, - {EnginePrimitive::Vec2i, data_storage::PrimitiveType::Vec2i}, - {EnginePrimitive::Vec3i, data_storage::PrimitiveType::Vec3i}, - {EnginePrimitive::Vec4i, data_storage::PrimitiveType::Vec4i}, + {EnginePrimitive::Vec2f, data_storage::PrimitiveType::Struct}, + {EnginePrimitive::Vec3f, data_storage::PrimitiveType::Struct}, + {EnginePrimitive::Vec4f, data_storage::PrimitiveType::Struct}, + {EnginePrimitive::Vec2i, data_storage::PrimitiveType::Struct}, + {EnginePrimitive::Vec3i, data_storage::PrimitiveType::Struct}, + {EnginePrimitive::Vec4i, data_storage::PrimitiveType::Struct}, {EnginePrimitive::Array, data_storage::PrimitiveType::Table}, {EnginePrimitive::Struct, data_storage::PrimitiveType::Table}, {EnginePrimitive::TextureSampler2D, data_storage::PrimitiveType::Ref}, @@ -101,6 +104,8 @@ class EngineInterface { virtual bool parseLuaScriptModule(const std::string& luaScriptModule, std::string& outError) = 0; virtual bool extractLuaDependencies(const std::string& luaScript, std::vector& moduleList, std::string &outError) = 0; virtual const std::map& enumerationDescription(EngineEnumeration type) const = 0; + + virtual std::string luaNameForPrimitiveType(EnginePrimitive engineType) const = 0; }; } // namespace raco::core diff --git a/datamodel/libCore/include/core/Handles.h b/datamodel/libCore/include/core/Handles.h index 4abf554a..8abfe9f5 100644 --- a/datamodel/libCore/include/core/Handles.h +++ b/datamodel/libCore/include/core/Handles.h @@ -10,7 +10,7 @@ #pragma once #include "data_storage/Value.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include "core/PropertyDescriptor.h" #include @@ -122,11 +122,12 @@ class ValueHandle { // Extract scalar values directly - // Templated accessor; only works for scalar types: bool, int, double, std::string + // Templated accessor; only works for scalar types: bool, int, int64_t, double, std::string template T as() const; bool asBool() const; int asInt() const; + int64_t asInt64() const; double asDouble() const; std::string asString() const; SEditorObject asRef() const; @@ -137,6 +138,8 @@ class ValueHandle { const Vec3i& asVec3i() const; const Vec4i& asVec4i() const; + bool isVec3f() const; + bool isVec4f() const; template std::shared_ptr asTypedRef() const { diff --git a/datamodel/libCore/include/core/Link.h b/datamodel/libCore/include/core/Link.h index e938bebd..fe571781 100644 --- a/datamodel/libCore/include/core/Link.h +++ b/datamodel/libCore/include/core/Link.h @@ -13,7 +13,7 @@ #include "data_storage/ReflectionInterface.h" #include "data_storage/Table.h" #include "data_storage/Value.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "core/Handles.h" #include "core/PropertyDescriptor.h" diff --git a/datamodel/libCore/include/core/PrefabOperations.h b/datamodel/libCore/include/core/PrefabOperations.h index 332df48e..91f467a9 100644 --- a/datamodel/libCore/include/core/PrefabOperations.h +++ b/datamodel/libCore/include/core/PrefabOperations.h @@ -32,6 +32,7 @@ class PrefabOperations { static void globalPrefabUpdate(BaseContext& context, DataChangeRecorder& changes, bool propagateMissingInterfaceProperties = false); static raco::user_types::SPrefabInstance findContainingPrefabInstance(SEditorObject object); + static raco::user_types::SPrefabInstance findOuterContainingPrefabInstance(SEditorObject object); static raco::user_types::SPrefab findContainingPrefab(SEditorObject object); diff --git a/datamodel/libCore/include/core/ProjectMigration.h b/datamodel/libCore/include/core/ProjectMigration.h index 169585f1..e0bdfcb1 100644 --- a/datamodel/libCore/include/core/ProjectMigration.h +++ b/datamodel/libCore/include/core/ProjectMigration.h @@ -52,8 +52,15 @@ namespace raco::serialization { * 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 + * 25: Added mipmap flag to cubemaps + * 26: Added support for ramses-logic INT64 type + * 27: Removed vector PrimitiveTypes and made them normal structs. + * Vector types are now included in the serialized struct type map. + * 28: Added zipability functionality to projects. + * Added "Save As Zipped File" option to Project Settings. + * 29: CubeMap: added custom mipmap functionality (+ 18 URIs, 1 bool, 1 int property) */ -constexpr int RAMSES_PROJECT_FILE_VERSION = 24; +constexpr int RAMSES_PROJECT_FILE_VERSION = 29; void migrateProject(ProjectDeserializationInfoIR& deserializedIR); diff --git a/datamodel/libCore/include/core/ProjectSettings.h b/datamodel/libCore/include/core/ProjectSettings.h index 3be52631..27a89b05 100644 --- a/datamodel/libCore/include/core/ProjectSettings.h +++ b/datamodel/libCore/include/core/ProjectSettings.h @@ -10,13 +10,13 @@ #pragma once #include "core/EditorObject.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" namespace raco::core { class ProjectSettings : public EditorObject { public: - class DefaultResourceDirectories : public ClassWithReflectedMembers { + class DefaultResourceDirectories : public StructBase { public: static inline const TypeDescriptor typeDescription = {"DefaultResourceDirectories", false}; @@ -28,7 +28,7 @@ class ProjectSettings : public EditorObject { return true; } - DefaultResourceDirectories(const DefaultResourceDirectories& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(), + DefaultResourceDirectories(const DefaultResourceDirectories& other, std::function* translateRef = nullptr) : StructBase(), imageSubdirectory_(other.imageSubdirectory_), meshSubdirectory_(other.meshSubdirectory_), scriptSubdirectory_(other.scriptSubdirectory_), @@ -36,7 +36,7 @@ class ProjectSettings : public EditorObject { fillPropertyDescription(); } - DefaultResourceDirectories() : ClassWithReflectedMembers() { + DefaultResourceDirectories() : StructBase() { fillPropertyDescription(); } @@ -74,13 +74,19 @@ class ProjectSettings : public EditorObject { return typeDescription; } - ProjectSettings(ProjectSettings const& other) : EditorObject(other), sceneId_(other.sceneId_), viewport_(other.viewport_), backgroundColor_(other.backgroundColor_), runTimer_(other.runTimer_), enableTimerFlag_(other.enableTimerFlag_) { + ProjectSettings(ProjectSettings const& other) : EditorObject(other), + sceneId_(other.sceneId_), + viewport_(other.viewport_), + backgroundColor_(other.backgroundColor_), + runTimer_(other.runTimer_), + enableTimerFlag_(other.enableTimerFlag_), + saveAsZip_(other.saveAsZip_) { fillPropertyDescription(); } ProjectSettings(const std::string& name, const std::string& id = std::string()) : EditorObject(name, id) { fillPropertyDescription(); - backgroundColor_.asVec4f().w = 1.0; + backgroundColor_->w = 1.0; } ProjectSettings() : ProjectSettings("Main") {} @@ -92,11 +98,13 @@ class ProjectSettings : public EditorObject { properties_.emplace_back("defaultResourceFolders", &defaultResourceDirectories_); properties_.emplace_back("enableTimerFlag", &enableTimerFlag_); properties_.emplace_back("runTimer", &runTimer_); + properties_.emplace_back("saveAsZip", &saveAsZip_); } Property> sceneId_{123u, DisplayNameAnnotation("Scene Id"), {1, 1024}}; Property viewport_{{{1440, 720}, 0, 4096}, {"Display Size"}}; Property backgroundColor_{{}, {"Display Background Color"}}; + Property saveAsZip_{false, {"Save As Zipped File"}}; Property defaultResourceDirectories_{{}, {"Default Resource Folders"}}; diff --git a/datamodel/libCore/include/core/ProxyObjectFactory.h b/datamodel/libCore/include/core/ProxyObjectFactory.h index 915b461d..ad8e9890 100644 --- a/datamodel/libCore/include/core/ProxyObjectFactory.h +++ b/datamodel/libCore/include/core/ProxyObjectFactory.h @@ -19,6 +19,9 @@ namespace raco::serialization::proxy { using namespace raco::data_storage; +using raco::core::DisplayNameAnnotation; +using raco::core::RangeAnnotation; + using raco::user_types::EngineTypeAnnotation; using raco::core::LinkStartAnnotation; using raco::core::LinkEndAnnotation; @@ -70,6 +73,7 @@ class ProxyObjectFactory : public raco::core::UserObjectFactoryInterface { Property, Property, + Property, Property, Property, Property, @@ -86,6 +90,7 @@ class ProxyObjectFactory : public raco::core::UserObjectFactoryInterface { Property, Property, + Property, Property, Property, Property, @@ -102,6 +107,7 @@ class ProxyObjectFactory : public raco::core::UserObjectFactoryInterface { Property, Property, + Property, Property, Property, Property, diff --git a/datamodel/libCore/include/core/ProxyTypes.h b/datamodel/libCore/include/core/ProxyTypes.h index 86e98fdf..63681962 100644 --- a/datamodel/libCore/include/core/ProxyTypes.h +++ b/datamodel/libCore/include/core/ProxyTypes.h @@ -130,11 +130,16 @@ class StructProxy : public DynamicGenericStruct { StructProxy() : DynamicGenericStruct() {} StructProxy(const StructProxy& other, std::function* translateRef = nullptr) { - + for (size_t i = 0; i < other.dynamicProperties_.size(); i++) { + addProperty(other.name(i), other.get(i)->clone(translateRef), -1); + } } StructProxy& operator=(const StructProxy& other) { - dynamicProperties_ = other.dynamicProperties_; + removeAllProperties(); + for (size_t i = 0; i < other.dynamicProperties_.size(); i++) { + addProperty(other.name(i), other.get(i)->clone(nullptr), -1); + } return *this; } @@ -145,6 +150,20 @@ class StructProxy : public DynamicGenericStruct { } }; +extern const char vec2fTypeName[]; +using Vec2f = StructProxy; +extern const char vec3fTypeName[]; +using Vec3f = StructProxy; +extern const char vec4fTypeName[]; +using Vec4f = StructProxy; + +extern const char vec2iTypeName[]; +using Vec2i = StructProxy; +extern const char vec3iTypeName[]; +using Vec3i = StructProxy; +extern const char vec4iTypeName[]; +using Vec4i = StructProxy; + extern const char blendmodeOptionsTypeName[]; using BlendOptions = StructProxy; diff --git a/datamodel/libCore/include/core/Queries.h b/datamodel/libCore/include/core/Queries.h index 3d0fe20f..024e4788 100644 --- a/datamodel/libCore/include/core/Queries.h +++ b/datamodel/libCore/include/core/Queries.h @@ -41,6 +41,7 @@ namespace Queries { bool canPasteIntoObject(Project const& project, SEditorObject const& object); bool canPasteObjectAsExternalReference(const SEditorObject& editorObject, bool wasTopLevelObjectInSourceProject); bool canDeleteUnreferencedResources(const Project& project); + bool canDuplicateObjects(const std::vector& objects, const Project& project); SEditorObjectSet collectAllChildren(std::vector baseObjects); diff --git a/datamodel/libCore/include/core/Serialization.h b/datamodel/libCore/include/core/Serialization.h index 3ab56a57..9122fdd9 100644 --- a/datamodel/libCore/include/core/Serialization.h +++ b/datamodel/libCore/include/core/Serialization.h @@ -10,8 +10,8 @@ #pragma once #include "core/CoreAnnotations.h" -#include "data_storage/BasicAnnotations.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicAnnotations.h" +#include "core/BasicTypes.h" #include "data_storage/Value.h" #include @@ -127,6 +127,8 @@ ObjectsDeserialization deserializeObjects(const std::string& json); ProjectDeserializationInfo deserializeProject(const QJsonDocument& jsonDocument, const std::string& filename); std::map> makeUserTypePropertyMap(); +std::map> makeStructPropertyMap(); +std::map> deserializeUserTypePropertyMap(const QVariant& container); ProjectDeserializationInfoIR deserializeProjectToIR(const QJsonDocument& document, const std::string& filename); diff --git a/datamodel/libCore/src/CommandInterface.cpp b/datamodel/libCore/src/CommandInterface.cpp index eb2461fe..d336263a 100644 --- a/datamodel/libCore/src/CommandInterface.cpp +++ b/datamodel/libCore/src/CommandInterface.cpp @@ -68,6 +68,15 @@ void CommandInterface::set(ValueHandle const& handle, int const& value) { } } +void CommandInterface::set(ValueHandle const& handle, int64_t const& value) { + if (handle && handle.asInt64() != value) { + context_->set(handle, value); + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Set property '{}' to {}", handle.getPropertyPath(), value), + fmt::format("{}", handle.getPropertyPath(true))); + } +} + void CommandInterface::set(ValueHandle const& handle, double const& value) { if (handle && handle.asDouble() != value) { context_->set(handle, value); @@ -263,6 +272,27 @@ std::vector CommandInterface::pasteObjects(const std::string& val return result; } +std::vector CommandInterface::duplicateObjects(const std::vector& objects) { + if (!Queries::canDuplicateObjects(objects, *project())) { + return {}; + } + + std::vector duplicatedObjs; + for (const auto& obj : objects) { + auto target = obj->getParent(); + auto serializedObj = context_->copyObjects({obj}, false); + auto duplicatedObj = context_->pasteObjects(serializedObj, target).front(); + duplicatedObjs.emplace_back(duplicatedObj); + } + + if (!duplicatedObjs.empty()) { + PrefabOperations::globalPrefabUpdate(*context_, context_->modelChanges()); + undoStack_->push(fmt::format("Duplicate {} object{}", duplicatedObjs.size(), duplicatedObjs.size() > 1 ? "s" : "")); + } + + return duplicatedObjs; +} + SLink CommandInterface::addLink(const ValueHandle& start, const ValueHandle& end) { if (Queries::userCanCreateLink(*context_->project(), start, end)) { auto link = context_->addLink(start, end); diff --git a/datamodel/libCore/src/Context.cpp b/datamodel/libCore/src/Context.cpp index edd60e33..537e73b9 100644 --- a/datamodel/libCore/src/Context.cpp +++ b/datamodel/libCore/src/Context.cpp @@ -32,6 +32,7 @@ #include "user_types/MeshNode.h" #include "user_types/Node.h" #include "user_types/Prefab.h" +#include "user_types/PrefabInstance.h" #include #include @@ -179,7 +180,7 @@ void BaseContext::setT(ValueHandle const& handle, Table const& value) { } template <> -void BaseContext::setT(ValueHandle const& handle, ClassWithReflectedMembers const& value) { +void BaseContext::setT(ValueHandle const& handle, StructBase const& value) { ValueBase* v = handle.valueRef(); callReferenceToThisHandlerForAllTableEntries<&EditorObject::onBeforeRemoveReferenceToThis>(handle); @@ -203,6 +204,10 @@ void BaseContext::set(ValueHandle const& handle, int const& value) { setT(handle, value); } +void BaseContext::set(ValueHandle const& handle, int64_t const& value) { + setT(handle, value); +} + void BaseContext::set(ValueHandle const& handle, double const& value) { setT(handle, value); } @@ -228,30 +233,42 @@ void BaseContext::set(ValueHandle const& handle, Table const& value) { } void BaseContext::set(ValueHandle const& handle, std::array const& value) { - setT(handle, value); + Vec2f vecValue; + vecValue = value; + setT(handle, static_cast(vecValue)); } void BaseContext::set(ValueHandle const& handle, std::array const& value) { - setT(handle, value); + Vec3f vecValue; + vecValue = value; + setT(handle, static_cast(vecValue)); } void BaseContext::set(ValueHandle const& handle, std::array const& value) { - setT(handle, value); + Vec4f vecValue; + vecValue = value; + setT(handle, static_cast(vecValue)); } void BaseContext::set(ValueHandle const& handle, std::array const& value) { - setT(handle, value); + Vec2i vecValue; + vecValue = value; + setT(handle, static_cast(vecValue)); } void BaseContext::set(ValueHandle const& handle, std::array const& value) { - setT(handle, value); + Vec3i vecValue; + vecValue = value; + setT(handle, static_cast(vecValue)); } void BaseContext::set(ValueHandle const& handle, std::array const& value) { - setT(handle, value); + Vec4i vecValue; + vecValue = value; + setT(handle, static_cast(vecValue)); } -void BaseContext::set(ValueHandle const& handle, ClassWithReflectedMembers const& value) { +void BaseContext::set(ValueHandle const& handle, StructBase const& value) { setT(handle, value); } @@ -601,14 +618,32 @@ std::vector BaseContext::pasteObjects(const std::string& seralize rerootRelativePaths(newObjects, deserialization); } - // Generate new ids, reroot relative paths and call appropriate context functions for object creation + // Generate new ids: + // Pass 1: everything except PrefabInstance children + std::map prefabInstanceIDMap; for (auto& editorObject : newObjects) { - // change object id and reroot uris of non-extref objects if (!editorObject->query()) { - auto newId{EditorObject::normalizedObjectID(std::string())}; - editorObject->setObjectID(newId); + if (!PrefabOperations::findOuterContainingPrefabInstance(editorObject->getParent())) { + auto newId{EditorObject::normalizedObjectID(std::string())}; + prefabInstanceIDMap[newId] = editorObject->objectID(); + editorObject->setObjectID(newId); + } + } + } + // Pass 2: PrefabInstance children + // these are calculated from the containing PrefabInstance object id and need to know the old and new PrefabInstance ID + for (auto& editorObject : newObjects) { + if (!editorObject->query()) { + if (auto inst = PrefabOperations::findOuterContainingPrefabInstance(editorObject->getParent())) { + auto newInstID = inst->objectID(); + auto oldInstID = prefabInstanceIDMap[newInstID]; + auto newID = EditorObject::XorObjectIDs(EditorObject::XorObjectIDs(editorObject->objectID(), oldInstID), newInstID); + editorObject->setObjectID(newID); + } } + } + for (auto& editorObject : newObjects) { project_->addInstance(editorObject); changeMultiplexer_.recordCreateObject(editorObject); } diff --git a/datamodel/libCore/src/ExtrefOperations.cpp b/datamodel/libCore/src/ExtrefOperations.cpp index c7ab5699..b915c114 100644 --- a/datamodel/libCore/src/ExtrefOperations.cpp +++ b/datamodel/libCore/src/ExtrefOperations.cpp @@ -208,7 +208,8 @@ void ExtrefOperations::updateExternalObjects(BaseContext& context, Project* proj // Remove links - std::map> localLinks = Queries::getLinksConnectedToObjects(*project, localObjects, true, true); + // Only remove links ending on extref objects to avoid removing links starting on extref but ending on local objects. + std::map> localLinks = Queries::getLinksConnectedToObjects(*project, localObjects, false, true); std::map> externalLinks; for (const auto& item : externalObjects) { diff --git a/datamodel/libCore/src/Handles.cpp b/datamodel/libCore/src/Handles.cpp index ca54e076..523216b7 100644 --- a/datamodel/libCore/src/Handles.cpp +++ b/datamodel/libCore/src/Handles.cpp @@ -63,6 +63,11 @@ int ValueHandle::as() const { return asInt(); } +template <> +int64_t ValueHandle::as() const { + return asInt64(); +} + template<> float ValueHandle::as() const { return static_cast(asDouble()); @@ -88,6 +93,11 @@ int ValueHandle::asInt() const { return v->asInt(); } +int64_t ValueHandle::asInt64() const { + ValueBase* v = valueRef(); + return v->asInt64(); +} + double ValueHandle::asDouble() const { ValueBase* v = valueRef(); return v->asDouble(); @@ -105,32 +115,48 @@ SEditorObject ValueHandle::asRef() const { const Vec2f& ValueHandle::asVec2f() const { ValueBase* v = valueRef(); - return v->asVec2f(); + return dynamic_cast(v->asStruct()); } const Vec3f& ValueHandle::asVec3f() const { ValueBase* v = valueRef(); - return v->asVec3f(); + return dynamic_cast(v->asStruct()); } const Vec4f& ValueHandle::asVec4f() const { ValueBase* v = valueRef(); - return v->asVec4f(); + return dynamic_cast(v->asStruct()); } const Vec2i& ValueHandle::asVec2i() const { ValueBase* v = valueRef(); - return v->asVec2i(); + return dynamic_cast(v->asStruct()); } const Vec3i& ValueHandle::asVec3i() const { ValueBase* v = valueRef(); - return v->asVec3i(); + return dynamic_cast(v->asStruct()); } const Vec4i& ValueHandle::asVec4i() const { ValueBase* v = valueRef(); - return v->asVec4i(); + return dynamic_cast(v->asStruct()); +} + +bool ValueHandle::isVec3f() const { + ValueBase* v = valueRef(); + if (v->type() == PrimitiveType::Struct) { + return &v->asStruct().getTypeDescription() == &Vec3f::typeDescription; + } + return false; +} + +bool ValueHandle::isVec4f() const { + ValueBase* v = valueRef(); + if (v->type() == PrimitiveType::Struct) { + return &v->asStruct().getTypeDescription() == &Vec4f::typeDescription; + } + return false; } size_t ValueHandle::size() const { diff --git a/datamodel/libCore/src/PrefabOperations.cpp b/datamodel/libCore/src/PrefabOperations.cpp index f8816c15..e33db7ca 100644 --- a/datamodel/libCore/src/PrefabOperations.cpp +++ b/datamodel/libCore/src/PrefabOperations.cpp @@ -52,6 +52,16 @@ SPrefabInstance PrefabOperations::findContainingPrefabInstance(SEditorObject obj return nullptr; } +SPrefabInstance PrefabOperations::findOuterContainingPrefabInstance(SEditorObject object) { + auto currentInst = PrefabOperations::findContainingPrefabInstance(object); + if (currentInst) { + while (auto parentInst = PrefabOperations::findContainingPrefabInstance(currentInst->getParent())) { + currentInst = parentInst; + } + } + return currentInst; +} + namespace { SLink lookupLink(SLink srcLink, const std::map>& destLinks, translateRefFunc translateRef) { diff --git a/datamodel/libCore/src/Project.cpp b/datamodel/libCore/src/Project.cpp index b29e5db3..5a4495ea 100644 --- a/datamodel/libCore/src/Project.cpp +++ b/datamodel/libCore/src/Project.cpp @@ -30,6 +30,7 @@ bool Project::removeInstances(SEditorObjectSet const& objects, bool gcExternalPr } void Project::addInstance(SEditorObject object) { + assert(instanceMap_.find(object->objectID()) == instanceMap_.end()); instances_.push_back(object); instanceMap_[object->objectID()] = object; } diff --git a/datamodel/libCore/src/ProjectMigration.cpp b/datamodel/libCore/src/ProjectMigration.cpp index 3952effd..dc3c8778 100644 --- a/datamodel/libCore/src/ProjectMigration.cpp +++ b/datamodel/libCore/src/ProjectMigration.cpp @@ -140,10 +140,10 @@ void migrateProject(ProjectDeserializationInfoIR& deserializedIR) { 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->addProperty("viewPortOffsetX", new data_storage::Property, DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asStruct().get("i1")->asInt(), {-7680, 7680}, {"Viewport Offset X"}, {}}, -1); + dynObj->addProperty("viewPortOffsetY", new data_storage::Property, DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asStruct().get("i2")->asInt(), {-7680, 7680}, {"Viewport Offset Y"}, {}}, -1); + dynObj->addProperty("viewPortWidth", new data_storage::Property, DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asStruct().get("i3")->asInt(), {0, 7680}, {"Viewport Width"}, {}}, -1); + dynObj->addProperty("viewPortHeight", new data_storage::Property, DisplayNameAnnotation, core::LinkEndAnnotation>{oldviewportprop.asStruct().get("i4")->asInt(), {0, 7680}, {"Viewport Height"}, {}}, -1); dynObj->removeProperty("viewport"); } } @@ -422,14 +422,14 @@ void migrateProject(ProjectDeserializationInfoIR& deserializedIR) { continue; } - auto& scaleVec = dynObj->get("scale")->asVec3f(); - auto& rotationVec = dynObj->get("rotation")->asVec3f(); + auto& scaleVec = dynObj->get("scale")->asStruct(); + auto& rotationVec = dynObj->get("rotation")->asStruct(); 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 rotationNotZero = rotationVec.get("x")->asDouble() > EPSILON || rotationVec.get("y")->asDouble() > EPSILON || rotationVec.get("z")->asDouble() > EPSILON; + auto scaleNotUniform = std::abs(scaleVec.get("x")->asDouble() - scaleVec.get("y")->asDouble()) > EPSILON || std::abs(scaleVec.get("x")->asDouble() - scaleVec.get("z")->asDouble()) > EPSILON; auto rotationLinked = objectsWithAffectedProperties[objID][0]; auto scaleLinked = objectsWithAffectedProperties[objID][1]; @@ -479,13 +479,19 @@ void migrateProject(ProjectDeserializationInfoIR& deserializedIR) { 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); + auto& bgColor3Vec = bgColor3->asStruct(); + auto bgColor4Vec = new Property{{}, {"Display Background Color"}}; + if (bgColor3Vec.hasProperty("x")) { + (*bgColor4Vec)->addProperty("x", bgColor3Vec.get("x")->clone(nullptr), -1); + } + if (bgColor3Vec.hasProperty("y")) { + (*bgColor4Vec)->addProperty("y", bgColor3Vec.get("y")->clone(nullptr), -1); + } + if (bgColor3Vec.hasProperty("z")) { + (*bgColor4Vec)->addProperty("z", bgColor3Vec.get("z")->clone(nullptr), -1); + } + (*bgColor4Vec)->addProperty("w", new Property>{{1.0}, DisplayNameAnnotation{"W"}, RangeAnnotation(0.0, 1.0)}, -1); + dynObj->addProperty("backgroundColor", bgColor4Vec, -1); } } } diff --git a/datamodel/libCore/src/ProjectMigrationToV23.cpp b/datamodel/libCore/src/ProjectMigrationToV23.cpp index 71aadcec..48a248eb 100644 --- a/datamodel/libCore/src/ProjectMigrationToV23.cpp +++ b/datamodel/libCore/src/ProjectMigrationToV23.cpp @@ -1906,6 +1906,39 @@ std::string userTypePropMap_V9 = } })___"; +std::string vectorTypesStructMap = R"___({ + "Vec2f": { + "x": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "y": "Double::DisplayNameAnnotation::RangeAnnotationDouble" + }, + "Vec2i": { + "i1": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i2": "Int::DisplayNameAnnotation::RangeAnnotationInt" + }, + "Vec3f": { + "x": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "y": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "z": "Double::DisplayNameAnnotation::RangeAnnotationDouble" + }, + "Vec3i": { + "i1": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i2": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i3": "Int::DisplayNameAnnotation::RangeAnnotationInt" + }, + "Vec4f": { + "w": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "x": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "y": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "z": "Double::DisplayNameAnnotation::RangeAnnotationDouble" + }, + "Vec4i": { + "i1": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i2": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i3": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i4": "Int::DisplayNameAnnotation::RangeAnnotationInt" + } + })___"; + } // namespace @@ -1959,6 +1992,18 @@ QJsonDocument migrateProjectToV23(const QJsonDocument& document) { } } + // Merge struct type maps for vector types into the struct types from the deserialized document: + if (documentVersion < 27) { + auto structMap = documentObject[keys::STRUCT_PROP_MAP].toVariant().toMap(); + + auto vecStructMap = QJsonDocument::fromJson(vectorTypesStructMap.c_str()).toVariant().toMap(); + for (auto [name, map] : vecStructMap.toStdMap()) { + structMap[name] = map; + } + + documentObject.insert(keys::STRUCT_PROP_MAP, QJsonValue::fromVariant(structMap)); + } + QJsonDocument newDocument{documentObject}; // for debugging: //auto migratedJSON = QString(newDocument.toJson()).toStdString(); diff --git a/datamodel/libCore/src/ProxyTypes.cpp b/datamodel/libCore/src/ProxyTypes.cpp index 96d71348..1d90d8a4 100644 --- a/datamodel/libCore/src/ProxyTypes.cpp +++ b/datamodel/libCore/src/ProxyTypes.cpp @@ -33,6 +33,12 @@ const char renderTargetTypeName[] = "RenderTarget"; const char prefabTypeName[] = "Prefab"; const char prefabInstanceTypeName[] = "PrefabInstance"; +const char vec2fTypeName[] = "Vec2f"; +const char vec3fTypeName[] = "Vec3f"; +const char vec4fTypeName[] = "Vec4f"; +const char vec2iTypeName[] = "Vec2i"; +const char vec3iTypeName[] = "Vec3i"; +const char vec4iTypeName[] = "Vec4i"; const char blendmodeOptionsTypeName[] = "BlendOptions"; const char cameraViewportTypeName[] = "CameraViewport"; const char perspectiveFrustumTypeName[] = "PerspectiveFrustum"; diff --git a/datamodel/libCore/src/Queries.cpp b/datamodel/libCore/src/Queries.cpp index 54febd75..f680cde0 100644 --- a/datamodel/libCore/src/Queries.cpp +++ b/datamodel/libCore/src/Queries.cpp @@ -148,6 +148,22 @@ bool Queries::canDeleteUnreferencedResources(const Project& project) { return !toRemove.empty(); } +bool Queries::canDuplicateObjects(const std::vector& objects, const Project& project) { + if (objects.empty()) { + return false; + } + + auto parentForAllObjs = objects.front()->getParent(); + + for (const auto& obj : objects) { + if (obj->query() || !Queries::canPasteIntoObject(project, obj->getParent()) || obj->getParent() != parentForAllObjs) { + return false; + } + } + + return true; +} + std::vector Queries::findAllValidReferenceTargets(Project const& project, const ValueHandle& handle) { std::vector valid; for (auto obj : project.instances()) { @@ -643,6 +659,12 @@ bool sameStructure(const ReflectionInterface* left, const ReflectionInterface* r } bool checkLinkCompatibleTypes(const ValueHandle& start, const ValueHandle& end) { + // Node rotations can be linked as euler or quaternion values + if (end.rootObject()->as() && end.isRefToProp(&user_types::Node::rotation_) && + start.isVec4f() && end.isVec3f()) { + return true; + } + auto startType = start.type(); auto endType = end.type(); if ((startType == PrimitiveType::Table || startType == PrimitiveType::Struct) && @@ -650,12 +672,6 @@ bool checkLinkCompatibleTypes(const ValueHandle& start, const ValueHandle& end) return sameStructure(&start.constValueRef()->getSubstructure(), &end.constValueRef()->getSubstructure()); } - // Node rotations can be linked as euler or quaternion values - if (end.rootObject()->as() && end.isRefToProp(&user_types::Node::rotation_)) { - if (startType == PrimitiveType::Vec4f && endType == PrimitiveType::Vec3f) { - return true; - } - } return startType == endType; } diff --git a/datamodel/libCore/src/Serialization.cpp b/datamodel/libCore/src/Serialization.cpp index a10b9197..17b28d37 100644 --- a/datamodel/libCore/src/Serialization.cpp +++ b/datamodel/libCore/src/Serialization.cpp @@ -66,6 +66,8 @@ QJsonValue serializePrimitiveValue(const ValueBase& value) { return QJsonValue{value.asDouble()}; case PrimitiveType::Int: return QJsonValue{value.asInt()}; + case PrimitiveType::Int64: + return QJsonValue{QString::number(value.asInt64())}; case PrimitiveType::String: { assert(value.asString() == QString::fromStdString(value.asString()).toStdString()); assert(value.asString() == QJsonValue{QString::fromStdString(value.asString())}.toString().toStdString()); @@ -94,6 +96,10 @@ void deserializePrimitiveValue(const QJsonValue& jsonValue, ValueBase& value, Re case PrimitiveType::Int: value = jsonValue.toInt(); break; + case PrimitiveType::Int64: + // This cast seems to be necessary for the linux compiler to be able to decide which assignment operator to use + value = (int64_t)jsonValue.toString().toLongLong(); + break; case PrimitiveType::String: value = jsonValue.toString().toStdString(); break; @@ -477,25 +483,6 @@ 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; - } - - return structPropTypesMap; -} QMap serializeUserTypePropertyMap(const std::map>& propTypeMap) { QMap typesMap; @@ -509,20 +496,6 @@ QMap serializeUserTypePropertyMap(const 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; - } - return typesPropTypeMap; -} - SReflectionInterface deserializeTypedObject(const QJsonObject& jsonObject, raco::core::UserObjectFactoryInterface& factory, References& references) { return deserializeTypedObject(jsonObject, factory, references, makeUserTypePropertyMap(), makeStructPropertyMap()); } @@ -673,6 +646,39 @@ std::map> makeUserTypePropertyMa return typesPropTypesMap; } +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; + } + + return structPropTypesMap; +} + +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; + } + return typesPropTypeMap; +} 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{}; diff --git a/datamodel/libDataStorage/tests/Annotation_test.cpp b/datamodel/libCore/tests/Annotation_test.cpp similarity index 97% rename from datamodel/libDataStorage/tests/Annotation_test.cpp rename to datamodel/libCore/tests/Annotation_test.cpp index 22222e35..7090bcbd 100644 --- a/datamodel/libDataStorage/tests/Annotation_test.cpp +++ b/datamodel/libCore/tests/Annotation_test.cpp @@ -7,7 +7,7 @@ * 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 "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include "data_storage/Value.h" #include @@ -17,6 +17,7 @@ #include "gtest/gtest.h" +using namespace raco::core; using namespace raco::data_storage; class PropTest { diff --git a/datamodel/libCore/tests/CMakeLists.txt b/datamodel/libCore/tests/CMakeLists.txt index efd09251..a26486a6 100644 --- a/datamodel/libCore/tests/CMakeLists.txt +++ b/datamodel/libCore/tests/CMakeLists.txt @@ -8,7 +8,8 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. 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 +set(TEST_SOURCES + Annotation_test.cpp Context_test.cpp Handle_test Iterator_test.cpp diff --git a/datamodel/libCore/tests/Context_test.cpp b/datamodel/libCore/tests/Context_test.cpp index 3e19fb23..ca9a9ea2 100644 --- a/datamodel/libCore/tests/Context_test.cpp +++ b/datamodel/libCore/tests/Context_test.cpp @@ -97,8 +97,8 @@ TEST_F(ContextTest, Complex) { EXPECT_EQ(script->size(), 5); - auto val = &script->luaInputs_->get("in_array_struct")->asTable()[1]->asTable().get("bar")->asVec3f().y; - EXPECT_EQ(**val, 0); + double val = *ValueHandle(script, &MockLuaScript::luaInputs_).get("in_array_struct")[1].get("bar").asVec3f().y; + EXPECT_EQ(val, 0); ValueHandle s(script); diff --git a/datamodel/libCore/tests/Deserialization_test.cpp b/datamodel/libCore/tests/Deserialization_test.cpp index 16b4d26e..1df44e4f 100644 --- a/datamodel/libCore/tests/Deserialization_test.cpp +++ b/datamodel/libCore/tests/Deserialization_test.cpp @@ -35,7 +35,7 @@ TEST_F(DeserializationTest, deserializeNode) { SNode sNode{std::dynamic_pointer_cast(result.object)}; ASSERT_EQ(sNode->objectID(), "node_id"); ASSERT_EQ(sNode->objectName(), "node"); - ASSERT_EQ(100.0, *sNode->scale_->z.staticQuery>().max_); + ASSERT_EQ(100.0, *sNode->scale_->z.staticQuery>().max_); } TEST_F(DeserializationTest, deserializeNodeRotated) { diff --git a/datamodel/libCore/tests/ExternalReference_test.cpp b/datamodel/libCore/tests/ExternalReference_test.cpp index 714058da..2461f96e 100644 --- a/datamodel/libCore/tests/ExternalReference_test.cpp +++ b/datamodel/libCore/tests/ExternalReference_test.cpp @@ -245,6 +245,58 @@ class ExtrefTest : public RacoBaseTest<> { } }; + +class ExtrefTestWithZips : public ExtrefTest { +public: + std::string setupBase(const std::string &basePathName, std::function func, const std::string &baseProjectName = std::string("base")) { + RaCoApplication base{backend}; + app = &base; + project = base.activeRaCoProject().project(); + cmd = base.activeRaCoProject().commandInterface(); + + rename_project(baseProjectName); + + func(); + + cmd->set({base.activeRaCoProject().project()->settings(), &raco::user_types::ProjectSettings::saveAsZip_}, true); + base.activeRaCoProject().saveAs(basePathName.c_str()); + return project->projectID(); + } + + std::string setupGeneric(std::function func) { + RaCoApplication app_{backend}; + app = &app_; + project = app_.activeRaCoProject().project(); + cmd = app_.activeRaCoProject().commandInterface(); + + func(); + cmd->set({app_.activeRaCoProject().project()->settings(), &raco::user_types::ProjectSettings::saveAsZip_}, true); + return project->projectID(); + } + + std::string setupComposite(const std::string &basePathName, const std::string &compositePathName, const std::vector &externalObjectNames, + std::function func, const std::string &projectName = std::string()) { + RaCoApplication app_{backend}; + app = &app_; + project = app->activeRaCoProject().project(); + cmd = app->activeRaCoProject().commandInterface(); + + rename_project(projectName); + + pasteFromExt(basePathName, externalObjectNames, true); + + func(); + + cmd->set({app_.activeRaCoProject().project()->settings(), &raco::user_types::ProjectSettings::saveAsZip_}, true); + if (!compositePathName.empty()) { + app->activeRaCoProject().saveAs(compositePathName.c_str()); + } + return project->projectID(); + } + +}; + + TEST_F(ExtrefTest, normal_paste) { auto basePathName{(test_path() / "base.rcp").string()}; @@ -404,7 +456,6 @@ TEST_F(ExtrefTest, extref_paste_fail_from_filecopy) { }); } - TEST_F(ExtrefTest, extref_paste) { auto basePathName{(test_path() / "base.rcp").string()}; @@ -2180,3 +2231,260 @@ TEST_F(ExtrefTest, prefab_instance_update_lua_and_module_change_lua_uri) { ASSERT_FALSE(cmd->errors().hasError({lua})); }); } + +TEST_F(ExtrefTest, link_extref_to_local_reload) { + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; + + TextFile scriptFile = makeFile("script.lua", R"( +function interface() + IN.v = VEC3F + OUT.v = VEC3F +end +function run() +end +)"); + + setupBase(basePathName, [this, &scriptFile]() { + auto luaSource = create("luaSource"); + + cmd->set({luaSource, {"uri"}}, scriptFile); + }); + + setupComposite(basePathName, compositePathName, {"luaSource"}, [this, &scriptFile]() { + auto luaSource = findExt("luaSource"); + + auto luaSink = create("luaSink"); + cmd->set({luaSink, {"uri"}}, scriptFile); + cmd->addLink({luaSource, {"luaOutputs", "v"}}, {luaSink, {"luaInputs", "v"}}); + + EXPECT_EQ(project->links().size(), 1); + }); + + updateComposite(compositePathName, [this]() { + EXPECT_EQ(project->links().size(), 1); + }); +} + +TEST_F(ExtrefTest, link_extref_to_local_update_remove_source) { + auto basePathName{(test_path() / "base.rcp").string()}; + auto compositePathName{(test_path() / "composite.rcp").string()}; + + TextFile scriptFile = makeFile("script.lua", R"( +function interface() + IN.v = VEC3F + OUT.v = VEC3F +end +function run() +end +)"); + + setupBase(basePathName, [this, &scriptFile]() { + auto luaSource = create("luaSource"); + + cmd->set({luaSource, {"uri"}}, scriptFile); + }); + + setupComposite(basePathName, compositePathName, {"luaSource"}, [this, &scriptFile]() { + auto luaSource = findExt("luaSource"); + + auto luaSink = create("luaSink"); + cmd->set({luaSink, {"uri"}}, scriptFile); + cmd->addLink({luaSource, {"luaOutputs", "v"}}, {luaSink, {"luaInputs", "v"}}); + + EXPECT_EQ(project->links().size(), 1); + }); + + updateBase(basePathName, [this]() { + auto luaSource = find("luaSource"); + cmd->deleteObjects({luaSource}); + }); + + updateComposite(compositePathName, [this]() { + EXPECT_EQ(project->links().size(), 0); + }); +} + +TEST_F(ExtrefTestWithZips, filecopy_paste_fail_different_object) { + 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"); + auto prefab = create("prefab"); + auto meshnode = create("meshnode", prefab); + }, + std::string("base")); + + std::filesystem::copy(basePathName1, basePathName2); + updateBase(basePathName2, [this]() { + rename_project("copy"); + }); + + setupGeneric([this, basePathName1, basePathName2]() { + ASSERT_TRUE(pasteFromExt(basePathName1, {"prefab"}, true)); + ASSERT_FALSE(pasteFromExt(basePathName2, {"mesh"}, true)); + }); +} + +TEST_F(ExtrefTestWithZips, filecopy_paste_fail_new_object) { + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; + + setupBase( + basePathName1, [this]() { + auto mesh = create("mesh"); + }, + std::string("base")); + + std::filesystem::copy(basePathName1, basePathName2); + updateBase(basePathName2, [this]() { + auto material = create("material"); + rename_project("copy"); + }); + + setupGeneric([this, basePathName1, basePathName2]() { + ASSERT_TRUE(pasteFromExt(basePathName1, {"mesh"}, true)); + ASSERT_FALSE(pasteFromExt(basePathName2, {"material"}, true)); + }); +} + +TEST_F(ExtrefTestWithZips, filecopy_paste_fail_same_object) { + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; + + setupBase( + basePathName1, [this]() { + auto mesh = create("mesh"); + }, + std::string("base")); + + std::filesystem::copy(basePathName1, basePathName2); + updateBase(basePathName2, [this]() { + rename_project("copy"); + }); + + setupGeneric([this, basePathName1, basePathName2]() { + ASSERT_TRUE(pasteFromExt(basePathName1, {"mesh"}, true)); + ASSERT_FALSE(pasteFromExt(basePathName2, {"mesh"}, true)); + }); +} + +TEST_F(ExtrefTestWithZips, filecopy_update_fail_nested_same_object) { + 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"); + }, + std::string("base")); + + std::filesystem::copy(basePathName1, basePathName2); + updateBase(basePathName2, [this]() { + rename_project("copy"); + }); + + setupBase( + midPathName, [this]() { + auto prefab = create("prefab"); + auto meshnode = create("prefab_child", prefab); + }, + "mid"); + + setupBase( + compositePathName, [this, basePathName1, midPathName]() { + ASSERT_TRUE(pasteFromExt(basePathName1, {"mesh"}, true)); + ASSERT_TRUE(pasteFromExt(midPathName, {"prefab"}, true)); + }, + "composite"); + + updateBase(midPathName, [this, basePathName2]() { + ASSERT_TRUE(pasteFromExt(basePathName2, {"mesh"}, true)); + + auto meshnode = find("prefab_child"); + auto mesh = findExt("mesh"); + cmd->set({meshnode, {"mesh"}}, mesh); + }); + + EXPECT_THROW(updateComposite(compositePathName, [this]() {}), raco::core::ExtrefError); +} + +TEST_F(ExtrefTestWithZips, filecopy_update_fail_nested_different_object) { + 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"); + auto material = create("material"); + }, + std::string("base")); + + std::filesystem::copy(basePathName1, basePathName2); + updateBase(basePathName2, [this]() { + rename_project("copy"); + }); + + setupBase( + midPathName, [this]() { + auto prefab = create("prefab"); + auto meshnode = create("prefab_child", prefab); + }, + "mid"); + + setupBase( + compositePathName, [this, basePathName1, midPathName]() { + ASSERT_TRUE(pasteFromExt(basePathName1, {"material"}, true)); + ASSERT_TRUE(pasteFromExt(midPathName, {"prefab"}, true)); + }, + "composite"); + + updateBase(midPathName, [this, basePathName2]() { + ASSERT_TRUE(pasteFromExt(basePathName2, {"mesh"}, true)); + + auto meshnode = find("prefab_child"); + auto mesh = findExt("mesh"); + cmd->set({meshnode, {"mesh"}}, mesh); + }); + + EXPECT_THROW(updateComposite(compositePathName, [this]() {}), raco::core::ExtrefError); +} + +TEST_F(ExtrefTestWithZips, extref_paste_fail_from_filecopy) { + auto basePathName1{(test_path() / "base1.rcp").string()}; + auto basePathName2{(test_path() / "base2.rcp").string()}; + + setupBase( + basePathName1, [this]() { + auto mesh = create("mesh"); + }, + std::string("base")); + + std::filesystem::copy(basePathName1, basePathName2); + updateBase(basePathName2, [this]() { + auto mesh = find("mesh"); + auto prefab = create("prefab"); + auto meshnode = create("meshnode", prefab); + cmd->set({meshnode, {"mesh"}}, mesh); + auto material = create("material"); + }); + + updateComposite(basePathName1, [this, basePathName2]() { + ASSERT_FALSE(pasteFromExt(basePathName2, {"mesh"}, true)); + ASSERT_FALSE(pasteFromExt(basePathName2, {"material"}, true)); + ASSERT_FALSE(pasteFromExt(basePathName2, {"prefab"}, true)); + }); + + setupGeneric([this, basePathName2]() { + ASSERT_TRUE(pasteFromExt(basePathName2, {"mesh"}, true)); + ASSERT_TRUE(pasteFromExt(basePathName2, {"material"}, true)); + ASSERT_TRUE(pasteFromExt(basePathName2, {"prefab"}, true)); + }); +} diff --git a/datamodel/libCore/tests/Link_test.cpp b/datamodel/libCore/tests/Link_test.cpp index 9102f66b..9bc57c46 100644 --- a/datamodel/libCore/tests/Link_test.cpp +++ b/datamodel/libCore/tests/Link_test.cpp @@ -197,6 +197,23 @@ TEST_F(LinkTest, lua_ortho_camera_struct_viewport_frustum) { }}); } +TEST_F(LinkTest, lua_lua_compatibility_int64) { + auto start = create_lua("start", "scripts/types-scalar.lua"); + auto end = create_lua("end", "scripts/struct-simple.lua"); + + auto allowed64 = Queries::allowedLinkStartProperties(project, ValueHandle(end, {"luaInputs", "s", "integer64"})); + std::set refAllowed64{ + {start, {"luaOutputs", "ointeger64"}}}; + + EXPECT_EQ(allowed64, refAllowed64); + + auto allowed32 = Queries::allowedLinkStartProperties(project, ValueHandle(end, {"luaInputs", "s", "integer"})); + std::set refAllowed32{ + {start, {"luaOutputs", "ointeger"}}}; + + EXPECT_EQ(allowed32, refAllowed32); +} + TEST_F(LinkTest, lua_lua_compatibility_float) { auto start = create_lua("start", "scripts/types-scalar.lua"); auto end = create_lua("end", "scripts/struct-simple.lua"); diff --git a/datamodel/libCore/tests/Node_test.cpp b/datamodel/libCore/tests/Node_test.cpp index 099bb885..de069894 100644 --- a/datamodel/libCore/tests/Node_test.cpp +++ b/datamodel/libCore/tests/Node_test.cpp @@ -7,7 +7,7 @@ * 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 "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include "data_storage/Value.h" #include "user_types/Node.h" diff --git a/datamodel/libCore/tests/Prefab_test.cpp b/datamodel/libCore/tests/Prefab_test.cpp index e339a9c9..6476204a 100644 --- a/datamodel/libCore/tests/Prefab_test.cpp +++ b/datamodel/libCore/tests/Prefab_test.cpp @@ -101,6 +101,68 @@ TEST_F(PrefabTest, check_id_nesting) { EXPECT_EQ(node_2->objectID(), EditorObject::XorObjectIDs(node->objectID(), inst_3->objectID())); } +TEST_F(PrefabTest, check_id_copy_paste) { + auto prefab = create("prefab"); + auto lua = create_lua("lua", "scripts/types-scalar.lua", prefab); + + auto inst = create_prefabInstance("inst", prefab); + auto inst_lua = raco::select(inst->children_->asVector()); + + commandInterface.set({lua, {"luaInputs", "float"}}, 2.0); + commandInterface.set({inst_lua, {"luaInputs", "float"}}, 3.0); + + auto pasted = commandInterface.pasteObjects(commandInterface.copyObjects({inst})); + auto inst_2 = raco::select(pasted); + auto inst_2_lua = inst_2->children_->get(0)->asRef(); + ASSERT_TRUE(inst_2 != nullptr); + ASSERT_TRUE(inst_2_lua != nullptr); + + EXPECT_TRUE(ValueHandle(inst_2_lua, {"luaInputs"}).hasProperty("float")); + EXPECT_EQ(ValueHandle(inst_2_lua, {"luaInputs"}).get("float").asDouble(), 3.0); + EXPECT_EQ(inst_2_lua->objectID(), EditorObject::XorObjectIDs(lua->objectID(), inst_2->objectID())); +} + +TEST_F(PrefabTest, check_id_copy_paste_nesting) { + // prefab_2 inst_2 + // prefab inst_1 inst_3 + // lua lua_1 lua_2 + + auto prefab = create("prefab"); + auto lua = create_lua("lua", "scripts/types-scalar.lua", prefab); + commandInterface.set({lua, {"luaInputs", "float"}}, 2.0); + + auto prefab_2 = create("prefab2"); + auto inst_1 = create_prefabInstance("inst", prefab, prefab_2); + + EXPECT_EQ(inst_1->children_->size(), 1); + auto lua_1 = inst_1->children_->asVector()[0]; + commandInterface.set({lua_1, {"luaInputs", "float"}}, 3.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 lua_2 = inst_3->children_->asVector()[0]; + commandInterface.set({lua_2, {"luaInputs", "float"}}, 4.0); + + EXPECT_EQ(lua_1->objectID(), EditorObject::XorObjectIDs(lua->objectID(), inst_1->objectID())); + EXPECT_EQ(inst_3->objectID(), EditorObject::XorObjectIDs(inst_1->objectID(), inst_2->objectID())); + EXPECT_EQ(lua_2->objectID(), EditorObject::XorObjectIDs(lua_1->objectID(), inst_2->objectID())); + EXPECT_EQ(lua_2->objectID(), EditorObject::XorObjectIDs(lua->objectID(), inst_3->objectID())); + + auto pasted = commandInterface.pasteObjects(commandInterface.copyObjects({inst_2})); + auto inst_2_copy = raco::select(pasted); + EXPECT_EQ(inst_2_copy->children_->size(), 1); + auto inst_3_copy = inst_2_copy->children_->asVector()[0]; + EXPECT_EQ(inst_3_copy->children_->size(), 1); + auto lua_2_copy = inst_3_copy->children_->asVector()[0]; + + EXPECT_EQ(ValueHandle(lua_2_copy, {"luaInputs"}).get("float").asDouble(), 4.0); + EXPECT_EQ(inst_3_copy->objectID(), EditorObject::XorObjectIDs(inst_1->objectID(), inst_2_copy->objectID())); + EXPECT_EQ(lua_2_copy->objectID(), EditorObject::XorObjectIDs(lua_1->objectID(), inst_2_copy->objectID())); + EXPECT_EQ(lua_2_copy->objectID(), EditorObject::XorObjectIDs(lua->objectID(), inst_3_copy->objectID())); +} + TEST_F(PrefabTest, move_node_in) { auto prefab = create("prefab"); auto inst = create("inst"); @@ -656,6 +718,19 @@ TEST_F(PrefabTest, update_luascript_module_dependant_in_prefab_remove_module) { ASSERT_TRUE(commandInterface.errors().hasError({inst_lua})); } +TEST_F(PrefabTest, duplication_gets_propagated) { + auto prefab = create("prefab"); + auto inst = create("inst"); + commandInterface.set({inst, &PrefabInstance::template_}, prefab); + + auto node = create("node", prefab); + commandInterface.moveScenegraphChildren({node}, prefab); + + commandInterface.duplicateObjects({node}); + + ASSERT_EQ(inst->children_->asVector().size(), 2); +} + TEST_F(PrefabTest, update_private_material_ref_change_with_overlapping_property_names) { auto material1 = create("material1"); auto material2 = create("material2"); diff --git a/datamodel/libCore/tests/ProjectMigration_test.cpp b/datamodel/libCore/tests/ProjectMigration_test.cpp index 2fa79ef5..e7ed10c0 100644 --- a/datamodel/libCore/tests/ProjectMigration_test.cpp +++ b/datamodel/libCore/tests/ProjectMigration_test.cpp @@ -140,12 +140,12 @@ TEST_F(MigrationTest, migrate_from_V10) { ASSERT_TRUE((raco::core::PropertyInterface::primitiveType(engineType) != raco::data_storage::PrimitiveType::Ref) == hasLinkAnno); } - auto& uniforms = *material->uniforms_; - ASSERT_EQ(uniforms.get("scalar")->asDouble(), 42.0); - ASSERT_EQ(uniforms.get("count_")->asInt(), 42); - ASSERT_EQ(*uniforms.get("vec")->asVec3f().x, 0.1); - ASSERT_EQ(*uniforms.get("ambient")->asVec4f().w, 0.4); - ASSERT_EQ(*uniforms.get("iv2")->asVec2i().i2_, 2); + ValueHandle uniforms{material, &raco::user_types::Material::uniforms_}; + ASSERT_EQ(uniforms.get("scalar").asDouble(), 42.0); + ASSERT_EQ(uniforms.get("count_").asInt(), 42); + ASSERT_EQ(*uniforms.get("vec").asVec3f().x, 0.1); + ASSERT_EQ(*uniforms.get("ambient").asVec4f().w, 0.4); + ASSERT_EQ(*uniforms.get("iv2").asVec2i().i2_, 2); } TEST_F(MigrationTest, migrate_from_V12) { @@ -348,11 +348,10 @@ TEST_F(MigrationTest, migrate_from_V18) { auto bgColor = racoproject->project()->settings()->backgroundColor_; ASSERT_EQ(bgColor->typeDescription.typeName, Vec4f::typeDescription.typeName); - auto bgColorVec4 = bgColor.asVec4f(); - ASSERT_EQ(bgColorVec4.x.asDouble(), 0.3); - ASSERT_EQ(bgColorVec4.y.asDouble(), 0.2); - ASSERT_EQ(bgColorVec4.z.asDouble(), 0.1); - ASSERT_EQ(bgColorVec4.w.asDouble(), 1.0); + ASSERT_EQ(bgColor->x.asDouble(), 0.3); + ASSERT_EQ(bgColor->y.asDouble(), 0.2); + ASSERT_EQ(bgColor->z.asDouble(), 0.1); + ASSERT_EQ(bgColor->w.asDouble(), 1.0); } TEST_F(MigrationTest, migrate_from_V21) { @@ -437,6 +436,30 @@ TEST_F(MigrationTest, migrate_from_current) { }) != instances.end()); } } + +TEST_F(MigrationTest, check_current_type_maps) { + // Check that the type maps for user object and structs types in the "version-current.rca" are + // identical to the ones generated when saving a project. + // + // If this fails the file format has changed: we need to increase the file version number + // and potentially write migration code. + + QString filename = QString::fromStdString((test_path() / "migrationTestData" / "version-current.rca").string()); + QFile file{filename}; + EXPECT_TRUE(file.open(QIODevice::ReadOnly | QIODevice::Text)); + auto document{QJsonDocument::fromJson(file.readAll())}; + file.close(); + + auto fileUserPropTypeMap = raco::serialization::deserializeUserTypePropertyMap(document[raco::serialization::keys::USER_TYPE_PROP_MAP]); + auto fileStructTypeMap = raco::serialization::deserializeUserTypePropertyMap(document[raco::serialization::keys::STRUCT_PROP_MAP]); + + auto currentUserPropTypeMap = raco::serialization::makeUserTypePropertyMap(); + auto currentStructTypeMap = raco::serialization::makeStructPropertyMap(); + + EXPECT_EQ(fileUserPropTypeMap, currentUserPropTypeMap); + EXPECT_EQ(fileStructTypeMap, currentStructTypeMap); +} + 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. @@ -447,7 +470,7 @@ TEST_F(MigrationTest, check_proxy_factory_has_all_objects_types) { for (auto& item : objectFactory()->getTypes()) { auto name = item.first; - EXPECT_TRUE(proxyTypeMap.find(name) != proxyTypeMap.end()); + EXPECT_TRUE(proxyTypeMap.find(name) != proxyTypeMap.end()) << fmt::format("type name: '{}'", name); } } @@ -462,7 +485,7 @@ TEST_F(MigrationTest, check_proxy_factory_has_all_dynamic_property_types) { for (auto& item : userFactory.getProperties()) { auto name = item.first; - EXPECT_TRUE(proxyProperties.find(name) != proxyProperties.end()); + EXPECT_TRUE(proxyProperties.find(name) != proxyProperties.end()) << fmt::format("property name: '{}'", name); } } TEST_F(MigrationTest, check_proxy_factory_can_create_all_static_properties) { @@ -479,8 +502,8 @@ TEST_F(MigrationTest, check_proxy_factory_can_create_all_static_properties) { 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); + ASSERT_TRUE(proxyProperty != nullptr) << fmt::format("property type name: '{}'", propTypeName); + ASSERT_EQ(proxyProperty->typeName(), propTypeName) << fmt::format("property type name: '{}'", propTypeName); } } @@ -491,8 +514,8 @@ TEST_F(MigrationTest, check_proxy_factory_can_create_all_static_properties) { 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); + ASSERT_TRUE(proxyProperty != nullptr) << fmt::format("property type name: '{}'", propTypeName); + ASSERT_EQ(proxyProperty->typeName(), propTypeName) << fmt::format("property type name: '{}'", propTypeName); } } } diff --git a/datamodel/libCore/tests/Reference_test.cpp b/datamodel/libCore/tests/Reference_test.cpp index 66bd09f7..9408b188 100644 --- a/datamodel/libCore/tests/Reference_test.cpp +++ b/datamodel/libCore/tests/Reference_test.cpp @@ -7,7 +7,7 @@ * 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 "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include "data_storage/Value.h" #include "user_types/Node.h" diff --git a/datamodel/libCore/tests/Serialization_test.cpp b/datamodel/libCore/tests/Serialization_test.cpp index 349b6cee..ee9f116c 100644 --- a/datamodel/libCore/tests/Serialization_test.cpp +++ b/datamodel/libCore/tests/Serialization_test.cpp @@ -12,7 +12,7 @@ #include "testing/TestEnvironmentCore.h" #include "testing/TestUtil.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "core/ExternalReferenceAnnotation.h" #include "user_types/LuaScript.h" #include "user_types/Material.h" @@ -44,7 +44,7 @@ struct SerializationTest : public TestEnvironmentCore { TEST_F(SerializationTest, serializeNode) { const auto sNode{std::make_shared("node", "node_id")}; - sNode->scale_->z.staticQuery>().max_ = 100.0; + sNode->scale_->z.staticQuery>().max_ = 100.0; auto result = raco::serialization::test_helpers::serializeObject(sNode); if (WRITE_RESULT) file::write((u8path{CMAKE_CURRENT_SOURCE_DIR} / "expectations" / "Node.json").string(), result); diff --git a/datamodel/libCore/tests/Undo_test.cpp b/datamodel/libCore/tests/Undo_test.cpp index 481758cf..ca0a9683 100644 --- a/datamodel/libCore/tests/Undo_test.cpp +++ b/datamodel/libCore/tests/Undo_test.cpp @@ -672,18 +672,14 @@ end [this, node, lua]() { ASSERT_EQ(project.links().size(), 1); ASSERT_TRUE(project.links()[0]->isValid()); - auto nodeRotType = ValueHandle{node, {"rotation"}}.type(); - auto luaVecType = ValueHandle{lua, {"luaOutputs", "vec"}}.type(); - ASSERT_EQ(nodeRotType, raco::core::PrimitiveType::Vec3f); - ASSERT_EQ(luaVecType, raco::core::PrimitiveType::Vec4f); + ASSERT_TRUE(ValueHandle(node, {"rotation"}).isVec3f()); + ASSERT_TRUE(ValueHandle(lua, {"luaOutputs", "vec"}).isVec4f()); }, [this, node, lua]() { ASSERT_EQ(project.links().size(), 1); ASSERT_TRUE(project.links()[0]->isValid()); - auto nodeRotType = ValueHandle{node, {"rotation"}}.type(); - auto luaVecType = ValueHandle{lua, {"luaOutputs", "vec"}}.type(); - ASSERT_EQ(nodeRotType, raco::core::PrimitiveType::Vec3f); - ASSERT_EQ(luaVecType, raco::core::PrimitiveType::Vec3f); + ASSERT_TRUE(ValueHandle(node, {"rotation"}).isVec3f()); + ASSERT_TRUE(ValueHandle(lua, {"luaOutputs", "vec"}).isVec3f()); }); } diff --git a/datamodel/libCore/tests/ValueHandle_test.cpp b/datamodel/libCore/tests/ValueHandle_test.cpp index 47ae3eaa..6011ae09 100644 --- a/datamodel/libCore/tests/ValueHandle_test.cpp +++ b/datamodel/libCore/tests/ValueHandle_test.cpp @@ -8,7 +8,7 @@ * 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 #include #include "user_types/Node.h" #include "user_types/PerspectiveCamera.h" @@ -64,11 +64,11 @@ TEST(ValueHandle, ValueHandle_comparesByIndex_append) { ValueHandle handle{tableObject}; - tableObject->table_.asTable().addProperty(PrimitiveType::Vec2f); + tableObject->table_.asTable().addProperty(new Value()); ValueHandle child1BeforeAdd = handle.get("table")[0]; - tableObject->table_.asTable().addProperty(PrimitiveType::Vec2f); + tableObject->table_.asTable().addProperty(new Value()); ValueHandle child1AfterAdd = handle.get("table")[0]; ValueHandle child2AfterAdd = handle.get("table")[1]; @@ -84,11 +84,11 @@ TEST(ValueHandle, ValueHandle_comparesByIndex_prepend) { ValueHandle handle{tableObject}; - tableObject->table_.asTable().addProperty(PrimitiveType::Vec2f); + tableObject->table_.asTable().addProperty(new Value()); ValueHandle child1BeforeAdd = handle.get("table")[0]; - tableObject->table_.asTable().addProperty(PrimitiveType::Vec2f, 0); + tableObject->table_.asTable().addProperty(new Value(), 0); ValueHandle child1AfterAdd = handle.get("table")[0]; ValueHandle child2AfterAdd = handle.get("table")[1]; diff --git a/datamodel/libCore/tests/migrationTestData/version-current.rca b/datamodel/libCore/tests/migrationTestData/version-current.rca index 34b26ac6..d8cdb0bd 100644 --- a/datamodel/libCore/tests/migrationTestData/version-current.rca +++ b/datamodel/libCore/tests/migrationTestData/version-current.rca @@ -1,7 +1,7 @@ { "externalProjects": { }, - "fileVersion": 24, + "fileVersion": 29, "instances": [ { "properties": { @@ -682,6 +682,7 @@ "objectID": "71454add-eb56-4288-9057-825539914bed", "objectName": "test-offscreen-tex-uniform-migration-material", "runTimer": false, + "saveAsZip": false, "sceneId": { "annotations": [ { @@ -1224,8 +1225,39 @@ ], "value": 1 }, + "generateMipmaps": false, + "level2uriBack": "", + "level2uriBottom": "", + "level2uriFront": "", + "level2uriLeft": "", + "level2uriRight": "", + "level2uriTop": "", + "level3uriBack": "", + "level3uriBottom": "", + "level3uriFront": "", + "level3uriLeft": "", + "level3uriRight": "", + "level3uriTop": "", + "level4uriBack": "", + "level4uriBottom": "", + "level4uriFront": "", + "level4uriLeft": "", + "level4uriRight": "", + "level4uriTop": "", "magSamplingMethod": 0, "minSamplingMethod": 0, + "mipmapLevel": { + "annotations": [ + { + "properties": { + "max": 4, + "min": 1 + }, + "typeName": "RangeAnnotationInt" + } + ], + "value": 1 + }, "objectID": "410631d1-cb7f-40c9-88c7-3bb1515d5caa", "objectName": "CubeMap", "uriBack": "", @@ -1886,18 +1918,18 @@ ], "logicEngineVersion": [ 0, - 13, - 0 + 14, + 2 ], "racoVersion": [ 0, - 11, - 1 + 12, + 0 ], "ramsesVersion": [ 27, 0, - 114 + 115 ], "structPropMap": { "BlendOptions": { @@ -1937,6 +1969,36 @@ "farPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", "fieldOfView": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation", "nearPlane": "Double::DisplayNameAnnotation::RangeAnnotationDouble::LinkEndAnnotation" + }, + "Vec2f": { + "x": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "y": "Double::DisplayNameAnnotation::RangeAnnotationDouble" + }, + "Vec2i": { + "i1": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i2": "Int::DisplayNameAnnotation::RangeAnnotationInt" + }, + "Vec3f": { + "x": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "y": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "z": "Double::DisplayNameAnnotation::RangeAnnotationDouble" + }, + "Vec3i": { + "i1": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i2": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i3": "Int::DisplayNameAnnotation::RangeAnnotationInt" + }, + "Vec4f": { + "w": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "x": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "y": "Double::DisplayNameAnnotation::RangeAnnotationDouble", + "z": "Double::DisplayNameAnnotation::RangeAnnotationDouble" + }, + "Vec4i": { + "i1": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i2": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i3": "Int::DisplayNameAnnotation::RangeAnnotationInt", + "i4": "Int::DisplayNameAnnotation::RangeAnnotationInt" } }, "userTypePropMap": { @@ -1961,8 +2023,28 @@ "CubeMap": { "anisotropy": "Int::DisplayNameAnnotation::RangeAnnotationInt", "children": "Table::ArraySemanticAnnotation::HiddenProperty", + "generateMipmaps": "Bool::DisplayNameAnnotation", + "level2uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "level2uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "level2uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "level2uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "level2uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "level2uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "level3uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "level3uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "level3uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "level3uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "level3uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "level3uriTop": "String::URIAnnotation::DisplayNameAnnotation", + "level4uriBack": "String::URIAnnotation::DisplayNameAnnotation", + "level4uriBottom": "String::URIAnnotation::DisplayNameAnnotation", + "level4uriFront": "String::URIAnnotation::DisplayNameAnnotation", + "level4uriLeft": "String::URIAnnotation::DisplayNameAnnotation", + "level4uriRight": "String::URIAnnotation::DisplayNameAnnotation", + "level4uriTop": "String::URIAnnotation::DisplayNameAnnotation", "magSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", "minSamplingMethod": "Int::DisplayNameAnnotation::EnumerationAnnotation", + "mipmapLevel": "Int::DisplayNameAnnotation::RangeAnnotationInt", "objectID": "String::HiddenProperty", "objectName": "String::DisplayNameAnnotation", "uriBack": "String::URIAnnotation::DisplayNameAnnotation", @@ -2081,6 +2163,7 @@ "objectID": "String::HiddenProperty", "objectName": "String::DisplayNameAnnotation", "runTimer": "Bool::HiddenProperty", + "saveAsZip": "Bool::DisplayNameAnnotation", "sceneId": "Int::DisplayNameAnnotation::RangeAnnotationInt", "viewport": "Vec2i::DisplayNameAnnotation" }, diff --git a/datamodel/libDataStorage/CMakeLists.txt b/datamodel/libDataStorage/CMakeLists.txt index 5c165c4e..73ab1a0e 100644 --- a/datamodel/libDataStorage/CMakeLists.txt +++ b/datamodel/libDataStorage/CMakeLists.txt @@ -10,8 +10,6 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h add_library(libDataStorage include/data_storage/AnnotationBase.h - include/data_storage/BasicAnnotations.h - include/data_storage/BasicTypes.h include/data_storage/ReflectionInterface.h src/ReflectionInterface.cpp include/data_storage/Table.h src/Table.cpp include/data_storage/Value.h src/Value.cpp diff --git a/datamodel/libDataStorage/include/data_storage/ReflectionInterface.h b/datamodel/libDataStorage/include/data_storage/ReflectionInterface.h index b6b9d5fb..dbddb7c2 100644 --- a/datamodel/libDataStorage/include/data_storage/ReflectionInterface.h +++ b/datamodel/libDataStorage/include/data_storage/ReflectionInterface.h @@ -147,4 +147,12 @@ class ClassWithReflectedMembers : public ReflectionInterface { std::vector> annotations_; }; -} \ No newline at end of file + +// Base class for all PrimitiveType::Struct classes +// See Value.h for details on struct types +class StructBase : public ClassWithReflectedMembers { +public: + StructBase(std::vector>&& properties = {}) : ClassWithReflectedMembers(std::move(properties)) {} +}; + +} \ No newline at end of file diff --git a/datamodel/libDataStorage/include/data_storage/Value.h b/datamodel/libDataStorage/include/data_storage/Value.h index c01ea417..a8fa8cbb 100644 --- a/datamodel/libDataStorage/include/data_storage/Value.h +++ b/datamodel/libDataStorage/include/data_storage/Value.h @@ -31,17 +31,11 @@ class AnnotationBase; class Table; -class Vec2f; -class Vec3f; -class Vec4f; -class Vec2i; -class Vec3i; -class Vec4i; - enum class PrimitiveType { // Simple scalar types: Bool = 0, Int, + Int64, Double, String, @@ -51,18 +45,33 @@ enum class PrimitiveType { // Dictionary-like: holds a Table Table, - // Complex c++ class types - Vec2f, - Vec3f, - Vec4f, - Vec2i, - Vec3i, - Vec4i, - - // Complex C++ class types; must be class derived from ClassWithReflectedMembers + // Complex C++ class types; must be class derived from StructBase Struct }; +// For the datatypes Int, Int64 and Double we define extreme values which are allowed in our data model. +// For Int 64 this limit is lower then the theoretical machine maximum, since the old Lua version of ramses logic represents 64-bit Integers as 64-Bit Floats. +template +constexpr T numericalLimitMin() { + return std::numeric_limits::lowest(); +} + +template +constexpr T numericalLimitMax() { + return std::numeric_limits::max(); +} + +template <> +constexpr int64_t numericalLimitMin() { + // The numerical limit here is given by the lowest integer for which all integers between zero and this value can be stored precisely as a double: -(2 ^ 53) + return -9007199254740992; +} + +template <> +constexpr int64_t numericalLimitMax() { + // The numerical limit here is given by the highest integer for which all integers between zero and this value can be stored precisely as a double: 2 ^ 53 + return 9007199254740992; +} template struct AlwaysFalse : std::false_type {}; @@ -74,6 +83,8 @@ template constexpr PrimitiveType primitiveType() { if constexpr (std::is_same::value) { return PrimitiveType::Int; + } else if constexpr (std::is_same::value) { + return PrimitiveType::Int64; } else if constexpr (std::is_same::value) { return PrimitiveType::Double; } else if constexpr (std::is_same::value) { @@ -82,19 +93,7 @@ constexpr PrimitiveType primitiveType() { return PrimitiveType::String; } else if constexpr (std::is_same::value) { return PrimitiveType::Table; - } else if constexpr (std::is_same::value) { - return PrimitiveType::Vec2f; - } else if constexpr (std::is_same::value) { - return PrimitiveType::Vec3f; - } else if constexpr (std::is_same::value) { - return PrimitiveType::Vec4f; - } else if constexpr (std::is_same::value) { - return PrimitiveType::Vec2i; - } else if constexpr (std::is_same::value) { - return PrimitiveType::Vec3i; - } else if constexpr (std::is_same::value) { - return PrimitiveType::Vec4i; - } else if constexpr (std::is_base_of::value) { + } else if constexpr (std::is_base_of::value) { return PrimitiveType::Struct; } else if constexpr (std::is_base_of::value) { return PrimitiveType::Ref; @@ -106,7 +105,7 @@ std::string getTypeName(PrimitiveType type); bool isPrimitiveTypeName(const std::string& type); PrimitiveType toPrimitiveType(const std::string& type); -using TypeMapType = std::tuple; +using TypeMapType = std::tuple; // Variant with annotations // - variant types: @@ -115,7 +114,7 @@ using TypeMapType = std::tuple to be usable the class CC must be derived from ClassWithReflectedMembers +// - for a Value to be usable the class CC must be derived from StructBase // - CC must implement the following member functions (same as for e.g. Vec2f) // CC(const CC& other, std::function* translateRef) // CC& operator=(const CC& other) @@ -160,30 +159,17 @@ class ValueBase { virtual bool& asBool() = 0; virtual int& asInt() = 0; + virtual int64_t& asInt64() = 0; virtual double& asDouble() = 0; virtual std::string& asString() = 0; virtual Table& asTable() = 0; - virtual Vec2f& asVec2f() = 0; - virtual Vec3f& asVec3f() = 0; - virtual Vec4f& asVec4f() = 0; - virtual Vec2i& asVec2i() = 0; - virtual Vec3i& asVec3i() = 0; - virtual Vec4i& asVec4i() = 0; - virtual const bool& asBool() const = 0; virtual const int& asInt() const = 0; + virtual const int64_t& asInt64() const = 0; virtual const double& asDouble() const = 0; virtual const std::string& asString() const = 0; virtual const Table& asTable() const = 0; - - virtual const Vec2f& asVec2f() const = 0; - virtual const Vec3f& asVec3f() const = 0; - virtual const Vec4f& asVec4f() const = 0; - virtual const Vec2i& asVec2i() const = 0; - virtual const Vec3i& asVec3i() const = 0; - virtual const Vec4i& asVec4i() const = 0; - // Return a reference to the generic interface class for all // PrimitiveTypes that are not scalar types but contain substructure, i.e. // Tables and the Vec2f,... types. @@ -209,12 +195,12 @@ 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; + virtual StructBase& asStruct() = 0; + virtual const StructBase& asStruct() const = 0; // Set Struct type values. // Identical types are dynamically enforced as runtime. - virtual void setStruct(const ClassWithReflectedMembers&) = 0; + virtual void setStruct(const StructBase&) = 0; // Check for equality of the actual classes of the arguments; // differs from comparing the PrimitiveType by @@ -246,6 +232,7 @@ class ValueBase { ValueBase& operator=(bool value); ValueBase& operator=(int value); + ValueBase& operator=(int64_t value); ValueBase& operator=(double value); ValueBase& operator=(std::string const& value); ValueBase& operator=(SEditorObject value); @@ -436,6 +423,9 @@ class Value : public ValueBase { virtual int& asInt() override { return asImpl(); } + virtual int64_t& asInt64() override { + return asImpl(); + } virtual double& asDouble() override { return asImpl(); } @@ -461,19 +451,19 @@ class Value : public ValueBase { value_ = std::static_pointer_cast(v); } } - virtual ClassWithReflectedMembers& asStruct() override { + virtual StructBase& asStruct() override { if constexpr (primitiveType() == PrimitiveType::Struct) { return value_; } throw std::runtime_error("type mismatch"); } - virtual const ClassWithReflectedMembers& asStruct() const override { + virtual const StructBase& asStruct() const override { if constexpr (primitiveType() == PrimitiveType::Struct) { return value_; } throw std::runtime_error("type mismatch"); } - virtual void setStruct(const ClassWithReflectedMembers& newValue) override { + virtual void setStruct(const StructBase& newValue) override { if constexpr (primitiveType() == PrimitiveType::Struct) { const T* vp = dynamic_cast(&newValue); if (!vp) { @@ -503,31 +493,15 @@ class Value : public ValueBase { virtual Table& asTable() override { return asImpl
(); } - virtual Vec2f& asVec2f() override { - return asImpl(); - } - virtual Vec3f& asVec3f() override { - return asImpl(); - } - virtual Vec4f& asVec4f() override { - return asImpl(); - } - virtual Vec2i& asVec2i() override { - return asImpl(); - } - virtual Vec3i& asVec3i() override { - return asImpl(); - } - virtual Vec4i& asVec4i() override { - return asImpl(); - } - virtual const bool& asBool() const override { return asImplConst(); } virtual const int& asInt() const override { return asImplConst(); } + virtual const int64_t& asInt64() const override { + return asImplConst(); + } virtual const double& asDouble() const override { return asImplConst(); } @@ -540,24 +514,6 @@ class Value : public ValueBase { virtual const Table& asTable() const override { return asImplConst
(); } - virtual const Vec2f& asVec2f() const override { - return asImplConst(); - } - virtual const Vec3f& asVec3f() const override { - return asImplConst(); - } - virtual const Vec4f& asVec4f() const override { - return asImplConst(); - } - virtual const Vec2i& asVec2i() const override { - return asImplConst(); - } - virtual const Vec3i& asVec3i() const override { - return asImplConst(); - } - virtual const Vec4i& asVec4i() const override { - return asImplConst(); - } virtual std::unique_ptr clone(std::function* translateRef) const { if constexpr (std::is_same::value) { @@ -708,6 +664,10 @@ template <> inline Value::Value(const Value& other, std::function* translateRef) : ValueBase(), value_(*other) { } +template <> +inline Value::Value(const Value& other, std::function* translateRef) : ValueBase(), value_(*other) { +} + template <> inline Value::Value(const Value& other, std::function* translateRef) : ValueBase(), value_(*other) { } diff --git a/datamodel/libDataStorage/src/Value.cpp b/datamodel/libDataStorage/src/Value.cpp index c9bd6f4f..019b918f 100644 --- a/datamodel/libDataStorage/src/Value.cpp +++ b/datamodel/libDataStorage/src/Value.cpp @@ -10,7 +10,6 @@ #include "data_storage/Value.h" -#include "data_storage/BasicTypes.h" #include "data_storage/Table.h" #include @@ -22,18 +21,12 @@ static std::map& primitiveTypeName() { static std::map primitiveTypeNameMap{ {PrimitiveType::Bool, "Bool"}, {PrimitiveType::Int, "Int"}, + {PrimitiveType::Int64, "Int64"}, {PrimitiveType::Double, "Double"}, {PrimitiveType::String, "String"}, {PrimitiveType::Ref, "Ref"}, - {PrimitiveType::Table, "Table"}, - - {PrimitiveType::Vec2f, "Vec2f"}, - {PrimitiveType::Vec3f, "Vec3f"}, - {PrimitiveType::Vec4f, "Vec4f"}, - {PrimitiveType::Vec2i, "Vec2i"}, - {PrimitiveType::Vec3i, "Vec3i"}, - {PrimitiveType::Vec4i, "Vec4i"}}; + {PrimitiveType::Table, "Table"}}; return primitiveTypeNameMap; }; @@ -66,6 +59,9 @@ std::unique_ptr ValueBase::create(PrimitiveType type) { case PrimitiveType::Int: return std::unique_ptr(new Value()); break; + case PrimitiveType::Int64: + return std::unique_ptr(new Value()); + break; case PrimitiveType::Double: return std::unique_ptr(new Value()); break; @@ -81,25 +77,6 @@ std::unique_ptr ValueBase::create(PrimitiveType type) { 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; @@ -117,6 +94,11 @@ int& ValueBase::as() { return asInt(); } +template <> +int64_t& ValueBase::as() { + return asInt64(); +} + template <> double& ValueBase::as() { return asDouble(); @@ -132,32 +114,6 @@ Table& ValueBase::as
() { return asTable(); } -template <> -Vec2f& ValueBase::as() { - return asVec2f(); -} -template <> -Vec3f& ValueBase::as() { - return asVec3f(); -} -template <> -Vec4f& ValueBase::as() { - return asVec4f(); -} - -template <> -Vec2i& ValueBase::as() { - return asVec2i(); -} -template <> -Vec3i& ValueBase::as() { - return asVec3i(); -} -template <> -Vec4i& ValueBase::as() { - return asVec4i(); -} - template <> const bool& ValueBase::as() const { return asBool(); @@ -168,6 +124,11 @@ const int& ValueBase::as() const { return asInt(); } +template <> +const int64_t& ValueBase::as() const { + return asInt64(); +} + template <> const double& ValueBase::as() const { return asDouble(); @@ -183,32 +144,6 @@ const Table& ValueBase::as
() const { return asTable(); } -template <> -const Vec2f& ValueBase::as() const { - return asVec2f(); -} -template <> -const Vec3f& ValueBase::as() const { - return asVec3f(); -} -template <> -const Vec4f& ValueBase::as() const { - return asVec4f(); -} - -template <> -const Vec2i& ValueBase::as() const { - return asVec2i(); -} -template <> -const Vec3i& ValueBase::as() const { - return asVec3i(); -} -template <> -const Vec4i& ValueBase::as() const { - return asVec4i(); -} - bool ValueBase::classesEqual(const ValueBase& left, const ValueBase& right) { return typeid(left) == typeid(right); } @@ -223,6 +158,11 @@ ValueBase& ValueBase::operator=(int value) { return *this; } +ValueBase& ValueBase::operator=(int64_t value) { + asInt64() = value; + return *this; +} + ValueBase& ValueBase::operator=(double value) { asDouble() = value; return *this; @@ -246,6 +186,7 @@ ValueBase& ValueBase::set(T const& value) { template ValueBase& ValueBase::set(bool const& value); template ValueBase& ValueBase::set(int const& value); +template ValueBase& ValueBase::set(int64_t const& value); template ValueBase& ValueBase::set(double const& value); template ValueBase& ValueBase::set(std::string const& value); @@ -255,42 +196,6 @@ ValueBase& ValueBase::set>(std::vector con 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; @@ -311,36 +216,9 @@ void primitiveCopyAnnotationData(T& dest, const T& src) { template void primitiveCopyAnnotationData(bool& dest, const bool& src); template void primitiveCopyAnnotationData(int& dest, const int& src); +template void primitiveCopyAnnotationData(int64_t& dest, const int64_t& src); template void primitiveCopyAnnotationData(double& dest, const double& src); 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) { - dest.copyAnnotationData(src); -} - -template <> -void primitiveCopyAnnotationData(Vec3f& dest, const Vec3f& src) { - dest.copyAnnotationData(src); -} -template <> -void primitiveCopyAnnotationData(Vec4f& dest, const Vec4f& src) { - dest.copyAnnotationData(src); -} - -template <> -void primitiveCopyAnnotationData(Vec2i& dest, const Vec2i& src) { - dest.copyAnnotationData(src); -} - -template <> -void primitiveCopyAnnotationData(Vec3i& dest, const Vec3i& src) { - dest.copyAnnotationData(src); -} -template <> -void primitiveCopyAnnotationData(Vec4i& dest, const Vec4i& src) { - dest.copyAnnotationData(src); -} - } // namespace raco::data_storage \ No newline at end of file diff --git a/datamodel/libDataStorage/tests/CMakeLists.txt b/datamodel/libDataStorage/tests/CMakeLists.txt index 23de20db..339a82b6 100644 --- a/datamodel/libDataStorage/tests/CMakeLists.txt +++ b/datamodel/libDataStorage/tests/CMakeLists.txt @@ -10,7 +10,6 @@ If a copy of the MPL was not distributed with this file, You can obtain one at h set(TEST_SOURCES Value_test.cpp Property_test.cpp - Annotation_test.cpp StructTypes.h ) diff --git a/datamodel/libDataStorage/tests/Property_test.cpp b/datamodel/libDataStorage/tests/Property_test.cpp index 17203a2e..46886cc7 100644 --- a/datamodel/libDataStorage/tests/Property_test.cpp +++ b/datamodel/libDataStorage/tests/Property_test.cpp @@ -7,7 +7,6 @@ * 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 "data_storage/BasicTypes.h" #include "data_storage/Value.h" #include "StructTypes.h" @@ -41,42 +40,6 @@ TEST(PropertyTest, Scalar) u = 3.0; } -TEST(PropertyTest, Vec3f) -{ - Property v { Vec3f { 0.0 } }; - - v->x = 2.0; - EXPECT_EQ(*(v->x), 2.0); - - double a = *v->x; - EXPECT_EQ(a, 2.0); - - ValueBase* vp = &v; - - EXPECT_EQ(*vp->as().x, 2.0); - EXPECT_THROW(vp->as(), std::runtime_error); -} - -TEST(PropertyTest, clone) { - Property p_int_hidden{42, {"Name"}}; - auto pihc = p_int_hidden.clone(nullptr); - EXPECT_EQ(pihc->asInt(), *p_int_hidden); - - Property> pfr { 2.0, {1, 3}}; - auto pfrc = pfr.clone(nullptr); - EXPECT_EQ(pfrc->asDouble(), *pfr); - - auto range = pfr.query>(); - auto range_clone = pfrc->query>(); - - EXPECT_TRUE(range); - EXPECT_TRUE(range_clone); - - EXPECT_EQ(*range->min_, *range_clone->min_); - EXPECT_EQ(*range->max_, *range_clone->max_); - -} - TEST(PropertyTest, Struct) { SimpleStruct s; @@ -92,8 +55,8 @@ TEST(PropertyTest, Struct) { EXPECT_THROW(vs.asBool(), std::runtime_error); EXPECT_THROW(vs.as(), std::runtime_error); - EXPECT_THROW(vs.asVec3f(), std::runtime_error); - EXPECT_THROW(vs.as(), std::runtime_error); + EXPECT_THROW(vs.asTable(), std::runtime_error); + EXPECT_THROW(vs.as
(), std::runtime_error); ReflectionInterface& intf = vs.getSubstructure(); EXPECT_EQ(intf.size(), 2); diff --git a/datamodel/libDataStorage/tests/StructTypes.h b/datamodel/libDataStorage/tests/StructTypes.h index 04ff469c..71352456 100644 --- a/datamodel/libDataStorage/tests/StructTypes.h +++ b/datamodel/libDataStorage/tests/StructTypes.h @@ -9,14 +9,12 @@ */ #pragma once -#include "data_storage/BasicAnnotations.h" -#include "data_storage/BasicTypes.h" #include "data_storage/Table.h" #include "data_storage/Value.h" using namespace raco::data_storage; -class SimpleStruct : public ClassWithReflectedMembers { +class SimpleStruct : public StructBase { public: static inline const TypeDescriptor typeDescription = { "SimpleStruct", false }; TypeDescriptor const& getTypeDescription() const override { @@ -26,9 +24,9 @@ class SimpleStruct : public ClassWithReflectedMembers { return true; } - SimpleStruct() : ClassWithReflectedMembers(getProperties()) {} + SimpleStruct() : StructBase(getProperties()) {} - SimpleStruct(const SimpleStruct& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), bb(other.bb), dd(other.dd) {} + SimpleStruct(const SimpleStruct& other, std::function* translateRef = nullptr) : StructBase(getProperties()), bb(other.bb), dd(other.dd) {} SimpleStruct& operator=(const SimpleStruct& other) { bb = other.bb; @@ -49,7 +47,7 @@ class SimpleStruct : public ClassWithReflectedMembers { Value dd{1.5}; }; -class AltStruct : public ClassWithReflectedMembers { +class AltStruct : public StructBase { public: static inline const TypeDescriptor typeDescription = { "AltStruct", false }; TypeDescriptor const& getTypeDescription() const override { @@ -59,9 +57,9 @@ class AltStruct : public ClassWithReflectedMembers { return true; } - AltStruct() : ClassWithReflectedMembers(getProperties()) {} + AltStruct() : StructBase(getProperties()) {} - AltStruct(const AltStruct& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), bb(other.bb), dd(other.dd) {} + AltStruct(const AltStruct& other, std::function* translateRef = nullptr) : StructBase(getProperties()), bb(other.bb), dd(other.dd) {} AltStruct& operator=(const AltStruct& other) { bb = other.bb; diff --git a/datamodel/libDataStorage/tests/Value_test.cpp b/datamodel/libDataStorage/tests/Value_test.cpp index aedb9de2..fe50e02f 100644 --- a/datamodel/libDataStorage/tests/Value_test.cpp +++ b/datamodel/libDataStorage/tests/Value_test.cpp @@ -8,8 +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 "data_storage/Value.h" -#include "data_storage/BasicAnnotations.h" -#include "data_storage/BasicTypes.h" #include "data_storage/Table.h" #include "StructTypes.h" @@ -19,10 +17,6 @@ using namespace raco::data_storage; -TEST(Vec3f, has3Children) { - Vec3f underTest{0.0, 0.0, 0.0, 0.0}; - EXPECT_EQ(underTest.size(), 3); -} TEST(ValueTest, Scalar) { @@ -110,28 +104,6 @@ TEST(ValueTest, Table_nested_struct) { EXPECT_EQ((*tu)["struct"]->getSubstructure().get("double")->asDouble(), 2.0); } -TEST(ValueTest, Vec3f) { - Value v; - - v->x = 2.0; - EXPECT_EQ(*(v->x), 2.0); - - double a = *v->x; - EXPECT_EQ(a, 2.0); - - Vec3f u { 3.0 }; - v = u; - EXPECT_EQ(*(v->x), 3.0); - EXPECT_EQ(*(v->y), 3.0); - EXPECT_EQ(*(v->z), 3.0); - - - ValueBase* vp = &v; - - EXPECT_EQ(*vp->as().x, 3.0); - EXPECT_THROW(vp->as(), std::runtime_error); - -} TEST(ValueTest, clone) { Value vint {23}; @@ -161,8 +133,8 @@ TEST(ValueTest, Struct) { EXPECT_THROW(vs.asBool(), std::runtime_error); EXPECT_THROW(vs.as(), std::runtime_error); - EXPECT_THROW(vs.asVec3f(), std::runtime_error); - EXPECT_THROW(vs.as(), std::runtime_error); + EXPECT_THROW(vs.asTable(), std::runtime_error); + EXPECT_THROW(vs.as
(), std::runtime_error); ReflectionInterface& intf = vs.getSubstructure(); EXPECT_EQ(intf.size(), 2); @@ -178,7 +150,7 @@ TEST(ValueTest, Struct) { vs = cvs; EXPECT_EQ(*vs->dd, 2.0); - Value v; + Value v; EXPECT_THROW(vs = v, std::runtime_error); EXPECT_THROW(vs = va, std::runtime_error); diff --git a/datamodel/libTesting/include/testing/MockUserTypes.h b/datamodel/libTesting/include/testing/MockUserTypes.h index 10941dc0..c665cd80 100644 --- a/datamodel/libTesting/include/testing/MockUserTypes.h +++ b/datamodel/libTesting/include/testing/MockUserTypes.h @@ -10,7 +10,7 @@ #pragma once #include "core/EditorObject.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include @@ -33,7 +33,7 @@ class MockLuaScript : public EditorObject { auto s = luaInputs_->addProperty("in_struct", PrimitiveType::Table); s->asTable().addProperty("foo", PrimitiveType::Double); - s->asTable().addProperty("bar", PrimitiveType::Vec3f); + s->asTable().addProperty("bar", std::make_unique>()); auto a = luaInputs_->addProperty("in_array_double", PrimitiveType::Table); a->asTable().addProperty(std::string(), PrimitiveType::Double); @@ -41,17 +41,17 @@ class MockLuaScript : public EditorObject { a->asTable().addProperty(std::string(), PrimitiveType::Double); auto av = luaInputs_->addProperty("in_array_vec3f", PrimitiveType::Table); - av->asTable().addProperty(std::string(), PrimitiveType::Vec3f); - av->asTable().addProperty(std::string(), PrimitiveType::Vec3f); - av->asTable().addProperty(std::string(), PrimitiveType::Vec3f); + av->asTable().addProperty(std::string(), new Value()); + av->asTable().addProperty(std::string(), new Value()); + av->asTable().addProperty(std::string(), new Value()); auto as = luaInputs_->addProperty("in_array_struct", PrimitiveType::Table); auto as0 = as->asTable().addProperty(std::string(), PrimitiveType::Table); as0->asTable().addProperty("foo", PrimitiveType::Double); - as0->asTable().addProperty("bar", PrimitiveType::Vec3f); + as0->asTable().addProperty("bar", new Value()); auto as1 = as->asTable().addProperty(std::string(), PrimitiveType::Table); as1->asTable().addProperty("foo", PrimitiveType::Double); - as1->asTable().addProperty("bar", PrimitiveType::Vec3f); + as1->asTable().addProperty("bar", new Value()); } void fillPropertyDescription() { @@ -64,7 +64,7 @@ class MockLuaScript : public EditorObject { }; -class StructWithRef : public ClassWithReflectedMembers { +class StructWithRef : public StructBase { public: static inline const TypeDescriptor typeDescription = {"StructWithRef", false}; TypeDescriptor const& getTypeDescription() const override { @@ -74,9 +74,9 @@ class StructWithRef : public ClassWithReflectedMembers { return true; } - StructWithRef() : ClassWithReflectedMembers(getProperties()) {} + StructWithRef() : StructBase(getProperties()) {} - StructWithRef(const StructWithRef& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()) { + StructWithRef(const StructWithRef& other, std::function* translateRef = nullptr) : StructBase(getProperties()) { if (translateRef) { ref = (*translateRef)(*other.ref); } else { diff --git a/datamodel/libUserTypes/include/user_types/BaseCamera.h b/datamodel/libUserTypes/include/user_types/BaseCamera.h index 14465388..66a422a1 100644 --- a/datamodel/libUserTypes/include/user_types/BaseCamera.h +++ b/datamodel/libUserTypes/include/user_types/BaseCamera.h @@ -14,7 +14,7 @@ namespace raco::user_types { -class CameraViewport : public ClassWithReflectedMembers { +class CameraViewport : public StructBase { public: static inline const TypeDescriptor typeDescription = {"CameraViewport", false}; TypeDescriptor const& getTypeDescription() const override { @@ -24,10 +24,10 @@ class CameraViewport : public ClassWithReflectedMembers { return true; } - CameraViewport() : ClassWithReflectedMembers(getProperties()) {} + CameraViewport() : StructBase(getProperties()) {} CameraViewport(const CameraViewport& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), + : StructBase(getProperties()), offsetX_(other.offsetX_), offsetY_(other.offsetY_), width_(other.width_), diff --git a/datamodel/libUserTypes/include/user_types/BaseObject.h b/datamodel/libUserTypes/include/user_types/BaseObject.h index 7cdad24a..0606373d 100644 --- a/datamodel/libUserTypes/include/user_types/BaseObject.h +++ b/datamodel/libUserTypes/include/user_types/BaseObject.h @@ -9,8 +9,8 @@ */ #pragma once -#include "data_storage/BasicAnnotations.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicAnnotations.h" +#include "core/BasicTypes.h" #include "core/CoreAnnotations.h" #include "core/EditorObject.h" #include "core/FileChangeCallback.h" diff --git a/datamodel/libUserTypes/include/user_types/CubeMap.h b/datamodel/libUserTypes/include/user_types/CubeMap.h index aaf7acc3..4a91e969 100644 --- a/datamodel/libUserTypes/include/user_types/CubeMap.h +++ b/datamodel/libUserTypes/include/user_types/CubeMap.h @@ -22,12 +22,32 @@ class CubeMap : public BaseTexture { CubeMap(CubeMap const& other) : BaseTexture(other) + , generateMipmaps_(other.generateMipmaps_) + , mipmapLevel_(other.mipmapLevel_) , uriFront_(other.uriFront_) , uriBack_(other.uriBack_) , uriLeft_(other.uriLeft_) , uriRight_(other.uriRight_) , uriTop_(other.uriTop_) , uriBottom_(other.uriBottom_) + , level2uriFront_(other.level2uriFront_) + , level2uriBack_(other.level2uriBack_) + , level2uriLeft_(other.level2uriLeft_) + , level2uriRight_(other.level2uriRight_) + , level2uriTop_(other.level2uriTop_) + , level2uriBottom_(other.level2uriBottom_) + , level3uriFront_(other.level3uriFront_) + , level3uriBack_(other.level3uriBack_) + , level3uriLeft_(other.level3uriLeft_) + , level3uriRight_(other.level3uriRight_) + , level3uriTop_(other.level3uriTop_) + , level3uriBottom_(other.level3uriBottom_) + , level4uriFront_(other.level4uriFront_) + , level4uriBack_(other.level4uriBack_) + , level4uriLeft_(other.level4uriLeft_) + , level4uriRight_(other.level4uriRight_) + , level4uriTop_(other.level4uriTop_) + , level4uriBottom_(other.level4uriBottom_) { fillPropertyDescription(); } @@ -37,34 +57,72 @@ class CubeMap : public BaseTexture { } void fillPropertyDescription() { - properties_.emplace_back("uriFront", &uriFront_); - properties_.emplace_back("uriBack", &uriBack_); - properties_.emplace_back("uriLeft", &uriLeft_); + properties_.emplace_back("generateMipmaps", &generateMipmaps_); + properties_.emplace_back("mipmapLevel", &mipmapLevel_); properties_.emplace_back("uriRight", &uriRight_); + properties_.emplace_back("uriLeft", &uriLeft_); properties_.emplace_back("uriTop", &uriTop_); properties_.emplace_back("uriBottom", &uriBottom_); + properties_.emplace_back("uriFront", &uriFront_); + properties_.emplace_back("uriBack", &uriBack_); + properties_.emplace_back("level2uriRight", &level2uriRight_); + properties_.emplace_back("level2uriLeft", &level2uriLeft_); + properties_.emplace_back("level2uriTop", &level2uriTop_); + properties_.emplace_back("level2uriBottom", &level2uriBottom_); + properties_.emplace_back("level2uriFront", &level2uriFront_); + properties_.emplace_back("level2uriBack", &level2uriBack_); + properties_.emplace_back("level3uriRight", &level3uriRight_); + properties_.emplace_back("level3uriLeft", &level3uriLeft_); + properties_.emplace_back("level3uriTop", &level3uriTop_); + properties_.emplace_back("level3uriBottom", &level3uriBottom_); + properties_.emplace_back("level3uriFront", &level3uriFront_); + properties_.emplace_back("level3uriBack", &level3uriBack_); + properties_.emplace_back("level4uriRight", &level4uriRight_); + properties_.emplace_back("level4uriLeft", &level4uriLeft_); + properties_.emplace_back("level4uriTop", &level4uriTop_); + properties_.emplace_back("level4uriBottom", &level4uriBottom_); + properties_.emplace_back("level4uriFront", &level4uriFront_); + properties_.emplace_back("level4uriBack", &level4uriBack_); } + void onAfterValueChanged(BaseContext& context, ValueHandle const& value) override; void updateFromExternalFile(BaseContext& context) override; - Property uriFront_{ - std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"URI front"}}; - - Property uriBack_{ - std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"URI back"}}; - - Property uriLeft_{ - std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"URI left"}}; - - Property uriRight_{ - std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"URI right"}}; - - Property uriTop_{ - std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"URI top"}}; - - Property uriBottom_{ - std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"URI bottom"}}; - + Property> mipmapLevel_{1, {"Mipmap Level"}, {1, 4}}; + + Property generateMipmaps_{false, DisplayNameAnnotation("Automatically Generate Mipmaps")}; + + Property uriFront_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 1 URI +Z"}}; + Property uriBack_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 1 URI -Z"}}; + Property uriLeft_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 1 URI -X"}}; + Property uriRight_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 1 URI +X"}}; + Property uriTop_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 1 URI +Y"}}; + Property uriBottom_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 1 URI -Y"}}; + + Property level2uriFront_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 2 URI +Z"}}; + Property level2uriBack_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 2 URI -Z"}}; + Property level2uriLeft_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 2 URI -X"}}; + Property level2uriRight_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 2 URI +X"}}; + Property level2uriTop_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 2 URI +Y"}}; + Property level2uriBottom_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 2 URI -Y"}}; + + Property level3uriFront_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 3 URI +Z"}}; + Property level3uriBack_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 3 URI -Z"}}; + Property level3uriLeft_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 3 URI -X"}}; + Property level3uriRight_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 3 URI +X"}}; + Property level3uriTop_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 3 URI +Y"}}; + Property level3uriBottom_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 3 URI -Y"}}; + + Property level4uriFront_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 4 URI +Z"}}; + Property level4uriBack_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 4 URI -Z"}}; + Property level4uriLeft_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 4 URI -X"}}; + Property level4uriRight_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 4 URI +X"}}; + Property level4uriTop_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 4 URI +Y"}}; + Property level4uriBottom_{std::string{}, {"Image files(*.png)"}, DisplayNameAnnotation{"Level 4 URI -Y"}}; + +private: + void validateURIs(BaseContext& context); + void validateMipmapLevel(BaseContext& context); }; using SCubeMap = std::shared_ptr; diff --git a/datamodel/libUserTypes/include/user_types/Material.h b/datamodel/libUserTypes/include/user_types/Material.h index cb59b2bc..fed031ee 100644 --- a/datamodel/libUserTypes/include/user_types/Material.h +++ b/datamodel/libUserTypes/include/user_types/Material.h @@ -18,7 +18,7 @@ namespace raco::user_types { -class BlendOptions : public ClassWithReflectedMembers { +class BlendOptions : public StructBase { public: static inline const TypeDescriptor typeDescription = {"BlendOptions", false}; TypeDescriptor const& getTypeDescription() const override { @@ -28,9 +28,9 @@ class BlendOptions : public ClassWithReflectedMembers { return true; } - BlendOptions() : ClassWithReflectedMembers(getProperties()) {} + BlendOptions() : StructBase(getProperties()) {} - BlendOptions(const BlendOptions& other, std::function* translateRef = nullptr) : ClassWithReflectedMembers(getProperties()), + BlendOptions(const BlendOptions& other, std::function* translateRef = nullptr) : StructBase(getProperties()), blendOperationColor_(other.blendOperationColor_), blendOperationAlpha_(other.blendOperationAlpha_), blendFactorSrcColor_(other.blendFactorSrcColor_), diff --git a/datamodel/libUserTypes/include/user_types/Mesh.h b/datamodel/libUserTypes/include/user_types/Mesh.h index 7649819a..c80470d9 100644 --- a/datamodel/libUserTypes/include/user_types/Mesh.h +++ b/datamodel/libUserTypes/include/user_types/Mesh.h @@ -11,7 +11,7 @@ #include "user_types/BaseObject.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "core/MeshCacheInterface.h" diff --git a/datamodel/libUserTypes/include/user_types/OrthographicCamera.h b/datamodel/libUserTypes/include/user_types/OrthographicCamera.h index 2f12d754..b9f30bfe 100644 --- a/datamodel/libUserTypes/include/user_types/OrthographicCamera.h +++ b/datamodel/libUserTypes/include/user_types/OrthographicCamera.h @@ -13,7 +13,7 @@ namespace raco::user_types { -class OrthographicFrustum : public ClassWithReflectedMembers { +class OrthographicFrustum : public StructBase { public: static inline const TypeDescriptor typeDescription = {"OrthographicFrustum", false}; TypeDescriptor const& getTypeDescription() const override { @@ -23,10 +23,10 @@ class OrthographicFrustum : public ClassWithReflectedMembers { return true; } - OrthographicFrustum() : ClassWithReflectedMembers(getProperties()) {} + OrthographicFrustum() : StructBase(getProperties()) {} OrthographicFrustum(const OrthographicFrustum& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), + : StructBase(getProperties()), near_(other.near_), far_(other.far_), left_(other.left_), diff --git a/datamodel/libUserTypes/include/user_types/PerspectiveCamera.h b/datamodel/libUserTypes/include/user_types/PerspectiveCamera.h index 73aa0814..969bb428 100644 --- a/datamodel/libUserTypes/include/user_types/PerspectiveCamera.h +++ b/datamodel/libUserTypes/include/user_types/PerspectiveCamera.h @@ -13,7 +13,7 @@ namespace raco::user_types { -class PerspectiveFrustum : public ClassWithReflectedMembers { +class PerspectiveFrustum : public StructBase { public: static inline const TypeDescriptor typeDescription = {"PerspectiveFrustum", false}; TypeDescriptor const& getTypeDescription() const override { @@ -23,10 +23,10 @@ class PerspectiveFrustum : public ClassWithReflectedMembers { return true; } - PerspectiveFrustum() : ClassWithReflectedMembers(getProperties()) {} + PerspectiveFrustum() : StructBase(getProperties()) {} PerspectiveFrustum(const PerspectiveFrustum& other, std::function* translateRef = nullptr) - : ClassWithReflectedMembers(getProperties()), + : StructBase(getProperties()), near_(other.near_), far_(other.far_), fov_(other.fov_), @@ -58,7 +58,7 @@ class PerspectiveFrustum : public ClassWithReflectedMembers { Property, LinkEndAnnotation> near_{0.1, DisplayNameAnnotation("Near Plane"), RangeAnnotation(0.1, 1.0), {}}; Property, LinkEndAnnotation> far_{1000.0, DisplayNameAnnotation("Far Plane"), RangeAnnotation(100.0, 10000.0), {}}; - Property, LinkEndAnnotation> fov_{35.0, DisplayNameAnnotation("Field of View"), RangeAnnotation(10.0, 120.0), {}}; + Property, LinkEndAnnotation> fov_{35.0, DisplayNameAnnotation("Vert. Field of View"), RangeAnnotation(10.0, 120.0), {}}; Property, LinkEndAnnotation> aspect_{1440.0 / 720.0, DisplayNameAnnotation("Aspect"), RangeAnnotation(0.5, 4.0), {}}; }; diff --git a/datamodel/libUserTypes/include/user_types/PrefabInstance.h b/datamodel/libUserTypes/include/user_types/PrefabInstance.h index 6fa357ac..6e09080f 100644 --- a/datamodel/libUserTypes/include/user_types/PrefabInstance.h +++ b/datamodel/libUserTypes/include/user_types/PrefabInstance.h @@ -9,7 +9,7 @@ */ #pragma once -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "user_types/Node.h" #include "user_types/Prefab.h" diff --git a/datamodel/libUserTypes/include/user_types/SyncTableWithEngineInterface.h b/datamodel/libUserTypes/include/user_types/SyncTableWithEngineInterface.h index 325104ca..38368ec6 100644 --- a/datamodel/libUserTypes/include/user_types/SyncTableWithEngineInterface.h +++ b/datamodel/libUserTypes/include/user_types/SyncTableWithEngineInterface.h @@ -12,7 +12,7 @@ #include "core/Context.h" #include "core/EngineInterface.h" #include "core/Handles.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include "data_storage/Table.h" namespace raco::user_types { diff --git a/datamodel/libUserTypes/include/user_types/UserObjectFactory.h b/datamodel/libUserTypes/include/user_types/UserObjectFactory.h index 8757d66b..8698f451 100644 --- a/datamodel/libUserTypes/include/user_types/UserObjectFactory.h +++ b/datamodel/libUserTypes/include/user_types/UserObjectFactory.h @@ -55,6 +55,7 @@ class UserObjectFactory : public raco::core::UserObjectFactoryInterface { Property, Property, + Property, Property, Property, Property, @@ -71,6 +72,7 @@ class UserObjectFactory : public raco::core::UserObjectFactoryInterface { Property, Property, + Property, Property, Property, Property, @@ -87,6 +89,7 @@ class UserObjectFactory : public raco::core::UserObjectFactoryInterface { Property, Property, + Property, Property, Property, Property, diff --git a/datamodel/libUserTypes/src/CubeMap.cpp b/datamodel/libUserTypes/src/CubeMap.cpp index 8d7a84c5..df373e12 100644 --- a/datamodel/libUserTypes/src/CubeMap.cpp +++ b/datamodel/libUserTypes/src/CubeMap.cpp @@ -18,9 +18,48 @@ namespace raco::user_types { +void CubeMap::onAfterValueChanged(BaseContext& context, ValueHandle const& value) { + BaseObject::onAfterValueChanged(context, value); + if (value.isRefToProp(&CubeMap::mipmapLevel_)) { + validateURIs(context); + validateMipmapLevel(context); + } else if (value.isRefToProp(&CubeMap::generateMipmaps_)) { + validateMipmapLevel(context); + } +} void CubeMap::updateFromExternalFile(BaseContext& context) { - context.errors().removeError({shared_from_this()}); + validateURIs(context); + + context.changeMultiplexer().recordPreviewDirty(shared_from_this()); +} + +void CubeMap::validateURIs(BaseContext& context) { + context.errors().removeError({shared_from_this(), &CubeMap::uriFront_}); + context.errors().removeError({shared_from_this(), &CubeMap::uriBack_}); + context.errors().removeError({shared_from_this(), &CubeMap::uriLeft_}); + context.errors().removeError({shared_from_this(), &CubeMap::uriRight_}); + context.errors().removeError({shared_from_this(), &CubeMap::uriTop_}); + context.errors().removeError({shared_from_this(), &CubeMap::uriBottom_}); + context.errors().removeError({shared_from_this(), &CubeMap::level2uriFront_}); + context.errors().removeError({shared_from_this(), &CubeMap::level2uriBack_}); + context.errors().removeError({shared_from_this(), &CubeMap::level2uriLeft_}); + context.errors().removeError({shared_from_this(), &CubeMap::level2uriRight_}); + context.errors().removeError({shared_from_this(), &CubeMap::level2uriTop_}); + context.errors().removeError({shared_from_this(), &CubeMap::level2uriBottom_}); + context.errors().removeError({shared_from_this(), &CubeMap::level3uriFront_}); + context.errors().removeError({shared_from_this(), &CubeMap::level3uriBack_}); + context.errors().removeError({shared_from_this(), &CubeMap::level3uriLeft_}); + context.errors().removeError({shared_from_this(), &CubeMap::level3uriRight_}); + context.errors().removeError({shared_from_this(), &CubeMap::level3uriTop_}); + context.errors().removeError({shared_from_this(), &CubeMap::level3uriBottom_}); + context.errors().removeError({shared_from_this(), &CubeMap::level4uriFront_}); + context.errors().removeError({shared_from_this(), &CubeMap::level4uriBack_}); + context.errors().removeError({shared_from_this(), &CubeMap::level4uriLeft_}); + context.errors().removeError({shared_from_this(), &CubeMap::level4uriRight_}); + context.errors().removeError({shared_from_this(), &CubeMap::level4uriTop_}); + context.errors().removeError({shared_from_this(), &CubeMap::level4uriBottom_}); + validateURI(context, {shared_from_this(), &CubeMap::uriFront_}); validateURI(context, {shared_from_this(), &CubeMap::uriBack_}); validateURI(context, {shared_from_this(), &CubeMap::uriLeft_}); @@ -28,7 +67,45 @@ void CubeMap::updateFromExternalFile(BaseContext& context) { validateURI(context, {shared_from_this(), &CubeMap::uriTop_}); validateURI(context, {shared_from_this(), &CubeMap::uriBottom_}); - context.changeMultiplexer().recordPreviewDirty(shared_from_this()); + if (*mipmapLevel_ > 1) { + validateURI(context, {shared_from_this(), &CubeMap::level2uriFront_}); + validateURI(context, {shared_from_this(), &CubeMap::level2uriBack_}); + validateURI(context, {shared_from_this(), &CubeMap::level2uriLeft_}); + validateURI(context, {shared_from_this(), &CubeMap::level2uriRight_}); + validateURI(context, {shared_from_this(), &CubeMap::level2uriTop_}); + validateURI(context, {shared_from_this(), &CubeMap::level2uriBottom_}); + } + + if (*mipmapLevel_ > 2) { + validateURI(context, {shared_from_this(), &CubeMap::level3uriFront_}); + validateURI(context, {shared_from_this(), &CubeMap::level3uriBack_}); + validateURI(context, {shared_from_this(), &CubeMap::level3uriLeft_}); + validateURI(context, {shared_from_this(), &CubeMap::level3uriRight_}); + validateURI(context, {shared_from_this(), &CubeMap::level3uriTop_}); + validateURI(context, {shared_from_this(), &CubeMap::level3uriBottom_}); + } + + if (*mipmapLevel_ > 3) { + validateURI(context, {shared_from_this(), &CubeMap::level4uriFront_}); + validateURI(context, {shared_from_this(), &CubeMap::level4uriBack_}); + validateURI(context, {shared_from_this(), &CubeMap::level4uriLeft_}); + validateURI(context, {shared_from_this(), &CubeMap::level4uriRight_}); + validateURI(context, {shared_from_this(), &CubeMap::level4uriTop_}); + validateURI(context, {shared_from_this(), &CubeMap::level4uriBottom_}); + } +} + +void CubeMap::validateMipmapLevel(BaseContext& context) { + auto mipmapLevelValue = ValueHandle{shared_from_this(), &raco::user_types::CubeMap::mipmapLevel_}; + context.errors().removeError(mipmapLevelValue); + + if (*mipmapLevel_ < 1 || *mipmapLevel_ > 4) { + context.errors().addError(core::ErrorCategory::GENERAL, core::ErrorLevel::ERROR, mipmapLevelValue, + "Invalid mipmap level - only mipmap levels 1 to 4 are allowed."); + } else if (*generateMipmaps_ && *mipmapLevel_ != 1) { + context.errors().addError(core::ErrorCategory::GENERAL, core::ErrorLevel::WARNING, mipmapLevelValue, + "Mipmap level larger than 1 specified while auto-generation flag is on - Ramses will ignore the auto-generation flag and still try to use the manually specified URIs."); + } } } // namespace raco::user_types diff --git a/datamodel/libUserTypes/src/SyncTableWithEngineInterface.cpp b/datamodel/libUserTypes/src/SyncTableWithEngineInterface.cpp index e9150501..63f76dbe 100644 --- a/datamodel/libUserTypes/src/SyncTableWithEngineInterface.cpp +++ b/datamodel/libUserTypes/src/SyncTableWithEngineInterface.cpp @@ -37,6 +37,9 @@ raco::data_storage::ValueBase* createDynamicProperty(EnginePrimitive type) { case EnginePrimitive::UInt32: return UserObjectFactory::staticCreateProperty({}, {type}, {Args()}...); break; + case EnginePrimitive::Int64: + return UserObjectFactory::staticCreateProperty({}, {type}, {Args()}...); + break; case EnginePrimitive::Double: return UserObjectFactory::staticCreateProperty({}, {type}, {Args()}...); break; diff --git a/datamodel/libUserTypes/src/UserObjectFactory.cpp b/datamodel/libUserTypes/src/UserObjectFactory.cpp index 09d4f166..18b77df6 100644 --- a/datamodel/libUserTypes/src/UserObjectFactory.cpp +++ b/datamodel/libUserTypes/src/UserObjectFactory.cpp @@ -104,7 +104,14 @@ UserObjectFactory::UserObjectFactory() { ExternalReferenceAnnotation >(); - structTypes_ = makeStructTypeMap + +using namespace raco::core; +using namespace raco::user_types; + +class CubeMapTest : public TestEnvironmentCore {}; + +TEST_F(CubeMapTest, invalidLevels) { + auto cubeMap{commandInterface.createObject(CubeMap::typeDescription.typeName)}; + + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, -1); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 1); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 5); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 4); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); +} + +TEST_F(CubeMapTest, levelOtherThanOneWhenGenerationFlagIsActivated) { + auto cubeMap{commandInterface.createObject(CubeMap::typeDescription.typeName)}; + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}, 2); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::generateMipmaps_}, true); + ASSERT_TRUE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); + ASSERT_EQ(commandInterface.errors().getError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_}).level(), raco::core::ErrorLevel::WARNING); + + commandInterface.set({cubeMap, &raco::user_types::CubeMap::generateMipmaps_}, false); + ASSERT_FALSE(commandInterface.errors().hasError({cubeMap, &raco::user_types::CubeMap::mipmapLevel_})); +} diff --git a/gui/libCommonWidgets/CMakeLists.txt b/gui/libCommonWidgets/CMakeLists.txt index 7669f227..dedcaa1e 100644 --- a/gui/libCommonWidgets/CMakeLists.txt +++ b/gui/libCommonWidgets/CMakeLists.txt @@ -15,7 +15,10 @@ add_library(libCommonWidgets include/common_widgets/ErrorView.h src/ErrorView.cpp include/common_widgets/ExportDialog.h src/ExportDialog.cpp include/common_widgets/LinkStartSearchView.h src/LinkStartSearchView.cpp - include/common_widgets/LogWidget.h src/LogWidget.cpp + include/common_widgets/log_model/LogViewModel.h src/log_model/LogViewModel.cpp + include/common_widgets/log_model/LogViewSink.h src/log_model/LogViewSink.cpp + include/common_widgets/log_model/LogViewSortFilterProxyModel.h src/log_model/LogViewSortFilterProxyModel.cpp + include/common_widgets/LogView.h src/LogView.cpp include/common_widgets/MeshAssetImportDialog.h src/MeshAssetImportDialog.cpp include/common_widgets/NoContentMarginsLayout.h include/common_widgets/PreferencesView.h src/PreferencesView.cpp diff --git a/gui/libCommonWidgets/include/common_widgets/ErrorView.h b/gui/libCommonWidgets/include/common_widgets/ErrorView.h index 15ce7793..39bc6de6 100644 --- a/gui/libCommonWidgets/include/common_widgets/ErrorView.h +++ b/gui/libCommonWidgets/include/common_widgets/ErrorView.h @@ -26,6 +26,8 @@ class CommandInterface; namespace raco::common_widgets { +class LogViewModel; + class ErrorView : public QWidget { Q_OBJECT @@ -38,7 +40,7 @@ class ErrorView : public QWidget { COLUMN_COUNT }; - explicit ErrorView(raco::core::CommandInterface *commandInterface, raco::components::SDataChangeDispatcher dispatcher, QWidget *parent = nullptr); + explicit ErrorView(raco::core::CommandInterface *commandInterface, raco::components::SDataChangeDispatcher dispatcher, bool showFilterLayout, LogViewModel *logViewModel, QWidget *parent = nullptr); Q_SIGNALS: void objectSelectionRequested(const QString &objectID); @@ -55,16 +57,18 @@ class ErrorView : public QWidget { components::Subscription objNameChangeSubscription_; components::Subscription objChildrenChangeSubscription_; QTableView *tableView_; - QCheckBox *showWarningsCheckBox_; - QCheckBox *showErrorsCheckBox_; - QLabel *errorAmountLabel_; + QCheckBox *showWarningsCheckBox_ = nullptr; + QCheckBox *showErrorsCheckBox_ = nullptr; + QLabel *errorAmountLabel_ = nullptr; QStandardItemModel *tableModel_; QSortFilterProxyModel *proxyModel_; std::vector indexToObjID_; std::set objIDs_; + LogViewModel *logViewModel_; protected Q_SLOTS: void createCustomContextMenu(const QPoint &p); + void updateErrorAmountLabel(); }; } // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/include/common_widgets/ExportDialog.h b/gui/libCommonWidgets/include/common_widgets/ExportDialog.h index d974ddc4..53ffc7a0 100644 --- a/gui/libCommonWidgets/include/common_widgets/ExportDialog.h +++ b/gui/libCommonWidgets/include/common_widgets/ExportDialog.h @@ -17,10 +17,11 @@ #include namespace raco::common_widgets { +class LogViewModel; class ExportDialog final : public QDialog { public: - explicit ExportDialog(const application::RaCoApplication* application, QWidget* parent); + explicit ExportDialog(application::RaCoApplication* application, LogViewModel* logViewModel, QWidget* parent); private: Q_SLOT void exportProject(); @@ -35,7 +36,7 @@ class ExportDialog final : public QDialog { bool hasErrors_; - const application::RaCoApplication* application_; + application::RaCoApplication* application_; }; } // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/include/common_widgets/LogView.h b/gui/libCommonWidgets/include/common_widgets/LogView.h new file mode 100644 index 00000000..cf68b587 --- /dev/null +++ b/gui/libCommonWidgets/include/common_widgets/LogView.h @@ -0,0 +1,49 @@ +/* + * 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 "common_widgets/log_model/LogViewSink.h" +#include "common_widgets/log_model/LogViewSortFilterProxyModel.h" + +#include +#include +#include +#include +#include +#include + +namespace raco::common_widgets { + +class LogView : public QWidget { + Q_OBJECT + +public: + explicit LogView(LogViewModel* model_, QWidget* parent = nullptr); + +public Q_SLOTS: + void updateWarningErrorLabel(); + void customMenuRequested(QPoint pos); + +protected: + QTableView* tableView_; + + bool event(QEvent* event) override; + void copySelectedRows(); + void keyPressEvent(QKeyEvent* event) override; + + +private: + LogViewModel* model_; + LogViewSortFilterProxyModel* proxyModel_; + QLabel* warningErrorLabel_; + +}; + +} // namespace raco::common_widgets \ No newline at end of file diff --git a/gui/libCommonWidgets/include/common_widgets/log_model/LogViewModel.h b/gui/libCommonWidgets/include/common_widgets/log_model/LogViewModel.h new file mode 100644 index 00000000..9c93d9c0 --- /dev/null +++ b/gui/libCommonWidgets/include/common_widgets/log_model/LogViewModel.h @@ -0,0 +1,87 @@ +/* + * 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 "log_system/log.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace raco::common_widgets { + +class LogViewSink; + +class LogViewModel : public QAbstractItemModel { + Q_OBJECT + +public: + enum ColumnIndex { + COLUMNINDEX_TIMESTAMP, + COLUMNINDEX_LEVEL, + COLUMNINDEX_CATEGORY, + COLUMNINDEX_MESSAGE, + COLUMN_COUNT + }; + + struct LogEntry { + QString timestamp_; + spdlog::level::level_enum level_; + QString category_; + QString message_; + QString fullLogMessageRaw_; + }; + + static QString textForLogLevel(spdlog::level::level_enum level); + static QColor colorForLogLevel(spdlog::level::level_enum level); + + explicit LogViewModel(QObject* parent = nullptr); + ~LogViewModel(); + + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + const std::vector& entries() const; + + int warningCount() const; + int errorCount() const; + + std::string getStringFromSelection(QItemSelection selection) const; + +Q_SIGNALS: + void entriesChanged(); + +public Q_SLOTS: + void addEntry(const LogViewModel::LogEntry& entry); + void clear(); + +private Q_SLOTS: + void flushNewEntries(); + +private: + const std::chrono::milliseconds FLUSH_TIMER_INTERVAL = std::chrono::milliseconds(100); + const int MAX_ENTRY_COUNT = 10000; + + std::shared_ptr sink_; + std::vector entries_; + std::vector entriesToAdd_; + QFont boldFont_; + QTimer* flushTimer_; +}; + +} // namespace raco::common_widgets \ No newline at end of file diff --git a/gui/libCommonWidgets/include/common_widgets/log_model/LogViewSink.h b/gui/libCommonWidgets/include/common_widgets/log_model/LogViewSink.h new file mode 100644 index 00000000..b7100e8d --- /dev/null +++ b/gui/libCommonWidgets/include/common_widgets/log_model/LogViewSink.h @@ -0,0 +1,34 @@ +/* + * 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 "common_widgets/log_model/LogViewModel.h" +#include "log_system/log.h" + +#include +#include +#include + +namespace raco::common_widgets { + +class LogViewModel; + +class LogViewSink final : public spdlog::sinks::base_sink { +public: + explicit LogViewSink(LogViewModel* model); + + void sink_it_(const spdlog::details::log_msg& msg) override; + void flush_() override; + +private: + LogViewModel* model_; +}; + +} // namespace raco::common_widgets \ No newline at end of file diff --git a/gui/libCommonWidgets/include/common_widgets/log_model/LogViewSortFilterProxyModel.h b/gui/libCommonWidgets/include/common_widgets/log_model/LogViewSortFilterProxyModel.h new file mode 100644 index 00000000..3646e708 --- /dev/null +++ b/gui/libCommonWidgets/include/common_widgets/log_model/LogViewSortFilterProxyModel.h @@ -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/. + */ +#pragma once + +#include + +#include +#include + +namespace raco::common_widgets { + +class LogViewSortFilterProxyModel : public QSortFilterProxyModel { +public: + LogViewSortFilterProxyModel(LogViewModel* model, QObject* parent); + void setFilterMinLogLevel(spdlog::level::level_enum filterMinLogLevel); + void setFilterCategory(QString filterCategory); + void setFilterMessage(QString filterMessage); + + int warningCount() const; + int errorCount() const; + + std::string getStringFromSelection(QItemSelection selection) const; + +protected: + LogViewModel* model_; + spdlog::level::level_enum filterMinLogLevel_; + QString filterCategory_; + QString filterMessage_; + + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + bool filterAcceptsEntry(const LogViewModel::LogEntry& entry) const; +}; + +} // namespace raco::common_widgets \ No newline at end of file diff --git a/gui/libCommonWidgets/src/ErrorView.cpp b/gui/libCommonWidgets/src/ErrorView.cpp index 9bec021b..d8089aa1 100644 --- a/gui/libCommonWidgets/src/ErrorView.cpp +++ b/gui/libCommonWidgets/src/ErrorView.cpp @@ -9,6 +9,7 @@ */ #include "common_widgets/ErrorView.h" +#include "common_widgets/log_model/LogViewModel.h" #include "common_widgets/NoContentMarginsLayout.h" #include "common_widgets/RaCoClipboard.h" #include "core/CommandInterface.h" @@ -67,7 +68,7 @@ class ErrorViewModel : public QStandardItemModel { namespace raco::common_widgets { -ErrorView::ErrorView(raco::core::CommandInterface* commandInterface, raco::components::SDataChangeDispatcher dispatcher, QWidget* parent) : QWidget{parent}, commandInterface_{commandInterface} { +ErrorView::ErrorView(raco::core::CommandInterface* commandInterface, raco::components::SDataChangeDispatcher dispatcher, bool showFilterLayout, LogViewModel* logViewModel, QWidget* parent) : QWidget{parent}, commandInterface_{commandInterface}, logViewModel_(logViewModel) { auto* errorViewLayout{new NoContentMarginsLayout{this}}; tableModel_ = new ErrorViewModel(this); @@ -97,28 +98,34 @@ ErrorView::ErrorView(raco::core::CommandInterface* commandInterface, raco::compo connect(tableView_, &QTableView::customContextMenuRequested, this, &ErrorView::createCustomContextMenu); errorViewLayout->addWidget(tableView_); - auto* filterLayout{new QHBoxLayout{this}}; - filterLayout->setContentsMargins(5, 0, 10, 0); - showWarningsCheckBox_ = new QCheckBox("Show Warnings", this); - showWarningsCheckBox_->setChecked(true); - connect(showWarningsCheckBox_, &QCheckBox::stateChanged, [this]() { - filterRows(); - }); - filterLayout->addWidget(showWarningsCheckBox_); + if (showFilterLayout) { + auto filterLayout = new QHBoxLayout{this}; + filterLayout->setContentsMargins(5, 0, 10, 0); + showWarningsCheckBox_ = new QCheckBox("Show Warnings", this); + showWarningsCheckBox_->setChecked(true); + connect(showWarningsCheckBox_, &QCheckBox::stateChanged, [this]() { + filterRows(); + }); + filterLayout->addWidget(showWarningsCheckBox_); - showErrorsCheckBox_ = new QCheckBox("Show Errors", this); - showErrorsCheckBox_->setChecked(true); - connect(showErrorsCheckBox_, &QCheckBox::stateChanged, [this]() { - filterRows(); - }); - filterLayout->addWidget(showErrorsCheckBox_); + showErrorsCheckBox_ = new QCheckBox("Show Errors", this); + showErrorsCheckBox_->setChecked(true); + connect(showErrorsCheckBox_, &QCheckBox::stateChanged, [this]() { + filterRows(); + }); + filterLayout->addWidget(showErrorsCheckBox_); + + filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); - filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); + errorAmountLabel_ = new QLabel(this); + filterLayout->addWidget(errorAmountLabel_); - errorAmountLabel_ = new QLabel(this); - filterLayout->addWidget(errorAmountLabel_); + errorViewLayout->addLayout(filterLayout); - errorViewLayout->addLayout(filterLayout); + if (logViewModel) { + connect(logViewModel, &LogViewModel::entriesChanged, this, &ErrorView::updateErrorAmountLabel); + } + } errorChangeSubscription_ = dispatcher->registerOnErrorChangedInScene([this]() { regenerateTable(); @@ -153,8 +160,8 @@ std::vector ErrorView::rowToVector(const QModelIndex& rowIndex) con } void ErrorView::filterRows() { - auto warningsVisible = showWarningsCheckBox_->isChecked(); - auto errorsVisible = showErrorsCheckBox_->isChecked(); + auto warningsVisible = !showWarningsCheckBox_ || showWarningsCheckBox_->isChecked(); + auto errorsVisible = !showErrorsCheckBox_ || showErrorsCheckBox_->isChecked(); const auto WARNING = errorLevelToString(raco::core::ErrorLevel::WARNING); for (auto row = 0; row < tableModel_->rowCount(); ++row) { @@ -200,12 +207,6 @@ void ErrorView::regenerateTable() { auto errorID = rootObj ? rootObj->objectID() : commandInterface_->project()->currentPath(); indexToObjID_.emplace_back(errorID); objIDs_.insert(errorID); - - if (error.level() == core::ErrorLevel::WARNING) { - ++warningAmount; - } else if (error.level() == core::ErrorLevel::ERROR) { - ++errorAmount; - } } } @@ -225,7 +226,7 @@ void ErrorView::regenerateTable() { } } - errorAmountLabel_->setText(QString::fromStdString(fmt::format("Warnings: {} Errors: {}", warningAmount, errorAmount))); + updateErrorAmountLabel(); filterRows(); } @@ -248,5 +249,39 @@ void ErrorView::createCustomContextMenu(const QPoint& p) { treeViewMenu->exec(tableView_->viewport()->mapToGlobal(p)); } +void ErrorView::updateErrorAmountLabel() { + if (errorAmountLabel_) { + + auto warningAmount = 0; + auto errorAmount = 0; + + const auto& errors = commandInterface_->errors(); + for (const auto& [errorValueHandle, error] : errors.getAllErrors()) { + if (error.level() <= core::ErrorLevel::INFORMATION) { + continue; + } + + auto rootObj = !errorValueHandle ? nullptr : errorValueHandle.rootObject(); + + if (!rootObj || (rootObj && !raco::core::Queries::isReadOnly(rootObj))) { + if (error.level() == core::ErrorLevel::WARNING) { + ++warningAmount; + } else if (error.level() == core::ErrorLevel::ERROR) { + ++errorAmount; + } + } + } + + std::string logErrorString = ""; + if (logViewModel_) { + auto logErrorAmount = logViewModel_->errorCount(); + if (logErrorAmount > 0) { + logErrorString = fmt::format(" (additional errors in log)"); + } + } + + errorAmountLabel_->setText(QString::fromStdString(fmt::format("Warnings: {} Errors: {}{}", warningAmount, errorAmount, logErrorString))); + } +} } // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/src/ExportDialog.cpp b/gui/libCommonWidgets/src/ExportDialog.cpp index 9b8e7115..15df7249 100644 --- a/gui/libCommonWidgets/src/ExportDialog.cpp +++ b/gui/libCommonWidgets/src/ExportDialog.cpp @@ -9,6 +9,7 @@ */ #include "common_widgets/ExportDialog.h" +#include "common_widgets/ErrorView.h" #include "components/RaCoNameConstants.h" #include "core/PathManager.h" #include "core/SceneBackendInterface.h" @@ -56,7 +57,7 @@ QStandardItemModel* createSummaryModel(const raco::core::SceneBackendInterface* namespace raco::common_widgets { -ExportDialog::ExportDialog(const application::RaCoApplication* application, QWidget* parent) : QDialog{parent}, application_{application} { +ExportDialog::ExportDialog(application::RaCoApplication* application, LogViewModel * logViewModel, QWidget* parent) : QDialog{parent}, application_{application} { setWindowTitle(QString{"Export Project - %1"}.arg(application->activeRaCoProject().name())); auto* content = new QGroupBox{"Export Configuration:", this}; @@ -109,6 +110,28 @@ ExportDialog::ExportDialog(const application::RaCoApplication* application, QWid tabWidget->addTab(listView, QString{"Scene ID: %1"}.arg(application->sceneBackend()->currentSceneIdValue())); hasErrors_ = false; + + auto* commandInterface = application->activeRaCoProject().commandInterface(); + if (!commandInterface->errors().getAllErrors().empty()) { + bool hasComposerErrors = false; + bool hasComposerWarnings = false; + + for (const auto& errorPair : commandInterface->errors().getAllErrors()) { + if (errorPair.second.level() == raco::core::ErrorLevel::WARNING) { + hasComposerWarnings = true; + } else if (errorPair.second.level() == raco::core::ErrorLevel::ERROR) { + hasErrors_ = true; + hasComposerErrors = true; + break; + } + } + + if (hasComposerErrors || hasComposerWarnings) { + auto* errorView = new ErrorView(commandInterface, application->dataChangeDispatcher(), false, logViewModel, this); + tabWidget->setCurrentIndex(tabWidget->addTab(errorView, hasComposerErrors ? "Composer Errors" : "Composer Warnings")); + } + } + if (!application->sceneBackend()->sceneValid()) { auto* textBox = new QTextEdit(summaryBox); textBox->setAcceptRichText(false); @@ -116,14 +139,13 @@ ExportDialog::ExportDialog(const application::RaCoApplication* application, QWid QString message(application->sceneBackend()->getValidationReport(core::ErrorLevel::ERROR).c_str()); if (!message.isEmpty()) { textBox->setText(message); - tabWidget->addTab(textBox, QString{"Errors"}); - tabWidget->setCurrentIndex(1); + tabWidget->setCurrentIndex(tabWidget->addTab(textBox, "Ramses Errors")); hasErrors_ = true; } else { message = QString::fromStdString(application->sceneBackend()->getValidationReport(core::ErrorLevel::WARNING)); if (!message.isEmpty()) { textBox->setText(message); - tabWidget->addTab(textBox, QString{"Warnings"}); + tabWidget->setCurrentIndex(tabWidget->addTab(textBox, "Ramses Warnings")); } } } diff --git a/gui/libCommonWidgets/src/LogView.cpp b/gui/libCommonWidgets/src/LogView.cpp new file mode 100644 index 00000000..009a9970 --- /dev/null +++ b/gui/libCommonWidgets/src/LogView.cpp @@ -0,0 +1,175 @@ +/* + * 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 "common_widgets/LogView.h" + +#include "common_widgets/NoContentMarginsLayout.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace raco::common_widgets { + +LogView::LogView(LogViewModel* model, QWidget* parent) : model_(model) { + + proxyModel_ = new LogViewSortFilterProxyModel(model_, this); + + auto* logViewLayout{new NoContentMarginsLayout{this}}; + + tableView_ = new QTableView(this); + tableView_->setModel(proxyModel_); + tableView_->setEditTriggers(QAbstractItemView::NoEditTriggers); + tableView_->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection); + tableView_->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows); + tableView_->setContextMenuPolicy(Qt::CustomContextMenu); + tableView_->setAlternatingRowColors(true); + tableView_->setWordWrap(true); + tableView_->setSortingEnabled(true); + tableView_->setColumnWidth(LogViewModel::COLUMNINDEX_TIMESTAMP, 120); + tableView_->setColumnWidth(LogViewModel::COLUMNINDEX_LEVEL, 60); + tableView_->setColumnWidth(LogViewModel::COLUMNINDEX_CATEGORY, 120); + tableView_->verticalHeader()->hide(); + tableView_->verticalHeader()->setDefaultSectionSize(18); + tableView_->horizontalHeader()->setStretchLastSection(true); + connect(tableView_, &QTableView::customContextMenuRequested, this, &LogView::customMenuRequested); + + auto* clearButton = new QPushButton("Clear Log", this); + connect(clearButton, &QPushButton::clicked, model_, &LogViewModel::clear); + + auto filterLevelBox = new QComboBox(this); + filterLevelBox->addItem(LogViewModel::textForLogLevel(spdlog::level::trace), spdlog::level::trace); + filterLevelBox->addItem(LogViewModel::textForLogLevel(spdlog::level::debug), spdlog::level::debug); + filterLevelBox->addItem(LogViewModel::textForLogLevel(spdlog::level::info), spdlog::level::info); + filterLevelBox->addItem(LogViewModel::textForLogLevel(spdlog::level::warn), spdlog::level::warn); + filterLevelBox->addItem(LogViewModel::textForLogLevel(spdlog::level::err), spdlog::level::err); + // Let's not allow users to hide errors, so disable filter for critical log level + // filterLevelBox->addItem(LogViewModel::textForLogLevel(spdlog::level::critical), spdlog::level::critical); + QObject::connect(filterLevelBox, qOverload(&QComboBox::activated), this, [this, filterLevelBox](int index) { + auto filter = static_cast(filterLevelBox->itemData(index, Qt::UserRole).toInt()); + proxyModel_->setFilterMinLogLevel(filter); + updateWarningErrorLabel(); + }); + filterLevelBox->setCurrentIndex(2); + proxyModel_->setFilterMinLogLevel(spdlog::level::info); + + auto filterCategoryBox = new QComboBox(this); + filterCategoryBox->addItem("All Categories", ""); + filterCategoryBox->addItem(raco::log_system::DEFAULT, raco::log_system::DEFAULT); + filterCategoryBox->addItem(raco::log_system::COMMON, raco::log_system::COMMON); + filterCategoryBox->addItem(raco::log_system::CONTEXT, raco::log_system::CONTEXT); + filterCategoryBox->addItem(raco::log_system::USER_TYPES, raco::log_system::USER_TYPES); + filterCategoryBox->addItem(raco::log_system::PROPERTY_BROWSER, raco::log_system::PROPERTY_BROWSER); + filterCategoryBox->addItem(raco::log_system::OBJECT_TREE_VIEW, raco::log_system::OBJECT_TREE_VIEW); + filterCategoryBox->addItem(raco::log_system::STYLE, raco::log_system::STYLE); + filterCategoryBox->addItem(raco::log_system::LOGGING, raco::log_system::LOGGING); + filterCategoryBox->addItem(raco::log_system::DATA_CHANGE, raco::log_system::DATA_CHANGE); + filterCategoryBox->addItem(raco::log_system::PREVIEW_WIDGET, raco::log_system::PREVIEW_WIDGET); + filterCategoryBox->addItem(raco::log_system::RAMSES_BACKEND, raco::log_system::RAMSES_BACKEND); + filterCategoryBox->addItem(raco::log_system::RAMSES_ADAPTOR, raco::log_system::RAMSES_ADAPTOR); + filterCategoryBox->addItem(raco::log_system::DESERIALIZATION, raco::log_system::DESERIALIZATION); + filterCategoryBox->addItem(raco::log_system::PROJECT, raco::log_system::PROJECT); + filterCategoryBox->addItem(raco::log_system::MESH_LOADER, raco::log_system::MESH_LOADER); + filterCategoryBox->addItem(raco::log_system::RAMSES_LOGIC, raco::log_system::RAMSES_LOGIC); + QObject::connect(filterCategoryBox, qOverload(&QComboBox::activated), this, [this, filterCategoryBox](int index) { + auto filter = filterCategoryBox->itemData(index, Qt::UserRole).toString(); + proxyModel_->setFilterCategory(filter); + updateWarningErrorLabel(); + }); + + auto searchLineEdit = new QLineEdit(this); + searchLineEdit->setFixedHeight(18); + searchLineEdit->setFixedWidth(150); + searchLineEdit->setPlaceholderText("Search Message..."); + QObject::connect(searchLineEdit, &QLineEdit::textChanged, this, [this](QString filter) { + proxyModel_->setFilterMessage(filter); + updateWarningErrorLabel(); + }); + + warningErrorLabel_ = new QLabel(this); + QObject::connect(model_, &LogViewModel::entriesChanged, this, &LogView::updateWarningErrorLabel); + updateWarningErrorLabel(); + + auto* filterLayoutWidget = new QWidget(this); + auto* filterLayout = new NoContentMarginsLayout(filterLayoutWidget); + filterLayout->addItem(new QSpacerItem(5, 0)); + filterLayout->addWidget(clearButton); + filterLayout->addWidget(filterLevelBox); + filterLayout->addWidget(filterCategoryBox); + filterLayout->addWidget(searchLineEdit); + filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); + filterLayout->addWidget(warningErrorLabel_); + filterLayout->addItem(new QSpacerItem(5, 0)); + + logViewLayout->addWidget(tableView_); + logViewLayout->addWidget(filterLayoutWidget); +} + +bool LogView::event(QEvent* event) { + // We want to override the application wide Ctrl+C handling if this widget is focussed. + if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent* keyEvent = static_cast(event); + + if (keyEvent->matches(QKeySequence::Copy)) { + keyEvent->setAccepted(true); + return true; + } + } + return QWidget::event(event); +} + +void LogView::keyPressEvent(QKeyEvent* event) { + if (event->matches(QKeySequence::Copy)) { + copySelectedRows(); + event->setAccepted(true); + } +} + +void LogView::customMenuRequested(QPoint pos) { + + if (!tableView_->selectionModel()->hasSelection()) { + return; + } + + QMenu* menu = new QMenu(this); + menu->addAction("Copy", [this]() { copySelectedRows(); }, QKeySequence::Copy); + menu->popup(tableView_->viewport()->mapToGlobal(pos)); +} + +void LogView::updateWarningErrorLabel() { + warningErrorLabel_->setText(QString::fromStdString(fmt::format("Warnings: {} / {} Errors: {} / {}", + proxyModel_->warningCount(), + model_->warningCount(), + proxyModel_->errorCount(), + model_->errorCount()))); +} + +void LogView::copySelectedRows() { + auto selection = tableView_->selectionModel()->selection(); + + if (!selection.isEmpty()) { + QMimeData* mimeData = new QMimeData(); + mimeData->setData("text/plain", proxyModel_->getStringFromSelection(selection).c_str()); + QApplication::clipboard()->setMimeData(mimeData); + } +} + + +} // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/src/LogWidget.cpp b/gui/libCommonWidgets/src/LogWidget.cpp deleted file mode 100644 index be83d561..00000000 --- a/gui/libCommonWidgets/src/LogWidget.cpp +++ /dev/null @@ -1,66 +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 "common_widgets/LogWidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace raco::common_widgets { - -class LogWidgetSink final : public spdlog::sinks::base_sink { -public: - explicit LogWidgetSink(QStandardItemModel* model) : model_{model} {} - void sink_it_(const spdlog::details::log_msg& msg) override { - auto time = std::chrono::system_clock::to_time_t(msg.time); - std::stringstream timeSS{}; - struct tm buf; -#if (defined(Q_OS_WIN)) - localtime_s(&buf, &time); -#else - localtime_r(&time, &buf); -#endif - timeSS << std::put_time(&buf, "%F %T"); - model_->appendRow(QList{ - new QStandardItem{timeSS.str().c_str()}, - new QStandardItem{msg.logger_name.data()}, - new QStandardItem{msg.payload.data()}}); - } - void flush_() override {} - -private: - QStandardItemModel* model_; -}; - -LogWidget::LogWidget(QWidget* parent) : QTableView{parent}, model_{}, sink_{std::make_shared(&model_)} { - log_system::registerSink(sink_); - verticalHeader()->hide(); - setGridStyle(Qt::PenStyle::NoPen); - - model_.setHeaderData(0, Qt::Horizontal, "timestamp"); - model_.setHeaderData(1, Qt::Horizontal, "category"); - model_.setHeaderData(2, Qt::Horizontal, "message"); - - setModel(&model_); -} - -LogWidget::~LogWidget() { - log_system::unregisterSink(sink_); -} - -} // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/src/MeshAssetImportDialog.cpp b/gui/libCommonWidgets/src/MeshAssetImportDialog.cpp index 55816db0..f9b6729c 100644 --- a/gui/libCommonWidgets/src/MeshAssetImportDialog.cpp +++ b/gui/libCommonWidgets/src/MeshAssetImportDialog.cpp @@ -107,7 +107,7 @@ MeshAssetImportDialog::MeshAssetImportDialog(raco::core::MeshScenegraph& sceneGr auto& item = nodeTreeList_[i] = new QTreeWidgetItem({QString::fromStdString(node.name), QString::fromStdString((node.subMeshIndeces.size() == 1) ? raco::user_types::MeshNode::typeDescription.typeName : raco::user_types::Node::typeDescription.typeName)}); widgetItemToSubmeshIndexMap_[item] = &node.subMeshIndeces; - item->setIcon(0, node.subMeshIndeces.size() == 1 ? raco::style::Icons::pixmap(raco::style::Pixmap::typeMesh) : raco::style::Icons::pixmap(raco::style::Pixmap::typeNode)); + item->setIcon(0, node.subMeshIndeces.size() == 1 ? raco::style::Icons::instance().typeMesh : raco::style::Icons::instance().typeNode); item->setCheckState(0, Qt::CheckState::Checked); if (node.parentIndex == raco::core::MeshScenegraphNode::NO_PARENT) { widget_->addTopLevelItem(item); @@ -117,7 +117,7 @@ MeshAssetImportDialog::MeshAssetImportDialog(raco::core::MeshScenegraph& sceneGr // We currently split those into separate MeshNodes under a singular, separate Node if (node.subMeshIndeces.size() > 1) { auto primitiveParent = new QTreeWidgetItem({QString::fromStdString(fmt::format("{}_meshnodes", node.name)), QString::fromStdString(raco::user_types::Node::typeDescription.typeName)}); - primitiveParent->setIcon(0, raco::style::Icons::pixmap(raco::style::Pixmap::typeNode)); + primitiveParent->setIcon(0, raco::style::Icons::instance().typeNode); primitiveParent->setCheckState(0, Qt::CheckState::Checked); item->addChild(primitiveParent); // first element in primitive tree list is the aforementioned singular Node (primitive parent) @@ -125,7 +125,7 @@ MeshAssetImportDialog::MeshAssetImportDialog(raco::core::MeshScenegraph& sceneGr for (auto primitiveIndex = 0; primitiveIndex < node.subMeshIndeces.size(); ++primitiveIndex) { auto* primitive = new QTreeWidgetItem({QString::fromStdString(fmt::format("{}_meshnode_{}", node.name, primitiveIndex)), QString::fromStdString(raco::user_types::MeshNode::typeDescription.typeName)}); - primitive->setIcon(0, raco::style::Icons::pixmap(raco::style::Pixmap::typeMesh)); + primitive->setIcon(0, raco::style::Icons::instance().typeMesh); primitive->setCheckState(0, Qt::CheckState::Checked); nodeToPrimitiveTreeList_[i].emplace_back(primitive); primitiveToMeshIndexMap_[primitive] = *node.subMeshIndeces[primitiveIndex]; @@ -148,7 +148,7 @@ MeshAssetImportDialog::MeshAssetImportDialog(raco::core::MeshScenegraph& sceneGr for (auto i = 0; i < sceneGraph_.meshes.size(); ++i) { auto& mesh = sceneGraph_.meshes[i].value(); auto item = meshTreeList_[i] = new QTreeWidgetItem({QString::fromStdString(mesh), QString::fromStdString(raco::user_types::Mesh::typeDescription.typeName)}); - item->setIcon(0, raco::style::Icons::pixmap(raco::style::Pixmap::typeMesh)); + item->setIcon(0, raco::style::Icons::instance().typeMesh); item->setCheckState(0, Qt::CheckState::Checked); widget_->addTopLevelItem(item); } @@ -156,7 +156,7 @@ MeshAssetImportDialog::MeshAssetImportDialog(raco::core::MeshScenegraph& sceneGr for (auto animIndex = 0; animIndex < sceneGraph_.animations.size(); ++animIndex) { auto& anim = sceneGraph_.animations[animIndex].value(); auto animItem = animTreeList_[animIndex] = new QTreeWidgetItem({QString::fromStdString(anim.name), QString::fromStdString(raco::user_types::Animation::typeDescription.typeName)}); - animItem->setIcon(0, raco::style::Icons::pixmap(raco::style::Pixmap::typeAnimation)); + animItem->setIcon(0, raco::style::Icons::instance().typeAnimation); animItem->setCheckState(0, Qt::CheckState::Checked); widget_->addTopLevelItem(animItem); @@ -164,7 +164,7 @@ MeshAssetImportDialog::MeshAssetImportDialog(raco::core::MeshScenegraph& sceneGr auto& sampler = sceneGraph_.animationSamplers[animIndex][samplerIndex]; auto sampItem = new QTreeWidgetItem({QString::fromStdString(*sampler), QString::fromStdString(raco::user_types::AnimationChannel::typeDescription.typeName)}); animSamplerItemMap_[animIndex].emplace_back(sampItem); - sampItem->setIcon(0, raco::style::Icons::pixmap(raco::style::Pixmap::typeAnimationChannel)); + sampItem->setIcon(0, raco::style::Icons::instance().typeAnimationChannel); sampItem->setCheckState(0, Qt::CheckState::Checked); widget_->addTopLevelItem(sampItem); } diff --git a/gui/libCommonWidgets/src/PreferencesView.cpp b/gui/libCommonWidgets/src/PreferencesView.cpp index de55537e..11af411e 100644 --- a/gui/libCommonWidgets/src/PreferencesView.cpp +++ b/gui/libCommonWidgets/src/PreferencesView.cpp @@ -65,7 +65,7 @@ PreferencesView::PreferencesView(QWidget* parent) : QDialog{parent} { auto cancelButton{new QPushButton{"Close", buttonBox}}; QObject::connect(cancelButton, &QPushButton::clicked, this, &PreferencesView::close); auto saveButton{new QPushButton{"Save", buttonBox}}; - saveButton->setDisabled(true); + saveButton->setDisabled(raco::utils::u8path(RaCoPreferences::instance().userProjectsDirectory.toStdString()).existsDirectory()); QObject::connect(this, &PreferencesView::dirtyChanged, saveButton, &QPushButton::setEnabled); QObject::connect(saveButton, &QPushButton::clicked, this, &PreferencesView::save); buttonBox->addButton(cancelButton, QDialogButtonBox::RejectRole); @@ -74,15 +74,33 @@ PreferencesView::PreferencesView(QWidget* parent) : QDialog{parent} { } void PreferencesView::save() { - if (raco::utils::u8path(userProjectEdit_->text().toStdString()).existsDirectory()) { - RaCoPreferences::instance().userProjectsDirectory = userProjectEdit_->text(); - } + auto newUserProjectPath = raco::utils::u8path(userProjectEdit_->text().toStdString()); + auto newUserProjectPathString = QString::fromStdString(newUserProjectPath.string()); + + if (!newUserProjectPath.existsDirectory()) { + if(QMessageBox::question( + this, "User Projects Directory does not exist", + QString("The user projects directory '") + newUserProjectPathString + "' does not exist. Choose OK to create the directory or press cancel to not save the preferences.", + QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok) { + + std::filesystem::create_directories(newUserProjectPath); + if (!newUserProjectPath.existsDirectory()) { + QMessageBox::critical(this, "Saving settings failed", "Cannot create directory: " + newUserProjectPathString); + return; + } + } else { + return; + } + } + + RaCoPreferences::instance().userProjectsDirectory = newUserProjectPathString; + 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())); + 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() { diff --git a/gui/libCommonWidgets/src/log_model/LogViewModel.cpp b/gui/libCommonWidgets/src/log_model/LogViewModel.cpp new file mode 100644 index 00000000..eded451b --- /dev/null +++ b/gui/libCommonWidgets/src/log_model/LogViewModel.cpp @@ -0,0 +1,206 @@ +/* + * 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 "common_widgets/log_model/LogViewModel.h" + +#include "common_widgets/log_model/LogViewSink.h" + +#include "style/Colors.h" + +#include + +namespace raco::common_widgets { + +QString LogViewModel::textForLogLevel(spdlog::level::level_enum level) { + using namespace spdlog::level; + + switch (level) { + case level_enum::trace: + return "Trace"; + case level_enum::debug: + return "Debug"; + case level_enum::info: + return "Info"; + case level_enum::warn: + return "Warn"; + case level_enum::err: + return "Error"; + case level_enum::critical: + return "Critical"; + default: + return "Other"; + } +} + +QColor LogViewModel::colorForLogLevel(spdlog::level::level_enum level) { + using namespace spdlog::level; + using namespace raco::style; + + switch (level) { + case level_enum::trace: + return Colors::color(Colormap::textDisabled); + case level_enum::debug: + return Colors::color(Colormap::textDisabled); + case level_enum::info: + return Colors::color(Colormap::text); + case level_enum::warn: + return Colors::color(Colormap::warningColor); + case level_enum::err: + return Colors::color(Colormap::errorColor); + case level_enum::critical: + return Colors::color(Colormap::errorColor).lighter(); + default: + return Colors::color(Colormap::text); + } +} + +LogViewModel::LogViewModel(QObject* parent) : QAbstractItemModel(parent), boldFont_() { + boldFont_.setBold(true); + flushTimer_ = new QTimer(this); + flushTimer_->setSingleShot(true); + connect(flushTimer_, &QTimer::timeout, this, &LogViewModel::flushNewEntries); + + qRegisterMetaType("LogViewModel::LogEntry"); + + sink_ = std::make_shared(this); + log_system::registerSink(sink_); +} + +LogViewModel::~LogViewModel() { + log_system::unregisterSink(sink_); +} + +QModelIndex LogViewModel::index(int row, int column, const QModelIndex& parent) const { + assert(!parent.isValid()); + return createIndex(row, column); +}; + +QModelIndex LogViewModel::parent(const QModelIndex& child) const { + return {}; +} + +QVariant LogViewModel::data(const QModelIndex& index, int role) const { + if (role == Qt::DisplayRole) { + switch (index.column()) { + case COLUMNINDEX_TIMESTAMP: + return entries_[index.row()].timestamp_; + case COLUMNINDEX_LEVEL: + return textForLogLevel(entries_[index.row()].level_); + case COLUMNINDEX_CATEGORY: + return entries_[index.row()].category_; + case COLUMNINDEX_MESSAGE: + return entries_[index.row()].message_; + } + } + if (index.column() == COLUMNINDEX_LEVEL) { + if (role == Qt::UserRole) { + return entries_[index.row()].level_; + } + if (role == Qt::ForegroundRole) { + return colorForLogLevel(entries_[index.row()].level_); + } + if (role == Qt::FontRole && entries_[index.row()].level_ >= spdlog::level::warn) { + return boldFont_; + } + + } + + + return {}; +} + +int LogViewModel::rowCount(const QModelIndex& parent) const { + return parent.isValid() ? 0 : entries_.size(); +} + +int LogViewModel::columnCount(const QModelIndex& parent) const { + return COLUMN_COUNT; +} + +QVariant LogViewModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::DisplayRole) { + switch (section) { + case COLUMNINDEX_TIMESTAMP: + return "Timestamp"; + case COLUMNINDEX_LEVEL: + return "Level"; + case COLUMNINDEX_CATEGORY: + return "Category"; + case COLUMNINDEX_MESSAGE: + return "Message"; + } + } + + return {}; +} + +const std::vector& LogViewModel::entries() const { + return entries_; +} + +void LogViewModel::addEntry(const LogEntry& entry) { + entriesToAdd_.emplace_back(entry); + + if (!flushTimer_->isActive()) { + flushTimer_->start(FLUSH_TIMER_INTERVAL); + } +} + +void LogViewModel::clear() { + beginResetModel(); + entries_.clear(); + endResetModel(); + entriesToAdd_.clear(); + + Q_EMIT entriesChanged(); +} + +void LogViewModel::flushNewEntries() { + if (entriesToAdd_.empty()) { + return; + } + + if (entriesToAdd_.size() > MAX_ENTRY_COUNT) { + entriesToAdd_.erase(entriesToAdd_.begin(), entriesToAdd_.begin() + (entriesToAdd_.size() - MAX_ENTRY_COUNT)); + } + + if (entries_.size() + entriesToAdd_.size() > MAX_ENTRY_COUNT) { + beginRemoveRows({}, 0, entries_.size() + entriesToAdd_.size() - MAX_ENTRY_COUNT - 1); + entries_.erase(entries_.begin(), entries_.begin() + (entries_.size() + entriesToAdd_.size() - MAX_ENTRY_COUNT)); + endRemoveRows(); + } + + beginInsertRows({}, entries_.size(), entries_.size() + entriesToAdd_.size() - 1); + entries_.insert(entries_.end(), entriesToAdd_.begin(), entriesToAdd_.end()); + endInsertRows(); + entriesToAdd_.clear(); + + Q_EMIT entriesChanged(); +} + +int LogViewModel::warningCount() const { + return std::count_if(entries_.begin(), entries_.end(), [](const LogEntry& entry) { return entry.level_ == spdlog::level::warn; }); +} + +int LogViewModel::errorCount() const { + return std::count_if(entries_.begin(), entries_.end(), [](const LogEntry& entry) { return entry.level_ == spdlog::level::err || entry.level_ == spdlog::level::critical; }); +} + +std::string LogViewModel::getStringFromSelection(QItemSelection selection) const { + std::stringstream result; + for (const auto& index : selection.indexes()) { + if (index.column() == 0) { + const auto& entry = entries_[index.row()]; + result << entry.fullLogMessageRaw_.toStdString(); + } + } + return result.str(); + +} +} // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/src/log_model/LogViewSink.cpp b/gui/libCommonWidgets/src/log_model/LogViewSink.cpp new file mode 100644 index 00000000..67cb519d --- /dev/null +++ b/gui/libCommonWidgets/src/log_model/LogViewSink.cpp @@ -0,0 +1,52 @@ +/* + * 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 "common_widgets/log_model/LogViewSink.h" + +#include "common_widgets/log_model/LogViewModel.h" + +#include +#include +#include +#include + +namespace raco::common_widgets { + +LogViewSink::LogViewSink(LogViewModel* model) : spdlog::sinks::base_sink(), model_(model) { +} + +void LogViewSink::sink_it_(const spdlog::details::log_msg& msg) { + auto time = std::chrono::system_clock::to_time_t(msg.time); + struct tm localTime; +#if (defined(Q_OS_WIN)) + localtime_s(&localTime, &time); +#else + localtime_r(&time, &localTime); +#endif + std::stringstream timeStringStream; + timeStringStream << std::put_time(&localTime, "%F %T"); + + spdlog::memory_buf_t messageRaw; + base_sink::formatter_->format(msg, messageRaw); + + LogViewModel::LogEntry entry = { + timeStringStream.str().c_str(), + msg.level, + QString::fromLocal8Bit(msg.logger_name.data(), (int)msg.logger_name.size()), + QString::fromLocal8Bit(msg.payload.data(), (int)msg.payload.size()), + QString::fromLocal8Bit(messageRaw.data(), messageRaw.size()) + }; + + // Always call addEntry on the main thread, in case Ramses Composer becomes multi-threaded in the future. + QMetaObject::invokeMethod(model_, "addEntry", Qt::ConnectionType::QueuedConnection, Q_ARG(LogViewModel::LogEntry, entry)); +} + +void LogViewSink::flush_() {} + +} // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/src/log_model/LogViewSortFilterProxyModel.cpp b/gui/libCommonWidgets/src/log_model/LogViewSortFilterProxyModel.cpp new file mode 100644 index 00000000..52ce8c45 --- /dev/null +++ b/gui/libCommonWidgets/src/log_model/LogViewSortFilterProxyModel.cpp @@ -0,0 +1,65 @@ +/* + * 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 "common_widgets/log_model/LogViewSortFilterProxyModel.h" + +namespace raco::common_widgets { + +LogViewSortFilterProxyModel::LogViewSortFilterProxyModel(LogViewModel* model, QObject* parent) : QSortFilterProxyModel(parent), model_(model) { + setSourceModel(model); +} + +void LogViewSortFilterProxyModel::setFilterMinLogLevel(spdlog::level::level_enum filterMinLogLevel) { + filterMinLogLevel_ = filterMinLogLevel; + invalidateFilter(); +} +void LogViewSortFilterProxyModel::setFilterCategory(QString filterCategory) { + filterCategory_ = filterCategory; + invalidateFilter(); +} + +void LogViewSortFilterProxyModel::setFilterMessage(QString filterMessage) { + filterMessage_ = filterMessage; + invalidateFilter(); +} + +bool LogViewSortFilterProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { + // For the timestamp column, we just sort by row index instead to avoid duplicate timestamps messing up the sort order. + if (sortColumn() == LogViewModel::COLUMNINDEX_TIMESTAMP) { + return source_left.row() < source_right.row(); + } else if (sortColumn() == LogViewModel::COLUMNINDEX_LEVEL) { + return model_->entries()[source_left.row()].level_ < model_->entries()[source_right.row()].level_; + } + + return QSortFilterProxyModel::lessThan(source_left, source_right); +} + +bool LogViewSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { + return filterAcceptsEntry(model_->entries()[sourceRow]); +} + +bool LogViewSortFilterProxyModel::filterAcceptsEntry(const LogViewModel::LogEntry& entry) const { + return entry.level_ >= filterMinLogLevel_ && + (filterCategory_.isEmpty() || entry.category_ == filterCategory_) && + (filterMessage_.isEmpty() || entry.message_.contains(filterMessage_, Qt::CaseInsensitive)); +} + +int LogViewSortFilterProxyModel::warningCount() const { + return std::count_if(model_->entries().begin(), model_->entries().end(), [this](const LogViewModel::LogEntry& entry) { return filterAcceptsEntry(entry) && entry.level_ == spdlog::level::warn; }); +} + +int LogViewSortFilterProxyModel::errorCount() const { + return std::count_if(model_->entries().begin(), model_->entries().end(), [this](const LogViewModel::LogEntry& entry) { return filterAcceptsEntry(entry) && (entry.level_ == spdlog::level::err || entry.level_ == spdlog::level::critical); }); +} + +std::string LogViewSortFilterProxyModel::getStringFromSelection(QItemSelection selection) const { + return model_->getStringFromSelection(mapSelectionToSource(selection)); +} + +} // namespace raco::common_widgets diff --git a/gui/libCommonWidgets/tests/ErrorView_test.h b/gui/libCommonWidgets/tests/ErrorView_test.h index d5d08677..6c08f7c3 100644 --- a/gui/libCommonWidgets/tests/ErrorView_test.h +++ b/gui/libCommonWidgets/tests/ErrorView_test.h @@ -21,5 +21,5 @@ class ErrorViewTest : public TestEnvironmentCore { int argc{0}; QApplication fakeApp_{argc, nullptr}; raco::components::SDataChangeDispatcher dataChangeDispatcher_{std::make_shared()}; - raco::common_widgets::ErrorView errorView_{&commandInterface, dataChangeDispatcher_}; + raco::common_widgets::ErrorView errorView_{&commandInterface, dataChangeDispatcher_, true, nullptr}; }; diff --git a/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h b/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h index 3f1aefc2..893f610e 100644 --- a/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h +++ b/gui/libObjectTree/include/object_tree_view/ObjectTreeView.h @@ -57,6 +57,7 @@ public Q_SLOTS: void resetSelection(); void globalCopyCallback(); void cut(); + void duplicateObjects(); void globalPasteCallback(const QModelIndex &index, bool asExtRef = false); void shortcutDelete(); void selectObject(const QString &objectID); diff --git a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h index 171f756e..666b0a9b 100644 --- a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h +++ b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewDefaultModel.h @@ -90,6 +90,7 @@ class ObjectTreeViewDefaultModel : public QAbstractItemModel { virtual bool canCopyAtIndices(const QModelIndexList& index) const; virtual bool canDeleteAtIndices(const QModelIndexList& indices) const; virtual bool canPasteIntoIndex(const QModelIndex& index, const std::vector& objects, const std::set& sourceProjectTopLevelObjectIds, bool asExtRef = false) const; + virtual bool canDuplicateAtIndices(const QModelIndexList& indices) const; virtual bool canDeleteUnusedResources() const; @@ -106,6 +107,7 @@ public Q_SLOTS: virtual void copyObjectsAtIndices(const QModelIndexList& indices, bool deepCopy); virtual void cutObjectsAtIndices(const QModelIndexList& indices, bool deepCut); virtual bool pasteObjectAtIndex(const QModelIndex& index, bool pasteAsExtref = false, std::string* outError = nullptr, const std::string& serializedObjects = RaCoClipboard::get()); + virtual std::vector duplicateObjectsAtIndices(const QModelIndexList& indices); void moveScenegraphChildren(const std::vector& objects, core::SEditorObject parent, int row = -1); void importMeshScenegraph(const QString& filePath, const QModelIndex& selectedIndex); void deleteUnusedResources(); @@ -136,24 +138,22 @@ public Q_SLOTS: static inline constexpr const char* OBJECT_EDITOR_ID_MIME_TYPE = "application/editorobject.id"; - using Pixmap = ::raco::style::Pixmap; - - static inline const std::map typeIconMap{ - {"PerspectiveCamera", Pixmap::typeCamera}, - {"OrthographicCamera", Pixmap::typeCamera}, - {"Texture", Pixmap::typeTexture}, - {"CubeMap", Pixmap::typeCubemap}, - {"LuaScript", Pixmap::typeScript}, - {"Material", Pixmap::typeMaterial}, - {"Mesh", Pixmap::typeMesh}, - {"MeshNode", Pixmap::typeMesh}, - {"Node", Pixmap::typeNode}, - {"Prefab", Pixmap::typePrefabInternal}, - {"ExtrefPrefab", Pixmap::typePrefabExternal}, - {"PrefabInstance", Pixmap::typePrefabInstance}, - {"LuaScriptModule", Pixmap::typeLuaScriptModule}, - {"AnimationChannel", Pixmap::typeAnimationChannel}, - {"Animation", Pixmap::typeAnimation} + const std::map typeIconMap{ + {"PerspectiveCamera", raco::style::Icons::instance().typeCamera}, + {"OrthographicCamera", raco::style::Icons::instance().typeCamera}, + {"Texture", raco::style::Icons::instance().typeTexture}, + {"CubeMap", raco::style::Icons::instance().typeCubemap}, + {"LuaScript", raco::style::Icons::instance().typeLuaScript}, + {"Material", raco::style::Icons::instance().typeMaterial}, + {"Mesh", raco::style::Icons::instance().typeMesh}, + {"MeshNode", raco::style::Icons::instance().typeMesh}, + {"Node", raco::style::Icons::instance().typeNode}, + {"Prefab", raco::style::Icons::instance().typePrefabInternal}, + {"ExtrefPrefab", raco::style::Icons::instance().typePrefabExternal}, + {"PrefabInstance", raco::style::Icons::instance().typePrefabInstance}, + {"LuaScriptModule", raco::style::Icons::instance().typeLuaScriptModule}, + {"AnimationChannel", raco::style::Icons::instance().typeAnimationChannel}, + {"Animation", raco::style::Icons::instance().typeAnimation} }; std::string getOriginPathFromMimeData(const QMimeData* data) const; diff --git a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h index 860b0b06..7e61f0a9 100644 --- a/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h +++ b/gui/libObjectTree/include/object_tree_view_model/ObjectTreeViewExternalProjectModel.h @@ -30,6 +30,7 @@ class ObjectTreeViewExternalProjectModel : public ObjectTreeViewDefaultModel { bool canCopyAtIndices(const QModelIndexList& indices) const override; bool canDeleteAtIndices(const QModelIndexList& indices) const override; + bool canDuplicateAtIndices(const QModelIndexList& indices) const override; 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; @@ -39,6 +40,7 @@ public Q_SLOTS: void copyObjectsAtIndices(const QModelIndexList& indices, bool deepCopy) override; void cutObjectsAtIndices(const QModelIndexList& indices, bool deepCut) override; bool pasteObjectAtIndex(const QModelIndex& index, bool pasteAsExtref = false, std::string* outError = nullptr, const std::string& serializedObjects = RaCoClipboard::get()) override; + std::vector duplicateObjectsAtIndices(const QModelIndexList& indices) override; protected: void setNodeExternalProjectInfo(ObjectTreeNode* node) const override; diff --git a/gui/libObjectTree/src/object_tree_view/ObjectTreeDock.cpp b/gui/libObjectTree/src/object_tree_view/ObjectTreeDock.cpp index d8ce1361..0d554f41 100644 --- a/gui/libObjectTree/src/object_tree_view/ObjectTreeDock.cpp +++ b/gui/libObjectTree/src/object_tree_view/ObjectTreeDock.cpp @@ -31,8 +31,9 @@ ObjectTreeDock::ObjectTreeDock(const char *dockTitle, QWidget *parent) availableTreesComboBox_ = new QComboBox(treeDockContent_); availableTreesComboBox_->setVisible(false); treeDockLayout_->addWidget(availableTreesComboBox_); - treeDockSettingsLayout_ = new raco::common_widgets::NoContentMarginsLayout(treeDockContent_); - treeDockLayout_->addLayout(treeDockSettingsLayout_); + auto treeDockSettingsWidget = new QWidget(treeDockContent_); + treeDockSettingsLayout_ = new raco::common_widgets::NoContentMarginsLayout(treeDockSettingsWidget); + treeDockLayout_->addWidget(treeDockSettingsWidget); treeViewStack_ = new QStackedWidget(treeDockContent_); treeDockLayout_->addWidget(treeViewStack_); diff --git a/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp b/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp index 9da6b23c..b1b662f7 100644 --- a/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp +++ b/gui/libObjectTree/src/object_tree_view/ObjectTreeView.cpp @@ -89,8 +89,11 @@ ObjectTreeView::ObjectTreeView(const QString &viewTitle, ObjectTreeViewDefaultMo auto cutShortcut = new QShortcut(QKeySequence::Cut, this, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(cutShortcut, &QShortcut::activated, this, &ObjectTreeView::cut); - auto deleteShortcut = new QShortcut(QKeySequence::Delete, this, nullptr, nullptr, Qt::WidgetShortcut); + auto deleteShortcut = new QShortcut({"Del"}, this, nullptr, nullptr, Qt::WidgetShortcut); QObject::connect(deleteShortcut, &QShortcut::activated, this, &ObjectTreeView::shortcutDelete); + + auto duplicateShortcut = new QShortcut({"Ctrl+D"}, this, nullptr, nullptr, Qt::WidgetShortcut); + QObject::connect(duplicateShortcut, &QShortcut::activated, this, &ObjectTreeView::duplicateObjects); } std::set ObjectTreeView::getSelectedHandles() const { @@ -171,6 +174,13 @@ void ObjectTreeView::cut() { } } +void raco::object_tree::view::ObjectTreeView::duplicateObjects() { + auto selectedIndices = getSelectedIndices(true); + if (!selectedIndices.isEmpty() && treeModel_->canDuplicateAtIndices(selectedIndices)) { + treeModel_->duplicateObjectsAtIndices(selectedIndices); + } +} + void ObjectTreeView::globalPasteCallback(const QModelIndex &index, bool asExtRef) { if (canPasteIntoIndex(index, asExtRef)) { treeModel_->pasteObjectAtIndex(index, asExtRef); @@ -292,6 +302,13 @@ QMenu* ObjectTreeView::createCustomContextMenu(const QPoint &p) { actionPaste->setEnabled(false); } + auto actionDuplicate = treeViewMenu->addAction( + "Duplicate", [this, selectedItemIndices] { + treeModel_->duplicateObjectsAtIndices(selectedItemIndices); + }, + QKeySequence("Ctrl+D")); + actionDuplicate->setEnabled(treeModel_->canDuplicateAtIndices(selectedItemIndices)); + auto actionCut = treeViewMenu->addAction( "Cut", [this, selectedItemIndices]() { treeModel_->cutObjectsAtIndices(selectedItemIndices, false); diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp index b801881f..3ec143c2 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewDefaultModel.cpp @@ -95,7 +95,7 @@ QVariant ObjectTreeViewDefaultModel::data(const QModelIndex& index, int role) co auto itr = typeIconMap.find(editorObj->getTypeDescription().typeName); if (itr == typeIconMap.end()) return QVariant(); - return QVariant(Icons::icon(itr->second)); + return QVariant(itr->second); } return QVariant(); } @@ -459,8 +459,26 @@ bool ObjectTreeViewDefaultModel::canPasteIntoIndex(const QModelIndex& index, con return false; } -size_t ObjectTreeViewDefaultModel::deleteObjectsAtIndices(const QModelIndexList& indices) { +bool ObjectTreeViewDefaultModel::canDuplicateAtIndices(const QModelIndexList& indices) const { + if (indices.empty()) { + return false; + } + + std::vector objs; + + for (const auto& index : indices) { + auto obj = indexToSEditorObject(index); + if (!obj) { + return false; + } + objs.emplace_back(obj); + } + + return Queries::canDuplicateObjects(objs, *project()); +} + +size_t ObjectTreeViewDefaultModel::deleteObjectsAtIndices(const QModelIndexList& indices) { return commandInterface_->deleteObjects(indicesToSEditorObjects(indices)); } @@ -483,6 +501,11 @@ bool ObjectTreeViewDefaultModel::pasteObjectAtIndex(const QModelIndex& index, bo return success; } +std::vector ObjectTreeViewDefaultModel::duplicateObjectsAtIndices(const QModelIndexList& indices) { + auto objects = indicesToSEditorObjects(indices); + return commandInterface_->duplicateObjects(objects); +} + void ObjectTreeViewDefaultModel::cutObjectsAtIndices(const QModelIndexList& indices, bool deepCut) { auto objects = indicesToSEditorObjects(indices); auto text = commandInterface_->cutObjects(objects, deepCut); diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp index ec8e4f33..2373cce0 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewExternalProjectModel.cpp @@ -55,7 +55,7 @@ QVariant ObjectTreeViewExternalProjectModel::data(const QModelIndex& index, int } if (failed) { - return QVariant(raco::style::Icons::icon(Pixmap::error)); + return QVariant(raco::style::Icons::instance().error); } else { return QVariant(QIcon()); } @@ -223,10 +223,13 @@ bool ObjectTreeViewExternalProjectModel::canDeleteAtIndices(const QModelIndexLis return false; } -bool ObjectTreeViewExternalProjectModel::canPasteIntoIndex(const QModelIndex& index, const std::vector& objects, const std::set& sourceProjectTopLevelObjectIds, bool asExtRef) const { +bool ObjectTreeViewExternalProjectModel::canDuplicateAtIndices(const QModelIndexList& indices) const { return false; } +bool ObjectTreeViewExternalProjectModel::canPasteIntoIndex(const QModelIndex& index, const std::vector& objects, const std::set& sourceProjectTopLevelObjectIds, bool asExtRef) const { + return false; +} bool ObjectTreeViewExternalProjectModel::isObjectAllowedIntoIndex(const QModelIndex& index, const core::SEditorObject& obj) const { return false; @@ -246,4 +249,9 @@ bool ObjectTreeViewExternalProjectModel::pasteObjectAtIndex(const QModelIndex& i return true; } +std::vector ObjectTreeViewExternalProjectModel::duplicateObjectsAtIndices(const QModelIndexList& indices) { + // Don't modify external project structure. + return {}; +} + } // namespace raco::object_tree::model \ No newline at end of file diff --git a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp index 6a03ba59..31535207 100644 --- a/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp +++ b/gui/libObjectTree/src/object_tree_view_model/ObjectTreeViewPrefabModel.cpp @@ -28,7 +28,7 @@ QVariant ObjectTreeViewPrefabModel::data(const QModelIndex& index, int role) con 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 QVariant(typeIconMap.at("ExtrefPrefab")); } } diff --git a/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp index 3da08100..97500da9 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.cpp @@ -739,6 +739,121 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsCheckAllSceneGraphObjectCombin } } +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationCanDuplicateAllTopLevelItems) { + auto allSceneGraphNodes = createAllSceneGraphObjects(); + + for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); + ASSERT_TRUE(viewModel_->canDuplicateAtIndices({sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationCanDuplicateAllChildren) { + auto allSceneGraphNodes = createAllSceneGraphObjects(); + + auto parent = createNodes(Node::typeDescription.typeName, {"Parent"}).front(); + moveScenegraphChildren(allSceneGraphNodes, parent); + + for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); + ASSERT_TRUE(viewModel_->canDuplicateAtIndices({sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationParent) { + auto parent = createNodes(Node::typeDescription.typeName, {"Parent"}).front(); + + auto duplicatedObjs = viewModel_->duplicateObjectsAtIndices({viewModel_->indexFromTreeNodeID(parent->objectID())}); + + ASSERT_EQ(duplicatedObjs.size(), 1); + ASSERT_EQ(duplicatedObjs.front()->getParent(), nullptr); + ASSERT_NE(duplicatedObjs.front()->objectID(), parent->objectID()); +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationChild) { + auto hierarchy = createNodes(Node::typeDescription.typeName, {"Parent", "Child"}); + + moveScenegraphChildren({hierarchy[1]}, hierarchy[0]); + + auto duplicatedObjs = viewModel_->duplicateObjectsAtIndices({viewModel_->indexFromTreeNodeID(hierarchy[1]->objectID())}); + + ASSERT_EQ(duplicatedObjs.size(), 1); + ASSERT_EQ(duplicatedObjs.front()->getParent(), hierarchy[0]); + ASSERT_NE(duplicatedObjs.front()->objectID(), hierarchy[1]->objectID()); +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationCanNotDuplicateHierarchy) { + auto allSceneGraphNodes = createAllSceneGraphObjects(); + + auto parent = createNodes(Node::typeDescription.typeName, {"Parent"}).front(); + moveScenegraphChildren(allSceneGraphNodes, parent); + + for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); + auto parentIndex = viewModel_->indexFromTreeNodeID(parent->objectID()); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({parentIndex, sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationCanNotDuplicatePrefabInstanceChildren) { + auto allSceneGraphNodes = createAllSceneGraphObjects(); + auto prefab = createNodes(Prefab::typeDescription.typeName, {"Template"}).front(); + auto prefabInstance = createNodes(PrefabInstance::typeDescription.typeName, {"Instance"}).front(); + + commandInterface.set({prefabInstance, &PrefabInstance::template_}, prefab); + + moveScenegraphChildren(allSceneGraphNodes, prefab); + + for (const auto &child : prefabInstance->children_->asVector()) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(child->objectID()); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationCanNotDuplicateNestedPrefabInstanceChildren) { + auto allSceneGraphNodes = createAllSceneGraphObjects(); + auto prefab1 = createNodes(Prefab::typeDescription.typeName, {"Prefab1"}).front(); + auto prefabInstance1 = createNodes(PrefabInstance::typeDescription.typeName, {"Instance1"}).front(); + + auto prefab2 = createNodes(Prefab::typeDescription.typeName, {"Prefab1"}).front(); + auto prefabInstance2 = createNodes(PrefabInstance::typeDescription.typeName, {"Instance2"}).front(); + + moveScenegraphChildren(allSceneGraphNodes, prefab1); + moveScenegraphChildren({prefabInstance1}, prefab2); + + commandInterface.set({prefabInstance1, &PrefabInstance::template_}, prefab1); + commandInterface.set({prefabInstance2, &PrefabInstance::template_}, prefab2); + + viewModel_->buildObjectTree(); + + for (const auto &child : prefabInstance2->children_->asVector()[0]->children_->asVector()) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(child->objectID()); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationCanNotDuplicateChildAndUncle) { + auto allSceneGraphNodes = createAllSceneGraphObjects(); + auto nodes = createNodes(Node::typeDescription.typeName, {"Grandparent", "Parent", "Child", "Uncle"}); + auto grandparent = nodes[0]; + auto parent = nodes[1]; + auto child = nodes[2]; + auto uncle = nodes[3]; + + moveScenegraphChildren({parent}, grandparent); + moveScenegraphChildren({uncle}, grandparent); + moveScenegraphChildren({child}, parent); + + viewModel_->buildObjectTree(); + auto childIndex = viewModel_->indexFromTreeNodeID(child->objectID()); + auto uncleIndex = viewModel_->indexFromTreeNodeID(uncle->objectID()); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({childIndex, uncleIndex})); +} + +TEST_F(ObjectTreeViewDefaultModelTest, DuplicationCanNotDuplicateNothing) { + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({})); +} + TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsResourcesAreNotAllowedOnTopLevel) { for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); @@ -886,7 +1001,7 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsLuaScriptIsAllowedAsExtRefOnTo TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsChildLuaScriptIsNotAllowedAsExtRef) { auto luaScript = createNodes(LuaScript::typeDescription.typeName, {LuaScript::typeDescription.typeName}).front(); auto externalParentNode = createNodes(Node::typeDescription.typeName, {Node::typeDescription.typeName}).front(); - auto localParentNode = createNodes(Node::typeDescription.typeName, {"localParent"}); + auto localParentNode = createNodes(Node::typeDescription.typeName, {"localParent"}).front(); moveScenegraphChildren({luaScript}, externalParentNode); externalParentNode->addAnnotation(std::make_shared("differentProject")); @@ -899,12 +1014,12 @@ TEST_F(ObjectTreeViewDefaultModelTest, AllowedObjsChildLuaScriptIsNotAllowedAsEx auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(cutObjs); ASSERT_FALSE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds, true)); - ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID("NodelocalParent"), parsedObjs, sourceProjectTopLevelObjectIds, true)); + ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID(localParentNode->objectID()), parsedObjs, sourceProjectTopLevelObjectIds, true)); } TEST_F(ObjectTreeViewDefaultModelTest, PasteLuaScriptAsExtRefNotAllowedAsChild) { auto luaScript = createNodes(LuaScript::typeDescription.typeName, {LuaScript::typeDescription.typeName}).front(); - auto localParentNode = createNodes(Node::typeDescription.typeName, {"localParent"}); + auto localParentNode = createNodes(Node::typeDescription.typeName, {"localParent"}).front(); luaScript->addAnnotation(std::make_shared("differentProject")); dataChangeDispatcher_->dispatch(recorder.release()); @@ -912,14 +1027,14 @@ TEST_F(ObjectTreeViewDefaultModelTest, PasteLuaScriptAsExtRefNotAllowedAsChild) dataChangeDispatcher_->dispatch(recorder.release()); auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(cutObjs); - ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID("NodelocalParent"), parsedObjs, sourceProjectTopLevelObjectIds, true)); + ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID(localParentNode->objectID()), parsedObjs, sourceProjectTopLevelObjectIds, true)); ASSERT_TRUE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds, true)); } TEST_F(ObjectTreeViewDefaultModelTest, PasteLuaScriptAsExtRefNotAllowedWhenChildInExternalProject) { auto luaScript = createNodes(LuaScript::typeDescription.typeName, {LuaScript::typeDescription.typeName}).front(); auto externalParentNode = createNodes(Node::typeDescription.typeName, {"externalParentNode"}).front(); - auto localParentNode = createNodes(Node::typeDescription.typeName, {"localParent"}); + auto localParentNode = createNodes(Node::typeDescription.typeName, {"localParent"}).front(); moveScenegraphChildren({luaScript}, externalParentNode); luaScript->addAnnotation(std::make_shared("differentProject")); dataChangeDispatcher_->dispatch(recorder.release()); diff --git a/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.h b/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.h index a1d85503..6e6fa1d6 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.h +++ b/gui/libObjectTree/tests/ObjectTreeViewDefaultModel_test.h @@ -31,7 +31,7 @@ class ObjectTreeViewDefaultModelTest : public TestEnvironmentCore { std::vector createdNodes; for (const auto &name : nodeNames) { - createdNodes.emplace_back(context.createObject(type, name, std::string(type + name))); + createdNodes.emplace_back(context.createObject(type, name)); dataChangeDispatcher_->dispatch(recorder.release()); } diff --git a/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp index 2a7eea8c..44870c19 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewExternalProjectModel_test.cpp @@ -221,3 +221,20 @@ TEST_F(ObjectTreeViewExternalProjectModelTest, CanDeleteAtIndicesNever) { ASSERT_FALSE(externalProjectModel.canDeleteAtIndices({project1NodeIndex})); ASSERT_FALSE(externalProjectModel.canDeleteAtIndices({project1Index, project1NodeIndex, {}})); } + +TEST_F(ObjectTreeViewExternalProjectModelTest, CanNeverDuplicate) { + std::string project1Path = "project1Path.rca"; + auto project1Node = std::make_shared(); + generateExternalProject({project1Node}, project1Path); + + externalProjectModel.triggerObjectTreeRebuilding(); + + auto project1Index = externalProjectModel.indexFromTreeNodeID(project1Path); + auto project1NodeIndex = externalProjectModel.indexFromTreeNodeID(project1Node->objectID()); + + ASSERT_FALSE(externalProjectModel.canDuplicateAtIndices({})); + ASSERT_FALSE(externalProjectModel.canDuplicateAtIndices({{}})); + ASSERT_FALSE(externalProjectModel.canDuplicateAtIndices({project1Index})); + ASSERT_FALSE(externalProjectModel.canDuplicateAtIndices({project1NodeIndex})); + ASSERT_FALSE(externalProjectModel.canDuplicateAtIndices({project1Index, project1NodeIndex, {}})); +} diff --git a/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp index 608e0fd8..3cad4bcd 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewPrefabModel_test.cpp @@ -87,12 +87,12 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsResourcesAreNotAllowedOnTopLeve TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsResourcesAreNotAllowedUnderPrefab) { - auto prefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}); + auto prefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}).front(); 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_->indexFromTreeNodeID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName), newObj)); + ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(viewModel_->indexFromTreeNodeID(prefab->objectID()), newObj)); } } } @@ -111,8 +111,8 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsCheckSceneGraphObjectsOnTopLeve } TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsCheckExternalSceneGraphObjectsUnderPrefab) { - auto prefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}); - auto prefabIndex = viewModel_->indexFromTreeNodeID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName); + auto prefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}).front(); + auto prefabIndex = viewModel_->indexFromTreeNodeID(prefab->objectID()); for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); @@ -128,7 +128,8 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsCheckExternalSceneGraphObjectsU TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsPrefabInTreeViewIsNotMovable) { auto prefabs = createNodes(Prefab::typeDescription.typeName, {"prefab1", "prefab2"}); - auto prefabIndex = viewModel_->indexFromTreeNodeID(Prefab::typeDescription.typeName + "prefab1"); + auto prefabIndex = viewModel_->indexFromTreeNodeID(prefabs[0]->objectID()); + ASSERT_TRUE(prefabIndex.isValid()); ASSERT_FALSE(viewModel_->isObjectAllowedIntoIndex(prefabIndex, prefabs[1])); } @@ -194,7 +195,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsDeepCopiedPrefabIsAllowedInEmpt ASSERT_TRUE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds)); } -TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsDeepCopiedPrefabIsAllowedUnderPrefab) { +TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsDeepCopiedPrefabIsNotAllowedUnderPrefab) { auto meshNode = createNodes(MeshNode::typeDescription.typeName, {MeshNode::typeDescription.typeName}).front(); auto mesh = createNodes(Mesh::typeDescription.typeName, {Mesh::typeDescription.typeName}).front(); auto prefabs = createNodes(Prefab::typeDescription.typeName, {"prefab", "prefab2"}); @@ -209,7 +210,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsDeepCopiedPrefabIsAllowedUnderP dataChangeDispatcher_->dispatch(recorder.release()); auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(copiedObjs); - ASSERT_TRUE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID("prefab2"), parsedObjs, sourceProjectTopLevelObjectIds)); + ASSERT_FALSE(viewModel_->canPasteIntoIndex(viewModel_->indexFromTreeNodeID(prefabs[1]->objectID()), parsedObjs, sourceProjectTopLevelObjectIds)); } TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsNothingIsAllowedUnderExtRef) { @@ -217,7 +218,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, AllowedObjsNothingIsAllowedUnderExtRef) { extRefPrefab->addAnnotation(std::make_shared("differentProject")); viewModel_->buildObjectTree(); - auto extRefPrefabIndex = viewModel_->indexFromTreeNodeID(Prefab::typeDescription.typeName + Prefab::typeDescription.typeName); + auto extRefPrefabIndex = viewModel_->indexFromTreeNodeID(extRefPrefab->objectID()); for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); @@ -240,6 +241,7 @@ TEST_F(ObjectTreeViewPrefabModelTest, CanNotDoAnythingButPasteWithExtRefGroup) { ASSERT_FALSE(viewModel_->canDeleteAtIndices({extRefGroupIndex})); ASSERT_FALSE(viewModel_->canCopyAtIndices({extRefGroupIndex})); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({extRefGroupIndex})); dataChangeDispatcher_->dispatch(recorder.release()); auto copiedObjs = commandInterface.copyObjects({extRefPrefab}); @@ -248,4 +250,80 @@ TEST_F(ObjectTreeViewPrefabModelTest, CanNotDoAnythingButPasteWithExtRefGroup) { auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(copiedObjs); ASSERT_TRUE(viewModel_->canPasteIntoIndex({}, parsedObjs, sourceProjectTopLevelObjectIds)); ASSERT_TRUE(viewModel_->canPasteIntoIndex(extRefGroupIndex, parsedObjs, sourceProjectTopLevelObjectIds)); +} + + +TEST_F(ObjectTreeViewPrefabModelTest, DuplicationPrefabCanBeDuplicated) { + auto prefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}).front(); + + ASSERT_TRUE(viewModel_->canDuplicateAtIndices({viewModel_->indexFromTreeNodeID(prefab->objectID())})); +} + +TEST_F(ObjectTreeViewPrefabModelTest, DuplicationPrefabChildrenCanBeDuplicated) { + auto prefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}).front(); + auto allSceneGraphNodes = createAllSceneGraphObjects(); + + moveScenegraphChildren(allSceneGraphNodes, prefab); + + for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); + ASSERT_TRUE(viewModel_->canDuplicateAtIndices({sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewPrefabModelTest, DuplicationExtrefPrefabCanNotBeDuplicated) { + auto extRefPrefab = createNodes(Prefab::typeDescription.typeName, {Prefab::typeDescription.typeName}).front(); + auto allSceneGraphNodes = createAllSceneGraphObjects(); + + moveScenegraphChildren(allSceneGraphNodes, extRefPrefab); + + extRefPrefab->addAnnotation(std::make_shared("differentProject")); + for (auto &node : allSceneGraphNodes) { + node->addAnnotation(std::make_shared("differentProject")); + } + + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({viewModel_->indexFromTreeNodeID(extRefPrefab->objectID())})); + for (const auto &sceneGraphNodeInScene : allSceneGraphNodes) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(sceneGraphNodeInScene->objectID()); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewPrefabModelTest, DuplicationPrefabInstanceChildrenInPrefabCanNotBeDuplicated) { + auto prefabs = createNodes(Prefab::typeDescription.typeName, {"p1", "p2"}); + auto prefabInstance = createNodes(PrefabInstance::typeDescription.typeName, {PrefabInstance::typeDescription.typeName}).front(); + auto allSceneGraphNodes = createAllSceneGraphObjects(); + + moveScenegraphChildren(allSceneGraphNodes, prefabs[0]); + moveScenegraphChildren({prefabInstance}, prefabs[1]); + + commandInterface.set({prefabInstance, &PrefabInstance::template_}, prefabs[0]); + + viewModel_->buildObjectTree(); + + for (const auto &instChild : prefabs[1]->children_->asVector()[0]->children_->asVector()) { + auto sceneObjIndex = viewModel_->indexFromTreeNodeID(instChild->objectID()); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({sceneObjIndex})); + } +} + +TEST_F(ObjectTreeViewPrefabModelTest, DuplicationDifferentNodesInDifferentPrefabsCanNotBeDuplicated) { + auto prefabs = createNodes(Prefab::typeDescription.typeName, {"p1", "p2"}); + auto nodes = createNodes(Node::typeDescription.typeName, {"n11", "n12", "n21"}); + + + moveScenegraphChildren({nodes[0]}, prefabs[0]); + moveScenegraphChildren({nodes[1]}, nodes[0]); + moveScenegraphChildren({nodes[2]}, prefabs[1]); + + viewModel_->buildObjectTree(); + + auto prefab1Child = viewModel_->indexFromTreeNodeID(nodes[1]->objectID()); + auto prefab2Child = viewModel_->indexFromTreeNodeID(nodes[2]->objectID()); + + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({prefab1Child, prefab2Child})); +} + +TEST_F(ObjectTreeViewPrefabModelTest, DuplicationCanNotDuplicateNothing) { + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({})); } \ No newline at end of file diff --git a/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp b/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp index 3e4cafdd..0cd987e1 100644 --- a/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp +++ b/gui/libObjectTree/tests/ObjectTreeViewResourceModel_test.cpp @@ -83,6 +83,30 @@ TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsResourcesAreAllowedOnTopLevel } } +TEST_F(ObjectTreeViewResourceModelTest, DuplicationCanNotDuplicateNothing) { + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({})); +} + +TEST_F(ObjectTreeViewResourceModelTest, DuplicationResourcesCanBeDuplicated) { + for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { + if (typeInfo.description.isResource) { + auto newObj = createNodes(typeName, {typeName}).front(); + ASSERT_TRUE(viewModel_->canDuplicateAtIndices({viewModel_->indexFromTreeNodeID(newObj->objectID())})); + } + } +} + +TEST_F(ObjectTreeViewResourceModelTest, DuplicationExtRefResourcesCanNotBeDuplicated) { + for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { + if (typeInfo.description.isResource) { + auto newObj = createNodes(typeName, {typeName}).front(); + newObj->addAnnotation(std::make_shared("differentProject")); + + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({viewModel_->indexFromTreeNodeID(newObj->objectID())})); + } + } +} + TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsResourcesAreAllowedOnTopLevelAsExtRefs) { for (const auto &[typeName, typeInfo] : viewModel_->objectFactory()->getTypes()) { auto newObj = viewModel_->objectFactory()->createObject(typeName); @@ -137,14 +161,14 @@ TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsSceneGraphObjectsAreNotAllowe } TEST_F(ObjectTreeViewResourceModelTest, PastePastingMaterialsUnderMaterialCreatesMaterialOnTopLevel) { - createNodes(Material::typeDescription.typeName, {Material::typeDescription.typeName}).front(); - auto materialIndex = viewModel_->indexFromTreeNodeID(Material::typeDescription.typeName + Material::typeDescription.typeName); + auto material = createNodes(Material::typeDescription.typeName, {Material::typeDescription.typeName}).front(); + auto materialIndex = viewModel_->indexFromTreeNodeID(material->objectID()); 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); + materialIndex = viewModel_->indexFromTreeNodeID(material->objectID()); auto [parsedObjs, sourceProjectTopLevelObjectIds] = viewModel_->getObjectsAndRootIdsFromClipboardString(cutObjs); ASSERT_FALSE(viewModel_->canPasteIntoIndex(materialIndex, parsedObjs, sourceProjectTopLevelObjectIds)); @@ -153,7 +177,7 @@ TEST_F(ObjectTreeViewResourceModelTest, PastePastingMaterialsUnderMaterialCreate viewModel_->pasteObjectAtIndex(materialIndex, false, nullptr, cutObjs); dataChangeDispatcher_->dispatch(recorder.release()); - materialIndex = viewModel_->indexFromTreeNodeID(Material::typeDescription.typeName + Material::typeDescription.typeName); + materialIndex = viewModel_->indexFromTreeNodeID(material->objectID()); ASSERT_TRUE(materialIndex.isValid()); ASSERT_EQ(viewModel_->indexToTreeNode(materialIndex)->childCount(), 0); ASSERT_EQ(viewModel_->project()->instances().size(), 2); @@ -192,7 +216,7 @@ TEST_F(ObjectTreeViewResourceModelTest, AllowedObjsNoSceneGraphObjectsAreAllowed extRefMesh->addAnnotation(std::make_shared("differentProject")); viewModel_->buildObjectTree(); - auto extRefMeshIndex = viewModel_->indexFromTreeNodeID(Mesh::typeDescription.typeName + Mesh::typeDescription.typeName); + auto extRefMeshIndex = viewModel_->indexFromTreeNodeID(extRefMesh->objectID()); auto extRefGroupIndex = viewModel_->index(0, 0); ASSERT_EQ(viewModel_->indexToTreeNode(extRefGroupIndex)->getType(), raco::object_tree::model::ObjectTreeNodeType::ExtRefGroup); @@ -213,6 +237,7 @@ TEST_F(ObjectTreeViewResourceModelTest, CanNotDoAnythingButPasteWithExtRefGroup) ASSERT_FALSE(viewModel_->canDeleteAtIndices({extRefGroupIndex})); ASSERT_FALSE(viewModel_->canCopyAtIndices({extRefGroupIndex})); + ASSERT_FALSE(viewModel_->canDuplicateAtIndices({extRefGroupIndex})); dataChangeDispatcher_->dispatch(recorder.release()); auto copiedObjs = commandInterface.copyObjects({extRefMesh}); diff --git a/gui/libPropertyBrowser/CMakeLists.txt b/gui/libPropertyBrowser/CMakeLists.txt index 6240f62c..5c9a543f 100644 --- a/gui/libPropertyBrowser/CMakeLists.txt +++ b/gui/libPropertyBrowser/CMakeLists.txt @@ -24,13 +24,14 @@ add_library(libPropertyBrowser include/property_browser/editors/DoubleEditor.h src/editors/DoubleEditor.cpp include/property_browser/editors/EnumerationEditor.h src/editors/EnumerationEditor.cpp include/property_browser/editors/IntEditor.h src/editors/IntEditor.cpp + include/property_browser/editors/Int64Editor.h src/editors/Int64Editor.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 include/property_browser/editors/URIEditor.h src/editors/URIEditor.cpp - include/property_browser/editors/VecNTEditor.h + include/property_browser/editors/VecNTEditor.h src/editors/VecNTEditor.cpp include/property_browser/controls/ExpandButton.h src/controls/ExpandButton.cpp include/property_browser/controls/MouseWheelGuard.h src/controls/MouseWheelGuard.cpp diff --git a/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h b/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h index 10a451ea..b230531c 100644 --- a/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h +++ b/gui/libPropertyBrowser/include/property_browser/PropertyBrowserItem.h @@ -41,6 +41,7 @@ class PropertyBrowserItem final : public QObject { PropertyBrowserItem(raco::core::ValueHandle valueHandle, raco::components::SDataChangeDispatcher dispatcher, raco::core::CommandInterface* commandInterface, PropertyBrowserModel *model, QObject* parent = nullptr); raco::core::PrimitiveType type() const noexcept; + std::string luaTypeName() const noexcept; std::string displayName() const noexcept; size_t size() noexcept; raco::core::Queries::LinkState linkState() const noexcept; diff --git a/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h b/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h index ed03f966..73a7d8bf 100644 --- a/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h +++ b/gui/libPropertyBrowser/include/property_browser/PropertySubtreeView.h @@ -19,7 +19,6 @@ #include "property_browser/PropertyBrowserLayouts.h" #include "property_browser/PropertyBrowserModel.h" #include "property_browser/PropertySubtreeChildrenContainer.h" -#include "property_browser/controls/ExpandButton.h" namespace raco::property_browser { class PropertyControl; @@ -52,7 +51,7 @@ public Q_SLOTS: PropertyBrowserItem* item_{nullptr}; PropertyBrowserModel* model_ {nullptr}; PropertyBrowserGridLayout layout_{nullptr}; - QWidget* button_{nullptr}; + QWidget* decorationWidget_{nullptr}; QLabel* label_{nullptr}; QWidget* propertyControl_{nullptr}; PropertySubtreeChildrenContainer* childrenContainer_{nullptr}; diff --git a/gui/libPropertyBrowser/include/property_browser/controls/ExpandButton.h b/gui/libPropertyBrowser/include/property_browser/controls/ExpandButton.h index b33212f1..394914f1 100644 --- a/gui/libPropertyBrowser/include/property_browser/controls/ExpandButton.h +++ b/gui/libPropertyBrowser/include/property_browser/controls/ExpandButton.h @@ -9,20 +9,19 @@ */ #pragma once + #include -#include namespace raco::property_browser { class PropertyBrowserItem; -class ExpandControlButton final : public QPushButton { +class ExpandButton final : public QPushButton { Q_OBJECT public: - explicit ExpandControlButton(PropertyBrowserItem* item, QWidget* parent = nullptr); - + explicit ExpandButton(PropertyBrowserItem* item, QWidget* parent = nullptr); -private Q_SLOTS: + private Q_SLOTS: void updateIcon(bool expanded); }; diff --git a/gui/libPropertyBrowser/include/property_browser/controls/ScalarSlider.h b/gui/libPropertyBrowser/include/property_browser/controls/ScalarSlider.h index 14320e7c..b6ba48e0 100644 --- a/gui/libPropertyBrowser/include/property_browser/controls/ScalarSlider.h +++ b/gui/libPropertyBrowser/include/property_browser/controls/ScalarSlider.h @@ -25,7 +25,7 @@ class ScalarSlider : public QWidget { T value() const noexcept; bool insideRange() const noexcept; - void setRange(T min, T max); + void setSoftRange(T min, T max); protected: void slotSetValue(T value); @@ -37,18 +37,19 @@ class ScalarSlider : public QWidget { void mouseReleaseEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; virtual void signalValueEdited(T value) = 0; - virtual void signalValueChange(T value) = 0; virtual void signalSingleClicked() = 0; private: void addValue(T step); - T min_{static_cast(0.0)}; - T max_{static_cast(1.0)}; - T value_{static_cast(0.5)}; + T min_; + T max_; + T softMin_; + T softMax_; + T value_; T stepSize_{static_cast(std::is_integral::value ? 1 : 0.1)}; bool mouseIsDragging_{false}; bool mouseIsInside_{false}; - QPoint mousePivot_; + QPointF mousePivot_; int mouseDraggingCurrentOffsetX_; }; @@ -59,7 +60,6 @@ class DoubleSlider final : public ScalarSlider { Q_SIGNALS: void valueEdited(double value); - void valueChanged(double value); void singleClicked(); public Q_SLOTS: void setValue(double v) { slotSetValue(v); } @@ -68,9 +68,6 @@ public Q_SLOTS: void signalValueEdited(double value) override { Q_EMIT valueEdited(value); } - void signalValueChange(double value) override { - Q_EMIT valueChanged(value); - } void signalSingleClicked() override { Q_EMIT singleClicked(); } @@ -82,7 +79,6 @@ class IntSlider final : public ScalarSlider { explicit IntSlider(QWidget* parent) : ScalarSlider{parent} {} Q_SIGNALS: - void valueChanged(int value); void valueEdited(int value); void singleClicked(); public Q_SLOTS: @@ -92,8 +88,25 @@ public Q_SLOTS: void signalValueEdited(int value) override { Q_EMIT valueEdited(value); } - void signalValueChange(int value) override { - Q_EMIT valueChanged(value); + void signalSingleClicked() override { + Q_EMIT singleClicked(); + } +}; + +class Int64Slider final : public ScalarSlider { + Q_OBJECT +public: + explicit Int64Slider(QWidget* parent) : ScalarSlider{parent} {} + +Q_SIGNALS: + void valueEdited(int64_t value); + void singleClicked(); +public Q_SLOTS: + void setValue(int64_t v) { slotSetValue(v); } + +protected: + void signalValueEdited(int64_t value) override { + Q_EMIT valueEdited(value); } void signalSingleClicked() override { Q_EMIT singleClicked(); diff --git a/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h b/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h index b698b34c..8dc3c90f 100644 --- a/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h +++ b/gui/libPropertyBrowser/include/property_browser/controls/SpinBox.h @@ -9,7 +9,9 @@ */ #pragma once +#include "data_storage/Value.h" #include "property_browser/PropertyBrowserLayouts.h" + #include #include #include @@ -18,128 +20,57 @@ namespace raco::property_browser { template -struct SpinBoxTraits { - typedef QSpinBox BaseType; -}; - -template <> -struct SpinBoxTraits { - typedef QDoubleSpinBox BaseType; -}; - -template <> -struct SpinBoxTraits { - typedef QSpinBox BaseType; -}; - -template -std::optional evaluateLuaExpression(QString expression); +std::optional evaluateLuaExpression(QString expression, T min = raco::data_storage::numericalLimitMin(), T max = raco::data_storage::numericalLimitMax()); template -class InternalSpinBox : public SpinBoxTraits::BaseType { +class InternalSpinBox : public QAbstractSpinBox { public: - InternalSpinBox(QWidget* parent = nullptr) : SpinBoxTraits::BaseType(parent) { - this->setKeyboardTracking(false); - this->setCorrectionMode(QAbstractSpinBox::CorrectionMode::CorrectToNearestValue); - } + InternalSpinBox(QWidget* parent, std::function valueChanged); - virtual ~InternalSpinBox() {} - QValidator::State validate(QString& input, int& pos) const override { - return QValidator::Acceptable; - } + QString textFromValue(T value) const; + T valueFromText(const QString& text) const; - QString textFromValue(T value) const { - return SpinBoxTraits::BaseType::textFromValue(value); - } + T value() const; + void setValue(T value); - T valueFromText(const QString& text) const { - return tryGetValueFromText(text).value_or(SpinBoxTraits::BaseType::value()); - } + void setRange(T min, T max); + + void stepBy(int steps) override; + QAbstractSpinBox::StepEnabled stepEnabled() const override; protected: + T value_; + T min_; + T max_; T focusInOldValue_; + std::function valueChanged_; - void focusInEvent(QFocusEvent* event) { - this->selectAll(); - focusInOldValue_ = SpinBoxTraits::BaseType::value(); - SpinBoxTraits::BaseType::focusInEvent(event); - } - - void keyPressEvent(QKeyEvent* event) { - SpinBoxTraits::BaseType::keyPressEvent(event); - - if (event->key() == Qt::Key_Escape) { - SpinBoxTraits::BaseType::setValue(focusInOldValue_); - SpinBoxTraits::BaseType::clearFocus(); - } - } - - std::optional tryGetValueFromText(const QString& text) const { - return evaluateLuaExpression(text); - } -}; - -template <> -inline QString InternalSpinBox::textFromValue(double value) const { - return QLocale(QLocale::C).toString(value, 'f', QLocale::FloatingPointShortest); + void focusInEvent(QFocusEvent* event); + void keyPressEvent(QKeyEvent* event); }; template class SpinBox : public QWidget { public: - SpinBox(QWidget* parent = nullptr) : QWidget{parent} { - valueChangedConnection_ = QObject::connect(&widget_, qOverload(&SpinBoxTraits::BaseType::valueChanged), this, [this]() { emitValueChanged(widget_.value()); }); - QObject::connect(&widget_, &SpinBoxTraits::BaseType::editingFinished, this, [this]() { emitEditingFinished(); }); - layout_.addWidget(&widget_); - widget_.setRange(min_, max_); - // Disable QSpinBox sizing based on range - widget_.setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - setFocusPolicy(Qt::FocusPolicy::StrongFocus); - setFocusProxy(&widget_); - } - - void setValue(T v) { - QObject::disconnect(valueChangedConnection_); - widget_.setValue(v); - valueChangedConnection_ = QObject::connect(&widget_, qOverload(&SpinBoxTraits::BaseType::valueChanged), this, [this]() { emitValueChanged(widget_.value()); }); - } - - int outOfRange() const noexcept { - return false; - // todo: disabled range check until we have full range handling - //return value() < min_ ? -1 : (value() > max_ ? 1 : 0); - } - - T value() const noexcept { - return widget_.value(); - } - - void setRange(T min, T max) { - min_ = min; - max_ = max; - } - - void keyPressEvent(QKeyEvent* event) { - QWidget::keyPressEvent(event); - - if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { - widget_.clearFocus(); - emitFocusNextRequested(); - } - } + SpinBox(QWidget* parent = nullptr); + + void setValue(T v); + T value() const noexcept; + + int outOfRange() const noexcept; + void setSoftRange(T min, T max); + + void keyPressEvent(QKeyEvent* event); protected: virtual void emitValueChanged(T value) = 0; virtual void emitEditingFinished() = 0; virtual void emitFocusNextRequested() = 0; - InternalSpinBox widget_{this}; + InternalSpinBox widget_; private: - QMetaObject::Connection valueChangedConnection_; - PropertyBrowserGridLayout layout_{this}; - T min_{std::numeric_limits::lowest()}; - T max_{std::numeric_limits::max()}; + PropertyBrowserGridLayout layout_; }; class DoubleSpinBox final : public SpinBox { @@ -164,7 +95,9 @@ class IntSpinBox final : public SpinBox { public: // Property used in RaCoStyle to draw error colors (careful we depend on hierarchy as the actual element which is drawen is SpinBox->QSpinBox->QLineEdit) Q_PROPERTY(int outOfRange READ outOfRange); + IntSpinBox(QWidget* parent = nullptr); + Q_SIGNALS: void valueChanged(int val); void editingFinished(); @@ -176,4 +109,22 @@ class IntSpinBox final : public SpinBox { void emitFocusNextRequested() override; }; +class Int64SpinBox final : public SpinBox { + Q_OBJECT +public: + // Property used in RaCoStyle to draw error colors (careful we depend on hierarchy as the actual element which is drawen is SpinBox->QSpinBox->QLineEdit) + Q_PROPERTY(int outOfRange READ outOfRange); + Int64SpinBox(QWidget* parent = nullptr); + +Q_SIGNALS: + void valueChanged(int64_t val); + void editingFinished(); + void focusNextRequested(); + +protected: + void emitValueChanged(int64_t value) override; + void emitEditingFinished() override; + void emitFocusNextRequested() override; +}; + } // namespace raco::property_browser diff --git a/gui/libCommonWidgets/include/common_widgets/LogWidget.h b/gui/libPropertyBrowser/include/property_browser/editors/Int64Editor.h similarity index 51% rename from gui/libCommonWidgets/include/common_widgets/LogWidget.h rename to gui/libPropertyBrowser/include/property_browser/editors/Int64Editor.h index f5cfb910..a0ce5859 100644 --- a/gui/libCommonWidgets/include/common_widgets/LogWidget.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/Int64Editor.h @@ -9,22 +9,22 @@ */ #pragma once -#include -#include -#include +#include "PropertyEditor.h" -namespace raco::common_widgets { +class QStackedWidget; -class LogWidgetSink; +namespace raco::property_browser { -class LogWidget final : public QTableView { +class PropertyBrowserItem; + +class Int64Editor final : public PropertyEditor { public: - explicit LogWidget(QWidget* parent = nullptr); - ~LogWidget(); + explicit Int64Editor( + PropertyBrowserItem* item, + QWidget* parent = nullptr); -private: - QStandardItemModel model_; - std::shared_ptr sink_; +protected: + QStackedWidget* stack_; }; -} // namespace raco::common_widgets +} // namespace raco::property_browser diff --git a/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h b/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h index 7ff379b3..62f1ca0d 100644 --- a/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h +++ b/gui/libPropertyBrowser/include/property_browser/editors/VecNTEditor.h @@ -12,7 +12,7 @@ #include "PropertyEditor.h" #include "common_widgets/PropertyBrowserButton.h" #include "core/Queries.h" -#include "data_storage/BasicTypes.h" +#include "core/BasicTypes.h" #include "property_browser/PropertyBrowserItem.h" #include "property_browser/PropertyBrowserLayouts.h" #include "property_browser/controls/SpinBox.h" @@ -31,98 +31,15 @@ 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)); - } + VecNTEditorColorPickerButton(PropertyBrowserItem* item, QWidget* parent); + void paintEvent(QPaintEvent* event) override; 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()}); + void showColorPicker(); }; template @@ -147,105 +64,21 @@ class VecNTEditor final : public PropertyEditor { public: explicit VecNTEditor( PropertyBrowserItem* item, - QWidget* parent = nullptr) - : 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>()) { - spinboxes_[i]->setRange(*rangeAnnotation->min_, *rangeAnnotation->max_); - } - spinboxes_[i]->setValue(item->children().at(i)->valueHandle().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, this, &VecNTEditor::updateSpinBoxesAndColorPicker); - QObject::connect(item, &PropertyBrowserItem::childrenChanged, this, [this](const QList& children) { - for (int i = 0; i < N; i++) { - QObject::connect(children.at(i), &PropertyBrowserItem::valueChanged, this, &VecNTEditor::updateSpinBoxesAndColorPicker); - } - }); - - setupColorPicker(item); - - QObject::connect(item, &PropertyBrowserItem::expandedChanged, this, &VecNTEditor::setExpandedMode); - setExpandedMode(item->expanded()); - } + QWidget* parent = nullptr); public Q_SLOTS: - void setEnabled(bool val) { - for (auto* child : layout()->children()) { - if (auto* widget{dynamic_cast(child)}) { - widget->setEnabled(val); - } - } - } - - 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(); - } + void setEnabled(bool val); + virtual void setExpandedMode(bool expanded); + void updateSpinBoxesAndColorPicker(); protected: QPushButton* colorPickerButton_ = nullptr; std::array, N> spinboxes_; void setupColorPicker(PropertyBrowserItem* item); - void updateColorPicker() { - if (colorPickerButton_) { - auto* button = static_cast*>(colorPickerButton_); - button->update(); - } - } + void updateColorPicker(); }; -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 99d6b742..79e35809 100644 --- a/gui/libPropertyBrowser/src/PropertyBrowserItem.cpp +++ b/gui/libPropertyBrowser/src/PropertyBrowserItem.cpp @@ -14,11 +14,12 @@ #include "core/Project.h" #include "core/PropertyDescriptor.h" #include "core/Queries.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "log_system/log.h" #include "property_browser/PropertyBrowserRef.h" #include "user_types/RenderPass.h" #include "user_types/MeshNode.h" +#include "user_types/EngineTypeAnnotation.h" using raco::log_system::PROPERTY_BROWSER; using raco::data_storage::PrimitiveType; @@ -126,6 +127,13 @@ core::PrimitiveType PropertyBrowserItem::type() const noexcept { return valueHandle_.type(); } +std::string PropertyBrowserItem::luaTypeName() const noexcept { + if (auto anno = valueHandle_.constValueRef()->query()) { + return commandInterface_->engineInterface().luaNameForPrimitiveType(static_cast(*anno->engineType_)); + } + return {}; +} + PropertyBrowserRef* PropertyBrowserItem::refItem() noexcept { return refItem_; } @@ -134,8 +142,7 @@ PropertyBrowserModel* PropertyBrowserItem::model() const noexcept { return model_; } -const QList& PropertyBrowserItem::children() -{ +const QList& PropertyBrowserItem::children() { return children_; } @@ -201,8 +208,7 @@ bool PropertyBrowserItem::editable() noexcept { } bool PropertyBrowserItem::expandable() const noexcept { - // Currently the only item which could be expanded but is not allowed to do so is the tag container. - return valueHandle_.isObject() || !(query() || query()); + return valueHandle_.isObject() || !(query() || query()); } bool PropertyBrowserItem::showChildren() const { @@ -361,7 +367,7 @@ void PropertyBrowserItem::syncChildrenWithValueHandle() { bool PropertyBrowserItem::canBeChosenByColorPicker() const { - if(valueHandle_.isObject() || !(valueHandle_.type() == PrimitiveType::Vec3f || valueHandle_.type() == PrimitiveType::Vec4f)) { + if(valueHandle_.isObject() || !(valueHandle_.isVec3f() || valueHandle_.isVec4f())) { return false; } diff --git a/gui/libPropertyBrowser/src/PropertyBrowserWidget.cpp b/gui/libPropertyBrowser/src/PropertyBrowserWidget.cpp index 23b8bb7d..b26b0765 100644 --- a/gui/libPropertyBrowser/src/PropertyBrowserWidget.cpp +++ b/gui/libPropertyBrowser/src/PropertyBrowserWidget.cpp @@ -157,7 +157,7 @@ PropertyBrowserWidget::PropertyBrowserWidget( lockButton_ = new QPushButton{this}; lockButton_->setContentsMargins(0, 0, 0, 0); lockButton_->setFlat(true); - lockButton_->setIcon(Icons::icon(Pixmap::unlocked, this)); + lockButton_->setIcon(Icons::instance().unlocked); lockButton_->connect(lockButton_, &QPushButton::clicked, this, [this]() { setLocked(!locked_); }); @@ -178,7 +178,7 @@ void PropertyBrowserWidget::clear() { void PropertyBrowserWidget::setLocked(bool locked) { locked_ = locked; - lockButton_->setIcon(locked_ ? Icons::icon(Pixmap::locked, this) : Icons::icon(Pixmap::unlocked, this)); + lockButton_->setIcon(locked_ ? Icons::instance().locked : Icons::instance().unlocked); } void PropertyBrowserWidget::clearValueHandle(bool restorable) { diff --git a/gui/libPropertyBrowser/src/PropertySubtreeView.cpp b/gui/libPropertyBrowser/src/PropertySubtreeView.cpp index 2a19a791..c1dcbae9 100644 --- a/gui/libPropertyBrowser/src/PropertySubtreeView.cpp +++ b/gui/libPropertyBrowser/src/PropertySubtreeView.cpp @@ -10,12 +10,16 @@ #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/controls/ExpandButton.h" +#include "property_browser/editors/LinkEditor.h" +#include "property_browser/editors/PropertyEditor.h" #include "property_browser/PropertyBrowserLayouts.h" #include "property_browser/WidgetFactory.h" -#include "property_browser/editors/LinkEditor.h" +#include "user_types/LuaScript.h" +#include "user_types/LuaScriptModule.h" + #include #include #include @@ -61,34 +65,39 @@ PropertySubtreeView::PropertySubtreeView(PropertyBrowserModel* model, PropertyBr label_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); if (item->expandable()) { - button_ = new ExpandControlButton{ item, labelContainer }; - } - else { - button_ = new QWidget{ this }; // Easier to make a dummy spacer widget than figuring out how to move everything else at the right place without it - button_->setFixedHeight(0); + decorationWidget_ = new ExpandButton(item, labelContainer); + } else { + // Easier to make a dummy spacer widget than figuring out how to move everything else at the right place without it + decorationWidget_ = new QWidget(this); + decorationWidget_->setFixedHeight(0); } - button_->setFixedWidth(28); + decorationWidget_->setFixedWidth(28); auto* linkControl = WidgetFactory::createLinkControl(item, labelContainer); propertyControl_ = WidgetFactory::createPropertyEditor(item, labelContainer); - labelLayout->addWidget(button_, 0); + labelLayout->addWidget(decorationWidget_, 0); labelLayout->addWidget(label_, 0); labelLayout->addWidget(linkControl, 1); + auto isLuaScriptProperty = !item->valueHandle().isObject() && &item->valueHandle().rootObject()->getTypeDescription() == &raco::user_types::LuaScript::typeDescription && !item->valueHandle().parent().isObject(); + if (isLuaScriptProperty) { + label_->setToolTip(QString::fromStdString(item->luaTypeName())); + } + linkControl->setControl(propertyControl_); label_->setEnabled(item->editable()); QObject::connect(item, &PropertyBrowserItem::editableChanged, label_, &QWidget::setEnabled); } else { // Dummy label for the case that we are an object. - button_ = new QWidget{this}; + decorationWidget_ = new QWidget{this}; label_ = new QLabel{this}; - button_->setFixedWidth(0); - button_->setFixedHeight(0); + decorationWidget_->setFixedWidth(0); + decorationWidget_->setFixedHeight(0); label_->setFixedWidth(0); label_->setFixedHeight(0); - labelLayout->addWidget(button_, 0); + labelLayout->addWidget(decorationWidget_, 0); labelLayout->addWidget(label_, 0); } @@ -194,15 +203,15 @@ void PropertySubtreeView::setLabelAreaWidth(int width) { void PropertySubtreeView::recalculateLabelWidth() { if (childrenContainer_ != nullptr) { - childrenContainer_->setOffset(button_->width()); + childrenContainer_->setOffset(decorationWidget_->width()); } - const int labelWidthHint = std::max(labelWidth_, getLabelAreaWidthHint() - button_->width()); + const int labelWidthHint = std::max(labelWidth_, getLabelAreaWidthHint() - decorationWidget_->width()); if (label_->width() != labelWidthHint) { label_->setFixedWidth(labelWidthHint); } for (const auto& child : childrenContainer_->findChildren(QString{}, Qt::FindDirectChildrenOnly)) { - child->setLabelAreaWidth(labelWidthHint - button_->width()); + child->setLabelAreaWidth(labelWidthHint - decorationWidget_->width()); } } @@ -216,7 +225,7 @@ int PropertySubtreeView::getLabelAreaWidthHint() const { } } } - return labelWidthHint + button_->width(); + return labelWidthHint + decorationWidget_->width(); } void PropertySubtreeView::paintEvent(QPaintEvent* event) { diff --git a/gui/libPropertyBrowser/src/WidgetFactory.cpp b/gui/libPropertyBrowser/src/WidgetFactory.cpp index f158e82e..a3c7b391 100644 --- a/gui/libPropertyBrowser/src/WidgetFactory.cpp +++ b/gui/libPropertyBrowser/src/WidgetFactory.cpp @@ -15,6 +15,7 @@ #include "property_browser/editors/DoubleEditor.h" #include "property_browser/editors/EnumerationEditor.h" #include "property_browser/editors/IntEditor.h" +#include "property_browser/editors/Int64Editor.h" #include "property_browser/editors/LinkEditor.h" #include "property_browser/editors/RefEditor.h" #include "property_browser/editors/StringEditor.h" @@ -52,24 +53,32 @@ PropertyEditor* WidgetFactory::createPropertyEditor(PropertyBrowserItem* item, Q } else { return new IntEditor{item, parent}; } + case PrimitiveType::Int64: + return new Int64Editor{item, parent}; + case PrimitiveType::Double: return new DoubleEditor{item, parent}; case PrimitiveType::String: return (item->query()) ? new URIEditor{item, parent} : new StringEditor{item, parent}; - case PrimitiveType::Vec2f: - return new Vec2fEditor{item, parent}; - case PrimitiveType::Vec3f: - return new Vec3fEditor{item, parent}; - case PrimitiveType::Vec4f: - return new Vec4fEditor{item, parent}; - case PrimitiveType::Vec2i: - return new Vec2iEditor{item, parent}; - case PrimitiveType::Vec3i: - return new Vec3iEditor{item, parent}; - case PrimitiveType::Vec4i: - return new Vec4iEditor{item, parent}; case PrimitiveType::Ref: return new RefEditor{item, parent}; + case PrimitiveType::Struct: { + auto typeDesc = &item->valueHandle().constValueRef()->asStruct().getTypeDescription(); + if (typeDesc == &core::Vec2f::typeDescription) { + return new Vec2fEditor{item, parent}; + } else if (typeDesc == &core::Vec3f::typeDescription) { + return new Vec3fEditor{item, parent}; + } else if (typeDesc == &core::Vec4f::typeDescription) { + return new Vec4fEditor{item, parent}; + } else if (typeDesc == &core::Vec2i::typeDescription) { + return new Vec2iEditor{item, parent}; + } else if (typeDesc == &core::Vec3i::typeDescription) { + return new Vec3iEditor{item, parent}; + } else if (typeDesc == &core::Vec4i::typeDescription) { + return new Vec4iEditor{item, parent}; + } + return nullptr; + } case PrimitiveType::Table: if (item->query() || item->query()) { return new TagContainerEditor{ item, parent }; diff --git a/gui/libPropertyBrowser/src/controls/ExpandButton.cpp b/gui/libPropertyBrowser/src/controls/ExpandButton.cpp index 80b03604..65c8e4c2 100644 --- a/gui/libPropertyBrowser/src/controls/ExpandButton.cpp +++ b/gui/libPropertyBrowser/src/controls/ExpandButton.cpp @@ -17,13 +17,11 @@ #include "property_browser/PropertyBrowserItem.h" namespace raco::property_browser { - using namespace ::raco::style; -ExpandControlButton::ExpandControlButton(PropertyBrowserItem* item, QWidget* parent) +ExpandButton::ExpandButton(PropertyBrowserItem* item, QWidget* parent) : QPushButton{parent} { - auto icon = Icons::icon(Pixmap::collapsed, this); - setIcon(icon); + setIcon(Icons::instance().collapsed); setContentsMargins(0, 0, 0, 0); setFlat(true); @@ -35,7 +33,7 @@ ExpandControlButton::ExpandControlButton(PropertyBrowserItem* item, QWidget* par setVisible(false); setMaximumHeight(0); } - + QObject::connect(item, &PropertyBrowserItem::childrenChanged, this, [this](const auto& children) { if (children.size() > 0) { setMaximumHeight(QWIDGETSIZE_MAX); @@ -46,7 +44,7 @@ ExpandControlButton::ExpandControlButton(PropertyBrowserItem* item, QWidget* par } }); - QObject::connect(this, &ExpandControlButton::clicked, this, [this, item]() { + QObject::connect(this, &ExpandButton::clicked, this, [this, item]() { if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::KeyboardModifier::ShiftModifier)) { item->setExpandedRecursively(!item->expanded()); } else { @@ -54,15 +52,15 @@ ExpandControlButton::ExpandControlButton(PropertyBrowserItem* item, QWidget* par } }); - QObject::connect(item, &PropertyBrowserItem::expandedChanged, this, &ExpandControlButton::updateIcon); + QObject::connect(item, &PropertyBrowserItem::expandedChanged, this, &ExpandButton::updateIcon); updateIcon(item->expanded()); } -void ExpandControlButton::updateIcon(bool expanded) { +void ExpandButton::updateIcon(bool expanded) { if (expanded) { - setIcon(Icons::icon(Pixmap::expanded, this)); + setIcon(Icons::instance().expanded); } else { - setIcon(Icons::icon(Pixmap::collapsed, this)); + setIcon(Icons::instance().collapsed); } } diff --git a/gui/libPropertyBrowser/src/controls/ScalarSlider.cpp b/gui/libPropertyBrowser/src/controls/ScalarSlider.cpp index a1057d31..d2fa114a 100644 --- a/gui/libPropertyBrowser/src/controls/ScalarSlider.cpp +++ b/gui/libPropertyBrowser/src/controls/ScalarSlider.cpp @@ -7,11 +7,15 @@ * 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 "data_storage/Value.h" #include "property_browser/controls/ScalarSlider.h" #include "style/Colors.h" #include "style/Icons.h" #include "style/RaCoStyle.h" +#include "log_system/log.h" +#include #include #include #include @@ -25,7 +29,12 @@ using namespace raco::style; template ScalarSlider::ScalarSlider(QWidget* parent) - : QWidget{parent} { + : QWidget{parent}, + min_(raco::data_storage::numericalLimitMin()), + max_(raco::data_storage::numericalLimitMax()), + softMin_(static_cast(0.0)), + softMax_(static_cast(1.0)), + value_(static_cast(0.5)) { static_assert(std::is_integral::value || std::is_floating_point::value, "ScalarSlider requires integral or floating point type"); // adjust size to LineEdits @@ -47,18 +56,19 @@ bool ScalarSlider::insideRange() const noexcept { } template -void ScalarSlider::setRange(T min, T max) { - min_ = min; - max_ = max; +void ScalarSlider::setSoftRange(T min, T max) { + softMin_ = min; + softMax_ = max; update(); } template -void ScalarSlider::slotSetValue(T v) { - if (value_ != v) { - value_ = v; +void ScalarSlider::slotSetValue(T value) { + value = std::clamp(value, min_, max_); + + if (value_ != value) { + value_ = value; update(); - signalValueChange(v); } } @@ -90,7 +100,7 @@ void ScalarSlider::paintEvent(QPaintEvent* event) { roundStyle->drawRoundedRect(rect(), &painter, backgroundBrush); auto valueRect{rect()}; - valueRect.setWidth(((static_cast(value_) - min_) / (max_ - min_)) * valueRect.width()); + valueRect.setWidth(((static_cast(value_) - softMin_) / (softMax_ - softMin_)) * valueRect.width()); painter.setClipRect(valueRect); roundStyle->drawRoundedRect(rect(), &painter, sliderBrush); @@ -101,8 +111,8 @@ void ScalarSlider::paintEvent(QPaintEvent* event) { painter.drawText(rect(), Qt::AlignHCenter | Qt::AlignVCenter, text); if (mouseIsInside_ && !mouseIsDragging_) { - painter.drawPixmap(rect().x() - 4, rect().y() - 2, Icons::pixmap(Pixmap::decrement)); - painter.drawPixmap(rect().width() - rect().height(), rect().y() - 2, Icons::pixmap(Pixmap::increment)); + Icons::instance().decrement.paint(&painter, {rect().x() - 4, rect().y() - 2, 20, 20}); + Icons::instance().increment.paint(&painter, {rect().width() - rect().height(), rect().y() - 2, 20, 20}); } } @@ -135,7 +145,7 @@ template void ScalarSlider::mousePressEvent(QMouseEvent* event) { if (isEnabled()) { setFocus(Qt::FocusReason::MouseFocusReason); - mousePivot_ = event->globalPos(); + mousePivot_ = event->screenPos(); mouseDraggingCurrentOffsetX_ = 0; setCursor(Qt::CursorShape::BlankCursor); update(); @@ -144,9 +154,18 @@ void ScalarSlider::mousePressEvent(QMouseEvent* event) { template void ScalarSlider::addValue(T step) { - T newValue = value() + step; + auto newValue = value(); + + // Add step to newValue, clamping if their is an overflow. + if (newValue > 0 && step > max_ - newValue) { + newValue = max_; + } else if (newValue < 0 && step < min_ - newValue) { + newValue = min_; + } else { + newValue += step; + } + slotSetValue(newValue); - signalValueEdited(newValue); } template @@ -154,8 +173,10 @@ void ScalarSlider::mouseReleaseEvent(QMouseEvent* event) { if (!mouseIsDragging_) { if (event->localPos().x() < rect().x() + 24l) { addValue(-1); + signalValueEdited(value()); } else if (event->localPos().x() > rect().right() - 24l) { addValue(1); + signalValueEdited(value()); } else { signalSingleClicked(); } @@ -170,20 +191,21 @@ template void ScalarSlider::mouseMoveEvent(QMouseEvent* event) { if (hasFocus()) { mouseIsDragging_ = true; - mouseDraggingCurrentOffsetX_ += event->globalPos().x() - mousePivot_.x(); - cursor().setPos(mousePivot_.x(), mousePivot_.y()); + mouseDraggingCurrentOffsetX_ += event->screenPos().x() - mousePivot_.x(); + cursor().setPos(screen() , mousePivot_.x(), mousePivot_.y()); double widgetFraction = (mouseDraggingCurrentOffsetX_ / static_cast(rect().width())); - T newValue = value() + static_cast((max_ - min_) * widgetFraction); - if (newValue != value()) { + T valueChange = static_cast((softMax_ - softMin_) * widgetFraction); + if (valueChange != 0) { mouseDraggingCurrentOffsetX_ -= static_cast(widgetFraction * rect().width()); - slotSetValue(newValue); - signalValueEdited(newValue); + addValue(valueChange); + signalValueEdited(value()); } } } template class ScalarSlider; template class ScalarSlider; +template class ScalarSlider; } // namespace raco::property_browser diff --git a/gui/libPropertyBrowser/src/controls/SpinBox.cpp b/gui/libPropertyBrowser/src/controls/SpinBox.cpp index 7b6d314e..76d3c7d1 100644 --- a/gui/libPropertyBrowser/src/controls/SpinBox.cpp +++ b/gui/libPropertyBrowser/src/controls/SpinBox.cpp @@ -12,47 +12,189 @@ #include "log_system/log.h" -#include "lualib.h" -#include "lauxlib.h" #include "lapi.h" +#include "lauxlib.h" +#include "lualib.h" +#include namespace raco::property_browser { const int EVALUATE_LUA_EXECUTION_LIMIT = 1000; -raco::property_browser::IntSpinBox::IntSpinBox(QWidget* parent) : SpinBox{parent} {} +template +InternalSpinBox::InternalSpinBox(QWidget* parent, std::function valueChanged) + : QAbstractSpinBox(parent), + min_(raco::data_storage::numericalLimitMin()), + max_(raco::data_storage::numericalLimitMax()), + valueChanged_(valueChanged) { + this->setKeyboardTracking(false); + this->setCorrectionMode(QAbstractSpinBox::CorrectionMode::CorrectToNearestValue); + connect(lineEdit(), &QLineEdit::editingFinished, this, [this]() { + setValue(valueFromText(lineEdit()->text())); + }); +} + +template +QString InternalSpinBox::textFromValue(T value) const { + return QLocale(QLocale::C).toString(value); +} + +template +T InternalSpinBox::valueFromText(const QString& text) const { + return evaluateLuaExpression(text, min_, max_).value_or(value()); +} + +template +T InternalSpinBox::value() const { + return value_; +} + +template +void InternalSpinBox::setValue(T value) { + value = std::clamp(value, min_, max_); + + if (value_ != value) { + lineEdit()->setText(textFromValue(value)); + value_ = value; + valueChanged_(value); + } +} + +template +void InternalSpinBox::setRange(T min, T max) { + min_ = min; + max_ = max; +} + +template +void InternalSpinBox::stepBy(int steps) { + setValue(valueFromText(lineEdit()->text()) + steps); +} + +template +QAbstractSpinBox::StepEnabled InternalSpinBox::stepEnabled() const { + QAbstractSpinBox::StepEnabled flags; + if (value_ != max_) { + flags |= StepUpEnabled; + } + if (value_ != min_) { + flags |= StepDownEnabled; + } + return flags; +} + +template +void InternalSpinBox::focusInEvent(QFocusEvent* event) { + this->selectAll(); + focusInOldValue_ = value(); + QAbstractSpinBox::focusInEvent(event); +} + +template +void InternalSpinBox::keyPressEvent(QKeyEvent* event) { + QAbstractSpinBox::keyPressEvent(event); + + if (event->key() == Qt::Key_Escape) { + setValue(focusInOldValue_); + QAbstractSpinBox::clearFocus(); + } +} +template +SpinBox::SpinBox(QWidget* parent) + : QWidget(parent), + widget_(this, [this](T newValue) { emitValueChanged(newValue); }), + layout_(this) { + QObject::connect(&widget_, &QAbstractSpinBox::editingFinished, this, [this]() { emitEditingFinished(); }); + layout_.addWidget(&widget_); + + // Disable QSpinBox sizing based on range + widget_.setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + setFocusPolicy(Qt::FocusPolicy::StrongFocus); + setFocusProxy(&widget_); +} + +template +void SpinBox::setValue(T v) { + widget_.setValue(v); +} + +template +int SpinBox::outOfRange() const noexcept { + return false; + // TODO: disabled range check until we have full range handling + // return value() < min_ ? -1 : (value() > max_ ? 1 : 0); +} + +template +void SpinBox::setSoftRange(T min, T max) { + // TODO: disabled range check until we have full range handling + // widget_.setRange(min, max); +} + +template +T SpinBox::value() const noexcept { + return widget_.value(); +} + +template +void SpinBox::keyPressEvent(QKeyEvent* event) { + QWidget::keyPressEvent(event); -void raco::property_browser::IntSpinBox::emitValueChanged(int value) { + if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { + widget_.clearFocus(); + emitFocusNextRequested(); + } +} + +template <> +QString InternalSpinBox::textFromValue(double value) const { + return QLocale(QLocale::C).toString(value, 'f', QLocale::FloatingPointShortest); +}; + +DoubleSpinBox::DoubleSpinBox(QWidget* parent) : SpinBox(parent) { +} + +void DoubleSpinBox::emitValueChanged(double value) { Q_EMIT valueChanged(value); } -void raco::property_browser::IntSpinBox::emitEditingFinished() { +void DoubleSpinBox::emitEditingFinished() { Q_EMIT editingFinished(); } -void raco::property_browser::IntSpinBox::emitFocusNextRequested() { +void DoubleSpinBox::emitFocusNextRequested() { Q_EMIT focusNextRequested(); } -raco::property_browser::DoubleSpinBox::DoubleSpinBox(QWidget* parent) : SpinBox{parent} { - widget_.setDecimals(5); -} +IntSpinBox::IntSpinBox(QWidget* parent) : SpinBox(parent) {} -void raco::property_browser::DoubleSpinBox::emitValueChanged(double value) { +void IntSpinBox::emitValueChanged(int value) { Q_EMIT valueChanged(value); } -void raco::property_browser::DoubleSpinBox::emitEditingFinished() { +void IntSpinBox::emitEditingFinished() { Q_EMIT editingFinished(); } -void raco::property_browser::DoubleSpinBox::emitFocusNextRequested() { +void IntSpinBox::emitFocusNextRequested() { Q_EMIT focusNextRequested(); } +Int64SpinBox::Int64SpinBox(QWidget* parent) : SpinBox(parent) {} +void Int64SpinBox::emitValueChanged(int64_t value) { + Q_EMIT valueChanged(value); +} + +void Int64SpinBox::emitEditingFinished() { + Q_EMIT editingFinished(); +} + +void Int64SpinBox::emitFocusNextRequested() { + Q_EMIT focusNextRequested(); +} template -std::optional evaluateLuaExpression(QString expression) { +std::optional evaluateLuaExpression(QString expression, T min, T max) { // 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(',', '.'); @@ -64,14 +206,17 @@ std::optional evaluateLuaExpression(QString expression) { 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); + auto result = lua_tonumber(l, -1); + if (result < min) { + LOG_INFO(raco::log_system::PROPERTY_BROWSER, "Lua result {} is under numerical limit {} and will be clamped.", result, min); + result = min; + } else if (result > max) { + LOG_INFO(raco::log_system::PROPERTY_BROWSER, "Lua result {} is over numerical limit {} and will be clamped.", result, max); + result = max; } + lua_close(l); - return result; + return (T)result; } else { LOG_INFO(raco::log_system::PROPERTY_BROWSER, "Could not evaluate Lua Expression: {}", lua_tostring(l, -1)); lua_close(l); @@ -79,8 +224,12 @@ std::optional evaluateLuaExpression(QString expression) { } }; -template std::optional evaluateLuaExpression(QString expression); +template class SpinBox; +template class SpinBox; +template class SpinBox; -template std::optional evaluateLuaExpression(QString expression); +template std::optional evaluateLuaExpression(QString expression, double min, double max); +template std::optional evaluateLuaExpression(QString expression, int min, int max); +template std::optional evaluateLuaExpression(QString expression, int64_t min, int64_t max); } // namespace raco::property_browser \ No newline at end of file diff --git a/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp b/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp index bcb83b98..70a04017 100644 --- a/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/DoubleEditor.cpp @@ -11,7 +11,7 @@ #include "core/Queries.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "property_browser/PropertyBrowserItem.h" #include "property_browser/PropertyBrowserLayouts.h" @@ -36,13 +36,13 @@ DoubleEditor::DoubleEditor( spinBox->setValue(item->valueHandle().as()); if (auto rangeAnnotation = item->query>()) { - spinBox->setRange(*rangeAnnotation->min_, *rangeAnnotation->max_); - slider->setRange(*rangeAnnotation->min_, *rangeAnnotation->max_); + spinBox->setSoftRange(*rangeAnnotation->min_, *rangeAnnotation->max_); + slider->setSoftRange(*rangeAnnotation->min_, *rangeAnnotation->max_); } // connect everything to our item values { - QObject::connect(spinBox, qOverload(&DoubleSpinBox::valueChanged), item, [item](double value) { + QObject::connect(spinBox, &DoubleSpinBox::valueChanged, item, [item](double value) { item->set(value); }); QObject::connect(slider, &DoubleSlider::valueEdited, item, [item](double value) { diff --git a/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp b/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp index 1c76678d..331eb986 100644 --- a/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/EnumerationEditor.cpp @@ -12,7 +12,7 @@ #include "core/EngineInterface.h" #include "core/Queries.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "property_browser/PropertyBrowserItem.h" #include "property_browser/PropertyBrowserLayouts.h" #include "property_browser/controls/MouseWheelGuard.h" diff --git a/gui/libPropertyBrowser/src/editors/Int64Editor.cpp b/gui/libPropertyBrowser/src/editors/Int64Editor.cpp new file mode 100644 index 00000000..291dfa4a --- /dev/null +++ b/gui/libPropertyBrowser/src/editors/Int64Editor.cpp @@ -0,0 +1,73 @@ +/* + * 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/Int64Editor.h" + + +#include "core/Queries.h" +#include "core/BasicAnnotations.h" + +#include "property_browser/PropertyBrowserItem.h" +#include "property_browser/PropertyBrowserLayouts.h" +#include "property_browser/controls/ScalarSlider.h" +#include "property_browser/controls/SpinBox.h" + +#include +#include + +namespace raco::property_browser { + +Int64Editor::Int64Editor( + PropertyBrowserItem* item, + QWidget* parent) : PropertyEditor(item, parent) { + auto* layout = new PropertyBrowserGridLayout{this}; + stack_ = new QStackedWidget{this}; + + auto* spinBox = new Int64SpinBox{stack_}; + using raco::property_browser::Int64Slider; + auto* slider = new Int64Slider{stack_}; + + slider->setValue(item->valueHandle().as()); + spinBox->setValue(item->valueHandle().as()); + + if (auto rangeAnnotation = item->query>()) { + spinBox->setSoftRange(*rangeAnnotation->min_, *rangeAnnotation->max_); + slider->setSoftRange(*rangeAnnotation->min_, *rangeAnnotation->max_); + } + + // connect everything to our item values + { + QObject::connect(spinBox, &Int64SpinBox::valueChanged, item, [item](int64_t value) { item->set(value); }); + QObject::connect(slider, &Int64Slider::valueEdited, item, [item](int64_t value) { item->set(value); }); + QObject::connect(item, &PropertyBrowserItem::valueChanged, this, [slider, spinBox](core::ValueHandle& handle) { + slider->setValue(handle.as()); + spinBox->setValue(handle.as()); + }); + } + + // State change: Show spinbox or slider + QObject::connect(slider, &Int64Slider::singleClicked, this, [this, spinBox]() { stack_->setCurrentWidget(spinBox); }); + QObject::connect(spinBox, &Int64SpinBox::editingFinished, this, [this, slider]() { + stack_->setCurrentWidget(slider); + slider->clearFocus(); + }); + QObject::connect(spinBox, &Int64SpinBox::focusNextRequested, this, [this, item]() { item->requestNextSiblingFocus(); }); + QObject::connect(item, &PropertyBrowserItem::widgetRequestFocus, this, [this, spinBox]() { + stack_->setCurrentWidget(spinBox); + spinBox->setFocus(); + }); + + stack_->addWidget(slider); + stack_->addWidget(spinBox); + + stack_->setCurrentWidget(slider); + layout->addWidget(stack_); +} + +} // namespace raco::property_browser diff --git a/gui/libPropertyBrowser/src/editors/IntEditor.cpp b/gui/libPropertyBrowser/src/editors/IntEditor.cpp index e70eaee7..5c57bbe5 100644 --- a/gui/libPropertyBrowser/src/editors/IntEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/IntEditor.cpp @@ -11,7 +11,7 @@ #include "core/Queries.h" -#include "data_storage/BasicAnnotations.h" +#include "core/BasicAnnotations.h" #include "property_browser/PropertyBrowserItem.h" #include "property_browser/PropertyBrowserLayouts.h" @@ -37,8 +37,8 @@ IntEditor::IntEditor( spinBox->setValue(item->valueHandle().as()); if (auto rangeAnnotation = item->query>()) { - spinBox->setRange(*rangeAnnotation->min_, *rangeAnnotation->max_); - slider->setRange(*rangeAnnotation->min_, *rangeAnnotation->max_); + spinBox->setSoftRange(*rangeAnnotation->min_, *rangeAnnotation->max_); + slider->setSoftRange(*rangeAnnotation->min_, *rangeAnnotation->max_); } // connect everything to our item values diff --git a/gui/libPropertyBrowser/src/editors/LinkEditor.cpp b/gui/libPropertyBrowser/src/editors/LinkEditor.cpp index bf605c26..7db2df60 100644 --- a/gui/libPropertyBrowser/src/editors/LinkEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/LinkEditor.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -45,20 +46,23 @@ class LinkEditorPopup : public QDialog { currentLink_.setReadOnly(true); currentLink_.setText(item->linkText().c_str()); deleteButton_.setFlat(true); - deleteButton_.setIcon(Icons::icon(Pixmap::trash)); + deleteButton_.setIcon(Icons::instance().remove); } else { currentLink_.setVisible(false); deleteButton_.setVisible(false); } acceptButton_.setFlat(true); - acceptButton_.setIcon(Icons::icon(Pixmap::done)); + acceptButton_.setIcon(Icons::instance().done); closeButton_.setFlat(true); - closeButton_.setIcon(Icons::icon(Pixmap::close)); + closeButton_.setIcon(Icons::instance().close); frame_.setLineWidth(1); frame_.setFrameStyle(QFrame::Panel | QFrame::Raised); + dataTypeLabel_.setAlignment(Qt::AlignCenter); + dataTypeLabel_.setText(QString::fromStdString(item->luaTypeName())); + outerLayout_.setContentsMargins(0, 0, 0, 0); outerLayout_.addWidget(&frame_, 0, 0, 1, 1); layout_.addWidget(¤tLink_, 0, 0, 1, 2); @@ -66,6 +70,7 @@ class LinkEditorPopup : public QDialog { layout_.addWidget(&search_, 1, 0, 1, 3); layout_.addWidget(&list_, 2, 0, 1, 3); layout_.addWidget(&acceptButton_, 3, 0); + layout_.addWidget(&dataTypeLabel_, 3, 1); layout_.addWidget(&closeButton_, 3, 2); layout_.setColumnStretch(1, 1); @@ -140,6 +145,7 @@ class LinkEditorPopup : public QDialog { LinkStartSearchView list_; QPushButton acceptButton_{this}; QPushButton closeButton_{this}; + QLabel dataTypeLabel_{this}; }; LinkEditor::LinkEditor( @@ -188,25 +194,25 @@ void LinkEditor::setLinkState(const LinkState& linkstate) { linkButton_->setDisabled(linkstate.readonly || !linkstate.validLinkTarget); if (linkstate.current == CurrentLinkState::PARENT_LINKED) { - linkButton_->setIcon(Icons::icon(Pixmap::parentIsLinked)); + linkButton_->setIcon(Icons::instance().parentIsLinked); } else { if (linkstate.validLinkTarget) { switch (linkstate.current) { case CurrentLinkState::NOT_LINKED: - linkButton_->setIcon(Icons::icon(Pixmap::linkable)); + linkButton_->setIcon(Icons::instance().linkable); break; case CurrentLinkState::LINKED: - linkButton_->setIcon(Icons::icon(Pixmap::linked)); + linkButton_->setIcon(Icons::instance().linked); break; case CurrentLinkState::BROKEN: - linkButton_->setIcon(Icons::icon(Pixmap::linkBroken)); + linkButton_->setIcon(Icons::instance().linkBroken); break; } } else { - linkButton_->setIcon(Icons::icon(Pixmap::unlinkable)); + linkButton_->setIcon(Icons::instance().unlinkable); } } } diff --git a/gui/libPropertyBrowser/src/editors/RefEditor.cpp b/gui/libPropertyBrowser/src/editors/RefEditor.cpp index 7d5d7bb3..96c0e948 100644 --- a/gui/libPropertyBrowser/src/editors/RefEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/RefEditor.cpp @@ -39,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::goTo, this), "", this); + goToRefObjectButton_ = new raco::common_widgets::PropertyBrowserButton(raco::style::Icons::instance().goTo, "", this); QObject::connect(goToRefObjectButton_, &QPushButton::clicked, [this, item]() { item->model()->Q_EMIT objectSelectionRequested(ref_->items().at(ref_->currentIndex()).second); diff --git a/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp b/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp index b01a2127..4bd5dc9c 100644 --- a/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/TagContainerEditor.cpp @@ -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::openInNew)); + editButton_->setIcon(style::Icons::instance().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 dcc9c4b7..8d77a949 100644 --- a/gui/libPropertyBrowser/src/editors/URIEditor.cpp +++ b/gui/libPropertyBrowser/src/editors/URIEditor.cpp @@ -78,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::openInNew, this), "", this); + editButton_ = new raco::common_widgets::PropertyBrowserButton(raco::style::Icons::instance().openInNew, "", this); editButton_->setMaximumWidth(raco::common_widgets::PropertyBrowserButton::MAXIMUM_WIDTH_PX + 5); editButton_->setEnabled(fileExists()); connect(editButton_, &QPushButton::clicked, [this]() { diff --git a/gui/libPropertyBrowser/src/editors/VecNTEditor.cpp b/gui/libPropertyBrowser/src/editors/VecNTEditor.cpp new file mode 100644 index 00000000..b29835b9 --- /dev/null +++ b/gui/libPropertyBrowser/src/editors/VecNTEditor.cpp @@ -0,0 +1,203 @@ +/* + * 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/VecNTEditor.h" + +#include + +namespace raco::property_browser { + +template +VecNTEditorColorPickerButton::VecNTEditorColorPickerButton(PropertyBrowserItem* item, QWidget* parent) : item_(item), QPushButton("", parent) { + QObject::connect(this, &QPushButton::clicked, [this]() { this->showColorPicker(); }); + + setFlat(true); + + setToolTip("Show Color Picker"); + setFixedHeight(24); + setFixedWidth(24); + setProperty("slimButton", true); +} + +template +void VecNTEditorColorPickerButton::paintEvent(QPaintEvent* event) { + 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(), 1.5); + painter.setBrush(backgroundBrush); + painter.setPen(pen); + painter.drawEllipse(QRect(rect().x() + 4 , rect().y() + 4, 16, 16)); + } + +template +void VecNTEditorColorPickerButton::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); + } +} + +template <> +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( + std::clamp(r, 0.0, 1.0), + std::clamp(g, 0.0, 1.0), + std::clamp(b, 0.0, 1.0)); +}; + +template <> +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( + std::clamp(r, 0.0, 1.0), + std::clamp(g, 0.0, 1.0), + std::clamp(b, 0.0, 1.0), + std::clamp(a, 0.0, 1.0)); +}; + +template <> +void VecNTEditorColorPickerButton<3>::setColorToItem(QColor color) { + item_->set(std::array{color.redF(), color.greenF(), color.blueF()}); +}; + +template <> +void VecNTEditorColorPickerButton<4>::setColorToItem(QColor color) { + item_->set(std::array{color.redF(), color.greenF(), color.blueF(), color.alphaF()}); +}; + +template +VecNTEditor::VecNTEditor( + PropertyBrowserItem* item, + 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>()) { + spinboxes_[i]->setSoftRange(*rangeAnnotation->min_, *rangeAnnotation->max_); + } + spinboxes_[i]->setValue(item->children().at(i)->valueHandle().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, this, &VecNTEditor::updateSpinBoxesAndColorPicker); + QObject::connect(item, &PropertyBrowserItem::childrenChanged, this, [this](const QList& children) { + for (int i = 0; i < N; i++) { + QObject::connect(children.at(i), &PropertyBrowserItem::valueChanged, this, &VecNTEditor::updateSpinBoxesAndColorPicker); + } + }); + + setupColorPicker(item); + + QObject::connect(item, &PropertyBrowserItem::expandedChanged, this, &VecNTEditor::setExpandedMode); + setExpandedMode(item->expanded()); +} + +template +void VecNTEditor::setEnabled(bool val) { + for (auto* child : layout()->children()) { + if (auto* widget{dynamic_cast(child)}) { + widget->setEnabled(val); + } + } +} + +template +void VecNTEditor::setExpandedMode(bool expanded) { + for (int i = 0; i < N; i++) { + spinboxes_[i]->setVisible(!expanded); + } + if (colorPickerButton_) { + colorPickerButton_->setVisible(expanded); + } +} + +template +void VecNTEditor::updateSpinBoxesAndColorPicker() { + for (int i = 0; i < N; i++) { + spinboxes_[i]->setValue(item_->children().at(i)->valueHandle().as()); + } + updateColorPicker(); +} + +template +void VecNTEditor::setupColorPicker(PropertyBrowserItem* item) { + // Don't do anything, only 3 and 4 double vectors are supported. +} + +template <> +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 <> +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); + } +} + +template +void VecNTEditor::updateColorPicker() { + if (colorPickerButton_) { + auto* button = static_cast*>(colorPickerButton_); + button->update(); + } +} + +template class VecNTEditor; +template class VecNTEditor; +template class VecNTEditor; +template class VecNTEditor; +template class VecNTEditor; +template class VecNTEditor; + +} // namespace raco::property_browser \ No newline at end of file diff --git a/gui/libPropertyBrowser/tests/PropertyBrowserItemTestHelper.h b/gui/libPropertyBrowser/tests/PropertyBrowserItemTestHelper.h index 460d97df..c6fbfbc4 100644 --- a/gui/libPropertyBrowser/tests/PropertyBrowserItemTestHelper.h +++ b/gui/libPropertyBrowser/tests/PropertyBrowserItemTestHelper.h @@ -59,6 +59,12 @@ struct PropertyBrowserItemTestHelper { dispatcher->dispatch(recorder.release()); } + void addPropertyTo(const std::string& containerName, const std::string& name, raco::data_storage::ValueBase* property) { + editorObject->get(containerName)->asTable().addProperty(name, property); + recorder.recordValueChanged(valueHandle.get(containerName)); + dispatcher->dispatch(recorder.release()); + } + void removePropertyFrom(const std::string& containerName) { editorObject->get(containerName)->asTable().removeProperty(0); recorder.recordValueChanged(valueHandle.get(containerName)); diff --git a/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp b/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp index 64ae47aa..5008715f 100644 --- a/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp +++ b/gui/libPropertyBrowser/tests/PropertyBrowserItem_test.cpp @@ -153,7 +153,7 @@ TEST(PropertyBrowserItem, setExpanded_doesnt_influence_showChildren_ifItemHasNoC TEST(PropertyBrowserItem, setExpandedRecursively) { PropertyBrowserItemTestHelper data{}; const ValueHandle tableHandle{data.valueHandle.get("table")}; - data.addPropertyTo("table", PrimitiveType::Vec3f, "vec"); + data.addPropertyTo("table", "vec", new Value()); const ValueHandle vecHandle{data.valueHandle.get("table").get("vec")}; PropertyBrowserItem tableItem{tableHandle, data.dispatcher, &data.commandInterface, nullptr}; diff --git a/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h b/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h index 7724a464..323d461a 100644 --- a/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h +++ b/gui/libRamsesWidgets/include/ramses_widgets/PreviewContentWidget.h @@ -27,7 +27,7 @@ class PreviewContentWidget final : public QWidget { virtual QPaintEngine* paintEngine() const override { return nullptr; } ramses::sceneId_t getSceneId(); void setSceneId(ramses::sceneId_t id); - void setBackgroundColor(data_storage::Vec4f backgroundColor); + void setBackgroundColor(core::Vec4f backgroundColor); void setFilteringMode(PreviewFilteringMode mode); void commit(); @@ -44,6 +44,7 @@ public Q_SLOTS: void newMousePosition(const QPoint globalPosition); protected: + void paintEvent(QPaintEvent* event) override; bool event(QEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; diff --git a/gui/libRamsesWidgets/include/ramses_widgets/PreviewMainWindow.h b/gui/libRamsesWidgets/include/ramses_widgets/PreviewMainWindow.h index ee4969d1..518af43f 100644 --- a/gui/libRamsesWidgets/include/ramses_widgets/PreviewMainWindow.h +++ b/gui/libRamsesWidgets/include/ramses_widgets/PreviewMainWindow.h @@ -55,7 +55,7 @@ class PreviewMainWindow final : public QMainWindow { raco::components::SDataChangeDispatcher dispatcher, QWidget* parent = nullptr); ~PreviewMainWindow(); - void displayScene(ramses::sceneId_t sceneId, data_storage::Vec4f const& backgroundColor); + void displayScene(ramses::sceneId_t sceneId, core::Vec4f const& backgroundColor); public Q_SLOTS: void setViewport(const QSize& sceneSize); diff --git a/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h b/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h index f6162a87..9181978c 100644 --- a/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h +++ b/gui/libRamsesWidgets/include/ramses_widgets/RamsesPreviewWindow.h @@ -23,6 +23,7 @@ class RamsesPreviewWindow final { struct State { ramses::sceneId_t sceneId{ramses::sceneId_t::Invalid()}; /** + * The pixel sizes here are given in device pixel sizes, NOT virtualized Qt pixel sizes. * ---------------------------------------------------- * |\ | * | \ (x, y) viewportOffset | @@ -39,6 +40,16 @@ class RamsesPreviewWindow final { QSize virtualSize{0, 0}; QColor backgroundColor{}; PreviewFilteringMode filteringMode{PreviewFilteringMode::NearestNeighbor}; + + bool operator!=(const State & other) const { + return this->backgroundColor != other.backgroundColor + || this->filteringMode != other.filteringMode + || this->sceneId != other.sceneId + || this->targetSize != other.targetSize + || this->viewportOffset != other.viewportOffset + || this->viewportSize != other.viewportSize + || this->virtualSize != other.virtualSize; + } }; explicit RamsesPreviewWindow( @@ -46,7 +57,8 @@ class RamsesPreviewWindow final { RendererBackend& rendererBackend); ~RamsesPreviewWindow(); - State& state(); + const State& currentState(); + State& nextState(); void commit(); private: diff --git a/gui/libRamsesWidgets/src/PreviewContentWidget.cpp b/gui/libRamsesWidgets/src/PreviewContentWidget.cpp index fb91e057..016f8678 100644 --- a/gui/libRamsesWidgets/src/PreviewContentWidget.cpp +++ b/gui/libRamsesWidgets/src/PreviewContentWidget.cpp @@ -15,6 +15,7 @@ #include "components/QtFormatter.h" #include #include +#include namespace raco::ramses_widgets { @@ -30,21 +31,21 @@ PreviewContentWidget::PreviewContentWidget(RendererBackend& rendererBackend, QWi void PreviewContentWidget::setSceneId(ramses::sceneId_t id) { if (ramsesPreview_) { - ramsesPreview_->state().sceneId = id; + ramsesPreview_->nextState().sceneId = id; update(); } } -void PreviewContentWidget::setBackgroundColor(data_storage::Vec4f backgroundColor) { +void PreviewContentWidget::setBackgroundColor(core::Vec4f backgroundColor) { if (ramsesPreview_) { - ramsesPreview_->state().backgroundColor = QColor::fromRgbF(backgroundColor.x.asDouble(), backgroundColor.y.asDouble(), backgroundColor.z.asDouble(), backgroundColor.w.asDouble()); + ramsesPreview_->nextState().backgroundColor = QColor::fromRgbF(backgroundColor.x.asDouble(), backgroundColor.y.asDouble(), backgroundColor.z.asDouble(), backgroundColor.w.asDouble()); update(); } } ramses::sceneId_t PreviewContentWidget::getSceneId() { if (ramsesPreview_) { - return ramsesPreview_->state().sceneId; + return ramsesPreview_->nextState().sceneId; } else { return ramses::sceneId_t::Invalid(); } @@ -73,36 +74,51 @@ void PreviewContentWidget::setViewportRect( if (!ramsesPreview_) { return; } + auto devicePixelScaleFactor = window()->screen()->devicePixelRatio(); if constexpr (BuildOptions::minimalPreviewDisplayArea) { // Minimal resize to size of actual viewport resize(viewportSize); move(viewportPosition); - ramsesPreview_->state().viewportOffset = viewportOffset; - ramsesPreview_->state().viewportSize = viewportSize; - ramsesPreview_->state().virtualSize = virtualSize; - ramsesPreview_->state().targetSize = targetSize; + ramsesPreview_->nextState().viewportOffset = viewportOffset * devicePixelScaleFactor; + ramsesPreview_->nextState().viewportSize = viewportSize * devicePixelScaleFactor; + ramsesPreview_->nextState().virtualSize = virtualSize * devicePixelScaleFactor; + ramsesPreview_->nextState().targetSize = targetSize; } else { // resize to entire area resize(areaSize); + move(0, 0); setMask({viewportPosition.x(), viewportPosition.y(), viewportSize.width(), viewportSize.height()}); - ramsesPreview_->state().viewportOffset = (-1 * viewportPosition) + viewportOffset; - ramsesPreview_->state().viewportSize = areaSize; - ramsesPreview_->state().virtualSize = virtualSize; - ramsesPreview_->state().targetSize = targetSize; + ramsesPreview_->nextState().viewportOffset = ((-1 * viewportPosition) + viewportOffset) * devicePixelScaleFactor; + ramsesPreview_->nextState().viewportSize = areaSize * devicePixelScaleFactor; + ramsesPreview_->nextState().virtualSize = virtualSize * devicePixelScaleFactor; + ramsesPreview_->nextState().targetSize = targetSize; } update(); } +void PreviewContentWidget::paintEvent(QPaintEvent* e) { + // time scene id changes to paint event instead of our mainWindow timer + // because the mainWindow timer interval is actually too high for RaCo + // to register all scene id changes in the UI (?) + if (ramsesPreview_->currentState().sceneId != ramsesPreview_->nextState().sceneId) { + ramsesPreview_->commit(); + } +} + void PreviewContentWidget::setFilteringMode(PreviewFilteringMode filteringMode) { if (ramsesPreview_) { - ramsesPreview_->state().filteringMode = filteringMode; + ramsesPreview_->nextState().filteringMode = filteringMode; update(); } } void PreviewContentWidget::commit() { - ramsesPreview_->commit(); + const auto& currentState = ramsesPreview_->currentState(); + auto& nextState = ramsesPreview_->nextState(); + if (nextState != currentState && nextState.sceneId == currentState.sceneId) { + ramsesPreview_->commit(); + } } void PreviewContentWidget::mouseMoveEvent(QMouseEvent* event) { diff --git a/gui/libRamsesWidgets/src/PreviewMainWindow.cpp b/gui/libRamsesWidgets/src/PreviewMainWindow.cpp index 47e9969e..93d22557 100644 --- a/gui/libRamsesWidgets/src/PreviewMainWindow.cpp +++ b/gui/libRamsesWidgets/src/PreviewMainWindow.cpp @@ -64,7 +64,7 @@ PreviewMainWindow::PreviewMainWindow(RendererBackend& rendererBackend, raco::ram // Size mode tool button { - auto* sizeMenu = new QMenu{this}; + auto* sizeMenu = new QMenu{ui_->toolBar}; sizeMenu->addAction(ui_->actionSetSizeModeOff); sizeMenu->addAction(ui_->actionSetSizeModeVerticalFit); sizeMenu->addAction(ui_->actionSetSizeModeHorizontalFit); @@ -80,7 +80,7 @@ PreviewMainWindow::PreviewMainWindow(RendererBackend& rendererBackend, raco::ram connect(ui_->actionSetSizeModeHorizontalFit, &QAction::triggered, scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingHorizontalFit); connect(ui_->actionSetSizeModeBestFit, &QAction::triggered, scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingBestFit); connect(ui_->actionSetSizeModeOriginalFit, &QAction::triggered, scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingOriginalFit); - auto* sizeMenuButton = new QToolButton{this}; + auto* sizeMenuButton = new QToolButton{ui_->toolBar}; sizeMenuButton->setMenu(sizeMenu); sizeMenuButton->setPopupMode(QToolButton::InstantPopup); connect(scrollAreaWidget_, &PreviewScrollAreaWidget::autoSizingChanged, [this, sizeMenuButton](PreviewScrollAreaWidget::AutoSizing mode) { @@ -111,14 +111,14 @@ PreviewMainWindow::PreviewMainWindow(RendererBackend& rendererBackend, raco::ram } // Filtering mode tool button { - auto* filteringMenu = new QMenu{this}; + auto* filteringMenu = new QMenu{ui_->toolBar}; filteringMenu->addAction(ui_->actionSetFilteringModeNearestNeighbor); filteringMenu->addAction(ui_->actionSetFilteringModeLinear); ui_->actionSetFilteringModeNearestNeighbor->setCheckable(true); ui_->actionSetFilteringModeNearestNeighbor->setChecked(true); - auto* filteringMenuButton = new QToolButton{this}; + auto* filteringMenuButton = new QToolButton{ui_->toolBar}; filteringMenuButton->setMenu(filteringMenu); filteringMenuButton->setPopupMode(QToolButton::InstantPopup); @@ -145,7 +145,7 @@ PreviewMainWindow::~PreviewMainWindow() { delete previewWidget_; } -void PreviewMainWindow::displayScene(ramses::sceneId_t sceneId, data_storage::Vec4f const& backgroundColor) { +void PreviewMainWindow::displayScene(ramses::sceneId_t sceneId, core::Vec4f const& backgroundColor) { previewWidget_->setBackgroundColor(backgroundColor); if (sceneId != previewWidget_->getSceneId()) { sceneIdLabel_->setText(QString{"scene id: %1"}.arg(sceneId.getValue())); diff --git a/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp b/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp index eafae48f..5d5ef43f 100644 --- a/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp +++ b/gui/libRamsesWidgets/src/RamsesPreviewWindow.cpp @@ -97,7 +97,11 @@ RamsesPreviewWindow::~RamsesPreviewWindow() { } } -RamsesPreviewWindow::State& RamsesPreviewWindow::state() { +const RamsesPreviewWindow::State& RamsesPreviewWindow::currentState() { + return current_; +} + +RamsesPreviewWindow::State& RamsesPreviewWindow::nextState() { return next_; } @@ -182,6 +186,7 @@ void RamsesPreviewWindow::commit() { if (displayId_.isValid() && offscreenBufferId_.isValid() && next_.backgroundColor != current_.backgroundColor) { rendererBackend_.renderer().setDisplayBufferClearColor(displayId_, offscreenBufferId_, next_.backgroundColor.redF(), next_.backgroundColor.greenF(), next_.backgroundColor.blueF(), next_.backgroundColor.alphaF()); rendererBackend_.renderer().flush(); + current_.backgroundColor = next_.backgroundColor; } current_.viewportOffset = next_.viewportOffset; diff --git a/gui/libStyle/include/style/Icons.h b/gui/libStyle/include/style/Icons.h index 2fa26bbc..9def3eab 100644 --- a/gui/libStyle/include/style/Icons.h +++ b/gui/libStyle/include/style/Icons.h @@ -10,69 +10,56 @@ #pragma once #include -#include #include namespace raco::style { -enum class Pixmap { - done, - trash, - - unlocked, - locked, - expanded, - collapsed, - - increment, - decrement, - - linkable, - linked, - parentIsLinked, - unlinkable, - linkBroken, - - warning, - error, - - close, - undock, - menu, - openInNew, - goTo, - - typeNode, - typeCamera, - typeMesh, - typeMaterial, - typeTexture, - typeCubemap, - typeScript, - typePrefabInternal, - typePrefabExternal, - typePrefabInstance, - typeLuaScriptModule, - typeAnimationChannel, - typeAnimation -}; +typedef QString IconPath; class Icons { public: - static QIcon icon(Pixmap pixmap, const QWidget* widget = nullptr) { - return Icons::instance().createIcon(pixmap, widget); - } - static QPixmap pixmap(Pixmap pixmap) { - return Icons::instance().icons_[pixmap]; - } + const QIcon done{":doneIcon"}; + const QIcon remove{":removeIcon"}; + const QIcon expanded{":expandedIcon"}; + const QIcon collapsed{":collapsedIcon"}; + const QIcon linkable{":linkableIcon"}; + const QIcon linked{":linkedIcon"}; + const QIcon parentIsLinked{":parentLinkedIcon"}; + const QIcon unlinkable{":unlinkableIcon"}; + const QIcon linkBroken{":linkBrokenIcon"}; + const QIcon locked{":lockedIcon"}; + const QIcon unlocked{":unlockedIcon"}; + const QIcon close{":closeIcon"}; + const QIcon undock{":undockIcon"}; + const QIcon menu{":menuIcon"}; + const QIcon openInNew{":openInNewIcon"}; + const QIcon goTo{":gotoIcon"}; + const QIcon increment{":incrementIcon"}; + const QIcon decrement{":decrementIcon"}; + const QIcon warning{":warningIcon"}; + const QIcon error{":errorIcon"}; + const QIcon typeNode{":typeNodeIcon"}; + const QIcon typeCamera{":typeCameraIcon"}; + const QIcon typeMesh{":typeMeshIcon"}; + const QIcon typeMaterial{":typeMaterialIcon"}; + const QIcon typeTexture{":typeTextureIcon"}; + const QIcon typeCubemap{":typeCubemapIcon"}; + const QIcon typeLuaScript{":typeLuaScriptIcon"}; + const QIcon typePrefabInternal{":typePrefabInternalIcon"}; + const QIcon typePrefabExternal{":typePrefabExternalIcon"}; + const QIcon typePrefabInstance{":typePrefabInstanceIcon"}; + const QIcon typeLuaScriptModule{":typeLuaScriptModuleIcon"}; + const QIcon typeAnimationChannel{":typeAnimationChannelIcon"}; + const QIcon typeAnimation{":typeAnimationIcon"}; + + static const Icons& instance(); + + Icons(const Icons &) = delete; + Icons &operator=(const Icons &) = delete; private: - QIcon createIcon(Pixmap pixmap, const QWidget* widget = nullptr) { - return QIcon(QPixmap(icons_[pixmap])); - } - static Icons& instance(); - static Icons* instance_; - static QMap icons_; + Icons(); + ~Icons(); }; } // namespace raco::style diff --git a/gui/libStyle/src/Icons.cpp b/gui/libStyle/src/Icons.cpp index 6ed3c03d..366a72b1 100644 --- a/gui/libStyle/src/Icons.cpp +++ b/gui/libStyle/src/Icons.cpp @@ -10,51 +10,16 @@ #include "style/Icons.h" namespace raco::style { + +const Icons& Icons::instance() { + static Icons icons; + return icons; +} -Icons& Icons::instance() { - if (Icons::instance_ == nullptr) { - Icons::instance_ = new Icons{}; - Icons::icons_ = { - {Pixmap::done, QPixmap{":doneIcon"}}, - {Pixmap::trash, QPixmap{":trashIcon"}}, - {Pixmap::expanded, QPixmap{":expandedIcon"}}, - {Pixmap::collapsed, QPixmap{":collapsedIcon"}}, - {Pixmap::linkable, QPixmap{":linkableIcon"}}, - {Pixmap::linked, QPixmap{":linkedIcon"}}, - {Pixmap::parentIsLinked, QPixmap{":parentLinkedIcon"}}, - {Pixmap::unlinkable, QPixmap{":unlinkableIcon"}}, - {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::openInNew, QPixmap{":openInNewIcon"}}, - {Pixmap::goTo, QPixmap{":gotoIcon"}}, - {Pixmap::increment, QPixmap{":incrementIcon"}}, - {Pixmap::decrement, QPixmap{":decrementIcon"}}, - {Pixmap::warning, QPixmap{":warningIcon"}}, - {Pixmap::error, QPixmap{":errorIcon"}}, - - {Pixmap::typeNode, QPixmap{":typeNodeIcon"}}, - {Pixmap::typeCamera, QPixmap{":typeCameraIcon"}}, - {Pixmap::typeMesh, QPixmap{":typeMeshIcon"}}, - {Pixmap::typeMaterial, QPixmap{":typeMaterialIcon"}}, - {Pixmap::typeTexture, QPixmap{":typeTextureIcon"}}, - {Pixmap::typeCubemap, QPixmap{":typeCubemapIcon"}}, - {Pixmap::typeScript, QPixmap{":typeScriptIcon"}}, - {Pixmap::typePrefabInternal, QPixmap{":typePrefabInternalIcon"}}, - {Pixmap::typePrefabExternal, QPixmap{":typePrefabExternalIcon"}}, - {Pixmap::typePrefabInstance, QPixmap{":typePrefabInstanceIcon"}}, - {Pixmap::typeLuaScriptModule, QPixmap{":typeLuaScriptModuleIcon"}}, - {Pixmap::typeAnimationChannel, QPixmap{":typeAnimationChannelIcon"}}, - {Pixmap::typeAnimation, QPixmap{":typeAnimationIcon"}} - }; - } - return *Icons::instance_; +Icons::Icons() { } -QMap Icons::icons_{}; -Icons* Icons::instance_{nullptr}; +Icons::~Icons() { +} } // namespace raco::style \ No newline at end of file diff --git a/gui/libStyle/src/RaCoStyle.cpp b/gui/libStyle/src/RaCoStyle.cpp index 093c89c1..22c2f75e 100644 --- a/gui/libStyle/src/RaCoStyle.cpp +++ b/gui/libStyle/src/RaCoStyle.cpp @@ -149,13 +149,13 @@ QIcon RaCoStyle::standardIcon(StandardPixmap standardIcon, const QStyleOption *o QIcon icon; switch (standardIcon) { case SP_TitleBarCloseButton: - return Icons::icon(Pixmap::close); + return Icons::instance().close; break; case SP_TitleBarNormalButton: - return Icons::icon(Pixmap::undock); + return Icons::instance().undock; break; case SP_TitleBarMenuButton: - return Icons::icon(Pixmap::menu); + return Icons::instance().menu; break; default: return QProxyStyle::standardIcon(standardIcon, option, widget); @@ -509,9 +509,9 @@ void RaCoStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *opti aft_v += delta; // draw icons if (option->state & State_Open) - p->drawPixmap(bef_h, bef_v, Icons::icon(Pixmap::expanded).pixmap(decoration_size, decoration_size)); + Icons::instance().expanded.paint(p, {bef_h, bef_v, decoration_size, decoration_size}); else - p->drawPixmap(bef_h, bef_v, Icons::icon(Pixmap::collapsed).pixmap(decoration_size, decoration_size)); + Icons::instance().collapsed.paint(p, {bef_h, bef_v, decoration_size, decoration_size}); } // draw lines, untouched code QBrush brush(option->palette.dark().color(), Qt::Dense4Pattern); diff --git a/resources/images/blue_1024.png b/resources/images/blue_1024.png new file mode 100644 index 00000000..f3943515 --- /dev/null +++ b/resources/images/blue_1024.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7023c75de06bfd95acad413233e9f1c6afbd2b83021d360b942f63b1fdee349b +size 6540 diff --git a/resources/images/green_512.png b/resources/images/green_512.png new file mode 100644 index 00000000..2d35c91b --- /dev/null +++ b/resources/images/green_512.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3b1b5c9f1fe9d305da9b79989eb5c9840d348c74cd76abaeb34639a59f377e9 +size 2602 diff --git a/resources/images/red_128.png b/resources/images/red_128.png new file mode 100644 index 00000000..1b0669cb --- /dev/null +++ b/resources/images/red_128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3fbbfc6af6f7cb1616244dd770e11eb63bc09d78a207bac55aba79c30b3c037 +size 796 diff --git a/resources/images/yellow_256.png b/resources/images/yellow_256.png new file mode 100644 index 00000000..fff79028 --- /dev/null +++ b/resources/images/yellow_256.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f7058b95e30075c4e8914799acb0ac00e8664c3fe918aa4197e0c09934a551c +size 1255 diff --git a/resources/meshes/MosquitoInAmber/MosquitoInAmber.bin b/resources/meshes/MosquitoInAmber/MosquitoInAmber.bin new file mode 100644 index 00000000..beca1e30 Binary files /dev/null and b/resources/meshes/MosquitoInAmber/MosquitoInAmber.bin differ diff --git a/resources/meshes/MosquitoInAmber/MosquitoInAmber.gltf b/resources/meshes/MosquitoInAmber/MosquitoInAmber.gltf new file mode 100644 index 00000000..0ebf00c3 --- /dev/null +++ b/resources/meshes/MosquitoInAmber/MosquitoInAmber.gltf @@ -0,0 +1,682 @@ +{ + "asset": { + "extras": { + "author": "Sketchfab (https://sketchfab.com/Sketchfab)", + "license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)", + "source": "https://sketchfab.com/3d-models/real-time-refraction-demo-mosquito-in-amber-37233d6ed84844fea1ebe88069ea58d1", + "title": "Real-time Refraction Demo: Mosquito in Amber" + }, + "generator": "Sketchfab-7.51.0, hand-edited to add additional PBR glTF extensions.", + "version": "2.0" + }, + "accessors": [ + { + "bufferView": 2, + "componentType": 5126, + "count": 1085, + "max": [ + 0.5109253525733948, + 0.49603474140167236, + 0.511311411857605 + ], + "min": [ + -0.36829671263694763, + -0.47642603516578674, + -0.4853871166706085 + ], + "type": "VEC3", + "byteOffset": 0 + }, + { + "bufferView": 2, + "byteOffset": 13020, + "componentType": 5126, + "count": 1085, + "max": [ + 0.9988976716995239, + 0.9995962381362915, + 0.9998509883880615 + ], + "min": [ + -0.9989103674888611, + -0.9995899200439453, + -0.9961240291595459 + ], + "type": "VEC3" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 1085, + "max": [ + 0.9919444918632507, + 0.8364054560661316, + 0.9993917942047119, + 1 + ], + "min": [ + -0.9992862939834595, + -0.99994957447052, + -0.999899685382843, + 1 + ], + "type": "VEC4", + "byteOffset": 0 + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 1085, + "max": [ + 0.9928414821624756, + 0.9936410188674927 + ], + "min": [ + 0.009734660387039185, + 0.004527479410171509 + ], + "type": "VEC2", + "byteOffset": 0 + }, + { + "bufferView": 0, + "componentType": 5125, + "count": 5556, + "max": [ + 1084 + ], + "min": [ + 0 + ], + "type": "SCALAR", + "byteOffset": 0 + }, + { + "bufferView": 2, + "byteOffset": 26040, + "componentType": 5126, + "count": 3057, + "max": [ + 0.4662538170814514, + 0.43218323588371277, + 0.46371984481811523 + ], + "min": [ + -0.3395809829235077, + -0.4264683425426483, + -0.4342540502548218 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 62724, + "componentType": 5126, + "count": 3057, + "max": [ + 0.9999549388885498, + 0.9983750581741333, + 0.9994456768035889 + ], + "min": [ + -0.976579487323761, + -0.967200517654419, + -0.9996895790100098 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 8680, + "componentType": 5126, + "count": 3057, + "max": [ + 0.9928414821624756, + 0.9936410188674927 + ], + "min": [ + 0.009734660387039185, + 0.004527479410171509 + ], + "type": "VEC2" + }, + { + "bufferView": 1, + "byteOffset": 33136, + "componentType": 5126, + "count": 3057, + "max": [ + 0, + 0 + ], + "min": [ + 0, + 0 + ], + "type": "VEC2" + }, + { + "bufferView": 1, + "byteOffset": 57592, + "componentType": 5126, + "count": 3057, + "max": [ + 0, + 0 + ], + "min": [ + 0, + 0 + ], + "type": "VEC2" + }, + { + "bufferView": 0, + "byteOffset": 22224, + "componentType": 5125, + "count": 3189, + "max": [ + 3056 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 2, + "byteOffset": 99408, + "componentType": 5126, + "count": 14536, + "max": [ + 0.22900402545928955, + 0.478808730840683, + 0.291715145111084 + ], + "min": [ + -0.17561697959899902, + -0.2953760623931885, + -0.32297801971435547 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 273840, + "componentType": 5126, + "count": 14536, + "max": [ + 0.9998576641082764, + 0.9977716207504272, + 0.9997305870056152 + ], + "min": [ + -0.9995299577713013, + -0.9999469518661499, + -0.9993008375167847 + ], + "type": "VEC3" + }, + { + "bufferView": 3, + "byteOffset": 17360, + "componentType": 5126, + "count": 14536, + "max": [ + 0.9998598098754883, + 0.9999244809150696, + 0.9999922513961792, + 1 + ], + "min": [ + -0.9996232390403748, + -0.999721348285675, + -0.9998897314071655, + -1 + ], + "type": "VEC4" + }, + { + "bufferView": 1, + "byteOffset": 82048, + "componentType": 5126, + "count": 14536, + "max": [ + 0.9982950687408447, + 0.998286247253418 + ], + "min": [ + 0.0017050158930942416, + 0.0017050158930942416 + ], + "type": "VEC2" + }, + { + "bufferView": 0, + "byteOffset": 34980, + "componentType": 5125, + "count": 34302, + "max": [ + 14535 + ], + "min": [ + 0 + ], + "type": "SCALAR" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 172188, + "byteOffset": 0, + "name": "floatBufferViews", + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 198336, + "byteOffset": 172188, + "byteStride": 8, + "name": "floatBufferViews", + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 448272, + "byteOffset": 370524, + "byteStride": 12, + "name": "floatBufferViews", + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 249936, + "byteOffset": 818796, + "byteStride": 16, + "name": "floatBufferViews", + "target": 34962 + } + ], + "buffers": [ + { + "name": "MosquitoInAmber", + "byteLength": 1068732, + "uri": "MosquitoInAmber.bin" + } + ], + "images": [ + { + "mimeType": "image/jpeg", + "uri": "MosquitoInAmber0.jpg" + }, + { + "mimeType": "image/png", + "uri": "MosquitoInAmber1.png" + }, + { + "mimeType": "image/png", + "uri": "MosquitoInAmber2.png" + }, + { + "mimeType": "image/jpeg", + "uri": "MosquitoInAmber3.jpg" + }, + { + "mimeType": "image/jpeg", + "uri": "MosquitoInAmber4.jpg" + } + ], + "extensionsUsed": [ + "KHR_materials_transmission", + "KHR_materials_ior", + "KHR_materials_volume" + ], + "materials": [ + { + "doubleSided": false, + "name": "material", + "normalTexture": { + "index": 2, + "scale": 1, + "texCoord": 0 + }, + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "texCoord": 0 + }, + "metallicFactor": 0, + "metallicRoughnessTexture": { + "index": 1, + "texCoord": 0 + }, + "roughnessFactor": 0.5, + "baseColorFactor": [ + 1, + 1, + 1, + 1 + ] + }, + "extensions": { + "KHR_materials_transmission": { + "transmissionFactor": 0.75 + }, + "KHR_materials_ior": { + "ior": 1.55 + }, + "KHR_materials_volume": { + "thicknessFactor": 0.9 + } + }, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "alphaMode": "OPAQUE" + }, + { + "doubleSided": false, + "name": "eclats", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1, + 1, + 1, + 1 + ], + "metallicFactor": 1, + "roughnessFactor": 0.3922348485 + }, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "alphaMode": "OPAQUE" + }, + { + "doubleSided": false, + "name": "original.o_material_0", + "normalTexture": { + "index": 4, + "scale": 1, + "texCoord": 0 + }, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.7469135802, + 0.7469135802, + 0.7469135802, + 1 + ], + "baseColorTexture": { + "index": 3, + "texCoord": 0 + }, + "metallicFactor": 0, + "roughnessFactor": 0.6790123457 + }, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "alphaMode": "OPAQUE" + } + ], + "meshes": [ + { + "name": "5_amber_lr_PBR_0", + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 0, + "TANGENT": 2, + "TEXCOORD_0": 3 + }, + "indices": 4, + "material": 0, + "mode": 4 + } + ] + }, + { + "name": "6_eclats_eclats_0", + "primitives": [ + { + "attributes": { + "NORMAL": 6, + "POSITION": 5, + "TEXCOORD_0": 7, + "TEXCOORD_1": 8, + "TEXCOORD_2": 9 + }, + "indices": 10, + "material": 1, + "mode": 4 + } + ] + }, + { + "name": "2_mosquito_lr_original.o_material_0_0", + "primitives": [ + { + "attributes": { + "NORMAL": 12, + "POSITION": 11, + "TANGENT": 13, + "TEXCOORD_0": 14 + }, + "indices": 15, + "material": 2, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "name": "RootNode (gltf orientation matrix)", + "rotation": [ + -0.5, + 0.5, + 0.5, + 0.5 + ], + "translation": [ + 0, + 0, + 0 + ], + "scale": [ + 1, + 1, + 1 + ] + }, + { + "children": [ + 2 + ], + "name": "RootNode (model correction matrix)" + }, + { + "children": [ + 3 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + -1, + 0, + 0, + 0, + 0, + 0, + 1 + ], + "name": "0a89b95064be46e6943aaad2c83817a2.fbx" + }, + { + "children": [ + 4, + 6, + 8 + ], + "name": "RootNode", + "scale": [ + 0.1, + 0.1, + 0.1 + ], + "translation": [ + 0, + 0, + 0 + ], + "rotation": [ + 0, + 0, + 0, + 1 + ] + }, + { + "children": [ + 5 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 0, + -1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ], + "name": "5_amber_lr" + }, + { + "mesh": 0, + "name": "5_amber_lr_PBR_0" + }, + { + "children": [ + 7 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 0, + -1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ], + "name": "6_eclats" + }, + { + "mesh": 1, + "name": "6_eclats_eclats_0" + }, + { + "children": [ + 9 + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 0, + -1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ], + "name": "2_mosquito_lr" + }, + { + "mesh": 2, + "name": "2_mosquito_lr_original.o_material_0_0" + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9987, + "wrapS": 10497, + "wrapT": 10497 + } + ], + "scene": 0, + "scenes": [ + { + "name": "OSG_Scene", + "nodes": [ + 0 + ] + } + ], + "textures": [ + { + "sampler": 0, + "source": 0 + }, + { + "sampler": 0, + "source": 1 + }, + { + "sampler": 0, + "source": 2 + }, + { + "sampler": 0, + "source": 3 + }, + { + "sampler": 0, + "source": 4 + } + ] +} diff --git a/resources/meshes/MultipleVCols/multiple_VCols.gltf b/resources/meshes/MultipleVCols/multiple_VCols.gltf new file mode 100644 index 00000000..37a17818 --- /dev/null +++ b/resources/meshes/MultipleVCols/multiple_VCols.gltf @@ -0,0 +1,139 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.6.16", + "version" : "2.0" + }, + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Sphere" + } + ], + "meshes" : [ + { + "name" : "Sphere", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2, + "COLOR_0" : 3, + "COLOR_1" : 4, + "COLOR_2" : 5 + }, + "indices" : 6 + } + ] + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 625, + "max" : [ + 0.9999997019767761, + 1, + 0.9999994039535522 + ], + "min" : [ + -0.9999990463256836, + -1, + -0.9999987483024597 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 625, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 625, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 625, + "normalized" : true, + "type" : "VEC4" + }, + { + "bufferView" : 4, + "componentType" : 5123, + "count" : 625, + "normalized" : true, + "type" : "VEC4" + }, + { + "bufferView" : 5, + "componentType" : 5123, + "count" : 625, + "normalized" : true, + "type" : "VEC4" + }, + { + "bufferView" : 6, + "componentType" : 5123, + "count" : 2880, + "type" : "SCALAR" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 7500, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 7500, + "byteOffset" : 7500 + }, + { + "buffer" : 0, + "byteLength" : 5000, + "byteOffset" : 15000 + }, + { + "buffer" : 0, + "byteLength" : 5000, + "byteOffset" : 20000 + }, + { + "buffer" : 0, + "byteLength" : 5000, + "byteOffset" : 25000 + }, + { + "buffer" : 0, + "byteLength" : 5000, + "byteOffset" : 30000 + }, + { + "buffer" : 0, + "byteLength" : 5760, + "byteOffset" : 35000 + } + ], + "buffers" : [ + { + "byteLength" : 40760, + "uri" : "data:application/octet-stream;base64,AAAAAL4Uez/CxUe+AAAAAF6DbD8W78O+AAAAADHbVD/aOQ6/AAAAAPMENT/zBDW/AAAAANo5Dj8x21S/AAAAABXvwz5eg2y/AAAAABXvwz5eg2y/AAAAAMTFRz6+FHu/AAAAANk5Dr8y21S/AAAAAPMENb/zBDW/AAAAADLbVL/ZOQ6/EeUbPb4Uez8V70O+OOaYPV6DbD9LK8C+sPndPTHbVD8/fgu/sEINPvMENT+FijG/0homPto5Dj8oxFC/05A4PhXvwz7392e/05A4PhXvwz7392e/Fu9DPsTFRz6uQXa/w8VHPi69O7O+FHu/Fu9DPsLFR76uQXa/1JA4PhTvw77492e/1JA4PhTvw77492e/0xomPtk5Dr8pxFC/sEINPvMENb+FijG/rvndPTLbVL8+fgu/OOaYPV6DbL9MK8C+EeUbPb8Ue78U70O+OeaYPb4Uez/SkDi+HPYVPl6DbD/0BLW+zLVZPjHbVD9QZgO/1YuKPvMENT90PSe/wemiPto5Dj9Kp0S/9AS1PhXvwz55glq/9AS1PhXvwz55glq/SyvAPsTFRz7392e/F+/DPi69O7Ndg2y/SyvAPsLFR77392e/9QS1PhTvw756glq/9QS1PhTvw756glq/w+miPtk5Dr9Lp0S/1YuKPvMENb90PSe/yrVZPjLbVL9QZgO/HfYVPl6DbL/1BLW+OOaYPb8Ue7/RkDi+svndPb4Uez/QGia+zbVZPl6DbD/B6aK+dwiePjHbVD9cg+y+UCPJPvMENT8Xgxa/XoPsPto5Dj/D+zC/UWYDPxXvwz5Lp0S/UWYDPxXvwz5Lp0S/P34LP8TFRz4oxFC/2jkOPy69O7Mv21S/P34LP8LFR74oxFC/UmYDPxTvw75Mp0S/UmYDPxTvw75Mp0S/YYPsPtk5Dr/E+zC/UCPJPvMENb8Xgxa/dgiePjLbVL9cg+y+zrVZPl6DbL/C6aK+sfndPb8Ue7/PGia+skINPr4Uez+tQg2+14uKPl6DbD/Ti4q+UCPJPjHbVD9LI8m+AAAAP/MENT////++FoMWP9o5Dj8Vgxa/dD0nPxXvwz5zPSe/dD0nPxXvwz5zPSe/hYoxP8TFRz6EijG/8wQ1Py69O7PwBDW/hYoxP8LFR76EijG/dj0nPxTvw750PSe/dj0nPxTvw750PSe/GIMWP9k5Dr8Vgxa/AAAAP/MENb////++TyPJPjLbVL9LI8m+14uKPl6DbL/Ui4q+sUINPr8Ue7+sQg2+1RomPr4Uez+o+d29xemiPl6DbD/HtVm+YIPsPjHbVD9yCJ6+F4MWP/MENT9MI8m+xPswP9o5Dj9ag+y+S6dEPxXvwz5QZgO/S6dEPxXvwz5QZgO/KMRQP8TFRz49fgu/MNtUPy69O7PWOQ6/KMRQP8LFR749fgu/TqdEPxTvw75QZgO/TqdEPxTvw75QZgO/xfswP9k5Dr9ag+y+F4MWP/MENb9MI8m+X4PsPjLbVL9yCJ6+xemiPl6DbL/JtVm+1BomPr8Ue7+m+d291pA4Pr4Uez8u5pi99wS1Pl6DbD8V9hW+UWYDPzHbVD/CtVm+dD0nP/MENT/Si4q+SqdEP9o5Dj+86aK+eIJaPxXvwz7yBLW+eIJaPxXvwz7yBLW+9vdnP8TFRz5GK8C+XINsPy69O7MO78O+9vdnP8LFR75GK8C+e4JaPxTvw77wBLW+e4JaPxTvw77wBLW+S6dEP9k5Dr+86aK+dD0nP/MENb/Si4q+UWYDPzLbVL/CtVm++AS1Pl6DbL8X9hW+1ZA4Pr8Ue78s5pi9GO9DPr4Uez/85Bu9TivAPl6DbD8o5pi9Pn4LPzHbVD+d+d29hYoxP/MENT+sQg2+JsRQP9o5Dj/KGia+9vdnPxXvwz7RkDi+9vdnPxXvwz7RkDi+rUF2P8TFRz4O70O+uxR7Py69O7O1xUe+rUF2P8LFR74O70O++fdnPxTvw77KkDi++fdnPxTvw77KkDi+J8RQP9k5Dr/JGia+hYoxP/MENb+sQg2+Pn4LPzLbVL+d+d29TyvAPl6DbL8r5pi9F+9DPr8Ue7/55Bu9xcVHPr4Uez98m5MzGO/DPl6DbD++zQE02DkOPzHbVD98m/Mz8gQ1P/MENT/4NkczLttUP9o5Dj98m8MzXINsPxXvwz7wbY4yXINsPxXvwz7wbY4yvBR7P8TFRz58m8Mz+/9/Py69O7O+zTE0vBR7P8LFR758m8MzX4NsPxTvw76+zSE0X4NsPxTvw76+zSE0L9tUP9k5Dr98m+Mz8gQ1P/MENb/4Nkcz2DkOPzLbVL98m/MzGu/DPl6DbL98m9MzxMVHPr8Ue798m6MzGO9DPr4Uez8h5Rs9TSvAPl6DbD9H5pg9PX4LPzHbVD+5+d09hIoxP/MENT+xQg0+JcRQP9o5Dj/VGiY+9fdnPxXvwz7SkDg+9fdnPxXvwz7SkDg+rEF2P8TFRz4Z70M+uBR7Py69O7PJxUc+rEF2P8LFR74Z70M++PdnPxTvw77dkDg++PdnPxTvw77dkDg+JsRQP9k5Dr/XGiY+hIoxP/MENb+xQg0+PX4LPzLbVL+5+d09TyvAPl6DbL9G5pg9F+9DPr8Ue78i5Rs91ZA4Pr4Uez9A5pg99gS1Pl6DbD8k9hU+TmYDPzHbVD/OtVk+cz0nP/MENT/Ui4o+SKdEP9o5Dj/B6aI+d4JaPxXvwz7yBLU+d4JaPxXvwz7yBLU+9PdnP8TFRz5LK8A+V4NsPy69O7MW78M+9PdnP8LFR75LK8A+eYJaPxTvw774BLU+eYJaPxTvw774BLU+SadEP9k5Dr/C6aI+cz0nP/MENb/Ui4o+TmYDPzLbVL/OtVk+9wS1Pl6DbL8k9hU+1JA4Pr8Ue79A5pg90xomPr4Uez+4+d09w+miPl6DbD/UtVk+WYPsPjHbVD92CJ4+FoMWP/MENT9OI8k+wfswP9o5Dj9cg+w+SadEPxXvwz5QZgM/SadEPxXvwz5QZgM/JcRQP8TFRz4+fgs/KdtUPy69O7PZOQ4/JcRQP8LFR74+fgs/S6dEPxTvw75TZgM/S6dEPxTvw75TZgM/wvswP9k5Dr9eg+w+FoMWP/MENb9OI8k+WYPsPjLbVL92CJ4+xOmiPl6DbL/VtVk+0homPr8Ue7+4+d09sEINPr4Uez+1Qg0+1YuKPl6DbD/ai4o+SSPJPjHbVD9OI8k+////PvMENT8AAAA/E4MWP9o5Dj8VgxY/cj0nPxXvwz5zPSc/cj0nPxXvwz5zPSc/gooxP8TFRz6EijE/6gQ1Py69O7PxBDU/gooxP8LFR76EijE/cz0nPxTvw752PSc/cz0nPxTvw752PSc/FIMWP9k5Dr8WgxY/////PvMENb8AAAA/SSPJPjLbVL9OI8k+1ouKPl6DbL/ai4o+r0INPr8Ue7+0Qg0+r/ndPb4Uez/YGiY+y7VZPl6DbD/G6aI+cQiePjHbVD9eg+w+TSPJPvMENT8XgxY/WIPsPto5Dj/C+zA/T2YDPxXvwz5Kp0Q/T2YDPxXvwz5Kp0Q/O34LP8TFRz4mxFA/0TkOPy69O7Mt21Q/O34LP8LFR74mxFA/T2YDPxTvw75Np0Q/T2YDPxTvw75Np0Q/WYPsPtk5Dr/D+zA/TSPJPvMENb8XgxY/cQiePjLbVL9eg+w+zbVZPl6DbL/G6aI+rfndPb8Ue7/WGiY+NuaYPb4Uez/ZkDg+GvYVPl6DbD/4BLU+wrVZPjHbVD9QZgM/1IuKPvMENT90PSc/vOmiPto5Dj9Ip0Q/8QS1PhXvwz53glo/8QS1PhXvwz53glo/QyvAPsTFRz7092c/Bu/DPi69O7NYg2w/QyvAPsLFR77092c/7wS1PhTvw756glo/7wS1PhTvw756glo/vemiPtk5Dr9Jp0Q/1IuKPvMENb90PSc/wrVZPjLbVL9QZgM/HPYVPl6DbL/4BLU+NOaYPb8Ue7/XkDg+DeUbPb4Uez8b70M+M+aYPV6DbD9PK8A+ofndPTHbVD8+fgs/sUINPvMENT+FijE/zBomPto5Dj8kxFA/0ZA4PhXvwz7192c/0ZA4PhXvwz7192c/C+9DPsTFRz6qQXY/qcVHPi69O7O2FHs/C+9DPsLFR76qQXY/ypA4PhTvw77492c/ypA4PhTvw77492c/yxomPtk5Dr8lxFA/sUINPvMENb+FijE/ofndPTLbVL8+fgs/N+aYPV6DbL9PK8A+C+UbPb8Ue78Z70M+sAgUMb4Uez/IxUc+6n7tsl6DbD8Z78M+ul+LszHbVD/YOQ4/i0BJM/MENT/yBDU/1P1asto5Dj8s21Q/FoGSMhXvwz5bg2w/FoGSMhXvwz5bg2w/ul+bs8TFRz65FHs/79eOtC69O7P2/38/ul+bs8LFR765FHs/ul/7sxTvw75eg2w/ul/7sxTvw75eg2w/db82s9k5Dr8t21Q/i0BJM/MENb/yBDU/ul+LszLbVL/YOQ4/sAgUMV6DbL8Z78M+oO7XsL8Ue7/GxUc+C+Ubvb4Uez8b70M+OuaYvV6DbD9OK8A+sfndvTHbVD89fgs/qkINvvMENT+EijE/zRomvto5Dj8jxFA/zpA4vhXvwz7092c/zpA4vhXvwz7092c/Fe9DvsTFRz6pQXY/y8VHvi69O7OzFHs/Fe9DvsLFR76pQXY/2ZA4vhTvw77392c/2ZA4vhTvw77392c/0Bomvtk5Dr8kxFA/qkINvvMENb+EijE/sfndvTLbVL89fgs/NuaYvV6DbL9OK8A+CuUbvb8Ue78Z70M+NOaYvb4Uez/YkDg+HfYVvl6DbD/2BLU+ybVZvjHbVD9OZgM/0IuKvvMENT9zPSc/u+mivto5Dj9Hp0Q/7gS1vhXvwz52glo/7gS1vhXvwz52glo/RyvAvsTFRz7y92c/FO/Dvi69O7NSg2w/RyvAvsLFR77y92c/9QS1vhTvw754glo/9QS1vhTvw754glo/vemivtk5Dr9Ip0Q/0IuKvvMENb9zPSc/ybVZvjLbVL9OZgM/G/YVvl6DbL/2BLU+M+aYvb8Ue7/WkDg+rPndvb4Uez/WGiY+zbVZvl6DbD/D6aI+cwievjHbVD9Yg+w+SSPJvvMENT8WgxY/VoPsvto5Dj/B+zA/TmYDvxXvwz5Jp0Q/TmYDvxXvwz5Jp0Q/PH4Lv8TFRz4jxFA/1zkOvy69O7Mk21Q/PH4Lv8LFR74jxFA/UWYDvxTvw75Kp0Q/UWYDvxTvw75Kp0Q/V4Psvtk5Dr/C+zA/SSPJvvMENb8WgxY/cwievjLbVL9Yg+w+y7VZvl6DbL/D6aI+qvndvb8Ue7/UGiY+rkINvr4Uez+zQg0+1YuKvl6DbD/Vi4o+SSPJvjHbVD9II8k++f//vvMENT/+//8+EoMWv9o5Dj8UgxY/cT0nvxXvwz5yPSc/cT0nvxXvwz5yPSc/gYoxv8TFRz6AijE/7gQ1vy69O7PmBDU/gYoxv8LFR76AijE/dD0nvxTvw75yPSc/dD0nvxTvw75yPSc/E4MWv9k5Dr8UgxY/+f//vvMENb/+//8+SSPJvjLbVL9II8k+1IuKvl6DbL/Vi4o+rUINvr8Ue7+yQg0+0Bomvr4Uez+2+d09wemivl6DbD/MtVk+V4PsvjHbVD9wCJ4+FIMWv/MENT9NI8k+v/swv9o5Dj9ag+w+SKdEvxXvwz5PZgM/SKdEvxXvwz5PZgM/I8RQv8TFRz46fgs/KdtUvy69O7PNOQ4/I8RQv8LFR746fgs/S6dEvxTvw75OZgM/S6dEvxTvw75OZgM/wPswv9k5Dr9ag+w+FIMWv/MENb9NI8k+V4PsvjLbVL9wCJ4+wOmivl6DbL/MtVk+zxomvr8Ue7+0+d09unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE0unKtswAAgD+yygE00ZA4vr4Uez8+5pg98gS1vl6DbD8c9hU+TGYDvzHbVD/CtVk+cT0nv/MENT/Ui4o+RadEv9o5Dj++6aI+dYJavxXvwz7wBLU+dYJavxXvwz7wBLU+8fdnv8TFRz5CK8A+U4Nsvy69O7P+7sM+8fdnv8LFR75CK8A+eIJavxTvw77uBLU+eIJavxTvw77uBLU+RqdEv9k5Dr++6aI+cT0nv/MENb/Ui4o+TGYDvzLbVL/CtVk+8QS1vl6DbL8c9hU+z5A4vr8Ue7885pg9E+9Dvr4Uez8e5Rs9SCvAvl6DbD865pg9On4LvzHbVD+l+d09gooxv/MENT+yQg0+IsRQv9o5Dj/QGiY+8/dnvxXvwz7PkDg+8/dnvxXvwz7PkDg+p0F2v8TFRz4K70M+sBR7vy69O7OcxUc+p0F2v8LFR74K70M+9fdnvxTvw77JkDg+9fdnvxTvw77JkDg+IsRQv9k5Dr/PGiY+gooxv/MENb+yQg0+On4LvzLbVL+l+d09RyvAvl6DbL875pg9Ee9Dvr8Ue78c5Rs9v8VHvr4Uez98m5szv8VHvr4Uez98m5szEu/Dvl6DbD/4NkczEu/Dvl6DbD/4Nkcz1DkOvzHbVD9ASMax1DkOvzHbVD9ASMax7wQ1v/MENT98m8Mz7wQ1v/MENT98m8MzKttUv9o5Dj/4NkczKttUv9o5Dj/4NkczWYNsvxXvwz4A3+YwWYNsvxXvwz4A3+YwWYNsvxXvwz4A3+YwWYNsvxXvwz4A3+YwthR7v8TFRz6EZJyzthR7v8TFRz6EZJyz8P9/vy69O7MhGc+08P9/vy69O7MhGc+0thR7v8LFR76EZJyzthR7v8LFR76EZJyzW4NsvxTvw76EZPyzW4NsvxTvw76EZPyzW4NsvxTvw76EZPyzW4NsvxTvw76EZPyzKttUv9k5Dr/4NgczKttUv9k5Dr/4Ngcz7wQ1v/MENb98m8Mz7wQ1v/MENb98m8Mz1DkOvzLbVL9ASMax1DkOvzLbVL9ASMaxEe/Dvl6DbL98m4MzEe/Dvl6DbL98m4MzvcVHvr8Ue798m5MzvcVHvr8Ue798m5MzEu9Dvr4Uez/35Bu9RivAvl6DbD8s5pi9OX4LvzHbVD+l+d29gYoxv/MENT+mQg2+IcRQv9o5Dj/JGia+8vdnvxXvwz7OkDi+8vdnvxXvwz7OkDi+pkF2v8TFRz4U70O+rRR7vy69O7PPxUe+pkF2v8LFR74U70O+9PdnvxTvw77YkDi+9PdnvxTvw77YkDi+IcRQv9k5Dr/KGia+gYoxv/MENb+mQg2+OX4LvzLbVL+l+d29RSvAvl6DbL8q5pi9EO9Dvr8Ue7/25Bu9z5A4vr4Uez8p5pi97gS1vl6DbD8U9hW+S2YDvzHbVD/AtVm+cD0nv/MENT/Oi4q+RadEv9o5Dj+66aK+dIJavxXvwz7uBLW+dIJavxXvwz7uBLW+7/dnv8TFRz5GK8C+TYNsvy69O7MV78O+7/dnv8LFR75GK8C+dYJavxTvw770BLW+dYJavxTvw770BLW+RadEv9k5Dr+66aK+cD0nv/MENb/Oi4q+S2YDvzLbVL/AtVm+7gS1vl6DbL8S9hW+zZA4vr8Ue78o5pi9zRomvr4Uez+g+d29u+mivl6DbD/CtVm+UoPsvjHbVD9uCJ6+FIMWv/MENT9GI8m+v/swv9o5Dj9Vg+y+R6dEvxXvwz5NZgO/R6dEvxXvwz5NZgO/IMRQv8TFRz47fgu/H9tUvy69O7PWOQ6/IMRQv8LFR747fgu/R6dEvxTvw75QZgO/R6dEvxTvw75QZgO/v/swv9k5Dr9Vg+y+FIMWv/MENb9GI8m+UoPsvjLbVL9uCJ6+u+mivl6DbL/AtVm+yxomvr8Ue7+e+d29qkINvr4Uez+oQg2+zouKvl6DbD/Pi4q+QSPJvjHbVD9EI8m++v//vvMENT/3//++EoMWv9o5Dj8Rgxa/cD0nvxXvwz5wPSe/cD0nvxXvwz5wPSe/fooxv8TFRz6AijG/4QQ1vy69O7PsBDW/fooxv8LFR76AijG/cD0nvxTvw75yPSe/cD0nvxTvw75yPSe/EoMWv9k5Dr8Rgxa/+v//vvMENb/3//++QSPJvjLbVL9EI8m+zouKvl6DbL/Oi4q+qUINvr8Ue7+mQg2+pPndvb4Uez/JGia+v7VZvl6DbD+76aK+aQievjHbVD9Sg+y+SCPJvvMENT8Sgxa/VYPsvto5Dj+++zC/TWYDvxXvwz5Hp0S/TWYDvxXvwz5Hp0S/OH4Lv8TFRz4ixFC/yTkOvy69O7Mm21S/OH4Lv8LFR74ixFC/TWYDvxTvw75Jp0S/TWYDvxTvw75Jp0S/VYPsvtk5Dr+++zC/SCPJvvMENb8Sgxa/aQievjLbVL9Sg+y+v7VZvl6DbL+66aK+o/ndvb8Ue7/HGia+LOaYvb4Uez/JkDi+EPYVvl6DbD/tBLW+tbVZvjHbVD9JZgO/z4uKvvMENT9vPSe/uemivto5Dj9Dp0S/7AS1vhXvwz50glq/7AS1vhXvwz50glq/PSvAvsTFRz7w92e/9+7Dvi69O7NPg2y/PSvAvsLFR77w92e/6wS1vhTvw752glq/6wS1vhTvw752glq/uemivtk5Dr9Dp0S/z4uKvvMENb9vPSe/tbVZvjLbVL9JZgO/EfYVvl6DbL/sBLW+LOaYvb8Ue7/HkDi+/eQbvb4Uez8K70O+JOaYvV6DbD9DK8C+jvndvTHbVD82fgu/qUINvvMENT+AijG/yBomvto5Dj8fxFC/x5A4vhXvwz7x92e/x5A4vhXvwz7x92e/AO9DvsTFRz6mQXa/kMVHvi69O7OsFHu/AO9DvsLFR76mQXa/w5A4vhTvw77z92e/w5A4vhTvw77z92e/yBomvtk5Dr8fxFC/qUINvvMENb+AijG/jvndvTLbVL82fgu/J+aYvV6DbL9CK8C+/+Qbvb8Ue78I70O+CZQQNS69O7Pr/3+/I1ByNMLFR760FHu/I1BSNBTvw75Zg2y/I1BSNBTvw75Zg2y/RqCUM16DbL8M78O+FoHyMr8Ue7+0xUe+AAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAAAAgL8uvbszAAAAAPbFej+cyU2+AAAAANjzaz+Nlca+AAAAAKghVD8fTQ+/AAAAAGg7ND9syzW/AAAAABuBDT+rVVW/AAAAAIbVwj7Zu2y/AAAAAIbVwj7Zu2y/AAAAAI2RRj72IXu/AAAAABuBDb+rVVW/AAAAAGg7NL9syzW/AAAAAKghVL8fTQ+/QYEgPfbFej+U0Um+NvGaPdjzaz+GxcK+v6HfPaghVD8Ziwy/HNkNPmg7ND9lTTK/TXkmPhuBDT+iO1G/cbk4PobVwj7QL2i/cbk4PobVwj7QL2i/iPlDPo2RRj7tT3a/kMFHPgAAAAD2E3u/iPlDPo2RRr7tT3a/cbk4PobVwr7QL2i/cbk4PobVwr7QL2i/TXkmPhuBDb+iO1G/HNkNPmg7NL9lTTK/v6HfPaghVL8Ziwy/NvGaPdjza7+GxcK+QYEgPfbFer+U0Um+O4GdPfbFej98GT6+MPkXPtjzaz9vebe+t1lbPqghVD8JYwS/FiGLPmg7ND9Q8ye/R0WjPhuBDT+KF0W/ai21PobVwj61t1q/ai21PobVwj61t1q/gDXAPo2RRj7QBWi/iO3DPgAAAADZgWy/gDXAPo2RRr7QBWi/ai21PobVwr61t1q/ai21PobVwr61t1q/R0WjPhuBDb+KF0W/FiGLPmg7NL9Q8ye/t1lbPqghVL8JYwS/MPkXPtjza79vebe+O4GdPfbFer98GT6+yaHkPfbFej9WGSu+ualcPtjzaz9KHaW+PjmfPqghVD/dSe6+lP3JPmg7ND8uJxe/2gntPhuBDT9jYTG/B4UDP4bVwj6K10S/B4UDP4bVwj6K10S/F4ULP42RRj6iz1C/HDkOPwAAAACq2VS/F4ULP42RRr6iz1C/B4UDP4bVwr6K10S/B4UDP4bVwr6K10S/2gntPhuBDb9jYTG/lP3JPmg7NL8uJxe/PjmfPqghVL/dSe6+ualcPtjza79KHaW+yaHkPfbFer9WGSu+I4ERPvbFej8jgRG+GW2MPtjzaz8ZbYy+laXKPqghVD+Vpcq+AYsAP2g7ND8BiwC/LtkWPxuBDT8u2Ra/T2UnP4bVwj5PZSe/T2UnP4bVwj5PZSe/Y5MxP42RRj5jkzG/agM1PwAAAABqAzW/Y5MxP42RRr5jkzG/T2UnP4bVwr5PZSe/T2UnP4bVwr5PZSe/LtkWPxuBDb8u2Ra/AYsAP2g7NL8BiwC/laXKPqghVL+Vpcq+GW2MPtjza78ZbYy+I4ERPvbFer8jgRG+VhkrPvbFej/JoeS9Sh2lPtjzaz+5qVy+3UnuPqghVD8+OZ++LicXP2g7ND+U/cm+Y2ExPxuBDT/aCe2+itdEP4bVwj4HhQO/itdEP4bVwj4HhQO/os9QP42RRj4XhQu/qtlUPwAAAAAcOQ6/os9QP42RRr4XhQu/itdEP4bVwr4HhQO/itdEP4bVwr4HhQO/Y2ExPxuBDb/aCe2+LicXP2g7NL+U/cm+3UnuPqghVL8+OZ++Sh2lPtjza7+5qVy+VhkrPvbFer/JoeS9fBk+PvbFej87gZ29b3m3Ptjzaz8w+Re+CWMEP6ghVD+3WVu+UPMnP2g7ND8WIYu+ihdFPxuBDT9HRaO+tbdaP4bVwj5qLbW+tbdaP4bVwj5qLbW+0AVoP42RRj6ANcC+2YFsPwAAAACI7cO+0AVoP42RRr6ANcC+tbdaP4bVwr5qLbW+tbdaP4bVwr5qLbW+ihdFPxuBDb9HRaO+UPMnP2g7NL8WIYu+CWMEP6ghVL+3WVu+b3m3Ptjza78w+Re+fBk+PvbFer87gZ29lNFJPvbFej9BgSC9hsXCPtjzaz828Zq9GYsMP6ghVD+/od+9ZU0yP2g7ND8c2Q2+ojtRPxuBDT9NeSa+0C9oP4bVwj5xuTi+0C9oP4bVwj5xuTi+7U92P42RRj6I+UO+9hN7PwAAAACQwUe+7U92P42RRr6I+UO+0C9oP4bVwr5xuTi+0C9oP4bVwr5xuTi+ojtRPxuBDb9NeSa+ZU0yP2g7NL8c2Q2+GYsMP6ghVL+/od+9hsXCPtjza7828Zq9lNFJPvbFer9BgSC9nMlNPvbFej8AAACAjZXGPtjzaz8AAACAH00PP6ghVD8AAACAbMs1P2g7ND8AAACAq1VVPxuBDT8AAACA2btsP4bVwj4AAACA2btsP4bVwj4AAACA9iF7P42RRj4AAACAAACAPwAAAAAAAACA9iF7P42RRr4AAACA2btsP4bVwr4AAACA2btsP4bVwr4AAACAq1VVPxuBDb8AAACAbMs1P2g7NL8AAACAH00PP6ghVL8AAACAjZXGPtjza78AAACAnMlNPvbFer8AAACAlNFJPvbFej9BgSA9hsXCPtjzaz828Zo9GYsMP6ghVD+/od89ZU0yP2g7ND8c2Q0+ojtRPxuBDT9NeSY+0C9oP4bVwj5xuTg+0C9oP4bVwj5xuTg+7U92P42RRj6I+UM+9hN7PwAAAACQwUc+7U92P42RRr6I+UM+0C9oP4bVwr5xuTg+0C9oP4bVwr5xuTg+ojtRPxuBDb9NeSY+ZU0yP2g7NL8c2Q0+GYsMP6ghVL+/od89hsXCPtjza7828Zo9lNFJPvbFer9BgSA9fBk+PvbFej87gZ09b3m3Ptjzaz8w+Rc+CWMEP6ghVD+3WVs+UPMnP2g7ND8WIYs+ihdFPxuBDT9HRaM+tbdaP4bVwj5qLbU+tbdaP4bVwj5qLbU+0AVoP42RRj6ANcA+2YFsPwAAAACI7cM+0AVoP42RRr6ANcA+tbdaP4bVwr5qMbU+tbdaP4bVwr5qMbU+ihdFPxuBDb9HRaM+UPMnP2g7NL8WIYs+CWMEP6ghVL+3WVs+b3m3Ptjza78w+Rc+fBk+PvbFer87gZ09VhkrPvbFej/JoeQ9Sh2lPtjzaz+5qVw+3UnuPqghVD8+OZ8+LicXP2g7ND+U/ck+Y2ExPxuBDT/aCe0+itdEP4bVwj4HhQM/itdEP4bVwj4HhQM/os9QP42RRj4XhQs/qtlUPwAAAAAcOQ4/os9QP42RRr4XhQs/itdEP4bVwr4HhQM/itdEP4bVwr4HhQM/Y2ExPxuBDb/aCe0+LicXP2g7NL+U/ck+3UnuPqghVL8+OZ8+Sh2lPtjza7+5qVw+VhkrPvbFer/JoeQ9I4ERPvbFej8jgRE+GW2MPtjzaz8ZbYw+laXKPqghVD+Vpco+AYsAP2g7ND8BiwA/LtkWPxuBDT8u2RY/T2UnP4bVwj5PZSc/T2UnP4bVwj5PZSc/Y5MxP42RRj5jkzE/agM1PwAAAABqAzU/Y5MxP42RRr5jkzE/T2UnP4bVwr5PZSc/T2UnP4bVwr5PZSc/LtkWPxuBDb8u2RY/AYsAP2g7NL8BiwA/laXKPqghVL+Vpco+GW2MPtjza78ZbYw+I4ERPvbFer8jgRE+yaHkPfbFej9WGSs+ualcPtjzaz9KHaU+PjmfPqghVD/dSe4+lP3JPmg7ND8uJxc/2gntPhuBDT9jYTE/B4UDP4bVwj6K10Q/B4UDP4bVwj6K10Q/F4ULP42RRj6iz1A/HDkOPwAAAACq2VQ/F4ULP42RRr6iz1A/B4UDP4bVwr6K10Q/B4UDP4bVwr6K10Q/2gntPhuBDb9jYTE/lP3JPmg7NL8uJxc/PjmfPqghVL/dSe4+ualcPtjza79KHaU+yaHkPfbFer9WGSs+O3GdPfbFej98GT4+MPkXPtjzaz9vebc+t1lbPqghVD8JYwQ/FiGLPmg7ND9Q8yc/R0WjPhuBDT+KF0U/ai21PobVwj61t1o/ai21PobVwj61t1o/gDXAPo2RRj7QBWg/iO3DPgAAAADZgWw/gDXAPo2RRr7QBWg/ai21PobVwr61t1o/ai21PobVwr61t1o/R0WjPhuBDb+KF0U/FiGLPmg7NL9Q8yc/t1lbPqghVL8JYwQ/MPkXPtjza79vebc+O4GdPfbFer98GT4+QYEgPfbFej+U0Uk+NvGaPdjzaz+GxcI+v6HfPaghVD8Ziww/HNkNPmg7ND9lTTI/TXkmPhuBDT+iO1E/cbk4PobVwj7QL2g/cbk4PobVwj7QL2g/iPlDPo2RRj7tT3Y/kMFHPgAAAAD2E3s/iPlDPo2RRr7tT3Y/cbk4PobVwr7QL2g/cbk4PobVwr7QL2g/TXkmPhuBDb+iO1E/HNkNPmg7NL9lTTI/v6HfPaghVL8Ziww/NvGaPdjza7+GxcI+QYEgPfbFer+U0Uk+AAAAAPbFej+cyU0+AAAAANjzaz+NlcY+AAAAAKghVD8fTQ8/AAAAAGg7ND9syzU/AAAAABuBDT+rVVU/AAAAAIbVwj7Zu2w/AAAAAIbVwj7Zu2w/AAAAAI2RRj72IXs/AAAAAAAAAAAAAIA/AAAAAI2RRr72IXs/AAAAAIbVwr7Zu2w/AAAAAIbVwr7Zu2w/AAAAABuBDb+rVVU/AAAAAGg7NL9syzU/AAAAAKghVL8fTQ8/AAAAANjza7+NlcY+AAAAAPbFer+cyU0+QYEgvfbFej+U0Uk+NvGavdjzaz+GxcI+v6HfvaghVD8Ziww/HNkNvmg7ND9lTTI/TXkmvhuBDT+iO1E/cbk4vobVwj7QL2g/cbk4vobVwj7QL2g/iPlDvo2RRj7tT3Y/kMFHvgAAAAD2E3s/iPlDvo2RRr7tT3Y/cbk4vobVwr7QL2g/cbk4vobVwr7QL2g/TXkmvhuBDb+iO1E/HNkNvmg7NL9lTTI/v6HfvaghVL8Ziww/NvGavdjza7+GxcI+QYEgvfbFer+U0Uk+O4GdvfbFej98GT4+MPkXvtjzaz9vebc+t1lbvqghVD8JYwQ/FiGLvmg7ND9Q8yc/R0WjvhuBDT+KF0U/ai21vobVwj61t1o/ai21vobVwj61t1o/gDXAvo2RRj7QBWg/iO3DvgAAAADZgWw/gDXAvo2RRr7QBWg/ajG1vobVwr61t1o/ajG1vobVwr61t1o/R0WjvhuBDb+KF0U/FiGLvmg7NL9Q8yc/t1lbvqghVL8JYwQ/MPkXvtjza79vebc+O4GdvfbFer98GT4+yaHkvfbFej9WGSs+ualcvtjzaz9KHaU+PjmfvqghVD/dSe4+lP3Jvmg7ND8uJxc/2gntvhuBDT9jYTE/B4UDv4bVwj6K10Q/B4UDv4bVwj6K10Q/F4ULv42RRj6iz1A/HDkOvwAAAACq2VQ/F4ULv42RRr6iz1A/B4UDv4bVwr6K10Q/B4UDv4bVwr6K10Q/2gntvhuBDb9jYTE/lP3Jvmg7NL8uJxc/PjmfvqghVL/dSe4+ualcvtjza79KHaU+yaHkvfbFer9WGSs+I4ERvvbFej8jgRE+GW2Mvtjzaz8ZbYw+laXKvqghVD+Vpco+AYsAv2g7ND8BiwA/LtkWvxuBDT8u2RY/T2Unv4bVwj5PZSc/T2Unv4bVwj5PZSc/Y5Mxv42RRj5jkzE/agM1vwAAAABqAzU/Y5Mxv42RRr5jkzE/T2Unv4bVwr5PZSc/T2Unv4bVwr5PZSc/LtkWvxuBDb8u2RY/AYsAv2g7NL8BiwA/laXKvqghVL+Vpco+GW2Mvtjza78ZbYw+I4ERvvbFer8jgRE+VhkrvvbFej/JoeQ9Sh2lvtjzaz+5qVw+3UnuvqghVD8+OZ8+LicXv2g7ND+U/ck+Y2ExvxuBDT/aCe0+itdEv4bVwj4HhQM/itdEv4bVwj4HhQM/os9Qv42RRj4XhQs/qtlUvwAAAAAcOQ4/os9Qv42RRr4XhQs/itdEv4bVwr4HhQM/itdEv4bVwr4HhQM/Y2ExvxuBDb/aCe0+LicXv2g7NL+U/ck+3UnuvqghVL8+OZ8+Sh2lvtjza7+5qVw+VhkrvvbFer/JoeQ9AAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAfBk+vvbFej87gZ09b3m3vtjzaz8w+Rc+CWMEv6ghVD+3WVs+UPMnv2g7ND8WIYs+ihdFvxuBDT9HRaM+tbdav4bVwj5qLbU+tbdav4bVwj5qLbU+0AVov42RRj6ANcA+2YFsvwAAAACI7cM+0AVov42RRr6ANcA+tbdav4bVwr5qLbU+tbdav4bVwr5qLbU+ihdFvxuBDb9HRaM+UPMnv2g7NL8WIYs+CWMEv6ghVL+3WVs+b3m3vtjza78w+Rc+fBk+vvbFer87gZ09lNFJvvbFej9BgSA9hsXCvtjzaz828Zo9GYsMv6ghVD+/od89ZU0yv2g7ND8c2Q0+ojtRvxuBDT9NeSY+0C9ov4bVwj5xuTg+0C9ov4bVwj5xuTg+7U92v42RRj6I+UM+9hN7vwAAAACQwUc+7U92v42RRr6I+UM+0C9ov4bVwr5xuTg+0C9ov4bVwr5xuTg+ojtRvxuBDb9NeSY+ZU0yv2g7NL8c2Q0+GYsMv6ghVL+/od89hsXCvtjza7828Zo9lNFJvvbFer9BgSA9nMlNvvbFej8AAACAnMlNvvbFej8AAACAjZXGvtjzaz8AAACAjZXGvtjzaz8AAACAH00Pv6ghVD8AAACAH00Pv6ghVD8AAACAbMs1v2g7ND8AAACAbMs1v2g7ND8AAACAq1VVvxuBDT8AAACAq1VVvxuBDT8AAACA2btsv4bVwj4AAACA2btsv4bVwj4AAACA2btsv4bVwj4AAACA2btsv4bVwj4AAACA9iF7v42RRj4AAACA9iF7v42RRj4AAACAAP5/vwAAAAAAAACAAP5/vwAAAAAAAACA9iF7v42RRr4AAACA9iF7v42RRr4AAACA2btsv4bVwr4AAACA2btsv4bVwr4AAACA2btsv4bVwr4AAACA2btsv4bVwr4AAACAq1VVvxuBDb8AAACAq1VVvxuBDb8AAACAbMs1v2g7NL8AAACAbMs1v2g7NL8AAACAH00Pv6ghVL8AAACAH00Pv6ghVL8AAACAjZXGvtjza78AAACAjZXGvtjza78AAACAnMlNvvbFer8AAACAnMlNvvbFer8AAACAlNFJvvbFej9BgSC9hsXCvtjzaz828Zq9GYsMv6ghVD+/od+9ZU0yv2g7ND8c2Q2+ojtRvxuBDT9NeSa+0C9ov4bVwj5xuTi+0C9ov4bVwj5xuTi+7U92v42RRj6I+UO+9hN7vwAAAACQwUe+7U92v42RRr6I+UO+0C9ov4bVwr5xuTi+0C9ov4bVwr5xuTi+ojtRvxuBDb9NeSa+ZU0yv2g7NL8c2Q2+GYsMv6ghVL+/od+9hsXCvtjza7828Zq9lNFJvvbFer9BgSC9fBk+vvbFej87gZ29b3m3vtjzaz8w+Re+CWMEv6ghVD+3WVu+UPMnv2g7ND8WIYu+ihdFvxuBDT9HRaO+tbdav4bVwj5qLbW+tbdav4bVwj5qLbW+0AVov42RRj6ANcC+2YFsvwAAAACI7cO+0AVov42RRr6ANcC+tbdav4bVwr5qMbW+tbdav4bVwr5qMbW+ihdFvxuBDb9HRaO+UPMnv2g7NL8WIYu+CWMEv6ghVL+3WVu+b3m3vtjza78w+Re+fBk+vvbFer87gZ29VhkrvvbFej/JoeS9Sh2lvtjzaz+5qVy+3UnuvqghVD8+OZ++LicXv2g7ND+U/cm+Y2ExvxuBDT/aCe2+itdEv4bVwj4HhQO/itdEv4bVwj4HhQO/os9Qv42RRj4XhQu/qtlUvwAAAAAcOQ6/os9Qv42RRr4XhQu/itdEv4bVwr4HhQO/itdEv4bVwr4HhQO/Y2ExvxuBDb/aCe2+LicXv2g7NL+U/cm+3UnuvqghVL8+OZ++Sh2lvtjza7+5qVy+VhkrvvbFer/JoeS9I4ERvvbFej8jgRG+GW2Mvtjzaz8ZbYy+laXKvqghVD+Vpcq+AYsAv2g7ND8BiwC/LtkWvxuBDT8u2Ra/T2Unv4bVwj5PZSe/T2Unv4bVwj5PZSe/Y5Mxv42RRj5jkzG/agM1vwAAAABqAzW/Y5Mxv42RRr5jkzG/T2Unv4bVwr5PZSe/T2Unv4bVwr5PZSe/LtkWvxuBDb8u2Ra/AYsAv2g7NL8BiwC/laXKvqghVL+Vpcq+GW2Mvtjza78ZbYy+I4ERvvbFer8jgRG+yaHkvfbFej9WGSu+ualcvtjzaz9KHaW+PjmfvqghVD/dSe6+lP3Jvmg7ND8uJxe/2gntvhuBDT9jYTG/B4UDv4bVwj6K10S/B4UDv4bVwj6K10S/F4ULv42RRj6iz1C/HDkOvwAAAACq2VS/F4ULv42RRr6iz1C/B4UDv4bVwr6K10S/B4UDv4bVwr6K10S/2gntvhuBDb9jYTG/lP3Jvmg7NL8uJxe/PjmfvqghVL/dSe6+ualcvtjza79KHaW+yaHkvfbFer9WGSu+O3GdvfbFej98GT6+MPkXvtjzaz9vebe+t1lbvqghVD8JYwS/FiGLvmg7ND9Q8ye/R0WjvhuBDT+KF0W/ai21vobVwj61t1q/ai21vobVwj61t1q/gDXAvo2RRj7QBWi/iO3DvgAAAADZgWy/gDXAvo2RRr7QBWi/ai21vobVwr61t1q/ai21vobVwr61t1q/R0WjvhuBDb+KF0W/FiGLvmg7NL9Q8ye/t1lbvqghVL8JYwS/MPkXvtjza79vebe+O4GdvfbFer98GT6+QYEgvfbFej+U0Um+NvGavdjzaz+GxcK+v6HfvaghVD8Ziwy/HNkNvmg7ND9lTTK/TXkmvhuBDT+iO1G/cbk4vobVwj7QL2i/cbk4vobVwj7QL2i/iPlDvo2RRj7tT3a/kMFHvgAAAAD2E3u/iPlDvo2RRr7tT3a/cbk4vobVwr7QL2i/cbk4vobVwr7QL2i/TXkmvhuBDb+iO1G/HNkNvmg7NL9lTTK/v6HfvaghVL8Ziwy/NvGavdjza7+GxcK+QYEgvfbFer+U0Um+AAAAAAAAAAAAAIC/AAAAAI2RRr72IXu/AAAAAIbVwr7Zu2y/AAAAAIbVwr7Zu2y/AAAAANjza7+Nlca+AAAAAPbFer+cyU2+AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAABAP/D/fz0AAEA/AAAAPgAAQD8AAEA+AABAP/z/fz4AAEA/AACgPgAAQD8AAMA+AABAPwAAwD4AAEA/AADgPgAAQD///y8/AABAPwAAQD8AAEA/AABQPwAAOD/w/389AAA4PwAAAD4AADg/AABAPgAAOD/8/38+AAA4PwAAoD4AADg/AADAPgAAOD8AAMA+AAA4PwAA4D4AADg/AAAAPwAAOD8AABA/AAA4PwAAID8AADg/AAAgPwAAOD///y8/AAA4PwAAQD8AADg/AABQPwAAOD///18/AAA4PwEAcD8AADA/8P9/PQAAMD8AAAA+AAAwPwAAQD4AADA//P9/PgAAMD8AAKA+AAAwPwAAwD4AADA/AADAPgAAMD8AAOA+AAAwPwAAAD8AADA/AAAQPwAAMD8AACA/AAAwPwAAID8AADA///8vPwAAMD8AAEA/AAAwPwAAUD8AADA///9fPwAAMD8BAHA/AAAoP/D/fz0AACg/AAAAPgAAKD8AAEA+AAAoPwAAgD4AACg/AACgPgAAKD8AAMA+AAAoPwAAwD4AACg/AADgPgAAKD8AAAA/AAAoPwAAED8AACg/AAAgPwAAKD8AACA/AAAoP///Lz8AACg/AABAPwAAKD8AAFA/AAAoP///Xz8AACg/AQBwP///Hz/w/389//8fPwAAAD7//x8/AABAPgAAID/8/38+AAAgP/7/nz4AACA//v+/PgAAID/+/78+AAAgPwAA4D4AACA/AAAAPwAAID8AABA/AAAgPwAAID8AACA/AAAgPwAAID///y8/AAAgPwAAQD8AACA/AABQPwAAID///18///8fPwEAcD///xc/8P9/Pf//Fz8AAAA+AAAYPwAAQD4AABg//P9/PgAAGD8AAKA+AAAYP/7/vz4AABg//v+/PgAAGD8AAOA+AAAYPwAAAD8AABg/AAAQPwAAGD8AACA/AAAYPwAAID8AABg///8vPwAAGD8AAEA/AAAYPwAAUD8AABg///9fP///Fz8BAHA///8PP/D/fz3//w8/AAAAPv//Dz8AAEA+AAAQP/z/fz4AABA/AACgPgAAED/+/78+AAAQP/7/vz4AABA/AADgPgAAED8AAAA/AAAQPwAAED8AABA/AAAgPwAAED8AACA/AAAQP///Lz8AABA/AABAP///Dz8AAFA///8PP///Xz///w8/AQBwP///Bz/w/389//8HPwAAAD7//wc/AABAPgAACD/8/38+AAAIP/7/nz4AAAg//v+/PgAACD/+/78+AAAIPwAA4D4AAAg/AAAAPwAACD8AABA/AAAIPwAAID8AAAg/AAAgPwAACD///y8/AAAIPwAAQD///wc/AABQP///Bz///18///8HPwEAcD/+//8+8P9/Pf7//z4AAAA+////PgAAQD4AAAA//P9/Pv///z7+/58+AAAAP/7/vz4AAAA//v+/PgAAAD8AAOA+////PgAAAD8AAAA/AAAQP////z4AACA/////PgAAID////8+//8vPwAAAD8AAEA/////PgAAUD////8+//9fP/7//z4BAHA//v/vPvD/fz3+/+8+AAAAPv//7z4AAEA+AADwPvz/fz7//+8+/v+fPgAA8D7+/78+AADwPv7/vz4AAPA+AADgPv//7z4AAAA/AADwPgAAED///+8+AAAgP///7z4AACA////vPgEAMD8AAPA+AABAP///7z4AAFA////vPv//Xz/+/+8+AQBwP/7/3z7w/389/v/fPgAAAD7//98+/P8/PgAA4D78/38+///fPv7/nz4AAOA+/v+/PgAA4D7+/78+AADgPgAA4D7//98+AAAAPwAA4D4AABA////fPgAAID///98+AAAgP///3z4BADA/AADgPgAAQD///98+AABQP///3z7//18//v/fPgEAcD/+/88+8P9/Pf//zz4AAAA+///PPvz/Pz4AANA+/P9/PgAA0D7+/58+AADQPv7/vz4AANA+/v+/PgAA0D4AAOA+///PPgAAAD8AANA+AAAQP///zz4AACA////PPgAAID8AANA+AQAwPwAA0D4AAEA////PPgAAUD///88+//9fP/7/zz4BAHA//v+/PvD/fz3+/78+AAAAPv//vz4AAEA+AADAPvz/fz4AAMA+/v+fPgAAwD7+/78+AADAPv7/vz4AAMA+AADgPv7/vz4AAAA/AADAPgAAED8AAMA+AAAgPwAAwD4AACA/AADAPgEAMD8AAMA+AABAP///vz4AAFA///+/Pv//Xz/+/78+AQBwP///rz7w/389AACwPgAAAD4AALA+AABAPgAAsD78/38+AACwPv7/nz4AALA+/v+/PgAAsD7+/78+AACwPgAA4D7+/68+AAAAPwAAsD4AABA/AACwPgAAID8AALA+AAAgPwAAsD4BADA/AACwPgAAQD8AALA+AABQPwAAsD7//18///+vPgEAcD8AAKA+8P9/PQAAoD4AAAA+AACgPgAAQD4AAKA+/P9/PgAAoD7+/58+AACgPv7/vz4AAKA+/v+/PgAAoD4AAOA+//+fPgAAAD8AAKA+AAAQPwAAoD4AACA/AACgPgAAID8AAKA+AQAwPwAAoD4AAEA/AACgPgAAUD8AAKA+//9fPwAAoD4BAHA/AACQPvD/fz0AAJA+AAAAPgAAkD4AAEA+AACQPvz/fz4AAJA+/v+fPgAAkD7+/78+AACQPv7/vz4AAJA+AADgPv7/jz4AAAA/AACQPgAAED8AAJA+AAAgPwAAkD4AACA/AACQPgEAMD8AAJA+AABAPwAAkD4AAFA/AACQPv//Xz8AAJA+AQBwPwAAgD7w/389AACAPgAAAD7+/38+AABAPgAAgD78/38+AACAPv7/nz4AAIA+/v+/PgAAgD7+/78+AACAPv7/3z7+/38+AAAAPwAAgD4AABA//v9/PgAAID/+/38+AAAgPwAAgD4BADA/AACAPgAAQD/+/38+AABQPwAAgD7//18/AACAPgEAcD8CAGA+8P9/PQAAYD4AAAA+AABgPgAAQD4CAGA+/P9/PgAAYD7+/58+AABgPv7/vz4AAGA+/v+/PgAAYD7+/98+/v9fPgAAAD8AAGA+AAAQPwAAYD4AACA/AABgPgAAID8AAGA+AQAwPwIAYD4AAEA/AABgPgAAUD8AAGA+//9fPwIAYD4BAHA/AgBAPvD/fz0AAEA+AAAAPgAAQD78/z8+AgBAPvz/fz4AAEA+/v+fPgAAQD7+/78+AABAPv7/vz4AAEA+/v/fPv7/Pz4AAAA/AABAPgAAED8AAEA+AAAgPwAAQD4AACA/AABAPgEAMD8CAEA+AABAPwAAQD4AAFA/AABAPv//Xz8CAEA+AQBwPwQAID7w/389AgAgPgAAAD7+/x8+/P8/PgIAID78/38+AgAgPv7/nz4CACA+/v+/PgIAID7+/78+/v8fPv7/3z78/x8+AAAAP/7/Hz4AABA//v8fPgAAID/+/x8+AAAgPwIAID4BADA/AgAgPgAAQD/+/x8+AQBQPwIAID7//18/BAAgPgEAcD8EAAA+8P9/PQAAAD4AAAA+AAAAPvz/Pz4EAAA+/P9/PgAAAD7+/58+AAAAPv7/vz4AAAA+/v+/PgAAAD7+/98+/P//PQAAAD8AAAA+AAAQPwAAAD4AACA/AAAAPgAAID8AAAA+AQAwPwQAAD4BAEA/AAAAPgEAUD8AAAA+//9fPwQAAD4BAHA/CADAPfD/fz0EAMA9AAAAPgAAwD38/z8+BADAPfz/fz4EAMA9/v+fPgQAwD3+/78+BADAPf7/vz4AAMA9/v/fPvz/vz0AAAA/AADAPQAAED8AAMA9AAAgPwAAwD0AACA/BADAPQEAMD8EAMA9AQBAPwAAwD0BAFA/BADAPf//Xz8IAMA9AQBwP0AAgDwAAAAAGABAPQAAAAAQAKA9AAAAAAwA4D0AAAAABAAQPgAAAAACADA+AAAAAAIAUD4AAAAA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AABwPgAAgD8AAIg+AACAPwAAmD4AAIA//v+nPgAAgD/+/7c+AACAP/3/xz4AAIA//f/XPgAAgD/9/+c+AACAP/3/9z4AAIA//v8DPwAAgD/+/ws/AACAP/7/Ez8AAIA///8bPwAAgD///yM/AACAP///Kz8AAIA/AAA0PwAAgD///zs/AACAPwAARD8AAIA/AABMPwAAgD8AAFQ/AACAPwAAXD8AAIA/AABkPwAAgD8BAGw/AACAPwEAdD8AAIA/AQB8PwAAgD8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////KAAoAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD//7j9KAAoAP//AAAAAAAA/////ygAKAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8oACgA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//3T7KAAoAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//+69CgAKAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//88ADwAPAD//7r0AAAAAP//PAA8ADwA//+69AAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//KAAoACgA//90+wAAAAD///fcAAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA//+69AAAAAD/////AAAAAP//uP0AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD//7j9AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA//8AAAAAAAD/////AAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP//AAAAAAAA/////wAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP////8AAAAA/////wAAAAD//wAAAAAAAP////8AAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//xQA//8UAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//9QAP//UAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//KAAoACgA//8oACgAKAD//1AAUABQAP//PAA8ADwA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//3cAdwB3AP//UABQAFAA//9QAFAAUAD//xQAFAAUAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//88ADwAPAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAFAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAALj9//8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//FAAUABQA//88ADwAPAD//wAAAAB0+///AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//xQAFAAUAP//FAAUABQA//8AAAAAuP3//wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAP////8AAAAAAAD//wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAD/////AAAAAAAA//8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAAAAP//AAAAAAAA//8AAAAA/////wAAAAAAAP//AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AAAAAP////8KAAkAGAAKABgAGQAFAAQADwAFAA8AEABPAgoAGQBPAhkAGgAHAAYAEQAHABEAEgBQAk8CGgBQAhoAGwBLAgcAEgBLAhIAEwAAAIcBCwBoAlACGwBMAksCEwBMAhMAFAABAAAACwABAAsADABOAkwCFABOAhQAFgACAAEADAACAAwADQAIAE0CFQAIABUAFwADAAIADQADAA0ADgAJAAgAFwAJABcAGAAEAAMADgAEAA4ADwAYABcAKAAYACgAKQAPAA4AHwAPAB8AIAAZABgAKQAZACkAKgAQAA8AIAAQACAAIQAaABkAKgAaACoAKwASABEAIgASACIAIwAbABoAKwAbACsALAATABIAIwATACMAJAALAIYBHABnAhsALAAUABMAJAAUACQAJQAMAAsAHAAMABwAHQAWABQAJQAWACUAJwANAAwAHQANAB0AHgAXABUAJgAXACYAKAAOAA0AHgAOAB4AHwAcAIUBLQBmAiwAPQAlACQANQAlADUANgAdABwALQAdAC0ALgAnACUANgAnADYAOAAeAB0ALgAeAC4ALwAoACYANwAoADcAOQAfAB4ALwAfAC8AMAApACgAOQApADkAOgAgAB8AMAAgADAAMQAqACkAOgAqADoAOwAhACAAMQAhADEAMgArACoAOwArADsAPAAjACIAMwAjADMANAAsACsAPAAsADwAPQAkACMANAAkADQANQAxADAAQQAxAEEAQgA7ADoASwA7AEsATAAyADEAQgAyAEIAQwA8ADsATAA8AEwATQA0ADMARAA0AEQARQA9ADwATQA9AE0ATgA1ADQARQA1AEUARgAtAIQBPgBlAj0ATgA2ADUARgA2AEYARwAuAC0APgAuAD4APwA4ADYARwA4AEcASQAvAC4APwAvAD8AQAA5ADcASAA5AEgASgAwAC8AQAAwAEAAQQA6ADkASgA6AEoASwBHAEYAVwBHAFcAWAA/AD4ATwA/AE8AUABJAEcAWABJAFgAWgBAAD8AUABAAFAAUQBKAEgAWQBKAFkAWwBBAEAAUQBBAFEAUgBLAEoAWwBLAFsAXABCAEEAUgBCAFIAUwBMAEsAXABMAFwAXQBDAEIAUwBDAFMAVABNAEwAXQBNAF0AXgBFAEQAVQBFAFUAVgBOAE0AXgBOAF4AXwBGAEUAVgBGAFYAVwA+AIMBTwBkAk4AXwBdAFwAbQBdAG0AbgBUAFMAZABUAGQAZQBeAF0AbgBeAG4AbwBWAFUAZgBWAGYAZwBfAF4AbwBfAG8AcABXAFYAZwBXAGcAaABPAIIBYABjAl8AcABYAFcAaABYAGgAaQBQAE8AYABQAGAAYQBaAFgAaQBaAGkAawBRAFAAYQBRAGEAYgBbAFkAagBbAGoAbABSAFEAYgBSAGIAYwBcAFsAbABcAGwAbQBTAFIAYwBTAGMAZABhAGAAcQBhAHEAcgBrAGkAegBrAHoAfABiAGEAcgBiAHIAcwBsAGoAewBsAHsAfQBjAGIAcwBjAHMAdABtAGwAfQBtAH0AfgBkAGMAdABkAHQAdQBuAG0AfgBuAH4AfwBlAGQAdQBlAHUAdgBvAG4AfwBvAH8AgABnAGYAdwBnAHcAeABwAG8AgABwAIAAgQBoAGcAeABoAHgAeQBgAIEBcQBiAnAAgQBpAGgAeQBpAHkAegB2AHUAhgB2AIYAhwCAAH8AkACAAJAAkQB4AHcAiAB4AIgAiQCBAIAAkQCBAJEAkgB5AHgAiQB5AIkAigBxAIABggBhAoEAkgB6AHkAigB6AIoAiwByAHEAggByAIIAgwB8AHoAiwB8AIsAjQBzAHIAgwBzAIMAhAB9AHsAjAB9AIwAjgB0AHMAhAB0AIQAhQB+AH0AjgB+AI4AjwB1AHQAhQB1AIUAhgB/AH4AjwB/AI8AkACNAIsAnACNAJwAngCEAIMAlACEAJQAlQCOAIwAnQCOAJ0AnwCFAIQAlQCFAJUAlgCPAI4AnwCPAJ8AoACGAIUAlgCGAJYAlwCQAI8AoACQAKAAoQCHAIYAlwCHAJcAmACRAJAAoQCRAKEAogCJAIgAmQCJAJkAmgCSAJEAogCSAKIAowCKAIkAmgCKAJoAmwCCAH8BkwBgApIAowCLAIoAmwCLAJsAnACDAIIAkwCDAJMAlACiAKEAsgCiALIAswCaAJkAqgCaAKoAqwCjAKIAswCjALMAtACbAJoAqwCbAKsArACTAH4BpABfAqMAtACcAJsArACcAKwArQCUAJMApACUAKQApQCeAJwArQCeAK0ArwCVAJQApQCVAKUApgCfAJ0ArgCfAK4AsACWAJUApgCWAKYApwCgAJ8AsACgALAAsQCXAJYApwCXAKcAqAChAKAAsQChALEAsgCYAJcAqACYAKgAqQCwAK4AvwCwAL8AwQCnAKYAtwCnALcAuACxALAAwQCxAMEAwgCoAKcAuACoALgAuQCyALEAwgCyAMIAwwCpAKgAuQCpALkAugCzALIAwwCzAMMAxACrAKoAuwCrALsAvAC0ALMAxAC0AMQAxQCsAKsAvACsALwAvQCkAH0BtQBeArQAxQCtAKwAvQCtAL0AvgClAKQAtQClALUAtgCvAK0AvgCvAL4AwACmAKUAtgCmALYAtwDFAMQA1QDFANUA1gC9ALwAzQC9AM0AzgC1AHwBxgBdAsUA1gC+AL0AzgC+AM4AzwC2ALUAxgC2AMYAxwDAAL4AzwDAAM8A0QC3ALYAxwC3AMcAyADBAL8A0ADBANAA0gC4ALcAyAC4AMgAyQDCAMEA0gDCANIA0wC5ALgAyQC5AMkAygDDAMIA0wDDANMA1AC6ALkAygC6AMoAywDEAMMA1ADEANQA1QC8ALsAzAC8AMwAzQDJAMgA2QDJANkA2gDTANIA4wDTAOMA5ADKAMkA2gDKANoA2wDUANMA5ADUAOQA5QDLAMoA2wDLANsA3ADVANQA5QDVAOUA5gDNAMwA3QDNAN0A3gDWANUA5gDWAOYA5wDOAM0A3gDOAN4A3wDGAHsB1wBcAtYA5wDPAM4A3wDPAN8A4ADHAMYA1wDHANcA2ADRAM8A4ADRAOAA4gDIAMcA2ADIANgA2QDSANAA4QDSAOEA4wDfAN4A7wDfAO8A8ADXAHoB6ABbAucA+ADgAN8A8ADgAPAA8QDYANcA6ADYAOgA6QDiAOAA8QDiAPEA8wDZANgA6QDZAOkA6gDjAOEA8gDjAPIA9ADaANkA6gDaAOoA6wDkAOMA9ADkAPQA9QDbANoA6wDbAOsA7ADlAOQA9QDlAPUA9gDcANsA7ADcAOwA7QDmAOUA9gDmAPYA9wDeAN0A7gDeAO4A7wDnAOYA9wDnAPcA+AD1APQABQH1AAUBBgHsAOsA/ADsAPwA/QD2APUABgH2AAYBBwHtAOwA/QDtAP0A/gD3APYABwH3AAcBCAHvAO4A/wDvAP8AAAH4APcACAH4AAgBCQHwAO8AAAHwAAABAQHoAHkB+QBaAvgACQHxAPAAAQHxAAEBAgHpAOgA+QDpAPkA+gDzAPEAAgHzAAIBBAHqAOkA+gDqAPoA+wD0APIAAwH0AAMBBQHrAOoA+wDrAPsA/AD5AHgBCgFZAgkBGgECAQEBEgECARIBEwH6APkACgH6AAoBCwEEAQIBEwEEARMBFQH7APoACwH7AAsBDAEFAQMBFAEFARQBFgH8APsADAH8AAwBDQEGAQUBFgEGARYBFwH9APwADQH9AA0BDgEHAQYBFwEHARcBGAH+AP0ADgH+AA4BDwEIAQcBGAEIARgBGQEAAf8AEAEAARABEQEJAQgBGQEJARkBGgEBAQABEQEBAREBEgEOAQ0BHgEOAR4BHwEYARcBKAEYASgBKQEPAQ4BHwEPAR8BIAEZARgBKQEZASkBKgERARABIQERASEBIgEaARkBKgEaASoBKwESAREBIgESASIBIwEKAXcBGwFYAhoBKwETARIBIwETASMBJAELAQoBGwELARsBHAEVARMBJAEVASQBJgEMAQsBHAEMARwBHQEWARQBJQEWASUBJwENAQwBHQENAR0BHgEXARYBJwEXAScBKAEkASMBNAEkATQBNQEcARsBLAEcASwBLQEmASQBNQEmATUBNwEdARwBLQEdAS0BLgEnASUBNgEnATYBOAEeAR0BLgEeAS4BLwEoAScBOAEoATgBOQEfAR4BLwEfAS8BMAEpASgBOQEpATkBOgEgAR8BMAEgATABMQEqASkBOgEqAToBOwEiASEBMgEiATIBMwErASoBOwErATsBPAEjASIBMwEjATMBNAEbAXYBLAFXAisBPAE6ATkBSgE6AUoBSwExATABQQExAUEBQgE7AToBSwE7AUsBTAEzATIBQwEzAUMBRAE8ATsBTAE8AUwBTQE0ATMBRAE0AUQBRQEsAXUBPQFWAjwBTQE1ATQBRQE1AUUBRgEtASwBPQEtAT0BPgE3ATUBRgE3AUYBSAEuAS0BPgEuAT4BPwE4ATYBRwE4AUcBSQEvAS4BPwEvAT8BQAE5ATgBSQE5AUkBSgEwAS8BQAEwAUABQQFIAUYBVwFIAVcBWQE/AT4BTwE/AU8BUAFJAUcBWAFJAVgBWgFAAT8BUAFAAVABUQFKAUkBWgFKAVoBWwFBAUABUQFBAVEBUgFLAUoBWwFLAVsBXAFCAUEBUgFCAVIBUwFMAUsBXAFMAVwBXQFEAUMBVAFEAVQBVQFNAUwBXQFNAV0BXgFFAUQBVQFFAVUBVgE9AXQBTgFVAk0BXgFGAUUBVgFGAVYBVwE+AT0BTgE+AU4BTwFdAVwBbQFdAW0BbgFVAVQBZQFVAWUBZgFeAV0BbgFeAW4BbwFWAVUBZgFWAWYBZwFOAXMBXwFUAl4BbwFXAVYBZwFXAWcBaAFPAU4BXwFPAV8BYAFZAVcBaAFZAWgBagFQAU8BYAFQAWABYQFaAVgBaQFaAWkBawFRAVABYQFRAWEBYgFbAVoBawFbAWsBbAFSAVEBYgFSAWIBYwFcAVsBbAFcAWwBbQFTAVIBYwFTAWMBZAFhAWABkQFhAZEBkgFrAWkBmgFrAZoBnAFiAWEBkgFiAZIBkwFsAWsBnAFsAZwBnQFjAWIBkwFjAZMBlAFtAWwBnQFtAZ0BngFkAWMBlAFkAZQBlQFuAW0BngFuAZ4BnwFmAWUBlgFmAZYBlwFvAW4BnwFvAZ8BoAFnAWYBlwFnAZcBmAFfAXIBkAFTAm8BoAFoAWcBmAFoAZgBmQFgAV8BkAFgAZABkQFqAWgBmQFqAZkBmwGXAZYBpwGXAacBqAGgAZ8BsAGgAbABsQGYAZcBqAGYAagBqQGQAXEBoQFSAqABsQGZAZgBqQGZAakBqgGRAZABoQGRAaEBogGbAZkBqgGbAaoBrAGSAZEBogGSAaIBowGcAZoBqwGcAasBrQGTAZIBowGTAaMBpAGdAZwBrQGdAa0BrgGUAZMBpAGUAaQBpQGeAZ0BrgGeAa4BrwGVAZQBpQGVAaUBpgGfAZ4BrwGfAa8BsAGtAasBxgGtAcYBygGkAaMBtgGkAbYBuAGuAa0BygGuAcoBzAGlAaQBuAGlAbgBugGvAa4BzAGvAcwBzgGmAaUBugGmAboBvAGwAa8BzgGwAc4B0AGoAacBvQGoAb0BwAGxAbAB0AGxAdAB0gGpAagBwAGpAcABwgGhAXABsgFRArEB0gGqAakBwgGqAcIBxAGiAaEBsgGiAbIBtAGsAaoBxAGsAcQBxwGjAaIBtAGjAbQBtgHTAdEB4wHTAeMB5AHDAcEB2wHDAdsB3AGzAY8B1AFwAtMB5AHFAcMB3AHFAdwB3QG1AbMB1AG1AdQB1QHJAcUB3QHJAd0B3wG3AbUB1QG3AdUB1gHLAcgB3gHLAd4B4AG5AbcB1gG5AdYB1wHNAcsB4AHNAeAB4QG7AbkB1wG7AdcB2AHPAc0B4QHPAeEB4gG+AbsB2AG+AdgB2QHRAc8B4gHRAeIB4wHBAb8B2gHBAdoB2wHXAdYB5wHXAecB6AHhAeAB8QHhAfEB8gHYAdcB6AHYAegB6QHiAeEB8gHiAfIB8wHZAdgB6QHZAekB6gHjAeIB8wHjAfMB9AHbAdoB6wHbAesB7AHkAeMB9AHkAfQB9QHcAdsB7AHcAewB7QHUAY4B5QFvAuQB9QHdAdwB7QHdAe0B7gHVAdQB5QHVAeUB5gHfAd0B7gHfAe4B8AHWAdUB5gHWAeYB5wHgAd4B7wHgAe8B8QHtAewB/QHtAf0B/gHlAY0B9gFuAvUBBgLuAe0B/gHuAf4B/wHmAeUB9gHmAfYB9wHwAe4B/wHwAf8BAQLnAeYB9wHnAfcB+AHxAe8BAALxAQACAgLoAecB+AHoAfgB+QHyAfEBAgLyAQICAwLpAegB+QHpAfkB+gHzAfIBAwLzAQMCBALqAekB+gHqAfoB+wH0AfMBBAL0AQQCBQLsAesB/AHsAfwB/QH1AfQBBQL1AQUCBgIDAgICEwIDAhMCFAL6AfkBCgL6AQoCCwIEAgMCFAIEAhQCFQL7AfoBCwL7AQsCDAIFAgQCFQIFAhUCFgL9AfwBDQL9AQ0CDgIGAgUCFgIGAhYCFwL+Af0BDgL+AQ4CDwL2AYwBBwJtAgYCFwL/Af4BDwL/AQ8CEAL3AfYBBwL3AQcCCAIBAv8BEAIBAhACEgL4AfcBCAL4AQgCCQICAgACEQICAhECEwL5AfgBCQL5AQkCCgJsAhcCKAIQAg8CIAIQAiACIQIIAgcCGAIIAhgCGQISAhACIQISAiECIwIJAggCGQIJAhkCGgITAhECIgITAiICJAIKAgkCGgIKAhoCGwIUAhMCJAIUAiQCJQILAgoCGwILAhsCHAIVAhQCJQIVAiUCJgIMAgsCHAIMAhwCHQIWAhUCJgIWAiYCJwIOAg0CHgIOAh4CHwIXAhYCJwIXAicCKAIPAg4CHwIPAh8CIAIHAosBGAImAiUCNgImAjYCNwIdAhwCLQIdAi0CLgInAiYCNwInAjcCOAIfAh4CLwIfAi8CMAIoAicCOAIoAjgCOQIgAh8CMAIgAjACMQIYAooBKQJrAigCOQIhAiACMQIhAjECMgIZAhgCKQIZAikCKgIjAiECMgIjAjICNAIaAhkCKgIaAioCKwIkAiICMwIkAjMCNQIbAhoCKwIbAisCLAIlAiQCNQIlAjUCNgIcAhsCLAIcAiwCLQIqAikCOgIqAjoCOwI0AjICQwI0AkMCRQIrAioCOwIrAjsCPAI1AjMCRAI1AkQCRgIsAisCPAIsAjwCPQI2AjUCRgI2AkYCRwItAiwCPQItAj0CPgI3AjYCRwI3AkcCSAIuAi0CPgIuAj4CPwI4AjcCSAI4AkgCSQIwAi8CQAIwAkACQQI5AjgCSQI5AkkCSgIxAjACQQIxAkECQgIpAokBOgJqAjkCSgIyAjECQgIyAkICQwI/Aj4CBAA/AgQABQBJAkgCCgBJAgoATwJBAkACBgBBAgYABwBKAkkCTwJKAk8CUAJCAkECBwBCAgcASwI6AogBAABpAkoCUAJDAkICSwJDAksCTAI7AjoCAAA7AgAAAQBFAkMCTAJFAkwCTgI8AjsCAQA8AgEAAgBGAkQCTQJGAk0CCAA9AjwCAgA9AgIAAwBHAkYCCABHAggACQA+Aj0CAwA+AgMABABIAkcCCQBIAgkACgA=" + } + ] +} diff --git a/resources/meshes/README.md b/resources/meshes/README.md index 0e36b34a..a64ab3cd 100644 --- a/resources/meshes/README.md +++ b/resources/meshes/README.md @@ -36,6 +36,24 @@ Licensed under the SCEA Shared Source License, Version 1.0. You may obtain a cop https://spdx.org/licenses/SCEA.html +## MosquitoInAmber + +https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/MosquitoInAmber + +Creative Commons Attribution 4.0 International License + +https://creativecommons.org/licenses/by/4.0/ + + +## MultipleVCols + +Own production + +Creative Commons Attribution 4.0 International License + +https://creativecommons.org/licenses/by/4.0/ + + ## RiggedFigure https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/RiggedFigure diff --git a/resources/scripts/struct-simple.lua b/resources/scripts/struct-simple.lua index 3fb00b36..741aeacb 100644 --- a/resources/scripts/struct-simple.lua +++ b/resources/scripts/struct-simple.lua @@ -7,6 +7,7 @@ function interface() vector3f = VEC3F, vector4f = VEC4F, integer = INT, + integer64 = INT64, vector2i = VEC2I, vector3i = VEC3I, vector4i = VEC4I, @@ -24,6 +25,7 @@ function run() OUT.s.vector4f = IN.s.vector4f OUT.s.integer = IN.s.integer + OUT.s.integer64 = IN.s.integer64 OUT.s.vector2i = IN.s.vector2i OUT.s.vector3i = IN.s.vector3i OUT.s.vector4i = IN.s.vector4i diff --git a/resources/scripts/types-scalar.lua b/resources/scripts/types-scalar.lua index 6d3bfd51..e1bc750e 100644 --- a/resources/scripts/types-scalar.lua +++ b/resources/scripts/types-scalar.lua @@ -5,6 +5,7 @@ function interface() IN.vector3f = VEC3F IN.vector4f = VEC4F IN.integer = INT + IN.integer64 = INT64 IN.vector2i = VEC2I IN.vector3i = VEC3I IN.vector4i = VEC4I @@ -13,6 +14,7 @@ function interface() OUT.ofloat = FLOAT OUT.ointeger = INT + OUT.ointeger64 = INT64 OUT.ovector3f = VEC3F OUT.ovector4f = VEC4F OUT.obool = BOOL @@ -31,6 +33,7 @@ function run() OUT.ofloat = test(IN.vector3f) OUT.ointeger = 2*IN.integer + OUT.ointeger64 = 2*IN.integer64 OUT.ovector3f = {IN.float, 2*IN.float, 3.0} OUT.ovector4f = {v[1], IN.float, IN.vector3f[1], IN.vector3f[2]} diff --git a/styles/_Default/icons/animation_sampler_white_18dp.png b/styles/_Default/icons/animation_sampler_white_18dp.png deleted file mode 100644 index f755aabd..00000000 --- a/styles/_Default/icons/animation_sampler_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8727f46d07af39c4cd79623277009004c2e21cc8773885166f260c5b49bc07b1 -size 128 diff --git a/styles/_Default/icons/animation_white_18dp.png b/styles/_Default/icons/animation_white_18dp.png deleted file mode 100644 index f2ca3380..00000000 --- a/styles/_Default/icons/animation_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:05a98f65292a279e846913a8e902aa5b11504b0dfe66742974a8906999b5ce47 -size 166 diff --git a/styles/_Default/icons/arrowDown.svg b/styles/_Default/icons/arrowDown.svg new file mode 100644 index 00000000..e2b0248a --- /dev/null +++ b/styles/_Default/icons/arrowDown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/arrowLeft.svg b/styles/_Default/icons/arrowLeft.svg new file mode 100644 index 00000000..b7f5a054 --- /dev/null +++ b/styles/_Default/icons/arrowLeft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/arrowRight.svg b/styles/_Default/icons/arrowRight.svg new file mode 100644 index 00000000..a08961a4 --- /dev/null +++ b/styles/_Default/icons/arrowRight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/baseline_center_focus_strong_white_18dp.png b/styles/_Default/icons/baseline_center_focus_strong_white_18dp.png deleted file mode 100644 index 2762fbe0..00000000 --- a/styles/_Default/icons/baseline_center_focus_strong_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52520a6e48d5aadfd8cb7abdd0b50aa2562541ce44248c3cc91735eef6776f0a -size 155 diff --git a/styles/_Default/icons/baseline_center_focus_weak_white_18dp.png b/styles/_Default/icons/baseline_center_focus_weak_white_18dp.png deleted file mode 100644 index 19e44a10..00000000 --- a/styles/_Default/icons/baseline_center_focus_weak_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47efba4d25c5a81cbacb7ae745697cf093acadeb96f5e4c5c18ff646ecadecca -size 164 diff --git a/styles/_Default/icons/baseline_crop_free_white_18dp.png b/styles/_Default/icons/baseline_crop_free_white_18dp.png deleted file mode 100644 index d0a744e3..00000000 --- a/styles/_Default/icons/baseline_crop_free_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:246c8e2b3df25a3dc030d26a7a723a950dbc0bdeae65b6155dd522b14e9e490f -size 130 diff --git a/styles/_Default/icons/baseline_miscellaneous_services_white_18dp.png b/styles/_Default/icons/baseline_miscellaneous_services_white_18dp.png deleted file mode 100644 index 9059f9b4..00000000 --- a/styles/_Default/icons/baseline_miscellaneous_services_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4768fd086099cf58f039e56e0c777c7b6817035f8ac2172b20b63c08d65bf965 -size 249 diff --git a/styles/_Default/icons/baseline_report_red_24dp.png b/styles/_Default/icons/baseline_report_red_24dp.png deleted file mode 100644 index 2ffb9fd9..00000000 --- a/styles/_Default/icons/baseline_report_red_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:03ebda557f2151c683b7b8ee137c5c32bdd348c577f4da810e4ce15825bcc6df -size 358 diff --git a/styles/_Default/icons/baseline_texture_white_18dp.png b/styles/_Default/icons/baseline_texture_white_18dp.png deleted file mode 100644 index 1dacc58c..00000000 --- a/styles/_Default/icons/baseline_texture_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bdda4c7c0c37717dc181c7e03bd475c7a45a23d8466e1a733688dee1ec5897e3 -size 193 diff --git a/styles/_Default/icons/baseline_view_module_white_18dp.png b/styles/_Default/icons/baseline_view_module_white_18dp.png deleted file mode 100644 index 5495f987..00000000 --- a/styles/_Default/icons/baseline_view_module_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:765064d0698fa2587e9610fa9206fb2fbceb61e5dc6f3cab56478665d06abc4c -size 106 diff --git a/styles/_Default/icons/baseline_warning_orange_24dp.png b/styles/_Default/icons/baseline_warning_orange_24dp.png deleted file mode 100644 index 1a572058..00000000 --- a/styles/_Default/icons/baseline_warning_orange_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a66129eb42e8f47dd0496f420a51545fd0fa3784190d027a4217cd4b400bd9b -size 653 diff --git a/styles/_Default/icons/camera_white_18dp.png b/styles/_Default/icons/camera_white_18dp.png deleted file mode 100644 index c6f55065..00000000 --- a/styles/_Default/icons/camera_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8b90fe9db74d7b01a9b4f6452ae5c3e44a779603ad4a68831eba7853178feb3 -size 259 diff --git a/styles/_Default/icons/close.svg b/styles/_Default/icons/close.svg new file mode 100644 index 00000000..5427cd27 --- /dev/null +++ b/styles/_Default/icons/close.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/styles/_Default/icons/cubemap_white_18dp.png b/styles/_Default/icons/cubemap_white_18dp.png deleted file mode 100644 index b09516e3..00000000 --- a/styles/_Default/icons/cubemap_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:36e26a7d8fbedfff013ce7f255b7b62d8c1b6ca32a3aa0d3f964a58f1c8a792a -size 435 diff --git a/styles/_Default/icons/done.svg b/styles/_Default/icons/done.svg new file mode 100644 index 00000000..5d5d7d2d --- /dev/null +++ b/styles/_Default/icons/done.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/error.svg b/styles/_Default/icons/error.svg new file mode 100644 index 00000000..4bb82cce --- /dev/null +++ b/styles/_Default/icons/error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/goto.svg b/styles/_Default/icons/goto.svg new file mode 100644 index 00000000..ef81fbe5 --- /dev/null +++ b/styles/_Default/icons/goto.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/linkBroken.svg b/styles/_Default/icons/linkBroken.svg new file mode 100644 index 00000000..5962dec3 --- /dev/null +++ b/styles/_Default/icons/linkBroken.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/styles/_Default/icons/linkable.svg b/styles/_Default/icons/linkable.svg new file mode 100644 index 00000000..3ebb7d54 --- /dev/null +++ b/styles/_Default/icons/linkable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/linked.svg b/styles/_Default/icons/linked.svg new file mode 100644 index 00000000..090418de --- /dev/null +++ b/styles/_Default/icons/linked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/locked.svg b/styles/_Default/icons/locked.svg new file mode 100644 index 00000000..95a257b2 --- /dev/null +++ b/styles/_Default/icons/locked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/material_white_18dp.png b/styles/_Default/icons/material_white_18dp.png deleted file mode 100644 index 537df394..00000000 --- a/styles/_Default/icons/material_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:92193305da5c585b1159402b9359a6c97f2aabd0e6cb1c1c4f63002d430a1f73 -size 235 diff --git a/styles/_Default/icons/menu.svg b/styles/_Default/icons/menu.svg new file mode 100644 index 00000000..b5ae5d58 --- /dev/null +++ b/styles/_Default/icons/menu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/styles/_Default/icons/mesh_white_18dp.png b/styles/_Default/icons/mesh_white_18dp.png deleted file mode 100644 index b9a4ff28..00000000 --- a/styles/_Default/icons/mesh_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e10a11cd8fbb57752243ebbf284bb458ad38e1d8532478ea2c77c8b0b74f1c16 -size 351 diff --git a/styles/_Default/icons/node_white_18dp.png b/styles/_Default/icons/node_white_18dp.png deleted file mode 100644 index d7a946ef..00000000 --- a/styles/_Default/icons/node_white_18dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52638f110eec4738f260b0ba1a6c812808f77b2bd52ceaf4ddccdc55dba5ab97 -size 267 diff --git a/styles/_Default/icons/openInNew.svg b/styles/_Default/icons/openInNew.svg new file mode 100644 index 00000000..8d9c4ee5 --- /dev/null +++ b/styles/_Default/icons/openInNew.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/outline_arrow_right_alt_white_24dp.png b/styles/_Default/icons/outline_arrow_right_alt_white_24dp.png deleted file mode 100644 index 6be732a5..00000000 --- a/styles/_Default/icons/outline_arrow_right_alt_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:557d5bebc6f0c1e0aef15f037b2b65602aca9f1dae0f10dd159359e59259d5c0 -size 110 diff --git a/styles/_Default/icons/outline_call_missed_white_24dp.png b/styles/_Default/icons/outline_call_missed_white_24dp.png deleted file mode 100644 index 62e935df..00000000 --- a/styles/_Default/icons/outline_call_missed_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:880a9737df11a4ce3d5ef99fa4f32b244d453ce9c5ee161a9ae95eae80389afd -size 159 diff --git a/styles/_Default/icons/outline_chevron_left_white_24dp.png b/styles/_Default/icons/outline_chevron_left_white_24dp.png deleted file mode 100644 index 3eefe56e..00000000 --- a/styles/_Default/icons/outline_chevron_left_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab088646555a0940dc0744910d79671cf62b2f0df3fb700fecab56653b5c0ece -size 207 diff --git a/styles/_Default/icons/outline_chevron_right_white_24dp.png b/styles/_Default/icons/outline_chevron_right_white_24dp.png deleted file mode 100644 index 0e44262c..00000000 --- a/styles/_Default/icons/outline_chevron_right_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41b8af24fc49ed45784339012e25bfa30ffb375234fe4e32ac9b6179057d01fa -size 120 diff --git a/styles/_Default/icons/outline_close_white_24dp.png b/styles/_Default/icons/outline_close_white_24dp.png deleted file mode 100644 index 956f218f..00000000 --- a/styles/_Default/icons/outline_close_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e9d18b35d4dc48fa521367c948fdc1f3be5b1583f576374f50afcf3173c956a -size 184 diff --git a/styles/_Default/icons/outline_delete_white_24dp.png b/styles/_Default/icons/outline_delete_white_24dp.png deleted file mode 100644 index bf4470c8..00000000 --- a/styles/_Default/icons/outline_delete_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0885d1c717c8517d99b7901fe37486322db5659d7c22f0e21b05cf7d8122957f -size 126 diff --git a/styles/_Default/icons/outline_done_white_24dp.png b/styles/_Default/icons/outline_done_white_24dp.png deleted file mode 100644 index 0f517585..00000000 --- a/styles/_Default/icons/outline_done_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4eb212f0dca4c13f33e9c4313b396cd2a624e915e0150b56ab563daa93285ac -size 145 diff --git a/styles/_Default/icons/outline_edit_white_24dp.png b/styles/_Default/icons/outline_edit_white_24dp.png deleted file mode 100644 index 549ad24c..00000000 --- a/styles/_Default/icons/outline_edit_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ba212ec38682c26bdabd3e2c9458218dd5ba7bc0553f89d61cf818a313b1501 -size 167 diff --git a/styles/_Default/icons/outline_expand_more_white_24dp.png b/styles/_Default/icons/outline_expand_more_white_24dp.png deleted file mode 100644 index 079c0319..00000000 --- a/styles/_Default/icons/outline_expand_more_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0d433c6c1bc31f788e6ab793c072afcdb68d8a3f31b8565a55d410f122ca32d -size 140 diff --git a/styles/_Default/icons/outline_link_broken_red_24dp.png b/styles/_Default/icons/outline_link_broken_red_24dp.png deleted file mode 100644 index 39f9cf87..00000000 --- a/styles/_Default/icons/outline_link_broken_red_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93abe85fe68fc12ddc500d9b6a332c7909891ae54fa95574696116c08025d1ba -size 819 diff --git a/styles/_Default/icons/outline_link_invisible_24dp.png b/styles/_Default/icons/outline_link_invisible_24dp.png deleted file mode 100644 index cd096402..00000000 --- a/styles/_Default/icons/outline_link_invisible_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f3efb7711c272cd4ff06b2342ef79529c0a011df703154c0ccef1136fbcfb45 -size 162 diff --git a/styles/_Default/icons/outline_link_white_24dp.png b/styles/_Default/icons/outline_link_white_24dp.png deleted file mode 100644 index 2260003f..00000000 --- a/styles/_Default/icons/outline_link_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4643c92de73f7f88392512402863f0d0845907b8e13da8ada82dfa8c44c387b -size 229 diff --git a/styles/_Default/icons/outline_link_yellow_24dp.png b/styles/_Default/icons/outline_link_yellow_24dp.png deleted file mode 100644 index 042917ba..00000000 --- a/styles/_Default/icons/outline_link_yellow_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9782dcbf9560512de4409dec56b5774bbe6ab8eafbf3e4ceedb374efcf05e1e2 -size 494 diff --git a/styles/_Default/icons/outline_lock_open_white_24dp.png b/styles/_Default/icons/outline_lock_open_white_24dp.png deleted file mode 100644 index 3dee3d4c..00000000 --- a/styles/_Default/icons/outline_lock_open_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b1537738d0d4e06c309bc4c6a6b270ea9b174b96de163d9b6eef2cc602acd811 -size 224 diff --git a/styles/_Default/icons/outline_lock_white_24dp.png b/styles/_Default/icons/outline_lock_white_24dp.png deleted file mode 100644 index 46d3e878..00000000 --- a/styles/_Default/icons/outline_lock_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8bc0932feb2c3117d61ed34c107a494d24153534c15184e6220aa54c943baa9a -size 223 diff --git a/styles/_Default/icons/outline_menu_white_24dp.png b/styles/_Default/icons/outline_menu_white_24dp.png deleted file mode 100644 index b0089c45..00000000 --- a/styles/_Default/icons/outline_menu_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d7356d4d8fd1d6616a749e5b5c35f5def77f248b2189439e35d6ef1355d7118 -size 90 diff --git a/styles/_Default/icons/outline_push_pin_white_24dp.png b/styles/_Default/icons/outline_push_pin_white_24dp.png deleted file mode 100644 index 0a2b4919..00000000 --- a/styles/_Default/icons/outline_push_pin_white_24dp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c433b38c6cd92b06454e4a221a82c4540db27265569832346be4c9c091e1358 -size 171 diff --git a/styles/_Default/icons/parentLinked.svg b/styles/_Default/icons/parentLinked.svg new file mode 100644 index 00000000..1945896e --- /dev/null +++ b/styles/_Default/icons/parentLinked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/remove.svg b/styles/_Default/icons/remove.svg new file mode 100644 index 00000000..270ede55 --- /dev/null +++ b/styles/_Default/icons/remove.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typeAnimation.svg b/styles/_Default/icons/typeAnimation.svg new file mode 100644 index 00000000..5845c16e --- /dev/null +++ b/styles/_Default/icons/typeAnimation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typeAnimationChannel.svg b/styles/_Default/icons/typeAnimationChannel.svg new file mode 100644 index 00000000..5efd9c9b --- /dev/null +++ b/styles/_Default/icons/typeAnimationChannel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typeCamera.svg b/styles/_Default/icons/typeCamera.svg new file mode 100644 index 00000000..7dd6f8be --- /dev/null +++ b/styles/_Default/icons/typeCamera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typeCubemap.svg b/styles/_Default/icons/typeCubemap.svg new file mode 100644 index 00000000..30ed90a9 --- /dev/null +++ b/styles/_Default/icons/typeCubemap.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/styles/_Default/icons/typeLuaScript.svg b/styles/_Default/icons/typeLuaScript.svg new file mode 100644 index 00000000..6418836b --- /dev/null +++ b/styles/_Default/icons/typeLuaScript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typeLuaScriptModule.svg b/styles/_Default/icons/typeLuaScriptModule.svg new file mode 100644 index 00000000..85d332a7 --- /dev/null +++ b/styles/_Default/icons/typeLuaScriptModule.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typeMaterial.svg b/styles/_Default/icons/typeMaterial.svg new file mode 100644 index 00000000..a6931ca8 --- /dev/null +++ b/styles/_Default/icons/typeMaterial.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/styles/_Default/icons/typeMesh.svg b/styles/_Default/icons/typeMesh.svg new file mode 100644 index 00000000..c9db1948 --- /dev/null +++ b/styles/_Default/icons/typeMesh.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/styles/_Default/icons/typeNode.svg b/styles/_Default/icons/typeNode.svg new file mode 100644 index 00000000..e96a1336 --- /dev/null +++ b/styles/_Default/icons/typeNode.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/_Default/icons/typePrefabExternal.svg b/styles/_Default/icons/typePrefabExternal.svg new file mode 100644 index 00000000..3172cc57 --- /dev/null +++ b/styles/_Default/icons/typePrefabExternal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typePrefabInstance.svg b/styles/_Default/icons/typePrefabInstance.svg new file mode 100644 index 00000000..c232a122 --- /dev/null +++ b/styles/_Default/icons/typePrefabInstance.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typePrefabInternal.svg b/styles/_Default/icons/typePrefabInternal.svg new file mode 100644 index 00000000..fc25d94e --- /dev/null +++ b/styles/_Default/icons/typePrefabInternal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/typeTexture.svg b/styles/_Default/icons/typeTexture.svg new file mode 100644 index 00000000..4447d5b8 --- /dev/null +++ b/styles/_Default/icons/typeTexture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/undock.svg b/styles/_Default/icons/undock.svg new file mode 100644 index 00000000..83e5196a --- /dev/null +++ b/styles/_Default/icons/undock.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/styles/_Default/icons/unlinkable.svg b/styles/_Default/icons/unlinkable.svg new file mode 100644 index 00000000..31e29ce7 --- /dev/null +++ b/styles/_Default/icons/unlinkable.svg @@ -0,0 +1,4 @@ + + + + diff --git a/styles/_Default/icons/unlocked.svg b/styles/_Default/icons/unlocked.svg new file mode 100644 index 00000000..fb91bb51 --- /dev/null +++ b/styles/_Default/icons/unlocked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/_Default/icons/warning.svg b/styles/_Default/icons/warning.svg new file mode 100644 index 00000000..c8d5469b --- /dev/null +++ b/styles/_Default/icons/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/icons.qrc b/styles/icons.qrc index e05ebdc7..07128030 100644 --- a/styles/icons.qrc +++ b/styles/icons.qrc @@ -1,42 +1,42 @@ - _Default/icons/outline_done_white_24dp.png - _Default/icons/outline_delete_white_24dp.png + _Default/icons/done.svg + _Default/icons/remove.svg ramses-composer-logo.png - _Default/icons/outline_chevron_right_white_24dp.png - _Default/icons/outline_expand_more_white_24dp.png - _Default/icons/outline_link_invisible_24dp.png - _Default/icons/outline_link_white_24dp.png - _Default/icons/outline_link_yellow_24dp.png - _Default/icons/outline_call_missed_white_24dp.png - _Default/icons/outline_link_broken_red_24dp.png - _Default/icons/outline_lock_white_24dp.png - _Default/icons/outline_lock_open_white_24dp.png - _Default/icons/outline_close_white_24dp.png - _Default/icons/outline_push_pin_white_24dp.png - _Default/icons/outline_menu_white_24dp.png - _Default/icons/outline_edit_white_24dp.png - _Default/icons/outline_arrow_right_alt_white_24dp.png - _Default/icons/outline_chevron_left_white_24dp.png - _Default/icons/outline_chevron_right_white_24dp.png - _Default/icons/baseline_warning_orange_24dp.png - _Default/icons/baseline_report_red_24dp.png + _Default/icons/arrowRight.svg + _Default/icons/arrowDown.svg + _Default/icons/unlinkable.svg + _Default/icons/linkable.svg + _Default/icons/linked.svg + _Default/icons/parentLinked.svg + _Default/icons/linkBroken.svg + _Default/icons/locked.svg + _Default/icons/unlocked.svg + _Default/icons/close.svg + _Default/icons/undock.svg + _Default/icons/menu.svg + _Default/icons/openInNew.svg + _Default/icons/goto.svg + _Default/icons/arrowLeft.svg + _Default/icons/arrowRight.svg + _Default/icons/warning.svg + _Default/icons/error.svg _Default/fonts/Roboto-Bold.ttf _Default/fonts/Roboto-Medium.ttf _Default/fonts/Roboto-Regular.ttf _Default/fonts/Roboto-Light.ttf - _Default/icons/animation_white_18dp.png - _Default/icons/animation_sampler_white_18dp.png - _Default/icons/node_white_18dp.png - _Default/icons/camera_white_18dp.png - _Default/icons/mesh_white_18dp.png - _Default/icons/material_white_18dp.png - _Default/icons/baseline_texture_white_18dp.png - _Default/icons/cubemap_white_18dp.png - _Default/icons/baseline_miscellaneous_services_white_18dp.png - _Default/icons/baseline_center_focus_strong_white_18dp.png - _Default/icons/baseline_center_focus_weak_white_18dp.png - _Default/icons/baseline_crop_free_white_18dp.png - _Default/icons/baseline_view_module_white_18dp.png + _Default/icons/typeAnimation.svg + _Default/icons/typeAnimationChannel.svg + _Default/icons/typeNode.svg + _Default/icons/typeCamera.svg + _Default/icons/typeMesh.svg + _Default/icons/typeMaterial.svg + _Default/icons/typeTexture.svg + _Default/icons/typeCubemap.svg + _Default/icons/typeLuaScript.svg + _Default/icons/typePrefabInternal.svg + _Default/icons/typePrefabExternal.svg + _Default/icons/typePrefabInstance.svg + _Default/icons/typeLuaScriptModule.svg \ No newline at end of file diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 1dd7a138..c86efdbe 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -31,6 +31,13 @@ set_target_properties(glm PROPERTIES FOLDER third_party/glm ) +## zip +add_subdirectory(zip/) +set_target_properties(zip uninstall test_append.out test_entry.out test_extract.out test_permissions.out test_read.out test_write.out PROPERTIES + FOLDER third_party/zip +) + + # Configure ramses-sdk build options set(ramses-sdk_BUILD_TOOLS OFF CACHE BOOL "" FORCE) set(ramses-sdk_BUILD_IVI_TEST_APPS OFF CACHE BOOL "" FORCE) diff --git a/third_party/zip b/third_party/zip new file mode 160000 index 00000000..a14f978c --- /dev/null +++ b/third_party/zip @@ -0,0 +1 @@ +Subproject commit a14f978c50087a25a5d6b333c8c5f1609c297b01 diff --git a/utils/libLogSystem/include/log_system/log.h b/utils/libLogSystem/include/log_system/log.h index 9869d8b6..02153721 100644 --- a/utils/libLogSystem/include/log_system/log.h +++ b/utils/libLogSystem/include/log_system/log.h @@ -36,11 +36,12 @@ constexpr const char* RAMSES_ADAPTOR {"RAMSES_ADAPTOR"}; constexpr const char* DESERIALIZATION{"DESERIALIZATION"}; constexpr const char* PROJECT{"PROJECT"}; constexpr const char* MESH_LOADER{"MESH_LOADER"}; +constexpr const char* RAMSES_LOGIC{"RAMSES_LOGIC"}; 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 init(spdlog::filename_t logFileName = SPDLOG_FILENAME_T("")); void deinit(); void registerSink(const SinkPtr sink); void unregisterSink(const SinkPtr sink); diff --git a/utils/libLogSystem/src/log.cpp b/utils/libLogSystem/src/log.cpp index befdcc1e..c7c770aa 100644 --- a/utils/libLogSystem/src/log.cpp +++ b/utils/libLogSystem/src/log.cpp @@ -17,6 +17,11 @@ #include #include +#if defined(_WIN32) +#include +#include +#endif + namespace raco::log_system { bool initialized{false}; @@ -44,7 +49,7 @@ void setConsoleLogLevel(spdlog::level::level_enum level) { consoleSink->set_level(level); } -void init(const spdlog::filename_t& logFileName) { +void init(spdlog::filename_t logFileName) { if (initialized) { LOG_WARNING(LOGGING, "log_system already initialized - call has no effect."); return; @@ -54,11 +59,27 @@ void init(const spdlog::filename_t& logFileName) { consoleSink = std::make_shared(); sinks = {consoleSink, multiplexSink}; + std::string logFileSinkError = ""; if (!logFileName.empty()) { +#if defined(_WIN32) + // Under Windows, the renaming of the log file that rotating_file_sink_mt perfoms will fail, when another RaCo instance is already opened. + // Thus if a log file already exists, we will check whether we can rename it and if not, append the process id to the file name to have a non colliding file name. + // Technically, there is a tiny race condition in here, when two new instances launch exactly at the same time and execttue this code block at the same time. + // This is very unlikely to happen. If it happens, it will cause the log file of one instance to be lost, but both of them will keep running. + // Under Linux, these precautions are not necessary, since renaming files that are being written to works transparently. + if (std::filesystem::exists(logFileName)) { + try { + std::filesystem::rename(logFileName, logFileName); + } catch(const std::filesystem::filesystem_error& e) { + logFileName = logFileName.substr(0, logFileName.size() - 4) + L"-" + std::to_wstring(GetCurrentProcessId()) + L".log"; + } + } +#endif + try { - sinks.push_back(std::make_shared(logFileName, MAX_LOG_FILE_SIZE_BYTES, MAX_LOG_FILE_AMOUNT, false)); + sinks.push_back(std::make_shared(logFileName, MAX_LOG_FILE_SIZE_BYTES, MAX_LOG_FILE_AMOUNT, true)); } catch (const spdlog::spdlog_ex& ex) { - LOG_ERROR(LOGGING, "Log file initialization failed: {}\n", ex.what()); + logFileSinkError = ex.what(); } } @@ -70,10 +91,7 @@ void init(const spdlog::filename_t& logFileName) { spdlog::register_logger(makeLogger(OBJECT_TREE_VIEW, spdlog::level::debug)); spdlog::register_logger(makeLogger(USER_TYPES, spdlog::level::debug)); spdlog::register_logger(makeLogger(CONTEXT, spdlog::level::debug)); - // Try catch block above may already have created this, so check first: - if (!spdlog::get(LOGGING)) { - spdlog::register_logger(makeLogger(LOGGING)); - } + spdlog::register_logger(makeLogger(LOGGING)); spdlog::register_logger(makeLogger(PREVIEW_WIDGET, spdlog::level::debug)); spdlog::register_logger(makeLogger(RAMSES_BACKEND)); spdlog::register_logger(makeLogger(RAMSES_ADAPTOR)); @@ -83,20 +101,22 @@ void init(const spdlog::filename_t& logFileName) { spdlog::register_logger(makeLogger(STYLE, spdlog::level::off)); spdlog::register_logger(makeLogger(DEFAULT)); spdlog::register_logger(makeLogger(MESH_LOADER)); + spdlog::register_logger(makeLogger(RAMSES_LOGIC)); 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; + spdlog::set_pattern("%^[%L] [%D %T:%f] [%n] [%s:%#] [%!] %v"); #if defined(_WIN32) + SetConsoleOutputCP(CP_UTF8); 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 + + if (!logFileSinkError.empty()) { + LOG_ERROR(LOGGING, "Log file initialization failed: {}", logFileSinkError); + } + + initialized = true; } void deinit() { diff --git a/utils/libUtils/CMakeLists.txt b/utils/libUtils/CMakeLists.txt index 0177db70..875dab97 100644 --- a/utils/libUtils/CMakeLists.txt +++ b/utils/libUtils/CMakeLists.txt @@ -12,8 +12,9 @@ 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/u8path.h src/u8path.cpp include/utils/stdfilesystem.h + include/utils/u8path.h src/u8path.cpp + include/utils/ZipUtils.h src/ZipUtils.cpp ) target_include_directories(libUtils PUBLIC include/) enable_warnings_as_errors(libUtils) @@ -25,6 +26,7 @@ target_compile_definitions(libUtils PUBLIC -DRACO_VERSION_PATCH=${PROJECT_VERSIO target_link_libraries(libUtils PRIVATE glm + zip PUBLIC raco::LogSystem diff --git a/utils/libUtils/include/utils/ZipUtils.h b/utils/libUtils/include/utils/ZipUtils.h new file mode 100644 index 00000000..07c341d9 --- /dev/null +++ b/utils/libUtils/include/utils/ZipUtils.h @@ -0,0 +1,27 @@ +/* + * 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::utils::zip { + +constexpr auto ZIP_COMPRESSION_LEVEL = 10; + +struct UnZipStatus { + bool success; + std::string payload; +}; + +std::string projectToZip(const char* fileContents, const char* projectFileName); +UnZipStatus zipToProject(const char* fileContents, int fileContentSize); +bool isZipFile(const std::string& fileContents); + +} // namespace raco::utils::zip diff --git a/utils/libUtils/src/ZipUtils.cpp b/utils/libUtils/src/ZipUtils.cpp new file mode 100644 index 00000000..3f39e57c --- /dev/null +++ b/utils/libUtils/src/ZipUtils.cpp @@ -0,0 +1,89 @@ +/* + * 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/ZipUtils.h" + +#include "zip.h" + + +namespace raco::utils::zip { + +std::string projectToZip(const char* fileContents, const char* projectFileName) { + char* buffer = nullptr; + size_t bufferSize = 0; + + struct zip_t* zip = zip_stream_open(nullptr, 0, ZIP_COMPRESSION_LEVEL, 'w'); + { + zip_entry_open(zip, projectFileName); + { + zip_entry_write(zip, fileContents, strlen(fileContents)); + } + zip_entry_close(zip); + + zip_stream_copy(zip, (void**)&buffer, &bufferSize); + } + + if (zip_entries_total(zip) != 1) { + delete buffer; + return ""; + } + + zip_stream_close(zip); + + std::string out(&buffer[0], &buffer[0] + bufferSize); + + delete buffer; + + return out; +} + +UnZipStatus zipToProject(const char* fileContents, int fileContentSize) { + char* buffer = nullptr; + size_t bufferSize = 0; + + struct zip_t* zip = zip_stream_open(fileContents, fileContentSize, 0, 'r'); + if (!zip) { + return {false, "File was not able to be opened as a zip archive."}; + } + + if (zip_entries_total(zip) != 1) { + return {false, "This archive does not contain one singular file - RaCo zip archives should contain only one singular project file"}; + } + + { + // Open the only file in our RaCo zip project: the actual project file + // Ignore file name parameter for edge case: renaming of archive + auto errorcode = zip_entry_openbyindex(zip, 0); + + if (errorcode != 0) { + zip_stream_close(zip); + return {false, zip_strerror(errorcode)}; + } else { + zip_entry_read(zip, (void**)&buffer, &bufferSize); + } + + zip_entry_close(zip); + } + zip_stream_close(zip); + + std::string out(&buffer[0], &buffer[0] + bufferSize); + + delete buffer; + + return {true, out}; +} + +bool isZipFile(const std::string& fileContents) { + return (fileContents.size() >= 4) && (fileContents[0] == 0x50 && fileContents[1] == 0x4b + && ((fileContents[2] == 0x03 && fileContents[3] == 0x04) + || (fileContents[2] == 0x05 && fileContents[3] == 0x06) + || (fileContents[2] == 0x07 && fileContents[3] == 0x08))); +} + +} // namespace raco::utils::zip