/src/assimp/code/AssetLib/IFC/IFCLoader.cpp
Line | Count | Source |
1 | | /* |
2 | | Open Asset Import Library (assimp) |
3 | | ---------------------------------------------------------------------- |
4 | | |
5 | | Copyright (c) 2006-2026, assimp team |
6 | | |
7 | | All rights reserved. |
8 | | |
9 | | Redistribution and use of this software in source and binary forms, |
10 | | with or without modification, are permitted provided that the |
11 | | following conditions are met: |
12 | | |
13 | | * Redistributions of source code must retain the above |
14 | | copyright notice, this list of conditions and the |
15 | | following disclaimer. |
16 | | |
17 | | * Redistributions in binary form must reproduce the above |
18 | | copyright notice, this list of conditions and the |
19 | | following disclaimer in the documentation and/or other |
20 | | materials provided with the distribution. |
21 | | |
22 | | * Neither the name of the assimp team, nor the names of its |
23 | | contributors may be used to endorse or promote products |
24 | | derived from this software without specific prior |
25 | | written permission of the assimp team. |
26 | | |
27 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | | |
39 | | ---------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | /// @file IFCLoad.cpp |
43 | | /// @brief Implementation of the Industry Foundation Classes loader. |
44 | | |
45 | | #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER |
46 | | |
47 | | #include <iterator> |
48 | | #include <limits> |
49 | | #include <memory> |
50 | | #include <tuple> |
51 | | |
52 | | #ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC |
53 | | #ifdef ASSIMP_USE_HUNTER |
54 | | #include <minizip/unzip.h> |
55 | | #else |
56 | | #include <unzip.h> |
57 | | #endif |
58 | | #endif |
59 | | |
60 | | #include "../STEPParser/STEPFileReader.h" |
61 | | #include "IFCLoader.h" |
62 | | |
63 | | #include "IFCUtil.h" |
64 | | |
65 | | #include <assimp/MemoryIOWrapper.h> |
66 | | #include <assimp/importerdesc.h> |
67 | | #include <assimp/scene.h> |
68 | | #include <assimp/Importer.hpp> |
69 | | #include <utility> |
70 | | |
71 | | namespace Assimp { |
72 | | template <> |
73 | 1 | const char *LogFunctions<IFCImporter>::Prefix() { |
74 | 1 | return "IFC: "; |
75 | 1 | } |
76 | | } // namespace Assimp |
77 | | |
78 | | using namespace Assimp; |
79 | | using namespace Assimp::Formatter; |
80 | | using namespace Assimp::IFC; |
81 | | |
82 | | /* DO NOT REMOVE this comment block. The genentitylist.sh script |
83 | | * just looks for names adhering to the IfcSomething naming scheme |
84 | | * and includes all matches in the whitelist for code-generation. Thus, |
85 | | * all entity classes that are only indirectly referenced need to be |
86 | | * mentioned explicitly. |
87 | | |
88 | | IfcRepresentationMap |
89 | | IfcProductRepresentation |
90 | | IfcUnitAssignment |
91 | | IfcClosedShell |
92 | | IfcDoor |
93 | | */ |
94 | | |
95 | | namespace { |
96 | | |
97 | | // forward declarations |
98 | | void SetUnits(ConversionData &conv); |
99 | | void SetCoordinateSpace(ConversionData &conv); |
100 | | void ProcessSpatialStructures(ConversionData &conv); |
101 | | void MakeTreeRelative(ConversionData &conv); |
102 | | void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType &dt, ConversionData &conv); |
103 | | |
104 | | } // namespace |
105 | | |
106 | | static constexpr aiImporterDesc desc = { |
107 | | "Industry Foundation Classes (IFC) Importer", |
108 | | "", |
109 | | "", |
110 | | "", |
111 | | aiImporterFlags_SupportBinaryFlavour, |
112 | | 0, |
113 | | 0, |
114 | | 0, |
115 | | 0, |
116 | | "ifc ifczip step stp" |
117 | | }; |
118 | | |
119 | | // ------------------------------------------------------------------------------------------------ |
120 | | // Returns whether the class can handle the format of the given file. |
121 | 131 | bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { |
122 | | // note: this is the common identification for STEP-encoded files, so |
123 | | // it is only unambiguous as long as we don't support any further |
124 | | // file formats with STEP as their encoding. |
125 | 131 | static const char *tokens[] = { "ISO-10303-21" }; |
126 | 131 | return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); |
127 | 131 | } |
128 | | |
129 | | // ------------------------------------------------------------------------------------------------ |
130 | | // List all extensions handled by this loader |
131 | 31.9k | const aiImporterDesc *IFCImporter::GetInfo() const { |
132 | 31.9k | return &desc; |
133 | 31.9k | } |
134 | | |
135 | | // ------------------------------------------------------------------------------------------------ |
136 | | // Setup configuration properties for the loader |
137 | 2 | void IFCImporter::SetupProperties(const Importer *pImp) { |
138 | 2 | settings.skipSpaceRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS, true); |
139 | 2 | settings.useCustomTriangulation = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION, true); |
140 | 2 | settings.conicSamplingAngle = std::min(std::max((float)pImp->GetPropertyFloat(AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE, AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE), 5.0f), 120.0f); |
141 | 2 | settings.cylindricalTessellation = std::min(std::max(pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION, AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION), 3), 180); |
142 | 2 | settings.skipAnnotations = true; |
143 | 2 | } |
144 | | |
145 | | // ------------------------------------------------------------------------------------------------ |
146 | | // Imports the given file into the given scene structure. |
147 | 2 | void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { |
148 | 2 | std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile)); |
149 | 2 | if (!stream) { |
150 | 0 | ThrowException("Could not open file for reading"); |
151 | 0 | } |
152 | | |
153 | | // if this is a ifczip file, decompress its contents first |
154 | 2 | if (GetExtension(pFile) == "ifczip") { |
155 | 0 | #ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC |
156 | 0 | unzFile zip = unzOpen(pFile.c_str()); |
157 | 0 | if (zip == nullptr) { |
158 | 0 | ThrowException("Could not open ifczip file for reading, unzip failed"); |
159 | 0 | } |
160 | | |
161 | | // chop 'zip' postfix |
162 | 0 | std::string fileName = pFile.substr(0, pFile.length() - 3); |
163 | |
|
164 | 0 | std::string::size_type s = pFile.find_last_of('\\'); |
165 | 0 | if (s == std::string::npos) { |
166 | 0 | s = pFile.find_last_of('/'); |
167 | 0 | } |
168 | 0 | if (s != std::string::npos) { |
169 | 0 | fileName = fileName.substr(s + 1); |
170 | 0 | } |
171 | | |
172 | | // search file (same name as the IFCZIP except for the file extension) and place file pointer there |
173 | 0 | if (UNZ_OK == unzGoToFirstFile(zip)) { |
174 | 0 | do { |
175 | | // get file size, etc. |
176 | 0 | unz_file_info fileInfo; |
177 | 0 | char filename[256]; |
178 | 0 | unzGetCurrentFileInfo(zip, &fileInfo, filename, sizeof(filename), nullptr, 0, nullptr, 0); |
179 | 0 | if (GetExtension(filename) != "ifc") { |
180 | 0 | continue; |
181 | 0 | } |
182 | 0 | uint8_t *buff = new uint8_t[fileInfo.uncompressed_size]; |
183 | 0 | LogInfo("Decompressing IFCZIP file"); |
184 | 0 | unzOpenCurrentFile(zip); |
185 | 0 | size_t total = 0; |
186 | 0 | int read = 0; |
187 | 0 | do { |
188 | 0 | unsigned bufferSize = fileInfo.uncompressed_size < INT16_MAX ? static_cast<unsigned>(fileInfo.uncompressed_size) : INT16_MAX; |
189 | 0 | void *buffer = malloc(bufferSize); |
190 | 0 | read = unzReadCurrentFile(zip, buffer, bufferSize); |
191 | 0 | if (read > 0) { |
192 | 0 | memcpy((char *)buff + total, buffer, read); |
193 | 0 | total += read; |
194 | 0 | } |
195 | 0 | free(buffer); |
196 | 0 | } while (read > 0); |
197 | 0 | size_t filesize = fileInfo.uncompressed_size; |
198 | 0 | if (total == 0 || size_t(total) != filesize) { |
199 | 0 | delete[] buff; |
200 | 0 | ThrowException("Failed to decompress IFC ZIP file"); |
201 | 0 | } |
202 | 0 | unzCloseCurrentFile(zip); |
203 | 0 | stream = std::make_shared<MemoryIOStream>(buff, fileInfo.uncompressed_size, true); |
204 | 0 | if (unzGoToNextFile(zip) == UNZ_END_OF_LIST_OF_FILE) { |
205 | 0 | ThrowException("Found no IFC file member in IFCZIP file (1)"); |
206 | 0 | } |
207 | 0 | break; |
208 | |
|
209 | 0 | } while (true); |
210 | 0 | } else { |
211 | 0 | ThrowException("Found no IFC file member in IFCZIP file (2)"); |
212 | 0 | } |
213 | |
|
214 | 0 | unzClose(zip); |
215 | | #else |
216 | | ThrowException("Could not open ifczip file for reading, assimp was built without ifczip support"); |
217 | | #endif |
218 | 0 | } |
219 | | |
220 | 2 | std::unique_ptr<STEP::DB> db(STEP::ReadFileHeader(std::move(stream))); |
221 | 2 | const STEP::HeaderInfo &head = static_cast<const STEP::DB &>(*db).GetHeader(); |
222 | | |
223 | 2 | if (!head.fileSchema.size() || head.fileSchema.substr(0, 4) != "IFC2") { |
224 | 1 | ThrowException("Unrecognized file schema: " + head.fileSchema); |
225 | 1 | } |
226 | | |
227 | 2 | if (!DefaultLogger::isNullLogger()) { |
228 | 0 | LogDebug("File schema is \'", head.fileSchema, '\''); |
229 | 0 | if (head.timestamp.length()) { |
230 | 0 | LogDebug("Timestamp \'", head.timestamp, '\''); |
231 | 0 | } |
232 | 0 | if (head.app.length()) { |
233 | 0 | LogDebug("Application/Exporter identline is \'", head.app, '\''); |
234 | 0 | } |
235 | 0 | } |
236 | | |
237 | | // obtain a copy of the machine-generated IFC scheme |
238 | 2 | ::Assimp::STEP::EXPRESS::ConversionSchema schema; |
239 | 2 | Schema_2x3::GetSchema(schema); |
240 | | |
241 | | // tell the reader which entity types to track with special care |
242 | 2 | static const char *const types_to_track[] = { |
243 | 2 | "ifcsite", "ifcbuilding", "ifcproject" |
244 | 2 | }; |
245 | | |
246 | | // tell the reader for which types we need to simulate STEPs reverse indices |
247 | 2 | static const char *const inverse_indices_to_track[] = { |
248 | 2 | "ifcrelcontainedinspatialstructure", |
249 | 2 | "ifcrelaggregates", |
250 | 2 | "ifcrelvoidselement", |
251 | 2 | "ifcreldefinesbyproperties", |
252 | 2 | "ifcpropertyset", |
253 | 2 | "ifcstyleditem" |
254 | 2 | }; |
255 | | |
256 | | // feed the IFC schema into the reader and pre-parse all lines |
257 | 2 | STEP::ReadFile(*db, schema, types_to_track, inverse_indices_to_track); |
258 | 2 | const STEP::LazyObject *proj = db->GetObject("ifcproject"); |
259 | 2 | if (!proj) { |
260 | 0 | ThrowException("missing IfcProject entity"); |
261 | 0 | } |
262 | | |
263 | 2 | ConversionData conv(*db, proj->To<Schema_2x3::IfcProject>(), pScene, settings); |
264 | 2 | SetUnits(conv); |
265 | 2 | SetCoordinateSpace(conv); |
266 | 2 | ProcessSpatialStructures(conv); |
267 | 2 | MakeTreeRelative(conv); |
268 | | |
269 | | // NOTE - this is a stress test for the importer, but it works only |
270 | | // in a build with no entities disabled. See |
271 | | // scripts/IFCImporter/CPPGenerator.py |
272 | | // for more information. |
273 | | #ifdef ASSIMP_IFC_TEST |
274 | | db->EvaluateAll(); |
275 | | #endif |
276 | | |
277 | | // do final data copying |
278 | 2 | if (conv.meshes.size()) { |
279 | 0 | pScene->mNumMeshes = static_cast<unsigned int>(conv.meshes.size()); |
280 | 0 | pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); |
281 | 0 | std::copy(conv.meshes.begin(), conv.meshes.end(), pScene->mMeshes); |
282 | | |
283 | | // needed to keep the d'tor from burning us |
284 | 0 | conv.meshes.clear(); |
285 | 0 | } |
286 | | |
287 | 2 | if (conv.materials.size()) { |
288 | 0 | pScene->mNumMaterials = static_cast<unsigned int>(conv.materials.size()); |
289 | 0 | pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials](); |
290 | 0 | std::copy(conv.materials.begin(), conv.materials.end(), pScene->mMaterials); |
291 | | |
292 | | // needed to keep the d'tor from burning us |
293 | 0 | conv.materials.clear(); |
294 | 0 | } |
295 | | |
296 | | // apply world coordinate system (which includes the scaling to convert to meters and a -90 degrees rotation around x) |
297 | 2 | aiMatrix4x4 scale, rot; |
298 | 2 | aiMatrix4x4::Scaling(static_cast<aiVector3D>(IfcVector3(conv.len_scale)), scale); |
299 | 2 | aiMatrix4x4::RotationX(-AI_MATH_HALF_PI_F, rot); |
300 | | |
301 | 2 | pScene->mRootNode->mTransformation = rot * scale * conv.wcs * pScene->mRootNode->mTransformation; |
302 | | |
303 | | // this must be last because objects are evaluated lazily as we process them |
304 | 2 | if (!DefaultLogger::isNullLogger()) { |
305 | 0 | LogDebug("STEP: evaluated ", db->GetEvaluatedObjectCount(), " object records"); |
306 | 0 | } |
307 | 2 | } |
308 | | |
309 | | namespace { |
310 | | |
311 | | // ------------------------------------------------------------------------------------------------ |
312 | 0 | void ConvertUnit(const Schema_2x3::IfcNamedUnit &unit, ConversionData &conv) { |
313 | 0 | if (const Schema_2x3::IfcSIUnit *const si = unit.ToPtr<Schema_2x3::IfcSIUnit>()) { |
314 | 0 | if (si->UnitType == "LENGTHUNIT") { |
315 | 0 | conv.len_scale = si->Prefix ? ConvertSIPrefix(si->Prefix) : 1.f; |
316 | 0 | IFCImporter::LogDebug("got units used for lengths"); |
317 | 0 | } |
318 | 0 | if (si->UnitType == "PLANEANGLEUNIT") { |
319 | 0 | if (si->Name != "RADIAN") { |
320 | 0 | IFCImporter::LogWarn("expected base unit for angles to be radian"); |
321 | 0 | } |
322 | 0 | } |
323 | 0 | } else if (const Schema_2x3::IfcConversionBasedUnit *const convu = unit.ToPtr<Schema_2x3::IfcConversionBasedUnit>()) { |
324 | 0 | if (convu->UnitType == "PLANEANGLEUNIT") { |
325 | 0 | try { |
326 | 0 | conv.angle_scale = convu->ConversionFactor->ValueComponent->To<::Assimp::STEP::EXPRESS::REAL>(); |
327 | 0 | ConvertUnit(*convu->ConversionFactor->UnitComponent, conv); |
328 | 0 | IFCImporter::LogDebug("got units used for angles"); |
329 | 0 | } catch (std::bad_cast &) { |
330 | 0 | IFCImporter::LogError("skipping unknown IfcConversionBasedUnit.ValueComponent entry - expected REAL"); |
331 | 0 | } |
332 | 0 | } |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | // ------------------------------------------------------------------------------------------------ |
337 | 0 | void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType &dt, ConversionData &conv) { |
338 | 0 | try { |
339 | 0 | const ::Assimp::STEP::EXPRESS::ENTITY &e = dt.To<::Assimp::STEP::EXPRESS::ENTITY>(); |
340 | |
|
341 | 0 | const Schema_2x3::IfcNamedUnit &unit = e.ResolveSelect<Schema_2x3::IfcNamedUnit>(conv.db); |
342 | 0 | if (unit.UnitType != "LENGTHUNIT" && unit.UnitType != "PLANEANGLEUNIT") { |
343 | 0 | return; |
344 | 0 | } |
345 | | |
346 | 0 | ConvertUnit(unit, conv); |
347 | 0 | } catch (std::bad_cast &) { |
348 | | // not entity, somehow |
349 | 0 | IFCImporter::LogError("skipping unknown IfcUnit entry - expected entity"); |
350 | 0 | } |
351 | 0 | } |
352 | | |
353 | | // ------------------------------------------------------------------------------------------------ |
354 | 0 | void SetUnits(ConversionData &conv) { |
355 | 0 | if (conv.proj.UnitsInContext == nullptr) { |
356 | 0 | IFCImporter::LogError("Skipping conversion data, nullptr."); |
357 | 0 | return; |
358 | 0 | } |
359 | | |
360 | | // see if we can determine the coordinate space used to express. |
361 | 0 | for (size_t i = 0; i < conv.proj.UnitsInContext->Units.size(); ++i) { |
362 | 0 | ConvertUnit(*conv.proj.UnitsInContext->Units[i], conv); |
363 | 0 | } |
364 | 0 | } |
365 | | |
366 | | // ------------------------------------------------------------------------------------------------ |
367 | 0 | void SetCoordinateSpace(ConversionData &conv) { |
368 | 0 | const Schema_2x3::IfcRepresentationContext *fav = nullptr; |
369 | 0 | for (const Schema_2x3::IfcRepresentationContext &v : conv.proj.RepresentationContexts) { |
370 | 0 | fav = &v; |
371 | | // Model should be the most suitable type of context, hence ignore the others |
372 | 0 | if (v.ContextType && v.ContextType.Get() == "Model") { |
373 | 0 | break; |
374 | 0 | } |
375 | 0 | } |
376 | 0 | if (fav) { |
377 | 0 | if (const Schema_2x3::IfcGeometricRepresentationContext *const geo = fav->ToPtr<Schema_2x3::IfcGeometricRepresentationContext>()) { |
378 | 0 | ConvertAxisPlacement(conv.wcs, *geo->WorldCoordinateSystem, conv); |
379 | 0 | IFCImporter::LogDebug("got world coordinate system"); |
380 | 0 | } |
381 | 0 | } |
382 | 0 | } |
383 | | |
384 | | // ------------------------------------------------------------------------------------------------ |
385 | 0 | void ResolveObjectPlacement(aiMatrix4x4 &m, const Schema_2x3::IfcObjectPlacement &place, ConversionData &conv) { |
386 | 0 | if (const Schema_2x3::IfcLocalPlacement *const local = place.ToPtr<Schema_2x3::IfcLocalPlacement>()) { |
387 | 0 | IfcMatrix4 tmp; |
388 | 0 | ConvertAxisPlacement(tmp, *local->RelativePlacement, conv); |
389 | |
|
390 | 0 | m = static_cast<aiMatrix4x4>(tmp); |
391 | |
|
392 | 0 | if (local->PlacementRelTo) { |
393 | 0 | aiMatrix4x4 tmpM; |
394 | 0 | ResolveObjectPlacement(tmpM, local->PlacementRelTo.Get(), conv); |
395 | 0 | m = tmpM * m; |
396 | 0 | } |
397 | 0 | } else { |
398 | 0 | IFCImporter::LogWarn("skipping unknown IfcObjectPlacement entity, type is ", place.GetClassName()); |
399 | 0 | } |
400 | 0 | } |
401 | | |
402 | | // ------------------------------------------------------------------------------------------------ |
403 | 0 | bool ProcessMappedItem(const Schema_2x3::IfcMappedItem &mapped, aiNode *nd_src, std::vector<aiNode *> &subnodes_src, unsigned int matid, ConversionData &conv) { |
404 | | // insert a custom node here, the carthesian transform operator is simply a conventional transformation matrix |
405 | 0 | std::unique_ptr<aiNode> nd(new aiNode()); |
406 | 0 | nd->mName.Set("IfcMappedItem"); |
407 | | |
408 | | // handle the Cartesian operator |
409 | 0 | IfcMatrix4 m; |
410 | 0 | ConvertTransformOperator(m, *mapped.MappingTarget); |
411 | |
|
412 | 0 | IfcMatrix4 msrc; |
413 | 0 | ConvertAxisPlacement(msrc, *mapped.MappingSource->MappingOrigin, conv); |
414 | |
|
415 | 0 | msrc = m * msrc; |
416 | |
|
417 | 0 | std::set<unsigned int> meshes; |
418 | 0 | const size_t old_openings = conv.collect_openings ? conv.collect_openings->size() : 0; |
419 | 0 | if (conv.apply_openings) { |
420 | 0 | IfcMatrix4 minv = msrc; |
421 | 0 | minv.Inverse(); |
422 | 0 | for (TempOpening &open : *conv.apply_openings) { |
423 | 0 | open.Transform(minv); |
424 | 0 | } |
425 | 0 | } |
426 | |
|
427 | 0 | unsigned int localmatid = ProcessMaterials(mapped.GetID(), matid, conv, false); |
428 | 0 | const Schema_2x3::IfcRepresentation &repr = mapped.MappingSource->MappedRepresentation; |
429 | |
|
430 | 0 | bool got = false; |
431 | 0 | for (const Schema_2x3::IfcRepresentationItem &item : repr.Items) { |
432 | 0 | if (!ProcessRepresentationItem(item, localmatid, meshes, conv)) { |
433 | 0 | IFCImporter::LogWarn("skipping mapped entity of type ", item.GetClassName(), ", no representations could be generated"); |
434 | 0 | } else |
435 | 0 | got = true; |
436 | 0 | } |
437 | |
|
438 | 0 | if (!got) { |
439 | 0 | return false; |
440 | 0 | } |
441 | | |
442 | 0 | AssignAddedMeshes(meshes, nd.get(), conv); |
443 | 0 | if (conv.collect_openings) { |
444 | | |
445 | | // if this pass serves us only to collect opening geometry, |
446 | | // make sure we transform the TempMesh's which we need to |
447 | | // preserve as well. |
448 | 0 | if (const size_t diff = conv.collect_openings->size() - old_openings) { |
449 | 0 | for (size_t i = 0; i < diff; ++i) { |
450 | 0 | (*conv.collect_openings)[old_openings + i].Transform(msrc); |
451 | 0 | } |
452 | 0 | } |
453 | 0 | } |
454 | |
|
455 | 0 | nd->mTransformation = nd_src->mTransformation * static_cast<aiMatrix4x4>(msrc); |
456 | 0 | subnodes_src.push_back(nd.release()); |
457 | |
|
458 | 0 | return true; |
459 | 0 | } |
460 | | |
461 | | // ------------------------------------------------------------------------------------------------ |
462 | | struct RateRepresentationPredicate { |
463 | 0 | int Rate(const Schema_2x3::IfcRepresentation *r) const { |
464 | | // the smaller, the better |
465 | |
|
466 | 0 | if (!r->RepresentationIdentifier) { |
467 | | // neutral choice if no extra information is specified |
468 | 0 | return 0; |
469 | 0 | } |
470 | | |
471 | 0 | const std::string &name = r->RepresentationIdentifier.Get(); |
472 | 0 | if (name == "MappedRepresentation") { |
473 | 0 | if (!r->Items.empty()) { |
474 | | // take the first item and base our choice on it |
475 | 0 | const Schema_2x3::IfcMappedItem *const m = r->Items.front()->ToPtr<Schema_2x3::IfcMappedItem>(); |
476 | 0 | if (m) { |
477 | 0 | return Rate(m->MappingSource->MappedRepresentation); |
478 | 0 | } |
479 | 0 | } |
480 | 0 | return 100; |
481 | 0 | } |
482 | | |
483 | 0 | return Rate(name); |
484 | 0 | } |
485 | | |
486 | 0 | int Rate(const std::string &r) const { |
487 | 0 | if (r == "SolidModel") { |
488 | 0 | return -3; |
489 | 0 | } |
490 | | |
491 | | // give strong preference to extruded geometry. |
492 | 0 | if (r == "SweptSolid") { |
493 | 0 | return -10; |
494 | 0 | } |
495 | | |
496 | 0 | if (r == "Clipping") { |
497 | 0 | return -5; |
498 | 0 | } |
499 | | |
500 | | // 'Brep' is difficult to get right due to possible voids in the |
501 | | // polygon boundaries, so take it only if we are forced to (i.e. |
502 | | // if the only alternative is (non-clipping) boolean operations, |
503 | | // which are not supported at all). |
504 | 0 | if (r == "Brep") { |
505 | 0 | return -2; |
506 | 0 | } |
507 | | |
508 | | // Curves, bounding boxes - those will most likely not be loaded |
509 | | // as we can't make any use out of this data. So consider them |
510 | | // last. |
511 | 0 | if (r == "BoundingBox" || r == "Curve2D") { |
512 | 0 | return 100; |
513 | 0 | } |
514 | 0 | return 0; |
515 | 0 | } |
516 | | |
517 | 0 | bool operator()(const Schema_2x3::IfcRepresentation *a, const Schema_2x3::IfcRepresentation *b) const { |
518 | 0 | return Rate(a) < Rate(b); |
519 | 0 | } |
520 | | }; |
521 | | |
522 | | // ------------------------------------------------------------------------------------------------ |
523 | 0 | void ProcessProductRepresentation(const Schema_2x3::IfcProduct &el, aiNode *nd, std::vector<aiNode *> &subnodes, ConversionData &conv) { |
524 | 0 | if (!el.Representation) { |
525 | 0 | return; |
526 | 0 | } |
527 | | |
528 | | // extract Color from metadata, if present |
529 | 0 | unsigned int matid = ProcessMaterials(el.GetID(), std::numeric_limits<uint32_t>::max(), conv, false); |
530 | 0 | std::set<unsigned int> meshes; |
531 | | |
532 | | // we want only one representation type, so bring them in a suitable order (i.e try those |
533 | | // that look as if we could read them quickly at first). This way of reading |
534 | | // representation is relatively generic and allows the concrete implementations |
535 | | // for the different representation types to make some sensible choices what |
536 | | // to load and what not to load. |
537 | 0 | const STEP::ListOf<STEP::Lazy<Schema_2x3::IfcRepresentation>, 1, 0> &src = el.Representation.Get()->Representations; |
538 | 0 | std::vector<const Schema_2x3::IfcRepresentation *> repr_ordered(src.size()); |
539 | 0 | std::copy(src.begin(), src.end(), repr_ordered.begin()); |
540 | 0 | std::sort(repr_ordered.begin(), repr_ordered.end(), RateRepresentationPredicate()); |
541 | 0 | for (const Schema_2x3::IfcRepresentation *repr : repr_ordered) { |
542 | 0 | bool res = false; |
543 | 0 | for (const Schema_2x3::IfcRepresentationItem &item : repr->Items) { |
544 | 0 | if (const Schema_2x3::IfcMappedItem *const geo = item.ToPtr<Schema_2x3::IfcMappedItem>()) { |
545 | 0 | res = ProcessMappedItem(*geo, nd, subnodes, matid, conv) || res; |
546 | 0 | } else { |
547 | 0 | res = ProcessRepresentationItem(item, matid, meshes, conv) || res; |
548 | 0 | } |
549 | 0 | } |
550 | | // if we got something meaningful at this point, skip any further representations |
551 | 0 | if (res) { |
552 | 0 | break; |
553 | 0 | } |
554 | 0 | } |
555 | 0 | AssignAddedMeshes(meshes, nd, conv); |
556 | 0 | } |
557 | | |
558 | | typedef std::map<std::string, std::string> Metadata; |
559 | | |
560 | | // ------------------------------------------------------------------------------------------------ |
561 | | void ProcessMetadata(const Schema_2x3::ListOf<Schema_2x3::Lazy<Schema_2x3::IfcProperty>, 1, 0> &set, ConversionData &conv, Metadata &properties, |
562 | | const std::string &prefix = std::string(), |
563 | 0 | unsigned int nest = 0) { |
564 | 0 | for (const Schema_2x3::IfcProperty &property : set) { |
565 | 0 | const std::string &key = prefix.length() > 0 ? (prefix + "." + property.Name) : property.Name; |
566 | 0 | if (const Schema_2x3::IfcPropertySingleValue *const singleValue = property.ToPtr<Schema_2x3::IfcPropertySingleValue>()) { |
567 | 0 | if (singleValue->NominalValue) { |
568 | 0 | if (const ::Assimp::STEP::EXPRESS::STRING *str = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) { |
569 | 0 | std::string value = static_cast<std::string>(*str); |
570 | 0 | properties[key] = value; |
571 | 0 | } else if (const ::Assimp::STEP::EXPRESS::REAL *val1 = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { |
572 | 0 | float value = static_cast<float>(*val1); |
573 | 0 | std::stringstream s; |
574 | 0 | s << value; |
575 | 0 | properties[key] = s.str(); |
576 | 0 | } else if (const ::Assimp::STEP::EXPRESS::INTEGER *val2 = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) { |
577 | 0 | int64_t curValue = static_cast<int64_t>(*val2); |
578 | 0 | std::stringstream s; |
579 | 0 | s << curValue; |
580 | 0 | properties[key] = s.str(); |
581 | 0 | } |
582 | 0 | } |
583 | 0 | } else if (const Schema_2x3::IfcPropertyListValue *const listValue = property.ToPtr<Schema_2x3::IfcPropertyListValue>()) { |
584 | 0 | std::stringstream ss; |
585 | 0 | ss << "["; |
586 | 0 | unsigned index = 0; |
587 | 0 | for (const Schema_2x3::IfcValue::Out &v : listValue->ListValues) { |
588 | 0 | if (!v) continue; |
589 | 0 | if (const ::Assimp::STEP::EXPRESS::STRING *str = v->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) { |
590 | 0 | std::string value = static_cast<std::string>(*str); |
591 | 0 | ss << "'" << value << "'"; |
592 | 0 | } else if (const ::Assimp::STEP::EXPRESS::REAL *val1 = v->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) { |
593 | 0 | float value = static_cast<float>(*val1); |
594 | 0 | ss << value; |
595 | 0 | } else if (const ::Assimp::STEP::EXPRESS::INTEGER *val2 = v->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) { |
596 | 0 | int64_t value = static_cast<int64_t>(*val2); |
597 | 0 | ss << value; |
598 | 0 | } |
599 | 0 | if (index + 1 < listValue->ListValues.size()) { |
600 | 0 | ss << ","; |
601 | 0 | } |
602 | 0 | index++; |
603 | 0 | } |
604 | 0 | ss << "]"; |
605 | 0 | properties[key] = ss.str(); |
606 | 0 | } else if (const Schema_2x3::IfcComplexProperty *const complexProp = property.ToPtr<Schema_2x3::IfcComplexProperty>()) { |
607 | 0 | if (nest > 2) { // mostly arbitrary limit to prevent stack overflow vulnerabilities |
608 | 0 | IFCImporter::LogError("maximum nesting level for IfcComplexProperty reached, skipping this property."); |
609 | 0 | } else { |
610 | 0 | ProcessMetadata(complexProp->HasProperties, conv, properties, key, nest + 1); |
611 | 0 | } |
612 | 0 | } else { |
613 | 0 | properties[key] = std::string(); |
614 | 0 | } |
615 | 0 | } |
616 | 0 | } |
617 | | |
618 | | // ------------------------------------------------------------------------------------------------ |
619 | 0 | void ProcessMetadata(uint64_t relDefinesByPropertiesID, ConversionData &conv, Metadata &properties) { |
620 | 0 | if (const Schema_2x3::IfcRelDefinesByProperties *const pset = conv.db.GetObject(relDefinesByPropertiesID)->ToPtr<Schema_2x3::IfcRelDefinesByProperties>()) { |
621 | 0 | if (const Schema_2x3::IfcPropertySet *const set = conv.db.GetObject(pset->RelatingPropertyDefinition->GetID())->ToPtr<Schema_2x3::IfcPropertySet>()) { |
622 | 0 | ProcessMetadata(set->HasProperties, conv, properties); |
623 | 0 | } |
624 | 0 | } |
625 | 0 | } |
626 | | |
627 | | // ------------------------------------------------------------------------------------------------ |
628 | | aiNode *ProcessSpatialStructure(aiNode *parent, const Schema_2x3::IfcProduct &el, ConversionData &conv, |
629 | 0 | std::vector<TempOpening> *collect_openings = nullptr) { |
630 | 0 | const STEP::DB::RefMap &refs = conv.db.GetRefs(); |
631 | | |
632 | | // skip over space and annotation nodes - usually, these have no meaning in Assimp's context |
633 | 0 | bool skipGeometry = false; |
634 | 0 | if (conv.settings.skipSpaceRepresentations) { |
635 | 0 | if (el.ToPtr<Schema_2x3::IfcSpace>()) { |
636 | 0 | IFCImporter::LogVerboseDebug("skipping IfcSpace entity due to importer settings"); |
637 | 0 | skipGeometry = true; |
638 | 0 | } |
639 | 0 | } |
640 | |
|
641 | 0 | if (conv.settings.skipAnnotations) { |
642 | 0 | if (el.ToPtr<Schema_2x3::IfcAnnotation>()) { |
643 | 0 | IFCImporter::LogVerboseDebug("skipping IfcAnnotation entity due to importer settings"); |
644 | 0 | return nullptr; |
645 | 0 | } |
646 | 0 | } |
647 | | |
648 | | // add an output node for this spatial structure |
649 | 0 | aiNode *nd(new aiNode); |
650 | 0 | nd->mName.Set(el.GetClassName() + "_" + (el.Name ? el.Name.Get() : "Unnamed") + "_" + el.GlobalId); |
651 | 0 | nd->mParent = parent; |
652 | |
|
653 | 0 | conv.already_processed.insert(el.GetID()); |
654 | | |
655 | | // check for node metadata |
656 | 0 | STEP::DB::RefMapRange children = refs.equal_range(el.GetID()); |
657 | 0 | if (children.first != refs.end()) { |
658 | 0 | Metadata properties; |
659 | 0 | if (children.first == children.second) { |
660 | | // handles single property set |
661 | 0 | ProcessMetadata((*children.first).second, conv, properties); |
662 | 0 | } else { |
663 | | // handles multiple property sets (currently all property sets are merged, |
664 | | // which may not be the best solution in the long run) |
665 | 0 | for (STEP::DB::RefMap::const_iterator it = children.first; it != children.second; ++it) { |
666 | 0 | ProcessMetadata((*it).second, conv, properties); |
667 | 0 | } |
668 | 0 | } |
669 | |
|
670 | 0 | if (!properties.empty()) { |
671 | 0 | aiMetadata *data = aiMetadata::Alloc(static_cast<unsigned int>(properties.size())); |
672 | 0 | unsigned int index(0); |
673 | 0 | for (const Metadata::value_type &kv : properties) { |
674 | 0 | data->Set(index++, kv.first, aiString(kv.second)); |
675 | 0 | } |
676 | 0 | nd->mMetaData = data; |
677 | 0 | } |
678 | 0 | } |
679 | |
|
680 | 0 | if (el.ObjectPlacement) { |
681 | 0 | ResolveObjectPlacement(nd->mTransformation, el.ObjectPlacement.Get(), conv); |
682 | 0 | } |
683 | |
|
684 | 0 | std::vector<TempOpening> openings; |
685 | |
|
686 | 0 | IfcMatrix4 myInv; |
687 | 0 | bool didinv = false; |
688 | | |
689 | | // convert everything contained directly within this structure, |
690 | | // this may result in more nodes. |
691 | 0 | std::vector<aiNode *> subnodes; |
692 | 0 | try { |
693 | | // locate aggregates and 'contained-in-here'-elements of this spatial structure and add them in recursively |
694 | | // on our way, collect openings in *this* element |
695 | 0 | STEP::DB::RefMapRange range = refs.equal_range(el.GetID()); |
696 | |
|
697 | 0 | for (STEP::DB::RefMapRange range2 = range; range2.first != range.second; ++range2.first) { |
698 | | // skip over meshes that have already been processed before. This is strictly necessary |
699 | | // because the reverse indices also include references contained in argument lists and |
700 | | // therefore every element has a back-reference hold by its parent. |
701 | 0 | if (conv.already_processed.find((*range2.first).second) != conv.already_processed.end()) { |
702 | 0 | continue; |
703 | 0 | } |
704 | 0 | const STEP::LazyObject &obj = conv.db.MustGetObject((*range2.first).second); |
705 | | |
706 | | // handle regularly-contained elements |
707 | 0 | if (const Schema_2x3::IfcRelContainedInSpatialStructure *const cont = obj->ToPtr<Schema_2x3::IfcRelContainedInSpatialStructure>()) { |
708 | 0 | if (cont->RelatingStructure->GetID() != el.GetID()) { |
709 | 0 | continue; |
710 | 0 | } |
711 | 0 | for (const Schema_2x3::IfcProduct &pro : cont->RelatedElements) { |
712 | 0 | if (pro.ToPtr<Schema_2x3::IfcOpeningElement>()) { |
713 | | // IfcOpeningElement is handled below. Sadly we can't use it here as is: |
714 | | // The docs say that opening elements are USUALLY attached to building storey, |
715 | | // but we want them for the building elements to which they belong. |
716 | 0 | continue; |
717 | 0 | } |
718 | | |
719 | 0 | aiNode *const ndnew = ProcessSpatialStructure(nd, pro, conv, nullptr); |
720 | 0 | if (ndnew) { |
721 | 0 | subnodes.push_back(ndnew); |
722 | 0 | } |
723 | 0 | } |
724 | 0 | } |
725 | | // handle openings, which we collect in a list rather than adding them to the node graph |
726 | 0 | else if (const Schema_2x3::IfcRelVoidsElement *const fills = obj->ToPtr<Schema_2x3::IfcRelVoidsElement>()) { |
727 | 0 | if (fills->RelatingBuildingElement->GetID() == el.GetID()) { |
728 | 0 | const Schema_2x3::IfcFeatureElementSubtraction &open = fills->RelatedOpeningElement; |
729 | | |
730 | | // move opening elements to a separate node since they are semantically different than elements that are just 'contained' |
731 | 0 | std::unique_ptr<aiNode> nd_aggr(new aiNode()); |
732 | 0 | nd_aggr->mName.Set("$RelVoidsElement"); |
733 | 0 | nd_aggr->mParent = nd; |
734 | |
|
735 | 0 | nd_aggr->mTransformation = nd->mTransformation; |
736 | |
|
737 | 0 | std::vector<TempOpening> openings_local; |
738 | 0 | aiNode *const ndnew = ProcessSpatialStructure(nd_aggr.get(), open, conv, &openings_local); |
739 | 0 | if (ndnew) { |
740 | |
|
741 | 0 | nd_aggr->mNumChildren = 1; |
742 | 0 | nd_aggr->mChildren = new aiNode *[1](); |
743 | |
|
744 | 0 | nd_aggr->mChildren[0] = ndnew; |
745 | |
|
746 | 0 | if (openings_local.size()) { |
747 | 0 | if (!didinv) { |
748 | 0 | myInv = aiMatrix4x4(nd->mTransformation).Inverse(); |
749 | 0 | didinv = true; |
750 | 0 | } |
751 | | |
752 | | // we need all openings to be in the local space of *this* node, so transform them |
753 | 0 | for (TempOpening &op : openings_local) { |
754 | 0 | op.Transform(myInv * nd_aggr->mChildren[0]->mTransformation); |
755 | 0 | openings.push_back(op); |
756 | 0 | } |
757 | 0 | } |
758 | 0 | subnodes.push_back(nd_aggr.release()); |
759 | 0 | } |
760 | 0 | } |
761 | 0 | } |
762 | 0 | } |
763 | |
|
764 | 0 | for (; range.first != range.second; ++range.first) { |
765 | | // see note in loop above |
766 | 0 | if (conv.already_processed.find((*range.first).second) != conv.already_processed.end()) { |
767 | 0 | continue; |
768 | 0 | } |
769 | 0 | if (const Schema_2x3::IfcRelAggregates *const aggr = conv.db.GetObject((*range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) { |
770 | 0 | if (aggr->RelatingObject->GetID() != el.GetID()) { |
771 | 0 | continue; |
772 | 0 | } |
773 | | |
774 | | // move aggregate elements to a separate node since they are semantically different than elements that are just 'contained' |
775 | 0 | std::unique_ptr<aiNode> nd_aggr(new aiNode()); |
776 | 0 | nd_aggr->mName.Set("$RelAggregates"); |
777 | 0 | nd_aggr->mParent = nd; |
778 | |
|
779 | 0 | nd_aggr->mTransformation = nd->mTransformation; |
780 | |
|
781 | 0 | nd_aggr->mChildren = new aiNode *[aggr->RelatedObjects.size()](); |
782 | 0 | for (const Schema_2x3::IfcObjectDefinition &def : aggr->RelatedObjects) { |
783 | 0 | if (const Schema_2x3::IfcProduct *const prod = def.ToPtr<Schema_2x3::IfcProduct>()) { |
784 | |
|
785 | 0 | aiNode *const ndnew = ProcessSpatialStructure(nd_aggr.get(), *prod, conv, nullptr); |
786 | 0 | if (ndnew) { |
787 | 0 | nd_aggr->mChildren[nd_aggr->mNumChildren++] = ndnew; |
788 | 0 | } |
789 | 0 | } |
790 | 0 | } |
791 | |
|
792 | 0 | subnodes.push_back(nd_aggr.release()); |
793 | 0 | } |
794 | 0 | } |
795 | |
|
796 | 0 | conv.collect_openings = collect_openings; |
797 | 0 | if (!conv.collect_openings) { |
798 | 0 | conv.apply_openings = &openings; |
799 | 0 | } |
800 | |
|
801 | 0 | if (!skipGeometry) { |
802 | 0 | ProcessProductRepresentation(el, nd, subnodes, conv); |
803 | 0 | conv.apply_openings = conv.collect_openings = nullptr; |
804 | 0 | } |
805 | |
|
806 | 0 | if (subnodes.size()) { |
807 | 0 | nd->mChildren = new aiNode *[subnodes.size()](); |
808 | 0 | for (aiNode *nd2 : subnodes) { |
809 | 0 | nd->mChildren[nd->mNumChildren++] = nd2; |
810 | 0 | nd2->mParent = nd; |
811 | 0 | } |
812 | 0 | } |
813 | 0 | } catch (...) { |
814 | | // it hurts, but I don't want to pull boost::ptr_vector into -noboost only for these few spots here |
815 | 0 | std::for_each(subnodes.begin(), subnodes.end(), delete_fun<aiNode>()); |
816 | 0 | throw; |
817 | 0 | } |
818 | | |
819 | 0 | ai_assert(conv.already_processed.find(el.GetID()) != conv.already_processed.end()); |
820 | 0 | conv.already_processed.erase(conv.already_processed.find(el.GetID())); |
821 | 0 | return nd; |
822 | 0 | } |
823 | | |
824 | | // ------------------------------------------------------------------------------------------------ |
825 | 0 | void ProcessSpatialStructures(ConversionData &conv) { |
826 | | // XXX add support for multiple sites (i.e. IfcSpatialStructureElements with composition == COMPLEX) |
827 | | |
828 | | // process all products in the file. it is reasonable to assume that a |
829 | | // file that is relevant for us contains at least a site or a building. |
830 | 0 | const STEP::DB::ObjectMapByType &map = conv.db.GetObjectsByType(); |
831 | |
|
832 | 0 | ai_assert(map.find("ifcsite") != map.end()); |
833 | 0 | const STEP::DB::ObjectSet *range = &map.find("ifcsite")->second; |
834 | |
|
835 | 0 | if (range->empty()) { |
836 | 0 | ai_assert(map.find("ifcbuilding") != map.end()); |
837 | 0 | range = &map.find("ifcbuilding")->second; |
838 | 0 | if (range->empty()) { |
839 | | // no site, no building - fail; |
840 | 0 | IFCImporter::ThrowException("no root element found (expected IfcBuilding or preferably IfcSite)"); |
841 | 0 | } |
842 | 0 | } |
843 | |
|
844 | 0 | std::vector<aiNode *> nodes; |
845 | |
|
846 | 0 | for (const STEP::LazyObject *lz : *range) { |
847 | 0 | const Schema_2x3::IfcSpatialStructureElement *const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>(); |
848 | 0 | if (!prod) { |
849 | 0 | continue; |
850 | 0 | } |
851 | 0 | IFCImporter::LogVerboseDebug("looking at spatial structure `", (prod->Name ? prod->Name.Get() : "unnamed"), "`", (prod->ObjectType ? " which is of type " + prod->ObjectType.Get() : "")); |
852 | | |
853 | | // the primary sites are referenced by an IFCRELAGGREGATES element which assigns them to the IFCPRODUCT |
854 | 0 | const STEP::DB::RefMap &refs = conv.db.GetRefs(); |
855 | 0 | STEP::DB::RefMapRange ref_range = refs.equal_range(conv.proj.GetID()); |
856 | 0 | for (; ref_range.first != ref_range.second; ++ref_range.first) { |
857 | 0 | if (const Schema_2x3::IfcRelAggregates *const aggr = conv.db.GetObject((*ref_range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) { |
858 | |
|
859 | 0 | for (const Schema_2x3::IfcObjectDefinition &def : aggr->RelatedObjects) { |
860 | | // comparing pointer values is not sufficient, we would need to cast them to the same type first |
861 | | // as there is multiple inheritance in the game. |
862 | 0 | if (def.GetID() == prod->GetID()) { |
863 | 0 | IFCImporter::LogVerboseDebug("selecting this spatial structure as root structure"); |
864 | | // got it, this is one primary site. |
865 | 0 | nodes.push_back(ProcessSpatialStructure(nullptr, *prod, conv, nullptr)); |
866 | 0 | } |
867 | 0 | } |
868 | 0 | } |
869 | 0 | } |
870 | 0 | } |
871 | |
|
872 | 0 | size_t nb_nodes = nodes.size(); |
873 | |
|
874 | 0 | if (nb_nodes == 0) { |
875 | 0 | IFCImporter::LogWarn("failed to determine primary site element, taking all the IfcSite"); |
876 | 0 | for (const STEP::LazyObject *lz : *range) { |
877 | 0 | const Schema_2x3::IfcSpatialStructureElement *const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>(); |
878 | 0 | if (!prod) { |
879 | 0 | continue; |
880 | 0 | } |
881 | | |
882 | 0 | nodes.push_back(ProcessSpatialStructure(nullptr, *prod, conv, nullptr)); |
883 | 0 | } |
884 | |
|
885 | 0 | nb_nodes = nodes.size(); |
886 | 0 | } |
887 | |
|
888 | 0 | if (nb_nodes == 1) { |
889 | 0 | conv.out->mRootNode = nodes[0]; |
890 | 0 | } else if (nb_nodes > 1) { |
891 | 0 | conv.out->mRootNode = new aiNode("Root"); |
892 | 0 | conv.out->mRootNode->mParent = nullptr; |
893 | 0 | conv.out->mRootNode->mNumChildren = static_cast<unsigned int>(nb_nodes); |
894 | 0 | conv.out->mRootNode->mChildren = new aiNode *[conv.out->mRootNode->mNumChildren]; |
895 | |
|
896 | 0 | for (size_t i = 0; i < nb_nodes; ++i) { |
897 | 0 | aiNode *node = nodes[i]; |
898 | |
|
899 | 0 | node->mParent = conv.out->mRootNode; |
900 | |
|
901 | 0 | conv.out->mRootNode->mChildren[i] = node; |
902 | 0 | } |
903 | 0 | } else { |
904 | 0 | IFCImporter::ThrowException("failed to determine primary site element"); |
905 | 0 | } |
906 | 0 | } |
907 | | |
908 | | // ------------------------------------------------------------------------------------------------ |
909 | 0 | void MakeTreeRelative(aiNode *start, const aiMatrix4x4 &combined) { |
910 | | // combined is the parent's absolute transformation matrix |
911 | 0 | const aiMatrix4x4 old = start->mTransformation; |
912 | |
|
913 | 0 | if (!combined.IsIdentity()) { |
914 | 0 | start->mTransformation = aiMatrix4x4(combined).Inverse() * start->mTransformation; |
915 | 0 | } |
916 | | |
917 | | // All nodes store absolute transformations right now, so we need to make them relative |
918 | 0 | for (unsigned int i = 0; i < start->mNumChildren; ++i) { |
919 | 0 | MakeTreeRelative(start->mChildren[i], old); |
920 | 0 | } |
921 | 0 | } |
922 | | |
923 | | // ------------------------------------------------------------------------------------------------ |
924 | 0 | void MakeTreeRelative(ConversionData &conv) { |
925 | 0 | MakeTreeRelative(conv.out->mRootNode, IfcMatrix4()); |
926 | 0 | } |
927 | | |
928 | | } // namespace |
929 | | |
930 | | #endif // ASSIMP_BUILD_NO_IFC_IMPORTER |