nuclear@0: /* nuclear@0: Open Asset Import Library (assimp) nuclear@0: ---------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2012, assimp team nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the nuclear@0: following conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: nuclear@0: ---------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: /** @file IFCLoad.cpp nuclear@0: * @brief Implementation of the Industry Foundation Classes loader. nuclear@0: */ nuclear@0: #include "AssimpPCH.h" nuclear@0: nuclear@0: #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER nuclear@0: nuclear@0: #include nuclear@0: #include nuclear@0: nuclear@0: #ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC nuclear@0: # include "../contrib/unzip/unzip.h" nuclear@0: #endif nuclear@0: nuclear@0: #include "IFCLoader.h" nuclear@0: #include "STEPFileReader.h" nuclear@0: nuclear@0: #include "IFCUtil.h" nuclear@0: nuclear@0: #include "StreamReader.h" nuclear@0: #include "MemoryIOWrapper.h" nuclear@0: nuclear@0: namespace Assimp { nuclear@0: template<> const std::string LogFunctions::log_prefix = "IFC: "; nuclear@0: } nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: using namespace Assimp::Formatter; nuclear@0: using namespace Assimp::IFC; nuclear@0: nuclear@0: /* DO NOT REMOVE this comment block. The genentitylist.sh script nuclear@0: * just looks for names adhering to the IfcSomething naming scheme nuclear@0: * and includes all matches in the whitelist for code-generation. Thus, nuclear@0: * all entity classes that are only indirectly referenced need to be nuclear@0: * mentioned explicitly. nuclear@0: nuclear@0: IfcRepresentationMap nuclear@0: IfcProductRepresentation nuclear@0: IfcUnitAssignment nuclear@0: IfcClosedShell nuclear@0: IfcDoor nuclear@0: nuclear@0: */ nuclear@0: nuclear@0: namespace { nuclear@0: nuclear@0: nuclear@0: // forward declarations nuclear@0: void SetUnits(ConversionData& conv); nuclear@0: void SetCoordinateSpace(ConversionData& conv); nuclear@0: void ProcessSpatialStructures(ConversionData& conv); nuclear@0: aiNode* ProcessSpatialStructure(aiNode* parent, const IfcProduct& el ,ConversionData& conv); nuclear@0: void ProcessProductRepresentation(const IfcProduct& el, aiNode* nd, ConversionData& conv); nuclear@0: void MakeTreeRelative(ConversionData& conv); nuclear@0: void ConvertUnit(const EXPRESS::DataType& dt,ConversionData& conv); nuclear@0: nuclear@0: } // anon nuclear@0: nuclear@0: static const aiImporterDesc desc = { nuclear@0: "Industry Foundation Classes (IFC) Importer", nuclear@0: "", nuclear@0: "", nuclear@0: "", nuclear@0: aiImporterFlags_SupportBinaryFlavour, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: "ifc ifczip" nuclear@0: }; nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructor to be privately used by Importer nuclear@0: IFCImporter::IFCImporter() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Destructor, private as well nuclear@0: IFCImporter::~IFCImporter() nuclear@0: { nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Returns whether the class can handle the format of the given file. nuclear@0: bool IFCImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const nuclear@0: { nuclear@0: const std::string& extension = GetExtension(pFile); nuclear@0: if (extension == "ifc" || extension == "ifczip") { nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: else if ((!extension.length() || checkSig) && pIOHandler) { nuclear@0: // note: this is the common identification for STEP-encoded files, so nuclear@0: // it is only unambiguous as long as we don't support any further nuclear@0: // file formats with STEP as their encoding. nuclear@0: const char* tokens[] = {"ISO-10303-21"}; nuclear@0: return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // List all extensions handled by this loader nuclear@0: const aiImporterDesc* IFCImporter::GetInfo () const nuclear@0: { nuclear@0: return &desc; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Setup configuration properties for the loader nuclear@0: void IFCImporter::SetupProperties(const Importer* pImp) nuclear@0: { nuclear@0: settings.skipSpaceRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS,true); nuclear@0: settings.skipCurveRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_CURVE_REPRESENTATIONS,true); nuclear@0: settings.useCustomTriangulation = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION,true); nuclear@0: nuclear@0: settings.conicSamplingAngle = 10.f; nuclear@0: settings.skipAnnotations = true; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Imports the given file into the given scene structure. nuclear@0: void IFCImporter::InternReadFile( const std::string& pFile, nuclear@0: aiScene* pScene, IOSystem* pIOHandler) nuclear@0: { nuclear@0: boost::shared_ptr stream(pIOHandler->Open(pFile)); nuclear@0: if (!stream) { nuclear@0: ThrowException("Could not open file for reading"); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // if this is a ifczip file, decompress its contents first nuclear@0: if(GetExtension(pFile) == "ifczip") { nuclear@0: #ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC nuclear@0: unzFile zip = unzOpen( pFile.c_str() ); nuclear@0: if(zip == NULL) { nuclear@0: ThrowException("Could not open ifczip file for reading, unzip failed"); nuclear@0: } nuclear@0: nuclear@0: // chop 'zip' postfix nuclear@0: std::string fileName = pFile.substr(0,pFile.length() - 3); nuclear@0: nuclear@0: std::string::size_type s = pFile.find_last_of('\\'); nuclear@0: if(s == std::string::npos) { nuclear@0: s = pFile.find_last_of('/'); nuclear@0: } nuclear@0: if(s != std::string::npos) { nuclear@0: fileName = fileName.substr(s+1); nuclear@0: } nuclear@0: nuclear@0: // search file (same name as the IFCZIP except for the file extension) and place file pointer there nuclear@0: nuclear@0: if(UNZ_OK == unzGoToFirstFile(zip)) { nuclear@0: do { nuclear@0: // nuclear@0: nuclear@0: // get file size, etc. nuclear@0: unz_file_info fileInfo; nuclear@0: char filename[256]; nuclear@0: unzGetCurrentFileInfo( zip , &fileInfo, filename, sizeof(filename), 0, 0, 0, 0 ); nuclear@0: nuclear@0: if (GetExtension(filename) != "ifc") { nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: uint8_t* buff = new uint8_t[fileInfo.uncompressed_size]; nuclear@0: nuclear@0: LogInfo("Decompressing IFCZIP file"); nuclear@0: nuclear@0: unzOpenCurrentFile( zip ); nuclear@0: const int ret = unzReadCurrentFile( zip, buff, fileInfo.uncompressed_size); nuclear@0: size_t filesize = fileInfo.uncompressed_size; nuclear@0: if ( ret < 0 || size_t(ret) != filesize ) nuclear@0: { nuclear@0: delete[] buff; nuclear@0: ThrowException("Failed to decompress IFC ZIP file"); nuclear@0: } nuclear@0: unzCloseCurrentFile( zip ); nuclear@0: stream.reset(new MemoryIOStream(buff,fileInfo.uncompressed_size,true)); nuclear@0: break; nuclear@0: nuclear@0: if (unzGoToNextFile(zip) == UNZ_END_OF_LIST_OF_FILE) { nuclear@0: ThrowException("Found no IFC file member in IFCZIP file (1)"); nuclear@0: } nuclear@0: nuclear@0: } while(true); nuclear@0: } nuclear@0: else { nuclear@0: ThrowException("Found no IFC file member in IFCZIP file (2)"); nuclear@0: } nuclear@0: nuclear@0: unzClose(zip); nuclear@0: #else nuclear@0: ThrowException("Could not open ifczip file for reading, assimp was built without ifczip support"); nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: boost::scoped_ptr db(STEP::ReadFileHeader(stream)); nuclear@0: const STEP::HeaderInfo& head = static_cast(*db).GetHeader(); nuclear@0: nuclear@0: if(!head.fileSchema.size() || head.fileSchema.substr(0,3) != "IFC") { nuclear@0: ThrowException("Unrecognized file schema: " + head.fileSchema); nuclear@0: } nuclear@0: nuclear@0: if (!DefaultLogger::isNullLogger()) { nuclear@0: LogDebug("File schema is \'" + head.fileSchema + '\''); nuclear@0: if (head.timestamp.length()) { nuclear@0: LogDebug("Timestamp \'" + head.timestamp + '\''); nuclear@0: } nuclear@0: if (head.app.length()) { nuclear@0: LogDebug("Application/Exporter identline is \'" + head.app + '\''); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // obtain a copy of the machine-generated IFC scheme nuclear@0: EXPRESS::ConversionSchema schema; nuclear@0: GetSchema(schema); nuclear@0: nuclear@0: // tell the reader which entity types to track with special care nuclear@0: static const char* const types_to_track[] = { nuclear@0: "ifcsite", "ifcbuilding", "ifcproject" nuclear@0: }; nuclear@0: nuclear@0: // tell the reader for which types we need to simulate STEPs reverse indices nuclear@0: static const char* const inverse_indices_to_track[] = { nuclear@0: "ifcrelcontainedinspatialstructure", "ifcrelaggregates", "ifcrelvoidselement", "ifcreldefinesbyproperties", "ifcpropertyset", "ifcstyleditem" nuclear@0: }; nuclear@0: nuclear@0: // feed the IFC schema into the reader and pre-parse all lines nuclear@0: STEP::ReadFile(*db, schema, types_to_track, inverse_indices_to_track); nuclear@0: nuclear@0: const STEP::LazyObject* proj = db->GetObject("ifcproject"); nuclear@0: if (!proj) { nuclear@0: ThrowException("missing IfcProject entity"); nuclear@0: } nuclear@0: nuclear@0: ConversionData conv(*db,proj->To(),pScene,settings); nuclear@0: SetUnits(conv); nuclear@0: SetCoordinateSpace(conv); nuclear@0: ProcessSpatialStructures(conv); nuclear@0: MakeTreeRelative(conv); nuclear@0: nuclear@0: // NOTE - this is a stress test for the importer, but it works only nuclear@0: // in a build with no entities disabled. See nuclear@0: // scripts/IFCImporter/CPPGenerator.py nuclear@0: // for more information. nuclear@0: #ifdef ASSIMP_IFC_TEST nuclear@0: db->EvaluateAll(); nuclear@0: #endif nuclear@0: nuclear@0: // do final data copying nuclear@0: if (conv.meshes.size()) { nuclear@0: pScene->mNumMeshes = static_cast(conv.meshes.size()); nuclear@0: pScene->mMeshes = new aiMesh*[pScene->mNumMeshes](); nuclear@0: std::copy(conv.meshes.begin(),conv.meshes.end(),pScene->mMeshes); nuclear@0: nuclear@0: // needed to keep the d'tor from burning us nuclear@0: conv.meshes.clear(); nuclear@0: } nuclear@0: nuclear@0: if (conv.materials.size()) { nuclear@0: pScene->mNumMaterials = static_cast(conv.materials.size()); nuclear@0: pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials](); nuclear@0: std::copy(conv.materials.begin(),conv.materials.end(),pScene->mMaterials); nuclear@0: nuclear@0: // needed to keep the d'tor from burning us nuclear@0: conv.materials.clear(); nuclear@0: } nuclear@0: nuclear@0: // apply world coordinate system (which includes the scaling to convert to meters and a -90 degrees rotation around x) nuclear@0: aiMatrix4x4 scale, rot; nuclear@0: aiMatrix4x4::Scaling(static_cast(IfcVector3(conv.len_scale)),scale); nuclear@0: aiMatrix4x4::RotationX(-AI_MATH_HALF_PI_F,rot); nuclear@0: nuclear@0: pScene->mRootNode->mTransformation = rot * scale * conv.wcs * pScene->mRootNode->mTransformation; nuclear@0: nuclear@0: // this must be last because objects are evaluated lazily as we process them nuclear@0: if ( !DefaultLogger::isNullLogger() ){ nuclear@0: LogDebug((Formatter::format(),"STEP: evaluated ",db->GetEvaluatedObjectCount()," object records")); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: namespace { nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ConvertUnit(const IfcNamedUnit& unit,ConversionData& conv) nuclear@0: { nuclear@0: if(const IfcSIUnit* const si = unit.ToPtr()) { nuclear@0: nuclear@0: if(si->UnitType == "LENGTHUNIT") { nuclear@0: conv.len_scale = si->Prefix ? ConvertSIPrefix(si->Prefix) : 1.f; nuclear@0: IFCImporter::LogDebug("got units used for lengths"); nuclear@0: } nuclear@0: if(si->UnitType == "PLANEANGLEUNIT") { nuclear@0: if (si->Name != "RADIAN") { nuclear@0: IFCImporter::LogWarn("expected base unit for angles to be radian"); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: else if(const IfcConversionBasedUnit* const convu = unit.ToPtr()) { nuclear@0: nuclear@0: if(convu->UnitType == "PLANEANGLEUNIT") { nuclear@0: try { nuclear@0: conv.angle_scale = convu->ConversionFactor->ValueComponent->To(); nuclear@0: ConvertUnit(*convu->ConversionFactor->UnitComponent,conv); nuclear@0: IFCImporter::LogDebug("got units used for angles"); nuclear@0: } nuclear@0: catch(std::bad_cast&) { nuclear@0: IFCImporter::LogError("skipping unknown IfcConversionBasedUnit.ValueComponent entry - expected REAL"); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ConvertUnit(const EXPRESS::DataType& dt,ConversionData& conv) nuclear@0: { nuclear@0: try { nuclear@0: const EXPRESS::ENTITY& e = dt.To(); nuclear@0: nuclear@0: const IfcNamedUnit& unit = e.ResolveSelect(conv.db); nuclear@0: if(unit.UnitType != "LENGTHUNIT" && unit.UnitType != "PLANEANGLEUNIT") { nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: ConvertUnit(unit,conv); nuclear@0: } nuclear@0: catch(std::bad_cast&) { nuclear@0: // not entity, somehow nuclear@0: IFCImporter::LogError("skipping unknown IfcUnit entry - expected entity"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void SetUnits(ConversionData& conv) nuclear@0: { nuclear@0: // see if we can determine the coordinate space used to express. nuclear@0: for(size_t i = 0; i < conv.proj.UnitsInContext->Units.size(); ++i ) { nuclear@0: ConvertUnit(*conv.proj.UnitsInContext->Units[i],conv); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void SetCoordinateSpace(ConversionData& conv) nuclear@0: { nuclear@0: const IfcRepresentationContext* fav = NULL; nuclear@0: BOOST_FOREACH(const IfcRepresentationContext& v, conv.proj.RepresentationContexts) { nuclear@0: fav = &v; nuclear@0: // Model should be the most suitable type of context, hence ignore the others nuclear@0: if (v.ContextType && v.ContextType.Get() == "Model") { nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: if (fav) { nuclear@0: if(const IfcGeometricRepresentationContext* const geo = fav->ToPtr()) { nuclear@0: ConvertAxisPlacement(conv.wcs, *geo->WorldCoordinateSystem, conv); nuclear@0: IFCImporter::LogDebug("got world coordinate system"); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ResolveObjectPlacement(aiMatrix4x4& m, const IfcObjectPlacement& place, ConversionData& conv) nuclear@0: { nuclear@0: if (const IfcLocalPlacement* const local = place.ToPtr()){ nuclear@0: IfcMatrix4 tmp; nuclear@0: ConvertAxisPlacement(tmp, *local->RelativePlacement, conv); nuclear@0: nuclear@0: m = static_cast(tmp); nuclear@0: nuclear@0: if (local->PlacementRelTo) { nuclear@0: aiMatrix4x4 tmp; nuclear@0: ResolveObjectPlacement(tmp,local->PlacementRelTo.Get(),conv); nuclear@0: m = tmp * m; nuclear@0: } nuclear@0: } nuclear@0: else { nuclear@0: IFCImporter::LogWarn("skipping unknown IfcObjectPlacement entity, type is " + place.GetClassName()); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void GetAbsTransform(aiMatrix4x4& out, const aiNode* nd, ConversionData& conv) nuclear@0: { nuclear@0: aiMatrix4x4 t; nuclear@0: if (nd->mParent) { nuclear@0: GetAbsTransform(t,nd->mParent,conv); nuclear@0: } nuclear@0: out = t*nd->mTransformation; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: bool ProcessMappedItem(const IfcMappedItem& mapped, aiNode* nd_src, std::vector< aiNode* >& subnodes_src, ConversionData& conv) nuclear@0: { nuclear@0: // insert a custom node here, the cartesian transform operator is simply a conventional transformation matrix nuclear@0: std::auto_ptr nd(new aiNode()); nuclear@0: nd->mName.Set("IfcMappedItem"); nuclear@0: nuclear@0: // handle the Cartesian operator nuclear@0: IfcMatrix4 m; nuclear@0: ConvertTransformOperator(m, *mapped.MappingTarget); nuclear@0: nuclear@0: IfcMatrix4 msrc; nuclear@0: ConvertAxisPlacement(msrc,*mapped.MappingSource->MappingOrigin,conv); nuclear@0: nuclear@0: msrc = m*msrc; nuclear@0: nuclear@0: std::vector meshes; nuclear@0: const size_t old_openings = conv.collect_openings ? conv.collect_openings->size() : 0; nuclear@0: if (conv.apply_openings) { nuclear@0: IfcMatrix4 minv = msrc; nuclear@0: minv.Inverse(); nuclear@0: BOOST_FOREACH(TempOpening& open,*conv.apply_openings){ nuclear@0: open.Transform(minv); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: const IfcRepresentation& repr = mapped.MappingSource->MappedRepresentation; nuclear@0: nuclear@0: bool got = false; nuclear@0: BOOST_FOREACH(const IfcRepresentationItem& item, repr.Items) { nuclear@0: if(!ProcessRepresentationItem(item,meshes,conv)) { nuclear@0: IFCImporter::LogWarn("skipping mapped entity of type " + item.GetClassName() + ", no representations could be generated"); nuclear@0: } nuclear@0: else got = true; nuclear@0: } nuclear@0: nuclear@0: if (!got) { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: AssignAddedMeshes(meshes,nd.get(),conv); nuclear@0: if (conv.collect_openings) { nuclear@0: nuclear@0: // if this pass serves us only to collect opening geometry, nuclear@0: // make sure we transform the TempMesh's which we need to nuclear@0: // preserve as well. nuclear@0: if(const size_t diff = conv.collect_openings->size() - old_openings) { nuclear@0: for(size_t i = 0; i < diff; ++i) { nuclear@0: (*conv.collect_openings)[old_openings+i].Transform(msrc); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nd->mTransformation = nd_src->mTransformation * static_cast( msrc ); nuclear@0: subnodes_src.push_back(nd.release()); nuclear@0: nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: struct RateRepresentationPredicate { nuclear@0: nuclear@0: int Rate(const IfcRepresentation* r) const { nuclear@0: // the smaller, the better nuclear@0: nuclear@0: if (! r->RepresentationIdentifier) { nuclear@0: // neutral choice if no extra information is specified nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: const std::string& name = r->RepresentationIdentifier.Get(); nuclear@0: if (name == "MappedRepresentation") { nuclear@0: if (!r->Items.empty()) { nuclear@0: // take the first item and base our choice on it nuclear@0: const IfcMappedItem* const m = r->Items.front()->ToPtr(); nuclear@0: if (m) { nuclear@0: return Rate(m->MappingSource->MappedRepresentation); nuclear@0: } nuclear@0: } nuclear@0: return 100; nuclear@0: } nuclear@0: nuclear@0: return Rate(name); nuclear@0: } nuclear@0: nuclear@0: int Rate(const std::string& r) const { nuclear@0: nuclear@0: nuclear@0: if (r == "SolidModel") { nuclear@0: return -3; nuclear@0: } nuclear@0: nuclear@0: // give strong preference to extruded geometry. nuclear@0: if (r == "SweptSolid") { nuclear@0: return -10; nuclear@0: } nuclear@0: nuclear@0: if (r == "Clipping") { nuclear@0: return -5; nuclear@0: } nuclear@0: nuclear@0: // 'Brep' is difficult to get right due to possible voids in the nuclear@0: // polygon boundaries, so take it only if we are forced to (i.e. nuclear@0: // if the only alternative is (non-clipping) boolean operations, nuclear@0: // which are not supported at all). nuclear@0: if (r == "Brep") { nuclear@0: return -2; nuclear@0: } nuclear@0: nuclear@0: // Curves, bounding boxes - those will most likely not be loaded nuclear@0: // as we can't make any use out of this data. So consider them nuclear@0: // last. nuclear@0: if (r == "BoundingBox" || r == "Curve2D") { nuclear@0: return 100; nuclear@0: } nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: bool operator() (const IfcRepresentation* a, const IfcRepresentation* b) const { nuclear@0: return Rate(a) < Rate(b); nuclear@0: } nuclear@0: }; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessProductRepresentation(const IfcProduct& el, aiNode* nd, std::vector< aiNode* >& subnodes, ConversionData& conv) nuclear@0: { nuclear@0: if(!el.Representation) { nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: std::vector meshes; nuclear@0: nuclear@0: // we want only one representation type, so bring them in a suitable order (i.e try those nuclear@0: // that look as if we could read them quickly at first). This way of reading nuclear@0: // representation is relatively generic and allows the concrete implementations nuclear@0: // for the different representation types to make some sensible choices what nuclear@0: // to load and what not to load. nuclear@0: const STEP::ListOf< STEP::Lazy< IfcRepresentation >, 1, 0 >& src = el.Representation.Get()->Representations; nuclear@0: nuclear@0: std::vector repr_ordered(src.size()); nuclear@0: std::copy(src.begin(),src.end(),repr_ordered.begin()); nuclear@0: std::sort(repr_ordered.begin(),repr_ordered.end(),RateRepresentationPredicate()); nuclear@0: nuclear@0: BOOST_FOREACH(const IfcRepresentation* repr, repr_ordered) { nuclear@0: bool res = false; nuclear@0: BOOST_FOREACH(const IfcRepresentationItem& item, repr->Items) { nuclear@0: if(const IfcMappedItem* const geo = item.ToPtr()) { nuclear@0: res = ProcessMappedItem(*geo,nd,subnodes,conv) || res; nuclear@0: } nuclear@0: else { nuclear@0: res = ProcessRepresentationItem(item,meshes,conv) || res; nuclear@0: } nuclear@0: } nuclear@0: // if we got something meaningful at this point, skip any further representations nuclear@0: if(res) { nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: AssignAddedMeshes(meshes,nd,conv); nuclear@0: } nuclear@0: nuclear@0: typedef std::map Metadata; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessMetadata(const ListOf< Lazy< IfcProperty >, 1, 0 >& set, ConversionData& conv, Metadata& properties, nuclear@0: const std::string& prefix = "", nuclear@0: unsigned int nest = 0) nuclear@0: { nuclear@0: BOOST_FOREACH(const IfcProperty& property, set) { nuclear@0: const std::string& key = prefix.length() > 0 ? (prefix + "." + property.Name) : property.Name; nuclear@0: if (const IfcPropertySingleValue* const singleValue = property.ToPtr()) { nuclear@0: if (singleValue->NominalValue) { nuclear@0: if (const EXPRESS::STRING* str = singleValue->NominalValue.Get()->ToPtr()) { nuclear@0: std::string value = static_cast(*str); nuclear@0: properties[key]=value; nuclear@0: } nuclear@0: else if (const EXPRESS::REAL* val = singleValue->NominalValue.Get()->ToPtr()) { nuclear@0: float value = static_cast(*val); nuclear@0: std::stringstream s; nuclear@0: s << value; nuclear@0: properties[key]=s.str(); nuclear@0: } nuclear@0: else if (const EXPRESS::INTEGER* val = singleValue->NominalValue.Get()->ToPtr()) { nuclear@0: int64_t value = static_cast(*val); nuclear@0: std::stringstream s; nuclear@0: s << value; nuclear@0: properties[key]=s.str(); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: else if (const IfcPropertyListValue* const listValue = property.ToPtr()) { nuclear@0: std::stringstream ss; nuclear@0: ss << "["; nuclear@0: unsigned index=0; nuclear@0: BOOST_FOREACH(const IfcValue::Out& v, listValue->ListValues) { nuclear@0: if (!v) continue; nuclear@0: if (const EXPRESS::STRING* str = v->ToPtr()) { nuclear@0: std::string value = static_cast(*str); nuclear@0: ss << "'" << value << "'"; nuclear@0: } nuclear@0: else if (const EXPRESS::REAL* val = v->ToPtr()) { nuclear@0: float value = static_cast(*val); nuclear@0: ss << value; nuclear@0: } nuclear@0: else if (const EXPRESS::INTEGER* val = v->ToPtr()) { nuclear@0: int64_t value = static_cast(*val); nuclear@0: ss << value; nuclear@0: } nuclear@0: if (index+1ListValues.size()) { nuclear@0: ss << ","; nuclear@0: } nuclear@0: index++; nuclear@0: } nuclear@0: ss << "]"; nuclear@0: properties[key]=ss.str(); nuclear@0: } nuclear@0: else if (const IfcComplexProperty* const complexProp = property.ToPtr()) { nuclear@0: if(nest > 2) { // mostly arbitrary limit to prevent stack overflow vulnerabilities nuclear@0: IFCImporter::LogError("maximum nesting level for IfcComplexProperty reached, skipping this property."); nuclear@0: } nuclear@0: else { nuclear@0: ProcessMetadata(complexProp->HasProperties, conv, properties, key, nest + 1); nuclear@0: } nuclear@0: } nuclear@0: else { nuclear@0: properties[key]=""; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessMetadata(uint64_t relDefinesByPropertiesID, ConversionData& conv, Metadata& properties) nuclear@0: { nuclear@0: if (const IfcRelDefinesByProperties* const pset = conv.db.GetObject(relDefinesByPropertiesID)->ToPtr()) { nuclear@0: if (const IfcPropertySet* const set = conv.db.GetObject(pset->RelatingPropertyDefinition->GetID())->ToPtr()) { nuclear@0: ProcessMetadata(set->HasProperties, conv, properties); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: aiNode* ProcessSpatialStructure(aiNode* parent, const IfcProduct& el, ConversionData& conv, std::vector* collect_openings = NULL) nuclear@0: { nuclear@0: const STEP::DB::RefMap& refs = conv.db.GetRefs(); nuclear@0: nuclear@0: // skip over space and annotation nodes - usually, these have no meaning in Assimp's context nuclear@0: if(conv.settings.skipSpaceRepresentations) { nuclear@0: if(const IfcSpace* const space = el.ToPtr()) { nuclear@0: IFCImporter::LogDebug("skipping IfcSpace entity due to importer settings"); nuclear@0: return NULL; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(conv.settings.skipAnnotations) { nuclear@0: if(const IfcAnnotation* const ann = el.ToPtr()) { nuclear@0: IFCImporter::LogDebug("skipping IfcAnnotation entity due to importer settings"); nuclear@0: return NULL; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // add an output node for this spatial structure nuclear@0: std::auto_ptr nd(new aiNode()); nuclear@0: nd->mName.Set(el.GetClassName()+"_"+(el.Name?el.Name.Get():"Unnamed")+"_"+el.GlobalId); nuclear@0: nd->mParent = parent; nuclear@0: nuclear@0: conv.already_processed.insert(el.GetID()); nuclear@0: nuclear@0: // check for node metadata nuclear@0: STEP::DB::RefMapRange children = refs.equal_range(el.GetID()); nuclear@0: if (children.first!=refs.end()) { nuclear@0: Metadata properties; nuclear@0: if (children.first==children.second) { nuclear@0: // handles single property set nuclear@0: ProcessMetadata((*children.first).second, conv, properties); nuclear@0: } nuclear@0: else { nuclear@0: // handles multiple property sets (currently all property sets are merged, nuclear@0: // which may not be the best solution in the long run) nuclear@0: for (STEP::DB::RefMap::const_iterator it=children.first; it!=children.second; ++it) { nuclear@0: ProcessMetadata((*it).second, conv, properties); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if (!properties.empty()) { nuclear@0: aiMetadata* data = new aiMetadata(); nuclear@0: data->mNumProperties = properties.size(); nuclear@0: data->mKeys = new aiString*[data->mNumProperties](); nuclear@0: data->mValues = new aiString*[data->mNumProperties](); nuclear@0: nuclear@0: unsigned int i = 0; nuclear@0: BOOST_FOREACH(const Metadata::value_type& kv, properties) { nuclear@0: data->mKeys[i] = new aiString(kv.first); nuclear@0: if (kv.second.length() > 0) { nuclear@0: data->mValues[i] = new aiString(kv.second); nuclear@0: } nuclear@0: ++i; nuclear@0: } nuclear@0: nd->mMetaData = data; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(el.ObjectPlacement) { nuclear@0: ResolveObjectPlacement(nd->mTransformation,el.ObjectPlacement.Get(),conv); nuclear@0: } nuclear@0: nuclear@0: std::vector openings; nuclear@0: nuclear@0: IfcMatrix4 myInv; nuclear@0: bool didinv = false; nuclear@0: nuclear@0: // convert everything contained directly within this structure, nuclear@0: // this may result in more nodes. nuclear@0: std::vector< aiNode* > subnodes; nuclear@0: try { nuclear@0: // locate aggregates and 'contained-in-here'-elements of this spatial structure and add them in recursively nuclear@0: // on our way, collect openings in *this* element nuclear@0: STEP::DB::RefMapRange range = refs.equal_range(el.GetID()); nuclear@0: nuclear@0: for(STEP::DB::RefMapRange range2 = range; range2.first != range.second; ++range2.first) { nuclear@0: // skip over meshes that have already been processed before. This is strictly necessary nuclear@0: // because the reverse indices also include references contained in argument lists and nuclear@0: // therefore every element has a back-reference hold by its parent. nuclear@0: if (conv.already_processed.find((*range2.first).second) != conv.already_processed.end()) { nuclear@0: continue; nuclear@0: } nuclear@0: const STEP::LazyObject& obj = conv.db.MustGetObject((*range2.first).second); nuclear@0: nuclear@0: // handle regularly-contained elements nuclear@0: if(const IfcRelContainedInSpatialStructure* const cont = obj->ToPtr()) { nuclear@0: if(cont->RelatingStructure->GetID() != el.GetID()) { nuclear@0: continue; nuclear@0: } nuclear@0: BOOST_FOREACH(const IfcProduct& pro, cont->RelatedElements) { nuclear@0: if(const IfcOpeningElement* const open = pro.ToPtr()) { nuclear@0: // IfcOpeningElement is handled below. Sadly we can't use it here as is: nuclear@0: // The docs say that opening elements are USUALLY attached to building storey, nuclear@0: // but we want them for the building elements to which they belong. nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: aiNode* const ndnew = ProcessSpatialStructure(nd.get(),pro,conv,NULL); nuclear@0: if(ndnew) { nuclear@0: subnodes.push_back( ndnew ); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: // handle openings, which we collect in a list rather than adding them to the node graph nuclear@0: else if(const IfcRelVoidsElement* const fills = obj->ToPtr()) { nuclear@0: if(fills->RelatingBuildingElement->GetID() == el.GetID()) { nuclear@0: const IfcFeatureElementSubtraction& open = fills->RelatedOpeningElement; nuclear@0: nuclear@0: // move opening elements to a separate node since they are semantically different than elements that are just 'contained' nuclear@0: std::auto_ptr nd_aggr(new aiNode()); nuclear@0: nd_aggr->mName.Set("$RelVoidsElement"); nuclear@0: nd_aggr->mParent = nd.get(); nuclear@0: nuclear@0: nd_aggr->mTransformation = nd->mTransformation; nuclear@0: nuclear@0: std::vector openings_local; nuclear@0: aiNode* const ndnew = ProcessSpatialStructure( nd_aggr.get(),open, conv,&openings_local); nuclear@0: if (ndnew) { nuclear@0: nuclear@0: nd_aggr->mNumChildren = 1; nuclear@0: nd_aggr->mChildren = new aiNode*[1](); nuclear@0: nuclear@0: nuclear@0: nd_aggr->mChildren[0] = ndnew; nuclear@0: nuclear@0: if(openings_local.size()) { nuclear@0: if (!didinv) { nuclear@0: myInv = aiMatrix4x4(nd->mTransformation ).Inverse(); nuclear@0: didinv = true; nuclear@0: } nuclear@0: nuclear@0: // we need all openings to be in the local space of *this* node, so transform them nuclear@0: BOOST_FOREACH(TempOpening& op,openings_local) { nuclear@0: op.Transform( myInv*nd_aggr->mChildren[0]->mTransformation); nuclear@0: openings.push_back(op); nuclear@0: } nuclear@0: } nuclear@0: subnodes.push_back( nd_aggr.release() ); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: for(;range.first != range.second; ++range.first) { nuclear@0: // see note in loop above nuclear@0: if (conv.already_processed.find((*range.first).second) != conv.already_processed.end()) { nuclear@0: continue; nuclear@0: } nuclear@0: if(const IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr()) { nuclear@0: if(aggr->RelatingObject->GetID() != el.GetID()) { nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: // move aggregate elements to a separate node since they are semantically different than elements that are just 'contained' nuclear@0: std::auto_ptr nd_aggr(new aiNode()); nuclear@0: nd_aggr->mName.Set("$RelAggregates"); nuclear@0: nd_aggr->mParent = nd.get(); nuclear@0: nuclear@0: nd_aggr->mTransformation = nd->mTransformation; nuclear@0: nuclear@0: nd_aggr->mChildren = new aiNode*[aggr->RelatedObjects.size()](); nuclear@0: BOOST_FOREACH(const IfcObjectDefinition& def, aggr->RelatedObjects) { nuclear@0: if(const IfcProduct* const prod = def.ToPtr()) { nuclear@0: nuclear@0: aiNode* const ndnew = ProcessSpatialStructure(nd_aggr.get(),*prod,conv,NULL); nuclear@0: if(ndnew) { nuclear@0: nd_aggr->mChildren[nd_aggr->mNumChildren++] = ndnew; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: subnodes.push_back( nd_aggr.release() ); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: conv.collect_openings = collect_openings; nuclear@0: if(!conv.collect_openings) { nuclear@0: conv.apply_openings = &openings; nuclear@0: } nuclear@0: nuclear@0: ProcessProductRepresentation(el,nd.get(),subnodes,conv); nuclear@0: conv.apply_openings = conv.collect_openings = NULL; nuclear@0: nuclear@0: if (subnodes.size()) { nuclear@0: nd->mChildren = new aiNode*[subnodes.size()](); nuclear@0: BOOST_FOREACH(aiNode* nd2, subnodes) { nuclear@0: nd->mChildren[nd->mNumChildren++] = nd2; nuclear@0: nd2->mParent = nd.get(); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: catch(...) { nuclear@0: // it hurts, but I don't want to pull boost::ptr_vector into -noboost only for these few spots here nuclear@0: std::for_each(subnodes.begin(),subnodes.end(),delete_fun()); nuclear@0: throw; nuclear@0: } nuclear@0: nuclear@0: ai_assert(conv.already_processed.find(el.GetID()) != conv.already_processed.end()); nuclear@0: conv.already_processed.erase(conv.already_processed.find(el.GetID())); nuclear@0: return nd.release(); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessSpatialStructures(ConversionData& conv) nuclear@0: { nuclear@0: // XXX add support for multiple sites (i.e. IfcSpatialStructureElements with composition == COMPLEX) nuclear@0: nuclear@0: nuclear@0: // process all products in the file. it is reasonable to assume that a nuclear@0: // file that is relevant for us contains at least a site or a building. nuclear@0: const STEP::DB::ObjectMapByType& map = conv.db.GetObjectsByType(); nuclear@0: nuclear@0: ai_assert(map.find("ifcsite") != map.end()); nuclear@0: const STEP::DB::ObjectSet* range = &map.find("ifcsite")->second; nuclear@0: nuclear@0: if (range->empty()) { nuclear@0: ai_assert(map.find("ifcbuilding") != map.end()); nuclear@0: range = &map.find("ifcbuilding")->second; nuclear@0: if (range->empty()) { nuclear@0: // no site, no building - fail; nuclear@0: IFCImporter::ThrowException("no root element found (expected IfcBuilding or preferably IfcSite)"); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: BOOST_FOREACH(const STEP::LazyObject* lz, *range) { nuclear@0: const IfcSpatialStructureElement* const prod = lz->ToPtr(); nuclear@0: if(!prod) { nuclear@0: continue; nuclear@0: } nuclear@0: IFCImporter::LogDebug("looking at spatial structure `" + (prod->Name ? prod->Name.Get() : "unnamed") + "`" + (prod->ObjectType? " which is of type " + prod->ObjectType.Get():"")); nuclear@0: nuclear@0: // the primary site is referenced by an IFCRELAGGREGATES element which assigns it to the IFCPRODUCT nuclear@0: const STEP::DB::RefMap& refs = conv.db.GetRefs(); nuclear@0: STEP::DB::RefMapRange range = refs.equal_range(conv.proj.GetID()); nuclear@0: for(;range.first != range.second; ++range.first) { nuclear@0: if(const IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr()) { nuclear@0: nuclear@0: BOOST_FOREACH(const IfcObjectDefinition& def, aggr->RelatedObjects) { nuclear@0: // comparing pointer values is not sufficient, we would need to cast them to the same type first nuclear@0: // as there is multiple inheritance in the game. nuclear@0: if (def.GetID() == prod->GetID()) { nuclear@0: IFCImporter::LogDebug("selecting this spatial structure as root structure"); nuclear@0: // got it, this is the primary site. nuclear@0: conv.out->mRootNode = ProcessSpatialStructure(NULL,*prod,conv,NULL); nuclear@0: return; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: IFCImporter::LogWarn("failed to determine primary site element, taking the first IfcSite"); nuclear@0: BOOST_FOREACH(const STEP::LazyObject* lz, *range) { nuclear@0: const IfcSpatialStructureElement* const prod = lz->ToPtr(); nuclear@0: if(!prod) { nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: conv.out->mRootNode = ProcessSpatialStructure(NULL,*prod,conv,NULL); nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: IFCImporter::ThrowException("failed to determine primary site element"); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void MakeTreeRelative(aiNode* start, const aiMatrix4x4& combined) nuclear@0: { nuclear@0: // combined is the parent's absolute transformation matrix nuclear@0: const aiMatrix4x4 old = start->mTransformation; nuclear@0: nuclear@0: if (!combined.IsIdentity()) { nuclear@0: start->mTransformation = aiMatrix4x4(combined).Inverse() * start->mTransformation; nuclear@0: } nuclear@0: nuclear@0: // All nodes store absolute transformations right now, so we need to make them relative nuclear@0: for (unsigned int i = 0; i < start->mNumChildren; ++i) { nuclear@0: MakeTreeRelative(start->mChildren[i],old); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void MakeTreeRelative(ConversionData& conv) nuclear@0: { nuclear@0: MakeTreeRelative(conv.out->mRootNode,IfcMatrix4()); nuclear@0: } nuclear@0: nuclear@0: } // !anon nuclear@0: nuclear@0: nuclear@0: nuclear@0: #endif