/******************************************************************************** * * * This file is part of IfcOpenShell. * * * * IfcOpenShell is free software: you can redistribute it and/or modify * * it under the terms of the Lesser GNU General Public License as published by * * the Free Software Foundation, either version 3.0 of the License, or * * (at your option) any later version. * * * * IfcOpenShell is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * Lesser GNU General Public License for more details. * * * * You should have received a copy of the Lesser GNU General Public License * * along with this program. If not, see . * * * ********************************************************************************/ #include "parse.h" #include "express.h" #include "character_decoder.h" #include "exception.h" #include "file.h" #include "logger.h" #include "schema.h" #include "si_prefix.h" #include "file_reader.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include #ifdef USE_MMAP #include #endif #define PERMISSIVE_FLOAT using namespace ifcopenshell; // A static locale for the real number parser. strtod() is locale-dependent, causing issues // in locales that have ',' as a decimal separator. Therefore the non standard _strtod_l() / // strtod_l() is used and a reference to the "C" locale is obtained here. The alternative is // to use std::istringstream::imbue(std::locale::classic()), but there are subtleties in // parsing in MSVC2010 and it appears to be much slower. #if defined(_MSC_VER) static _locale_t locale = (_locale_t)0; void init_locale() { if (locale == (_locale_t)0) { locale = _create_locale(LC_NUMERIC, "C"); } } #else #if defined(__MINGW64__) || defined(__MINGW32__) #include #include typedef void* locale_t; static locale_t locale = (locale_t)0; void init_locale() {} double strtod_l(const char* start, char** end, locale_t loc) { double d; std::stringstream ss; ss.imbue(std::locale::classic()); ss << start; ss >> d; size_t nread = ss.tellg(); *end = const_cast(start) + nread; return d; } #else #ifdef __APPLE__ #include #endif #include static locale_t locale = (locale_t)0; void init_locale() { if (locale == (locale_t)0) { locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); } } #endif #endif template spf_lexer::spf_lexer(Reader* stream_) { stream = stream_; decoder_ = new character_decoder(stream_); } template spf_lexer::~spf_lexer() { delete decoder_; } template size_t spf_lexer::skip_whitespace() const { size_t index = 0; while (!stream->eof()) { char character = stream->peek(); if ((character == ' ' || character == '\r' || character == '\n' || character == '\t')) { stream->increment(); ++index; } else { break; } } return index; } template size_t spf_lexer::skip_comment() const { if (stream->eof()) { return 0; } char character = stream->peek(); if (character != '/') { return 0; } stream->increment(); character = stream->peek(); if (character != '*') { stream->seek(stream->tell() - 1); return 0; } size_t index = 2; char intermediate = 0; while (!stream->eof()) { character = stream->peek(); stream->increment(); ++index; if (character == '/' && intermediate == '*') { break; } intermediate = character; } return index; } template std::string& spf_lexer::get_temp_string() const { const size_t idx = pool_index++; const size_t slice = idx >> 4; const size_t offset = idx & 0xF; while (stringpool_.size() <= slice) { stringpool_.push_back(std::make_unique>()); } // std::wcout << "Num contexts: " << idx << std::endl; return (*stringpool_[slice])[offset]; } namespace { bool parse_int_(const char* pStart, int& val) { char* pEnd; long result = strtol(pStart, &pEnd, 10); if (*pEnd != 0) { return false; } val = (int)result; return true; } bool parse_float_(const char* pStart, double& val) { char* pEnd; #ifdef _MSC_VER double result = _strtod_l(pStart, &pEnd, locale); #else double result = strtod_l(pStart, &pEnd, locale); #endif if (*pEnd != 0) { return false; } val = result; return true; } } // namespace namespace SWAR { constexpr uint32_t ONES32 = 0x01010101u; constexpr uint32_t HIGHS32 = 0x80808080u; constexpr uint64_t ONES = 0x0101010101010101ull; constexpr uint64_t HIGHS = 0x8080808080808080ull; constexpr uint64_t splat(unsigned char c) { return ONES * c; } inline uint32_t has_zero_byte(uint32_t x) { return (x - ONES32) & ~x & HIGHS32; } inline uint64_t has_zero_byte(uint64_t x) { return (x - ONES) & ~x & HIGHS; } inline uint32_t eq_mask(uint32_t x, uint32_t c) { return has_zero_byte(x ^ c); } inline uint64_t eq_mask(uint64_t x, uint64_t c) { return has_zero_byte(x ^ c); } namespace chars { constexpr uint64_t lpar = splat('('); constexpr uint64_t rpar = splat(')'); constexpr uint64_t eq = splat('='); constexpr uint64_t comma = splat(','); constexpr uint64_t semi = splat(';'); constexpr uint64_t slash = splat('/'); constexpr uint64_t space = splat(' '); constexpr uint64_t cr = splat('\r'); constexpr uint64_t lf = splat('\n'); constexpr uint64_t tab = splat('\t'); constexpr uint64_t quote = splat('"'); constexpr uint64_t dot = splat('.'); } // namespace chars template inline uint64_t has_special_char(uint64_t x) { return eq_mask(x, chars::lpar) | eq_mask(x, chars::rpar) | eq_mask(x, chars::eq) | eq_mask(x, chars::comma) | eq_mask(x, chars::semi) | eq_mask(x, chars::slash) | eq_mask(x, chars::space) | eq_mask(x, chars::cr) | eq_mask(x, chars::lf) | eq_mask(x, chars::tab) | eq_mask(x, chars::quote) | (IncludeDot ? eq_mask(x, chars::dot) : uint64_t{0}); } template inline uint32_t has_special_char(uint32_t x) { return eq_mask(x, static_cast(chars::lpar)) | eq_mask(x, static_cast(chars::rpar)) | eq_mask(x, static_cast(chars::eq)) | eq_mask(x, static_cast(chars::comma)) | eq_mask(x, static_cast(chars::semi)) | eq_mask(x, static_cast(chars::slash)) | eq_mask(x, static_cast(chars::space)) | eq_mask(x, static_cast(chars::cr)) | eq_mask(x, static_cast(chars::lf)) | eq_mask(x, static_cast(chars::tab)) | eq_mask(x, static_cast(chars::quote)) | (IncludeDot ? eq_mask(x, static_cast(chars::dot)) : uint32_t{0}); } } // // Returns the offset of the current token and moves cursor to next // template token spf_lexer::next() { if (stream->eof()) { return token{}; } auto pos = stream->tell(); char character = stream->read(); if (character == '/' || character == ' ' || character == '\r' || character == '\n' || character == '\t') { while ((skip_whitespace() != 0U) || (skip_comment() != 0U)) { } if (stream->eof()) { return token{}; } pos = stream->tell(); character = stream->read(); } // If the cursor is at [()=,;$*] we know token consists of single char if (character == '(' || character == ')' || character == '=' || character == ',' || character == ';' || character == '$' || character == '*') { return token(pos, character); } auto& str = get_temp_string(); if (character == '\'') { // If a string is encountered defer processing to the character_decoder str = *decoder_; return token(pos, token::Token_STRING, str); } else { auto ttype = token::Token_NONE; if (character == '"' || character == '.') { if (character == '"') { ttype = token::Token_BINARY; } else { ttype = token::Token_ENUMERATION; } str.clear(); } else if (character == '#') { ttype = token::Token_IDENTIFIER; str.clear(); } else { str.assign(&character, 1); } auto remaining = stream->remaining(); while (remaining) { if (remaining >= 8) { uint64_t x = stream->peek_u64(); if ((ttype == token::Token_NONE ? SWAR::has_special_char(x) : SWAR::has_special_char(x)) == 0) { str.append(reinterpret_cast(&x), 8); stream->increment(8); remaining -= 8; continue; } } if (remaining >= 4) { uint32_t x = stream->peek_u32(); if ((ttype == token::Token_NONE ? SWAR::has_special_char(x) : SWAR::has_special_char(x)) == 0) { str.append(reinterpret_cast(&x), 4); stream->increment(4); remaining -= 4; continue; } } // Read character and increment pointer if not starting a new token char character = stream->peek(); if (character == '(' || character == ')' || character == '=' || character == ',' || character == ';' || character == '/') { break; } if (!(character == ' ' || character == '\r' || character == '\n' || character == '\t')) { if ((ttype == token::Token_BINARY && character == '"') || (ttype == token::Token_ENUMERATION && character == '.')) { // Skip } else { str.push_back(character); } } stream->increment(); remaining -= 1; } if (ttype == token::Token_ENUMERATION && str.size() == 1 && (str[0] == 'T' || str[0] == 'F' || str[0] == 'U')) { pop_pool_entry(); return token(pos, token::Token_BOOL, str[0]); } else if (ttype == token::Token_IDENTIFIER) { int int_val; if (!parse_int_(str.c_str(), int_val)) { throw invalid_token_exception(pos, str, "instance name"); } pop_pool_entry(); return token(pos, ttype, int_val); } else if (ttype == token::Token_NONE && !str.empty()) { int int_val; double float_val; auto& first = str.front(); if ((first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z')) { ttype = token::Token_KEYWORD; return token(pos, ttype, str); } else if (parse_int_(str.c_str(), int_val)) { ttype = token::Token_INT; pop_pool_entry(); return token(pos, ttype, int_val); } else if (parse_float_(str.c_str(), float_val)) { ttype = token::Token_FLOAT; pop_pool_entry(); return token(pos, float_val); } } else if (ttype == token::Token_BINARY || ttype == token::Token_ENUMERATION) { return token(pos, ttype, str); } throw invalid_token_exception(pos, str, "valid token"); } } template class spf_lexer>; template class spf_lexer>; template class spf_lexer>; #ifdef USE_MMAP template class spf_lexer>; #endif bool token::is_operator() { return type == Token_OPERATOR; } bool token::is_operator(char character) { return type == Token_OPERATOR && value_char == character; } bool token::is_identifier() { return type == Token_IDENTIFIER; } bool token::is_string() { return type == Token_STRING; } bool token::is_enumeration() { // @nb this is a bit confusing? return type == Token_ENUMERATION || type == Token_BOOL; } bool token::is_binary() { return type == Token_BINARY; } bool token::is_keyword() { return type == Token_KEYWORD; } bool token::is_int() { return type == Token_INT; } bool token::is_bool() { // Bool and logical share the same storage type, just logical unknown is stored as 'U'. return type == Token_BOOL && value_char != 'U'; } bool token::is_logical() { return type == Token_BOOL; } bool token::is_float() { #ifdef PERMISSIVE_FLOAT /// NB: We are being more permissive here then allowed by the standard return type == Token_FLOAT || type == Token_INT; #else return type == Token_FLOAT; #endif } int token::as_int() { if (type != Token_INT) { throw invalid_token_exception(start_pos, to_string(), "integer"); } return value_int; } unsigned token::as_identifier() { if (type != Token_IDENTIFIER) { throw invalid_token_exception(start_pos, to_string(), "instance name"); } return (unsigned) value_int; } bool token::as_bool() { if (type != Token_BOOL) { throw invalid_token_exception(start_pos, to_string(), "boolean"); } return value_char == 'T'; } boost::logic::tribool token::as_logical() { if (type != Token_BOOL) { throw invalid_token_exception(start_pos, to_string(), "logical"); } if (value_int == 'F') { return false; } if (value_int == 'T') { return true; } return boost::logic::indeterminate; } double token::as_float() { #ifdef PERMISSIVE_FLOAT if (type == Token_INT) { /// NB: We are being more permissive here then allowed by the standard return value_int; } // ----> continues beyond preprocessor directive #endif if (type == Token_FLOAT) { return value_double; } throw invalid_token_exception(start_pos, to_string(), "real"); } const std::string& token::as_string() { if (is_string() || is_enumeration() || is_binary() || is_keyword()) { // @todo quotes return *value_string; } throw invalid_token_exception(start_pos, to_string(), "string"); } boost::dynamic_bitset<> token::as_binary() { const std::string& str = as_string(); if (str.empty()) { throw exception("token is not a valid binary sequence"); } std::string::const_iterator it = str.begin(); int n = *it - '0'; if ((n < 0 || n > 3) || (str.size() == 1 && n != 0)) { throw exception("token is not a valid binary sequence"); } ++it; unsigned i = (str.size() - 1) * 4 - n; boost::dynamic_bitset<> bitset(i); for (; it != str.end(); ++it) { const std::string::value_type& c = *it; int value = (c < 'A') ? (c - '0') : (c - 'A' + 10); for (unsigned j = 0; j < 4; ++j) { if (i-- == 0) { break; } if ((value & (1 << (3 - j))) != 0) { bitset.set(i); } } } return bitset; } std::string token::to_string() { std::string result; if (type == Token_OPERATOR || type == Token_BOOL) { result.push_back(value_char); } else if (type == Token_INT) { result = std::to_string(value_int); } else if (type == Token_FLOAT) { std::ostringstream oss; oss << std::setprecision(15) << value_double; result = oss.str(); } else { return as_string(); } return result; } // // Reads the arguments from a list of token // Aditionally, registers the ids (i.e. #[\d]+) in the inverse map // template void ifcopenshell::impl::in_memory_file_storage::load(ifcopenshell::spf_lexer* tokens, std::optional entity_instance_name, const ifcopenshell::entity* entity, parse_context& context, int attribute_index) { token next = tokens->next(); size_t attribute_index_within_data = 0; size_t return_value = 0; while (next) { if (next.is_operator(',')) { if (attribute_index == -1) { attribute_index_within_data += 1; } } else if (next.is_operator(')')) { break; } else if (next.is_operator('(')) { return_value++; load(tokens, entity_instance_name, entity, context.push(), attribute_index == -1 ? (int) attribute_index_within_data : attribute_index); } else { return_value++; if (next.is_identifier() && entity && entity_instance_name) { register_inverse(*entity_instance_name, entity, next.value_int, attribute_index == -1 ? (int) attribute_index_within_data : attribute_index); } if (next.is_keyword()) { try { const auto* decl = (schema ? schema : file->schema())->declaration_by_name(next.as_string()); parse_context ps(&context_pool_); tokens->next(); // The only case we know where a defined type contains entity // instance references is IfcPropertySetDefinitionSet. For // that purpose we propagate the entity_instance_name to // register inverses to the host entity (and not the defined // type) and to be able to actually register the references in // the 2nd pass. load(tokens, entity_instance_name, entity, ps, attribute_index == -1 ? (int)attribute_index_within_data : attribute_index); express::Base simple_type_instance(read_simple_type_instances.emplace_back( ps.construct(file, entity_instance_name, *references_to_resolve, decl, std::nullopt, attribute_index == -1 ? (int)attribute_index_within_data : attribute_index)) ); // @todo do we need express::Base here? Or should we just push instance_data? context.push(simple_type_instance); } catch (exception& e) { logger::message(logger::LOG_ERROR, std::string(e.what()) + " at offset " + std::to_string(next.start_pos)); // #4070 We didn't actually capture an aggregate entry, undo length increment. return_value--; } } else { context.push(next); } } next = tokens->next(); } } template void ifcopenshell::impl::in_memory_file_storage::try_read_semicolon(ifcopenshell::spf_lexer* tokens) const { auto old_offset = tokens->stream->tell(); token semilocon = tokens->next(); if (!semilocon.is_operator(';')) { tokens->stream->seek(old_offset); } } void ifcopenshell::impl::in_memory_file_storage::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) { // Assume a check on token type has already been performed byref_excl_[inst_id][{from_entity->index_in_schema(), attribute_index}].push_back(id_from); } void ifcopenshell::impl::in_memory_file_storage::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) { auto& ids = byref_excl_[inst.id()][{from_entity->index_in_schema(), attribute_index}]; auto iter = std::find(ids.begin(), ids.end(), id_from); if (iter == ids.end()) { // @todo inverses also need to be populated when multiple instances are added to a new file. // throw ifcopenshell::exception("Instance not found among inverses"); } else { ids.erase(iter); } } namespace { template std::string to_string_fixed_width(const T& t, size_t) { // @todo currently inactive std::ostringstream oss; oss << /*std::setfill('0') << std::setw(w) <<*/ t; return oss.str(); } } void ifcopenshell::impl::rocks_db_file_storage::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) { #ifdef IFOPSH_WITH_ROCKSDB static std::string s; uint32_t v = id_from; s.resize(sizeof(uint32_t)); memcpy(s.data(), &v, sizeof(uint32_t)); auto key = "v|" + to_string_fixed_width(inst_id, 10) + "|" + to_string_fixed_width(from_entity->index_in_schema(), 4) + "|" + to_string_fixed_width(attribute_index, 2); db->Merge(wopts, key, s); /* // Python client does not support merges // @todo turn this into a setting { std::string current; db->Get(rocksdb::ReadOptions{}, key, ¤t); auto new_val = current + s; db->Put(wopts, key, new_val); }*/ #endif } void ifcopenshell::impl::rocks_db_file_storage::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) { #ifdef IFOPSH_WITH_ROCKSDB static std::string s; auto inst_id = inst.id(); auto key = "v|" + to_string_fixed_width(inst_id, 10) + "|" + to_string_fixed_width(from_entity->index_in_schema(), 4) + "|" + to_string_fixed_width(attribute_index, 2); if (db->Get(rocksdb::ReadOptions{}, key, &s).ok()) { std::vector vals(s.size() / sizeof(uint32_t)); memcpy(vals.data(), s.data(), s.size()); auto it = std::find(vals.begin(), vals.end(), (uint32_t)id_from); if (it != vals.end()) { vals.erase(it); } else { logger::error("Unregistering non-existant inverse #" + std::to_string(id_from) + " on instance #" + std::to_string(inst_id) + " at attribute " + std::to_string(attribute_index)); } s.resize(vals.size() * sizeof(uint32_t)); memcpy(s.data(), vals.data(), s.size()); db->Put(wopts, key, s); } #endif } void ifcopenshell::impl::rocks_db_file_storage::add_type_ref(const express::Base& new_entity) { #ifdef IFOPSH_WITH_ROCKSDB size_t v; std::string s(sizeof(size_t), ' '); if (new_entity.declaration().as_entity()) { v = new_entity.id(); memcpy(s.data(), &v, sizeof(size_t)); // no merges yet, because the python client doesn't support them db->Merge(wopts, "t|" + std::to_string(new_entity.declaration().index_in_schema()), s); /*{ std::string current; // @todo this uses the same key-namespace as typedecl instances, not a direct conflict, but also not very clear auto key = "t|" + std::to_string(new_entity.declaration().index_in_schema()); db->Get(rocksdb::ReadOptions{}, key, ¤t); auto new_val = current + s; db->Put(wopts, key, new_val); }*/ } // not only mapping also register type v = new_entity.declaration().index_in_schema(); memcpy(s.data(), &v, sizeof(size_t)); db->Put(wopts, (new_entity.declaration().as_entity() ? "i|" : "t|") + std::to_string(new_entity.id() ? new_entity.id() : new_entity.identity()) + "|_", s); #endif } void ifcopenshell::impl::rocks_db_file_storage::remove_type_ref(const express::Base& new_entity) { #ifdef IFOPSH_WITH_ROCKSDB if (new_entity.declaration().as_entity()) { std::string s; auto key = "t|" + std::to_string(new_entity.declaration().index_in_schema()); if (db->Get(rocksdb::ReadOptions{}, key, &s).ok()) { std::vector vals(s.size() / sizeof(size_t)); memcpy(vals.data(), s.data(), s.size()); vals.erase(std::find(vals.begin(), vals.end(), (size_t)new_entity.id())); s.resize(vals.size() * sizeof(size_t)); memcpy(s.data(), vals.data(), s.size()); db->Put(wopts, key, s); } } db->Delete(wopts, (new_entity.declaration().as_entity() ? "i|" : "t|") + std::to_string(new_entity.id() ? new_entity.id() : new_entity.identity()) + "|_"); #endif } namespace { class StringBuilderVisitor : public boost::static_visitor { private: StringBuilderVisitor(const StringBuilderVisitor&); //N/A StringBuilderVisitor& operator=(const StringBuilderVisitor&); //N/A std::ostream& data_; template void serialize(const std::vector& i) { data_ << "("; for (typename std::vector::const_iterator it = i.begin(); it != i.end(); ++it) { if (it != i.begin()) { data_ << ","; } data_ << *it; } data_ << ")"; } // The REAL token definition from the IFC SPF standard does not necessarily match // the output of the C++ ostream formatting operation. // REAL = [ SIGN ] DIGIT { DIGIT } "." { DIGIT } [ "E" [ SIGN ] DIGIT { DIGIT } ] . static std::string format_double(const double& d) { std::ostringstream oss; oss.imbue(std::locale::classic()); oss << std::setprecision(std::numeric_limits::digits10) << d; const std::string str = oss.str(); oss.str(""); std::string::size_type e = str.find('e'); if (e == std::string::npos) { e = str.find('E'); } const std::string mantissa = str.substr(0, e); oss << mantissa; if (mantissa.find('.') == std::string::npos) { oss << "."; } if (e != std::string::npos) { oss << "E"; oss << str.substr(e + 1); } return oss.str(); } static std::string format_binary(const boost::dynamic_bitset<>& b) { std::ostringstream oss; oss.imbue(std::locale::classic()); oss.put('"'); oss << std::uppercase << std::hex << std::setw(1); unsigned c = (unsigned)b.size(); unsigned n = (4 - (c % 4)) & 3; oss << n; for (unsigned i = 0; i < c + n;) { unsigned accum = 0; for (int j = 0; j < 4; ++j, ++i) { unsigned bit = i < n ? 0 : b.test(c - i + n - 1) ? 1 : 0; accum |= bit << (3 - j); } oss << accum; } oss.put('"'); return oss.str(); } bool upper_; public: StringBuilderVisitor(std::ostream& stream, bool upper = false) : data_(stream), upper_(upper) {} void operator()(const blank& /*i*/) { data_ << "$"; } void operator()(const derived& /*i*/) { data_ << "*"; } void operator()(const int& i) { data_ << i; } void operator()(const bool& i) { data_ << (i ? ".T." : ".F."); } void operator()(const boost::logic::tribool& i) { data_ << (i ? ".T." : (boost::logic::indeterminate(i) ? ".U." : ".F.")); } void operator()(const double& i) { data_ << format_double(i); } void operator()(const boost::dynamic_bitset<>& i) { data_ << format_binary(i); } void operator()(const std::string& i) { std::string s = i; if (upper_) { data_ << static_cast(character_encoder(s)); } else { data_ << '\'' << s << '\''; } } void operator()(const std::vector& i); void operator()(const std::vector& i); void operator()(const std::vector& i); void operator()(const std::vector>& i); void operator()(const enumeration_reference& i) { data_ << "." << i.value() << "."; } void operator()(const express::Base& i) { if (i.declaration().as_entity() == nullptr || i.declaration().schema() == &Header_section_schema::get_schema()) { i.to_string(data_, upper_); } else { data_ << "#" << i.id(); } } void operator()(const std::vector& i) { data_ << "("; for (auto it = i.begin(); it != i.end(); ++it) { if (it != i.begin()) { data_ << ","; } (*this)(*it); } data_ << ")"; } void operator()(const std::vector>& i); void operator()(const std::vector>& i); void operator()(const std::vector>& i) { data_ << "("; for (auto outer_it = i.begin(); outer_it != i.end(); ++outer_it) { if (outer_it != i.begin()) { data_ << ","; } data_ << "("; for (auto inner_it = outer_it->begin(); inner_it != outer_it->end(); ++inner_it) { if (inner_it != outer_it->begin()) { data_ << ","; } (*this)(*inner_it); } data_ << ")"; } data_ << ")"; } void operator()(const empty_aggregate_t& /*unused*/) const { data_ << "()"; } void operator()(const empty_aggregate_of_aggregate_t& /*unused*/) const { data_ << "()"; } }; template <> void StringBuilderVisitor::serialize(const std::vector& i) { data_ << "("; for (std::vector::const_iterator it = i.begin(); it != i.end(); ++it) { if (it != i.begin()) { data_ << ","; } std::string encoder = character_encoder(*it); data_ << encoder; } data_ << ")"; } template <> void StringBuilderVisitor::serialize(const std::vector& i) { data_ << "("; for (std::vector::const_iterator it = i.begin(); it != i.end(); ++it) { if (it != i.begin()) { data_ << ","; } data_ << format_double(*it); } data_ << ")"; } template <> void StringBuilderVisitor::serialize(const std::vector>& i) { data_ << "("; for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) { if (it != i.begin()) { data_ << ","; } data_ << format_binary(*it); } data_ << ")"; } void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); } void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); } void StringBuilderVisitor::operator()(const std::vector& i) { serialize(i); } void StringBuilderVisitor::operator()(const std::vector>& i) { serialize(i); } void StringBuilderVisitor::operator()(const std::vector>& i) { data_ << "("; for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) { if (it != i.begin()) { data_ << ","; } serialize(*it); } data_ << ")"; } void StringBuilderVisitor::operator()(const std::vector>& i) { data_ << "("; for (std::vector>::const_iterator it = i.begin(); it != i.end(); ++it) { if (it != i.begin()) { data_ << ","; } serialize(*it); } data_ << ")"; } } // // Returns a string representation of the entity // Note that this initializes the entity if it is not initialized // void instance_data::to_string(std::ostream& ss, bool upper) const { ss.imbue(std::locale::classic()); ss << "("; StringBuilderVisitor vis(ss, upper); // In almost all cases, storage is initialized with the size of the schema declaration, // apparently except in case of header entities and invalid in-line type declarations. auto size = (declaration_ && declaration_->as_entity() ? declaration_->as_entity()->attribute_count() : 1); if (storage_) { size = (std::min)(size, storage_->size()); } for (size_t i = 0; i < size; ++i) { if (i != 0) { ss << ","; } if (has_attribute_value(i)) { if (declaration_ != nullptr && declaration_->as_entity() && declaration_->as_entity()->derived()[i]) { ss << "*"; } else { ss << "$"; } } else { get_attribute_value(i).apply_visitor(vis); } } ss << ")"; } /* unsigned ifcopenshell::IfcBaseEntity::set_id(const std::optional& i) { if (i) { return id_ = *i; } return id_ = file_->fresh_id(); } */ namespace { // @todo remove redundancy with python wrapper code (which is not identical due to // different handling of enumerations) ifcopenshell::argument_type get_argument_type(const ifcopenshell::declaration* decl, size_t i) { const ifcopenshell::parameter_type* pt = 0; if (decl->as_entity() != nullptr) { pt = decl->as_entity()->attribute_by_index(i)->type_of_attribute(); if (decl->as_entity()->derived()[i]) { return ifcopenshell::Argument_DERIVED; } } else if ((decl->as_type_declaration() != nullptr) && i == 0) { pt = decl->as_type_declaration()->declared_type(); } else if ((decl->as_enumeration_type() != nullptr) && i == 0) { return ifcopenshell::Argument_ENUMERATION; } if (pt == 0) { return ifcopenshell::Argument_UNKNOWN; } return ifcopenshell::from_parameter_type(pt); } } // namespace class unregister_inverse_visitor { private: file& file_; const express::Base data_; public: unregister_inverse_visitor(file& file, const express::Base& data) : file_(file), data_(data) {} void operator()(const express::Base& inst, int index) { file_.unregister_inverse(data_.id(), data_.declaration().as_entity(), inst, index); } }; class register_inverse_visitor { private: file& file_; const express::Base data_; public: register_inverse_visitor(file& file, const express::Base& data) : file_(file), data_(data) {} void operator()(const express::Base& inst, int index) { file_.register_inverse(data_.id(), data_.declaration().as_entity(), inst.id(), index); } }; class add_to_instance_list_visitor { private: std::vector* list_; public: add_to_instance_list_visitor(std::vector* list) : list_(list) {} void operator()(const express::Base& inst) { list_->push_back(inst); } }; class apply_individual_instance_visitor { private: std::optional attribute_; int attribute_index_; const express::Base inst_; template void apply_attribute_(T& t, const attribute_value& attr, int index) const { switch (attr.type()) { case ifcopenshell::Argument_ENTITY_INSTANCE: { express::Base inst = attr; t(inst, index); break; } case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: { std::vector entity_list_attribute = attr; for (auto& inst : entity_list_attribute) { t(inst, index); } break; } case ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: { std::vector> nested_list_attr = attr; for (auto& vec : nested_list_attr) { for (auto& inst : vec) { t(inst, index); } } break; } default: break; } } public: apply_individual_instance_visitor(const attribute_value& attribute, int idx) : attribute_(attribute) , attribute_index_(idx) {} apply_individual_instance_visitor(const express::Base& data) : inst_(data) {} template void apply(T& t) const { if (attribute_) { apply_attribute_(t, *attribute_, attribute_index_); } else { const auto& decl = inst_.declaration(); for (size_t i = 0; i < (decl.as_entity() ? decl.as_entity()->attribute_count() : 1); ++i) { auto attr = inst_.get_attribute_value(i); apply_attribute_(t, attr, (int) i); } } }; }; template typename std::enable_if< (!std::is_base_of_v || std::is_same_v), void>::type express::Base::set_attribute_value(size_t i, const T& t) { if constexpr (std::is_same_v, double>) { if (!std::isfinite(t)) { throw ifcopenshell::exception("Only finite values are allowed"); } } if constexpr (std::is_same_v, std::vector>) { if (std::any_of(t.begin(), t.end(), [](double d) { return !std::isfinite(d); })) { throw ifcopenshell::exception("Only finite values are allowed"); } } if constexpr (std::is_same_v, std::vector>>) { for (auto& tt : t) { if (std::any_of(tt.begin(), tt.end(), [](double d) { return !std::isfinite(d); })) { throw ifcopenshell::exception("Only finite values are allowed"); } } } auto current_attribute = get_attribute_value(i); // Deregister old attribute guid in file guid map. if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) { try { auto guid = (std::string) current_attribute; auto it = file()->internal_guid_map().find(guid); if (it != file()->internal_guid_map().end()) { const std::pair& p = *it; if (p.second == *this) { file()->internal_guid_map().erase(it); } } } catch (ifcopenshell::exception& e) { logger::error(e); } } if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>> || std::is_same_v) { // Deregister inverse indices in file unregister_inverse_visitor visitor(*file(), *this); apply_individual_instance_visitor(current_attribute, (int)i).apply(visitor); } { void* const storage = std::visit([](const auto& m) { return (void*)&m; }, file()->storage_); data()->set_attribute_value(i, t); } auto new_attribute = get_attribute_value(i); // Register inverse indices in file if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>>) { register_inverse_visitor visitor(*file(), *this); apply_individual_instance_visitor(new_attribute, (int)i).apply(visitor); } // Register new attribute guid in guid map if (i == 0 && (file()->ifcroot_type() != nullptr) && this->declaration().is(*file()->ifcroot_type())) { try { auto guid = (std::string) new_attribute; auto it = file()->internal_guid_map().find(guid); if (it != file()->internal_guid_map().end()) { logger::warning("Duplicate guid " + guid); } file()->internal_guid_map().insert({guid, *this}); } catch (ifcopenshell::exception& e) { logger::error(e); } } } template typename std::enable_if< (!std::is_base_of_v || std::is_same_v), void>::type express::Base::set_attribute_value(const std::string& s, const T& t) { set_attribute_value(declaration().as_entity()->attribute_index(s), t); } // // Parses the IFC file in fn // Creates the maps // #ifdef USE_MMAP file::file(const std::string& fn, bool mmap) { initialize(fn, mmap); } bool ifcopenshell::file::initialize(const std::string& fn, bool mmap) { if (mmap) { file_reader s(fn); storage_.emplace<1>(this); std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_); } else { file_reader s(fn); storage_.emplace<1>(this); std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_); } if ((good_ = std::get(storage_).good_)) { // @todo unify these names, it's already confusing enough as it stands byid_ = decltype(byid_)(&std::get(storage_).byid_read_); byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_); byguid_ = decltype(byguid_)(&std::get(storage_).byguid_); } ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr; return good_ == file_open_status::SUCCESS; } #endif file::file(const uninitialized_tag&) : schema_(nullptr), max_id_(0), header_(new ifcopenshell::spf_header(this)), good_(file_open_status::UNKNOWN), ifcroot_type_(nullptr) {} bool ifcopenshell::file::initialize(const std::string& path, filetype ty, bool readonly) { if (ty == FT_AUTODETECT) { ty = guess_file_type(path); } if (ty == FT_IFCSPF) { file_reader s(path); storage_.emplace<1>(this); std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_); if ((good_ = std::get(storage_).good_)) { // @todo unify these names, it's already confusing enough as it stands byid_ = decltype(byid_)(&std::get(storage_).byid_read_); byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_); byguid_ = decltype(byguid_)(&std::get(storage_).byguid_); } // byidentity_ = decltype(byidentity_)(&std::get(storage_).byidentity_); } else if (ty == FT_ROCKSDB) { // This would make some difference, but in the greater light of things, not really significant // LateBoundEntity is also still large per instance // instantiate_typed_instances = false; // @todo this can only be used for databases that already exist, because otherwise there is no way to specify the schema storage_.emplace<2>(path, this, readonly); if (std::get(storage_).db == nullptr) { storage_.emplace<0>(); good_ = file_open_status::READ_ERROR; } else { if (std::get(storage_).read_schema(schema_)) { byid_ = decltype(byid_)(&std::get(storage_).instance_by_name_); byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_); byguid_ = decltype(byguid_)(&std::get(storage_).byguid_); } else { good_ = file_open_status::UNSUPPORTED_SCHEMA; } } // byidentity_ = decltype(byidentity_)(&std::get(storage_).instance_cache_); } else { storage_.emplace<0>(); good_ = file_open_status::READ_ERROR; // throw std::runtime_error("Unsupported file format"); } ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr; return good_ == file_open_status::SUCCESS; } void ifcopenshell::file::bypass_type(const std::string& type_name) { types_to_bypass_loading_.insert(type_name); } file::file(const std::string& path, filetype ty, bool readonly) : schema_(nullptr) , max_id_(0) , header_(new spf_header(this)) { initialize(path, ty, readonly); } file::file(std::istream& stream, int length) : schema_(nullptr) , max_id_(0) , header_(new ifcopenshell::spf_header(this)) { file_reader s(caller_fed_tag{}); std::string string_data; string_data.resize(length); stream.read(string_data.data(), length); s.push_next_page(string_data); storage_.emplace<1>(this); std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_); good_ = std::get(storage_).good_; ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr; byid_ = decltype(byid_)(&std::get(storage_).byid_read_); byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_); byguid_ = decltype(byguid_)(&std::get(storage_).byguid_); } file::file(void* data, int length) : schema_(nullptr) , max_id_(0), header_(new ifcopenshell::spf_header(this)) { file_reader s(std::string((char*)data, length), caller_fed_tag{}); storage_.emplace<1>(this); std::get(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_); good_ = std::get(storage_).good_; ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr; byid_ = decltype(byid_)(&std::get(storage_).byid_read_); byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_); byguid_ = decltype(byguid_)(&std::get(storage_).byguid_); } file::file(const ifcopenshell::schema_definition* schema, filetype ty, const std::string& path) : schema_(schema) , ifcroot_type_(schema_->declaration_by_name("IfcRoot")) , max_id_(0) { if (ty == FT_AUTODETECT) { ty = guess_file_type(path); } if (ty == FT_IFCSPF) { storage_.emplace<1>(this); byid_ = decltype(byid_)(&std::get(storage_).byid_read_); byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_); byguid_ = decltype(byguid_)(&std::get(storage_).byguid_); // byidentity_ = decltype(byidentity_)(&std::get(storage_).byidentity_); } else if (ty == FT_ROCKSDB) { storage_.emplace<2>(path, this); byid_ = decltype(byid_)(&std::get(storage_).instance_by_name_); byref_excl_ = decltype(byref_excl_)(&std::get(storage_).byref_excl_); byguid_ = decltype(byguid_)(&std::get(storage_).byguid_); // byidentity_ = decltype(byidentity_)(&std::get(storage_).instance_cache_); } else { throw std::runtime_error("Unsupported file format"); } header_.reset(new spf_header(this)); set_default_header_values(); } namespace { template void read_terminal(spf_lexer& lexer, const std::string& term, bool trailing_semicolon) { if (lexer.next().as_string() != term) { throw exception(std::string("Expected " + term)); } if (trailing_semicolon) { if (!lexer.next().is_operator(';')) { throw exception("Expected ;"); } } } template std::shared_ptr read_header_entity( ifcopenshell::file* file, ifcopenshell::impl::in_memory_file_storage& storage, spf_lexer& lexer, ifcopenshell::unresolved_references& references_to_resolve, const ifcopenshell::entity& decl) { parse_context pc(&storage.context_pool_); lexer.next(); storage.load(&lexer, std::nullopt, nullptr, pc, -1); auto result = pc.construct(file, std::nullopt, references_to_resolve, &decl, decl.attribute_count(), -1); storage.context_pool_.reset(); return result; } template void parse_header( ifcopenshell::spf_header& header, ifcopenshell::impl::in_memory_file_storage& storage, spf_lexer& lexer, ifcopenshell::unresolved_references& references_to_resolve) { static const char* const ISO_10303_21 = "ISO-10303-21"; static const char* const HEADER = "HEADER"; read_terminal(lexer, ISO_10303_21, true); read_terminal(lexer, HEADER, true); read_terminal(lexer, Header_section_schema::file_description::Class().name_uc(), false); header.set_file_description(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_description::Class())); if (!lexer.next().is_operator(';')) { throw exception("Expected ;"); } read_terminal(lexer, Header_section_schema::file_name::Class().name_uc(), false); header.set_file_name(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_name::Class())); if (!lexer.next().is_operator(';')) { throw exception("Expected ;"); } read_terminal(lexer, Header_section_schema::file_schema::Class().name_uc(), false); header.set_file_schema(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_schema::Class())); if (!lexer.next().is_operator(';')) { throw exception("Expected ;"); } } template bool try_parse_header( ifcopenshell::spf_header& header, ifcopenshell::impl::in_memory_file_storage& storage, spf_lexer& lexer, ifcopenshell::unresolved_references& references_to_resolve) { try { parse_header(header, storage, lexer, references_to_resolve); return true; } catch (const std::exception& e) { storage.context_pool_.reset(); logger::error(e); return false; } } } // namespace template spf_header& ifcopenshell::instance_streamer::ensure_header() { if (header_) { return *header_; } if (owner_ != nullptr) { header_ = &owner_->header(); header_->owner_file(owner_); } else { owned_header_ = std::make_unique(owner_); header_ = owned_header_.get(); } return *header_; } template void ifcopenshell::instance_streamer::initialize_header() { storage_.file = owner_; storage_.schema = schema_; storage_.references_to_resolve = &references_to_resolve_; if (!lexer_ || !stream_ || !stream_->size() || stream_->eof()) { return; } auto& header = ensure_header(); if (try_parse_header(header, storage_, *lexer_, references_to_resolve_) && header.file_schema().schema_identifiers().size() == 1) { try { schema_ = ifcopenshell::schema_by_name(header.file_schema().schema_identifiers().front()); good_ = file_open_status::SUCCESS; } catch (const ifcopenshell::exception&) { } } storage_.schema = schema_; } template bool ifcopenshell::instance_streamer::has_semicolon() const { auto local_stream = stream_->clone(); auto local_lexer = spf_lexer(&local_stream); token t; try { t = local_lexer.next(); } catch (const std::out_of_range&) { return false; } while (t.type != token::Token_NONE) { if (t.is_operator(';')) { return true; } try { t = local_lexer.next(); } catch (const std::out_of_range&) { break; } } return false; } template size_t ifcopenshell::instance_streamer::semicolon_count() const { auto local_stream = stream_->clone(); auto local_lexer = spf_lexer(&local_stream); token t; size_t count = 0; try { t = local_lexer.next(); } catch (const std::out_of_range&) { return false; } while (t.type != token::Token_NONE) { if (t.is_operator(';')) { count++; } try { t = local_lexer.next(); } catch (const std::out_of_range&) { break; } } return count; } template void ifcopenshell::instance_streamer::push_page(const std::string& page) { stream_->push_next_page(page); if (good_ == file_open_status::NO_HEADER) { initialize_header(); } } template ifcopenshell::instance_streamer::instance_streamer(ifcopenshell::file* f) : stream_(nullptr) , header_(nullptr) , owner_(f) , token_stream_(3, token{}) , schema_(nullptr) , progress_(0) { init_locale(); if constexpr (std::is_same_v>) { owned_stream_ = std::make_unique(caller_fed_tag{}); } else if constexpr (std::is_same_v>) { owned_stream_ = std::make_unique(caller_fed_tag{}); } else { static_assert(file_reader_dependent_false_v, "Default instance_streamer requires a pushed sequential reader"); } stream_ = owned_stream_.get(); lexer_ = std::make_unique>(stream_); good_ = file_open_status::NO_HEADER; storage_.file = f; storage_.references_to_resolve = &references_to_resolve_; } template ifcopenshell::instance_streamer::instance_streamer(const std::string& fn, bool mmap, ifcopenshell::file* f) : stream_(nullptr) , header_(nullptr) , owner_(f) , token_stream_(3, token{}) , schema_(nullptr) , progress_(0) { init_locale(); if constexpr (std::is_same_v>) { (void)mmap; owned_stream_ = std::make_unique(fn); #ifdef USE_MMAP } else if constexpr (std::is_same_v>) { (void)mmap; owned_stream_ = std::make_unique(fn); #endif } else { static_assert(file_reader_dependent_false_v, "Path-based instance_streamer requires a file-backed reader"); } stream_ = owned_stream_.get(); lexer_ = std::make_unique>(stream_); good_ = file_open_status::NO_HEADER; initialize_header(); } template ifcopenshell::instance_streamer::instance_streamer(void* data, int length, ifcopenshell::file* f) : stream_(nullptr) , header_(nullptr) , owner_(f) , token_stream_(3, token{}) , schema_(nullptr) , progress_(0) { init_locale(); if constexpr (std::is_same_v>) { owned_stream_ = std::make_unique(std::string((char*)data, length), caller_fed_tag{}); } else if constexpr (std::is_same_v>) { owned_stream_ = std::make_unique(std::string((char*)data, length), caller_fed_tag{}); } else { static_assert(file_reader_dependent_false_v, "Buffer-based instance_streamer requires a pushed sequential reader"); } stream_ = owned_stream_.get(); lexer_ = std::make_unique>(stream_); good_ = file_open_status::NO_HEADER; initialize_header(); } template ifcopenshell::instance_streamer::instance_streamer(Reader* stream, ifcopenshell::file* f) : stream_(stream) , header_(nullptr) , owner_(f) , token_stream_(3, token{}) , schema_(nullptr) , progress_(0) { init_locale(); lexer_ = std::make_unique>(stream_); good_ = file_open_status::NO_HEADER; initialize_header(); } template void ifcopenshell::instance_streamer::bypass_types(const std::set& type_names) { for (auto& name : type_names) { try { types_to_bypass_.push_back(schema_->declaration_by_name(name)); } catch (const exception&) { continue; } } } template std::optional>> ifcopenshell::instance_streamer::read_instance() { std::optional>> return_value; if (yield_header_instances_ && header_ && yielded_header_instances_ < 3) { if (yielded_header_instances_ == 0) { return_value.emplace( 0, &header_->file_description().declaration(), header_->file_description().data_weak().lock()); } else if (yielded_header_instances_ == 1) { return_value.emplace( 0, &header_->file_name().declaration(), header_->file_name().data_weak().lock()); } else if (yielded_header_instances_ == 2) { return_value.emplace( 0, &header_->file_schema().declaration(), header_->file_schema().data_weak().lock()); } yielded_header_instances_ += 1; return return_value; } unsigned current_id = 0; while (good_ && !lexer_->stream->eof() && !current_id) { if (token_stream_[0].type == ifcopenshell::token::Token_IDENTIFIER && token_stream_[1].type == ifcopenshell::token::Token_OPERATOR && token_stream_[1].value_char == '=' && token_stream_[2].type == ifcopenshell::token::Token_KEYWORD) { current_id = token_stream_[0].as_identifier(); const ifcopenshell::declaration* entity_type; try { entity_type = schema_->declaration_by_name(token_stream_[2].as_string()); } catch (const exception& ex) { logger::message(logger::LOG_ERROR, std::string(ex.what()) + " at offset " + std::to_string(token_stream_[2].start_pos)); current_id = 0; goto advance; } if (entity_type->as_entity() == nullptr) { logger::message(logger::LOG_ERROR, "Non entity type " + entity_type->name() + " at offset " + std::to_string(token_stream_[2].start_pos)); goto advance; } for (auto& ty : types_to_bypass_) { if (entity_type->is(*ty)) { bypassed_instances_.push_back(current_id); current_id = 0; goto advance; } } parse_context ps(&storage_.context_pool_); lexer_->next(); try { storage_.load(lexer_.get(), current_id, entity_type->as_entity(), ps, -1); } catch (const invalid_token_exception& e) { good_ = file_open_status::INVALID_SYNTAX; logger::error(e); break; } if (((++progress_) % 1000) == 0) { std::stringstream ss; ss << "\r#" << current_id; logger::status(ss.str(), false); } auto data = ps.construct(owner_, current_id, references_to_resolve_, entity_type, std::nullopt, -1, coerce_attribute_count); storage_.context_pool_.reset(); return_value.emplace( (size_t)current_id, entity_type, data); } advance: token next_token; try { next_token = lexer_->next(); } catch (const exception& e) { logger::message(logger::LOG_ERROR, std::string(e.what()) + ". Parsing terminated"); } catch (...) { logger::message(logger::LOG_ERROR, "Parsing terminated"); } if (!lexer_->stream->eof() && !next_token) { good_ = file_open_status::INVALID_SYNTAX; break; } token_stream_.push_back(next_token); } stream_->drop_pages(); lexer_->reset_pool(); return return_value; } template class ifcopenshell::instance_streamer>; template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(Reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& typed_to_bypass) { init_locale(); schema = nullptr; if (!s->size() || s->eof()) { good_ = file_open_status::READ_ERROR; return; } std::vector schemas; instance_streamer streamer(s, file); streamer.yield_header_instances(false); if (const auto* header = streamer.header()) { try { schemas = header->file_schema().schema_identifiers(); } catch (...) { } } schema = streamer.schema(); if (schema == nullptr && schemas.size() == 1) { try { schema = ifcopenshell::schema_by_name(schemas.front()); } catch (const ifcopenshell::exception& e) { good_ = file_open_status::UNSUPPORTED_SCHEMA; logger::error(e); } } if (schema == nullptr) { if (schemas.empty()) { good_ = streamer.status(); } else { good_ = file_open_status::UNSUPPORTED_SCHEMA; } logger::message(logger::LOG_ERROR, "No support for file schema encountered (" + boost::algorithm::join(schemas, ", ") + ")"); return; } auto ifcroot_type_ = schema->declaration_by_name("IfcRoot"); streamer.bypass_types(typed_to_bypass); logger::status("Scanning file..."); while (streamer) { auto inst = streamer.read_instance(); if (!inst) { break; } auto current_id = std::get<0>(*inst); express::Base instance(std::get<2>(*inst)); if (instance.declaration().is(*ifcroot_type_)) { try { const std::string guid = instance.get_attribute_value(0); if (byguid_.find(guid) != byguid_.end()) { std::stringstream ss; ss << "Instance encountered with non-unique GlobalId " << guid; logger::message(logger::LOG_WARNING, ss.str()); } byguid_[guid] = instance; } catch (const exception& ex) { logger::message(logger::LOG_ERROR, ex.what()); } } const ifcopenshell::declaration* ty = &instance.declaration(); bytype_excl_[ty].push_back(instance); if (byid_.find(current_id) != byid_.end()) { std::stringstream ss; ss << "Overwriting instance with name #" << current_id; logger::message(logger::LOG_WARNING, ss.str()); } byid_.insert({(uint32_t)current_id, std::get<2>(*inst)}); max_id = (std::max)(max_id, (unsigned int)current_id); } good_ = streamer.status(); byref_excl_ = streamer.inverses(); read_simple_type_instances = streamer.steal_instances(); logger::status("\rDone scanning file "); if (good_ != file_open_status::SUCCESS) { return; } const auto& bypassed = streamer.bypassed_instances(); for (const auto& p : streamer.references()) { const auto& ref = p.first.name_; const auto& refattr = p.first.index_; if (auto* v = std::get_if(&p.second)) { if (auto* name = std::get_if(v)) { if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) { continue; } auto it = byid_.find(*name); if (it == byid_.end()) { logger::error("Instance reference #" + std::to_string(*name) + " used by instance #" + std::to_string(ref) + " at attribute index " + std::to_string(refattr) + " not found at offset " + std::to_string(name->file_offset)); } else { auto& storage = byid_[p.first.name_]; auto attr_index = p.first.index_; if (storage->template has_attribute_value(attr_index)) { express::Base inst = storage->get_attribute_value(attr_index); if (!inst.declaration().as_entity()) { // Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance storage = inst.data_weak().lock(); attr_index = 0; } } if (storage->template has_attribute_value(attr_index)) { storage->set_attribute_value(attr_index, express::Base(it->second)); } else { logger::error("Duplicate definition for instance reference"); } } } else if (auto inst = std::get_if(v)) { byid_[p.first.name_]->set_attribute_value(p.first.index_, *inst); } } else if (auto* vv = std::get_if>(&p.second)) { std::vector instances; instances.reserve(vv->size()); for (const auto& vi : *vv) { if (auto* name = std::get_if(&vi)) { if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) { continue; } auto it = byid_.find(*name); if (it == byid_.end()) { logger::error("Instance reference #" + std::to_string(*name) + " used by instance #" + std::to_string(ref) + " at attribute index " + std::to_string(refattr) + " not found at offset " + std::to_string(name->file_offset)); } else { instances.push_back(express::Base(it->second)); } } else if (auto* inst = std::get_if(&vi)) { instances.push_back(*inst); } } auto& storage = byid_[p.first.name_]; auto attr_index = p.first.index_; if (storage->template has_attribute_value(attr_index)) { express::Base inst = storage->get_attribute_value(attr_index); if (!inst.declaration().as_entity()) { // Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance storage = inst.data_weak().lock(); attr_index = 0; } } if (storage->template has_attribute_value(attr_index)) { storage->set_attribute_value(attr_index, instances); } else { logger::error("Duplicate definition for instance reference"); } } else if (auto* vvv = std::get_if>>(&p.second)) { std::vector> instances; for (const auto& vi : *vvv) { auto& inner = instances.emplace_back(); for (const auto& vii : vi) { if (auto* name = std::get_if(&vii)) { if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) { continue; } auto it = byid_.find(*name); if (it == byid_.end()) { logger::error("Instance reference #" + std::to_string(*name) + " used by instance #" + std::to_string(ref) + " at attribute index " + std::to_string(refattr) + " not found at offset " + std::to_string(name->file_offset)); } else { inner.push_back(express::Base(it->second)); } } else if (auto* inst = std::get_if(&vii)) { inner.push_back(*inst); } } } auto& storage = byid_[p.first.name_]; auto attr_index = p.first.index_; if (storage->template has_attribute_value(attr_index)) { express::Base inst = storage->get_attribute_value(attr_index); if (!inst.declaration().as_entity()) { // Probably a case of IfcPropertySetDefinitionSet, divert storage of reference to the simply type instance storage = inst.data_weak().lock(); attr_index = 0; } } if (storage->template has_attribute_value(attr_index)) { storage->set_attribute_value(attr_index, instances); } else { logger::error("Duplicate definition for instance reference"); } } } logger::status("Done resolving references"); } template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& typed_to_bypass); template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& typed_to_bypass); #ifdef USE_MMAP template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set& typed_to_bypass); #endif void file::recalculate_id_counter() { /* // @todo entity_by_id_t::key_type k = 0; for (auto& p : byid_) { if (p.first > k) { k = p.first; } } max_id_ = (unsigned int)k; */ } class traversal_recorder { std::vector list_; std::map> instances_by_level_; int mode_; public: traversal_recorder(int mode) : mode_(mode) { }; void push_back(int level, const express::Base& instance) { if (mode_ == 0) { list_.push_back(instance); } else { auto& l = instances_by_level_[level]; l.push_back(instance); } } std::vector get_list() const { if (mode_ == 0) { return list_; } std::vector l; for (const auto& p : instances_by_level_) { l.insert(l.end(), p.second.begin(), p.second.end()); } return l; } }; class traversal_visitor { private: std::set& visited_; traversal_recorder& list_; int level_; int max_level_; public: traversal_visitor(std::set& visited, traversal_recorder& list, int level, int max_level) : visited_(visited), list_(list), level_(level), max_level_(max_level) {} void operator()(const express::Base& inst, int index); }; void traverse_(const express::Base& instance, std::set& visited, traversal_recorder& list, int level, int max_level) { if (visited.find(instance) != visited.end()) { return; } visited.insert(instance); list.push_back(level, instance); if (level >= max_level && max_level > 0) { return; } traversal_visitor visit(visited, list, level + 1, max_level); apply_individual_instance_visitor(instance).apply(visit); } void traversal_visitor::operator()(const express::Base& inst, int /* index */) { traverse_(inst, visited_, list_, level_, max_level_); } std::vector ifcopenshell::traverse(const express::Base& instance, int max_level) { std::set visited; traversal_recorder recorder(0); traverse_(instance, visited, recorder, 0, max_level); return recorder.get_list(); } // I'm cheating this isn't breadth-first, but rather we record visited instances // keeping track of their rank and return a list ordered by rank. Is this equivalent? std::vector ifcopenshell::traverse_breadth_first(const express::Base& instance, int max_level) { std::set visited; traversal_recorder recorder(1); traverse_(instance, visited, recorder, 0, max_level); return recorder.get_list(); } /// @note: for backwards compatibility std::vector file::traverse(const express::Base& instance, int max_level) { return ifcopenshell::traverse(instance, max_level); } /// @note: for backwards compatibility std::vector file::traverse_breadth_first(const express::Base& instance, int max_level) { return ifcopenshell::traverse_breadth_first(instance, max_level); } express::Base file::add_entity(const express::Base& entity, int id) { if (entity.file() == this) { return entity; } if (entity.declaration().schema() != schema()) { throw ifcopenshell::exception("Unabled to add instance from " + entity.declaration().schema()->name() + " schema to file with " + schema()->name() + " schema"); } // If this instance has been inserted before, return // a reference to the copy that was created from it. entity_entity_map_t::iterator mit = entity_file_map_.find(entity.identity()); if (mit != entity_file_map_.end()) { return mit->second; } // Obtain all forward references by a depth-first // traversal and add them to the file. try { auto entity_attributes = traverse(entity, 1); for (auto it = entity_attributes.begin() + 1; it != entity_attributes.end(); ++it) { if (*it != entity) { entity_entity_map_t::iterator mit2 = entity_file_map_.find(it->identity()); if (mit2 == entity_file_map_.end()) { entity_file_map_.insert(entity_entity_map_t::value_type(it->identity(), add_entity(*it))); } } } } catch (...) { logger::message(logger::LOG_ERROR, "Failed to visit forward references of", entity); } // An instance is being added from another file. A copy of the // container and entity is created. The attribute references // need to be updated to point to instances in this file. file* other_file = entity.file(); auto new_entity = create(&entity.declaration(), id); auto* decl = &entity.declaration(); auto num_attributes = (decl->as_entity() ? decl->as_entity()->attribute_count() : 1); for (size_t i = 0; i < num_attributes; ++i) { entity.get_attribute_value(i).apply_visitor([this, i, decl, &new_entity](const auto& v) { using U = std::decay_t; // only need to copy non-instance attribute values, others are assigned below after mapping if constexpr (std::is_same_v) { } else if constexpr (std::is_same_v>) { } else if constexpr (std::is_same_v>>) { } else { new_entity.set_attribute_value(i, v); } }); } // In case an entity is added that contains geometry, the unit // information needs to be accounted for for IfcLengthMeasures. double conversion_factor = calculate_unit_factors ? std::numeric_limits::quiet_NaN() : 1.0; for (size_t i = 0; i < num_attributes; ++i) { // old attribute value auto attr = entity.get_attribute_value(i); ifcopenshell::argument_type attr_type = attr.type(); ifcopenshell::declaration* potentially_length_measure_decl = 0; if (decl->as_entity() != nullptr) { potentially_length_measure_decl = 0; const parameter_type* pt = decl->as_entity()->attribute_by_index(i)->type_of_attribute(); while (pt->as_aggregation_type() != nullptr) { pt = pt->as_aggregation_type()->type_of_element(); } if (pt->as_named_type() != nullptr) { potentially_length_measure_decl = pt->as_named_type()->declared_type(); } } if (attr_type == ifcopenshell::Argument_ENTITY_INSTANCE) { entity_entity_map_t::const_iterator eit = entity_file_map_.find(((express::Base)(attr)).identity()); if (eit == entity_file_map_.end()) { throw ifcopenshell::exception("Unable to map instance to file"); } // @todo previously, we directly use storage::set() not to trigger inverse recalculation which happens at the end new_entity.set_attribute_value(i, eit->second); } else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE) { std::vector instances = attr; std::vector new_instances; for (auto& i : instances) { entity_entity_map_t::const_iterator eit = entity_file_map_.find(i.identity()); if (eit == entity_file_map_.end()) { throw ifcopenshell::exception("Unable to map instance to file"); } new_instances.push_back(eit->second); } new_entity.set_attribute_value(i, new_instances); } else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE) { std::vector> instances = attr; std::vector> new_instances; for (auto& v : instances) { new_instances.emplace_back(); for (auto& i : v) { entity_entity_map_t::const_iterator eit = entity_file_map_.find(i.identity()); if (eit == entity_file_map_.end()) { throw ifcopenshell::exception("Unable to map instance to file"); } new_instances.back().push_back(eit->second); } } new_entity.set_attribute_value(i, new_instances); } else if ((potentially_length_measure_decl != nullptr) && potentially_length_measure_decl->is(*schema()->declaration_by_name("IfcLengthMeasure"))) { if (boost::math::isnan(conversion_factor)) { std::pair this_file_unit = {express::Base{}, 1.0}; std::pair other_file_unit = {express::Base{}, 1.0}; try { this_file_unit = get_unit("LENGTHUNIT"); other_file_unit = other_file->get_unit("LENGTHUNIT"); } catch (ifcopenshell::exception&) { } if (this_file_unit.first && other_file_unit.first) { conversion_factor = other_file_unit.second / this_file_unit.second; } else { conversion_factor = 1.; } } if (attr_type == ifcopenshell::Argument_DOUBLE) { double v = attr; v *= conversion_factor; new_entity.set_attribute_value(i, v); } else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_DOUBLE) { std::vector v = attr; for (std::vector::iterator it = v.begin(); it != v.end(); ++it) { (*it) *= conversion_factor; } new_entity.set_attribute_value(i, v); } else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE) { std::vector> v = attr; for (std::vector>::iterator it = v.begin(); it != v.end(); ++it) { std::vector& v2 = (*it); for (std::vector::iterator jt = v2.begin(); jt != v2.end(); ++jt) { (*jt) *= conversion_factor; } } new_entity.set_attribute_value(i, v); } } } entity_file_map_.insert(entity_entity_map_t::value_type(entity.identity(), new_entity)); // For subtypes of IfcRoot, the GUID mapping needs to be updated. /* if (decl->is(*ifcroot_type_)) { try { const std::string guid = new_entity.get_attribute_value(0); if (byguid_.find(guid) != byguid_.end()) { std::stringstream ss; ss << "Overwriting entity with guid " << guid; logger::message(logger::LOG_WARNING, ss.str()); } byguid_.insert({ guid, new_entity }); } catch (const std::exception& ex) { logger::message(logger::LOG_ERROR, ex.what()); } } */ // add_type_ref(new_entity); return new_entity; } void file::remove_entity(const express::Base& entity) { auto id = entity.id(); auto file_entity = instance_by_id(id); // Attention when running remove_entity inside a loop over a list of entities to be removed. // This invalidates the iterator. A workaround is to reverse the loop: // boost::shared_ptr entities = ...; // for (auto it = entities->end() - 1; it >= entities->begin(); --it) { // express::Base *const inst = *it; // model->remove_entity(inst); // } // TODO: Create a set of weak relations. Inverse relations that do not dictate an // instance to be retained. For example: when deleting an IfcRepresentation, the // individual IfcRepresentationItems can not be deleted if an IfcStyledItem is // related. Hence, the IfcRepresentationItem::StyledByItem relation could be // characterized as weak. // std::set weak_roots; if (entity != file_entity) { throw ifcopenshell::exception("Instance not part of this file"); } if (batch_mode_) { batch_deletion_ids_.push_back(id); } else { process_deletion_(entity); byid_.erase(entity.id()); } } void file::process_deletion_(const express::Base& entity) { auto references = instances_by_reference(entity.id()); // Alter entity instances with INVERSE relations to the entity being // deleted. This is necessary to maintain a valid IFC file, because // dangling references to it's entities name should be removed. At this // moment, inversely related instances affected by the removal of the // entity being deleted are not deleted themselves. if (!references.empty()) { for (auto& related_instance : references) { if (std::find(batch_deletion_ids_.begin(), batch_deletion_ids_.end(), related_instance.id()) != batch_deletion_ids_.end()) { continue; } const auto& decl = related_instance.declaration(); for (size_t i = 0; i < (decl.as_entity() ? decl.as_entity()->attribute_count() : 1); ++i) { auto attr = related_instance.get_attribute_value(i); if (attr.isNull()) { continue; } ifcopenshell::argument_type attr_type = attr.type(); switch (attr_type) { case ifcopenshell::Argument_ENTITY_INSTANCE: { express::Base instance_attribute = attr; if (instance_attribute == entity) { related_instance.set_attribute_value(i, blank{}); } } break; case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: { std::vector instance_list = attr; auto it = std::remove(instance_list.begin(), instance_list.end(), entity); if (it != instance_list.end()) { instance_list.erase(it, instance_list.end()); if (instance_list.empty() && related_instance.declaration().as_entity()->attribute_by_index(i)->optional()) { // @todo we can also check the lower bound of the attribute type before setting to null. related_instance.set_attribute_value(i, blank{}); } else { related_instance.set_attribute_value(i, instance_list); } } } break; case ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: { std::vector> instance_list_list = attr; bool updated = false; for (auto& li : instance_list_list) { auto it = std::remove(li.begin(), li.end(), entity); if (it != li.end()) { li.erase(it, li.end()); updated = true; } } related_instance.set_attribute_value(i, instance_list_list); } break; default: break; } } } } if (entity.declaration().is(*ifcroot_type_) && !entity.get_attribute_value(0).isNull()) { const std::string global_id = entity.get_attribute_value(0); auto it = byguid_.find(global_id); if (it != byguid_.end()) { byguid_.erase(it); } else { logger::warning("GlobalId on rooted instance not encountered in map"); } } process_deletion_inverse(entity); remove_type_ref(entity); // entity_file_map is in place to prevent duplicate definitions with usage of add(). // Upon deletion the pairs need to be erased. // @todo this is not strictly necessary anymore and can be amortized to e.g 1/100 times // to delete the expired weak_ptrs. for (auto it = entity_file_map_.begin(); it != entity_file_map_.end();) { if (it->second == entity) { it = entity_file_map_.erase(it); } else { ++it; } } } void ifcopenshell::impl::in_memory_file_storage::process_deletion_inverse(const express::Base& entity) { auto id = entity.id(); // Delete inverses into entity byref_excl_.erase(id); // This is based on traversal which needs instances to still be contained in the map. // another option would be to keep byid intact for the remainder of this loop auto entity_attributes = traverse(entity, 1); for (auto it = entity_attributes.begin(); it != entity_attributes.end(); ++it) { auto entity_attribute = *it; if (entity_attribute == entity) { continue; } const unsigned int name = entity_attribute.id(); // Do not update inverses for simple types (which have id()==0 in IfcOpenShell). if (name != 0) { // Find instances entity -> other // and update inverses from entity into other auto submap = byref_excl_.find(name); if (submap != byref_excl_.end()) { for (auto& [key, ids] : submap->second) { ids.erase(std::remove(ids.begin(), ids.end(), id), ids.end()); } } } } } namespace { template void visit_subtypes(const ifcopenshell::entity* ent, Fn fn) { fn(ent); for (const auto& st : ent->subtypes()) { visit_subtypes(st, fn); } } template void visit_supertypes(const ifcopenshell::entity* ent, Fn fn) { fn(ent); if (ent->supertype()) { visit_supertypes(ent->supertype(), fn); } } } std::vector file::instances_by_type(const ifcopenshell::declaration* t) { std::vector insts; if (t->as_entity() != nullptr) { visit_subtypes(t->as_entity(), [this, &insts](const ifcopenshell::entity* ent) { auto subtype_insts = instances_by_type_excl_subtypes(ent); insts.insert(insts.end(), subtype_insts.begin(), subtype_insts.end()); }); } return insts; } std::vector file::instances_by_type_excl_subtypes(const ifcopenshell::declaration* t) { return std::visit([t](auto& x) { if constexpr (std::is_same_v, impl::in_memory_file_storage>) { auto it = x.bytype_excl_.find(t); return (it == x.bytype_excl_.end()) ? std::vector{} : it->second; } else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { std::vector ret; auto it = x.bytype_.find(t->index_in_schema()); if (it != x.bytype_.end()) { const auto& s = it->second; // @todo generalize this, bytype_ should be a map_adapter std::vector vals(s.size() / sizeof(size_t)); memcpy(vals.data(), s.data(), s.size()); for (auto& v : vals) { ret.push_back(x.assert_existance(v, ifcopenshell::impl::rocks_db_file_storage::entityinstance_ref)); } } return ret; } else { throw std::runtime_error("Storage not initialized"); std::vector ret; return ret; } }, storage_); } std::vector file::instances_by_type(const std::string& t) { return instances_by_type(schema()->declaration_by_name(t)); } std::vector file::instances_by_type_excl_subtypes(const std::string& t) { return instances_by_type_excl_subtypes(schema()->declaration_by_name(t)); } std::vector file::instances_by_reference(int t) { std::vector ret; std::visit([this, t, &ret](auto& x) { if constexpr (std::is_same_v, impl::in_memory_file_storage>) { auto submap = x.byref_excl_.find(t); if (submap == x.byref_excl_.end()) { return; } for (auto& [key, ids] : submap->second) { for (auto& i : ids) { ret.push_back(instance_by_id(i)); } } } #ifdef IFOPSH_WITH_ROCKSDB else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { // @todo no lower/upper_bounds() implemented yet auto prefix = "v|" + std::to_string(t) + "|"; auto it = std::unique_ptr(x.db->NewIterator(rocksdb::ReadOptions())); it->Seek(prefix); while (it->Valid() && it->key().starts_with(prefix)) { std::vector vals(it->value().size() / sizeof(uint32_t)); memcpy(vals.data(), it->value().data(), it->value().size()); for (auto& v : vals) { ret.push_back(instance_by_id(v)); } it->Next(); } } #endif else { throw std::runtime_error("Storage not initialized"); } }, storage_); return ret; } express::Base file::instance_by_id(int id) { return std::visit([id](auto& x) { if constexpr (std::is_same_v, std::monostate>) { throw std::runtime_error("Storage not initialized"); return express::Base{}; } else { return x.instance_by_id(id); } }, storage_); } void ifcopenshell::file::add_type_ref(const express::Base& new_entity) { std::visit([new_entity](auto& x) { if constexpr (std::is_same_v, std::monostate>) { throw std::runtime_error("Storage not initialized"); } else { return x.add_type_ref(new_entity); } }, storage_); } void ifcopenshell::file::remove_type_ref(const express::Base& new_entity) { std::visit([new_entity](auto& x) { if constexpr (std::is_same_v, std::monostate>) { throw std::runtime_error("Storage not initialized"); } else { return x.remove_type_ref(new_entity); } }, storage_); } void ifcopenshell::file::process_deletion_inverse(const express::Base& inst) { std::visit([inst](auto& x) { if constexpr (std::is_same_v, std::monostate>) { throw std::runtime_error("Storage not initialized"); } else { return x.process_deletion_inverse(inst); } }, storage_); } express::Base file::instance_by_guid(const std::string& guid) { auto it = byguid_.find(guid); if (it == byguid_.end()) { throw exception("Instance with GlobalId '" + guid + "' not found"); } return it->second; } file::type_iterator file::types_begin() const { return std::visit([](const auto& x) { if constexpr (std::is_same_v, std::monostate>) { return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} }; } else if constexpr (std::is_same_v, impl::in_memory_file_storage>) { return file::type_iterator{ x.bytype_excl_.begin() }; } else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator(&x) }; } }, storage_); } file::type_iterator file::types_end() const { return std::visit([](const auto& x) { if constexpr (std::is_same_v, std::monostate>) { return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} }; } else if constexpr (std::is_same_v, impl::in_memory_file_storage>) { return file::type_iterator{ x.bytype_excl_.end() }; } else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} }; } }, storage_); } std::ostream& operator<<(std::ostream& out, const ifcopenshell::file& file) { file.header().write(out); typedef std::vector vector_t; vector_t sorted; std::transform(file.begin(), file.end(), std::back_inserter(sorted), [&file](const auto& x) { return x.second; }); std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) { return a.id() < b.id(); }); for (auto& e : sorted) { // @todo this check should no longer be necessary? if (e.declaration().as_entity() != nullptr) { e.to_string(out, true); out << ";" << std::endl; } } out << "ENDSEC;" << std::endl; out << "END-ISO-10303-21;" << std::endl; return out; } std::string file::create_timestamp() { char buf[255]; time_t t; time(&t); struct tm* ti = localtime(&t); std::string result; if (strftime(buf, 255, "%Y-%m-%dT%H:%M:%S", ti) != 0U) { result = std::string(buf); } return result; } const ifcopenshell::schema_definition* file::schema() const { if (schema_ == nullptr) { throw exception("No schema loaded"); } return schema_; } std::vector file::get_inverse_indices(int instance_id) { std::vector return_value; // Mapping of instance id to attribute offset. std::map> mapping; std::visit([&mapping, instance_id](const auto& x) { if constexpr (std::is_same_v, std::monostate>) { } else if constexpr (std::is_same_v, impl::in_memory_file_storage>) { auto submap = x.byref_excl_.find(instance_id); if (submap == x.byref_excl_.end()) { return; } for (auto& [key, ids] : submap->second) { for (auto& i : ids) { mapping[i].push_back(std::get<1>(key)); } } } else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { #ifdef IFOPSH_WITH_ROCKSDB // @todo no lower/upper_bounds() implemented yet auto prefix = "v|" + std::to_string(instance_id) + "|"; auto it = std::unique_ptr(x.db->NewIterator(rocksdb::ReadOptions())); it->Seek(prefix); while (it->Valid() && it->key().starts_with(prefix)) { std::vector vals(it->value().size() / sizeof(uint32_t)); memcpy(vals.data(), it->value().data(), it->value().size()); auto tuple = key_from_string>(it->key().ToString().substr(2)); for (auto& i : vals) { mapping[i].push_back(std::get<2>(tuple)); } it->Next(); } #endif } }, storage_); auto refs = instances_by_reference(instance_id); for (const auto& ref : refs) { auto it = mapping.find(ref.id()); if (it == mapping.end() || it->second.empty()) { throw exception("Internal error"); } return_value.push_back(it->second.front()); it->second.erase(it->second.begin()); if (it->second.empty()) { mapping.erase(it); } } // Test whether all mappings where indeed used. if (!mapping.empty()) { throw exception("Internal error"); } return return_value; } std::vector file::get_inverse(int instance_id, const ifcopenshell::declaration* type, int attribute_index) { std::vector return_value; if (type == nullptr && attribute_index == -1) { // @todo this is silly. auto r = instances_by_reference(instance_id); for (auto& i : r) { return_value.push_back(i.as()); } return return_value; } std::visit([&return_value, this, attribute_index, instance_id, type](const auto& x) { if constexpr (std::is_same_v, std::monostate>) { } else if constexpr (std::is_same_v, impl::in_memory_file_storage>) { auto submap = x.byref_excl_.find(instance_id); if (submap != x.byref_excl_.end()) { visit_subtypes(type->as_entity(), [this, attribute_index, instance_id, &return_value, &submap](const ifcopenshell::declaration* ent) { if (attribute_index == -1) { auto lower = submap->second.lower_bound({ent->index_in_schema(), std::numeric_limits::min()}); auto upper = submap->second.upper_bound({ent->index_in_schema(), std::numeric_limits::max()}); for (auto it = lower; it != upper; ++it) { for (auto& i : it->second) { return_value.push_back(instance_by_id(i).template as()); } } } else { auto it = submap->second.find({ent->index_in_schema(), attribute_index}); if (it != submap->second.end()) { for (auto& i : it->second) { return_value.push_back(instance_by_id(i).template as()); } } } }); } } #ifdef IFOPSH_WITH_ROCKSDB else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { visit_subtypes(type->as_entity(), [this, attribute_index, instance_id, &return_value, &x](const ifcopenshell::declaration* ent) { if (attribute_index == -1) { // @todo no lower/upper_bounds() implemented yet auto prefix = "v|" + std::to_string(instance_id) + "|" + std::to_string(ent->index_in_schema()) + "|"; auto it = std::unique_ptr(x.db->NewIterator(rocksdb::ReadOptions())); it->Seek(prefix); while (it->Valid() && it->key().starts_with(prefix)) { std::vector vals(it->value().size() / sizeof(uint32_t)); memcpy(vals.data(), it->value().data(), it->value().size()); for (auto& v : vals) { return_value.push_back(instance_by_id(v).template as()); } it->Next(); } } else { auto it = x.byref_excl_.find({instance_id, ent->index_in_schema(), attribute_index}); if (it != x.byref_excl_.end()) { for (auto& i : it->second) { return_value.push_back(instance_by_id(i).template as()); } } } }); } #endif }, storage_); return return_value; } size_t file::get_total_inverses(int instance_id) { std::set counted_ids; std::visit([&counted_ids, instance_id](const auto& x) { if constexpr (std::is_same_v, std::monostate>) { } else if constexpr (std::is_same_v, impl::in_memory_file_storage>) { auto submap = x.byref_excl_.find(instance_id); if (submap == x.byref_excl_.end()) { return; } for (auto& [key, ids] : submap->second) { for (auto& i : ids) { counted_ids.insert(i); } } } else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { // @todo } }, storage_); return counted_ids.size(); } void file::set_default_header_values() { const std::string empty_string; std::vector file_description; std::vector schema_identifiers; std::vector string_vector = {""}; file_description.push_back("ViewDefinition [CoordinationView]"); if (schema() != nullptr) { schema_identifiers.push_back(schema()->name()); } header().file_description().setdescription(file_description); header().file_description().setimplementation_level("2;1"); header().file_name().setname(empty_string); header().file_name().settime_stamp(create_timestamp()); header().file_name().setauthor(string_vector); header().file_name().setorganization(string_vector); header().file_name().setpreprocessor_version("IfcOpenShell " + std::string(IFCOPENSHELL_VERSION)); header().file_name().setoriginating_system("IfcOpenShell " + std::string(IFCOPENSHELL_VERSION)); header().file_name().setauthorization(empty_string); header().file_schema().setschema_identifiers(schema_identifiers); } std::pair file::get_unit(const std::string& unit_type) { std::pair return_value(express::Base{}, 1.); auto projects = instances_by_type(schema()->declaration_by_name("IfcProject")); if (projects.empty()) { try { projects = instances_by_type(schema()->declaration_by_name("IfcContext")); } catch (exception&) { } } if (!projects.empty()) { auto project = *projects.begin(); express::Base unit_assignment = project.as().get("UnitsInContext"); std::vector units = unit_assignment.as().get("Units"); for (auto& unit : units) { if (unit.declaration().is("IfcNamedUnit")) { const std::string file_unit_type = unit.as().get("UnitType"); if (file_unit_type != unit_type) { continue; } express::Base siunit; if (unit.declaration().is("IfcConversionBasedUnit")) { express::Base mu = unit.as().get("ConversionFactor"); express::Base vlc = mu.as().get("ValueComponent"); express::Base unc = mu.as().get("UnitComponent"); return_value.second *= static_cast(vlc.get_attribute_value(0)); return_value.first = unit; if (unc.declaration().is("IfcSIUnit")) { siunit = unc; } } else if (unit.declaration().is("IfcSIUnit")) { return_value.first = siunit = unit; } if (siunit) { attribute_value prefix = siunit.as().get("Prefix"); if (!prefix.isNull()) { return_value.second *= si_prefix_to_value(prefix); } } } } } return return_value; } void ifcopenshell::file::build_inverses_(const express::Base& inst) { std::function fn = [this, inst](const express::Base& attr, int idx) { if (attr.declaration().as_entity() != nullptr) { unsigned entity_attribute_id = attr.id(); const auto* decl = inst.declaration().as_entity(); std::visit([entity_attribute_id, decl, idx, inst](auto& x) { if constexpr (std::is_same_v, std::monostate>) { } else if constexpr (std::is_same_v, impl::in_memory_file_storage>) { x.byref_excl_[entity_attribute_id][{decl->index_in_schema(), idx}].push_back(inst.id()); } else if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { // @todo } }, storage_); } }; apply_individual_instance_visitor(inst).apply(fn); } void ifcopenshell::file::unbatch() { for (auto& id : batch_deletion_ids_) { process_deletion_(instance_by_id(id)); } // keep in memory until all deletions are processed for (auto& id : batch_deletion_ids_) { byid_.erase(id); } batch_mode_ = false; batch_deletion_ids_.clear(); } void ifcopenshell::file::reset_identity_cache() { std::visit([](auto& x) { if constexpr (std::is_same_v, impl::rocks_db_file_storage>) { x.instance_cache_.clear(); x.type_instance_cache_.clear(); } }, storage_); } void ifcopenshell::file::build_inverses() { for (const auto& pair : *this) { build_inverses_(express::Base(pair.second)); } } void ifcopenshell::file::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) { std::visit([id_from, from_entity, inst_id, attribute_index](auto& x) { if constexpr (std::is_same_v, std::monostate>) { throw std::runtime_error("Storage not initialized"); } else { return x.register_inverse(id_from, from_entity, inst_id, attribute_index); } }, storage_); } void ifcopenshell::file::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) { std::visit([id_from, from_entity, inst, attribute_index](auto& x) { if constexpr (std::is_same_v, std::monostate>) { throw std::runtime_error("Storage not initialized"); } else { return x.unregister_inverse(id_from, from_entity, inst, attribute_index); } }, storage_); } std::atomic_uint32_t instance_data::counter_(0); // bool ifcopenshell::file::guid_map_ = true; void express::Base::unset_attribute_value(size_t index) { data()->set_attribute_value(index, blank{}); } attribute_value express::Base::get_attribute_value(size_t index) const { return data()->get_attribute_value(index); } void express::Base::to_string(std::ostream& out, bool upper) const { const auto *ent = declaration().as_entity(); if (ent != nullptr && declaration().schema() != &Header_section_schema::get_schema()) { out << "#" << id() << "="; } if (upper) { out << declaration().name_uc(); } else { out << declaration().name(); } data()->to_string(out, upper); } ifcopenshell::file* express::Base::file() const { return data()->file(); } /* instance_data::instance_data(const instance_data& data) : storage_(data.size()) { } */ attribute_value instance_data::get_attribute_value(size_t index) const { if (storage_) { return attribute_value(storage_, (uint8_t)index); } else { auto* const storage = std::visit([](auto& m) -> ifcopenshell::impl::rocks_db_file_storage* { using U = std::decay_t; if constexpr (std::is_same_v) { return &m; } else { return nullptr; } }, file_->storage_); return attribute_value(storage, identity_, declaration_, (uint8_t) index); } } bool ifcopenshell::impl::rocks_db_file_storage::read_schema(const ifcopenshell::schema_definition*& schema) { #ifdef IFOPSH_WITH_ROCKSDB std::string value; auto key = "h|file_schema|0"; db->Get(rocksdb::ReadOptions{}, key, &value); std::vector strings; if (::impl::deserialize(this, value, strings) && strings.size() == 1) { try { schema = schema_by_name(strings[0]); } catch (exception&) { return false; } return true; } #endif return false; } /* express::Base::IfcBaseClass(instance_data&& data) : identity_(counter_++) , id_(0) , file_(nullptr) , data_(std::move(data)) { * @todo this is not allowed cannot call virtual func in constructor if (!declaration().as_entity()) { // @nb from v0.9 type decl instances have their own id, which may collide with instance names in the file // but is otherwise unique id_ = identity_; } } */ void express::Base::set_attribute_value(size_t i, const express::Base& p) { set_attribute_value(i, p); } void express::Base::set_attribute_value(const std::string& name, const express::Base& p) { set_attribute_value(name, p); } template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const blank& value); template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const derived& value); template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const int& value); template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const bool& value); template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const boost::logic::tribool& value); template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const double& value); template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const std::string& value); template void IFC_PARSE_API express::Base::set_attribute_value>(size_t index, const boost::dynamic_bitset<>& value); template void IFC_PARSE_API express::Base::set_attribute_value(size_t index, const enumeration_reference& value); template void IFC_PARSE_API express::Base::set_attribute_value>(size_t index, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>(size_t index, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>(size_t index, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(size_t index, const std::vector>& value); template void IFC_PARSE_API express::Base::set_attribute_value>(size_t index, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(size_t index, const std::vector>& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(size_t index, const std::vector>& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(size_t index, const std::vector>& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const blank& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const derived& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const int& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const bool& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const boost::logic::tribool& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const double& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const std::string& value); template void IFC_PARSE_API express::Base::set_attribute_value>(const std::string& name, const boost::dynamic_bitset<>& value); template void IFC_PARSE_API express::Base::set_attribute_value(const std::string& name, const enumeration_reference& value); template void IFC_PARSE_API express::Base::set_attribute_value>(const std::string& name, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>(const std::string& name, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>(const std::string& name, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(const std::string& name, const std::vector>& value); template void IFC_PARSE_API express::Base::set_attribute_value>(const std::string& name, const std::vector& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(const std::string& name, const std::vector>& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(const std::string& name, const std::vector>& value); template void IFC_PARSE_API express::Base::set_attribute_value>>(const std::string& name, const std::vector>& value); namespace express { template T Entity::get_value(const std::string& name) const { auto attr = get(name); T v = attr; return v; } template T Entity::get_value(const std::string& name, const T& default_value) const { auto attr = get(name); if (attr.isNull()) { return default_value; } T v = attr; return v; } } // namespace express template int IFC_PARSE_API express::Entity::get_value(const std::string&) const; template bool IFC_PARSE_API express::Entity::get_value(const std::string&) const; template boost::logic::tribool IFC_PARSE_API express::Entity::get_value(const std::string&) const; template double IFC_PARSE_API express::Entity::get_value(const std::string&) const; template std::string IFC_PARSE_API express::Entity::get_value(const std::string&) const; template express::Base IFC_PARSE_API express::Entity::get_value(const std::string&) const; template boost::dynamic_bitset<> IFC_PARSE_API express::Entity::get_value>(const std::string&) const; template enumeration_reference IFC_PARSE_API express::Entity::get_value(const std::string&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&) const; template int IFC_PARSE_API express::Entity::get_value(const std::string&, const int&) const; template bool IFC_PARSE_API express::Entity::get_value(const std::string&, const bool&) const; template boost::logic::tribool IFC_PARSE_API express::Entity::get_value(const std::string&, const boost::logic::tribool&) const; template double IFC_PARSE_API express::Entity::get_value(const std::string&, const double&) const; template std::string IFC_PARSE_API express::Entity::get_value(const std::string&, const std::string&) const; template express::Base IFC_PARSE_API express::Entity::get_value(const std::string&, const express::Base&) const; template boost::dynamic_bitset<> IFC_PARSE_API express::Entity::get_value>(const std::string&, const boost::dynamic_bitset<>&) const; template enumeration_reference IFC_PARSE_API express::Entity::get_value(const std::string&, const enumeration_reference&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&, const std::vector&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&, const std::vector&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&, const std::vector&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&, const std::vector>&) const; template std::vector IFC_PARSE_API express::Entity::get_value>(const std::string&, const std::vector&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&, const std::vector>&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&, const std::vector>&) const; template std::vector> IFC_PARSE_API express::Entity::get_value>>(const std::string&, const std::vector>&) const;