2015-05-19 03:48:29 +00:00
/*
Open Asset Import Library ( assimp )
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2016-01-01 20:07:24 +00:00
Copyright ( c ) 2006 - 2016 , assimp team
2015-05-19 03:48:29 +00:00
All rights reserved .
2015-05-19 03:52:10 +00:00
Redistribution and use of this software in source and binary forms ,
with or without modification , are permitted provided that the
2015-05-19 03:48:29 +00:00
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 .
2015-05-19 03:52:10 +00:00
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
" AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
2015-05-19 03:48:29 +00:00
LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2015-05-19 03:52:10 +00:00
A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
2015-05-19 03:48:29 +00:00
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
2015-05-19 03:52:10 +00:00
SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT
2015-05-19 03:48:29 +00:00
LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
2015-05-19 03:52:10 +00:00
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
2015-05-19 03:48:29 +00:00
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>
2016-04-05 22:17:21 +00:00
# include <tuple>
2015-05-19 03:48:29 +00:00
# ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC
2016-06-06 20:04:29 +00:00
# include <contrib / unzip / unzip.h>
2015-05-19 03:48:29 +00:00
# endif
# include "IFCLoader.h"
# include "STEPFileReader.h"
# include "IFCUtil.h"
# include "StreamReader.h"
# include "MemoryIOWrapper.h"
2016-06-06 20:04:29 +00:00
# include <assimp/scene.h>
# include <assimp/Importer.hpp>
2015-05-19 03:48:29 +00:00
namespace Assimp {
2015-05-19 03:57:13 +00:00
template < > const std : : string LogFunctions < IFCImporter > : : log_prefix = " IFC: " ;
2015-05-19 03:48:29 +00:00
}
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 EXPRESS : : DataType & dt , ConversionData & conv ) ;
} // anon
static const aiImporterDesc desc = {
2015-05-19 03:57:13 +00:00
" Industry Foundation Classes (IFC) Importer " ,
" " ,
" " ,
" " ,
aiImporterFlags_SupportBinaryFlavour ,
0 ,
0 ,
0 ,
0 ,
" ifc ifczip "
2015-05-19 03:48:29 +00:00
} ;
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
IFCImporter : : IFCImporter ( )
{ }
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
// Destructor, private as well
2015-05-19 03:48:29 +00:00
IFCImporter : : ~ IFCImporter ( )
{
}
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
// Returns whether the class can handle the format of the given file.
2015-05-19 03:48:29 +00:00
bool IFCImporter : : CanRead ( const std : : string & pFile , IOSystem * pIOHandler , bool checkSig ) const
{
2015-05-19 03:57:13 +00:00
const std : : string & extension = GetExtension ( pFile ) ;
if ( extension = = " ifc " | | extension = = " ifczip " ) {
return true ;
}
else if ( ( ! extension . length ( ) | | checkSig ) & & pIOHandler ) {
// 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.
const char * tokens [ ] = { " ISO-10303-21 " } ;
return SearchFileHeaderForToken ( pIOHandler , pFile , tokens , 1 ) ;
}
return false ;
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
// List all extensions handled by this loader
const aiImporterDesc * IFCImporter : : GetInfo ( ) const
{
2015-05-19 03:57:13 +00:00
return & desc ;
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
// Setup configuration properties for the loader
void IFCImporter : : SetupProperties ( const Importer * pImp )
{
2015-05-19 03:57:13 +00:00
settings . skipSpaceRepresentations = pImp - > GetPropertyBool ( AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS , true ) ;
settings . skipCurveRepresentations = pImp - > GetPropertyBool ( AI_CONFIG_IMPORT_IFC_SKIP_CURVE_REPRESENTATIONS , true ) ;
settings . useCustomTriangulation = pImp - > GetPropertyBool ( AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION , true ) ;
2015-05-19 03:48:29 +00:00
2015-05-19 03:57:13 +00:00
settings . conicSamplingAngle = 10.f ;
settings . skipAnnotations = true ;
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
// Imports the given file into the given scene structure.
void IFCImporter : : InternReadFile ( const std : : string & pFile ,
2015-05-19 03:57:13 +00:00
aiScene * pScene , IOSystem * pIOHandler )
2015-05-19 03:48:29 +00:00
{
2016-04-05 21:23:53 +00:00
std : : shared_ptr < IOStream > stream ( pIOHandler - > Open ( pFile ) ) ;
2015-05-19 03:57:13 +00:00
if ( ! stream ) {
ThrowException ( " Could not open file for reading " ) ;
}
2015-05-19 03:48:29 +00:00
2015-05-19 03:57:13 +00:00
// if this is a ifczip file, decompress its contents first
if ( GetExtension ( pFile ) = = " ifczip " ) {
2015-05-19 03:48:29 +00:00
# ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC
2015-05-19 03:57:13 +00:00
unzFile zip = unzOpen ( pFile . c_str ( ) ) ;
if ( zip = = NULL ) {
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 ) ;
const int ret = unzReadCurrentFile ( zip , buff , fileInfo . uncompressed_size ) ;
size_t filesize = fileInfo . uncompressed_size ;
if ( ret < 0 | | size_t ( ret ) ! = filesize )
{
delete [ ] buff ;
ThrowException ( " Failed to decompress IFC ZIP file " ) ;
}
unzCloseCurrentFile ( zip ) ;
stream . reset ( new MemoryIOStream ( buff , fileInfo . uncompressed_size , true ) ) ;
break ;
if ( unzGoToNextFile ( zip ) = = UNZ_END_OF_LIST_OF_FILE ) {
ThrowException ( " Found no IFC file member in IFCZIP file (1) " ) ;
}
} while ( true ) ;
}
else {
ThrowException ( " Found no IFC file member in IFCZIP file (2) " ) ;
}
unzClose ( zip ) ;
2015-05-19 03:48:29 +00:00
# else
2015-05-19 03:57:13 +00:00
ThrowException ( " Could not open ifczip file for reading, assimp was built without ifczip support " ) ;
2015-05-19 03:48:29 +00:00
# endif
2015-05-19 03:57:13 +00:00
}
2016-04-05 21:23:53 +00:00
std : : unique_ptr < STEP : : DB > db ( STEP : : ReadFileHeader ( stream ) ) ;
2015-05-19 03:57:13 +00:00
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
EXPRESS : : ConversionSchema schema ;
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 < 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 ( ( Formatter : : format ( ) , " STEP: evaluated " , db - > GetEvaluatedObjectCount ( ) , " object records " ) ) ;
}
2015-05-19 03:48:29 +00:00
}
namespace {
// ------------------------------------------------------------------------------------------------
void ConvertUnit ( const IfcNamedUnit & unit , ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
if ( const IfcSIUnit * const si = unit . ToPtr < 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 IfcConversionBasedUnit * const convu = unit . ToPtr < IfcConversionBasedUnit > ( ) ) {
if ( convu - > UnitType = = " PLANEANGLEUNIT " ) {
try {
conv . angle_scale = convu - > ConversionFactor - > ValueComponent - > To < 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 " ) ;
}
}
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
void ConvertUnit ( const EXPRESS : : DataType & dt , ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
try {
const EXPRESS : : ENTITY & e = dt . To < ENTITY > ( ) ;
const IfcNamedUnit & unit = e . ResolveSelect < 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 " ) ;
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
void SetUnits ( ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
// 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 ) ;
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
void SetCoordinateSpace ( ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
const IfcRepresentationContext * fav = NULL ;
2016-04-05 20:53:54 +00:00
for ( const IfcRepresentationContext & v : conv . proj . RepresentationContexts ) {
2015-05-19 03:57:13 +00:00
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 IfcGeometricRepresentationContext * const geo = fav - > ToPtr < IfcGeometricRepresentationContext > ( ) ) {
ConvertAxisPlacement ( conv . wcs , * geo - > WorldCoordinateSystem , conv ) ;
IFCImporter : : LogDebug ( " got world coordinate system " ) ;
}
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
void ResolveObjectPlacement ( aiMatrix4x4 & m , const IfcObjectPlacement & place , ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
if ( const IfcLocalPlacement * const local = place . ToPtr < IfcLocalPlacement > ( ) ) {
IfcMatrix4 tmp ;
ConvertAxisPlacement ( tmp , * local - > RelativePlacement , conv ) ;
m = static_cast < aiMatrix4x4 > ( tmp ) ;
if ( local - > PlacementRelTo ) {
aiMatrix4x4 tmp ;
ResolveObjectPlacement ( tmp , local - > PlacementRelTo . Get ( ) , conv ) ;
m = tmp * m ;
}
}
else {
IFCImporter : : LogWarn ( " skipping unknown IfcObjectPlacement entity, type is " + place . GetClassName ( ) ) ;
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
bool ProcessMappedItem ( const IfcMappedItem & mapped , aiNode * nd_src , std : : vector < aiNode * > & subnodes_src , unsigned int matid , ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
// insert a custom node here, the cartesian transform operator is simply a conventional transformation matrix
2016-04-05 20:56:11 +00:00
std : : unique_ptr < aiNode > nd ( new aiNode ( ) ) ;
2015-05-19 03:57:13 +00:00
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 : : vector < 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 ( ) ;
2016-04-05 20:53:54 +00:00
for ( TempOpening & open : * conv . apply_openings ) {
2015-05-19 03:57:13 +00:00
open . Transform ( minv ) ;
}
}
unsigned int localmatid = ProcessMaterials ( mapped . GetID ( ) , matid , conv , false ) ;
const IfcRepresentation & repr = mapped . MappingSource - > MappedRepresentation ;
bool got = false ;
2016-04-05 20:53:54 +00:00
for ( const IfcRepresentationItem & item : repr . Items ) {
2015-05-19 03:57:13 +00:00
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 ;
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
struct RateRepresentationPredicate {
2015-05-19 03:57:13 +00:00
int Rate ( const 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 IfcMappedItem * const m = r - > Items . front ( ) - > ToPtr < 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 IfcRepresentation * a , const IfcRepresentation * b ) const {
return Rate ( a ) < Rate ( b ) ;
}
2015-05-19 03:48:29 +00:00
} ;
// ------------------------------------------------------------------------------------------------
void ProcessProductRepresentation ( const IfcProduct & el , aiNode * nd , std : : vector < aiNode * > & subnodes , ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
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 : : vector < 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 < IfcRepresentation > , 1 , 0 > & src = el . Representation . Get ( ) - > Representations ;
std : : vector < const IfcRepresentation * > repr_ordered ( src . size ( ) ) ;
std : : copy ( src . begin ( ) , src . end ( ) , repr_ordered . begin ( ) ) ;
std : : sort ( repr_ordered . begin ( ) , repr_ordered . end ( ) , RateRepresentationPredicate ( ) ) ;
2016-04-05 20:53:54 +00:00
for ( const IfcRepresentation * repr : repr_ordered ) {
2015-05-19 03:57:13 +00:00
bool res = false ;
2016-04-05 20:53:54 +00:00
for ( const IfcRepresentationItem & item : repr - > Items ) {
2015-05-19 03:57:13 +00:00
if ( const IfcMappedItem * const geo = item . ToPtr < 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 ) ;
2015-05-19 03:48:29 +00:00
}
typedef std : : map < std : : string , std : : string > Metadata ;
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
void ProcessMetadata ( const ListOf < Lazy < IfcProperty > , 1 , 0 > & set , ConversionData & conv , Metadata & properties ,
2015-05-19 03:57:13 +00:00
const std : : string & prefix = " " ,
unsigned int nest = 0 )
2015-05-19 03:48:29 +00:00
{
2016-04-05 20:53:54 +00:00
for ( const IfcProperty & property : set ) {
2015-05-19 03:57:13 +00:00
const std : : string & key = prefix . length ( ) > 0 ? ( prefix + " . " + property . Name ) : property . Name ;
if ( const IfcPropertySingleValue * const singleValue = property . ToPtr < IfcPropertySingleValue > ( ) ) {
if ( singleValue - > NominalValue ) {
if ( const EXPRESS : : STRING * str = singleValue - > NominalValue . Get ( ) - > ToPtr < EXPRESS : : STRING > ( ) ) {
std : : string value = static_cast < std : : string > ( * str ) ;
properties [ key ] = value ;
}
else if ( const EXPRESS : : REAL * val = singleValue - > NominalValue . Get ( ) - > ToPtr < EXPRESS : : REAL > ( ) ) {
float value = static_cast < float > ( * val ) ;
std : : stringstream s ;
s < < value ;
properties [ key ] = s . str ( ) ;
}
else if ( const EXPRESS : : INTEGER * val = singleValue - > NominalValue . Get ( ) - > ToPtr < EXPRESS : : INTEGER > ( ) ) {
int64_t value = static_cast < int64_t > ( * val ) ;
std : : stringstream s ;
s < < value ;
properties [ key ] = s . str ( ) ;
}
}
}
else if ( const IfcPropertyListValue * const listValue = property . ToPtr < IfcPropertyListValue > ( ) ) {
std : : stringstream ss ;
ss < < " [ " ;
unsigned index = 0 ;
2016-04-05 20:53:54 +00:00
for ( const IfcValue : : Out & v : listValue - > ListValues ) {
2015-05-19 03:57:13 +00:00
if ( ! v ) continue ;
if ( const EXPRESS : : STRING * str = v - > ToPtr < EXPRESS : : STRING > ( ) ) {
std : : string value = static_cast < std : : string > ( * str ) ;
ss < < " ' " < < value < < " ' " ;
}
else if ( const EXPRESS : : REAL * val = v - > ToPtr < EXPRESS : : REAL > ( ) ) {
float value = static_cast < float > ( * val ) ;
ss < < value ;
}
else if ( const EXPRESS : : INTEGER * val = v - > ToPtr < EXPRESS : : INTEGER > ( ) ) {
int64_t value = static_cast < int64_t > ( * val ) ;
ss < < value ;
}
if ( index + 1 < listValue - > ListValues . size ( ) ) {
ss < < " , " ;
}
index + + ;
}
ss < < " ] " ;
properties [ key ] = ss . str ( ) ;
}
else if ( const IfcComplexProperty * const complexProp = property . ToPtr < 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 ] = " " ;
}
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
2015-05-19 03:52:10 +00:00
void ProcessMetadata ( uint64_t relDefinesByPropertiesID , ConversionData & conv , Metadata & properties )
2015-05-19 03:48:29 +00:00
{
2015-05-19 03:57:13 +00:00
if ( const IfcRelDefinesByProperties * const pset = conv . db . GetObject ( relDefinesByPropertiesID ) - > ToPtr < IfcRelDefinesByProperties > ( ) ) {
if ( const IfcPropertySet * const set = conv . db . GetObject ( pset - > RelatingPropertyDefinition - > GetID ( ) ) - > ToPtr < IfcPropertySet > ( ) ) {
ProcessMetadata ( set - > HasProperties , conv , properties ) ;
}
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
aiNode * ProcessSpatialStructure ( aiNode * parent , const IfcProduct & el , ConversionData & conv , std : : vector < TempOpening > * collect_openings = NULL )
{
2015-05-19 03:57:13 +00:00
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 < IfcSpace > ( ) ) {
IFCImporter : : LogDebug ( " skipping IfcSpace entity due to importer settings " ) ;
skipGeometry = true ;
}
}
if ( conv . settings . skipAnnotations ) {
if ( el . ToPtr < IfcAnnotation > ( ) ) {
IFCImporter : : LogDebug ( " skipping IfcAnnotation entity due to importer settings " ) ;
return NULL ;
}
}
// add an output node for this spatial structure
2016-04-05 20:56:11 +00:00
std : : unique_ptr < aiNode > nd ( new aiNode ( ) ) ;
2015-05-19 03:57:13 +00:00
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 = new aiMetadata ( ) ;
2016-11-19 14:13:55 +00:00
data - > mNumProperties = static_cast < unsigned int > ( properties . size ( ) ) ;
2015-05-19 03:57:13 +00:00
data - > mKeys = new aiString [ data - > mNumProperties ] ( ) ;
data - > mValues = new aiMetadataEntry [ data - > mNumProperties ] ( ) ;
unsigned int index = 0 ;
2016-04-05 20:53:54 +00:00
for ( const Metadata : : value_type & kv : properties )
2015-05-19 03:57:13 +00:00
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 IfcRelContainedInSpatialStructure * const cont = obj - > ToPtr < IfcRelContainedInSpatialStructure > ( ) ) {
if ( cont - > RelatingStructure - > GetID ( ) ! = el . GetID ( ) ) {
continue ;
}
2016-04-05 20:53:54 +00:00
for ( const IfcProduct & pro : cont - > RelatedElements ) {
2015-05-19 03:57:13 +00:00
if ( pro . ToPtr < 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 . get ( ) , pro , conv , NULL ) ;
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 IfcRelVoidsElement * const fills = obj - > ToPtr < IfcRelVoidsElement > ( ) ) {
if ( fills - > RelatingBuildingElement - > GetID ( ) = = el . GetID ( ) ) {
const IfcFeatureElementSubtraction & open = fills - > RelatedOpeningElement ;
// move opening elements to a separate node since they are semantically different than elements that are just 'contained'
2016-04-05 20:56:11 +00:00
std : : unique_ptr < aiNode > nd_aggr ( new aiNode ( ) ) ;
2015-05-19 03:57:13 +00:00
nd_aggr - > mName . Set ( " $RelVoidsElement " ) ;
nd_aggr - > mParent = nd . get ( ) ;
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
2016-04-05 20:53:54 +00:00
for ( TempOpening & op : openings_local ) {
2015-05-19 03:57:13 +00:00
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 IfcRelAggregates * const aggr = conv . db . GetObject ( ( * range . first ) . second ) - > ToPtr < 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'
2016-04-05 20:56:11 +00:00
std : : unique_ptr < aiNode > nd_aggr ( new aiNode ( ) ) ;
2015-05-19 03:57:13 +00:00
nd_aggr - > mName . Set ( " $RelAggregates " ) ;
nd_aggr - > mParent = nd . get ( ) ;
nd_aggr - > mTransformation = nd - > mTransformation ;
nd_aggr - > mChildren = new aiNode * [ aggr - > RelatedObjects . size ( ) ] ( ) ;
2016-04-05 20:53:54 +00:00
for ( const IfcObjectDefinition & def : aggr - > RelatedObjects ) {
2015-05-19 03:57:13 +00:00
if ( const IfcProduct * const prod = def . ToPtr < IfcProduct > ( ) ) {
aiNode * const ndnew = ProcessSpatialStructure ( nd_aggr . get ( ) , * prod , conv , NULL ) ;
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 . get ( ) , subnodes , conv ) ;
conv . apply_openings = conv . collect_openings = NULL ;
}
if ( subnodes . size ( ) ) {
nd - > mChildren = new aiNode * [ subnodes . size ( ) ] ( ) ;
2016-04-05 20:53:54 +00:00
for ( aiNode * nd2 : subnodes ) {
2015-05-19 03:57:13 +00:00
nd - > mChildren [ nd - > mNumChildren + + ] = nd2 ;
nd2 - > mParent = nd . get ( ) ;
}
}
}
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 . release ( ) ;
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
void ProcessSpatialStructures ( ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
// 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) " ) ;
}
}
2016-04-05 20:53:54 +00:00
for ( const STEP : : LazyObject * lz : * range ) {
2015-05-19 03:57:13 +00:00
const IfcSpatialStructureElement * const prod = lz - > ToPtr < IfcSpatialStructureElement > ( ) ;
if ( ! prod ) {
continue ;
}
IFCImporter : : LogDebug ( " looking at spatial structure ` " + ( prod - > Name ? prod - > Name . Get ( ) : " unnamed " ) + " ` " + ( prod - > ObjectType ? " which is of type " + prod - > ObjectType . Get ( ) : " " ) ) ;
// the primary site is referenced by an IFCRELAGGREGATES element which assigns it to the IFCPRODUCT
const STEP : : DB : : RefMap & refs = conv . db . GetRefs ( ) ;
STEP : : DB : : RefMapRange range = refs . equal_range ( conv . proj . GetID ( ) ) ;
for ( ; range . first ! = range . second ; + + range . first ) {
if ( const IfcRelAggregates * const aggr = conv . db . GetObject ( ( * range . first ) . second ) - > ToPtr < IfcRelAggregates > ( ) ) {
2016-04-05 20:53:54 +00:00
for ( const IfcObjectDefinition & def : aggr - > RelatedObjects ) {
2015-05-19 03:57:13 +00:00
// 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 : : LogDebug ( " selecting this spatial structure as root structure " ) ;
// got it, this is the primary site.
conv . out - > mRootNode = ProcessSpatialStructure ( NULL , * prod , conv , NULL ) ;
return ;
}
}
}
}
}
IFCImporter : : LogWarn ( " failed to determine primary site element, taking the first IfcSite " ) ;
2016-04-05 20:53:54 +00:00
for ( const STEP : : LazyObject * lz : * range ) {
2015-05-19 03:57:13 +00:00
const IfcSpatialStructureElement * const prod = lz - > ToPtr < IfcSpatialStructureElement > ( ) ;
if ( ! prod ) {
continue ;
}
conv . out - > mRootNode = ProcessSpatialStructure ( NULL , * prod , conv , NULL ) ;
return ;
}
IFCImporter : : ThrowException ( " failed to determine primary site element " ) ;
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
void MakeTreeRelative ( aiNode * start , const aiMatrix4x4 & combined )
{
2015-05-19 03:57:13 +00:00
// combined is the parent's absolute transformation matrix
const aiMatrix4x4 old = start - > mTransformation ;
2015-05-19 03:48:29 +00:00
2015-05-19 03:57:13 +00:00
if ( ! combined . IsIdentity ( ) ) {
start - > mTransformation = aiMatrix4x4 ( combined ) . Inverse ( ) * start - > mTransformation ;
}
2015-05-19 03:48:29 +00:00
2015-05-19 03:57:13 +00:00
// 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 ) ;
}
2015-05-19 03:48:29 +00:00
}
// ------------------------------------------------------------------------------------------------
void MakeTreeRelative ( ConversionData & conv )
{
2015-05-19 03:57:13 +00:00
MakeTreeRelative ( conv . out - > mRootNode , IfcMatrix4 ( ) ) ;
2015-05-19 03:48:29 +00:00
}
} // !anon
# endif