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

1"""Module to convert ifc data from to python data""" 

2from __future__ import annotations 

3 

4import logging 

5import math 

6import os 

7from collections.abc import Iterable 

8from typing import Optional, Union, TYPE_CHECKING, Any 

9 

10import ifcopenshell 

11from ifcopenshell import entity_instance, file, open as ifc_open, guid 

12from pathlib import Path 

13import pandas as pd 

14 

15from bim2sim.elements.mapping.units import parse_ifc 

16 

17if TYPE_CHECKING: 

18 from bim2sim.elements.base_elements import ProductBased 

19 

20logger = logging.getLogger(__name__) 

21 

22 

23def load_ifc(path: Path) -> file: 

24 """loads the ifc file using ifcopenshell and returns the ifcopenshell 

25 instance 

26 

27 Args: 

28 path: Path to ifc file location 

29 

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 

49 

50 

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 

56 

57 

58def property_set2dict(property_set: entity_instance, 

59 ifc_units: Optional[dict]) -> dict: 

60 """Converts IfcPropertySet and IfcQuantitySet to python dict 

61 

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. 

66 

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 """ 

74 

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') 

90 

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 

99 

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 # 

128 

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()) 

172 

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) 

184 

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 

219 

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 

230 

231 

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 

240 

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] 

269 

270 if isinstance(layer_list, Iterable): 

271 for layer in layer_list: 

272 layers_list.append(layer) 

273 return layers_list 

274 

275 

276def getElementByGUID(ifcfile, guid): 

277 element = ifcfile.by_guid(guid) 

278 return element 

279 

280 

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 

289 

290 

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. 

294 

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 

299 

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 

319 

320 

321def get_property_sets(element: entity_instance, ifc_units: dict) -> dict: 

322 """Returns all PropertySets of element 

323 

324 

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 

328 

329 Returns: 

330 dict of dicts for each PropertySet. Each dict with key property name 

331 and value its value 

332 

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) 

352 

353 return property_sets 

354 

355 

356def get_type_property_sets(element, ifc_units): 

357 """Returns all PropertySets of element's types 

358 

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) 

369 

370 return property_sets 

371 

372 

373def get_quantity_sets(element, ifc_units): 

374 """Returns all QuantitySets of element""" 

375 

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) 

383 

384 return quantity_sets 

385 

386 

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 

395 

396 

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 

407 

408 

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 

415 

416 

417def checkIfcElementType(ifcElement, ifcType): 

418 """Checks for matching IFC element types.""" 

419 if getIfcAttribute(ifcElement, 'is_a'): 

420 return ifcElement.is_a() == ifcType 

421 

422 

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 

432 

433 

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] 

444 

445 

446def getSpatialParent(ifcElement): 

447 """Fetch the first spatial parent element.""" 

448 parent = getIfcAttribute(ifcElement, 'ContainedInStructure') 

449 if parent: 

450 return parent[0].RelatingStructure 

451 

452 

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 

458 

459 

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) 

474 

475 

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) 

492 

493 

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) 

514 

515 

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) 

536 

537 

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. 

553 

554 

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 

567 

568 

569def get_ports(element: entity_instance) -> list[Any]: 

570 """Get all ports for new and old IFC definition of ports. 

571 

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', [])) 

582 

583 for nested in ports_nested: 

584 for port_connection in nested.RelatedObjects: 

585 ports.append(port_connection) 

586 

587 for connected in ports_connects: 

588 ports.append(connected.RelatingPort) 

589 

590 return ports 

591 

592 

593def get_ports_connections(element_port: entity_instance) -> list[Any]: 

594 """Get all connected ports to a given port. 

595 

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 

605 

606 

607def get_ports_parent(element: entity_instance) -> list[Any]: 

608 """Get the parent of given port for new and old Ifc definitions of ports. 

609 

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 

620 

621 

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 } 

644 

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 

655 

656 

657def summary(ifcelement): 

658 txt = "** Summary **\n" 

659 for k, v in ifcelement.get_info().items(): 

660 txt += "%s: %s\n"%(k, v) 

661 

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) 

676 

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) 

684 

685 return txt 

686 

687 

688def used_properties(ifc_file): 

689 """Filters given IFC for property_sets. 

690 

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 

705 

706 

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 

714 

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 

723 

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 

730 

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) 

736 

737 def __iter__(self): 

738 yield self.min_val 

739 yield self.max_val 

740 

741 def __str__(self): 

742 if self.unit: 

743 return f"{self.__float__()} {self.unit}" 

744 else: 

745 return str(self.__float__())