See #3334. Optimise pset getting and add simple "broadphase" filtering for all facets for faster validation.

This commit is contained in:
Dion Moult
2023-07-04 15:50:29 +10:00
parent b8e2e693f1
commit ce8a9293bb
3 changed files with 55 additions and 16 deletions
@@ -77,7 +77,9 @@ def get_pset(element, name, prop=None, should_inherit=True):
if not prop:
if type_pset:
type_pset.update(get_property_definition(pset))
occurrence_pset = get_property_definition(pset)
if occurrence_pset:
type_pset.update(occurrence_pset)
return type_pset
return get_property_definition(pset)
@@ -128,9 +130,10 @@ def get_psets(element, psets_only=False, qtos_only=False, should_inherit=True):
continue
psets[definition.Name] = get_property_definition(definition)
elif hasattr(element, "IsDefinedBy"):
element_type = ifcopenshell.util.element.get_type(element)
if element_type and should_inherit:
psets = get_psets(element_type, psets_only=psets_only, qtos_only=qtos_only, should_inherit=False)
if should_inherit:
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
psets = get_psets(element_type, psets_only=psets_only, qtos_only=qtos_only, should_inherit=False)
for relationship in element.IsDefinedBy:
if relationship.is_a("IfcRelDefinesByProperties"):
definition = relationship.RelatingPropertyDefinition
+44 -4
View File
@@ -131,6 +131,9 @@ class Entity(Facet):
super().__init__(name, predefinedType, instructions)
def filter(self, ifc_file, elements):
if isinstance(elements, list):
return super().filter(ifc_file, elements)
if isinstance(self.name, str):
try:
results = ifc_file.by_type(self.name, include_subtypes=False)
@@ -180,6 +183,21 @@ class Attribute(Facet):
]
super().__init__(name, value, minOccurs, maxOccurs, instructions)
def filter(self, ifc_file, elements):
if isinstance(elements, list):
return super().filter(ifc_file, elements)
results = []
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(ifc_file.schema)
for entity in schema.entities():
for attribute in entity.attributes():
if attribute.name() == self.name:
results.extend(ifc_file.by_type(entity.name(), include_subtypes=False))
# TODO: perhaps we should consider value in the filter
return results
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return AttributeResult(True)
@@ -285,7 +303,9 @@ class Classification(Facet):
super().__init__(value, system, uri, minOccurs, maxOccurs, instructions)
def filter(self, ifc_file, elements):
pass
if isinstance(elements, list):
return super().filter(ifc_file, elements)
return ifc_file.by_type("IfcObjectDefinition")
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
@@ -341,6 +361,11 @@ class PartOf(Facet):
]
super().__init__(entity, predefinedType, relation, minOccurs, maxOccurs, instructions)
def filter(self, ifc_file, elements):
if isinstance(elements, list):
return super().filter(ifc_file, elements)
return list(ifc_file) # Lazy
def asdict(self):
results = super().asdict()
entity = {}
@@ -479,16 +504,26 @@ class Property(Facet):
]
super().__init__(propertySet, name, value, measure, uri, minOccurs, maxOccurs, instructions)
def filter(self, ifc_file, elements):
if isinstance(elements, list):
return super().filter(ifc_file, elements)
if ifc_file.schema == "IFC2X3":
return ifc_file.by_type("IfcObjectDefinition")
return (
ifc_file.by_type("IfcObjectDefinition")
+ ifc_file.by_type("IfcMaterialDefinition")
+ ifc_file.by_type("IfcProfileDef")
)
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return PropertyResult(True)
all_psets = ifcopenshell.util.element.get_psets(inst)
if isinstance(self.propertySet, str):
pset = all_psets.get(self.propertySet, None)
pset = ifcopenshell.util.element.get_pset(inst, self.propertySet)
psets = {self.propertySet: pset} if pset else {}
else:
all_psets = ifcopenshell.util.element.get_psets(inst)
psets = {k: v for k, v in all_psets.items() if k == self.propertySet}
is_pass = bool(psets)
@@ -730,6 +765,11 @@ class Material(Facet):
]
super().__init__(value, uri, minOccurs, maxOccurs, instructions)
def filter(self, ifc_file, elements):
if isinstance(elements, list):
return super().filter(ifc_file, elements)
return ifc_file.by_type("IfcObjectDefinition")
def __call__(self, inst, logger=None):
if self.minOccurs == 0 and self.maxOccurs != 0:
return MaterialResult(True)
+4 -8
View File
@@ -200,18 +200,14 @@ class Specification:
if filter_version and ifc_file.schema not in self.ifcVersion:
return
elements = []
elements = None
# This is a broadphase filter of applicability. We almost never want to
# test every single class in an IFC model.
for i, facet in enumerate(self.applicability):
# Usually, we rely on an entity applicability to give us our first
# shortlist of elements, as it's the most efficient way to filter
# elements. If this does not exist, then we have no choice but to
# check everything.
if i == 0 and not isinstance(facet, Entity):
elements = list(ifc_file)
elements = facet.filter(ifc_file, elements)
for element in elements:
for element in elements or []:
is_applicable = True
for facet in self.applicability:
if isinstance(facet, Entity):