Coverage for bim2sim/elements/mapping/ifc2python.py: 30%
409 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +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 the
63 given IfcPropertySet or IfcQuantitySet and converts them into a dictionary.
64 Also takes care of setting the correct unit to every property/quantity by
65 using the units defined in the IFC.
67 Args:
68 property_set: IfcPropertySet entity from ifcopenshell
69 ifc_units: dict with key ifc unit definition and value pint unit
70 Returns:
71 property_dict: dict with key name of the property/quantity and value
72 the property/quantity value as pint quantity if available
73 """
75 def add_quantities_to_dict(quantity):
76 quantity_unit = parse_ifc(quantity.Unit) if quantity.Unit else None
77 if not quantity_unit:
78 if prop.is_a() == 'IfcQuantityLength':
79 quantity_unit = ifc_units.get('ifclengthmeasure')
80 elif prop.is_a() == 'IfcQuantityArea':
81 quantity_unit = ifc_units.get('ifcareameasure')
82 elif prop.is_a() == 'IfcQuantityCount':
83 quantity_unit = ifc_units.get('ifccountmeasure')
84 elif prop.is_a() == 'IfcQuantityTime':
85 quantity_unit = ifc_units.get('ifctimemeasure')
86 elif prop.is_a() == 'IfcQuantityVolume':
87 quantity_unit = ifc_units.get('ifcvolumemeasure')
88 elif prop.is_a() == ' IfcQuantityWeight':
89 quantity_unit = ifc_units.get('ifcmassmeasure')
91 for attr, p_value in vars(quantity).items():
92 if attr.endswith('Value'):
93 if p_value is not None:
94 if quantity_unit:
95 property_dict[quantity.Name] = p_value * quantity_unit
96 else:
97 property_dict[quantity.Name] = p_value
98 break
100 def add_property_to_dict(prop):
101 if hasattr(prop, 'Unit'):
102 property_unit = parse_ifc(prop.Unit) if prop.Unit else None
103 else:
104 property_unit = None
105 if prop.is_a() == 'IfcPropertySingleValue':
106 if prop.NominalValue is not None:
107 property_unit = ifc_units.get(prop.NominalValue.is_a().lower())\
108 if not property_unit else property_unit
109 if property_unit:
110 property_dict[prop.Name] = \
111 prop.NominalValue.wrappedValue * property_unit
112 else:
113 property_dict[prop.Name] = prop.NominalValue.wrappedValue
114 elif prop.is_a() == 'IfcPropertyListValue':
115 values = []
116 for value in prop.ListValues:
117 property_unit = ifc_units.get(value.is_a().lower())\
118 if not property_unit else property_unit
119 if property_unit:
120 values.append(value.wrappedValue * property_unit)
121 else:
122 values.append(value.wrappedValue)
123 property_dict[prop.Name] = values
124 elif prop.is_a() == 'IfcPropertyBoundedValue':
125 # TODO: Currently, we have no IFC with IfcPropertyBoundedValue.
126 # This needs testing when we get/create a IFC with this
127 #
129 value = prop.SetPointValue
130 min_val = prop.LowerBoundValue
131 max_val = prop.UpperBoundValue
132 property_unit = ifc_units.get(value.is_a().lower()) \
133 if not property_unit else property_unit
134 bounded_value = BoundedValue(
135 min_val, max_val, value, property_unit)
136 if property_unit:
137 property_dict[prop.Name] = bounded_value
138 elif prop.is_a() == 'IfcPropertyEnumeratedValue':
139 values = []
140 for value in prop.EnumerationValues:
141 property_unit = ifc_units.get(value.is_a().lower())\
142 if not property_unit else property_unit
143 if property_unit:
144 values.append(value.wrappedValue * property_unit)
145 else:
146 values.append(value.wrappedValue)
147 property_dict[prop.Name] = values
148 elif prop.is_a() == 'IfcPropertyReferenceValue':
149 # handle tables
150 if prop.PropertyReference.is_a('IfcTable'):
151 prop_name = prop.Name + "_" + prop.Description
152 table = prop.PropertyReference
153 columns = table.Columns
154 rows = table.Rows
155 dataframe = pd.DataFrame(
156 columns=[column.Description for column in columns])
157 dataframe.name = prop_name
158 # Populate the DataFrame with data from IfcTable
159 for row in rows:
160 row_data = [entry for entry in row.RowCells]
161 if len(row_data) != len(dataframe.columns):
162 row_data = row_data[:len(dataframe.columns)]
163 dataframe = dataframe._append(
164 pd.Series(row_data, index=dataframe.columns),
165 ignore_index=True)
166 # TODO
167 property_dict[prop.Name] = dataframe
168 else:
169 raise NotImplementedError(
170 "Property of type '%s' is currently only implemented for "
171 "IfcTable."%prop.is_a())
173 property_dict = {}
174 if hasattr(property_set, 'HasProperties') and\
175 getattr(property_set, 'HasProperties') is not None:
176 for prop in property_set.HasProperties:
177 # IfcComplexProperty
178 if hasattr(prop, 'HasProperties') and \
179 getattr(prop, 'HasProperties') is not None:
180 for property in prop.HasProperties:
181 add_property_to_dict(property)
182 else:
183 add_property_to_dict(prop)
185 elif hasattr(property_set, 'Quantities') and\
186 getattr(property_set, 'Quantities') is not None:
187 for prop in property_set.Quantities:
188 # IfcPhysicalComplexQuantity
189 if hasattr(prop, 'HasQuantities') and \
190 getattr(prop, 'HasQuantities') is not None:
191 for quantity in prop.HasQuantities:
192 add_quantities_to_dict(quantity)
193 else:
194 add_quantities_to_dict(prop)
195 elif hasattr(property_set, 'Properties') and\
196 getattr(property_set, 'Properties') is not None:
197 for prop in property_set.Properties:
198 property_unit = parse_ifc(prop.Unit) if prop.Unit else None
199 if prop.is_a() == 'IfcPropertySingleValue':
200 if prop.NominalValue is not None:
201 property_unit = ifc_units.get(prop.NominalValue.is_a().lower())\
202 if not property_unit else property_unit
203 if property_unit:
204 property_dict[prop.Name] = \
205 prop.NominalValue.wrappedValue * property_unit
206 else:
207 property_dict[prop.Name] = \
208 prop.NominalValue.wrappedValue
209 elif prop.is_a() == 'IfcPropertyListValue':
210 values = []
211 for value in prop.ListValues:
212 property_unit = ifc_units.get(value.is_a().lower()) \
213 if not property_unit else property_unit
214 values.append(value.wrappedValue * property_unit)
215 property_dict[prop.Name] = values
216 elif prop.is_a() == 'IfcPropertyBoundedValue':
217 # TODO: Currently, we have no IFC with IfcPropertyBoundedValue.
218 # This needs testing when we get/create a IFC with this
220 value = prop.SetPointValue
221 min_val = prop.LowerBoundValue
222 max_val = prop.UpperBoundValue
223 property_unit = ifc_units.get(value.is_a().lower()) \
224 if not property_unit else property_unit
225 bounded_value = BoundedValue(
226 min_val, max_val, value, property_unit)
227 if property_unit:
228 property_dict[prop.Name] = bounded_value
229 return property_dict
232def get_layers_ifc(element: Union[entity_instance, ProductBased]):
233 # TODO only used for check, maybe we can use functions of base_tasks.py instead
234 """
235 Returns layers information of an element as list. It can be applied to
236 an IFCProduct directly or a Bim2Sim Instance. This only used to pre instance
237 creation check of IFC file now.
238 Args:
239 element: ifcopenshell instance or bim2sim Instance
241 Returns:
242 layers_list: list of all organized layers with all material information
243 """
244 layers_list = []
245 relation = 'RelatingMaterial'
246 ifc_instance = element.ifc if hasattr(element, 'ifc') else element
247 assoc_list = getIfcAttribute(ifc_instance, "HasAssociations") if \
248 hasattr(ifc_instance, 'HasAssociations') else []
249 for assoc in assoc_list:
250 association = getIfcAttribute(assoc, relation)
251 if association is not None:
252 layer_list = None
253 if association.is_a('IfcMaterial'):
254 layer_list = [association]
255 # IfcMaterialLayerSetUsage
256 elif hasattr(association, 'ForLayerSet'):
257 layer_list = association.ForLayerSet.MaterialLayers
258 # single IfcMaterial
259 elif hasattr(association, 'Materials'):
260 layer_list = association.Materials
261 elif hasattr(association, 'MaterialLayers'):
262 layer_list = association.MaterialLayers
263 elif hasattr(association, 'MaterialConstituents'):
264 layer_list = [Constituent.Material for Constituent in
265 association.MaterialConstituents]
266 elif hasattr(association, 'MaterialProfiles'):
267 layer_list = [profile.Material for profile in
268 association.MaterialProfiles]
270 if isinstance(layer_list, Iterable):
271 for layer in layer_list:
272 layers_list.append(layer)
273 return layers_list
276def getElementByGUID(ifcfile, guid):
277 element = ifcfile.by_guid(guid)
278 return element
281def getIfcAttribute(ifcElement, attribute):
282 """
283 Fetches non-empty attributes (if they exist).
284 """
285 try:
286 return getattr(ifcElement, attribute)
287 except AttributeError:
288 pass
291def get_property_set_by_name(property_set_name: str, element: entity_instance,
292 ifc_units: dict) -> dict:
293 """Try to find a IfcPropertySet for a given ifc instance by it's name.
295 This function searches an elements PropertySets for the defined
296 PropertySetName. If the PropertySet is found the function will return a
297 dict with the properties and their values. If the PropertySet is not
298 found the function will return None
300 Args:
301 property_set_name: Name of the PropertySet you are looking for
302 element: ifcopenshell element to search for PropertySet
303 ifc_units: dict with key ifc unit definition and value pint unit
304 Returns:
305 property_set: dict with key name of the property/quantity and value
306 the property/quantity value as pint quantity if available if found
307 """
308 property_dict = None
309 all_property_sets_list = element.IsDefinedBy
310 preselection = [item for item in all_property_sets_list
311 if hasattr(item, 'RelatingPropertyDefinition')]
312 property_set = next(
313 (item for item in preselection if
314 item.RelatingPropertyDefinition.Name == property_set_name), None)
315 if hasattr(property_set, 'RelatingPropertyDefinition'):
316 property_dict = property_set2dict(
317 property_set.RelatingPropertyDefinition, ifc_units)
318 return property_dict
321def get_property_sets(element: entity_instance, ifc_units: dict) -> dict:
322 """Returns all PropertySets of element
325 Args:
326 element: The element in which you want to search for the PropertySets
327 ifc_units: dict holding all unit definitions from ifc_units
329 Returns:
330 dict of dicts for each PropertySet. Each dict with key property name
331 and value its value
333 """
334 property_sets = {}
335 if hasattr(element, 'IsDefinedBy') and\
336 getattr(element, 'IsDefinedBy') is not None:
337 for defined in element.IsDefinedBy:
338 property_set_name = defined.RelatingPropertyDefinition.Name
339 property_sets[property_set_name] = property_set2dict(
340 defined.RelatingPropertyDefinition, ifc_units)
341 elif hasattr(element, 'Material') and\
342 getattr(element, 'Material') is not None:
343 for defined in element.Material.HasProperties:
344 property_set_name = defined.Name
345 property_sets[property_set_name] = property_set2dict(
346 defined, ifc_units)
347 elif element.is_a('IfcMaterial'):
348 for defined in element.HasProperties:
349 property_set_name = defined.Name
350 property_sets[property_set_name] = property_set2dict(
351 defined, ifc_units)
353 return property_sets
356def get_type_property_sets(element, ifc_units):
357 """Returns all PropertySets of element's types
359 :param element: The element in which you want to search for the PropertySets
360 :return: dict(of dicts)"""
361 # TODO: use guids to get type property_sets (they are userd by many entitys)
362 property_sets = {}
363 if hasattr(element, 'IsTypedBy') and\
364 getattr(element, 'IsTypedBy') is not None:
365 for defined_type in element.IsTypedBy:
366 for property_set in defined_type.RelatingType.HasPropertySets:
367 property_sets[property_set.Name] = property_set2dict(
368 property_set, ifc_units)
370 return property_sets
373def get_quantity_sets(element, ifc_units):
374 """Returns all QuantitySets of element"""
376 quantity_sets = {}
377 if hasattr(element, 'IsTypedBy') and \
378 getattr(element, 'IsTypedBy') is not None:
379 for defined_type in element.IsTypedBy:
380 for quantityset in defined_type.RelatingType.Quantities:
381 quantity_sets[quantityset.Name] = property_set2dict(
382 quantityset, ifc_units)
384 return quantity_sets
387def get_guid(ifcElement):
388 """
389 Returns the global id of the IFC element
390 """
391 try:
392 return getattr(ifcElement, 'GlobalId')
393 except TypeError:
394 pass
397def get_predefined_type(ifcElement) -> Union[str, None]:
398 """Returns the predefined type of the IFC element"""
399 try:
400 predefined_type = getattr(ifcElement, 'PredefinedType')
401 # todo cache "USERDEFINED" and check where information is stored
402 if predefined_type == "NOTDEFINED":
403 predefined_type = None
404 return predefined_type
405 except AttributeError:
406 return None
409def getElementType(ifcElement):
410 """Return the ifctype of the IFC element"""
411 try:
412 return ifcElement.wrapped_data.is_a()
413 except TypeError:
414 pass
417def checkIfcElementType(ifcElement, ifcType):
418 """Checks for matching IFC element types."""
419 if getIfcAttribute(ifcElement, 'is_a'):
420 return ifcElement.is_a() == ifcType
423def getHierarchicalParent(ifcElement):
424 """Fetch the first structural parent element."""
425 parent = getIfcAttribute(ifcElement, 'Decomposes')
426 if parent:
427 return parent[0].RelatingObject
428 # ... try again for voids:
429 parent = getIfcAttribute(ifcElement, 'VoidsElements')
430 if parent:
431 return parent[0].RelatingBuildingElement
434def getHierarchicalChildren(ifcElement):
435 """Fetch the child elements from the first structural aggregation."""
436 children = getIfcAttribute(ifcElement, 'IsDecomposedBy')
437 if children:
438 return children[0].RelatedObjects
439 # ... try again for voids:
440 children = getIfcAttribute(ifcElement, 'HasOpenings')
441 if children:
442 # just get the voids, not the relations
443 return [i.RelatedOpeningElement for i in children]
446def getSpatialParent(ifcElement):
447 """Fetch the first spatial parent element."""
448 parent = getIfcAttribute(ifcElement, 'ContainedInStructure')
449 if parent:
450 return parent[0].RelatingStructure
453def getSpatialChildren(ifcElement):
454 """Fetch the child elements from the first spatial structure."""
455 children = getIfcAttribute(ifcElement, 'ContainsElements')
456 if children:
457 return children[0].RelatedElements
460def getSpace(ifcElement):
461 """Find the space for this element."""
462 if ifcElement:
463 # direct spatial child elements in space
464 parent = getSpatialParent(ifcElement)
465 if checkIfcElementType(parent, 'IfcSpace'):
466 return parent
467 else:
468 # hierachically nested building elements
469 parent = getHierarchicalParent(ifcElement)
470 if checkIfcElementType(parent, 'IfcSpace'):
471 return parent
472 else:
473 return getSpace(parent)
476def getStorey(ifcElement):
477 """Find the building storey for this element."""
478 if ifcElement:
479 # direct spatial child elements in space or storey
480 parent = getSpatialParent(ifcElement)
481 if checkIfcElementType(parent, 'IfcBuildingStorey'):
482 return parent
483 elif checkIfcElementType(parent, 'IfcSpace'):
484 return getStorey(parent)
485 else:
486 # hierachically nested building elements
487 parent = getHierarchicalParent(ifcElement)
488 if checkIfcElementType(parent, 'IfcBuildingStorey'):
489 return parent
490 else:
491 return getStorey(parent)
494def getBuilding(ifcElement):
495 """Find the building for this element."""
496 if ifcElement:
497 # direct spatial child elements outside of storey
498 parent = getSpatialParent(ifcElement)
499 if checkIfcElementType(parent, 'IfcBuilding'):
500 return parent
501 else:
502 storey = getStorey(ifcElement)
503 if storey:
504 # part of building storey
505 parent = getHierarchicalParent(storey)
506 else:
507 # hierarchical child elements outside of storey
508 parent = getHierarchicalParent(ifcElement)
509 # ... and finally check and return or start recursion
510 if checkIfcElementType(parent, 'IfcBuilding'):
511 return parent
512 else:
513 return getBuilding(parent)
516def getSite(ifcElement):
517 """Find the building site for this element."""
518 if ifcElement:
519 # direct spatial child elements outside of building
520 parent = getSpatialParent(ifcElement)
521 if checkIfcElementType(parent, 'IfcSite'):
522 return parent
523 else:
524 building = getBuilding(ifcElement)
525 if building:
526 # part of building
527 parent = getHierarchicalParent(building)
528 else:
529 # hierarchical child elements outside of building
530 parent = getHierarchicalParent(ifcElement)
531 # ... and finally check and return or start recursion
532 if checkIfcElementType(parent, 'IfcSite'):
533 return parent
534 else:
535 return getSite(parent)
538def getProject(ifcElement):
539 """Find the project/root for this element."""
540 if ifcElement:
541 # the project might be the hierarchical parent of the site ...
542 site = getSite(ifcElement)
543 parent = getHierarchicalParent(site)
544 if checkIfcElementType(parent, 'IfcProject'):
545 return parent
546 else:
547 # ... or of the building (no site defined) ...
548 building = getBuilding(ifcElement)
549 parent = getHierarchicalParent(site)
550 if checkIfcElementType(parent, 'IfcProject'):
551 return parent
552 # ... or the parent of an IfcSpatialZone, which is non-hierarchical.
555def get_true_north(ifcElement: entity_instance):
556 """Find the true north in degree of this element, 0 °C means positive
557 Y-axis. 45 °C Degree means middle between X- and Y-Axis"""
558 project = getProject(ifcElement)
559 try:
560 true_north = project.RepresentationContexts[0].TrueNorth.DirectionRatios
561 except AttributeError as er:
562 logger.warning(f"Not able to calculate true north of IFC element "
563 f"{ifcElement} due to error: {er}")
564 true_north = [0, 1]
565 angle_true_north = math.degrees(math.atan(true_north[0] / true_north[1]))
566 return angle_true_north
569def get_ports(element: entity_instance) -> list[Any]:
570 """Get all ports for new and old IFC definition of ports.
572 Args:
573 element: ifcopenshell element to check for ports
574 Returns:
575 ports: list of all ports connected to the element
576 """
577 ports = []
578 # new IfcStandard with IfcRelNests
579 ports_nested = list(getattr(element, 'IsNestedBy', []))
580 # old IFC standard with IfcRelConnectsPortToElement
581 ports_connects = list(getattr(element, 'HasPorts', []))
583 for nested in ports_nested:
584 for port_connection in nested.RelatedObjects:
585 ports.append(port_connection)
587 for connected in ports_connects:
588 ports.append(connected.RelatingPort)
590 return ports
593def get_ports_connections(element_port: entity_instance) -> list[Any]:
594 """Get all connected ports to a given port.
596 Args:
597 element_port: ifcopenshell port element to check for connections
598 Returns:
599 connected_ports: list of all ports connected to given element_port
600 """
601 connected_ports = \
602 [conn.RelatingPort for conn in element_port.ConnectedFrom] + \
603 [conn.RelatedPort for conn in element_port.ConnectedTo]
604 return connected_ports
607def get_ports_parent(element: entity_instance) -> list[Any]:
608 """Get the parent of given port for new and old Ifc definitions of ports.
610 Args:
611 element: ifcopenshell port element which parents are searched
612 Returns:
613 parents: list of ifcopenshell elements that are parent of the port
614 """
615 parents = []
616 parent_nested = list(getattr(element, 'Nests', []))
617 for nest in parent_nested:
618 parents.append(nest.RelatingObject)
619 return parents
622def convertToSI(ifcUnit, value):
623 # TODO not used anywhere. Remove?
624 """Return the value in basic SI units, conversion according to ifcUnit."""
625 # IfcSIPrefix values
626 ifcSIPrefix = {
627 'EXA': 1e18,
628 'PETA': 1e15,
629 'TERA': 1e12,
630 'GIGA': 1e9,
631 'MEGA': 1e6,
632 'KILO': 1e3,
633 'HECTO': 1e2,
634 'DECA': 1e1,
635 'DECI': 1e-1,
636 'CENTI': 1e-2,
637 'MILLI': 1e-3,
638 'MICRO': 1e-6,
639 'NANO': 1e-9,
640 'PICO': 1e-12,
641 'FEMTO': 1e-15,
642 'ATTO': 1e-18
643 }
645 if checkIfcElementType(ifcUnit, 'IfcSIUnit'):
646 # just check the prefix to normalize the values
647 prefix = ifcUnit.Prefix
648 if prefix:
649 return value * ifcSIPrefix[prefix]
650 else:
651 return value
652 elif checkIfcElementType(ifcUnit, 'IfcConversionBasedUnit'):
653 factor = ifcUnit.ConversionFactor.ValueComponent.wrappedValue
654 return value * factor
657def summary(ifcelement):
658 txt = "** Summary **\n"
659 for k, v in ifcelement.get_info().items():
660 txt += "%s: %s\n"%(k, v)
662 # "HasAssignments", "ContainedInStructure", "IsTypedBy", "HasAssociations"
663 relations = ("HasPorts", "IsDefinedBy", )
664 for rel in relations:
665 attr = getattr(ifcelement, rel)
666 if attr:
667 txt += "%s:\n"%(rel)
668 for item in attr:
669 if item.is_a() == 'IfcRelDefinesByProperties':
670 pset = item.RelatingPropertyDefinition
671 txt += " - %s\n"%(pset.Name)
672 for prop in pset.HasProperties:
673 txt += " - %s\n"%(prop)
674 else:
675 txt += " - %s\n"%(item)
677 #for attrname in dir(ifcelement):
678 # if attrname.startswith('__'):
679 # continue
680 # value = getattr(ifcelement, attrname)
681 # if not value:
682 # continue
683 # txt += "\n%s:%s"%(attrname, value)
685 return txt
688def used_properties(ifc_file):
689 """Filters given IFC for property_sets.
691 Returns a dictionary with related ifc types as keys and lists of used
692 propertysets as values"""
693 props = ifc_file.by_type("IFCPROPERTYSET")
694 tuples = []
695 for prop in props:
696 for occ in prop.DefinesOccurrence:
697 for ro in occ.RelatedObjects:
698 tuples.append((ro.is_a(), prop.Name))
699 tuples = set(tuples)
700 types = set(tup[0] for tup in tuples)
701 type_dict = {typ:[] for typ in types}
702 for tup in tuples:
703 type_dict[tup[0]].append(tup[1])
704 return type_dict
707class BoundedValue(float):
708 def __new__(cls, min_val, max_val, initial_val, unit=None):
709 instance = super().__new__(cls, initial_val)
710 instance.min_val = min_val
711 instance.max_val = max_val
712 instance.unit = unit
713 return instance
715 def __init__(self, min_val, max_val, initial_val, unit=None):
716 self.unit = unit
717 if self.unit:
718 self.min_val = min_val * unit
719 self.max_val = max_val * unit
720 else:
721 self.min_val = min_val
722 self.max_val = max_val
724 def __set__(self, new_value):
725 if isinstance(new_value, pint.Quantity):
726 if self.unit:
727 new_value = new_value.to(self.unit).magnitude
728 else:
729 new_value = new_value.magnitude
731 if self.min_val <= new_value <= self.max_val:
732 return BoundedValue(self.min_val, self.max_val, new_value, self.unit)
733 else:
734 print(f"Value should be between {self.min_val} and {self.max_val}")
735 return BoundedValue(self.min_val, self.max_val, self, self.unit)
737 def __iter__(self):
738 yield self.min_val
739 yield self.max_val
741 def __str__(self):
742 if self.unit:
743 return f"{self.__float__()} {self.unit}"
744 else:
745 return str(self.__float__())