1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
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
21class OptionsManager {
22public:
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
135private:
136 QHash<QString, QCommandLineOption *> m_optionsMap;
137};
138
139struct BuiltinConditioners
140{
141 QSSGAssetImportManager::ImportState run(const QString &filename,
142 const QDir &outputPath,
143 QString *error);
144
145 QSSGIblBaker iblBaker;
146};
147
148QSSGAssetImportManager::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
182int 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

source code of qtquick3d/tools/balsam/main.cpp