| 1 | // Copyright (C) 2019 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include <QtGui/QGuiApplication> |
| 5 | #include <QtCore/QCommandLineParser> |
| 6 | #include <QtCore/QStandardPaths> |
| 7 | #include <QtCore/QDir> |
| 8 | #include <QtCore/QVariant> |
| 9 | #include <QtCore/QHash> |
| 10 | |
| 11 | #include <QtCore/QJsonObject> |
| 12 | |
| 13 | #include <QtGui/QImageReader> |
| 14 | |
| 15 | #include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h> |
| 16 | #include <QtQuick3DIblBaker/private/qssgiblbaker_p.h> |
| 17 | |
| 18 | #include <QJsonDocument> |
| 19 | #include <iostream> |
| 20 | |
| 21 | class OptionsManager { |
| 22 | public: |
| 23 | OptionsManager() { |
| 24 | |
| 25 | } |
| 26 | ~OptionsManager() { |
| 27 | qDeleteAll(c: m_optionsMap); |
| 28 | m_optionsMap.clear(); |
| 29 | } |
| 30 | void generateCommandLineOptions(const QJsonObject &options) |
| 31 | { |
| 32 | if (options.isEmpty() || !options.contains(QStringLiteral("options" ))) |
| 33 | return; |
| 34 | |
| 35 | QJsonObject optionsObject = options.value(QStringLiteral("options" )).toObject(); |
| 36 | for (const QString &optionsKey : optionsObject.keys()) { |
| 37 | QJsonObject option = optionsObject.value(key: optionsKey).toObject(); |
| 38 | QString optionType = option.value(QStringLiteral("type" )).toString(); |
| 39 | QString description = option.value(QStringLiteral("description" )).toString(); |
| 40 | if (optionType == QStringLiteral("Boolean" )) { |
| 41 | // boolean flags |
| 42 | m_optionsMap.insert(key: optionsKey, value: new QCommandLineOption(optionsKey, description)); |
| 43 | const QString disableKey = QStringLiteral("disable-" ) + optionsKey; |
| 44 | m_optionsMap.insert(key: disableKey, value: new QCommandLineOption(QStringLiteral("disable-" ) + optionsKey)); |
| 45 | } else { |
| 46 | // value types |
| 47 | if (optionType == QStringLiteral("Real" )) { |
| 48 | QString defaultValue = QString::number(option.value(key: "value" ).toDouble()); |
| 49 | QCommandLineOption *valueOption = new QCommandLineOption(optionsKey, description, optionsKey, defaultValue); |
| 50 | m_optionsMap.insert(key: optionsKey, value: valueOption); |
| 51 | } |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | QJsonObject processCommandLineOptions(const QCommandLineParser &cmdLineParser, const QJsonObject &options, const QJsonObject &loadedOptions) const |
| 57 | { |
| 58 | QJsonObject result = loadedOptions; |
| 59 | if (options.isEmpty() || !options.contains(QStringLiteral("options" ))) |
| 60 | return result; |
| 61 | |
| 62 | QJsonObject optionsObject = options.value(QStringLiteral("options" )).toObject(); |
| 63 | QJsonObject loadedOptionsObject = loadedOptions.value(QStringLiteral("options" )).toObject(); |
| 64 | for (const QString &optionsKey : optionsObject.keys()) { |
| 65 | QJsonObject option = optionsObject.value(key: optionsKey).toObject(); |
| 66 | QString optionType = option.value(QStringLiteral("type" )).toString(); |
| 67 | if (optionType == QStringLiteral("Boolean" )) { |
| 68 | const QString disableKey = QStringLiteral("disable-" ) + optionsKey; |
| 69 | if (m_optionsMap[optionsKey] && cmdLineParser.isSet(option: *m_optionsMap[optionsKey])) |
| 70 | option["value" ] = true; |
| 71 | else if (m_optionsMap[disableKey] && cmdLineParser.isSet(option: *m_optionsMap[disableKey])) |
| 72 | option["value" ] = false; |
| 73 | else if (auto loadedValue = loadedOptionsObject.value(key: optionsKey); !loadedValue.isUndefined()) |
| 74 | option.insert(key: "value" , value: loadedValue); |
| 75 | } else if (optionType == QStringLiteral("Real" )) { |
| 76 | if (cmdLineParser.isSet(name: optionsKey)) |
| 77 | option["value" ] = cmdLineParser.value(name: optionsKey).toDouble(); |
| 78 | else if (auto loadedValue = loadedOptionsObject.value(key: optionsKey); !loadedValue.isUndefined()) |
| 79 | option.insert(key: "value" , value: loadedValue); |
| 80 | } |
| 81 | // update values |
| 82 | optionsObject[optionsKey] = option; |
| 83 | } |
| 84 | |
| 85 | removeFlagConflicts(cmdLineParser, options&: optionsObject); |
| 86 | result["options" ] = optionsObject; |
| 87 | return result; |
| 88 | } |
| 89 | |
| 90 | void registerOptions(QCommandLineParser &parser) { |
| 91 | for (const auto &cmdLineOption : std::as_const(t&: m_optionsMap)) |
| 92 | parser.addOption(commandLineOption: *cmdLineOption); |
| 93 | } |
| 94 | |
| 95 | void removeFlagConflicts(const QCommandLineParser &cmdLineParser, QJsonObject &options) const |
| 96 | { |
| 97 | // "generateNormals" and "generateSmoothNormals" are mutually exclusive. "generateNormals" |
| 98 | // takes precedence. |
| 99 | QJsonObject opt; |
| 100 | if (cmdLineParser.isSet(option: *m_optionsMap[QStringLiteral("generateNormals" )])) { |
| 101 | opt = options.value(QStringLiteral("generateSmoothNormals" )).toObject(); |
| 102 | if (opt[QStringLiteral("value" )] == true) { |
| 103 | opt[QStringLiteral("value" )] = false; |
| 104 | options[QStringLiteral("generateSmoothNormals" )] = opt; |
| 105 | std::cerr << "\"--generateSmoothNormals\" disabled due to \"--generateNormals\".\n" ; |
| 106 | } |
| 107 | |
| 108 | } else if (cmdLineParser.isSet(option: *m_optionsMap[QStringLiteral("generateSmoothNormals" )])) { |
| 109 | opt = options.value(QStringLiteral("generateNormals" )).toObject(); |
| 110 | if (opt[QStringLiteral("value" )] == true) { |
| 111 | opt[QStringLiteral("value" )] = false; |
| 112 | options[QStringLiteral("generateNormals" )] = opt; |
| 113 | std::cerr << "\"--generateNormals\" disabled due to \"--generateSmoothNormals\".\n" ; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // Ditto for "optimizeGraph" and "preTransformVertices". |
| 118 | if (cmdLineParser.isSet(option: *m_optionsMap[QStringLiteral("optimizeGraph" )])) { |
| 119 | opt = options.value(QStringLiteral("preTransformVertices" )).toObject(); |
| 120 | if (opt[QStringLiteral("value" )] == true) { |
| 121 | opt[QStringLiteral("value" )] = false; |
| 122 | options[QStringLiteral("preTransformVertices" )] = opt; |
| 123 | std::cerr << "\"--preTransformVertices\" disabled due to \"--optimizeGraph\".\n" ; |
| 124 | } |
| 125 | } else if (cmdLineParser.isSet(option: *m_optionsMap[QStringLiteral("preTransformVertices" )])) { |
| 126 | opt = options.value(QStringLiteral("optimizeGraph" )).toObject(); |
| 127 | if (opt[QStringLiteral("value" )] == true) { |
| 128 | opt[QStringLiteral("value" )] = false; |
| 129 | options[QStringLiteral("optimizeGraph" )] = opt; |
| 130 | std::cerr << "\"--optimizeGraph\" disabled due to \"--preTransformVertices\".\n" ; |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | private: |
| 136 | QHash<QString, QCommandLineOption *> m_optionsMap; |
| 137 | }; |
| 138 | |
| 139 | struct BuiltinConditioners |
| 140 | { |
| 141 | QSSGAssetImportManager::ImportState run(const QString &filename, |
| 142 | const QDir &outputPath, |
| 143 | QString *error); |
| 144 | |
| 145 | QSSGIblBaker iblBaker; |
| 146 | }; |
| 147 | |
| 148 | QSSGAssetImportManager::ImportState BuiltinConditioners::run(const QString &filename, |
| 149 | const QDir &outputPath, |
| 150 | QString *error) |
| 151 | { |
| 152 | QFileInfo fileInfo(filename); |
| 153 | if (!fileInfo.exists()) { |
| 154 | if (error) |
| 155 | *error = QStringLiteral("File does not exist" ); |
| 156 | return QSSGAssetImportManager::ImportState::IoError; |
| 157 | } |
| 158 | |
| 159 | const QString extension = fileInfo.suffix().toLower(); |
| 160 | QStringList generatedFiles; |
| 161 | QSSGAssetImportManager::ImportState result = QSSGAssetImportManager::ImportState::Unsupported; |
| 162 | |
| 163 | if (iblBaker.inputExtensions().contains(str: extension)) { |
| 164 | QString errorMsg = iblBaker.import(sourceFile: fileInfo.absoluteFilePath(), savePath: outputPath, generatedFiles: &generatedFiles); |
| 165 | if (errorMsg.isEmpty()) { |
| 166 | result = QSSGAssetImportManager::ImportState::Success; |
| 167 | } else { |
| 168 | *error = errorMsg; |
| 169 | result = QSSGAssetImportManager::ImportState::IoError; |
| 170 | } |
| 171 | } else { |
| 172 | if (error) |
| 173 | *error = QStringLiteral("unsupported file extension %1" ).arg(a: extension); |
| 174 | } |
| 175 | |
| 176 | for (const auto &file : generatedFiles) |
| 177 | qDebug() << "generated file:" << file; |
| 178 | |
| 179 | return result; |
| 180 | } |
| 181 | |
| 182 | int main(int argc, char *argv[]) |
| 183 | { |
| 184 | QGuiApplication app(argc, argv); |
| 185 | |
| 186 | QImageReader::setAllocationLimit(0); |
| 187 | |
| 188 | const bool canUsePlugins = !QCoreApplication::arguments().contains(QStringLiteral("--no-plugins" )); |
| 189 | if (!canUsePlugins) |
| 190 | qDebug(msg: "balsam: Not loading assetimporter plugins" ); |
| 191 | |
| 192 | QScopedPointer<QSSGAssetImportManager> assetImporter; |
| 193 | OptionsManager optionsManager; |
| 194 | BuiltinConditioners builtins; |
| 195 | |
| 196 | // Setup command line arguments |
| 197 | QCommandLineParser cmdLineParser; |
| 198 | cmdLineParser.setApplicationDescription( |
| 199 | QStringLiteral("Converts graphical assets to a runtime format for use with Qt Quick 3D" )); |
| 200 | cmdLineParser.addHelpOption(); |
| 201 | cmdLineParser.addPositionalArgument(QStringLiteral("sourceFilename" ), QStringLiteral("Asset file to be imported" )); |
| 202 | QCommandLineOption outputPathOption({ "outputPath" , "o" }, QStringLiteral("Sets the location to place the generated file(s). Default is the current directory" ), QStringLiteral("outputPath" ), QDir::currentPath()); |
| 203 | cmdLineParser.addOption(commandLineOption: outputPathOption); |
| 204 | QCommandLineOption noPluginsOption(QStringLiteral("no-plugins" ), QStringLiteral("Disable assetimporter plugin loading, only considers built-ins" )); |
| 205 | cmdLineParser.addOption(commandLineOption: noPluginsOption); |
| 206 | |
| 207 | QCommandLineOption loadOptionsFromFileOption({"f" ,"options-file" }, QStringLiteral("Load options from <file>" ), QStringLiteral("file" )); |
| 208 | cmdLineParser.addOption(commandLineOption: loadOptionsFromFileOption); |
| 209 | |
| 210 | // Get Plugin options |
| 211 | if (canUsePlugins) { |
| 212 | assetImporter.reset(other: new QSSGAssetImportManager); |
| 213 | auto pluginOptions = assetImporter->getAllOptions(); |
| 214 | for (const auto &options : std::as_const(t&: pluginOptions)) |
| 215 | optionsManager.generateCommandLineOptions(options); |
| 216 | optionsManager.registerOptions(parser&: cmdLineParser); |
| 217 | } |
| 218 | |
| 219 | cmdLineParser.process(app); |
| 220 | |
| 221 | QStringList assetFileNames = cmdLineParser.positionalArguments(); |
| 222 | QDir outputDirectory = QDir::currentPath(); |
| 223 | if (cmdLineParser.isSet(option: outputPathOption)) { |
| 224 | outputDirectory = QDir(cmdLineParser.value(option: outputPathOption)); |
| 225 | if (!outputDirectory.exists()) { |
| 226 | if (!outputDirectory.mkpath(QStringLiteral("." ))) { |
| 227 | std::cerr << "Failed to create export directory: " << qPrintable(outputDirectory.path()) << "\n" ; |
| 228 | return 2; |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | // if there is nothing to do show help |
| 234 | if (assetFileNames.isEmpty()) |
| 235 | cmdLineParser.showHelp(exitCode: 1); |
| 236 | |
| 237 | // Convert each assetFile is possible |
| 238 | for (const auto &assetFileName : assetFileNames) { |
| 239 | QString errorString; |
| 240 | QSSGAssetImportManager::ImportState result = QSSGAssetImportManager::ImportState::Unsupported; |
| 241 | if (canUsePlugins) { |
| 242 | QJsonObject options = assetImporter->getOptionsForFile(filename: assetFileName); |
| 243 | QJsonObject loadedOptions; |
| 244 | |
| 245 | if (cmdLineParser.isSet(option: loadOptionsFromFileOption)) { |
| 246 | QFile optionsFile(cmdLineParser.value(option: loadOptionsFromFileOption)); |
| 247 | if (!optionsFile.open(flags: QIODevice::ReadOnly)) { |
| 248 | qCritical() << "Could not open options file" << optionsFile.fileName() << "for reading." ; |
| 249 | return -1; |
| 250 | } |
| 251 | QByteArray optionData = optionsFile.readAll(); |
| 252 | QJsonParseError error; |
| 253 | auto optionsDoc = QJsonDocument::fromJson(json: optionData, error: &error); |
| 254 | if (optionsDoc.isEmpty()) { |
| 255 | qCritical() << "Could not read options file:" << error.errorString(); |
| 256 | return -1; |
| 257 | } |
| 258 | loadedOptions = optionsDoc.object(); |
| 259 | } |
| 260 | |
| 261 | options = optionsManager.processCommandLineOptions(cmdLineParser, options, loadedOptions); |
| 262 | |
| 263 | // first try the plugin-based asset importer system |
| 264 | result = assetImporter->importFile(filename: assetFileName, outputPath: outputDirectory, options, error: &errorString); |
| 265 | } |
| 266 | // if the file extension is unsupported, try the builtins |
| 267 | if (result == QSSGAssetImportManager::ImportState::Unsupported) |
| 268 | result = builtins.run(filename: assetFileName, outputPath: outputDirectory, error: &errorString); |
| 269 | if (result != QSSGAssetImportManager::ImportState::Success) { |
| 270 | std::cerr << "Failed to import file with error: " << qPrintable(errorString) << "\n" ; |
| 271 | return 2; |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | return 0; |
| 276 | } |
| 277 | |