Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include <QtGui/QGuiApplication>
++ #include <QtCore/QCommandLineParser>
++ #include <QtCore/QStandardPaths>
++ #include <QtCore/QDir>
++ #include <QtCore/QVariant>
++ #include <QtCore/QHash>
++
++ #include <QtCore/QJsonObject>
++
++ #include <QtGui/QImageReader>
++
++ #include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
++ #include <QtQuick3DIblBaker/private/qssgiblbaker_p.h>
++
++ #include <QJsonDocument>
++ #include <iostream>
++
++ class OptionsManager {
++ public:
++ OptionsManager() {
++
++ }
++ ~OptionsManager() {
++ qDeleteAll(m_optionsMap);
++ m_optionsMap.clear();
++ }
++ void generateCommandLineOptions(const QJsonObject &options)
++ {
++ if (options.isEmpty() || !options.contains(QStringLiteral("options")))
++ return;
++
++ QJsonObject optionsObject = options.value(QStringLiteral("options")).toObject();
++ for (const QString &optionsKey : optionsObject.keys()) {
++ QJsonObject option = optionsObject.value(optionsKey).toObject();
++ QString optionType = option.value(QStringLiteral("type")).toString();
++ QString description = option.value(QStringLiteral("description")).toString();
++ if (optionType == QStringLiteral("Boolean")) {
++ // boolean flags
++ m_optionsMap.insert(optionsKey, new QCommandLineOption(optionsKey, description));
++ const QString disableKey = QStringLiteral("disable-") + optionsKey;
++ m_optionsMap.insert(disableKey, new QCommandLineOption(QStringLiteral("disable-") + optionsKey));
++ } else {
++ // value types
++ if (optionType == QStringLiteral("Real")) {
++ QString defaultValue = QString::number(option.value("value").toDouble());
++ QCommandLineOption *valueOption = new QCommandLineOption(optionsKey, description, optionsKey, defaultValue);
++ m_optionsMap.insert(optionsKey, valueOption);
++ } else if (optionType == QStringLiteral("String")) {
++ QString defaultValue = option.value("value").toString();
++ QCommandLineOption *valueOption = new QCommandLineOption(optionsKey, description, optionsKey, defaultValue);
++ m_optionsMap.insert(optionsKey, valueOption);
++ }
++ }
++ }
++ }
++
++ QJsonObject processCommandLineOptions(const QCommandLineParser &cmdLineParser, const QJsonObject &options, const QJsonObject &loadedOptions) const
++ {
++ QJsonObject result = loadedOptions;
++ if (options.isEmpty() || !options.contains(QStringLiteral("options")))
++ return result;
++
++ QJsonObject optionsObject = options.value(QStringLiteral("options")).toObject();
++ QJsonObject loadedOptionsObject = loadedOptions.value(QStringLiteral("options")).toObject();
++ for (const QString &optionsKey : optionsObject.keys()) {
++ QJsonObject option = optionsObject.value(optionsKey).toObject();
++ QString optionType = option.value(QStringLiteral("type")).toString();
++ if (optionType == QStringLiteral("Boolean")) {
++ const QString disableKey = QStringLiteral("disable-") + optionsKey;
++ if (m_optionsMap[optionsKey] && cmdLineParser.isSet(*m_optionsMap[optionsKey]))
++ option["value"] = true;
++ else if (m_optionsMap[disableKey] && cmdLineParser.isSet(*m_optionsMap[disableKey]))
++ option["value"] = false;
++ else if (auto loadedValue = loadedOptionsObject.value(optionsKey); !loadedValue.isUndefined())
++ option.insert("value", loadedValue);
++ } else if (optionType == QStringLiteral("Real")) {
++ if (cmdLineParser.isSet(optionsKey))
++ option["value"] = cmdLineParser.value(optionsKey).toDouble();
++ else if (auto loadedValue = loadedOptionsObject.value(optionsKey); !loadedValue.isUndefined())
++ option.insert("value", loadedValue);
++ } else if (optionType == QStringLiteral("String")) {
++ if (cmdLineParser.isSet(optionsKey))
++ option["value"] = cmdLineParser.value(optionsKey);
++ else if (auto loadedValue = loadedOptionsObject.value(optionsKey); !loadedValue.isUndefined())
++ option.insert("value", loadedValue);
++ }
++ // update values
++ optionsObject[optionsKey] = option;
++ }
++
++ removeFlagConflicts(cmdLineParser, optionsObject);
++ result["options"] = optionsObject;
++ return result;
++ }
++
++ void registerOptions(QCommandLineParser &parser) {
++ for (const auto &cmdLineOption : std::as_const(m_optionsMap))
++ parser.addOption(*cmdLineOption);
++ }
++
++ void removeFlagConflicts(const QCommandLineParser &cmdLineParser, QJsonObject &options) const
++ {
++ // m_optionsMap only has the options of the loaded plugins
++ const auto isSet = [&](const QString &key) {
++ const QCommandLineOption *option = m_optionsMap.value(key);
++ return option && cmdLineParser.isSet(*option);
++ };
++
++ // "generateNormals" and "generateSmoothNormals" are mutually exclusive. "generateNormals"
++ // takes precedence.
++ QJsonObject opt;
++ if (isSet(QStringLiteral("generateNormals"))) {
++ opt = options.value(QStringLiteral("generateSmoothNormals")).toObject();
++ if (opt[QStringLiteral("value")] == true) {
++ opt[QStringLiteral("value")] = false;
++ options[QStringLiteral("generateSmoothNormals")] = opt;
++ std::cerr << "\"--generateSmoothNormals\" disabled due to \"--generateNormals\".\n";
++ }
++
++ } else if (isSet(QStringLiteral("generateSmoothNormals"))) {
++ opt = options.value(QStringLiteral("generateNormals")).toObject();
++ if (opt[QStringLiteral("value")] == true) {
++ opt[QStringLiteral("value")] = false;
++ options[QStringLiteral("generateNormals")] = opt;
++ std::cerr << "\"--generateNormals\" disabled due to \"--generateSmoothNormals\".\n";
++ }
++ }
++
++ // Ditto for "optimizeGraph" and "preTransformVertices".
++ if (isSet(QStringLiteral("optimizeGraph"))) {
++ opt = options.value(QStringLiteral("preTransformVertices")).toObject();
++ if (opt[QStringLiteral("value")] == true) {
++ opt[QStringLiteral("value")] = false;
++ options[QStringLiteral("preTransformVertices")] = opt;
++ std::cerr << "\"--preTransformVertices\" disabled due to \"--optimizeGraph\".\n";
++ }
++ } else if (isSet(QStringLiteral("preTransformVertices"))) {
++ opt = options.value(QStringLiteral("optimizeGraph")).toObject();
++ if (opt[QStringLiteral("value")] == true) {
++ opt[QStringLiteral("value")] = false;
++ options[QStringLiteral("optimizeGraph")] = opt;
++ std::cerr << "\"--optimizeGraph\" disabled due to \"--preTransformVertices\".\n";
++ }
++ }
++ }
++
++ private:
++ QHash<QString, QCommandLineOption *> m_optionsMap;
++ };
++
++ struct BuiltinConditioners
++ {
++ QSSGAssetImportManager::ImportState run(const QString &filename,
++ const QDir &outputPath,
++ QString *error);
++
++ QSSGIblBaker iblBaker;
++ };
++
++ QSSGAssetImportManager::ImportState BuiltinConditioners::run(const QString &filename,
++ const QDir &outputPath,
++ QString *error)
++ {
++ QFileInfo fileInfo(filename);
++ if (!fileInfo.exists()) {
++ if (error)
++ *error = QStringLiteral("File does not exist");
++ return QSSGAssetImportManager::ImportState::IoError;
++ }
++
++ const QString extension = fileInfo.suffix().toLower();
++ QStringList generatedFiles;
++ QSSGAssetImportManager::ImportState result = QSSGAssetImportManager::ImportState::Unsupported;
++
++ if (iblBaker.inputExtensions().contains(extension)) {
++ QString errorMsg = iblBaker.import(fileInfo.absoluteFilePath(), outputPath, &generatedFiles);
++ if (errorMsg.isEmpty()) {
++ result = QSSGAssetImportManager::ImportState::Success;
++ } else {
++ *error = errorMsg;
++ result = QSSGAssetImportManager::ImportState::IoError;
++ }
++ } else {
++ if (error)
++ *error = QStringLiteral("unsupported file extension %1").arg(extension);
++ }
++
++ for (const auto &file : generatedFiles)
++ qDebug() << "generated file:" << file;
++
++ return result;
++ }
++
++ int main(int argc, char *argv[])
++ {
++ QGuiApplication app(argc, argv);
++
++ QImageReader::setAllocationLimit(0);
++
++ const bool canUsePlugins = !QCoreApplication::arguments().contains(QStringLiteral("--no-plugins"));
++ if (!canUsePlugins)
++ qDebug("balsam: Not loading assetimporter plugins");
++
++ QScopedPointer<QSSGAssetImportManager> assetImporter;
++ OptionsManager optionsManager;
++ BuiltinConditioners builtins;
++
++ // Setup command line arguments
++ QCommandLineParser cmdLineParser;
++ cmdLineParser.setApplicationDescription(
++ QStringLiteral("Converts graphical assets to a runtime format for use with Qt Quick 3D"));
++ cmdLineParser.addHelpOption();
++ cmdLineParser.addPositionalArgument(QStringLiteral("sourceFilename"), QStringLiteral("Asset file to be imported"));
++ QCommandLineOption outputPathOption({ "outputPath", "o" }, QStringLiteral("Sets the location to place the generated file(s). Default is the current directory"), QStringLiteral("outputPath"), QDir::currentPath());
++ cmdLineParser.addOption(outputPathOption);
++ QCommandLineOption noPluginsOption(QStringLiteral("no-plugins"), QStringLiteral("Disable assetimporter plugin loading, only considers built-ins"));
++ cmdLineParser.addOption(noPluginsOption);
++
++ QCommandLineOption loadOptionsFromFileOption({"f","options-file"}, QStringLiteral("Load options from <file>"), QStringLiteral("file"));
++ cmdLineParser.addOption(loadOptionsFromFileOption);
++
++ // Get Plugin options
++ if (canUsePlugins) {
++ assetImporter.reset(new QSSGAssetImportManager);
++ auto pluginOptions = assetImporter->getAllOptions();
++ for (const auto &options : std::as_const(pluginOptions))
++ optionsManager.generateCommandLineOptions(options);
++ optionsManager.registerOptions(cmdLineParser);
++ }
++
++ cmdLineParser.process(app);
++
++ QStringList assetFileNames = cmdLineParser.positionalArguments();
++ QDir outputDirectory = QDir::currentPath();
++ if (cmdLineParser.isSet(outputPathOption)) {
++ outputDirectory = QDir(cmdLineParser.value(outputPathOption));
++ if (!outputDirectory.exists()) {
++ if (!outputDirectory.mkpath(QStringLiteral("."))) {
++ std::cerr << "Failed to create export directory: " << qPrintable(outputDirectory.path()) << "\n";
++ return 2;
++ }
++ }
++ }
++
++ // if there is nothing to do show help
++ if (assetFileNames.isEmpty())
++ cmdLineParser.showHelp(1);
++
++ // Convert each assetFile is possible
++ for (const auto &assetFileName : assetFileNames) {
++ QString errorString;
++ QSSGAssetImportManager::ImportState result = QSSGAssetImportManager::ImportState::Unsupported;
++ if (canUsePlugins) {
++ QJsonObject options = assetImporter->getOptionsForFile(assetFileName);
++ QJsonObject loadedOptions;
++
++ if (cmdLineParser.isSet(loadOptionsFromFileOption)) {
++ QFile optionsFile(cmdLineParser.value(loadOptionsFromFileOption));
++ if (!optionsFile.open(QIODevice::ReadOnly)) {
++ qCritical() << "Could not open options file" << optionsFile.fileName() << "for reading.";
++ return -1;
++ }
++ QByteArray optionData = optionsFile.readAll();
++ QJsonParseError error;
++ auto optionsDoc = QJsonDocument::fromJson(optionData, &error);
++ if (optionsDoc.isEmpty()) {
++ qCritical() << "Could not read options file:" << error.errorString();
++ return -1;
++ }
++ loadedOptions = optionsDoc.object();
++ }
++
++ options = optionsManager.processCommandLineOptions(cmdLineParser, options, loadedOptions);
++
++ // first try the plugin-based asset importer system
++ result = assetImporter->importFile(assetFileName, outputDirectory, options, &errorString);
++ }
++ // if the file extension is unsupported, try the builtins
++ if (result == QSSGAssetImportManager::ImportState::Unsupported)
++ result = builtins.run(assetFileName, outputDirectory, &errorString);
++ if (result != QSSGAssetImportManager::ImportState::Success) {
++ std::cerr << "Failed to import file with error: " << qPrintable(errorString) << "\n";
++ return 2;
++ }
++ }
++
++ return 0;
++ }