diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index 3ce5442942..bc11ff1e26 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -377,17 +377,29 @@ class file(object): return [entity_instance(e, self) for e in fn(inst.wrapped_data, max_levels)] - def get_inverse(self, inst, allow_duplicate=False): + def get_inverse(self, inst, allow_duplicate=False, with_attribute_indices=False): """Return a list of entities that reference this entity :param inst: The entity instance to get inverse relationships :type inst: ifcopenshell.entity_instance.entity_instance + :param allow_duplicate: Returns a `list` when True, `set` when False + :param with_attribute_indices: Returns pairs of + where i[idx] is inst or contains inst. Requires allow_duplicate=True :returns: A list of ifcopenshell.entity_instance.entity_instance objects :rtype: list """ + if with_attribute_indices and not allow_duplicate: + raise ValueError("with_attribute_indices requires allow_duplicate to be True") + inverses = [entity_instance(e, self) for e in self.wrapped_data.get_inverse(inst.wrapped_data)] + if allow_duplicate: - return inverses + if with_attribute_indices: + idxs = self.wrapped_data.get_inverse_indices(inst.wrapped_data) + return list(zip(inverses, idxs)) + else: + return inverses + return set(inverses) def get_total_inverses(self, inst): diff --git a/src/ifcparse/IfcFile.h b/src/ifcparse/IfcFile.h index b3ff249616..c36d77a30f 100644 --- a/src/ifcparse/IfcFile.h +++ b/src/ifcparse/IfcFile.h @@ -252,7 +252,12 @@ public: /// breadth-first search aggregate_of_instance::ptr traverse_breadth_first(IfcUtil::IfcBaseClass* instance, int max_level=-1); + /// Get the attribute indices corresponding to the list of entity instances + /// returned by getInverse(). + std::vector get_inverse_indices(int instance_id); + aggregate_of_instance::ptr getInverse(int instance_id, const IfcParse::declaration* type, int attribute_index); + int getTotalInverses(int instance_id); template diff --git a/src/ifcparse/IfcParse.cpp b/src/ifcparse/IfcParse.cpp index 2669a71762..db764bcc8e 100644 --- a/src/ifcparse/IfcParse.cpp +++ b/src/ifcparse/IfcParse.cpp @@ -2428,6 +2428,47 @@ std::string IfcFile::createTimestamp() const { return result; } +std::vector IfcFile::get_inverse_indices(int instance_id) { + std::vector return_value; + + auto lower = byref.lower_bound({ instance_id, -1, -1 }); + auto upper = byref.upper_bound({ instance_id, std::numeric_limits::max(), std::numeric_limits::max() }); + + // Mapping of instance id to attribute offset. + std::map> mapping; + + for (auto it = lower; it != upper; ++it) { + for (auto& i : it->second) { + // We only take the tuple for the type that id=i actually is, in order not + // to count double. Because byref contains mappings for every supertype of id=i. + if (instance_by_id(i)->declaration().index_in_schema() == std::get<1>(it->first)) { + mapping[i].push_back(std::get<2>(it->first)); + } + } + } + + auto refs = instances_by_reference(instance_id); + + for (auto& r : *refs) { + auto it = mapping.find(r->data().id()); + if (it == mapping.end() || it->second.empty()) { + throw IfcException("Internal error"); + } + return_value.push_back(it->second.front()); + it->second.erase(it->second.begin()); + if (it->second.empty()) { + mapping.erase(it); + } + } + + // Test whether all mappings where indeed used. + if (!mapping.empty()) { + throw IfcException("Internal error"); + } + + return return_value; +} + aggregate_of_instance::ptr IfcFile::getInverse(int instance_id, const IfcParse::declaration* type, int attribute_index) { if (type == nullptr && attribute_index == -1) { return instances_by_reference(instance_id); diff --git a/src/ifcwrap/IfcParseWrapper.i b/src/ifcwrap/IfcParseWrapper.i index 00d7ac0347..e6e38eedea 100644 --- a/src/ifcwrap/IfcParseWrapper.i +++ b/src/ifcwrap/IfcParseWrapper.i @@ -85,9 +85,15 @@ static IfcUtil::ArgumentType helper_fn_attribute_type(const IfcUtil::IfcBaseClas IfcUtil::IfcBaseClass* by_guid(const std::string& guid) { return $self->instance_by_guid(guid); } + aggregate_of_instance::ptr get_inverse(IfcUtil::IfcBaseClass* e) { return $self->getInverse(e->data().id(), 0, -1); } + + std::vector get_inverse_indices(IfcUtil::IfcBaseClass* e) { + return $self->get_inverse_indices(e->data().id()); + } + int get_total_inverses(IfcUtil::IfcBaseClass* e) { return $self->getTotalInverses(e->data().id()); }