/******************************************************************************** * * * 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 . * * * ********************************************************************************/ #ifndef IFCFILE_H #define IFCFILE_H #include "ifc_parse_api.h" #include "parse.h" #include "schema.h" #include "spf_header.h" #include "storage.h" #include "file_open_status.h" #include #include #include #include #include #include #include #include #include #include #ifdef IFOPSH_WITH_ROCKSDB #include namespace { // @todo move to a proper place class ConcatenateIdMergeOperator : public rocksdb::AssociativeMergeOperator { public: virtual bool FullMergeV2(const MergeOperator::MergeOperationInput& merge_in, MergeOperator::MergeOperationOutput* merge_out) const { // Log(InfoLogLevel::INFO_LEVEL, merge_in.logger, "FullMergeV2 new_value size:%ld", merge_out->new_value.size()); merge_out->new_value.clear(); if (merge_in.existing_value) { merge_out->new_value.append(merge_in.existing_value->data(), merge_in.existing_value->size()); } for (auto& operand : merge_in.operand_list) { merge_out->new_value.append(operand.data(), operand.size()); } return true; } virtual bool Merge(const rocksdb::Slice&, const rocksdb::Slice*, const rocksdb::Slice&, std::string*, rocksdb::Logger*) const override { return false; } virtual const char* Name() const override { return "ConcatenateIdMergeOperator"; } }; } #endif namespace ifcopenshell { enum filetype { FT_IFCSPF, FT_IFCXML, FT_IFCZIP, FT_ROCKSDB, FT_UNKNOWN, FT_AUTODETECT }; IFC_PARSE_API filetype guess_file_type(const std::string& path); template > class IFC_PARSE_API instance_streamer { private: std::unique_ptr owned_stream_; Reader* stream_; std::unique_ptr> lexer_; std::unique_ptr owned_header_; spf_header* header_; ifcopenshell::file* owner_; boost::circular_buffer token_stream_; const ifcopenshell::schema_definition* schema_; ifcopenshell::impl::in_memory_file_storage storage_; ifcopenshell::file_open_status good_ = ifcopenshell::file_open_status::SUCCESS; std::reference_wrapper logger_; int progress_; ifcopenshell::unresolved_references references_to_resolve_; int yielded_header_instances_ = 0; bool yield_header_instances_ = true; std::vector types_to_bypass_; std::vector bypassed_instances_; std::vector types_to_bypass_materialized_; void initialize_header(); spf_header& ensure_header(); public: bool coerce_attribute_count = true; operator bool() const { return good_ && lexer_ && !lexer_->stream->eof(); } ifcopenshell::file_open_status status() const { return good_; } const ifcopenshell::unresolved_references& references() const { return references_to_resolve_; } ifcopenshell::unresolved_references& references() { return references_to_resolve_; } const std::vector& bypassed_instances() { std::sort(bypassed_instances_.begin(), bypassed_instances_.end()); return bypassed_instances_; } const ifcopenshell::impl::in_memory_file_storage::entities_by_ref_t& inverses() const { return storage_.byref_excl_; } ifcopenshell::impl::in_memory_file_storage::entities_by_ref_t& inverses() { return storage_.byref_excl_; } std::vector steal_instances() { return storage_.steal_instances(); } bool has_semicolon() const; size_t semicolon_count() const; void push_page(const std::string& page_data); instance_streamer(ifcopenshell::file* owner_file = nullptr, Logger& logger = Logger::Root()); instance_streamer(const std::string& path, bool use_mmap = false, ifcopenshell::file* owner_file = nullptr, Logger& logger = Logger::Root()); instance_streamer(void* data, int data_size, ifcopenshell::file* owner_file = nullptr, Logger& logger = Logger::Root()); instance_streamer(Reader* stream, ifcopenshell::file* owner_file = nullptr, Logger& logger = Logger::Root()); void bypass_types(const std::set& type_names); void yield_header_instances(bool enabled) { yield_header_instances_ = enabled; } const ifcopenshell::schema_definition* schema() const { return schema_; } const spf_header* header() const { return header_; } ~instance_streamer() = default; std::optional> read_instance(); }; class uninitialized_tag {}; /// This class provides access to the entity instances in an IFC file /// The file takes ownership of instances added to this file and deletes them when the file is deleted. class IFC_PARSE_API file { private: typedef std::map entity_entity_map_t; // @todo determine the constness of things (probably needs to be all const, we don't want to overwrite) // @todo we have variant_iterator and MapVariant, we probably need to retain only one? public: using const_iterator = variant_iterator; using type_iterator = variant_iterator; using storage_t = std::variant; typedef variant_map entity_instance_by_guid_t; entity_instance_by_guid_t byguid_; typedef variant_map entity_by_id_t; entity_by_id_t byid_; typedef variant_map entities_by_ref_t; entities_by_ref_t byref_excl_; bool check_existance_before_adding = true; bool calculate_unit_factors = true; // @todo temporarily public for header storage_t storage_; std::set types_to_bypass_loading_; private: file_open_status good_ = file_open_status::SUCCESS; std::reference_wrapper logger_; const ifcopenshell::schema_definition* schema_; const ifcopenshell::declaration* ifcroot_type_; entity_entity_map_t entity_file_map_; unsigned int max_id_; std::unique_ptr header_; void set_default_header_values(); typedef boost::multi_index_container< int, boost::multi_index::indexed_by< boost::multi_index::sequenced<>, boost::multi_index::ordered_unique< boost::multi_index::identity>>> batch_deletion_ids_t; batch_deletion_ids_t batch_deletion_ids_; bool batch_mode_ = false; void process_deletion_(const express::Base& entity); public: #ifdef USE_MMAP /// /// Constructs an file object from a file path, optionally using memory-mapped I/O, only supports IFC-SPF files. /// /// UTF-8 file path to an IFC-SPF file /// Whether to use memory-mapped I/O file(const std::string& path, bool use_mmap, Logger& logger = Logger::Root()); #endif /// /// Constructs an file object from a file path, supports IFC-SPF and the IfcOpenShell-specific RocksDB format. /// /// UTF-8 file path to an IFC-SPF file or RocksDB database directory /// File type of the path /// Whether to open in read-only mode, only supported on RocksDB databases file(const std::string& path, filetype type = FT_AUTODETECT, bool read_only = false, Logger& logger = Logger::Root()); /// /// Constructs an file object from a stream containing IFC-SPF data. /// file(std::istream& stream, int data_size, Logger& logger = Logger::Root()); /// /// Constructs an file object from a memory buffer containing IFC-SPF data. /// file(void* data, int data_size, Logger& logger = Logger::Root()); /// /// Constructs an file object with the specified schema, file type, and file path. /// @nb path is only used in rocksdb mode, for spf file is in-memory only until write() is called /// /// Pointer to the schema definition to use. Defaults to the IFC4 schema if not specified. /// The file type to use for the file. Defaults to FT_AUTODETECT. /// The file system path to the IFC file. Defaults to an empty string. file(const ifcopenshell::schema_definition* schema = ifcopenshell::schema_by_name("IFC4"), filetype type = FT_AUTODETECT, const std::string& path = "", Logger& logger = Logger::Root()); /// /// Constructs an unitialized file object. Call initialize() later on. Allows to specify which types to bypass during load. /// file(const uninitialized_tag& tag, Logger& logger = Logger::Root()); bool initialize(const std::string& path, filetype type = FT_AUTODETECT, bool read_only = false); #ifdef USE_MMAP bool initialize(const std::string& path, bool use_mmap); #endif /// @brief Bypass loading of all instances of the specified type name. Only applies to parsed IFC-SPF files. /// @param type_name case insensitive name of the type to bypass void bypass_type(const std::string& type_name); ~file(); ifcopenshell::file_open_status good() const { return good_; } Logger& logger() const { return logger_.get(); } /// Returns the first entity in the range of instances contained in the model, /// in arbitrary order entity_by_id_t::iterator begin() const { return byid_.begin(); } /// Returns the first entity in the range of instances contained in the model, /// in arbitrary order entity_by_id_t::iterator end() const { return byid_.end(); } type_iterator types_begin() const; type_iterator types_end() const; /// Returns all entities in the file that match the template argument. /// NOTE: This also returns subtypes of the requested type, for example: /// IfcWall will also return IfcWallStandardCase entities template typename std::vector instances_by_type() { std::vector untyped_list = instances_by_type(&T::Class()); std::vector return_value; for (auto& untyped : untyped_list) { return_value.push_back(untyped.as()); } return return_value; } template typename std::vector instances_by_type_excl_subtypes() { std::vector untyped_list = instances_by_type_excl_subtypes(&T::Class()); std::vector return_value; for (auto& untyped : untyped_list) { return_value.push_back(untyped.as()); } return return_value; } /// Returns all entities in the file that match the positional argument. /// NOTE: This also returns subtypes of the requested type, for example: /// IfcWall will also return IfcWallStandardCase entities std::vector instances_by_type(const ifcopenshell::declaration* declaration); /// Returns all entities in the file that match the positional argument. std::vector instances_by_type_excl_subtypes(const ifcopenshell::declaration* declaration); /// Returns all entities in the file that match the positional argument. /// NOTE: This also returns subtypes of the requested type, for example: /// IfcWall will also return IfcWallStandardCase entities std::vector instances_by_type(const std::string& type_name); /// Returns all entities in the file that match the positional argument. std::vector instances_by_type_excl_subtypes(const std::string& type_name); /// Returns all entities in the file that reference the id std::vector instances_by_reference(int reference_id); /// Returns the entity with the specified id express::Base instance_by_id(int instance_id); /// Returns the entity with the specified GlobalId express::Base instance_by_guid(const std::string& global_id); /// Performs a depth-first traversal, returning all entity instance /// attributes as a flat list. NB: includes the root instance specified /// in the first function argument. static std::vector traverse(const express::Base& instance, int max_depth = -1); /// Same as traverse() but maintains topological order by using a /// breadth-first search static std::vector traverse_breadth_first(const express::Base& instance, int max_depth = -1); /// Get the attribute indices corresponding to the list of entity instances /// returned by get_inverse(). std::vector get_inverse_indices_by_id(int instance_id); template typename T::list::ptr get_inverse(int instance_id, int attribute_index) { return get_inverse(instance_id, &T::Class(), attribute_index)->template as(); } std::vector get_inverse(int instance_id, const ifcopenshell::declaration* declaration, int attribute_index); size_t get_total_inverses(int instance_id); unsigned int fresh_id() { return ++max_id_; } unsigned int get_max_id() const { return max_id_; } const ifcopenshell::declaration* ifcroot_type() const { return ifcroot_type_; } void recalculate_id_counter(); express::Base add_entity(const express::Base& entity, int instance_id = -1); /// Removes entity instance from file and unsets references. /// /// Attention when running remove_entity inside a loop over a list of entities to be removed. /// This invalidates the iterator. A workaround is to reverse the loop: /// boost::shared_ptr entities = ...; /// for (auto it = entities->end() - 1; it >= entities->begin(); --it) { /// ifcopenshell::IfcBaseClass *const inst = *it; /// model->remove_entity(inst); /// } void remove_entity(const express::Base& entity); const spf_header& header() const { return *header_; } spf_header& header() { return *header_; } static std::string create_timestamp(); const ifcopenshell::schema_definition* schema() const; std::pair get_unit(const std::string& unit_type); void build_inverses(); void register_inverse(unsigned referenced_id, const ifcopenshell::entity* from_entity, int instance_id, int attribute_index); void unregister_inverse(unsigned referenced_id, const ifcopenshell::entity* from_entity, const express::Base& entity, int attribute_index); entity_instance_by_guid_t internal_guid_map() { return byguid_; }; void add_type_ref(const express::Base& new_entity); void remove_type_ref(const express::Base& new_entity); void process_deletion_inverse(const express::Base& entity); void build_inverses_(const express::Base& entity); template T create(int instance_id = -1) { return create(&T::Class(), instance_id).template as(); } express::Base create(const ifcopenshell::declaration* declaration, int instance_id = -1); void batch() { batch_mode_ = true; } void unbatch(); void reset_identity_cache(); }; namespace impl { // Trick to have a dependent static assertion template inline constexpr bool dependent_false_v = false; } } // namespace ifcopenshell template T ifcopenshell::impl::in_memory_file_storage::create(int id) { return create(&T::declaration(), id).template as(); } #ifdef IFOPSH_WITH_ROCKSDB template T ifcopenshell::impl::rocks_db_file_storage::create(int id) { if constexpr (std::is_same_v>, ifcopenshell::entity> || std::is_same_v>, ifcopenshell::type_declaration>) { auto* inst = new T(rocks_db_attribute_storage{}); inst->file_ = file; return file->add_entity(inst)->template as(); } else { static_assert(dependent_false_v, "Requires and entity or type declaration"); } } #endif namespace std { template <> struct iterator_traits { typedef ptrdiff_t difference_type; typedef const ifcopenshell::declaration* value_type; typedef const ifcopenshell::declaration*& reference; typedef const ifcopenshell::declaration** pointer; typedef std::forward_iterator_tag iterator_category; }; } // namespace std #endif