vrshoot

view libs/assimp/IFCLoader.cpp @ 0:b2f14e535253

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