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 | |
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 | |