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