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