Files
IfcOpenShell/src/ifcparse/parse.cpp
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

3093 lines
120 KiB
C++
Raw Normal View History

/********************************************************************************
2023-09-18 16:13:44 +02:00
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
2011-05-08 11:04:32 +00:00
2026-03-31 15:32:36 +02:00
#include "parse.h"
2023-09-17 12:30:17 +02:00
#include "express.h"
2026-03-31 15:32:36 +02:00
#include "character_decoder.h"
#include "exception.h"
#include "file.h"
#include "logger.h"
#include "schema.h"
#include "si_prefix.h"
#include "file_reader.h"
2023-09-17 12:30:17 +02:00
#include "utils.h"
2011-05-08 11:04:32 +00:00
2023-09-17 12:30:17 +02:00
#include <algorithm>
#include <boost/algorithm/string.hpp>
2024-08-23 20:29:07 +02:00
#include <boost/variant.hpp>
2023-09-17 12:30:17 +02:00
#include <boost/math/special_functions/fpclassify.hpp>
#include <ctime>
2023-09-17 12:30:17 +02:00
#include <set>
#include <stdio.h>
#include <stdlib.h>
2023-09-17 12:30:17 +02:00
#include <string>
2024-08-23 20:29:07 +02:00
#include <iomanip>
2023-09-18 16:13:44 +02:00
#ifdef USE_MMAP
#include <boost/filesystem/path.hpp>
#endif
2016-06-12 11:22:08 +02:00
#define PERMISSIVE_FLOAT
2026-03-31 15:32:36 +02:00
using namespace ifcopenshell;
2011-05-08 11:04:32 +00:00
2023-09-17 12:30:17 +02:00
// 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.
2016-06-05 14:03:03 +02:00
#if defined(_MSC_VER)
2023-09-17 12:30:17 +02:00
static _locale_t locale = (_locale_t)0;
void init_locale() {
2023-09-17 12:30:17 +02:00
if (locale == (_locale_t)0) {
locale = _create_locale(LC_NUMERIC, "C");
}
}
2016-06-05 14:03:03 +02:00
#else
2016-06-05 14:03:03 +02:00
#if defined(__MINGW64__) || defined(__MINGW32__)
#include <locale>
#include <sstream>
typedef void* locale_t;
2016-06-05 14:03:03 +02:00
static locale_t locale = (locale_t)0;
void init_locale() {}
double strtod_l(const char* start, char** end, locale_t loc) {
2023-09-17 12:30:17 +02:00
double d;
std::stringstream ss;
ss.imbue(std::locale::classic());
ss << start;
ss >> d;
size_t nread = ss.tellg();
*end = const_cast<char*>(start) + nread;
return d;
2016-06-05 14:03:03 +02:00
}
#else
#ifdef __APPLE__
#include <xlocale.h>
#endif
#include <locale.h>
static locale_t locale = (locale_t)0;
void init_locale() {
2023-09-17 12:30:17 +02:00
if (locale == (locale_t)0) {
locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
}
}
2016-06-05 14:03:03 +02:00
#endif
2016-06-05 14:03:03 +02:00
#endif
template <typename Reader>
2026-03-31 15:32:36 +02:00
spf_lexer<Reader>::spf_lexer(Reader* stream_) {
stream = stream_;
2026-03-31 15:32:36 +02:00
decoder_ = new character_decoder<Reader>(stream_);
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
spf_lexer<Reader>::~spf_lexer() {
delete decoder_;
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
size_t spf_lexer<Reader>::skip_whitespace() const {
2025-10-13 20:47:43 +02:00
size_t index = 0;
while (!stream->eof()) {
char character = stream->peek();
if ((character == ' ' || character == '\r' || character == '\n' || character == '\t')) {
2025-10-13 20:47:43 +02:00
stream->increment();
++index;
2023-09-17 12:30:17 +02:00
} else {
break;
}
}
return index;
2014-04-02 15:04:09 +00:00
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
size_t spf_lexer<Reader>::skip_comment() const {
if (stream->eof()) {
return 0;
}
2025-10-13 20:47:43 +02:00
char character = stream->peek();
if (character != '/') {
2023-09-17 12:30:17 +02:00
return 0;
}
2025-10-13 20:47:43 +02:00
stream->increment();
character = stream->peek();
if (character != '*') {
2025-10-13 20:47:43 +02:00
stream->seek(stream->tell() - 1);
2023-09-17 12:30:17 +02:00
return 0;
}
2025-10-13 20:47:43 +02:00
size_t index = 2;
char intermediate = 0;
2025-10-13 20:47:43 +02:00
while (!stream->eof()) {
character = stream->peek();
stream->increment();
++index;
if (character == '/' && intermediate == '*') {
2023-09-17 12:30:17 +02:00
break;
}
intermediate = character;
2023-09-17 12:30:17 +02:00
}
return index;
2014-04-02 15:04:09 +00:00
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
std::string& spf_lexer<Reader>::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::array<std::string, 16>>());
}
// 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 <bool IncludeDot = true>
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 <bool IncludeDot = true>
inline uint32_t has_special_char(uint32_t x) {
return eq_mask(x, static_cast<uint32_t>(chars::lpar)) |
eq_mask(x, static_cast<uint32_t>(chars::rpar)) |
eq_mask(x, static_cast<uint32_t>(chars::eq)) |
eq_mask(x, static_cast<uint32_t>(chars::comma)) |
eq_mask(x, static_cast<uint32_t>(chars::semi)) |
eq_mask(x, static_cast<uint32_t>(chars::slash)) |
eq_mask(x, static_cast<uint32_t>(chars::space)) |
eq_mask(x, static_cast<uint32_t>(chars::cr)) |
eq_mask(x, static_cast<uint32_t>(chars::lf)) |
eq_mask(x, static_cast<uint32_t>(chars::tab)) |
eq_mask(x, static_cast<uint32_t>(chars::quote)) |
(IncludeDot ? eq_mask(x, static_cast<uint32_t>(chars::dot)) : uint32_t{0});
}
}
//
2026-03-31 15:32:36 +02:00
// Returns the offset of the current token and moves cursor to next
//
template <typename Reader>
2026-03-31 15:32:36 +02:00
token spf_lexer<Reader>::next() {
2025-10-13 20:47:43 +02:00
if (stream->eof()) {
2026-03-31 15:32:36 +02:00
return token{};
2023-09-17 12:30:17 +02:00
}
2025-10-13 20:47:43 +02:00
auto pos = stream->tell();
char character = stream->read();
if (character == '/' || character == ' ' || character == '\r' || character == '\n' || character == '\t') {
2026-03-31 15:32:36 +02:00
while ((skip_whitespace() != 0U) || (skip_comment() != 0U)) {
}
if (stream->eof()) {
2026-03-31 15:32:36 +02:00
return token{};
}
pos = stream->tell();
character = stream->read();
}
2023-09-17 12:30:17 +02:00
// If the cursor is at [()=,;$*] we know token consists of single char
if (character == '(' ||
character == ')' ||
character == '=' ||
character == ',' ||
character == ';' ||
character == '$' ||
2025-10-13 20:47:43 +02:00
character == '*')
{
2026-03-31 15:32:36 +02:00
return token(pos, character);
2023-09-17 12:30:17 +02:00
}
2026-03-31 15:32:36 +02:00
auto& str = get_temp_string();
2025-10-13 20:47:43 +02:00
if (character == '\'') {
2026-03-31 15:32:36 +02:00
// If a string is encountered defer processing to the character_decoder
2025-10-13 20:47:43 +02:00
str = *decoder_;
2026-03-31 15:32:36 +02:00
return token(pos, token::Token_STRING, str);
2024-08-23 20:29:07 +02:00
} else {
2026-03-31 15:32:36 +02:00
auto ttype = token::Token_NONE;
if (character == '"' || character == '.') {
if (character == '"') {
2026-03-31 15:32:36 +02:00
ttype = token::Token_BINARY;
} else {
2026-03-31 15:32:36 +02:00
ttype = token::Token_ENUMERATION;
}
str.clear();
} else if (character == '#') {
2026-03-31 15:32:36 +02:00
ttype = token::Token_IDENTIFIER;
str.clear();
} else {
str.assign(&character, 1);
}
2025-10-13 20:47:43 +02:00
auto remaining = stream->remaining();
while (remaining) {
if (remaining >= 8) {
uint64_t x = stream->peek_u64();
2026-03-31 15:32:36 +02:00
if ((ttype == token::Token_NONE ? SWAR::has_special_char<false>(x) : SWAR::has_special_char<true>(x)) == 0) {
str.append(reinterpret_cast<const char*>(&x), 8);
stream->increment(8);
remaining -= 8;
continue;
}
}
if (remaining >= 4) {
uint32_t x = stream->peek_u32();
2026-03-31 15:32:36 +02:00
if ((ttype == token::Token_NONE ? SWAR::has_special_char<false>(x) : SWAR::has_special_char<true>(x)) == 0) {
str.append(reinterpret_cast<const char*>(&x), 4);
stream->increment(4);
remaining -= 4;
continue;
}
}
2025-10-13 20:47:43 +02:00
// Read character and increment pointer if not starting a new token
char character = stream->peek();
2025-10-13 20:47:43 +02:00
if (character == '(' ||
character == ')' ||
character == '=' ||
character == ',' ||
character == ';' ||
character == '/') {
break;
}
if (!(character == ' ' || character == '\r' || character == '\n' || character == '\t')) {
2026-03-31 15:32:36 +02:00
if ((ttype == token::Token_BINARY && character == '"') ||
(ttype == token::Token_ENUMERATION && character == '.')) {
// Skip
} else {
str.push_back(character);
}
}
2025-10-13 20:47:43 +02:00
stream->increment();
remaining -= 1;
2025-10-13 20:47:43 +02:00
}
2026-03-31 15:32:36 +02:00
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)) {
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(pos, str, "instance name");
}
2026-03-31 15:32:36 +02:00
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')) {
2026-03-31 15:32:36 +02:00
ttype = token::Token_KEYWORD;
return token(pos, ttype, str);
} else if (parse_int_(str.c_str(), int_val)) {
2026-03-31 15:32:36 +02:00
ttype = token::Token_INT;
pop_pool_entry();
return token(pos, ttype, int_val);
} else if (parse_float_(str.c_str(), float_val)) {
2026-03-31 15:32:36 +02:00
ttype = token::Token_FLOAT;
pop_pool_entry();
return token(pos, float_val);
}
2026-03-31 15:32:36 +02:00
} else if (ttype == token::Token_BINARY || ttype == token::Token_ENUMERATION) {
return token(pos, ttype, str);
2023-09-17 12:30:17 +02:00
}
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(pos, str, "valid token");
2023-09-17 12:30:17 +02:00
}
}
2026-04-24 13:36:06 +02:00
template class IFC_PARSE_API ifcopenshell::spf_lexer<file_reader<full_buffer_impl>>;
template class IFC_PARSE_API ifcopenshell::spf_lexer<file_reader<paged_file_impl>>;
template class IFC_PARSE_API ifcopenshell::spf_lexer<file_reader<pushed_sequential_impl>>;
#ifdef USE_MMAP
2026-04-24 13:36:06 +02:00
template class IFC_PARSE_API ifcopenshell::spf_lexer<file_reader<mmap_impl>>;
#endif
2026-03-31 15:32:36 +02:00
bool token::is_operator() {
return type == Token_OPERATOR;
2023-09-17 12:30:17 +02:00
}
2021-07-29 14:54:51 +02:00
2026-03-31 15:32:36 +02:00
bool token::is_operator(char character) {
return type == Token_OPERATOR && value_char == character;
}
2026-03-31 15:32:36 +02:00
bool token::is_identifier() {
return type == Token_IDENTIFIER;
}
2026-03-31 15:32:36 +02:00
bool token::is_string() {
return type == Token_STRING;
}
2026-03-31 15:32:36 +02:00
bool token::is_enumeration() {
// @nb this is a bit confusing?
return type == Token_ENUMERATION || type == Token_BOOL;
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
bool token::is_binary() {
return type == Token_BINARY;
2011-06-13 07:23:09 +00:00
}
2026-03-31 15:32:36 +02:00
bool token::is_keyword() {
return type == Token_KEYWORD;
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
bool token::is_int() {
return type == Token_INT;
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
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';
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
bool token::is_logical() {
return type == Token_BOOL;
}
2026-03-31 15:32:36 +02:00
bool token::is_float() {
2016-06-12 11:22:08 +02:00
#ifdef PERMISSIVE_FLOAT
2023-09-17 12:30:17 +02:00
/// NB: We are being more permissive here then allowed by the standard
return type == Token_FLOAT || type == Token_INT;
2016-06-12 11:22:08 +02:00
#else
return type == Token_FLOAT;
2016-06-12 11:22:08 +02:00
#endif
}
2026-03-31 15:32:36 +02:00
int token::as_int() {
if (type != Token_INT) {
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(start_pos, to_string(), "integer");
2023-09-17 12:30:17 +02:00
}
return value_int;
}
2026-03-31 15:32:36 +02:00
unsigned token::as_identifier() {
if (type != Token_IDENTIFIER) {
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(start_pos, to_string(), "instance name");
2023-09-17 12:30:17 +02:00
}
return (unsigned) value_int;
2011-06-13 07:23:09 +00:00
}
2026-03-31 15:32:36 +02:00
bool token::as_bool() {
if (type != Token_BOOL) {
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(start_pos, to_string(), "boolean");
2023-09-17 12:30:17 +02:00
}
return value_char == 'T';
2021-07-29 14:54:51 +02:00
}
2026-03-31 15:32:36 +02:00
boost::logic::tribool token::as_logical() {
if (type != Token_BOOL) {
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(start_pos, to_string(), "logical");
2023-09-17 12:30:17 +02:00
}
if (value_int == 'F') {
2023-09-17 12:30:17 +02:00
return false;
}
if (value_int == 'T') {
2023-09-17 12:30:17 +02:00
return true;
}
return boost::logic::indeterminate;
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
double token::as_float() {
2016-06-12 11:22:08 +02:00
#ifdef PERMISSIVE_FLOAT
if (type == Token_INT) {
2023-09-17 12:30:17 +02:00
/// NB: We are being more permissive here then allowed by the standard
return value_int;
} // ----> continues beyond preprocessor directive
2016-06-12 11:22:08 +02:00
#endif
if (type == Token_FLOAT) {
return value_double;
}
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(start_pos, to_string(), "real");
}
2026-03-31 15:32:36 +02:00
const std::string& token::as_string() {
if (is_string() || is_enumeration() || is_binary() || is_keyword()) {
// @todo quotes
return *value_string;
2023-09-17 12:30:17 +02:00
}
2026-03-31 15:32:36 +02:00
throw invalid_token_exception(start_pos, to_string(), "string");
}
2026-03-31 15:32:36 +02:00
boost::dynamic_bitset<> token::as_binary() {
const std::string& str = as_string();
2024-08-23 20:29:07 +02:00
if (str.empty()) {
2026-03-31 15:32:36 +02:00
throw exception("token is not a valid binary sequence");
2023-09-17 12:30:17 +02:00
}
2023-09-17 12:30:17 +02:00
std::string::const_iterator it = str.begin();
int n = *it - '0';
if ((n < 0 || n > 3) || (str.size() == 1 && n != 0)) {
2026-03-31 15:32:36 +02:00
throw exception("token is not a valid binary sequence");
2023-09-17 12:30:17 +02:00
}
++it;
unsigned i = (str.size() - 1) * 4 - n;
2023-09-17 12:30:17 +02:00
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) {
2023-09-17 12:30:17 +02:00
bitset.set(i);
}
}
}
2023-09-17 12:30:17 +02:00
return bitset;
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
std::string token::to_string() {
2023-09-17 12:30:17 +02:00
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) {
2025-10-13 20:47:43 +02:00
std::ostringstream oss;
oss << std::setprecision(15) << value_double;
2025-10-13 20:47:43 +02:00
result = oss.str();
} else {
return as_string();
2025-10-13 20:47:43 +02:00
}
2023-09-17 12:30:17 +02:00
return result;
}
2023-09-17 12:30:17 +02:00
//
// Reads the arguments from a list of token
2017-06-05 17:26:58 +02:00
// Aditionally, registers the ids (i.e. #[\d]+) in the inverse map
//
template <typename Reader>
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::in_memory_file_storage::load(ifcopenshell::spf_lexer<Reader>* tokens, std::optional<size_t> entity_instance_name, const ifcopenshell::entity* entity, parse_context& context, int attribute_index) {
token next = tokens->next();
2023-09-17 12:30:17 +02:00
2024-08-23 20:29:07 +02:00
size_t attribute_index_within_data = 0;
2023-09-17 12:30:17 +02:00
size_t return_value = 0;
while (next) {
if (next.is_operator(',')) {
2024-08-23 20:29:07 +02:00
if (attribute_index == -1) {
attribute_index_within_data += 1;
}
} else if (next.is_operator(')')) {
2023-09-17 12:30:17 +02:00
break;
} else if (next.is_operator('(')) {
2023-09-17 12:30:17 +02:00
return_value++;
load(tokens, entity_instance_name, entity, context.push(), attribute_index == -1 ? (int) attribute_index_within_data : attribute_index);
2023-09-17 12:30:17 +02:00
} else {
return_value++;
if (next.is_identifier() && entity && entity_instance_name) {
2025-10-13 20:47:43 +02:00
register_inverse(*entity_instance_name, entity, next.value_int, attribute_index == -1 ? (int) attribute_index_within_data : attribute_index);
2023-09-17 12:30:17 +02:00
}
if (next.is_keyword()) {
2023-09-17 12:30:17 +02:00
try {
const auto* decl = (schema ? schema : file->schema())->declaration_by_name(next.as_string());
2026-04-22 12:03:58 +02:00
parse_context ps;
2026-03-31 15:32:36 +02:00
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))
);
2026-03-31 15:32:36 +02:00
// @todo do we need express::Base here? Or should we just push instance_data?
2024-08-23 20:29:07 +02:00
context.push(simple_type_instance);
2026-03-31 15:32:36 +02:00
} 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--;
2023-09-17 12:30:17 +02:00
}
} else {
2024-08-23 20:29:07 +02:00
context.push(next);
2023-09-17 12:30:17 +02:00
}
}
2026-03-31 15:32:36 +02:00
next = tokens->next();
2023-09-17 12:30:17 +02:00
}
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::in_memory_file_storage::try_read_semicolon(ifcopenshell::spf_lexer<Reader>* tokens) const {
2025-10-13 20:47:43 +02:00
auto old_offset = tokens->stream->tell();
2026-03-31 15:32:36 +02:00
token semilocon = tokens->next();
if (!semilocon.is_operator(';')) {
2025-10-13 20:47:43 +02:00
tokens->stream->seek(old_offset);
2023-09-17 12:30:17 +02:00
}
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::in_memory_file_storage::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index) {
2023-09-17 12:30:17 +02:00
// Assume a check on token type has already been performed
2026-04-10 14:54:47 +02:00
byref_excl_[inst_id][{from_entity->index_in_schema(), attribute_index}].push_back(id_from);
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::in_memory_file_storage::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index) {
2026-04-10 14:54:47 +02:00
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()) {
2023-09-17 12:30:17 +02:00
// @todo inverses also need to be populated when multiple instances are added to a new file.
2026-03-31 15:32:36 +02:00
// throw ifcopenshell::exception("Instance not found among inverses");
2023-09-17 12:30:17 +02:00
} else {
ids.erase(iter);
2023-09-17 12:30:17 +02:00
}
2017-07-03 14:18:56 +02:00
}
namespace {
template <typename T>
2025-10-13 20:47:43 +02:00
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();
}
}
2026-03-31 15:32:36 +02:00
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));
2025-02-27 22:07:31 +01:00
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);
2025-02-27 22:07:31 +01:00
/*
// Python client does not support merges
// @todo turn this into a setting
2025-02-27 22:07:31 +01:00
{
std::string current;
db->Get(rocksdb::ReadOptions{}, key, &current);
auto new_val = current + s;
db->Put(wopts, key, new_val);
}*/
#endif
}
2026-03-31 15:32:36 +02:00
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<uint32_t> 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 {
2026-03-31 15:32:36 +02:00
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
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::rocks_db_file_storage::add_type_ref(const express::Base& new_entity)
2025-02-24 20:46:29 +01:00
{
#ifdef IFOPSH_WITH_ROCKSDB
size_t v;
2025-02-24 20:46:29 +01:00
std::string s(sizeof(size_t), ' ');
2025-02-27 22:07:31 +01:00
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, &current);
auto new_val = current + s;
db->Put(wopts, key, new_val);
}*/
2025-02-27 22:07:31 +01:00
}
// not only mapping also register type
v = new_entity.declaration().index_in_schema();
2025-02-27 22:07:31 +01:00
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
2025-02-24 20:46:29 +01:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::rocks_db_file_storage::remove_type_ref(const express::Base& new_entity)
2025-02-24 20:46:29 +01:00
{
#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<size_t> 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);
}
2025-02-24 20:46:29 +01:00
}
2025-02-27 22:07:31 +01:00
db->Delete(wopts, (new_entity.declaration().as_entity() ? "i|" : "t|") + std::to_string(new_entity.id() ? new_entity.id() : new_entity.identity()) + "|_");
#endif
2025-02-24 20:46:29 +01:00
}
2024-08-23 20:29:07 +02:00
namespace {
class StringBuilderVisitor : public boost::static_visitor<void> {
private:
StringBuilderVisitor(const StringBuilderVisitor&); //N/A
StringBuilderVisitor& operator=(const StringBuilderVisitor&); //N/A
std::ostream& data_;
template <typename T>
void serialize(const std::vector<T>& i) {
data_ << "(";
for (typename std::vector<T>::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<double>::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) {}
2026-03-31 15:32:36 +02:00
void operator()(const blank& /*i*/) { data_ << "$"; }
void operator()(const derived& /*i*/) { data_ << "*"; }
2024-08-23 20:29:07 +02:00
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_) {
2026-03-31 15:32:36 +02:00
data_ << static_cast<std::string>(character_encoder(s));
2024-08-23 20:29:07 +02:00
} else {
data_ << '\'' << s << '\'';
}
}
void operator()(const std::vector<int>& i);
void operator()(const std::vector<double>& i);
void operator()(const std::vector<std::string>& i);
void operator()(const std::vector<boost::dynamic_bitset<>>& i);
2026-03-31 15:32:36 +02:00
void operator()(const enumeration_reference& i) {
2024-08-23 20:29:07 +02:00
data_ << "." << i.value() << ".";
}
void operator()(const express::Base& i) {
if (i.declaration().as_entity() == nullptr || i.declaration().schema() == &Header_section_schema::get_schema()) {
2026-03-31 15:32:36 +02:00
i.to_string(data_, upper_);
2024-08-23 20:29:07 +02:00
} else {
data_ << "#" << i.id();
2024-08-23 20:29:07 +02:00
}
}
void operator()(const std::vector<express::Base>& i) {
2024-08-23 20:29:07 +02:00
data_ << "(";
for (auto it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
2024-08-23 20:29:07 +02:00
data_ << ",";
}
(*this)(*it);
}
data_ << ")";
}
void operator()(const std::vector<std::vector<int>>& i);
void operator()(const std::vector<std::vector<double>>& i);
void operator()(const std::vector<std::vector<express::Base>>& i) {
2024-08-23 20:29:07 +02:00
data_ << "(";
for (auto outer_it = i.begin(); outer_it != i.end(); ++outer_it) {
if (outer_it != i.begin()) {
2024-08-23 20:29:07 +02:00
data_ << ",";
}
data_ << "(";
for (auto inner_it = outer_it->begin(); inner_it != outer_it->end(); ++inner_it) {
2024-08-23 20:29:07 +02:00
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<std::string>& i) {
data_ << "(";
for (std::vector<std::string>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
2026-03-31 15:32:36 +02:00
std::string encoder = character_encoder(*it);
2024-08-23 20:29:07 +02:00
data_ << encoder;
}
data_ << ")";
2023-09-17 12:30:17 +02:00
}
2015-01-25 10:50:12 +00:00
2024-08-23 20:29:07 +02:00
template <>
void StringBuilderVisitor::serialize(const std::vector<double>& i) {
data_ << "(";
for (std::vector<double>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << format_double(*it);
}
data_ << ")";
}
2015-01-25 10:50:12 +00:00
2024-08-23 20:29:07 +02:00
template <>
void StringBuilderVisitor::serialize(const std::vector<boost::dynamic_bitset<>>& i) {
data_ << "(";
for (std::vector<boost::dynamic_bitset<>>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
data_ << format_binary(*it);
2023-09-17 12:30:17 +02:00
}
2024-08-23 20:29:07 +02:00
data_ << ")";
}
2023-09-17 12:30:17 +02:00
2024-08-23 20:29:07 +02:00
void StringBuilderVisitor::operator()(const std::vector<int>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<double>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<std::string>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<boost::dynamic_bitset<>>& i) { serialize(i); }
void StringBuilderVisitor::operator()(const std::vector<std::vector<int>>& i) {
data_ << "(";
for (std::vector<std::vector<int>>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
serialize(*it);
}
data_ << ")";
}
void StringBuilderVisitor::operator()(const std::vector<std::vector<double>>& i) {
data_ << "(";
for (std::vector<std::vector<double>>::const_iterator it = i.begin(); it != i.end(); ++it) {
if (it != i.begin()) {
data_ << ",";
}
serialize(*it);
2023-09-17 12:30:17 +02:00
}
2024-08-23 20:29:07 +02:00
data_ << ")";
2023-09-17 12:30:17 +02:00
}
2024-08-23 20:29:07 +02:00
}
2023-09-17 12:30:17 +02:00
2024-08-23 20:29:07 +02:00
//
// Returns a string representation of the entity
// Note that this initializes the entity if it is not initialized
//
2026-03-31 15:32:36 +02:00
void instance_data::to_string(std::ostream& ss, bool upper) const {
2024-08-23 20:29:07 +02:00
ss.imbue(std::locale::classic());
2023-09-17 12:30:17 +02:00
2024-08-23 20:29:07 +02:00
ss << "(";
StringBuilderVisitor vis(ss, upper);
2025-08-31 15:52:46 +02:00
// 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);
2025-08-31 15:52:46 +02:00
if (storage_) {
size = (std::min)(size, storage_->size());
}
for (size_t i = 0; i < size; ++i) {
2023-09-17 12:30:17 +02:00
if (i != 0) {
ss << ",";
}
2026-03-31 15:32:36 +02:00
if (has_attribute_value<blank>(i)) {
if (declaration_ != nullptr && declaration_->as_entity() && declaration_->as_entity()->derived()[i]) {
2024-07-10 12:43:01 -07:00
ss << "*";
2024-07-17 09:45:34 +02:00
} else {
2024-07-10 12:43:01 -07:00
ss << "$";
2024-08-23 20:29:07 +02:00
}
2023-09-17 12:30:17 +02:00
} else {
get_attribute_value(i).apply_visitor(vis);
2023-09-17 12:30:17 +02:00
}
}
ss << ")";
2011-05-08 11:04:32 +00:00
}
/*
2026-03-31 15:32:36 +02:00
unsigned ifcopenshell::IfcBaseEntity::set_id(const std::optional<unsigned>& i) {
2023-09-17 12:30:17 +02:00
if (i) {
return id_ = *i;
}
2026-03-31 15:32:36 +02:00
return id_ = file_->fresh_id();
2017-06-05 17:26:58 +02:00
}
*/
2017-06-05 17:26:58 +02:00
namespace {
2023-09-17 12:30:17 +02:00
// @todo remove redundancy with python wrapper code (which is not identical due to
// different handling of enumerations)
2026-03-31 15:32:36 +02:00
ifcopenshell::argument_type get_argument_type(const ifcopenshell::declaration* decl, size_t i) {
const ifcopenshell::parameter_type* pt = 0;
if (decl->as_entity() != nullptr) {
2023-09-17 12:30:17 +02:00
pt = decl->as_entity()->attribute_by_index(i)->type_of_attribute();
if (decl->as_entity()->derived()[i]) {
2026-03-31 15:32:36 +02:00
return ifcopenshell::Argument_DERIVED;
2023-09-17 12:30:17 +02:00
}
} else if ((decl->as_type_declaration() != nullptr) && i == 0) {
2023-09-17 12:30:17 +02:00
pt = decl->as_type_declaration()->declared_type();
} else if ((decl->as_enumeration_type() != nullptr) && i == 0) {
2026-03-31 15:32:36 +02:00
return ifcopenshell::Argument_ENUMERATION;
2023-09-17 12:30:17 +02:00
}
if (pt == 0) {
2026-03-31 15:32:36 +02:00
return ifcopenshell::Argument_UNKNOWN;
2023-09-17 12:30:17 +02:00
}
2026-03-31 15:32:36 +02:00
return ifcopenshell::from_parameter_type(pt);
}
2023-09-17 12:30:17 +02:00
} // namespace
2017-07-03 14:18:56 +02:00
class unregister_inverse_visitor {
2023-09-17 12:30:17 +02:00
private:
2026-03-31 15:32:36 +02:00
file& file_;
const express::Base data_;
2017-07-03 14:18:56 +02:00
2023-09-17 12:30:17 +02:00
public:
2026-03-31 15:32:36 +02:00
unregister_inverse_visitor(file& file, const express::Base& data)
2023-09-17 12:30:17 +02:00
: file_(file),
data_(data) {}
2017-07-03 14:18:56 +02:00
void operator()(const express::Base& inst, int index) {
file_.unregister_inverse(data_.id(), data_.declaration().as_entity(), inst, index);
2023-09-17 12:30:17 +02:00
}
2017-07-03 14:18:56 +02:00
};
class register_inverse_visitor {
2023-09-17 12:30:17 +02:00
private:
2026-03-31 15:32:36 +02:00
file& file_;
const express::Base data_;
2017-07-03 14:18:56 +02:00
2023-09-17 12:30:17 +02:00
public:
2026-03-31 15:32:36 +02:00
register_inverse_visitor(file& file, const express::Base& data)
2023-09-17 12:30:17 +02:00
: file_(file),
data_(data) {}
2017-07-03 14:18:56 +02:00
void operator()(const express::Base& inst, int index) {
file_.register_inverse(data_.id(), data_.declaration().as_entity(), inst.id(), index);
2023-09-17 12:30:17 +02:00
}
2017-07-03 14:18:56 +02:00
};
class add_to_instance_list_visitor {
2023-09-17 12:30:17 +02:00
private:
std::vector<express::Base>* list_;
2017-07-03 14:18:56 +02:00
2023-09-17 12:30:17 +02:00
public:
add_to_instance_list_visitor(std::vector<express::Base>* list)
2023-09-17 12:30:17 +02:00
: list_(list) {}
2017-07-03 14:18:56 +02:00
void operator()(const express::Base& inst) {
list_->push_back(inst);
2023-09-17 12:30:17 +02:00
}
2017-07-03 14:18:56 +02:00
};
class apply_individual_instance_visitor {
2023-09-17 12:30:17 +02:00
private:
2026-03-31 15:32:36 +02:00
std::optional<attribute_value> attribute_;
2023-09-17 12:30:17 +02:00
int attribute_index_;
const express::Base inst_;
2025-02-27 22:07:31 +01:00
2023-09-17 12:30:17 +02:00
template <typename T>
2026-03-31 15:32:36 +02:00
void apply_attribute_(T& t, const attribute_value& attr, int index) const {
switch (attr.type()) {
2026-03-31 15:32:36 +02:00
case ifcopenshell::Argument_ENTITY_INSTANCE: {
express::Base inst = attr;
2023-09-17 12:30:17 +02:00
t(inst, index);
break;
}
2026-03-31 15:32:36 +02:00
case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector<express::Base> entity_list_attribute = attr;
for (auto& inst : entity_list_attribute) {
t(inst, index);
2023-09-17 12:30:17 +02:00
}
break;
}
2026-03-31 15:32:36 +02:00
case ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector<std::vector<express::Base>> nested_list_attr = attr;
for (auto& vec : nested_list_attr) {
for (auto& inst : vec) {
t(inst, index);
2023-09-17 12:30:17 +02:00
}
}
break;
2023-09-17 12:30:17 +02:00
}
default:
break;
}
}
2023-09-17 12:30:17 +02:00
public:
2026-03-31 15:32:36 +02:00
apply_individual_instance_visitor(const attribute_value& attribute, int idx)
2024-08-23 20:29:07 +02:00
: attribute_(attribute)
, attribute_index_(idx)
{}
2023-09-17 12:30:17 +02:00
apply_individual_instance_visitor(const express::Base& data)
2025-02-27 22:07:31 +01:00
: inst_(data)
2024-08-23 20:29:07 +02:00
{}
2023-09-17 12:30:17 +02:00
template <typename T>
void apply(T& t) const {
if (attribute_) {
2024-08-23 20:29:07 +02:00
apply_attribute_(t, *attribute_, attribute_index_);
2023-09-17 12:30:17 +02:00
} else {
const auto& decl = inst_.declaration();
2025-02-27 22:07:31 +01:00
for (size_t i = 0; i < (decl.as_entity() ? decl.as_entity()->attribute_count() : 1); ++i) {
auto attr = inst_.get_attribute_value(i);
2024-08-23 20:29:07 +02:00
apply_attribute_(t, attr, (int) i);
2023-09-17 12:30:17 +02:00
}
}
};
2017-07-03 14:18:56 +02:00
};
2024-08-23 20:29:07 +02:00
template <typename T>
2025-08-26 10:19:47 +02:00
typename std::enable_if<
(!std::is_base_of_v<express::Base, T> || std::is_same_v<express::Base, T>),
2025-08-26 10:19:47 +02:00
void>::type
express::Base::set_attribute_value(size_t i, const T& t) {
2025-03-25 10:15:24 +01:00
if constexpr (std::is_same_v<std::decay_t<T>, double>) {
if (!std::isfinite(t)) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell::exception("Only finite values are allowed");
2025-03-25 10:15:24 +01:00
}
}
if constexpr (std::is_same_v<std::decay_t<T>, std::vector<double>>) {
if (std::any_of(t.begin(), t.end(), [](double d) { return !std::isfinite(d); })) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell::exception("Only finite values are allowed");
2025-03-25 10:15:24 +01:00
}
}
if constexpr (std::is_same_v<std::decay_t<T>, std::vector<std::vector<double>>>) {
for (auto& tt : t) {
if (std::any_of(tt.begin(), tt.end(), [](double d) { return !std::isfinite(d); })) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell::exception("Only finite values are allowed");
2025-03-25 10:15:24 +01:00
}
}
}
2025-02-27 22:07:31 +01:00
auto current_attribute = get_attribute_value(i);
2023-09-17 12:30:17 +02:00
// 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<const std::string, express::Base>& p = *it;
if (p.second == *this) {
file()->internal_guid_map().erase(it);
2023-09-17 12:30:17 +02:00
}
}
2026-03-31 15:32:36 +02:00
} catch (ifcopenshell::exception& e) {
logger::error(e);
2023-09-17 12:30:17 +02:00
}
}
2017-07-03 14:18:56 +02:00
2026-03-31 15:32:36 +02:00
if constexpr (std::is_same_v<T, express::Base> || std::is_same_v<T, std::vector<express::Base>> || std::is_same_v<T, std::vector<std::vector<express::Base>>> || std::is_same_v<T, blank>) {
// Deregister inverse indices in file
unregister_inverse_visitor visitor(*file(), *this);
apply_individual_instance_visitor(current_attribute, (int)i).apply(visitor);
2023-09-17 12:30:17 +02:00
}
2025-02-27 22:07:31 +01:00
{
void* const storage = std::visit([](const auto& m) { return (void*)&m; }, file()->storage_);
data()->set_attribute_value(i, t);
}
2025-02-27 22:07:31 +01:00
auto new_attribute = get_attribute_value(i);
2023-09-17 12:30:17 +02:00
// Register inverse indices in file
if constexpr (std::is_same_v<T, express::Base> || std::is_same_v<T, std::vector<express::Base>> || std::is_same_v<T, std::vector<std::vector<express::Base>>>) {
register_inverse_visitor visitor(*file(), *this);
apply_individual_instance_visitor(new_attribute, (int)i).apply(visitor);
}
2024-08-23 20:29:07 +02:00
// 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()) {
2026-03-31 15:32:36 +02:00
logger::warning("Duplicate guid " + guid);
2023-09-17 12:30:17 +02:00
}
file()->internal_guid_map().insert({guid, *this});
2026-03-31 15:32:36 +02:00
} catch (ifcopenshell::exception& e) {
logger::error(e);
2023-09-17 12:30:17 +02:00
}
}
2017-06-05 17:26:58 +02:00
}
2024-08-23 20:29:07 +02:00
template <typename T>
2025-08-26 10:19:47 +02:00
typename std::enable_if<
(!std::is_base_of_v<express::Base, T> || std::is_same_v<express::Base, T>),
2025-08-26 10:19:47 +02:00
void>::type
express::Base::set_attribute_value(const std::string& s, const T& t)
{
2024-08-23 20:29:07 +02:00
set_attribute_value(declaration().as_entity()->attribute_index(s), t);
}
//
// Parses the IFC file in fn
// Creates the maps
//
2017-07-23 14:52:04 +02:00
#ifdef USE_MMAP
2026-03-31 15:32:36 +02:00
file::file(const std::string& fn, bool mmap) {
initialize(fn, mmap);
}
2026-03-31 15:32:36 +02:00
bool ifcopenshell::file::initialize(const std::string& fn, bool mmap) {
2025-10-13 20:47:43 +02:00
if (mmap) {
2026-03-31 15:32:36 +02:00
file_reader<mmap_impl> s(fn);
storage_.emplace<1>(this);
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
2025-10-13 20:47:43 +02:00
} else {
2026-03-31 15:32:36 +02:00
file_reader<full_buffer_impl> s(fn);
storage_.emplace<1>(this);
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
}
2025-10-13 20:47:43 +02:00
if ((good_ = std::get<impl::in_memory_file_storage>(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
2025-10-13 20:47:43 +02:00
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(storage_).byguid_);
}
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
2025-10-24 13:32:18 +02:00
return good_ == file_open_status::SUCCESS;
2017-07-23 14:52:04 +02:00
}
2025-10-13 20:47:43 +02:00
#endif
2026-03-31 15:32:36 +02:00
file::file(const uninitialized_tag&)
: schema_(nullptr), max_id_(0), header_(new ifcopenshell::spf_header(this)), good_(file_open_status::UNKNOWN), ifcroot_type_(nullptr) {}
2026-03-31 15:32:36 +02:00
bool ifcopenshell::file::initialize(const std::string& path, filetype ty, bool readonly) {
2025-03-13 13:09:39 +01:00
if (ty == FT_AUTODETECT) {
ty = guess_file_type(path);
}
if (ty == FT_IFCSPF) {
2026-03-31 15:32:36 +02:00
file_reader<full_buffer_impl> s(path);
storage_.emplace<1>(this);
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
2025-02-27 22:07:31 +01:00
2025-10-13 20:47:43 +02:00
if ((good_ = std::get<impl::in_memory_file_storage>(storage_).good_)) {
// @todo unify these names, it's already confusing enough as it stands
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(storage_).byguid_);
}
// byidentity_ = decltype(byidentity_)(&std::get<impl::in_memory_file_storage>(storage_).byidentity_);
2025-03-13 13:09:39 +01:00
} 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;
2025-02-25 21:29:54 +01:00
// @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<impl::rocks_db_file_storage>(storage_).db == nullptr) {
storage_.emplace<0>();
good_ = file_open_status::READ_ERROR;
} else {
if (std::get<impl::rocks_db_file_storage>(storage_).read_schema(schema_)) {
byid_ = decltype(byid_)(&std::get<impl::rocks_db_file_storage>(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::rocks_db_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::rocks_db_file_storage>(storage_).byguid_);
2026-04-22 12:04:09 +02:00
good_ = file_open_status::SUCCESS;
} else {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
}
}
// byidentity_ = decltype(byidentity_)(&std::get<impl::rocks_db_file_storage>(storage_).instance_cache_);
2025-03-13 13:09:39 +01:00
} else {
2025-08-31 15:52:46 +02:00
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;
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::bypass_type(const std::string& type_name) {
types_to_bypass_loading_.insert(type_name);
}
2026-03-31 15:32:36 +02:00
file::file(const std::string& path, filetype ty, bool readonly)
: schema_(nullptr)
, max_id_(0)
2026-03-31 15:32:36 +02:00
, header_(new spf_header(this))
{
initialize(path, ty, readonly);
}
2017-08-22 10:16:07 +02:00
2026-03-31 15:32:36 +02:00
file::file(std::istream& stream, int length)
: schema_(nullptr)
2025-09-01 13:25:48 +02:00
, max_id_(0)
2026-03-31 15:32:36 +02:00
, header_(new ifcopenshell::spf_header(this))
{
2026-03-31 15:32:36 +02:00
file_reader<pushed_sequential_impl> s(caller_fed_tag{});
2025-10-13 20:47:43 +02:00
std::string string_data;
string_data.resize(length);
stream.read(string_data.data(), length);
2026-03-31 15:32:36 +02:00
s.push_next_page(string_data);
2025-10-13 20:47:43 +02:00
storage_.emplace<1>(this);
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
2025-09-19 13:23:04 +02:00
good_ = std::get<impl::in_memory_file_storage>(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
2025-08-29 14:15:58 +02:00
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(storage_).byguid_);
}
2026-03-31 15:32:36 +02:00
file::file(void* data, int length)
: schema_(nullptr)
2026-01-14 14:03:28 +01:00
, max_id_(0),
2026-03-31 15:32:36 +02:00
header_(new ifcopenshell::spf_header(this))
{
2026-03-31 15:32:36 +02:00
file_reader<pushed_sequential_impl> s(std::string((char*)data, length), caller_fed_tag{});
2025-10-13 20:47:43 +02:00
storage_.emplace<1>(this);
std::get<impl::in_memory_file_storage>(storage_).read_from_stream(&s, schema_, max_id_, types_to_bypass_loading_);
2025-09-19 13:23:04 +02:00
good_ = std::get<impl::in_memory_file_storage>(storage_).good_;
ifcroot_type_ = schema_ ? schema_->declaration_by_name("IfcRoot") : nullptr;
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
2025-08-29 14:15:58 +02:00
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(storage_).byguid_);
2017-12-12 10:33:24 +01:00
}
2026-03-31 15:32:36 +02:00
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)
{
2025-03-13 13:09:39 +01:00
if (ty == FT_AUTODETECT) {
ty = guess_file_type(path);
}
if (ty == FT_IFCSPF) {
storage_.emplace<1>(this);
2025-02-27 22:07:31 +01:00
byid_ = decltype(byid_)(&std::get<impl::in_memory_file_storage>(storage_).byid_read_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::in_memory_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::in_memory_file_storage>(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get<impl::in_memory_file_storage>(storage_).byidentity_);
2025-03-13 13:09:39 +01:00
} else if (ty == FT_ROCKSDB) {
2025-02-25 21:29:54 +01:00
storage_.emplace<2>(path, this);
2025-02-27 22:07:31 +01:00
byid_ = decltype(byid_)(&std::get<impl::rocks_db_file_storage>(storage_).instance_by_name_);
byref_excl_ = decltype(byref_excl_)(&std::get<impl::rocks_db_file_storage>(storage_).byref_excl_);
byguid_ = decltype(byguid_)(&std::get<impl::rocks_db_file_storage>(storage_).byguid_);
// byidentity_ = decltype(byidentity_)(&std::get<impl::rocks_db_file_storage>(storage_).instance_cache_);
2025-03-13 13:09:39 +01:00
} else {
throw std::runtime_error("Unsupported file format");
2025-02-25 21:29:54 +01:00
}
2026-03-31 15:32:36 +02:00
header_.reset(new spf_header(this));
set_default_header_values();
}
namespace {
template <typename Reader>
2026-03-31 15:32:36 +02:00
void read_terminal(spf_lexer<Reader>& lexer, const std::string& term, bool trailing_semicolon) {
if (lexer.next().as_string() != term) {
throw exception(std::string("Expected " + term));
}
if (trailing_semicolon) {
2026-03-31 15:32:36 +02:00
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
}
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
std::shared_ptr<instance_data> read_header_entity(
ifcopenshell::file* file,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer<Reader>& lexer,
ifcopenshell::unresolved_references& references_to_resolve,
const ifcopenshell::entity& decl) {
2026-04-22 12:03:58 +02:00
parse_context pc;
2026-03-31 15:32:36 +02:00
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);
return result;
}
template <typename Reader>
void parse_header(
2026-03-31 15:32:36 +02:00
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer<Reader>& 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);
2026-04-09 16:18:53 +02:00
header.set_file_description(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_description::Class()));
2026-03-31 15:32:36 +02:00
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
read_terminal(lexer, Header_section_schema::file_name::Class().name_uc(), false);
2026-04-09 16:18:53 +02:00
header.set_file_name(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_name::Class()));
2026-03-31 15:32:36 +02:00
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
read_terminal(lexer, Header_section_schema::file_schema::Class().name_uc(), false);
2026-04-09 16:18:53 +02:00
header.set_file_schema(read_header_entity(header.owner_file(), storage, lexer, references_to_resolve, Header_section_schema::file_schema::Class()));
2026-03-31 15:32:36 +02:00
if (!lexer.next().is_operator(';')) {
throw exception("Expected ;");
}
}
template <typename Reader>
bool try_parse_header(
2026-03-31 15:32:36 +02:00
ifcopenshell::spf_header& header,
ifcopenshell::impl::in_memory_file_storage& storage,
spf_lexer<Reader>& lexer,
ifcopenshell::unresolved_references& references_to_resolve) {
try {
parse_header(header, storage, lexer, references_to_resolve);
return true;
} catch (const std::exception& e) {
2026-03-31 15:32:36 +02:00
logger::error(e);
return false;
}
}
} // namespace
template <typename Reader>
2026-03-31 15:32:36 +02:00
spf_header& ifcopenshell::instance_streamer<Reader>::ensure_header() {
if (header_) {
return *header_;
}
if (owner_ != nullptr) {
header_ = &owner_->header();
2026-04-09 16:18:53 +02:00
header_->owner_file(owner_);
} else {
2026-03-31 15:32:36 +02:00
owned_header_ = std::make_unique<spf_header>(owner_);
header_ = owned_header_.get();
}
return *header_;
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
void ifcopenshell::instance_streamer<Reader>::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 {
2026-03-31 15:32:36 +02:00
schema_ = ifcopenshell::schema_by_name(header.file_schema().schema_identifiers().front());
good_ = file_open_status::SUCCESS;
2026-03-31 15:32:36 +02:00
} catch (const ifcopenshell::exception&) {
}
}
storage_.schema = schema_;
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
bool ifcopenshell::instance_streamer<Reader>::has_semicolon() const {
2025-10-13 20:47:43 +02:00
auto local_stream = stream_->clone();
2026-03-31 15:32:36 +02:00
auto local_lexer = spf_lexer<Reader>(&local_stream);
token t;
2025-10-22 21:42:15 +02:00
try {
2026-03-31 15:32:36 +02:00
t = local_lexer.next();
2025-10-22 21:42:15 +02:00
} catch (const std::out_of_range&) {
return false;
}
2026-03-31 15:32:36 +02:00
while (t.type != token::Token_NONE) {
if (t.is_operator(';')) {
2025-10-13 20:47:43 +02:00
return true;
}
2025-10-22 21:42:15 +02:00
try {
2026-03-31 15:32:36 +02:00
t = local_lexer.next();
2025-10-22 21:42:15 +02:00
} catch (const std::out_of_range&) {
break;
}
2025-10-13 20:47:43 +02:00
}
return false;
2025-10-13 20:47:43 +02:00
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
size_t ifcopenshell::instance_streamer<Reader>::semicolon_count() const {
auto local_stream = stream_->clone();
2026-03-31 15:32:36 +02:00
auto local_lexer = spf_lexer<Reader>(&local_stream);
token t;
size_t count = 0;
try {
2026-03-31 15:32:36 +02:00
t = local_lexer.next();
} catch (const std::out_of_range&) {
return false;
}
2026-03-31 15:32:36 +02:00
while (t.type != token::Token_NONE) {
if (t.is_operator(';')) {
count++;
}
try {
2026-03-31 15:32:36 +02:00
t = local_lexer.next();
} catch (const std::out_of_range&) {
break;
}
}
return count;
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
void ifcopenshell::instance_streamer<Reader>::push_page(const std::string& page) {
stream_->push_next_page(page);
2025-10-13 20:47:43 +02:00
if (good_ == file_open_status::NO_HEADER) {
initialize_header();
2025-10-13 20:47:43 +02:00
}
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
ifcopenshell::instance_streamer<Reader>::instance_streamer(ifcopenshell::file* f)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
2026-03-31 15:32:36 +02:00
, token_stream_(3, token{})
2025-10-13 20:47:43 +02:00
, schema_(nullptr)
, progress_(0)
{
init_locale();
2026-03-31 15:32:36 +02:00
if constexpr (std::is_same_v<Reader, file_reader<full_buffer_impl>>) {
owned_stream_ = std::make_unique<Reader>(caller_fed_tag{});
} else if constexpr (std::is_same_v<Reader, file_reader<pushed_sequential_impl>>) {
owned_stream_ = std::make_unique<Reader>(caller_fed_tag{});
} else {
2026-03-31 15:32:36 +02:00
static_assert(file_reader_dependent_false_v<Reader>, "Default instance_streamer requires a pushed sequential reader");
}
stream_ = owned_stream_.get();
2026-03-31 15:32:36 +02:00
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_);
2025-10-13 20:47:43 +02:00
good_ = file_open_status::NO_HEADER;
2026-01-14 14:03:28 +01:00
storage_.file = f;
storage_.references_to_resolve = &references_to_resolve_;
2025-10-13 20:47:43 +02:00
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
ifcopenshell::instance_streamer<Reader>::instance_streamer(const std::string& fn, bool mmap, ifcopenshell::file* f)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
2026-03-31 15:32:36 +02:00
, token_stream_(3, token{})
2025-09-10 21:42:55 +02:00
, schema_(nullptr)
, progress_(0) {
2025-09-10 21:42:55 +02:00
init_locale();
2026-03-31 15:32:36 +02:00
if constexpr (std::is_same_v<Reader, file_reader<full_buffer_impl>>) {
(void)mmap;
owned_stream_ = std::make_unique<Reader>(fn);
#ifdef USE_MMAP
2026-03-31 15:32:36 +02:00
} else if constexpr (std::is_same_v<Reader, file_reader<mmap_impl>>) {
(void)mmap;
owned_stream_ = std::make_unique<Reader>(fn);
#endif
} else {
2026-03-31 15:32:36 +02:00
static_assert(file_reader_dependent_false_v<Reader>, "Path-based instance_streamer requires a file-backed reader");
}
stream_ = owned_stream_.get();
2026-03-31 15:32:36 +02:00
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_);
good_ = file_open_status::NO_HEADER;
initialize_header();
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
ifcopenshell::instance_streamer<Reader>::instance_streamer(void* data, int length, ifcopenshell::file* f)
: stream_(nullptr)
, header_(nullptr)
, owner_(f)
2026-03-31 15:32:36 +02:00
, token_stream_(3, token{})
, schema_(nullptr)
, progress_(0)
{
init_locale();
2026-03-31 15:32:36 +02:00
if constexpr (std::is_same_v<Reader, file_reader<full_buffer_impl>>) {
owned_stream_ = std::make_unique<Reader>(std::string((char*)data, length), caller_fed_tag{});
} else if constexpr (std::is_same_v<Reader, file_reader<pushed_sequential_impl>>) {
owned_stream_ = std::make_unique<Reader>(std::string((char*)data, length), caller_fed_tag{});
} else {
2026-03-31 15:32:36 +02:00
static_assert(file_reader_dependent_false_v<Reader>, "Buffer-based instance_streamer requires a pushed sequential reader");
2025-09-10 21:42:55 +02:00
}
stream_ = owned_stream_.get();
2026-03-31 15:32:36 +02:00
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_);
good_ = file_open_status::NO_HEADER;
initialize_header();
2025-09-10 21:42:55 +02:00
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
ifcopenshell::instance_streamer<Reader>::instance_streamer(Reader* stream, ifcopenshell::file* f)
: stream_(stream)
2025-09-10 21:42:55 +02:00
, header_(nullptr)
, owner_(f)
2026-03-31 15:32:36 +02:00
, token_stream_(3, token{})
, schema_(nullptr)
, progress_(0) {
2025-09-10 21:42:55 +02:00
init_locale();
2026-03-31 15:32:36 +02:00
lexer_ = std::make_unique<spf_lexer<Reader>>(stream_);
good_ = file_open_status::NO_HEADER;
initialize_header();
2025-09-10 21:42:55 +02:00
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
void ifcopenshell::instance_streamer<Reader>::bypass_types(const std::set<std::string>& type_names) {
for (auto& name : type_names) {
try {
types_to_bypass_.push_back(schema_->declaration_by_name(name));
2026-03-31 15:32:36 +02:00
} catch (const exception&) {
continue;
}
}
}
template <typename Reader>
2026-03-31 15:32:36 +02:00
std::optional<std::tuple<size_t, const ifcopenshell::declaration*, std::shared_ptr<instance_data>>> ifcopenshell::instance_streamer<Reader>::read_instance() {
std::optional<std::tuple<size_t, const ifcopenshell::declaration*, std::shared_ptr<instance_data>>> 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) {
2026-03-31 15:32:36 +02:00
if (token_stream_[0].type == ifcopenshell::token::Token_IDENTIFIER &&
token_stream_[1].type == ifcopenshell::token::Token_OPERATOR &&
token_stream_[1].value_char == '=' &&
2026-03-31 15:32:36 +02:00
token_stream_[2].type == ifcopenshell::token::Token_KEYWORD) {
current_id = token_stream_[0].as_identifier();
2026-03-31 15:32:36 +02:00
const ifcopenshell::declaration* entity_type;
try {
entity_type = schema_->declaration_by_name(token_stream_[2].as_string());
2026-03-31 15:32:36 +02:00
} 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) {
2026-03-31 15:32:36 +02:00
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;
}
}
2026-04-22 12:03:58 +02:00
parse_context ps;
2026-03-31 15:32:36 +02:00
lexer_->next();
try {
storage_.load(lexer_.get(), current_id, entity_type->as_entity(), ps, -1);
2026-03-31 15:32:36 +02:00
} catch (const invalid_token_exception& e) {
good_ = file_open_status::INVALID_SYNTAX;
2026-03-31 15:32:36 +02:00
logger::error(e);
break;
}
if (((++progress_) % 1000) == 0) {
std::stringstream ss;
ss << "\r#" << current_id;
2026-03-31 15:32:36 +02:00
logger::status(ss.str(), false);
}
auto data = ps.construct(owner_, current_id, references_to_resolve_, entity_type, std::nullopt, -1, coerce_attribute_count);
return_value.emplace(
(size_t)current_id,
entity_type,
data);
}
advance:
2026-03-31 15:32:36 +02:00
token next_token;
try {
2026-03-31 15:32:36 +02:00
next_token = lexer_->next();
} catch (const exception& e) {
logger::message(logger::LOG_ERROR, std::string(e.what()) + ". Parsing terminated");
} catch (...) {
2026-03-31 15:32:36 +02:00
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);
}
2026-03-31 15:32:36 +02:00
stream_->drop_pages();
lexer_->reset_pool();
return return_value;
}
2026-04-21 11:55:04 +02:00
template class IFC_PARSE_API ifcopenshell::instance_streamer<file_reader<full_buffer_impl>>;
2026-03-31 15:32:36 +02:00
template <typename Reader>
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::in_memory_file_storage::read_from_stream(Reader* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass) {
2023-09-17 12:30:17 +02:00
init_locale();
schema = nullptr;
2025-10-13 20:47:43 +02:00
if (!s->size() || s->eof()) {
2023-09-17 12:30:17 +02:00
good_ = file_open_status::READ_ERROR;
return;
}
2023-09-17 12:30:17 +02:00
std::vector<std::string> schemas;
2017-12-12 10:33:24 +01:00
2026-03-31 15:32:36 +02:00
instance_streamer<Reader> streamer(s, file);
streamer.yield_header_instances(false);
if (const auto* header = streamer.header()) {
2023-09-17 12:30:17 +02:00
try {
schemas = header->file_schema().schema_identifiers();
2023-09-17 12:30:17 +02:00
} catch (...) {
}
}
schema = streamer.schema();
if (schema == nullptr && schemas.size() == 1) {
2023-09-17 12:30:17 +02:00
try {
2026-03-31 15:32:36 +02:00
schema = ifcopenshell::schema_by_name(schemas.front());
} catch (const ifcopenshell::exception& e) {
2023-09-17 12:30:17 +02:00
good_ = file_open_status::UNSUPPORTED_SCHEMA;
2026-03-31 15:32:36 +02:00
logger::error(e);
2023-09-17 12:30:17 +02:00
}
}
if (schema == nullptr) {
if (schemas.empty()) {
good_ = streamer.status();
} else {
good_ = file_open_status::UNSUPPORTED_SCHEMA;
}
2026-03-31 15:32:36 +02:00
logger::message(logger::LOG_ERROR, "No support for file schema encountered (" + boost::algorithm::join(schemas, ", ") + ")");
2023-09-17 12:30:17 +02:00
return;
}
auto ifcroot_type_ = schema->declaration_by_name("IfcRoot");
2026-03-31 15:32:36 +02:00
streamer.bypass_types(typed_to_bypass);
2023-09-17 12:30:17 +02:00
2026-03-31 15:32:36 +02:00
logger::status("Scanning file...");
while (streamer) {
2026-03-31 15:32:36 +02:00
auto inst = streamer.read_instance();
2023-09-17 12:30:17 +02:00
if (!inst) {
break;
}
auto current_id = std::get<0>(*inst);
express::Base instance(std::get<2>(*inst));
2023-09-17 12:30:17 +02:00
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;
2026-03-31 15:32:36 +02:00
logger::message(logger::LOG_WARNING, ss.str());
2023-09-17 12:30:17 +02:00
}
byguid_[guid] = instance;
2026-03-31 15:32:36 +02:00
} catch (const exception& ex) {
logger::message(logger::LOG_ERROR, ex.what());
2023-09-17 12:30:17 +02:00
}
}
2023-09-17 12:30:17 +02:00
2026-03-31 15:32:36 +02:00
const ifcopenshell::declaration* ty = &instance.declaration();
bytype_excl_[ty].push_back(instance);
2023-09-17 12:30:17 +02:00
if (byid_.find(current_id) != byid_.end()) {
std::stringstream ss;
ss << "Overwriting instance with name #" << current_id;
2026-03-31 15:32:36 +02:00
logger::message(logger::LOG_WARNING, ss.str());
2023-09-17 12:30:17 +02:00
}
byid_.insert({(uint32_t)current_id, std::get<2>(*inst)});
max_id = (std::max)(max_id, (unsigned int)current_id);
2023-09-17 12:30:17 +02:00
}
2017-06-05 17:26:58 +02:00
good_ = streamer.status();
byref_excl_ = streamer.inverses();
2026-03-31 15:32:36 +02:00
read_simple_type_instances = streamer.steal_instances();
2026-03-31 15:32:36 +02:00
logger::status("\rDone scanning file ");
2017-06-05 17:26:58 +02:00
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<reference_or_simple_type>(&p.second)) {
2026-03-31 15:32:36 +02:00
if (auto* name = std::get_if<instance_reference>(v)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
2026-03-31 15:32:36 +02:00
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_;
2026-04-09 16:18:53 +02:00
if (storage->template has_attribute_value<express::Base>(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;
}
}
2026-04-09 16:18:53 +02:00
if (storage->template has_attribute_value<blank>(attr_index)) {
storage->set_attribute_value(attr_index, express::Base(it->second));
} else {
2026-03-31 15:32:36 +02:00
logger::error("Duplicate definition for instance reference");
}
2024-08-23 20:29:07 +02:00
}
} else if (auto inst = std::get_if<express::Base>(v)) {
byid_[p.first.name_]->set_attribute_value(p.first.index_, *inst);
}
2025-10-13 20:47:43 +02:00
} else if (auto* vv = std::get_if<std::vector<reference_or_simple_type>>(&p.second)) {
std::vector<express::Base> instances;
instances.reserve(vv->size());
2025-10-13 20:47:43 +02:00
for (const auto& vi : *vv) {
2026-03-31 15:32:36 +02:00
if (auto* name = std::get_if<instance_reference>(&vi)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
2026-03-31 15:32:36 +02:00
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));
2024-08-23 20:29:07 +02:00
}
} else if (auto* inst = std::get_if<express::Base>(&vi)) {
instances.push_back(*inst);
2024-08-23 20:29:07 +02:00
}
}
auto& storage = byid_[p.first.name_];
auto attr_index = p.first.index_;
2026-04-09 16:18:53 +02:00
if (storage->template has_attribute_value<express::Base>(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;
}
}
2026-04-09 16:18:53 +02:00
if (storage->template has_attribute_value<blank>(attr_index)) {
storage->set_attribute_value(attr_index, instances);
} else {
2026-03-31 15:32:36 +02:00
logger::error("Duplicate definition for instance reference");
}
2025-10-13 20:47:43 +02:00
} else if (auto* vvv = std::get_if<std::vector<std::vector<reference_or_simple_type>>>(&p.second)) {
std::vector<std::vector<express::Base>> instances;
2025-10-13 20:47:43 +02:00
for (const auto& vi : *vvv) {
auto& inner = instances.emplace_back();
for (const auto& vii : vi) {
2026-03-31 15:32:36 +02:00
if (auto* name = std::get_if<instance_reference>(&vii)) {
if (std::binary_search(bypassed.begin(), bypassed.end(), *name)) {
continue;
}
auto it = byid_.find(*name);
if (it == byid_.end()) {
2026-03-31 15:32:36 +02:00
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));
2024-08-23 20:29:07 +02:00
}
} else if (auto* inst = std::get_if<express::Base>(&vii)) {
inner.push_back(*inst);
2024-08-23 20:29:07 +02:00
}
}
}
auto& storage = byid_[p.first.name_];
auto attr_index = p.first.index_;
2026-04-09 16:18:53 +02:00
if (storage->template has_attribute_value<express::Base>(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;
}
}
2026-04-09 16:18:53 +02:00
if (storage->template has_attribute_value<blank>(attr_index)) {
storage->set_attribute_value(attr_index, instances);
} else {
2026-03-31 15:32:36 +02:00
logger::error("Duplicate definition for instance reference");
}
}
2024-08-23 20:29:07 +02:00
}
2023-09-17 12:30:17 +02:00
2026-03-31 15:32:36 +02:00
logger::status("Done resolving references");
}
2026-03-31 15:32:36 +02:00
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader<full_buffer_impl>* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass);
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader<pushed_sequential_impl>* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass);
#ifdef USE_MMAP
2026-03-31 15:32:36 +02:00
template void ifcopenshell::impl::in_memory_file_storage::read_from_stream(file_reader<mmap_impl>* s, const ifcopenshell::schema_definition*& schema, unsigned int& max_id, const std::set<std::string>& typed_to_bypass);
#endif
2026-03-31 15:32:36 +02:00
void file::recalculate_id_counter() {
/*
// @todo
2023-09-17 12:30:17 +02:00
entity_by_id_t::key_type k = 0;
for (auto& p : byid_) {
2023-09-17 12:30:17 +02:00
if (p.first > k) {
k = p.first;
}
}
max_id_ = (unsigned int)k;
*/
2021-06-26 13:23:55 +02:00
}
class traversal_recorder {
std::vector<express::Base> list_;
std::map<int, std::vector<express::Base>> instances_by_level_;
2023-09-17 12:30:17 +02:00
int mode_;
public:
traversal_recorder(int mode) : mode_(mode) {
};
void push_back(int level, const express::Base& instance) {
2023-09-17 12:30:17 +02:00
if (mode_ == 0) {
list_.push_back(instance);
2023-09-17 12:30:17 +02:00
} else {
auto& l = instances_by_level_[level];
l.push_back(instance);
2023-09-17 12:30:17 +02:00
}
}
std::vector<express::Base> get_list() const {
2023-09-17 12:30:17 +02:00
if (mode_ == 0) {
return list_;
}
std::vector<express::Base> l;
for (const auto& p : instances_by_level_) {
l.insert(l.end(), p.second.begin(), p.second.end());
}
return l;
2023-09-17 12:30:17 +02:00
}
};
2017-08-01 19:07:46 +02:00
class traversal_visitor {
2023-09-17 12:30:17 +02:00
private:
std::set<express::Base>& visited_;
2023-09-17 12:30:17 +02:00
traversal_recorder& list_;
int level_;
int max_level_;
public:
traversal_visitor(std::set<express::Base>& visited, traversal_recorder& list, int level, int max_level)
2023-09-17 12:30:17 +02:00
: visited_(visited),
list_(list),
level_(level),
max_level_(max_level) {}
void operator()(const express::Base& inst, int index);
2017-08-01 19:07:46 +02:00
};
void traverse_(const express::Base& instance, std::set<express::Base>& visited, traversal_recorder& list, int level, int max_level) {
2023-09-17 12:30:17 +02:00
if (visited.find(instance) != visited.end()) {
return;
}
visited.insert(instance);
list.push_back(level, instance);
2023-09-17 12:30:17 +02:00
if (level >= max_level && max_level > 0) {
return;
}
2023-09-17 12:30:17 +02:00
traversal_visitor visit(visited, list, level + 1, max_level);
2025-02-27 22:07:31 +01:00
apply_individual_instance_visitor(instance).apply(visit);
}
void traversal_visitor::operator()(const express::Base& inst, int /* index */) {
2023-09-17 12:30:17 +02:00
traverse_(inst, visited_, list_, level_, max_level_);
2017-08-01 19:07:46 +02:00
}
2026-03-31 15:32:36 +02:00
std::vector<express::Base> ifcopenshell::traverse(const express::Base& instance, int max_level) {
std::set<express::Base> 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?
2026-03-31 15:32:36 +02:00
std::vector<express::Base> ifcopenshell::traverse_breadth_first(const express::Base& instance, int max_level) {
std::set<express::Base> visited;
traversal_recorder recorder(1);
traverse_(instance, visited, recorder, 0, max_level);
return recorder.get_list();
}
/// @note: for backwards compatibility
2026-03-31 15:32:36 +02:00
std::vector<express::Base> file::traverse(const express::Base& instance, int max_level) {
return ifcopenshell::traverse(instance, max_level);
}
/// @note: for backwards compatibility
2026-03-31 15:32:36 +02:00
std::vector<express::Base> file::traverse_breadth_first(const express::Base& instance, int max_level) {
return ifcopenshell::traverse_breadth_first(instance, max_level);
}
2026-03-31 15:32:36 +02:00
express::Base file::add_entity(const express::Base& entity, int id) {
if (entity.file() == this) {
return entity;
2023-09-17 12:30:17 +02:00
}
if (entity.declaration().schema() != schema()) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell::exception("Unabled to add instance from " + entity.declaration().schema()->name() + " schema to file with " + schema()->name() + " schema");
2023-09-17 12:30:17 +02:00
}
2023-09-17 12:30:17 +02:00
// 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()) {
2023-09-17 12:30:17 +02:00
return mit->second;
}
2023-09-17 12:30:17 +02:00
// Obtain all forward references by a depth-first
// traversal and add them to the file.
2024-08-23 20:29:07 +02:00
try {
auto entity_attributes = traverse(entity, 1);
for (auto it = entity_attributes.begin() + 1; it != entity_attributes.end(); ++it) {
2024-08-23 20:29:07 +02:00
if (*it != entity) {
entity_entity_map_t::iterator mit2 = entity_file_map_.find(it->identity());
2024-08-23 20:29:07 +02:00
if (mit2 == entity_file_map_.end()) {
2026-03-31 15:32:36 +02:00
entity_file_map_.insert(entity_entity_map_t::value_type(it->identity(), add_entity(*it)));
2023-09-17 12:30:17 +02:00
}
}
}
2024-08-23 20:29:07 +02:00
} catch (...) {
2026-03-31 15:32:36 +02:00
logger::message(logger::LOG_ERROR, "Failed to visit forward references of", entity);
2023-09-17 12:30:17 +02:00
}
// 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.
2026-03-31 15:32:36 +02:00
file* other_file = entity.file();
2026-01-08 11:49:29 +01:00
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<decltype(v)>;
// only need to copy non-instance attribute values, others are assigned below after mapping
if constexpr (std::is_same_v<U, express::Base>) {
} else if constexpr (std::is_same_v<U, std::vector<express::Base>>) {
} else if constexpr (std::is_same_v<U, std::vector<std::vector<express::Base>>>) {
} else {
new_entity.set_attribute_value(i, v);
}
});
}
2025-02-27 22:07:31 +01:00
// 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<double>::quiet_NaN() : 1.0;
for (size_t i = 0; i < num_attributes; ++i) {
// old attribute value
auto attr = entity.get_attribute_value(i);
2026-03-31 15:32:36 +02:00
ifcopenshell::argument_type attr_type = attr.type();
2026-03-31 15:32:36 +02:00
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();
}
}
2026-03-31 15:32:36 +02:00
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()) {
2026-03-31 15:32:36 +02:00
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);
2026-03-31 15:32:36 +02:00
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE) {
std::vector<express::Base> instances = attr;
std::vector<express::Base> 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()) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell::exception("Unable to map instance to file");
2023-09-17 12:30:17 +02:00
}
new_instances.push_back(eit->second);
}
new_entity.set_attribute_value(i, new_instances);
2026-03-31 15:32:36 +02:00
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE) {
std::vector<std::vector<express::Base>> instances = attr;
std::vector<std::vector<express::Base>> 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()) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell::exception("Unable to map instance to file");
2023-09-17 12:30:17 +02:00
}
new_instances.back().push_back(eit->second);
2023-09-17 12:30:17 +02:00
}
}
2024-08-23 20:29:07 +02:00
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<express::Base, double> this_file_unit = {express::Base{}, 1.0};
std::pair<express::Base, double> other_file_unit = {express::Base{}, 1.0};
try {
2026-03-31 15:32:36 +02:00
this_file_unit = get_unit("LENGTHUNIT");
other_file_unit = other_file->get_unit("LENGTHUNIT");
} catch (ifcopenshell::exception&) {
2023-09-17 12:30:17 +02:00
}
if (this_file_unit.first && other_file_unit.first) {
conversion_factor = other_file_unit.second / this_file_unit.second;
} else {
conversion_factor = 1.;
}
}
2026-03-31 15:32:36 +02:00
if (attr_type == ifcopenshell::Argument_DOUBLE) {
double v = attr;
v *= conversion_factor;
new_entity.set_attribute_value(i, v);
2026-03-31 15:32:36 +02:00
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_DOUBLE) {
std::vector<double> v = attr;
for (std::vector<double>::iterator it = v.begin(); it != v.end(); ++it) {
(*it) *= conversion_factor;
}
new_entity.set_attribute_value(i, v);
2026-03-31 15:32:36 +02:00
} else if (attr_type == ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE) {
std::vector<std::vector<double>> v = attr;
for (std::vector<std::vector<double>>::iterator it = v.begin(); it != v.end(); ++it) {
std::vector<double>& v2 = (*it);
for (std::vector<double>::iterator jt = v2.begin(); jt != v2.end(); ++jt) {
(*jt) *= conversion_factor;
2023-09-17 12:30:17 +02:00
}
}
new_entity.set_attribute_value(i, v);
2023-09-17 12:30:17 +02:00
}
}
}
entity_file_map_.insert(entity_entity_map_t::value_type(entity.identity(), new_entity));
2023-09-17 12:30:17 +02:00
// For subtypes of IfcRoot, the GUID mapping needs to be updated.
2026-01-07 13:51:48 +01:00
/*
if (decl->is(*ifcroot_type_)) {
2023-09-17 12:30:17 +02:00
try {
const std::string guid = new_entity.get_attribute_value(0);
if (byguid_.find(guid) != byguid_.end()) {
2023-09-17 12:30:17 +02:00
std::stringstream ss;
ss << "Overwriting entity with guid " << guid;
2026-03-31 15:32:36 +02:00
logger::message(logger::LOG_WARNING, ss.str());
2023-09-17 12:30:17 +02:00
}
byguid_.insert({ guid, new_entity });
2024-08-23 20:29:07 +02:00
} catch (const std::exception& ex) {
2026-03-31 15:32:36 +02:00
logger::message(logger::LOG_ERROR, ex.what());
2023-09-17 12:30:17 +02:00
}
}
2026-01-07 13:51:48 +01:00
*/
2026-01-07 13:51:48 +01:00
// add_type_ref(new_entity);
2023-09-17 12:30:17 +02:00
return new_entity;
}
2026-03-31 15:32:36 +02:00
void file::remove_entity(const express::Base& entity) {
auto id = entity.id();
2023-09-17 12:30:17 +02:00
auto file_entity = instance_by_id(id);
2023-09-17 12:30:17 +02:00
2026-03-31 15:32:36 +02:00
// Attention when running remove_entity inside a loop over a list of entities to be removed.
2023-09-17 12:30:17 +02:00
// This invalidates the iterator. A workaround is to reverse the loop:
// boost::shared_ptr<aggregate_of_instance> entities = ...;
// for (auto it = entities->end() - 1; it >= entities->begin(); --it) {
// express::Base *const inst = *it;
2026-03-31 15:32:36 +02:00
// model->remove_entity(inst);
2023-09-17 12:30:17 +02:00
// }
// 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<IfcSchema::Type::Enum> weak_roots;
if (entity != file_entity) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell::exception("Instance not part of this file");
2023-09-17 12:30:17 +02:00
}
2025-08-31 15:52:46 +02:00
if (batch_mode_) {
batch_deletion_ids_.push_back(id);
} else {
process_deletion_(entity);
byid_.erase(entity.id());
2025-08-31 15:52:46 +02:00
}
}
2026-03-31 15:32:36 +02:00
void file::process_deletion_(const express::Base& entity) {
2025-08-31 15:52:46 +02:00
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;
}
2023-09-17 12:30:17 +02:00
const auto& decl = related_instance.declaration();
2025-02-27 22:07:31 +01:00
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()) {
2023-09-17 12:30:17 +02:00
continue;
}
2026-03-31 15:32:36 +02:00
ifcopenshell::argument_type attr_type = attr.type();
switch (attr_type) {
2026-03-31 15:32:36 +02:00
case ifcopenshell::Argument_ENTITY_INSTANCE: {
express::Base instance_attribute = attr;
if (instance_attribute == entity) {
2026-03-31 15:32:36 +02:00
related_instance.set_attribute_value(i, blank{});
2023-09-17 12:30:17 +02:00
}
} break;
2026-03-31 15:32:36 +02:00
case ifcopenshell::Argument_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector<express::Base> 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.
2026-03-31 15:32:36 +02:00
related_instance.set_attribute_value(i, blank{});
} else {
related_instance.set_attribute_value(i, instance_list);
2023-09-17 12:30:17 +02:00
}
}
} break;
2026-03-31 15:32:36 +02:00
case ifcopenshell::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: {
std::vector<std::vector<express::Base>> 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;
2023-09-17 12:30:17 +02:00
}
}
related_instance.set_attribute_value(i, instance_list_list);
} break;
default:
break;
2023-09-17 12:30:17 +02:00
}
}
}
}
2021-03-13 12:16:40 +01:00
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 {
2026-03-31 15:32:36 +02:00
logger::warning("GlobalId on rooted instance not encountered in map");
}
}
2023-09-17 12:30:17 +02:00
2025-08-29 13:48:00 +02:00
process_deletion_inverse(entity);
2025-08-31 15:52:46 +02:00
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;
2023-09-17 12:30:17 +02:00
}
}
}
2023-09-17 12:30:17 +02:00
2026-03-31 15:32:36 +02:00
void ifcopenshell::impl::in_memory_file_storage::process_deletion_inverse(const express::Base& entity) {
auto id = entity.id();
2023-09-17 12:30:17 +02:00
// Delete inverses into entity
2026-04-10 14:54:47 +02:00
byref_excl_.erase(id);
2023-09-17 12:30:17 +02:00
// 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;
2023-09-17 12:30:17 +02:00
}
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
2026-04-10 14:54:47 +02:00
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());
}
2023-09-17 12:30:17 +02:00
}
}
2025-02-24 20:46:29 +01:00
}
}
2024-08-23 20:29:07 +02:00
namespace {
template <typename Fn>
2026-03-31 15:32:36 +02:00
void visit_subtypes(const ifcopenshell::entity* ent, Fn fn) {
2024-08-23 20:29:07 +02:00
fn(ent);
for (const auto& st : ent->subtypes()) {
visit_subtypes(st, fn);
}
}
template <typename Fn>
2026-03-31 15:32:36 +02:00
void visit_supertypes(const ifcopenshell::entity* ent, Fn fn) {
2024-08-23 20:29:07 +02:00
fn(ent);
if (ent->supertype()) {
visit_supertypes(ent->supertype(), fn);
}
}
}
2026-03-31 15:32:36 +02:00
std::vector<express::Base> file::instances_by_type(const ifcopenshell::declaration* t) {
std::vector<express::Base> insts;
2024-08-23 20:29:07 +02:00
if (t->as_entity() != nullptr) {
2026-03-31 15:32:36 +02:00
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());
2024-08-23 20:29:07 +02:00
});
}
return insts;
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
std::vector<express::Base> file::instances_by_type_excl_subtypes(const ifcopenshell::declaration* t) {
return std::visit([t](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
auto it = x.bytype_excl_.find(t);
return (it == x.bytype_excl_.end()) ? std::vector<express::Base>{} : it->second;
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
std::vector<express::Base> ret;
auto it = x.bytype_.find(t->index_in_schema());
2025-02-27 22:07:31 +01:00
if (it != x.bytype_.end()) {
const auto& s = it->second;
// @todo generalize this, bytype_ should be a map_adapter
std::vector<size_t> vals(s.size() / sizeof(size_t));
memcpy(vals.data(), s.data(), s.size());
for (auto& v : vals) {
2026-03-31 15:32:36 +02:00
ret.push_back(x.assert_existance(v, ifcopenshell::impl::rocks_db_file_storage::entityinstance_ref));
2025-02-27 22:07:31 +01:00
}
}
return ret;
} else {
throw std::runtime_error("Storage not initialized");
std::vector<express::Base> ret;
return ret;
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
std::vector<express::Base> file::instances_by_type(const std::string& t) {
2023-09-17 12:30:17 +02:00
return instances_by_type(schema()->declaration_by_name(t));
2012-07-19 00:14:01 +00:00
}
2026-03-31 15:32:36 +02:00
std::vector<express::Base> file::instances_by_type_excl_subtypes(const std::string& t) {
2023-09-17 12:30:17 +02:00
return instances_by_type_excl_subtypes(schema()->declaration_by_name(t));
2020-07-11 11:55:00 +02:00
}
2026-03-31 15:32:36 +02:00
std::vector<express::Base> file::instances_by_reference(int t) {
std::vector<express::Base> ret;
std::visit([this, t, &ret](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
2026-04-10 14:54:47 +02:00
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<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
// @todo no lower/upper_bounds() implemented yet
auto prefix = "v|" + std::to_string(t) + "|";
2025-09-01 14:32:13 +02:00
auto it = std::unique_ptr<rocksdb::Iterator>(x.db->NewIterator(rocksdb::ReadOptions()));
it->Seek(prefix);
while (it->Valid() && it->key().starts_with(prefix)) {
std::vector<uint32_t> 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");
2024-08-23 20:29:07 +02:00
}
}, storage_);
2023-09-17 12:30:17 +02:00
return ret;
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
express::Base file::instance_by_id(int id) {
2025-02-24 20:46:29 +01:00
return std::visit([id](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
return express::Base{};
} else {
return x.instance_by_id(id);
}
}, storage_);
2011-05-08 11:04:32 +00:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::add_type_ref(const express::Base& new_entity)
{
std::visit([new_entity](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.add_type_ref(new_entity);
}
}, storage_);
2012-03-13 11:56:52 +00:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::remove_type_ref(const express::Base& new_entity) {
std::visit([new_entity](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.remove_type_ref(new_entity);
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::process_deletion_inverse(const express::Base& inst) {
std::visit([inst](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.process_deletion_inverse(inst);
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
express::Base file::instance_by_guid(const std::string& guid) {
auto it = byguid_.find(guid);
if (it == byguid_.end()) {
2026-03-31 15:32:36 +02:00
throw exception("Instance with GlobalId '" + guid + "' not found");
}
return it->second;
}
2026-03-31 15:32:36 +02:00
file::type_iterator file::types_begin() const {
return std::visit([](const auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
2026-03-31 15:32:36 +02:00
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
2026-03-31 15:32:36 +02:00
return file::type_iterator{ x.bytype_excl_.begin() };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
2026-03-31 15:32:36 +02:00
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator(&x) };
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
file::type_iterator file::types_end() const {
return std::visit([](const auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
2026-03-31 15:32:36 +02:00
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
2026-03-31 15:32:36 +02:00
return file::type_iterator{ x.bytype_excl_.end() };
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
2026-03-31 15:32:36 +02:00
return file::type_iterator{ impl::rocks_db_file_storage::rocksdb_types_iterator{} };
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
std::ostream& operator<<(std::ostream& out, const ifcopenshell::file& file) {
file.header().write(out);
typedef std::vector<express::Base> vector_t;
vector_t sorted;
2025-02-25 21:29:54 +01:00
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(); });
2018-11-13 14:46:15 +01:00
for (auto& e : sorted) {
// @todo this check should no longer be necessary?
if (e.declaration().as_entity() != nullptr) {
2026-03-31 15:32:36 +02:00
e.to_string(out, true);
2024-08-23 20:29:07 +02:00
out << ";" << std::endl;
2023-09-17 12:30:17 +02:00
}
}
out << "ENDSEC;" << std::endl;
out << "END-ISO-10303-21;" << std::endl;
return out;
}
2026-03-31 15:32:36 +02:00
std::string file::create_timestamp() {
2023-09-17 12:30:17 +02:00
char buf[255];
time_t t;
time(&t);
struct tm* ti = localtime(&t);
2015-01-16 16:26:40 +00:00
2024-08-23 20:29:07 +02:00
std::string result;
if (strftime(buf, 255, "%Y-%m-%dT%H:%M:%S", ti) != 0U) {
2023-09-17 12:30:17 +02:00
result = std::string(buf);
}
return result;
2015-01-10 22:13:52 +00:00
}
2026-03-31 15:32:36 +02:00
const ifcopenshell::schema_definition* file::schema() const {
2025-09-19 13:23:04 +02:00
if (schema_ == nullptr) {
2026-03-31 15:32:36 +02:00
throw exception("No schema loaded");
2025-09-19 13:23:04 +02:00
}
return schema_;
}
2026-05-06 11:41:43 +02:00
std::vector<int> file::get_inverse_indices_by_id(int instance_id) {
2023-09-17 12:30:17 +02:00
std::vector<int> return_value;
2023-09-17 12:30:17 +02:00
// Mapping of instance id to attribute offset.
std::map<int, std::vector<int>> mapping;
std::visit([&mapping, instance_id](const auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
2026-04-10 14:54:47 +02:00
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));
}
2023-09-17 12:30:17 +02:00
}
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
2025-09-14 13:53:20 +02:00
#ifdef IFOPSH_WITH_ROCKSDB
2025-08-31 21:27:21 +02:00
// @todo no lower/upper_bounds() implemented yet
auto prefix = "v|" + std::to_string(instance_id) + "|";
2025-09-01 14:32:13 +02:00
auto it = std::unique_ptr<rocksdb::Iterator>(x.db->NewIterator(rocksdb::ReadOptions()));
2025-08-31 21:27:21 +02:00
it->Seek(prefix);
while (it->Valid() && it->key().starts_with(prefix)) {
std::vector<uint32_t> vals(it->value().size() / sizeof(uint32_t));
memcpy(vals.data(), it->value().data(), it->value().size());
auto tuple = key_from_string<std::tuple<int, int, int>>(it->key().ToString().substr(2));
for (auto& i : vals) {
mapping[i].push_back(std::get<2>(tuple));
}
it->Next();
}
2025-09-14 13:53:20 +02:00
#endif
2023-09-17 12:30:17 +02:00
}
}, storage_);
2023-09-17 12:30:17 +02:00
auto refs = instances_by_reference(instance_id);
for (const auto& ref : refs) {
auto it = mapping.find(ref.id());
2023-09-17 12:30:17 +02:00
if (it == mapping.end() || it->second.empty()) {
2026-03-31 15:32:36 +02:00
throw exception("Internal error");
2023-09-17 12:30:17 +02:00
}
return_value.push_back(it->second.front());
it->second.erase(it->second.begin());
if (it->second.empty()) {
mapping.erase(it);
}
}
2023-09-17 12:30:17 +02:00
// Test whether all mappings where indeed used.
if (!mapping.empty()) {
2026-03-31 15:32:36 +02:00
throw exception("Internal error");
2023-09-17 12:30:17 +02:00
}
return return_value;
}
2026-03-31 15:32:36 +02:00
std::vector<express::Entity> file::get_inverse(int instance_id, const ifcopenshell::declaration* type, int attribute_index) {
std::vector<express::Entity> return_value;
2023-09-17 12:30:17 +02:00
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<express::Entity>());
}
return return_value;
2023-09-17 12:30:17 +02:00
}
2026-04-10 14:54:47 +02:00
std::visit([&return_value, this, attribute_index, instance_id, type](const auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, 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<short>::min()});
auto upper = submap->second.upper_bound({ent->index_in_schema(), std::numeric_limits<short>::max()});
for (auto it = lower; it != upper; ++it) {
for (auto& i : it->second) {
return_value.push_back(instance_by_id(i).template as<express::Entity>());
}
}
2026-04-10 14:54:47 +02:00
} 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<express::Entity>());
}
}
}
2026-04-10 14:54:47 +02:00
});
}
2026-04-10 14:54:47 +02:00
}
#ifdef IFOPSH_WITH_ROCKSDB
else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, 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()) + "|";
2025-09-01 14:32:13 +02:00
auto it = std::unique_ptr<rocksdb::Iterator>(x.db->NewIterator(rocksdb::ReadOptions()));
it->Seek(prefix);
while (it->Valid() && it->key().starts_with(prefix)) {
std::vector<uint32_t> vals(it->value().size() / sizeof(uint32_t));
memcpy(vals.data(), it->value().data(), it->value().size());
for (auto& v : vals) {
2026-01-07 20:36:07 +01:00
return_value.push_back(instance_by_id(v).template as<express::Entity>());
}
it->Next();
}
} else {
2026-04-10 14:54:47 +02:00
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) {
2026-01-07 20:36:07 +01:00
return_value.push_back(instance_by_id(i).template as<express::Entity>());
}
}
}
2026-04-10 14:54:47 +02:00
});
}
#endif
2026-04-10 14:54:47 +02:00
}, storage_);
2023-09-17 12:30:17 +02:00
return return_value;
}
2026-03-31 15:32:36 +02:00
size_t file::get_total_inverses(int instance_id) {
2026-01-14 14:00:50 +01:00
std::set<uint32_t> counted_ids;
2026-01-14 14:00:50 +01:00
std::visit([&counted_ids, instance_id](const auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
2026-04-10 14:54:47 +02:00
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) {
2026-01-14 14:00:50 +01:00
counted_ids.insert(i);
}
}
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
// @todo
}
}, storage_);
2026-01-14 14:00:50 +01:00
return counted_ids.size();
2023-09-17 12:30:17 +02:00
}
2026-03-31 15:32:36 +02:00
void file::set_default_header_values() {
2024-08-23 20:29:07 +02:00
const std::string empty_string;
std::vector<std::string> file_description;
std::vector<std::string> schema_identifiers;
std::vector<std::string> string_vector = {""};
2015-01-16 16:26:40 +00:00
2023-09-17 12:30:17 +02:00
file_description.push_back("ViewDefinition [CoordinationView]");
if (schema() != nullptr) {
2023-09-17 12:30:17 +02:00
schema_identifiers.push_back(schema()->name());
}
2015-01-16 16:26:40 +00:00
header().file_description().setdescription(file_description);
header().file_description().setimplementation_level("2;1");
2015-01-16 16:26:40 +00:00
header().file_name().setname(empty_string);
2026-03-31 15:32:36 +02:00
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);
2015-01-16 16:26:40 +00:00
header().file_schema().setschema_identifiers(schema_identifiers);
2015-01-16 16:26:40 +00:00
}
2026-03-31 15:32:36 +02:00
std::pair<express::Base, double> file::get_unit(const std::string& unit_type) {
std::pair<express::Base, double> return_value(express::Base{}, 1.);
auto projects = instances_by_type(schema()->declaration_by_name("IfcProject"));
if (projects.empty()) {
2023-09-17 12:30:17 +02:00
try {
projects = instances_by_type(schema()->declaration_by_name("IfcContext"));
2026-03-31 15:32:36 +02:00
} catch (exception&) {
2023-09-17 12:30:17 +02:00
}
}
if (!projects.empty()) {
auto project = *projects.begin();
2023-09-17 12:30:17 +02:00
express::Base unit_assignment = project.as<express::Entity>().get("UnitsInContext");
2023-09-17 12:30:17 +02:00
std::vector<express::Base> units = unit_assignment.as<express::Entity>().get("Units");
2023-09-17 12:30:17 +02:00
for (auto& unit : units) {
if (unit.declaration().is("IfcNamedUnit")) {
const std::string file_unit_type = unit.as<express::Entity>().get("UnitType");
2023-09-17 12:30:17 +02:00
if (file_unit_type != unit_type) {
continue;
}
express::Base siunit;
if (unit.declaration().is("IfcConversionBasedUnit")) {
express::Base mu = unit.as<express::Entity>().get("ConversionFactor");
express::Base vlc = mu.as<express::Entity>().get("ValueComponent");
express::Base unc = mu.as<express::Entity>().get("UnitComponent");
return_value.second *= static_cast<double>(vlc.get_attribute_value(0));
2023-09-17 12:30:17 +02:00
return_value.first = unit;
if (unc.declaration().is("IfcSIUnit")) {
2023-09-17 12:30:17 +02:00
siunit = unc;
}
} else if (unit.declaration().is("IfcSIUnit")) {
2023-09-17 12:30:17 +02:00
return_value.first = siunit = unit;
}
if (siunit) {
2026-03-31 15:32:36 +02:00
attribute_value prefix = siunit.as<express::Entity>().get("Prefix");
2024-08-23 20:29:07 +02:00
if (!prefix.isNull()) {
2026-03-31 15:32:36 +02:00
return_value.second *= si_prefix_to_value(prefix);
2023-09-17 12:30:17 +02:00
}
}
}
}
}
return return_value;
2016-05-15 15:47:21 +02:00
}
2018-08-29 12:54:25 +02:00
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::build_inverses_(const express::Base& inst) {
std::function<void(const express::Base&, int)> 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::decay_t<decltype(x)>, std::monostate>) {
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::in_memory_file_storage>) {
2026-04-10 14:54:47 +02:00
x.byref_excl_[entity_attribute_id][{decl->index_in_schema(), idx}].push_back(inst.id());
} else if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
// @todo
}
}, storage_);
2023-09-17 12:30:17 +02:00
}
};
2025-02-27 22:07:31 +01:00
apply_individual_instance_visitor(inst).apply(fn);
2018-08-29 12:54:25 +02:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::unbatch() {
2025-08-31 15:52:46 +02:00
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);
}
2025-08-31 15:52:46 +02:00
batch_mode_ = false;
batch_deletion_ids_.clear();
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::reset_identity_cache() {
std::visit([](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, impl::rocks_db_file_storage>) {
x.instance_cache_.clear();
x.type_instance_cache_.clear();
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::build_inverses() {
for (const auto& pair : *this) {
build_inverses_(express::Base(pair.second));
2023-09-17 12:30:17 +02:00
}
}
2021-09-11 12:59:48 +02:00
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::register_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, int inst_id, int attribute_index)
2025-02-24 20:46:29 +01:00
{
std::visit([id_from, from_entity, inst_id, attribute_index](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.register_inverse(id_from, from_entity, inst_id, attribute_index);
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
void ifcopenshell::file::unregister_inverse(unsigned id_from, const ifcopenshell::entity* from_entity, const express::Base& inst, int attribute_index)
2025-02-24 20:46:29 +01:00
{
std::visit([id_from, from_entity, inst, attribute_index](auto& x) {
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, std::monostate>) {
throw std::runtime_error("Storage not initialized");
} else {
return x.unregister_inverse(id_from, from_entity, inst, attribute_index);
}
}, storage_);
}
2026-03-31 15:32:36 +02:00
std::atomic_uint32_t instance_data::counter_(0);
2021-09-13 13:17:08 +02:00
2026-03-31 15:32:36 +02:00
// bool ifcopenshell::file::guid_map_ = true;
void express::Base::unset_attribute_value(size_t index) {
2026-03-31 15:32:36 +02:00
data()->set_attribute_value(index, blank{});
}
2026-03-31 15:32:36 +02:00
attribute_value express::Base::get_attribute_value(size_t index) const {
return data()->get_attribute_value(index);
2024-08-23 20:29:07 +02:00
}
2026-03-31 15:32:36 +02:00
void express::Base::to_string(std::ostream& out, bool upper) const
2024-08-23 20:29:07 +02:00
{
const auto *ent = declaration().as_entity();
2025-08-28 11:15:18 +02:00
if (ent != nullptr && declaration().schema() != &Header_section_schema::get_schema()) {
out << "#" << id() << "=";
2024-08-23 20:29:07 +02:00
}
if (upper) {
out << declaration().name_uc();
} else {
out << declaration().name();
}
2026-03-31 15:32:36 +02:00
data()->to_string(out, upper);
2024-08-23 20:29:07 +02:00
}
2026-03-31 15:32:36 +02:00
ifcopenshell::file* express::Base::file() const {
return data()->file();
}
2025-02-27 22:07:31 +01:00
/*
2026-03-31 15:32:36 +02:00
instance_data::instance_data(const instance_data& data)
: storage_(data.size())
2024-08-23 20:29:07 +02:00
{
2025-02-27 22:07:31 +01:00
2024-08-23 20:29:07 +02:00
}
2025-02-27 22:07:31 +01:00
*/
2024-08-23 20:29:07 +02:00
2026-03-31 15:32:36 +02:00
attribute_value instance_data::get_attribute_value(size_t index) const
2024-08-23 20:29:07 +02:00
{
2025-03-18 10:41:03 +01:00
if (storage_) {
2026-03-31 15:32:36 +02:00
return attribute_value(storage_, (uint8_t)index);
2025-03-18 10:41:03 +01:00
} else {
2026-03-31 15:32:36 +02:00
auto* const storage = std::visit([](auto& m) -> ifcopenshell::impl::rocks_db_file_storage* {
using U = std::decay_t<decltype(m)>;
2026-03-31 15:32:36 +02:00
if constexpr (std::is_same_v<U, ifcopenshell::impl::rocks_db_file_storage>) {
return &m;
} else {
return nullptr;
}
}, file_->storage_);
2026-04-22 18:11:01 +02:00
return attribute_value(storage, id_ ? id_ : identity_, declaration_, (uint8_t)index);
2025-03-18 10:41:03 +01:00
}
2024-08-23 20:29:07 +02:00
}
2026-03-31 15:32:36 +02:00
bool ifcopenshell::impl::rocks_db_file_storage::read_schema(const ifcopenshell::schema_definition*& schema) {
#ifdef IFOPSH_WITH_ROCKSDB
2025-02-27 22:07:31 +01:00
std::string value;
auto key = "h|file_schema|0";
db->Get(rocksdb::ReadOptions{}, key, &value);
std::vector<std::string> strings;
if (::impl::deserialize(this, value, strings) && strings.size() == 1) {
try {
schema = schema_by_name(strings[0]);
2026-03-31 15:32:36 +02:00
} catch (exception&) {
return false;
}
2025-02-27 22:07:31 +01:00
return true;
}
#endif
2025-02-27 22:07:31 +01:00
return false;
}
/*
2026-03-31 15:32:36 +02:00
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<express::Base>(i, p);
}
void express::Base::set_attribute_value(const std::string& name, const express::Base& p) {
set_attribute_value<express::Base>(name, p);
}
2026-03-31 15:32:36 +02:00
template void IFC_PARSE_API express::Base::set_attribute_value<blank>(size_t index, const blank& value);
template void IFC_PARSE_API express::Base::set_attribute_value<derived>(size_t index, const derived& value);
template void IFC_PARSE_API express::Base::set_attribute_value<int>(size_t index, const int& value);
template void IFC_PARSE_API express::Base::set_attribute_value<bool>(size_t index, const bool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::logic::tribool>(size_t index, const boost::logic::tribool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<double>(size_t index, const double& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::string>(size_t index, const std::string& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::dynamic_bitset<>>(size_t index, const boost::dynamic_bitset<>& value);
2026-03-31 15:32:36 +02:00
template void IFC_PARSE_API express::Base::set_attribute_value<enumeration_reference>(size_t index, const enumeration_reference& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<int>>(size_t index, const std::vector<int>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<double>>(size_t index, const std::vector<double>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::string>>(size_t index, const std::vector<std::string>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<boost::dynamic_bitset<>>>(size_t index, const std::vector<boost::dynamic_bitset<>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<express::Base>>(size_t index, const std::vector<express::Base>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<int>>>(size_t index, const std::vector<std::vector<int>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<double>>>(size_t index, const std::vector<std::vector<double>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<express::Base>>>(size_t index, const std::vector<std::vector<express::Base>>& value);
2026-03-31 15:32:36 +02:00
template void IFC_PARSE_API express::Base::set_attribute_value<blank>(const std::string& name, const blank& value);
template void IFC_PARSE_API express::Base::set_attribute_value<derived>(const std::string& name, const derived& value);
template void IFC_PARSE_API express::Base::set_attribute_value<int>(const std::string& name, const int& value);
template void IFC_PARSE_API express::Base::set_attribute_value<bool>(const std::string& name, const bool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::logic::tribool>(const std::string& name, const boost::logic::tribool& value);
template void IFC_PARSE_API express::Base::set_attribute_value<double>(const std::string& name, const double& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::string>(const std::string& name, const std::string& value);
template void IFC_PARSE_API express::Base::set_attribute_value<boost::dynamic_bitset<>>(const std::string& name, const boost::dynamic_bitset<>& value);
2026-03-31 15:32:36 +02:00
template void IFC_PARSE_API express::Base::set_attribute_value<enumeration_reference>(const std::string& name, const enumeration_reference& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<int>>(const std::string& name, const std::vector<int>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<double>>(const std::string& name, const std::vector<double>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::string>>(const std::string& name, const std::vector<std::string>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<boost::dynamic_bitset<>>>(const std::string& name, const std::vector<boost::dynamic_bitset<>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<express::Base>>(const std::string& name, const std::vector<express::Base>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<int>>>(const std::string& name, const std::vector<std::vector<int>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<double>>>(const std::string& name, const std::vector<std::vector<double>>& value);
template void IFC_PARSE_API express::Base::set_attribute_value<std::vector<std::vector<express::Base>>>(const std::string& name, const std::vector<std::vector<express::Base>>& value);
namespace express {
template <typename T>
T Entity::get_value(const std::string& name) const {
auto attr = get(name);
T v = attr;
return v;
}
2025-02-27 22:07:31 +01:00
template <typename T>
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<int>(const std::string&) const;
template bool IFC_PARSE_API express::Entity::get_value<bool>(const std::string&) const;
template boost::logic::tribool IFC_PARSE_API express::Entity::get_value<boost::logic::tribool>(const std::string&) const;
template double IFC_PARSE_API express::Entity::get_value<double>(const std::string&) const;
template std::string IFC_PARSE_API express::Entity::get_value<std::string>(const std::string&) const;
template express::Base IFC_PARSE_API express::Entity::get_value<express::Base>(const std::string&) const;
template boost::dynamic_bitset<> IFC_PARSE_API express::Entity::get_value<boost::dynamic_bitset<>>(const std::string&) const;
2026-03-31 15:32:36 +02:00
template enumeration_reference IFC_PARSE_API express::Entity::get_value<enumeration_reference>(const std::string&) const;
template std::vector<int> IFC_PARSE_API express::Entity::get_value<std::vector<int>>(const std::string&) const;
template std::vector<double> IFC_PARSE_API express::Entity::get_value<std::vector<double>>(const std::string&) const;
template std::vector<std::string> IFC_PARSE_API express::Entity::get_value<std::vector<std::string>>(const std::string&) const;
template std::vector<boost::dynamic_bitset<>> IFC_PARSE_API express::Entity::get_value<std::vector<boost::dynamic_bitset<>>>(const std::string&) const;
template std::vector<express::Base> IFC_PARSE_API express::Entity::get_value<std::vector<express::Base>>(const std::string&) const;
template std::vector<std::vector<int>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<int>>>(const std::string&) const;
template std::vector<std::vector<double>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<double>>>(const std::string&) const;
template std::vector<std::vector<express::Base>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<express::Base>>>(const std::string&) const;
template int IFC_PARSE_API express::Entity::get_value<int>(const std::string&, const int&) const;
template bool IFC_PARSE_API express::Entity::get_value<bool>(const std::string&, const bool&) const;
template boost::logic::tribool IFC_PARSE_API express::Entity::get_value<boost::logic::tribool>(const std::string&, const boost::logic::tribool&) const;
template double IFC_PARSE_API express::Entity::get_value<double>(const std::string&, const double&) const;
template std::string IFC_PARSE_API express::Entity::get_value<std::string>(const std::string&, const std::string&) const;
template express::Base IFC_PARSE_API express::Entity::get_value<express::Base>(const std::string&, const express::Base&) const;
template boost::dynamic_bitset<> IFC_PARSE_API express::Entity::get_value<boost::dynamic_bitset<>>(const std::string&, const boost::dynamic_bitset<>&) const;
2026-03-31 15:32:36 +02:00
template enumeration_reference IFC_PARSE_API express::Entity::get_value<enumeration_reference>(const std::string&, const enumeration_reference&) const;
template std::vector<int> IFC_PARSE_API express::Entity::get_value<std::vector<int>>(const std::string&, const std::vector<int>&) const;
template std::vector<double> IFC_PARSE_API express::Entity::get_value<std::vector<double>>(const std::string&, const std::vector<double>&) const;
template std::vector<std::string> IFC_PARSE_API express::Entity::get_value<std::vector<std::string>>(const std::string&, const std::vector<std::string>&) const;
template std::vector<boost::dynamic_bitset<>> IFC_PARSE_API express::Entity::get_value<std::vector<boost::dynamic_bitset<>>>(const std::string&, const std::vector<boost::dynamic_bitset<>>&) const;
template std::vector<express::Base> IFC_PARSE_API express::Entity::get_value<std::vector<express::Base>>(const std::string&, const std::vector<express::Base>&) const;
template std::vector<std::vector<int>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<int>>>(const std::string&, const std::vector<std::vector<int>>&) const;
template std::vector<std::vector<double>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<double>>>(const std::string&, const std::vector<std::vector<double>>&) const;
template std::vector<std::vector<express::Base>> IFC_PARSE_API express::Entity::get_value<std::vector<std::vector<express::Base>>>(const std::string&, const std::vector<std::vector<express::Base>>&) const;