Coverage Report

Created: 2026-02-05 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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