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