Skip to content

Commit 7329c48

Browse files
authored
Fix quantify() regression where by_type queries and grouped iteration could apply QTOs to all entities in the model instead of the intended element subset.
1 parent 9c93b12 commit 7329c48

1 file changed

Lines changed: 25 additions & 12 deletions

File tree

src/ifc5d/ifc5d/qto.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,38 @@ def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_inst
9292

9393
for calculator, queries in rules["calculators"].items():
9494
calculator = calculators[calculator]
95-
if not set(m.lower() for m in queries.keys()) - lower_case_entity_names(ifc_file.schema_identifier):
96-
# all defined queries are actually simple entity names: instead of looping over all
97-
# calculators we loop over the entity types so that we don't have to repeatedly query
98-
# the model, especially when the set of elements is small.
95+
# Cache schema entity names once per file
96+
schema_entity_names = lower_case_entity_names(ifc_file.schema_identifier)
97+
98+
# Fast path: all queries are simple entity names
99+
if not set(m.lower() for m in queries.keys()) - schema_entity_names:
99100
casenorm = {k.lower(): k for k in queries.keys()}
100101
pred = lambda inst: inst.is_a().lower()
101-
for ty, elements in itertools.groupby(sorted(elements, key=pred), key=pred):
102+
103+
for ty, group in itertools.groupby(sorted(elements, key=pred), key=pred):
104+
group_elements = list(group)
105+
# check entity type and its supertypes for matching QTO rules
102106
for sty in entity_supertypes(ifc_file.schema_identifier, ty):
103107
if qtos := queries.get(casenorm.get(sty)):
104-
calculator.calculate(ifc_file, list(elements), qtos, results)
108+
calculator.calculate(ifc_file, group_elements, qtos, results)
109+
110+
# Fallback: per-query evaluation
105111
for query, qtos in queries.items():
106-
if query.lower() in lower_case_entity_names(ifc_file.schema_identifier):
107-
# by_type is faster than selector parsing
108-
filtered_elements = ifc_file.by_type(query)
112+
# Simple entity name: use by_type but restrict to incoming elements
113+
if query.lower() in schema_entity_names:
114+
by_type_all = ifc_file.by_type(query)
115+
# ensure we don't expand beyond provided elements subset
116+
if isinstance(elements, set):
117+
filtered_elements = [e for e in by_type_all if e in elements]
118+
else:
119+
elements_set = set(elements)
120+
filtered_elements = [e for e in by_type_all if e in elements_set]
109121
else:
110122
filtered_elements = ifcopenshell.util.selector.filter_elements(ifc_file, query, elements)
123+
111124
if filtered_elements:
112125
calculator.calculate(ifc_file, filtered_elements, qtos, results)
126+
113127
return results
114128

115129

@@ -159,8 +173,7 @@ class is mimicking the iterator interface but works for IfcTypeProducts."""
159173
element: Union[ifcopenshell.entity_instance, None] = None
160174
shape: Union[ifcopenshell.geom.ShapeType, None] = None
161175

162-
def __init__(
163-
self,
176+
def __init__(self,
164177
ifc_file: ifcopenshell.file,
165178
settings: ifcopenshell.geom.settings,
166179
elements: Iterable[ifcopenshell.entity_instance],
@@ -560,4 +573,4 @@ def calculate(cls, ifc_file, elements, qtos, results):
560573
calculators: dict[str, type[QtoCalculator]] = {
561574
"Blender": Blender,
562575
"IfcOpenShell": IfcOpenShell,
563-
}
576+
}

0 commit comments

Comments
 (0)