ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
IoAbstractLoader.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include <QDebug>
9 #include <QDir>
10 #include <QFileInfo>
11 #include <QProgressDialog>
12 
13 #ifdef QT_DEBUG
14 #include <iostream>
15 #endif
16 
17 #include "IoAbstractLoader.h"
18 #include "IoUtils.h"
19 #include "assimp/DefaultLogger.hpp"
20 #include "assimp/Importer.hpp"
21 #include "assimp/ProgressHandler.hpp"
22 #include "assimp/postprocess.h"
23 #include "assimp/scene.h"
24 #include "ecvMaterialSet.h"
25 #include "ecvMesh.h"
26 
27 namespace {
28 class _LogStream : public Assimp::LogStream {
29  void write(const char *inMessage) override {
30  QString message = QString(inMessage).trimmed();
31  int messageLevel = CVLog::LOG_STANDARD;
32 
33  if (message.startsWith("Warn")) {
34  messageLevel = CVLog::LOG_WARNING;
35  } else if (message.startsWith("Error")) {
36  messageLevel = CVLog::LOG_ERROR;
37  } else if (message.startsWith("Info")) {
38  messageLevel = CVLog::LOG_VERBOSE;
39  }
40 
41  message.prepend("[qMeshIO] ai - ");
42 
43  CVLog::LogMessage(message, messageLevel);
44  }
45 };
46 
47 class _ProgressHandler : public QProgressDialog,
48  public Assimp::ProgressHandler {
49 public:
50  _ProgressHandler(const QString &inText) : cText(inText) {
51  setWindowModality(Qt::WindowModal);
52  setWindowTitle(tr("Import Mesh"));
53 
54  setMinimumDuration(0);
55 
56  setMinimumSize(400, 100);
57  }
58 
59  bool Update(float inPercent) override {
60  int value = qRound(inPercent * 100.0f);
61 
62  setLabelText(QStringLiteral("Loading %1: %2%")
63  .arg(cText, QString::number(value)));
64 
65  setValue(value);
66 
67  return wasCanceled();
68  }
69 
70 private:
71  const QString cText;
72 };
73 
74 class _Loader {
75 public:
76  _Loader(const aiScene *inScene,
77  const QString &inFileName,
78  const QString &inPath)
79  : cScene(inScene), cFileName(inFileName), cPath(inPath) {
80  _initCameraNames();
81  }
82 
83  void load(ccHObject &ioContainer) {
84  if (cScene->HasMeshes()) {
85  CVLog::Print(QStringLiteral("[qMeshIO] The file '%1' has %2 meshes")
86  .arg(cFileName, QLocale::system().toString(
87  cScene->mNumMeshes)));
88  }
89 
90  if (cScene->HasCameras()) {
92  QStringLiteral("[qMeshIO] The file '%1' has %2 cameras")
93  .arg(cFileName, QLocale::system().toString(
94  cScene->mNumCameras)));
95  }
96 
97  _recursiveAddNode(cScene->mRootNode, cScene, &ioContainer);
98 
99  ioContainer.applyGLTransformation_recursive();
100  ioContainer.resetGLTransformationHistory();
101 
102  _pruneTree(&ioContainer);
103  }
104 
105 private:
106  void _initCameraNames() {
107  for (unsigned int i = 0; i < cScene->mNumCameras; ++i) {
108  const auto cCamera = cScene->mCameras[i];
109  const QString cCameraName(cCamera->mName.C_Str());
110 
111  mCameraMap[cCameraName] = cCamera;
112  }
113  }
114 
115  void _recursiveAddNode(const aiNode *inNode,
116  const aiScene *inScene,
117  ccHObject *ioParentObject) {
118 #ifdef QT_DEBUG
119  std::cout << "Process node: " << inNode->mName.C_Str() << std::endl;
120  std::cout << " num children: " << inNode->mNumChildren << std::endl;
121  std::cout << " num meshes: " << inNode->mNumMeshes << std::endl;
122 #endif
123 
124  auto currentObject = new ccHObject(inNode->mName.C_Str());
125 
126  ioParentObject->addChild(currentObject);
127 
128  const bool cNodeHasTransform = !inNode->mTransformation.IsIdentity();
129 
130  if (cNodeHasTransform) {
131  ccGLMatrix transform =
132  IoUtils::convertMatrix(inNode->mTransformation);
133 
134  currentObject->setGLTransformation(transform);
135  }
136 
137  // meshes
138  for (unsigned int j = 0; j < inNode->mNumMeshes; ++j) {
139  const auto cMeshIndex = inNode->mMeshes[j];
140  const auto mesh = inScene->mMeshes[cMeshIndex];
141 
142  ccMesh *newMesh = IoUtils::newCCMeshFromAIMesh(mesh);
143 
144  if (newMesh == nullptr) {
145  continue;
146  }
147 
148  auto materialSet =
149  IoUtils::createMaterialSetForMesh(mesh, cPath, inScene);
150 
151  if (materialSet != nullptr) {
152  newMesh->setMaterialSet(materialSet);
153  newMesh->showMaterials(true);
154  }
155 
156  currentObject->addChild(newMesh);
157  }
158 
159  // metadata
160  if (inNode->mMetaData != nullptr) {
161  const auto data = inNode->mMetaData;
162 
163  for (unsigned int i = 0; i < data->mNumProperties; ++i) {
164  const auto cMetaKey = data->mKeys[i].C_Str();
165  QVariant metaValue =
167 
168 #ifdef QT_DEBUG
169  std::cout << "Setting meta: " << cMetaKey << " = "
170  << metaValue.toString().toLatin1().constData()
171  << std::endl;
172 #endif
173 
174  currentObject->setMetaData(cMetaKey, metaValue);
175  }
176  }
177 
178  for (unsigned int i = 0; i < inNode->mNumChildren; ++i) {
179  const auto cChild = inNode->mChildren[i];
180 
181  _recursiveAddNode(cChild, inScene, currentObject);
182  }
183  }
184 
185  void _pruneTree(ccHObject *ioCurrentObject) {
186  auto childCount = ioCurrentObject->getChildrenNumber();
187 
188  std::vector<ccHObject *> children;
189 
190  // Because the indices change when we delete children, save a list and
191  // process that instead
192  for (unsigned int i = 0; i < childCount; ++i) {
193  children.push_back(ioCurrentObject->getChild(i));
194  }
195 
196  for (auto child : children) {
197  _pruneTree(child);
198  }
199 
200  // If we are not a "naked" hierarchy object, then we contain useful
201  // info, so return
202  if (ioCurrentObject->getClassID() != CV_TYPES::HIERARCHY_OBJECT) {
203  return;
204  }
205 
206  // If we don't have a parent, then we are the top level, so return
207  auto parent = ioCurrentObject->getParent();
208 
209  if (parent == nullptr) {
210  return;
211  }
212 
213  // Our child count will be different now if we deleted some objects
214  childCount = ioCurrentObject->getChildrenNumber();
215  ;
216 
217  // If we don't have children, then we can be pruned
218  if (childCount == 0) {
219 #ifdef QT_DEBUG
220  std::cout << "pruning: "
221  << ioCurrentObject->getName().toLatin1().constData()
222  << " from parent: "
223  << parent->getName().toLatin1().constData() << std::endl;
224 #endif
225 
226  parent->detachChild(ioCurrentObject);
227 
228  delete ioCurrentObject;
229  } else if ((childCount == 1) && ioCurrentObject->metaData().empty()) {
230  // If we have one child, and it doesn't have useful data,
231  // Then we can reparent it
232 
233  auto child = ioCurrentObject->getChild(0);
234 
235  if (child != nullptr) {
236 #ifdef QT_DEBUG
237  std::cout << "reparenting: "
238  << child->getName().toLatin1().constData() << " from "
239  << ioCurrentObject->getName().toLatin1().constData()
240  << " to " << parent->getName().toLatin1().constData()
241  << std::endl;
242 #endif
243 
244  ioCurrentObject->detachChild(child);
245  child->setName(ioCurrentObject->getName());
246 
247  parent->addChild(child);
248 
249  delete ioCurrentObject;
250  }
251  }
252  }
253 
254  const aiScene *cScene;
255  const QString cFileName;
256  const QString cPath;
257 
258  QMap<QString, const aiCamera *> mCameraMap;
259 };
260 } // namespace
261 
263  : FileIOFilter(info) {}
264 
266  bool &multiple,
267  bool &exclusive) const {
268  Q_UNUSED(type);
269  Q_UNUSED(multiple);
270  Q_UNUSED(exclusive);
271 
272  return false;
273 }
274 
276  const QString &inFileName,
277  ccHObject &ioContainer,
278  FileIOFilter::LoadParameters &inParameters) {
279  Q_UNUSED(inParameters)
280 
281  const auto cFileName = QFileInfo(inFileName).fileName();
282  const auto cPath = QFileInfo(inFileName).absoluteDir().path();
283 
284  CVLog::Print(QStringLiteral("[qMeshIO] Loading file '%1'").arg(inFileName));
285 
286  Assimp::DefaultLogger::create("", Assimp::Logger::NORMAL,
287  aiDefaultLogStream_STDOUT);
288 
289  unsigned int loggingSeverity = Assimp::Logger::Err | Assimp::Logger::Warn;
290 
291 #ifdef QT_DEBUG
292  loggingSeverity |= Assimp::Logger::Info;
293  loggingSeverity |= Assimp::Logger::Debugging;
294 #endif
295 
296  Assimp::DefaultLogger::get()->attachStream(new _LogStream, loggingSeverity);
297 
298  Assimp::Importer importer;
299 
300  importer.SetProgressHandler(new _ProgressHandler(cFileName));
301 
302  // removes things we don't care about from the import
303  importer.SetPropertyInteger(
304  AI_CONFIG_PP_RVC_FLAGS,
305  aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS |
306  aiComponent_CAMERAS | aiComponent_LIGHTS);
307 
308  importer.SetPropertyBool(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, true);
309  importer.SetPropertyBool(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, true);
310  importer.SetPropertyBool(AI_CONFIG_PP_FD_REMOVE, true);
311  importer.SetPropertyBool(AI_CONFIG_PP_FID_IGNORE_TEXTURECOORDS, true);
312 
313  const aiScene *cScene = importer.ReadFile(
314  inFileName.toStdString(),
315  aiProcess_FindInvalidData | aiProcess_JoinIdenticalVertices |
316  aiProcess_RemoveComponent | aiProcess_Triangulate |
317  aiProcess_ValidateDataStructure);
318 
319  if (cScene == nullptr) {
320  CVLog::Warning(QStringLiteral("[qMeshIO] The file '%1' has errors: %2")
321  .arg(cFileName, importer.GetErrorString()));
322 
323  Assimp::DefaultLogger::kill();
324 
325  return CC_FERR_READING;
326  }
327 
328  _Loader loader(cScene, cFileName, cPath);
329 
330  loader.load(ioContainer);
331 
332  Assimp::DefaultLogger::kill();
333 
334  // allow individual loaders to do some processing on the results
335  _postProcess(ioContainer);
336 
337  return CC_FERR_NO_ERROR;
338 }
339 
341  Q_UNUSED(ioContainer);
342 }
int64_t CV_CLASS_ENUM
Type of object type flags (64 bits)
Definition: CVTypes.h:97
char type
CC_FILE_ERROR
Typical I/O filter errors.
Definition: FileIOFilter.h:20
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
@ CC_FERR_READING
Definition: FileIOFilter.h:26
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static void LogMessage(const QString &message, int level)
Static shortcut to CVLog::logMessage.
Definition: CVLog.cpp:64
@ LOG_STANDARD
Definition: CVLog.h:44
@ LOG_VERBOSE
Definition: CVLog.h:43
@ LOG_WARNING
Definition: CVLog.h:46
@ LOG_ERROR
Definition: CVLog.h:47
Generic file I/O filter.
Definition: FileIOFilter.h:46
virtual void _postProcess(ccHObject &ioContainer)
bool canSave(CV_CLASS_ENUM inType, bool &outMultiple, bool &outExclusive) const override
Returns whether this I/O filter can save the specified type of entity.
IoAbstractLoader(const FileIOFilter::FilterInfo &info)
CC_FILE_ERROR loadFile(const QString &inFileName, ccHObject &ioContainer, LoadParameters &inParameters) override
Loads one or more entities from a file.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
virtual void showMaterials(bool state)
Sets whether textures should be displayed or not.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
void detachChild(ccHObject *child)
Detaches a specific child.
void applyGLTransformation_recursive(const ccGLMatrix *trans=nullptr)
Applies the active OpenGL transformation to the entity (recursive)
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
virtual void resetGLTransformationHistory()
Resets the transformation 'history' matrix.
Definition: ecvHObject.h:639
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
CV_CLASS_ENUM getClassID() const override
Returns class ID.
Definition: ecvHObject.h:232
Triangular mesh.
Definition: ecvMesh.h:35
void setMaterialSet(ccMaterialSet *materialSet, bool autoReleaseOldMaterialSet=true)
Sets associated material set (may be shared)
const QVariantMap & metaData() const
Returns meta-data map (const only)
Definition: ecvObject.h:184
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
@ HIERARCHY_OBJECT
Definition: CVTypes.h:103
ccMaterialSet * createMaterialSetForMesh(const aiMesh *inMesh, const QString &inPath, const aiScene *inScene)
Definition: IoUtils.cpp:122
ccMesh * newCCMeshFromAIMesh(const aiMesh *inMesh)
Definition: IoUtils.cpp:239
QVariant convertMetaValueToVariant(aiMetadata *inData, unsigned int inValueIndex)
Definition: IoUtils.cpp:440
ccGLMatrix convertMatrix(const aiMatrix4x4 &inAssimpMatrix)
Definition: IoUtils.cpp:424
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
std::string toString(T x)
Definition: Common.h:80
Generic loading parameters.
Definition: FileIOFilter.h:51