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

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 

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. 

68 

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

76 

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

92 

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 

101 

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( 

110 

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 # 

132 

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

176 

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) 

188 

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 

224 

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 

235 

236 

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 

247 

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] 

276 

277 if isinstance(layer_list, Iterable): 

278 for layer in layer_list: 

279 layers_list.append(layer) 

280 return layers_list 

281 

282 

283def getElementByGUID(ifcfile, guid): 

284 element = ifcfile.by_guid(guid) 

285 return element 

286 

287 

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 

296 

297 

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. 

301 

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 

306 

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 

326 

327 

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

329 """Returns all PropertySets of element 

330 

331 

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 

335 

336 Returns: 

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

338 and value its value 

339 

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) 

359 

360 return property_sets 

361 

362 

363def get_type_property_sets(element, ifc_units): 

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

365 

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) 

378 

379 return property_sets 

380 

381 

382def get_quantity_sets(element, ifc_units): 

383 """Returns all QuantitySets of element""" 

384 

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) 

392 

393 return quantity_sets 

394 

395 

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 

404 

405 

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 

416 

417 

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 

424 

425 

426def checkIfcElementType(ifcElement, ifcType): 

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

428 if getIfcAttribute(ifcElement, 'is_a'): 

429 return ifcElement.is_a() == ifcType 

430 

431 

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 

441 

442 

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] 

453 

454 

455def getSpatialParent(ifcElement): 

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

457 parent = getIfcAttribute(ifcElement, 'ContainedInStructure') 

458 if parent: 

459 return parent[0].RelatingStructure 

460 

461 

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 

467 

468 

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) 

483 

484 

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) 

501 

502 

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) 

523 

524 

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) 

545 

546 

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. 

562 

563 

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 

577 

578 

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

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

581 

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

592 

593 for nested in ports_nested: 

594 for port_connection in nested.RelatedObjects: 

595 ports.append(port_connection) 

596 

597 for connected in ports_connects: 

598 ports.append(connected.RelatingPort) 

599 

600 return ports 

601 

602 

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

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

605 

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 

615 

616 

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

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

619 

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 

630 

631 

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 } 

654 

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 

665 

666 

667def summary(ifcelement): 

668 txt = "** Summary **\n" 

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

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

671 

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) 

686 

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) 

694 

695 return txt 

696 

697 

698def used_properties(ifc_file): 

699 """Filters given IFC for property_sets. 

700 

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 

715 

716 

717def check_guid(guid_to_check: str) -> bool: 

718 """Validates if a string is a valid IFC GUID. 

719 

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. 

723 

724 Args: 

725 guid_to_check: A string representing the IFC GUID to validate. 

726 

727 Returns: 

728 bool: True if the GUID is valid, False otherwise. 

729 

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

756 

757 if error: 

758 logging.error(f"GUID {guid_to_check} is invalid: {error}") 

759 return False 

760 return True 

761 

762 

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 

770 

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 

779 

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 

786 

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) 

793 

794 def __iter__(self): 

795 yield self.min_val 

796 yield self.max_val 

797 

798 def __str__(self): 

799 if self.unit: 

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

801 else: 

802 return str(self.__float__())