Coverage for bim2sim/elements/mapping/ifc2python.py: 34%
426 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-16 08:28 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-16 08:28 +0000
1"""Module to convert ifc data from to python data"""
2from __future__ import annotations
4import logging
5import math
6import os
7from collections.abc import Iterable
8from typing import Optional, Union, TYPE_CHECKING, Any
10import ifcopenshell
11from ifcopenshell import entity_instance, file, open as ifc_open, guid
12from pathlib import Path
13import pandas as pd
15from bim2sim.elements.mapping.units import parse_ifc
17if TYPE_CHECKING:
18 from bim2sim.elements.base_elements import ProductBased
20logger = logging.getLogger(__name__)
23def load_ifc(path: Path) -> file:
24 """loads the ifc file using ifcopenshell and returns the ifcopenshell
25 instance
27 Args:
28 path: Path to ifc file location
30 Returns:
31 ifc_file: ifcopenshell file object
32 """
33 logger = logging.getLogger('bim2sim')
34 if not isinstance(path, Path):
35 try:
36 path = Path(path)
37 except:
38 raise ValueError(f"Can't convert given path {path} to pathlib"
39 f" object")
40 logger.info(f"Loading IFC {path.name} from {path}")
41 if not os.path.exists(path):
42 raise IOError("Path '%s' does not exist" % (path))
43 try:
44 ifc_file = ifc_open(path)
45 except Exception as ex:
46 logger.error(f"Loading IFC raised the following error: {ex}")
47 raise RuntimeError("bim2sim canceled due to invalid IFC schema")
48 return ifc_file
51def reset_guids(ifc_file) -> file:
52 all_elements = ifc_file.by_type('IfcRoot')
53 for element in all_elements:
54 element.GlobalId = guid.new()
55 return ifc_file
58def property_set2dict(property_set: entity_instance,
59 ifc_units: Optional[dict]) -> dict:
60 """Converts IfcPropertySet and IfcQuantitySet to python dict
62 Takes all IfcPropertySet and IfcQuantitySet properties and quantities of
63 the
64 given IfcPropertySet or IfcQuantitySet and converts them into a
65 dictionary.
66 Also takes care of setting the correct unit to every property/quantity by
67 using the units defined in the IFC.
69 Args:
70 property_set: IfcPropertySet entity from ifcopenshell
71 ifc_units: dict with key ifc unit definition and value pint unit
72 Returns:
73 property_dict: dict with key name of the property/quantity and value
74 the property/quantity value as pint quantity if available
75 """
77 def add_quantities_to_dict(quantity):
78 quantity_unit = parse_ifc(quantity.Unit) if quantity.Unit else None
79 if not quantity_unit:
80 if prop.is_a() == 'IfcQuantityLength':
81 quantity_unit = ifc_units.get('ifclengthmeasure')
82 elif prop.is_a() == 'IfcQuantityArea':
83 quantity_unit = ifc_units.get('ifcareameasure')
84 elif prop.is_a() == 'IfcQuantityCount':
85 quantity_unit = ifc_units.get('ifccountmeasure')
86 elif prop.is_a() == 'IfcQuantityTime':
87 quantity_unit = ifc_units.get('ifctimemeasure')
88 elif prop.is_a() == 'IfcQuantityVolume':
89 quantity_unit = ifc_units.get('ifcvolumemeasure')
90 elif prop.is_a() == ' IfcQuantityWeight':
91 quantity_unit = ifc_units.get('ifcmassmeasure')
93 for attr, p_value in vars(quantity).items():
94 if attr.endswith('Value'):
95 if p_value is not None:
96 if quantity_unit:
97 property_dict[quantity.Name] = p_value * quantity_unit
98 else:
99 property_dict[quantity.Name] = p_value
100 break
102 def add_property_to_dict(prop):
103 if hasattr(prop, 'Unit'):
104 property_unit = parse_ifc(prop.Unit) if prop.Unit else None
105 else:
106 property_unit = None
107 if prop.is_a() == 'IfcPropertySingleValue':
108 if prop.NominalValue is not None:
109 property_unit = ifc_units.get(prop.NominalValue.is_a(
111 ).lower()) \
112 if not property_unit else property_unit
113 if property_unit:
114 property_dict[prop.Name] = \
115 prop.NominalValue.wrappedValue * property_unit
116 else:
117 property_dict[prop.Name] = prop.NominalValue.wrappedValue
118 elif prop.is_a() == 'IfcPropertyListValue':
119 values = []
120 for value in prop.ListValues:
121 property_unit = ifc_units.get(value.is_a().lower()) \
122 if not property_unit else property_unit
123 if property_unit:
124 values.append(value.wrappedValue * property_unit)
125 else:
126 values.append(value.wrappedValue)
127 property_dict[prop.Name] = values
128 elif prop.is_a() == 'IfcPropertyBoundedValue':
129 # TODO: Currently, we have no IFC with IfcPropertyBoundedValue.
130 # This needs testing when we get/create a IFC with this
131 #
133 value = prop.SetPointValue
134 min_val = prop.LowerBoundValue
135 max_val = prop.UpperBoundValue
136 property_unit = ifc_units.get(value.is_a().lower()) \
137 if not property_unit else property_unit
138 bounded_value = BoundedValue(
139 min_val, max_val, value, property_unit)
140 if property_unit:
141 property_dict[prop.Name] = bounded_value
142 elif prop.is_a() == 'IfcPropertyEnumeratedValue':
143 values = []
144 for value in prop.EnumerationValues:
145 property_unit = ifc_units.get(value.is_a().lower()) \
146 if not property_unit else property_unit
147 if property_unit:
148 values.append(value.wrappedValue * property_unit)
149 else:
150 values.append(value.wrappedValue)
151 property_dict[prop.Name] = values
152 elif prop.is_a() == 'IfcPropertyReferenceValue':
153 # handle tables
154 if prop.PropertyReference.is_a('IfcTable'):
155 prop_name = prop.Name + "_" + prop.Description
156 table = prop.PropertyReference
157 columns = table.Columns
158 rows = table.Rows
159 dataframe = pd.DataFrame(
160 columns=[column.Description for column in columns])
161 dataframe.name = prop_name
162 # Populate the DataFrame with data from IfcTable
163 for row in rows:
164 row_data = [entry for entry in row.RowCells]
165 if len(row_data) != len(dataframe.columns):
166 row_data = row_data[:len(dataframe.columns)]
167 dataframe = dataframe._append(
168 pd.Series(row_data, index=dataframe.columns),
169 ignore_index=True)
170 # TODO
171 property_dict[prop.Name] = dataframe
172 else:
173 raise NotImplementedError(
174 "Property of type '%s' is currently only implemented for "
175 "IfcTable." % prop.is_a())
177 property_dict = {}
178 if hasattr(property_set, 'HasProperties') and \
179 getattr(property_set, 'HasProperties') is not None:
180 for prop in property_set.HasProperties:
181 # IfcComplexProperty
182 if hasattr(prop, 'HasProperties') and \
183 getattr(prop, 'HasProperties') is not None:
184 for property in prop.HasProperties:
185 add_property_to_dict(property)
186 else:
187 add_property_to_dict(prop)
189 elif hasattr(property_set, 'Quantities') and \
190 getattr(property_set, 'Quantities') is not None:
191 for prop in property_set.Quantities:
192 # IfcPhysicalComplexQuantity
193 if hasattr(prop, 'HasQuantities') and \
194 getattr(prop, 'HasQuantities') is not None:
195 for quantity in prop.HasQuantities:
196 add_quantities_to_dict(quantity)
197 else:
198 add_quantities_to_dict(prop)
199 elif hasattr(property_set, 'Properties') and \
200 getattr(property_set, 'Properties') is not None:
201 for prop in property_set.Properties:
202 property_unit = parse_ifc(prop.Unit) if prop.Unit else None
203 if prop.is_a() == 'IfcPropertySingleValue':
204 if prop.NominalValue is not None:
205 property_unit = ifc_units.get(
206 prop.NominalValue.is_a().lower()) \
207 if not property_unit else property_unit
208 if property_unit:
209 property_dict[prop.Name] = \
210 prop.NominalValue.wrappedValue * property_unit
211 else:
212 property_dict[prop.Name] = \
213 prop.NominalValue.wrappedValue
214 elif prop.is_a() == 'IfcPropertyListValue':
215 values = []
216 for value in prop.ListValues:
217 property_unit = ifc_units.get(value.is_a().lower()) \
218 if not property_unit else property_unit
219 values.append(value.wrappedValue * property_unit)
220 property_dict[prop.Name] = values
221 elif prop.is_a() == 'IfcPropertyBoundedValue':
222 # TODO: Currently, we have no IFC with IfcPropertyBoundedValue.
223 # This needs testing when we get/create a IFC with this
225 value = prop.SetPointValue
226 min_val = prop.LowerBoundValue
227 max_val = prop.UpperBoundValue
228 property_unit = ifc_units.get(value.is_a().lower()) \
229 if not property_unit else property_unit
230 bounded_value = BoundedValue(
231 min_val, max_val, value, property_unit)
232 if property_unit:
233 property_dict[prop.Name] = bounded_value
234 return property_dict
237def get_layers_ifc(element: Union[entity_instance, ProductBased]):
238 # TODO only used for check, maybe we can use functions of base_tasks.py
239 # instead
240 """
241 Returns layers information of an element as list. It can be applied to
242 an IFCProduct directly or a Bim2Sim Instance. This only used to pre
243 instance
244 creation check of IFC file now.
245 Args:
246 element: ifcopenshell instance or bim2sim Instance
248 Returns:
249 layers_list: list of all organized layers with all material information
250 """
251 layers_list = []
252 relation = 'RelatingMaterial'
253 ifc_instance = element.ifc if hasattr(element, 'ifc') else element
254 assoc_list = getIfcAttribute(ifc_instance, "HasAssociations") if \
255 hasattr(ifc_instance, 'HasAssociations') else []
256 for assoc in assoc_list:
257 association = getIfcAttribute(assoc, relation)
258 if association is not None:
259 layer_list = None
260 if association.is_a('IfcMaterial'):
261 layer_list = [association]
262 # IfcMaterialLayerSetUsage
263 elif hasattr(association, 'ForLayerSet'):
264 layer_list = association.ForLayerSet.MaterialLayers
265 # single IfcMaterial
266 elif hasattr(association, 'Materials'):
267 layer_list = association.Materials
268 elif hasattr(association, 'MaterialLayers'):
269 layer_list = association.MaterialLayers
270 elif hasattr(association, 'MaterialConstituents'):
271 layer_list = [Constituent.Material for Constituent in
272 association.MaterialConstituents]
273 elif hasattr(association, 'MaterialProfiles'):
274 layer_list = [profile.Material for profile in
275 association.MaterialProfiles]
277 if isinstance(layer_list, Iterable):
278 for layer in layer_list:
279 layers_list.append(layer)
280 return layers_list
283def getElementByGUID(ifcfile, guid):
284 element = ifcfile.by_guid(guid)
285 return element
288def getIfcAttribute(ifcElement, attribute):
289 """
290 Fetches non-empty attributes (if they exist).
291 """
292 try:
293 return getattr(ifcElement, attribute)
294 except AttributeError:
295 pass
298def get_property_set_by_name(property_set_name: str, element: entity_instance,
299 ifc_units: dict) -> dict:
300 """Try to find a IfcPropertySet for a given ifc instance by it's name.
302 This function searches an elements PropertySets for the defined
303 PropertySetName. If the PropertySet is found the function will return a
304 dict with the properties and their values. If the PropertySet is not
305 found the function will return None
307 Args:
308 property_set_name: Name of the PropertySet you are looking for
309 element: ifcopenshell element to search for PropertySet
310 ifc_units: dict with key ifc unit definition and value pint unit
311 Returns:
312 property_set: dict with key name of the property/quantity and value
313 the property/quantity value as pint quantity if available if found
314 """
315 property_dict = None
316 all_property_sets_list = element.IsDefinedBy
317 preselection = [item for item in all_property_sets_list
318 if hasattr(item, 'RelatingPropertyDefinition')]
319 property_set = next(
320 (item for item in preselection if
321 item.RelatingPropertyDefinition.Name == property_set_name), None)
322 if hasattr(property_set, 'RelatingPropertyDefinition'):
323 property_dict = property_set2dict(
324 property_set.RelatingPropertyDefinition, ifc_units)
325 return property_dict
328def get_property_sets(element: entity_instance, ifc_units: dict) -> dict:
329 """Returns all PropertySets of element
332 Args:
333 element: The element in which you want to search for the PropertySets
334 ifc_units: dict holding all unit definitions from ifc_units
336 Returns:
337 dict of dicts for each PropertySet. Each dict with key property name
338 and value its value
340 """
341 property_sets = {}
342 if hasattr(element, 'IsDefinedBy') and \
343 getattr(element, 'IsDefinedBy') is not None:
344 for defined in element.IsDefinedBy:
345 property_set_name = defined.RelatingPropertyDefinition.Name
346 property_sets[property_set_name] = property_set2dict(
347 defined.RelatingPropertyDefinition, ifc_units)
348 elif hasattr(element, 'Material') and \
349 getattr(element, 'Material') is not None:
350 for defined in element.Material.HasProperties:
351 property_set_name = defined.Name
352 property_sets[property_set_name] = property_set2dict(
353 defined, ifc_units)
354 elif element.is_a('IfcMaterial'):
355 for defined in element.HasProperties:
356 property_set_name = defined.Name
357 property_sets[property_set_name] = property_set2dict(
358 defined, ifc_units)
360 return property_sets
363def get_type_property_sets(element, ifc_units):
364 """Returns all PropertySets of element's types
366 :param element: The element in which you want to search for the
367 PropertySets
368 :return: dict(of dicts)"""
369 # TODO: use guids to get type property_sets (they are userd by many
370 # entitys)
371 property_sets = {}
372 if hasattr(element, 'IsTypedBy') and \
373 getattr(element, 'IsTypedBy') is not None:
374 for defined_type in element.IsTypedBy:
375 for property_set in defined_type.RelatingType.HasPropertySets:
376 property_sets[property_set.Name] = property_set2dict(
377 property_set, ifc_units)
379 return property_sets
382def get_quantity_sets(element, ifc_units):
383 """Returns all QuantitySets of element"""
385 quantity_sets = {}
386 if hasattr(element, 'IsTypedBy') and \
387 getattr(element, 'IsTypedBy') is not None:
388 for defined_type in element.IsTypedBy:
389 for quantityset in defined_type.RelatingType.Quantities:
390 quantity_sets[quantityset.Name] = property_set2dict(
391 quantityset, ifc_units)
393 return quantity_sets
396def get_guid(ifcElement):
397 """
398 Returns the global id of the IFC element
399 """
400 try:
401 return getattr(ifcElement, 'GlobalId')
402 except TypeError:
403 pass
406def get_predefined_type(ifcElement) -> Union[str, None]:
407 """Returns the predefined type of the IFC element"""
408 try:
409 predefined_type = getattr(ifcElement, 'PredefinedType')
410 # todo cache "USERDEFINED" and check where information is stored
411 if predefined_type == "NOTDEFINED":
412 predefined_type = None
413 return predefined_type
414 except AttributeError:
415 return None
418def getElementType(ifcElement):
419 """Return the ifctype of the IFC element"""
420 try:
421 return ifcElement.wrapped_data.is_a()
422 except TypeError:
423 pass
426def checkIfcElementType(ifcElement, ifcType):
427 """Checks for matching IFC element types."""
428 if getIfcAttribute(ifcElement, 'is_a'):
429 return ifcElement.is_a() == ifcType
432def getHierarchicalParent(ifcElement):
433 """Fetch the first structural parent element."""
434 parent = getIfcAttribute(ifcElement, 'Decomposes')
435 if parent:
436 return parent[0].RelatingObject
437 # ... try again for voids:
438 parent = getIfcAttribute(ifcElement, 'VoidsElements')
439 if parent:
440 return parent[0].RelatingBuildingElement
443def getHierarchicalChildren(ifcElement):
444 """Fetch the child elements from the first structural aggregation."""
445 children = getIfcAttribute(ifcElement, 'IsDecomposedBy')
446 if children:
447 return children[0].RelatedObjects
448 # ... try again for voids:
449 children = getIfcAttribute(ifcElement, 'HasOpenings')
450 if children:
451 # just get the voids, not the relations
452 return [i.RelatedOpeningElement for i in children]
455def getSpatialParent(ifcElement):
456 """Fetch the first spatial parent element."""
457 parent = getIfcAttribute(ifcElement, 'ContainedInStructure')
458 if parent:
459 return parent[0].RelatingStructure
462def getSpatialChildren(ifcElement):
463 """Fetch the child elements from the first spatial structure."""
464 children = getIfcAttribute(ifcElement, 'ContainsElements')
465 if children:
466 return children[0].RelatedElements
469def getSpace(ifcElement):
470 """Find the space for this element."""
471 if ifcElement:
472 # direct spatial child elements in space
473 parent = getSpatialParent(ifcElement)
474 if checkIfcElementType(parent, 'IfcSpace'):
475 return parent
476 else:
477 # hierachically nested building elements
478 parent = getHierarchicalParent(ifcElement)
479 if checkIfcElementType(parent, 'IfcSpace'):
480 return parent
481 else:
482 return getSpace(parent)
485def getStorey(ifcElement):
486 """Find the building storey for this element."""
487 if ifcElement:
488 # direct spatial child elements in space or storey
489 parent = getSpatialParent(ifcElement)
490 if checkIfcElementType(parent, 'IfcBuildingStorey'):
491 return parent
492 elif checkIfcElementType(parent, 'IfcSpace'):
493 return getStorey(parent)
494 else:
495 # hierachically nested building elements
496 parent = getHierarchicalParent(ifcElement)
497 if checkIfcElementType(parent, 'IfcBuildingStorey'):
498 return parent
499 else:
500 return getStorey(parent)
503def getBuilding(ifcElement):
504 """Find the building for this element."""
505 if ifcElement:
506 # direct spatial child elements outside of storey
507 parent = getSpatialParent(ifcElement)
508 if checkIfcElementType(parent, 'IfcBuilding'):
509 return parent
510 else:
511 storey = getStorey(ifcElement)
512 if storey:
513 # part of building storey
514 parent = getHierarchicalParent(storey)
515 else:
516 # hierarchical child elements outside of storey
517 parent = getHierarchicalParent(ifcElement)
518 # ... and finally check and return or start recursion
519 if checkIfcElementType(parent, 'IfcBuilding'):
520 return parent
521 else:
522 return getBuilding(parent)
525def getSite(ifcElement):
526 """Find the building site for this element."""
527 if ifcElement:
528 # direct spatial child elements outside of building
529 parent = getSpatialParent(ifcElement)
530 if checkIfcElementType(parent, 'IfcSite'):
531 return parent
532 else:
533 building = getBuilding(ifcElement)
534 if building:
535 # part of building
536 parent = getHierarchicalParent(building)
537 else:
538 # hierarchical child elements outside of building
539 parent = getHierarchicalParent(ifcElement)
540 # ... and finally check and return or start recursion
541 if checkIfcElementType(parent, 'IfcSite'):
542 return parent
543 else:
544 return getSite(parent)
547def getProject(ifcElement):
548 """Find the project/root for this element."""
549 if ifcElement:
550 # the project might be the hierarchical parent of the site ...
551 site = getSite(ifcElement)
552 parent = getHierarchicalParent(site)
553 if checkIfcElementType(parent, 'IfcProject'):
554 return parent
555 else:
556 # ... or of the building (no site defined) ...
557 building = getBuilding(ifcElement)
558 parent = getHierarchicalParent(site)
559 if checkIfcElementType(parent, 'IfcProject'):
560 return parent
561 # ... or the parent of an IfcSpatialZone, which is non-hierarchical.
564def get_true_north(ifcElement: entity_instance):
565 """Find the true north in degree of this element, 0 °C means positive
566 Y-axis. 45 °C Degree means middle between X- and Y-Axis"""
567 project = getProject(ifcElement)
568 try:
569 true_north = project.RepresentationContexts[
570 0].TrueNorth.DirectionRatios
571 except AttributeError as er:
572 logger.warning(f"Not able to calculate true north of IFC element "
573 f"{ifcElement} due to error: {er}")
574 true_north = [0, 1]
575 angle_true_north = math.degrees(math.atan(true_north[0] / true_north[1]))
576 return angle_true_north
579def get_ports(element: entity_instance) -> list[Any]:
580 """Get all ports for new and old IFC definition of ports.
582 Args:
583 element: ifcopenshell element to check for ports
584 Returns:
585 ports: list of all ports connected to the element
586 """
587 ports = []
588 # new IfcStandard with IfcRelNests
589 ports_nested = list(getattr(element, 'IsNestedBy', []))
590 # old IFC standard with IfcRelConnectsPortToElement
591 ports_connects = list(getattr(element, 'HasPorts', []))
593 for nested in ports_nested:
594 for port_connection in nested.RelatedObjects:
595 ports.append(port_connection)
597 for connected in ports_connects:
598 ports.append(connected.RelatingPort)
600 return ports
603def get_ports_connections(element_port: entity_instance) -> list[Any]:
604 """Get all connected ports to a given port.
606 Args:
607 element_port: ifcopenshell port element to check for connections
608 Returns:
609 connected_ports: list of all ports connected to given element_port
610 """
611 connected_ports = \
612 [conn.RelatingPort for conn in element_port.ConnectedFrom] + \
613 [conn.RelatedPort for conn in element_port.ConnectedTo]
614 return connected_ports
617def get_ports_parent(element: entity_instance) -> list[Any]:
618 """Get the parent of given port for new and old Ifc definitions of ports.
620 Args:
621 element: ifcopenshell port element which parents are searched
622 Returns:
623 parents: list of ifcopenshell elements that are parent of the port
624 """
625 parents = []
626 parent_nested = list(getattr(element, 'Nests', []))
627 for nest in parent_nested:
628 parents.append(nest.RelatingObject)
629 return parents
632def convertToSI(ifcUnit, value):
633 # TODO not used anywhere. Remove?
634 """Return the value in basic SI units, conversion according to ifcUnit."""
635 # IfcSIPrefix values
636 ifcSIPrefix = {
637 'EXA': 1e18,
638 'PETA': 1e15,
639 'TERA': 1e12,
640 'GIGA': 1e9,
641 'MEGA': 1e6,
642 'KILO': 1e3,
643 'HECTO': 1e2,
644 'DECA': 1e1,
645 'DECI': 1e-1,
646 'CENTI': 1e-2,
647 'MILLI': 1e-3,
648 'MICRO': 1e-6,
649 'NANO': 1e-9,
650 'PICO': 1e-12,
651 'FEMTO': 1e-15,
652 'ATTO': 1e-18
653 }
655 if checkIfcElementType(ifcUnit, 'IfcSIUnit'):
656 # just check the prefix to normalize the values
657 prefix = ifcUnit.Prefix
658 if prefix:
659 return value * ifcSIPrefix[prefix]
660 else:
661 return value
662 elif checkIfcElementType(ifcUnit, 'IfcConversionBasedUnit'):
663 factor = ifcUnit.ConversionFactor.ValueComponent.wrappedValue
664 return value * factor
667def summary(ifcelement):
668 txt = "** Summary **\n"
669 for k, v in ifcelement.get_info().items():
670 txt += "%s: %s\n" % (k, v)
672 # "HasAssignments", "ContainedInStructure", "IsTypedBy", "HasAssociations"
673 relations = ("HasPorts", "IsDefinedBy",)
674 for rel in relations:
675 attr = getattr(ifcelement, rel)
676 if attr:
677 txt += "%s:\n" % (rel)
678 for item in attr:
679 if item.is_a() == 'IfcRelDefinesByProperties':
680 pset = item.RelatingPropertyDefinition
681 txt += " - %s\n" % (pset.Name)
682 for prop in pset.HasProperties:
683 txt += " - %s\n" % (prop)
684 else:
685 txt += " - %s\n" % (item)
687 # for attrname in dir(ifcelement):
688 # if attrname.startswith('__'):
689 # continue
690 # value = getattr(ifcelement, attrname)
691 # if not value:
692 # continue
693 # txt += "\n%s:%s"%(attrname, value)
695 return txt
698def used_properties(ifc_file):
699 """Filters given IFC for property_sets.
701 Returns a dictionary with related ifc types as keys and lists of used
702 propertysets as values"""
703 props = ifc_file.by_type("IFCPROPERTYSET")
704 tuples = []
705 for prop in props:
706 for occ in prop.DefinesOccurrence:
707 for ro in occ.RelatedObjects:
708 tuples.append((ro.is_a(), prop.Name))
709 tuples = set(tuples)
710 types = set(tup[0] for tup in tuples)
711 type_dict = {typ: [] for typ in types}
712 for tup in tuples:
713 type_dict[tup[0]].append(tup[1])
714 return type_dict
717def check_guid(guid_to_check: str) -> bool:
718 """Validates if a string is a valid IFC GUID.
720 This function is copied from IfcOpenShell v0.8 as it was not available in
721 previous versions. When we update to IfcOpenShell v0.8 this will be
722 replaced by ifcopenshell.validate.validate_guid.
724 Args:
725 guid_to_check: A string representing the IFC GUID to validate.
727 Returns:
728 bool: True if the GUID is valid, False otherwise.
730 Example:
731 >>> check_guid("0uNn8l9HL3BO$dCqBNQRCZ")
732 True
733 >>> check_guid("invalid_guid")
734 False # Also logs an error message
735 """
736 # TODO update, when updating to IfcOpenShell 0.8 #792
737 error = None
738 if len(guid_to_check) != 22:
739 error = "Guid length should be 22 characters."
740 elif guid_to_check[0] not in "0123":
741 error = "Guid first character must be either a 0, 1, 2, or 3."
742 else:
743 try:
744 ifcopenshell.guid.expand(guid_to_check)
745 except Exception:
746 allowed_characters = \
747 ("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs"
748 "tuvwxyz_$")
749 if any(c for c in guid_to_check if c not in allowed_characters):
750 error = (
751 "Guid contains invalid characters, allowed "
752 "characters: "
753 "'%s'.") % allowed_characters
754 else:
755 error = "Couldn't decompress guid, it's not base64 encoded."
757 if error:
758 logging.error(f"GUID {guid_to_check} is invalid: {error}")
759 return False
760 return True
763class BoundedValue(float):
764 def __new__(cls, min_val, max_val, initial_val, unit=None):
765 instance = super().__new__(cls, initial_val)
766 instance.min_val = min_val
767 instance.max_val = max_val
768 instance.unit = unit
769 return instance
771 def __init__(self, min_val, max_val, initial_val, unit=None):
772 self.unit = unit
773 if self.unit:
774 self.min_val = min_val * unit
775 self.max_val = max_val * unit
776 else:
777 self.min_val = min_val
778 self.max_val = max_val
780 def __set__(self, new_value):
781 if isinstance(new_value, pint.Quantity):
782 if self.unit:
783 new_value = new_value.to(self.unit).magnitude
784 else:
785 new_value = new_value.magnitude
787 if self.min_val <= new_value <= self.max_val:
788 return BoundedValue(self.min_val, self.max_val, new_value,
789 self.unit)
790 else:
791 print(f"Value should be between {self.min_val} and {self.max_val}")
792 return BoundedValue(self.min_val, self.max_val, self, self.unit)
794 def __iter__(self):
795 yield self.min_val
796 yield self.max_val
798 def __str__(self):
799 if self.unit:
800 return f"{self.__float__()} {self.unit}"
801 else:
802 return str(self.__float__())