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 { |
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 |
122 | IFCImporter::IFCImporter() |
123 | {} |
124 | |
125 | // ------------------------------------------------------------------------------------------------ |
126 | // Destructor, private as well |
127 | IFCImporter::~IFCImporter() |
128 | { |
129 | } |
130 | |
131 | // ------------------------------------------------------------------------------------------------ |
132 | // Returns whether the class can handle the format of the given file. |
133 | 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 |
150 | const aiImporterDesc* IFCImporter::GetInfo () const |
151 | { |
152 | return &desc; |
153 | } |
154 | |
155 | |
156 | // ------------------------------------------------------------------------------------------------ |
157 | // Setup configuration properties for the loader |
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. |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | |
487 | 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 | |
511 | 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 | |
544 | bool operator() (const IfcRepresentation* a, const IfcRepresentation* b) const { |
545 | return Rate(a) < Rate(b); |
546 | } |
547 | }; |
548 | |
549 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
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 | // ------------------------------------------------------------------------------------------------ |
973 | void MakeTreeRelative(ConversionData& conv) |
974 | { |
975 | MakeTreeRelative(conv.out->mRootNode,IfcMatrix4()); |
976 | } |
977 | |
978 | } // !anon |
979 | |
980 | |
981 | |
982 | #endif |
983 | |