diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index 9ada1529fc..cb98e3b79e 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -179,6 +179,14 @@ class file(object): :rtype: None """ return self.wrapped_data.remove(inst.wrapped_data) + + def batch(self): + """Low-level mechanism to speed up deletion of large subgraphs""" + return self.wrapped_data.batch() + + def unbatch(self): + """Low-level mechanism to speed up deletion of large subgraphs""" + return self.wrapped_data.unbatch() def __iter__(self): return iter(self[id] for id in self.wrapped_data.entity_names()) diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 197d045562..788acf7f34 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -126,6 +126,13 @@ def remove_deep(ifc_file, element): ifc_file.remove(ref) +def remove_deep_batched(ifc_file, element): + # @todo maybe some sort of try-finally mechanism. + ifc_file.batch() + remove_deep(ifc_file, element) + ifc_file.unbatch() + + def get_representation(element, context, subcontext=None, target_view=None): if element.is_a("IfcProduct") and element.Representation: for r in element.Representation.Representations: diff --git a/src/ifcparse/IfcFile.h b/src/ifcparse/IfcFile.h index 7b679ff7ba..9b46c4dd10 100644 --- a/src/ifcparse/IfcFile.h +++ b/src/ifcparse/IfcFile.h @@ -131,6 +131,11 @@ private: void initialize_(IfcParse::IfcSpfStream* f); void build_inverses_(IfcUtil::IfcBaseClass*); + + std::set batch_deletion_ids_; + bool batch_mode_ = false; + void process_deletion_(); + public: IfcParse::IfcSpfLexer* tokens; IfcParse::IfcSpfStream* stream; @@ -226,6 +231,9 @@ public: IfcUtil::IfcBaseClass* addEntity(IfcUtil::IfcBaseClass* entity); void addEntities(IfcEntityList::ptr es); + void batch() { batch_mode_ = true; } + void unbatch() { process_deletion_(); batch_mode_ = false; } + /// Removes entity instance from file and unsets references. /// /// Attention when running removeEntity inside a loop over a list of entities to be removed. diff --git a/src/ifcparse/IfcParse.cpp b/src/ifcparse/IfcParse.cpp index 96d1ed6edc..e5385eaa6e 100644 --- a/src/ifcparse/IfcParse.cpp +++ b/src/ifcparse/IfcParse.cpp @@ -1710,73 +1710,76 @@ IfcUtil::IfcBaseClass* IfcFile::addEntity(IfcUtil::IfcBaseClass* entity) { } } - // The mapping by entity type is updated. - const IfcParse::declaration* ty = &new_entity->declaration(); +// The mapping by entity type is updated. +const IfcParse::declaration* ty = &new_entity->declaration(); - if (ty->as_entity()) { - IfcEntityList::ptr insts = instances_by_type_excl_subtypes(ty); - if (!insts) { - insts = IfcEntityList::ptr(new IfcEntityList()); - bytype_excl[ty] = insts; - } - insts->push(new_entity); +if (ty->as_entity()) { + IfcEntityList::ptr insts = instances_by_type_excl_subtypes(ty); + if (!insts) { + insts = IfcEntityList::ptr(new IfcEntityList()); + bytype_excl[ty] = insts; + } + insts->push(new_entity); +} + +for (; ty->as_entity();) { + IfcEntityList::ptr insts = instances_by_type(ty); + if (!insts) { + insts = IfcEntityList::ptr(new IfcEntityList()); + bytype[ty] = insts; + } + insts->push(new_entity); + + const IfcParse::declaration* pt = ty->as_entity()->supertype(); + if (pt) { + ty = pt; + } + else { + break; + } +} + +if (ty->as_entity()) { + int new_id = -1; + if (!new_entity->data().file) { + // For newly created entities ensure a valid ENTITY_INSTANCE_NAME is set + new_entity->data().file = this; + new_id = new_entity->data().set_id(); + } + else { + new_id = new_entity->data().id(); } - for (; ty->as_entity();) { - IfcEntityList::ptr insts = instances_by_type(ty); - if (!insts) { - insts = IfcEntityList::ptr(new IfcEntityList()); - bytype[ty] = insts; - } - insts->push(new_entity); - - const IfcParse::declaration* pt = ty->as_entity()->supertype(); - if (pt) { - ty = pt; - } else { - break; - } + if (byid.find(new_id) != byid.end()) { + // This should not happen + std::stringstream ss; + ss << "Overwriting entity with id " << new_id; + Logger::Message(Logger::LOG_WARNING, ss.str()); } - if (ty->as_entity()) { - int new_id = -1; - if (!new_entity->data().file) { - // For newly created entities ensure a valid ENTITY_INSTANCE_NAME is set - new_entity->data().file = this; - new_id = new_entity->data().set_id(); - } else { - new_id = new_entity->data().id(); - } + // The mapping by entity instance name is updated. + byid[new_id] = new_entity; +} - if (byid.find(new_id) != byid.end()) { - // This should not happen - std::stringstream ss; - ss << "Overwriting entity with id " << new_id; - Logger::Message(Logger::LOG_WARNING, ss.str()); - } +if (parsing_complete_ && ty->as_entity()) { + build_inverses_(new_entity); +} - // The mapping by entity instance name is updated. - byid[new_id] = new_entity; - } - - if (parsing_complete_ && ty->as_entity()) { - build_inverses_(new_entity); - } - - return new_entity; +return new_entity; } void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) { const unsigned id = entity->data().id(); + IfcUtil::IfcBaseClass* file_entity = instance_by_id(id); - + // Attention when running removeEntity 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) { - // IfcUtil::IfcBaseClass *const inst = *it; - // model->removeEntity(inst); - // } + // IfcUtil::IfcBaseClass *const inst = *it; + // model->removeEntity(inst); + // } // TODO: Create a set of weak relations. Inverse relations that do not dictate an // instance to be retained. For example: when deleting an IfcRepresentation, the @@ -1789,133 +1792,180 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) { throw IfcParse::IfcException("Instance not part of this file"); } - IfcEntityList::ptr references = instances_by_reference(id); + batch_deletion_ids_.insert(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) { - for (IfcEntityList::it iit = references->begin(); iit != references->end(); ++iit) { - IfcUtil::IfcBaseEntity* related_instance = (IfcUtil::IfcBaseEntity*) *iit; + if (!batch_mode_) { + process_deletion_(); + } +} +void IfcFile::process_deletion_() { - for (unsigned i = 0; i < related_instance->data().getArgumentCount(); ++i) { - Argument* attr = related_instance->data().getArgument(i); - if (attr->isNull()) continue; + for (auto& id : batch_deletion_ids_) { + auto entity = instance_by_id(id); - IfcUtil::ArgumentType attr_type = attr->type(); - switch(attr_type) { - case IfcUtil::Argument_ENTITY_INSTANCE: { - IfcUtil::IfcBaseClass* instance_attribute = *attr; - if (instance_attribute == entity) { - IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); - copy->set(boost::blank()); - related_instance->data().setArgument(i, copy); - } } - break; - case IfcUtil::Argument_AGGREGATE_OF_ENTITY_INSTANCE: { - IfcEntityList::ptr instance_list = *attr; - if (instance_list->contains(entity)) { - instance_list->remove(entity); + IfcEntityList::ptr references = instances_by_reference(id); - IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); - copy->set(instance_list); - related_instance->data().setArgument(i, copy); - } } - break; - case IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: { - IfcEntityListList::ptr instance_list_list = *attr; - if (instance_list_list->contains(entity)) { - IfcEntityListList::ptr new_list(new IfcEntityListList); - for (IfcEntityListList::outer_it it = instance_list_list->begin(); it != instance_list_list->end(); ++it) { - std::vector instances = *it; - std::vector::iterator jt; - while ((jt = std::find(instances.begin(), instances.end(), entity)) != instances.end()) { - instances.erase(jt); + // 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) { + for (IfcEntityList::it iit = references->begin(); iit != references->end(); ++iit) { + IfcUtil::IfcBaseEntity* related_instance = (IfcUtil::IfcBaseEntity*) *iit; + + if (std::find(batch_deletion_ids_.begin(), batch_deletion_ids_.end(), related_instance->data().id()) != batch_deletion_ids_.end()) { + continue; + } + + for (unsigned i = 0; i < related_instance->data().getArgumentCount(); ++i) { + Argument* attr = related_instance->data().getArgument(i); + if (attr->isNull()) continue; + + IfcUtil::ArgumentType attr_type = attr->type(); + switch (attr_type) { + case IfcUtil::Argument_ENTITY_INSTANCE: { + IfcUtil::IfcBaseClass* instance_attribute = *attr; + if (instance_attribute == entity) { + IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); + copy->set(boost::blank()); + related_instance->data().setArgument(i, copy); + } } + break; + case IfcUtil::Argument_AGGREGATE_OF_ENTITY_INSTANCE: { + IfcEntityList::ptr instance_list = *attr; + if (instance_list->contains(entity)) { + instance_list->remove(entity); + + IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); + copy->set(instance_list); + related_instance->data().setArgument(i, copy); + } } + break; + case IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE: { + IfcEntityListList::ptr instance_list_list = *attr; + if (instance_list_list->contains(entity)) { + IfcEntityListList::ptr new_list(new IfcEntityListList); + for (IfcEntityListList::outer_it it = instance_list_list->begin(); it != instance_list_list->end(); ++it) { + std::vector instances = *it; + std::vector::iterator jt; + while ((jt = std::find(instances.begin(), instances.end(), entity)) != instances.end()) { + instances.erase(jt); + } + new_list->push(instances); } - new_list->push(instances); - } - IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); - copy->set(new_list); - related_instance->data().setArgument(i, copy); - } } - break; - default: break; + IfcWrite::IfcWriteArgument* copy = new IfcWrite::IfcWriteArgument(); + copy->set(new_list); + related_instance->data().setArgument(i, copy); + } } + break; + default: break; + } } } } - byref.erase(byref.find(id)); - } - IfcEntityList::ptr entity_attributes = traverse(entity, 1); - for (IfcEntityList::it it = entity_attributes->begin(); it != entity_attributes->end(); ++it) { - IfcUtil::IfcBaseClass* entity_attribute = *it; - if (entity_attribute == entity) continue; - const unsigned int name = entity_attribute->data().id(); - // Do not update inverses for simple types (which have id()==0 in IfcOpenShell). - if (name != 0) { - entities_by_ref_t::iterator byref_it = byref.find(name); - if (byref_it != byref.end()) { - std::vector& ids = byref_it->second; - ids.erase(std::remove(ids.begin(), ids.end(), id), ids.end()); + if (!batch_mode_) { + byref.erase(id); + + // This is based on traversal which needs instances to still be contained in the map. + // another option would be to keep byid intact for the remainder of this loop + IfcEntityList::ptr entity_attributes = traverse(entity, 1); + for (IfcEntityList::it it = entity_attributes->begin(); it != entity_attributes->end(); ++it) { + IfcUtil::IfcBaseClass* entity_attribute = *it; + if (entity_attribute == entity) continue; + const unsigned int name = entity_attribute->data().id(); + // Do not update inverses for simple types (which have id()==0 in IfcOpenShell). + if (name != 0) { + entities_by_ref_t::iterator byref_it = byref.find(name); + if (byref_it != byref.end()) { + std::vector& ids = byref_it->second; + ids.erase(std::remove(ids.begin(), ids.end(), id), ids.end()); + } + by_ref_cached_.erase(name); + } } - by_ref_cached_.erase(name); } - } - if (entity->declaration().is(*ifcroot_type_)) { - const std::string global_id = *entity->data().getArgument(0); - auto it = byguid.find(global_id); - if (it != byguid.end()) { - byguid.erase(it); - } else { - Logger::Warning("GlobalId on rooted instance not encountered in map"); + if (entity->declaration().is(*ifcroot_type_)) { + const std::string global_id = *entity->data().getArgument(0); + auto it = byguid.find(global_id); + if (it != byguid.end()) { + byguid.erase(it); + } + else { + Logger::Warning("GlobalId on rooted instance not encountered in map"); + } } - } - - byid.erase(byid.find(id)); - const IfcParse::declaration* ty = &entity->declaration(); + byid.erase(byid.find(id)); - { - IfcEntityList::ptr instances_of_same_type = instances_by_type_excl_subtypes(ty); - instances_of_same_type->remove(entity); - if (instances_of_same_type->size() == 0) { - bytype_excl.erase(ty); - } - } + const IfcParse::declaration* ty = &entity->declaration(); - for (;;) { - IfcEntityList::ptr instances_of_same_type = instances_by_type(ty); - if (instances_of_same_type) { + { + IfcEntityList::ptr instances_of_same_type = instances_by_type_excl_subtypes(ty); instances_of_same_type->remove(entity); - } - if (instances_of_same_type->size() == 0) { - bytype.erase(ty); + if (instances_of_same_type->size() == 0) { + bytype_excl.erase(ty); + } } - const IfcParse::declaration* pt = ty->as_entity()->supertype(); - if (pt) { - ty = pt; - } else { - break; - } - } + for (;;) { + IfcEntityList::ptr instances_of_same_type = instances_by_type(ty); + if (instances_of_same_type) { + instances_of_same_type->remove(entity); + } + if (instances_of_same_type->size() == 0) { + bytype.erase(ty); + } - // This entity_file_map remains obviously flawed, but until we have proper lookup by value, or another mechanism, - // to prevent duplicate definitions with usage of add() we have to keep it. This might be a good moment to clear it. - for (auto it = entity_file_map.begin(); it != entity_file_map.end();) { - if (it->second == entity) { - it = entity_file_map.erase(it); - } else { - ++it; + const IfcParse::declaration* pt = ty->as_entity()->supertype(); + if (pt) { + ty = pt; + } + else { + break; + } } + + // This entity_file_map remains obviously flawed, but until we have proper lookup by value, or another mechanism, + // to prevent duplicate definitions with usage of add() we have to keep it. This might be a good moment to clear it. + for (auto it = entity_file_map.begin(); it != entity_file_map.end();) { + if (it->second == entity) { + it = entity_file_map.erase(it); + } + else { + ++it; + } + } + + delete entity; + } - delete entity; + if (batch_mode_) { + for (auto it = byref.begin(); it != byref.end();) { + bool do_delete = batch_deletion_ids_.find(it->first) != batch_deletion_ids_.end(); + if (!do_delete) { + it->second.erase(std::remove_if(it->second.begin(), it->second.end(), [this](int x) { + return batch_deletion_ids_.find(x) != batch_deletion_ids_.end(); + }), it->second.end()); + do_delete = it->second.empty(); + } + if (do_delete) { + it = byref.erase(it); + } + else { + ++it; + } + } + } + + by_ref_cached_.clear(); + + batch_deletion_ids_.clear(); } IfcEntityList::ptr IfcFile::instances_by_type(const IfcParse::declaration* t) {