2828import ifcopenshell .util .selector
2929import ifcopenshell .util .shape
3030import ifcopenshell .util .representation
31+ import ifcopenshell .util .type
3132import multiprocessing
32- from collections import namedtuple
33- from typing import Any , Literal , get_args , Union
33+ from collections import namedtuple , defaultdict
34+ from typing import Any , Literal , get_args , Union , Iterable
3435
3536
3637Function = namedtuple ("Function" , ["measure" , "name" , "description" ])
@@ -52,10 +53,19 @@ def quantify(ifc_file: ifcopenshell.file, elements: set[ifcopenshell.entity_inst
5253
5354 """
5455 results : ResultsDict = {}
56+ elements_by_classes : defaultdict [str , set [ifcopenshell .entity_instance ]] = defaultdict (set )
57+ for element in elements :
58+ elements_by_classes [element .is_a ()].add (element )
59+
5560 for calculator , queries in rules ["calculators" ].items ():
5661 calculator = calculators [calculator ]
57- for query , qtos in queries .items ():
58- filtered_elements = ifcopenshell .util .selector .filter_elements (ifc_file , query , elements )
62+ for ifc_class , qtos in queries .items ():
63+ filtered_elements = set ()
64+ ifc_classes = [ifc_class ] + ifcopenshell .util .type .get_applicable_types (ifc_class )
65+ for ifc_class in ifc_classes :
66+ if ifc_class not in elements_by_classes :
67+ continue
68+ filtered_elements .update (elements_by_classes [ifc_class ])
5969 if filtered_elements :
6070 calculator .calculate (ifc_file , filtered_elements , qtos , results )
6171 return results
@@ -100,6 +110,55 @@ def convert(self, value: float, measure: str) -> float:
100110 return value
101111
102112
113+ class IteratorForTypes :
114+ """Currently ifcopenshell.geom.iterator support only IfcProducts, so this
115+ class is mimicking the iterator interface but works for IfcTypeProducts."""
116+
117+ element : Union [ifcopenshell .entity_instance , None ] = None
118+ shape : Union [ifcopenshell .geom .ShapeType , None ] = None
119+
120+ def __init__ (
121+ self ,
122+ ifc_file : ifcopenshell .file ,
123+ settings : ifcopenshell .geom .settings ,
124+ elements : Iterable [ifcopenshell .entity_instance ],
125+ ):
126+ self .settings = settings
127+ self .elements = list (elements )
128+ self .element = None
129+ self .file = ifc_file
130+ model = ifcopenshell .util .representation .get_context (ifc_file , "Model" , "Body" , "MODEL_VIEW" )
131+ assert model
132+ self .context = model
133+
134+ def initialize (self ) -> bool :
135+ return bool (self .next ())
136+
137+ def get_element_and_geometry (self ) -> tuple [ifcopenshell .entity_instance , ifcopenshell .geom .ShapeType ]:
138+ # get() is not implemented so it won't be confused with iteartor.get().
139+ # The difference is important since create_shape for product types
140+ # doesn't ouput SpapeElementType, only ShapeTypes.
141+ assert self .element and self .shape
142+ return (self .element , self .shape )
143+
144+ def next (self ) -> bool :
145+ if not self .elements :
146+ return False
147+ while self .elements :
148+ element = self .elements .pop ()
149+ if self .process_shape (element ):
150+ return True
151+ return False
152+
153+ def process_shape (self , element : ifcopenshell .entity_instance ):
154+ representation = ifcopenshell .util .representation .get_representation (element , self .context )
155+ if not representation :
156+ return False
157+ self .shape = ifcopenshell .geom .create_shape (self .settings , representation )
158+ self .element = element
159+ return True
160+
161+
103162class IfcOpenShell :
104163 """Calculates Model body context geometry using the default IfcOpenShell
105164 iterator on triangulation elements."""
@@ -189,44 +248,60 @@ def calculate(
189248 gross_or_net_qtos .setdefault (name , {})[quantity ] = formula
190249 formula_functions [formula ] = getattr (ifcopenshell .util .shape , formula )
191250
192- tasks : list [tuple [ifcopenshell .geom .iterator , QtosFormulas ]] = []
251+ tasks : list [tuple [Union [ ifcopenshell .geom .iterator , IteratorForTypes ] , QtosFormulas ]] = []
193252
194253 if gross_qtos :
195- tasks .append ((IfcOpenShell .create_iterator (ifc_file , cls .gross_settings , list (elements )), gross_qtos ))
254+ for iterator in IfcOpenShell .create_iterators (ifc_file , cls .gross_settings , list (elements )):
255+ tasks .append ((iterator , gross_qtos ))
196256
197257 if net_qtos :
198- tasks .append ((IfcOpenShell .create_iterator (ifc_file , cls .net_settings , list (elements )), net_qtos ))
258+ for iterator in IfcOpenShell .create_iterators (ifc_file , cls .gross_settings , list (elements )):
259+ tasks .append ((iterator , net_qtos ))
199260
200261 cls .unit_converter = SI2ProjectUnitConverter (ifc_file )
201262
202263 for iterator , qtos_ in tasks :
203264 if iterator .initialize ():
204265 while True :
205- shape = iterator .get ()
206- element = ifc_file .by_id (shape .id )
266+ if isinstance (iterator , ifcopenshell .geom .iterator ):
267+ shape = iterator .get ()
268+ geometry = shape .geometry
269+ element = ifc_file .by_id (shape .id )
270+ else :
271+ element , geometry = iterator .get_element_and_geometry ()
272+
207273 results .setdefault (element , {})
208274 for name , quantities in qtos_ .items ():
209275 results [element ].setdefault (name , {})
210276 for quantity , formula in quantities .items ():
211277 if formula == "get_segment_length" :
212- results [element ][name ][quantity ] = cls .get_segment_length (ifc_file , shape )
278+ results [element ][name ][quantity ] = cls .get_segment_length (element )
213279 else :
214280 results [element ][name ][quantity ] = cls .unit_converter .convert (
215- formula_functions [formula ](shape . geometry ),
281+ formula_functions [formula ](geometry ),
216282 IfcOpenShell .raw_functions [formula ].measure ,
217283 )
218284 if not iterator .next ():
219285 break
220286
221287 @staticmethod
222- def create_iterator (
288+ def create_iterators (
223289 ifc_file : ifcopenshell .file , settings : ifcopenshell .geom .settings , elements : list [ifcopenshell .entity_instance ]
224- ) -> ifcopenshell .geom .iterator :
225- return ifcopenshell .geom .iterator (settings , ifc_file , multiprocessing .cpu_count (), include = elements )
290+ ) -> list [Union [ifcopenshell .geom .iterator , IteratorForTypes ]]:
291+ elements_sorted : defaultdict [bool , list [ifcopenshell .entity_instance ]] = defaultdict (list )
292+ iterators = []
293+ for element in elements :
294+ elements_sorted [element .is_a ("IfcTypeProduct" )].append (element )
295+ if True in elements_sorted :
296+ iterators .append (IteratorForTypes (ifc_file , settings , elements_sorted [True ]))
297+ if False in elements_sorted :
298+ iterators .append (
299+ ifcopenshell .geom .iterator (settings , ifc_file , multiprocessing .cpu_count (), include = elements )
300+ )
301+ return iterators
226302
227303 @classmethod
228- def get_segment_length (cls , ifc_file : ifcopenshell .file , shape ) -> float :
229- element = ifc_file .by_id (shape .id )
304+ def get_segment_length (cls , element : ifcopenshell .entity_instance ) -> float :
230305 rep = ifcopenshell .util .representation .get_representation (element , "Model" , "Body" , "MODEL_VIEW" )
231306 if rep and len (rep .Items or []) == 1 and rep .Items [0 ].is_a ("IfcExtrudedAreaSolid" ):
232307 item = rep .Items [0 ]
0 commit comments