Coverage for bim2sim / tasks / checks / check_ifc.py: 0%

501 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-12 10:59 +0000

1"""Check ifc input file mainly based on IDS files.""" 

2 

3import inspect # used for _get_ifc_type_classes 

4import types # used for _get_class_property_sets 

5import os 

6import warnings 

7 

8from pathlib import Path 

9 

10from typing import Callable, Dict # Dict used for _get_class_property_sets 

11 

12import ifcopenshell as ifcos 

13from bim2sim.utilities.common_functions import all_subclasses # used in _get_ifc_type_classes 

14from bim2sim.elements.mapping import attribute # used in _get_ifc_type_classes 

15# get_layer_ifc needed for _check_inst_materials 

16from bim2sim.elements.mapping.ifc2python import get_layers_ifc, \ 

17 get_property_sets, get_ports 

18 

19import ifctester.ids 

20import ifctester.reporter 

21import webbrowser 

22 

23 

24from mako.lookup import TemplateLookup 

25from mako.template import Template 

26 

27from bim2sim.elements import bps_elements as bps, hvac_elements as hvac 

28from bim2sim.tasks.base import ITask, Playground 

29 

30from bim2sim.kernel.ifc_file import IfcFileClass 

31from bim2sim.utilities.types import IFCDomain 

32from bim2sim import __file__ as bs_file 

33from bim2sim.tasks.common.load_ifc import extract_ifc_file_names 

34 

35 

36class CheckIfc(ITask): 

37 """Check ifc files for their quality regarding simulation.""" 

38 

39 reads = ('ifc_files',) 

40 

41 def __init__(self, playground: Playground): 

42 """Initialize CheckIFC.""" 

43 super().__init__(playground) 

44 self.error_summary_sub_inst: dict = {} 

45 self.error_summary_inst: dict = {} 

46 self.error_summary_prop: dict = {} 

47 self.version_error: bool = False 

48 self.ifc_version: str = None 

49 self.all_guids_unique: bool = True 

50 self.double_guids: dict = {} 

51 self.all_guids_filled: bool = True 

52 self.empty_guids: dict = {} 

53 self.sub_inst: list = [] 

54 self.id_list: list = [] 

55 self.elements: list = [] 

56 self.ps_summary: dict = {} 

57 self.ifc_units: dict = {} 

58 self.sub_inst_cls = None 

59 self.plugin = None 

60 

61 def run(self, ifc_files: [IfcFileClass]): 

62 """ 

63 Analyzes sub_elements and elements of an IFC file. 

64 

65 Therefore validation functions check ifc files and export the errors 

66 found as .html files. 

67 

68 It creates following reports: 

69 error_summary: overview of all errors 

70 error_summary_inst: summary of errors related to elements 

71 error_summary_prop: summary of missing properties 

72 error_summary_guid: summary of GUID errors 

73 ifc_ids_check: results of checks based on IDS file 

74 These html files are stored in the log folder of the project folder. 

75 

76 Args: 

77 ifc_files: bim2sim IfcFileClass holding the ifcopenshell ifc 

78 instance 

79 """ 

80 self.logger.info("Processing IFC Checks with ifcTester") 

81 

82 base_path = self.paths.ifc_base 

83 

84 ifc_files_paths = extract_ifc_file_names(base_path) 

85 self.logger.info(f"Found {len(ifc_files_paths)} IFC files in project " 

86 f"directory.") 

87 

88 log_path = self.paths.log 

89 # ids check call start 

90 if self.playground.sim_settings.ids_file_path is None: 

91 self.logger.critical("Default ids file is used, pls set " + 

92 "project.sim_settings.ids_file_path!") 

93 self.playground.sim_settings.ids_file_path = ( 

94 Path(bs_file).parent / 

95 'plugins/PluginIFCCheck/bim2sim_ifccheck/ifc_bps.ids' 

96 ) 

97 

98 ids_file_path = self.playground.sim_settings.ids_file_path 

99 for ifc_file_path in ifc_files_paths: 

100 all_spec_pass = self.run_ids_check_on_ifc( 

101 ifc_file_path, ids_file_path, 

102 report_html=True, log_path=log_path) 

103 

104 if all_spec_pass: 

105 self.logger.info( 

106 "all checks of the specifications of this IDS pass: " + 

107 "{}".format(all_spec_pass)) 

108 else: 

109 self.logger.warning( 

110 "all checks of the specifications of this IDS pass: " + 

111 "{}".format(all_spec_pass)) 

112 # ids check call end 

113 

114 self.logger.info("Processing IFC Checks without ifcTester") 

115 

116 paths = self.paths 

117 for ifc_file in ifc_files: 

118 # checks are domain specific 

119 # Reset class based on domain to run the right check. 

120 # Not pretty but works. This might be refactored in #170 

121 

122 # check uniqueness of GUIDs 

123 self.all_guids_unique, self.double_guids = ( 

124 CheckLogicBase.run_check_guid_unique(ifc_file) 

125 ) 

126 list_guids_non_unique = list(self.double_guids.keys()) 

127 self.logger.info("the GUIDs of all elements are unique: " + 

128 "{}".format(self.all_guids_unique)) 

129 if self.all_guids_unique is False: 

130 self.logger.critical("non-unique GUIDs: " + 

131 "{}".format(list_guids_non_unique)) 

132 # check emptiness of GUID fields 

133 self.all_guids_filled, self.empty_guids = ( 

134 CheckLogicBase.run_check_guid_empty(ifc_file) 

135 ) 

136 list_guids_empty = list(self.empty_guids.keys()) 

137 self.logger.info("the GUIDs of all elements are filled " + 

138 "(NOT empty): {}".format(self.all_guids_filled)) 

139 if self.all_guids_filled is False: 

140 self.logger.critical("empty GUIDs: {}".format(list_guids_empty)) 

141 # check ifc version 

142 self.version_error, self.ifc_version = ( 

143 CheckLogicBase.run_check_ifc_version(ifc_file) 

144 ) 

145 # for doc string 

146 # Logs: 

147 # critical: if loaded IFC is not IFC4 

148 if self.version_error: 

149 self.logger.critical("ifc Version is not fitting. " + 

150 "Should be IFC4, but here: " + 

151 self.ifc_version) 

152 

153 if ifc_file.domain == IFCDomain.hydraulic: 

154 self.logger.info("Processing HVAC-IfcCheck") 

155 # used for data preparing, it is a filter keyword 

156 self.sub_inst_cls = 'IfcDistributionPort' 

157 self.plugin = hvac 

158 self.ps_summary = self._get_class_property_sets(self.plugin) 

159 self.sub_inst = ifc_file.file.by_type(self.sub_inst_cls) 

160 self.elements = self.get_relevant_elements(ifc_file.file) 

161 self.ifc_units = ifc_file.ifc_units 

162 # checking itself 

163 check_logic_hvac = CheckLogicHVAC( 

164 self.sub_inst, self.elements, self.ps_summary, 

165 self.ifc_units) 

166 self.error_summary_sub_inst = check_logic_hvac.check_inst_sub() 

167 self.error_summary_inst = check_logic_hvac.check_elements() 

168 

169 elif ifc_file.domain == IFCDomain.arch: 

170 self.logger.info("Processing BPS-IfcCheck") 

171 # used for data preparing, it is a filter keyword 

172 self.sub_inst_cls = 'IfcRelSpaceBoundary' 

173 self.plugin = bps 

174 self.ps_summary = self._get_class_property_sets(self.plugin) 

175 self.sub_inst = ifc_file.file.by_type(self.sub_inst_cls) 

176 self.elements = self.get_relevant_elements(ifc_file.file) 

177 self.ifc_units = ifc_file.ifc_units 

178 # checking itself 

179 check_logic_bps = CheckLogicBPS( 

180 self.sub_inst, self.elements, self.ps_summary, 

181 self.ifc_units) 

182 self.error_summary_sub_inst = check_logic_bps.check_inst_sub() 

183 self.error_summary_inst = check_logic_bps.check_elements() 

184 self.error_summary_prop = check_logic_bps.error_summary_prop 

185 self.paths = paths 

186 elif ifc_file.domain == IFCDomain.unknown: 

187 self.logger.info(f"No domain specified for ifc file " 

188 f"{ifc_file.ifc_file_name}, not processing " 

189 f"any checks") 

190 return 

191 else: 

192 self.logger.info( 

193 f"For the Domain {ifc_file.domain} no specific checks are" 

194 f" implemented currently. Just running the basic checks." 

195 f"") 

196 

197 # generating reports (of the additional checks) 

198 base_name = f"/{ifc_file.domain.name.upper()}_" \ 

199 f"{ifc_file.ifc_file_name[:-4]}" 

200 self._write_errors_to_html_table(base_name, ifc_file.domain) 

201 

202 def get_relevant_elements(self, ifc: ifcos.file): 

203 """Get all relevant ifc elements. 

204 

205 This function based on the plugin's classes that 

206 represent an IFCProduct. 

207 

208 Args: 

209 ifc: IFC file translated with ifcopenshell 

210 

211 Returns: 

212 ifc_elements: list of IFC instance (Products) 

213 """ 

214 relevant_ifc_types = list(self.ps_summary.keys()) 

215 ifc_elements = [] 

216 for ifc_type in relevant_ifc_types: 

217 ifc_elements.extend(ifc.by_type(ifc_type)) 

218 return ifc_elements 

219 

220 @staticmethod 

221 def _get_ifc_type_classes(plugin: types.ModuleType): 

222 """Get all the classes of a plugin that represent an IFCProduct. 

223 

224 Furthermore, organize them on a dictionary for each ifc_type. 

225 Args: 

226 plugin: plugin used in the check tasks (bps or hvac) 

227 

228 Returns: 

229 cls_summary: dictionary containing all the ifc_types on the 

230 plugin with the corresponding class 

231 """ 

232 plugin_classes = [plugin_class[1] for plugin_class in 

233 inspect.getmembers(plugin, inspect.isclass) if 

234 inspect.getmro(plugin_class[1])[1].__name__.endswith( 

235 'Product')] 

236 cls_summary = {} 

237 

238 for plugin_class in plugin_classes: 

239 # class itself 

240 if plugin_class.ifc_types: 

241 for ifc_type in plugin_class.ifc_types.keys(): 

242 cls_summary[ifc_type] = plugin_class 

243 # sub classes 

244 for subclass in all_subclasses(plugin_class): 

245 for ifc_type in subclass.ifc_types.keys(): 

246 cls_summary[ifc_type] = subclass 

247 return cls_summary 

248 

249 @classmethod 

250 def _get_class_property_sets(cls, plugin: types.ModuleType) -> Dict: 

251 """Get all property sets and properties. 

252 

253 Which are required for bim2sim for all classes of a plugin, that 

254 represent an IFCProduct, and organize them on a dictionary for each 

255 ifc_type Args: plugin: plugin used in the check tasks (bps or hvac) 

256 

257 Returns: 

258 ps_summary: dictionary containing all the ifc_types on the 

259 plugin with the corresponding property sets 

260 

261 """ 

262 ps_summary = {} 

263 cls_summary = cls._get_ifc_type_classes(plugin) 

264 for ifc_type, plugin_class in cls_summary.items(): 

265 attributes = inspect.getmembers( 

266 plugin_class, lambda a: isinstance(a, attribute.Attribute)) 

267 ps_summary[ifc_type] = {} 

268 for attr in attributes: 

269 if attr[1].default_ps: 

270 ps_summary[ifc_type][attr[0]] = attr[1].default_ps 

271 return ps_summary 

272 

273 def validate_sub_inst(self, sub_inst: list) -> list: 

274 """Raise NotImplemented Error.""" 

275 raise NotImplementedError 

276 

277 @staticmethod 

278 def run_ids_check_on_ifc(ifc_file: str, ids_file: str, 

279 report_html: bool = False, 

280 log_path: str = None) -> bool: 

281 """Run check on IFC file based on IDS. 

282 

283 print the check of specifications pass(true) or fail(false) 

284 and the name of the specification 

285 and if all specifications of one IDS pass 

286 

287 Args: 

288 ifc_file: path of the IFC file, which is checked 

289 ids_file: path of the IDS file, which includes the specifications 

290 log_path: path of the log folder as part of the project structure 

291 report_html: generate, save and open the report about checking 

292 default = False 

293 Returns: 

294 all_spec_pass: boolean 

295 (true: all specification passed, 

296 false: one or more specification not passed) 

297 """ 

298 model = ifcos.open(ifc_file) 

299 my_ids = ifctester.ids.open(ids_file) 

300 my_ids.validate(model) 

301 all_spec_pass = True 

302 for spec in my_ids.specifications: 

303 if not spec.status: 

304 all_spec_pass = False 

305 

306 # generate html report 

307 if report_html: 

308 engine = ifctester.reporter.Html(my_ids) 

309 engine.report() 

310 output_file = Path(log_path / 'ifc_ids_check.html') 

311 engine.to_file(output_file) 

312 # can comment out, if not the browser should show the report 

313 # webbrowser.open(f"file://{output_file}") 

314 

315 return all_spec_pass 

316 

317 def get_html_templates(self): 

318 """Get all stored html templates. 

319 

320 Which will be used to export the errors summaries. 

321 

322 Returns: 

323 templates: dictionary containing all error html templates 

324 """ 

325 templates = {} 

326 path_templates = os.path.join( 

327 self.paths.assets, "templates", "check_ifc") 

328 lookup = TemplateLookup(directories=[path_templates]) 

329 templates["inst_template"] = Template( 

330 filename=os.path.join(path_templates, "inst_template"), 

331 lookup=lookup) 

332 templates["prop_template"] = Template( 

333 filename=os.path.join(path_templates, "prop_template"), 

334 lookup=lookup) 

335 templates["summary_template"] = Template( 

336 filename=os.path.join(path_templates, "summary_template_extend"), 

337 lookup=lookup) 

338 templates["guid_template"] = Template( 

339 filename=os.path.join(path_templates, "guid_template"), 

340 lookup=lookup) 

341 return templates 

342 

343 @staticmethod 

344 def _categorize_errors(error_dict: dict): 

345 """Categorizes the resulting errors in a dictionary. 

346 

347 This dictionary contains two groups: 

348 'per_error' where the key is the error name and the value is the 

349 number of errors with this name 

350 'per type' where the key is the ifc_type and the values are the 

351 each element with its respective errors 

352 Args: 

353 error_dict: dictionary containing all errors without categorization 

354 

355 Returns: 

356 categorized_dict: dictionary containing all errors categorized 

357 """ 

358 categorized_dict = {'per_error': {}, 'per_type': {}} 

359 for instance, errors in error_dict.items(): 

360 if ' ' in instance: 

361 guid, ifc_type = instance.split(' ') 

362 else: 

363 guid = '-' 

364 ifc_type = instance 

365 if ifc_type not in categorized_dict['per_type']: 

366 categorized_dict['per_type'][ifc_type] = {} 

367 categorized_dict['per_type'][ifc_type][guid] = errors 

368 for error in errors: 

369 error_com = error.split(' - ') 

370 if error_com[0] not in categorized_dict['per_error']: 

371 categorized_dict['per_error'][error_com[0]] = 0 

372 categorized_dict['per_error'][error_com[0]] += 1 

373 return categorized_dict 

374 

375 def _write_errors_to_html_table(self, base_name: str, domain: IFCDomain): 

376 """Write all errors in the html templates in a summarized way. 

377 

378 Args: 

379 base_name: str of file base name for reports 

380 domain: IFCDomain of the checked IFC 

381 """ 

382 show_report = False # enable the automatic popup of the reports 

383 templates = self.get_html_templates() 

384 summary_inst = self._categorize_errors(self.error_summary_inst) 

385 summary_sbs = self._categorize_errors(self.error_summary_sub_inst) 

386 summary_props = self._categorize_errors(self.error_summary_prop) 

387 all_errors = {**summary_inst['per_type'], **summary_sbs['per_type']} 

388 

389 with open(str(self.paths.log) + 

390 base_name + 

391 '_error_summary_inst.html', 'w+') as \ 

392 out_file: 

393 out_file.write(templates["inst_template"].render_unicode( 

394 task=self, 

395 summary_inst=summary_inst, 

396 summary_sbs=summary_sbs, 

397 all_errors=all_errors)) 

398 out_file.close() 

399 # opens automatically browser tab showing the generated html report 

400 if show_report: 

401 webbrowser.open(f"file://{out_file.buffer.name}") 

402 with open(str(self.paths.log) + 

403 base_name + 

404 '_error_summary_prop.html', 'w+') as \ 

405 out_file: 

406 out_file.write(templates["prop_template"].render_unicode( 

407 task=self, 

408 summary_props=summary_props)) 

409 out_file.close() 

410 # opens automatically browser tab showing the generated html report 

411 if show_report: 

412 webbrowser.open(f"file://{out_file.buffer.name}") 

413 with open(str(self.paths.log) + 

414 base_name + 

415 '_error_summary.html', 'w+') as out_file: 

416 out_file.write(templates["summary_template"].render_unicode( 

417 ifc_version=self.ifc_version, 

418 version_error=self.version_error, 

419 all_guids_unique=self.all_guids_unique, 

420 double_guids=self.double_guids, 

421 all_guids_filled=self.all_guids_filled, 

422 empty_guids=self.empty_guids, 

423 task=self, 

424 plugin_name=domain.name.upper(), 

425 base_name=base_name[1:], 

426 summary_inst=summary_inst, 

427 summary_sbs=summary_sbs, 

428 summary_props=summary_props)) 

429 out_file.close() 

430 # opens automatically browser tab showing the generated html report 

431 if show_report: 

432 webbrowser.open(f"file://{out_file.buffer.name}") 

433 

434 with open(str(self.paths.log) + base_name + '_error_summary_guid.html', 

435 'w+') as \ 

436 out_file: 

437 out_file.write(templates["guid_template"].render_unicode( 

438 task=self, 

439 double_guids=self.double_guids, 

440 empty_guids=self.empty_guids, 

441 summary_inst=summary_inst, 

442 summary_sbs=summary_sbs, 

443 all_errors=all_errors)) 

444 out_file.close() 

445 # opens automatically browser tab showing the generated html report 

446 if show_report: 

447 webbrowser.open(f"file://{out_file.buffer.name}") 

448 

449 

450class CheckLogicBase(): 

451 """Provides logic for ifc files checking regarding simulation. 

452 

453 This is a base class. This base class includes all check logic, which is 

454 useful for all checking use cases. 

455 

456 Attributes: 

457 extract_data (list): filtered/extract data from ifc file 

458 """ 

459 

460 def __init__(self, extract_data, elements, ps_summary, ifc_units): 

461 """Initialize class.""" 

462 self.space_ndicator = True 

463 # filtered data, which will be processed 

464 self.extract_data = extract_data 

465 self.elements = elements 

466 self.ps_summary = ps_summary 

467 self.ifc_units = ifc_units 

468 self.error_summary_prop: dict = {} 

469 

470 @staticmethod 

471 def run_check_guid_unique(ifc_file) -> (bool, dict): 

472 """Check the uniqueness of the guids of the IFC file. 

473 

474 Here the bijective uniqueness is check, but also 

475 the uniqueness of modified guids by transforming 

476 the lowercase letters into uppercase letter 

477 Args: 

478 ifc_file: path of the IFC file, which is checked 

479 

480 Returns: 

481 all_guids_unique: boolean 

482 (true: all guids are unique 

483 false: one or more guids are not unique) 

484 

485 double_guid: dict 

486 

487 """ 

488 # dict of all elements with guids used in the checked ifc model 

489 used_guids: dict[str, ifcos.entity_instance] = dict() 

490 # dict of elements with guids, which are not unique 

491 double_guids: dict[str, ifcos.entity_instance] = dict() 

492 all_guids_unique = True 

493 used_guids_upper = [] # to store temporally guid in uppercase letters 

494 for inst in ifc_file.file: 

495 if hasattr(inst, "GlobalId"): 

496 guid = inst.GlobalId 

497 # print(guid) 

498 upper_guid = guid.upper() 

499 # print(upper_guid) 

500 if guid in used_guids: 

501 double_guids[guid] = inst 

502 all_guids_unique = False 

503 warnings.warn( 

504 "Some GUIDs are not unique! A bijective ifc file have " 

505 "to have unique GUIDs. But bim2sim provides an option " 

506 "in sim_settings: rest_guids = True" 

507 ) 

508 elif (guid.upper() in used_guids_upper): 

509 double_guids[guid] = inst 

510 all_guids_unique = False 

511 warnings.warn( 

512 "Some GUIDs are not unique (for transformed GUIDS " 

513 "letters low-case into uppercase)! " 

514 "A bijective ifc file have " 

515 "to have unique GUIDs. But bim2sim provides an option " 

516 "in sim_settings: rest_guids = True" 

517 ) 

518 else: 

519 used_guids[guid] = inst 

520 # store temporally guid in uppercase letters 

521 used_guids_upper.append(upper_guid) 

522 return (all_guids_unique, double_guids) 

523 

524 @staticmethod 

525 def run_check_guid_empty(ifc_file) -> (bool, dict): 

526 """Check it there is/are guid/s, which is/are empty in the IFC file. 

527 

528 Args: 

529 ifc_file: path of the IFC file, which is checked 

530 

531 Returns: 

532 all_guids_filled: boolean 

533 (true: all guids has a value (not empty) 

534 false: one or more guids has not value (empty)) 

535 

536 empty_guid: dict 

537 """ 

538 # dict of all elements with guids used in the checked ifc model 

539 used_guids: dict[str, ifcos.entity_instance] = dict() 

540 # dict of elements with guids, which are empty 

541 empty_guids: dict[str, ifcos.entity_instance] = dict() 

542 all_guids_filled = True 

543 # count the number of guids without value (empty), this number is used 

544 # to make unique identifier 

545 guid_empty_no = 0 

546 for inst in ifc_file.file: 

547 if hasattr(inst, "GlobalId"): 

548 guid = inst.GlobalId 

549 name = inst.Name 

550 if guid == '': 

551 all_guids_filled = False 

552 guid_empty_no = guid_empty_no + 1 

553 name_dict = name + '--' + str(guid_empty_no) 

554 empty_guids[name_dict] = inst 

555 else: 

556 used_guids[guid] = inst 

557 return (all_guids_filled, empty_guids) 

558 

559 @staticmethod 

560 def run_check_ifc_version(ifc: ifcos.file) -> (bool, str): 

561 """Check the IFC version. 

562 

563 Only IFC4 files are valid for bim2sim. 

564 

565 Attention: no Error is raised anymore. 

566 

567 Args: 

568 ifc: ifc file loaded with IfcOpenShell 

569 Returns: 

570 version_error: True if version NOT fit 

571 ifc_version: version of the ifc file 

572 """ 

573 schema = ifc.schema 

574 if "IFC4" not in schema: 

575 version_error = True 

576 else: 

577 version_error = False 

578 return (version_error, schema) 

579 

580 def check_inst_sub(self): 

581 """Check sub instances for errors. 

582 

583 Based on functions in validate_sub_inst via check_inst 

584 """ 

585 error_summary_sub_inst = self.check_inst( 

586 self.validate_sub_inst, self.extract_data) 

587 return error_summary_sub_inst 

588 

589 def check_elements(self): 

590 """Check elements for errors. 

591 

592 Based on functions in validate_sub_inst via check_inst 

593 """ 

594 error_summary = self.check_inst( 

595 self.validate_elements, self.elements) 

596 return error_summary 

597 

598 @staticmethod 

599 def check_inst(validation_function: Callable, elements: list): 

600 """Check each element lists. 

601 

602 Use sb_validation/ports/elements functions to check elements 

603 adds error to dictionary if object has errors. Combines the 

604 (error) return of the specific validation function with the 

605 key (mostly the GlobalID). 

606 

607 Args: 

608 validation_function: function that compiles all the validations 

609 to be performed on the object 

610 (sb/port/instance) 

611 elements: list containing all objects to be evaluates 

612 

613 Returns: 

614 summary: summarized dictionary of errors, where the key is the 

615 GUID + the ifc_type 

616 

617 """ 

618 summary = {} 

619 for inst in elements: 

620 error = validation_function(inst) 

621 if len(error) > 0: 

622 if hasattr(inst, 'GlobalId'): 

623 key = inst.GlobalId + ' ' + inst.is_a() 

624 else: 

625 key = inst.is_a() 

626 summary.update({key: error}) 

627 return summary 

628 

629 @staticmethod 

630 def apply_validation_function(fct: bool, err_name: str, error: list): 

631 """Apply a validation to an instance, space boundary or port. 

632 

633 Function to apply a validation to an instance, space boundary or 

634 port, it stores the error to the list of errors. 

635 

636 Args: 

637 fct: validation function to be applied 

638 err_name: string that define the error 

639 error: list of errors 

640 

641 """ 

642 if not fct: 

643 error.append(err_name) 

644 

645 @staticmethod 

646 def _check_rel_space(bound: ifcos.entity_instance): 

647 """Check the existence of related space. 

648 

649 Args: 

650 bound: Space boundary IFC instance 

651 

652 Returns: 

653 True: if check succeeds 

654 False: if check fails 

655 """ 

656 return any( 

657 [bound.RelatingSpace.is_a('IfcSpace') or 

658 bound.RelatingSpace.is_a('IfcExternalSpatialElement')]) 

659 

660 def validate_sub_inst(self, sub_inst: list) -> list: 

661 """Raise NotImplemented Error.""" 

662 raise NotImplementedError 

663 

664 

665class CheckLogicBPS(CheckLogicBase): 

666 """Provides additional logic for ifc files checking regarding BPS.""" 

667 

668 @staticmethod 

669 def _check_rel_space(bound: ifcos.entity_instance): 

670 """Check existence of related space. 

671 

672 And has the correctness of class type. 

673 

674 Args: 

675 bound: Space boundary IFC instance 

676 

677 Returns: 

678 True: if check succeeds 

679 False: if check fails 

680 """ 

681 return any( 

682 [bound.RelatingSpace.is_a('IfcSpace') or 

683 bound.RelatingSpace.is_a('IfcExternalSpatialElement')]) 

684 

685 @staticmethod 

686 def _check_rel_building_elem(bound: ifcos.entity_instance): 

687 """Check existence of related building element. 

688 

689 And the correctness of class type. 

690 

691 Args: 

692 bound: Space boundary IFC instance 

693 

694 Returns: 

695 True: if check succeeds 

696 False: if check fails 

697 """ 

698 if bound.RelatedBuildingElement is not None: 

699 return bound.RelatedBuildingElement.is_a('IfcElement') 

700 

701 @staticmethod 

702 def _check_conn_geom(bound: ifcos.entity_instance): 

703 """Check that the space boundary has a connection geometry. 

704 

705 And the correctness of class type. 

706 

707 Args: 

708 bound: Space boundary IFC instance 

709 

710 Returns: 

711 True: if check succeeds 

712 False: if check fails 

713 """ 

714 if bound.ConnectionGeometry is not None: 

715 return bound.ConnectionGeometry.is_a('IfcConnectionGeometry') 

716 

717 @staticmethod 

718 def _check_on_relating_elem(bound: ifcos.entity_instance): 

719 """Check geometric information. 

720 

721 Check that the surface on relating element of a space boundary has the 

722 geometric information and the correctness of class type. 

723 

724 Args: 

725 bound: Space boundary IFC instance 

726 

727 Returns: 

728 True: if check succeeds 

729 False: if check fails 

730 

731 """ 

732 if bound.ConnectionGeometry is not None: 

733 return bound.ConnectionGeometry.SurfaceOnRelatingElement.is_a( 

734 'IfcCurveBoundedPlane') 

735 

736 @staticmethod 

737 def _check_on_related_elem(bound: ifcos.entity_instance): 

738 """Check absence of geometric information. 

739 

740 Check that the surface on related element of a space boundary has no 

741 geometric information and the correctness of class type. 

742 

743 Args: 

744 bound: Space boundary IFC instance 

745 

746 Returns: 

747 True: if check succeeds 

748 False: if check fails 

749 """ 

750 if bound.ConnectionGeometry is not None: 

751 return (bound.ConnectionGeometry.SurfaceOnRelatedElement is None or 

752 bound.ConnectionGeometry.SurfaceOnRelatedElement.is_a( 

753 'IfcCurveBoundedPlane')) 

754 

755 @staticmethod 

756 def _check_basis_surface(bound: ifcos.entity_instance): 

757 """Check representation by an IFC Place. 

758 

759 Check that the surface on relating element of a space boundary is 

760 represented by an IFC Place and the correctness of class type. 

761 

762 Args: 

763 bound: Space boundary IFC instance 

764 

765 Returns: 

766 True: if check succeeds 

767 False: if check fails 

768 """ 

769 if bound.ConnectionGeometry is not None: 

770 return bound.ConnectionGeometry.SurfaceOnRelatingElement. \ 

771 BasisSurface.is_a('IfcPlane') 

772 

773 @staticmethod 

774 def _check_inner_boundaries(bound: ifcos.entity_instance): 

775 """Check absence of the surface and their structure. 

776 

777 Check if the surface on relating element of a space boundary inner 

778 boundaries don't exists or are composite curves. 

779 Args: 

780 bound: Space boundary IFC instance 

781 

782 Returns: 

783 True: if check succeeds 

784 False: if check fails 

785 """ 

786 if bound.ConnectionGeometry is not None: 

787 return (bound.ConnectionGeometry.SurfaceOnRelatingElement. 

788 InnerBoundaries is None) or \ 

789 (i.is_a('IfcCompositeCurve') for i in bound. 

790 ConnectionGeometry.SurfaceOnRelatingElement. 

791 InnerBoundaries) 

792 

793 @staticmethod 

794 def _check_outer_boundary_composite(bound: ifcos.entity_instance): 

795 """Check if the surface are composite curves. 

796 

797 Check if the surface on relating element of a space boundary outer 

798 boundaries are composite curves. 

799 

800 Args: 

801 bound: Space boundary IFC instance 

802 

803 Returns: 

804 True: if check succeeds 

805 False: if check fails 

806 """ 

807 return bound.ConnectionGeometry.SurfaceOnRelatingElement. \ 

808 OuterBoundary.is_a('IfcCompositeCurve') 

809 

810 @staticmethod 

811 def _check_segments(bound: ifcos.entity_instance): 

812 """Check if the surface are poly-line. 

813 

814 Check if the surface on relating element of a space boundary outer 

815 boundaries segments are poly-line. 

816 

817 Args: 

818 bound: Space boundary IFC instance 

819 

820 Returns: 

821 True: if check succeeds 

822 False: if check fails 

823 """ 

824 return (s.is_a('IfcCompositeCurveSegment') for s in 

825 bound.ConnectionGeometry.SurfaceOnRelatingElement. 

826 OuterBoundary.Segments) 

827 

828 @staticmethod 

829 def _check_coords(points: ifcos.entity_instance): 

830 """Check coordinates of a group of points (class and length). 

831 

832 Args: 

833 points: Points IFC instance 

834 

835 Returns: 

836 True: if check succeeds 

837 False: if check fails 

838 """ 

839 return points.is_a('IfcCartesianPoint') and 1 <= len( 

840 points.Coordinates) <= 4 

841 

842 @staticmethod 

843 def _check_dir_ratios(dir_ratios: ifcos.entity_instance): 

844 """Check length of direction ratios. 

845 

846 Args: 

847 dir_ratios: direction ratios IFC instance 

848 

849 Returns: 

850 True: if check succeeds 

851 False: if check fails 

852 """ 

853 return 2 <= len(dir_ratios.DirectionRatios) <= 3 

854 

855 @classmethod 

856 def _check_poly_points_coord(cls, polyline: ifcos.entity_instance): 

857 """Check if a poly-line has the correct coordinates. 

858 

859 Args: 

860 polyline: Poly-line IFC instance 

861 

862 Returns: 

863 True: if check succeeds 

864 False: if check fails 

865 """ 

866 return all(cls._check_coords(p) for p in polyline.Points) 

867 

868 @classmethod 

869 def _check_segments_poly_coord(cls, bound: ifcos.entity_instance): 

870 """Check segments coordinates. 

871 

872 Check segments coordinates of an outer boundary of a surface on 

873 relating element. 

874 

875 Args: 

876 bound: Space boundary IFC instance 

877 

878 Returns: 

879 True: if check succeeds 

880 False: if check fails 

881 """ 

882 return all(cls._check_poly_points_coord(s.ParentCurve) 

883 for s in 

884 bound.ConnectionGeometry.SurfaceOnRelatingElement. 

885 OuterBoundary.Segments) 

886 

887 @staticmethod 

888 def _check_poly_points(polyline: ifcos.entity_instance): 

889 """Check if a polyline has the correct class. 

890 

891 Args: 

892 polyline: Polyline IFC instance 

893 

894 Returns: 

895 True: if check succeeds 

896 False: if check fails 

897 """ 

898 return polyline.is_a('IfcPolyline') 

899 

900 @classmethod 

901 def _check_outer_boundary_poly(cls, bound: ifcos.entity_instance): 

902 """Check points of outer boundary of a surface on relating element. 

903 

904 Args: 

905 bound: Space boundary IFC instance 

906 

907 Returns: 

908 True: if check succeeds 

909 False: if check fails 

910 """ 

911 return cls._check_poly_points( 

912 bound.ConnectionGeometry.SurfaceOnRelatingElement.OuterBoundary) 

913 

914 @staticmethod 

915 def _check_outer_boundary_poly_coord(bound: ifcos.entity_instance): 

916 """Check outer boundary of a surface on relating element. 

917 

918 Args: 

919 bound: Space boundary IFC instance 

920 

921 Returns: 

922 True: if check succeeds 

923 False: if check fails 

924 """ 

925 return all( 

926 bound.ConnectionGeometry.SurfaceOnRelatingElement.OuterBoundary) 

927 

928 @staticmethod 

929 def _check_plane_position(bound: ifcos.entity_instance): 

930 """ 

931 Check class of plane position of space boundary. 

932 

933 Args: 

934 bound: Space boundary IFC instance 

935 

936 Returns: 

937 True: if check succeeds 

938 False: if check fails 

939 """ 

940 return bound.ConnectionGeometry.SurfaceOnRelatingElement.BasisSurface. \ 

941 Position.is_a('IfcAxis2Placement3D') 

942 

943 @staticmethod 

944 def _check_location(bound: ifcos.entity_instance): 

945 """ 

946 Check that location of a space boundary is an IfcCartesianPoint. 

947 

948 Args: 

949 bound: Space boundary IFC instance 

950 

951 Returns: 

952 True: if check succeeds 

953 False: if check fails 

954 """ 

955 return bound.ConnectionGeometry.SurfaceOnRelatingElement.BasisSurface. \ 

956 Position.Location.is_a('IfcCartesianPoint') 

957 

958 @staticmethod 

959 def _check_axis(bound: ifcos.entity_instance): 

960 """Check that axis of space boundary is an IfcDirection. 

961 

962 Args: 

963 bound: Space boundary IFC instance 

964 

965 Returns: 

966 True: if check succeeds 

967 False: if check fails 

968 """ 

969 return bound.ConnectionGeometry.SurfaceOnRelatingElement.BasisSurface. \ 

970 Position.Axis.is_a('IfcDirection') 

971 

972 @staticmethod 

973 def _check_refdirection(bound: ifcos.entity_instance): 

974 """Check that reference direction of space boundary is an IfcDirection. 

975 

976 Args: 

977 bound: Space boundary IFC instance 

978 

979 Returns: 

980 True: if check succeeds 

981 False: if check fails 

982 """ 

983 return bound.ConnectionGeometry.SurfaceOnRelatingElement.BasisSurface. \ 

984 Position.RefDirection.is_a('IfcDirection') 

985 

986 @classmethod 

987 def _check_location_coord(cls, bound: ifcos.entity_instance): 

988 """Check the correctness of related coordinates. 

989 

990 Check if space boundary surface on relating element coordinates are 

991 correct. 

992 

993 Args: 

994 bound: Space boundary IFC instance 

995 

996 Returns: 

997 True: if check succeeds 

998 False: if check fails 

999 

1000 """ 

1001 return cls._check_coords(bound.ConnectionGeometry. 

1002 SurfaceOnRelatingElement.BasisSurface. 

1003 Position.Location) 

1004 

1005 @classmethod 

1006 def _check_axis_dir_ratios(cls, bound: ifcos.entity_instance): 

1007 """Check correctness of space boundary surface. 

1008 

1009 Check if space boundary surface on relating element axis are correct. 

1010 

1011 Args: 

1012 bound: Space boundary IFC instance 

1013 

1014 Returns: 

1015 True: if check succeeds 

1016 False: if check fails 

1017 """ 

1018 return cls._check_dir_ratios( 

1019 bound.ConnectionGeometry.SurfaceOnRelatingElement.BasisSurface. 

1020 Position.Axis) 

1021 

1022 @classmethod 

1023 def _check_refdirection_dir_ratios(cls, bound: ifcos.entity_instance): 

1024 """Check correctness of space boundary surface. 

1025 

1026 Check if space boundary surface on relating element reference direction 

1027 are correct. 

1028 

1029 Args: 

1030 bound: Space boundary IFC instance 

1031 

1032 Returns: 

1033 True: if check succeeds 

1034 False: if check fails 

1035 """ 

1036 return cls._check_dir_ratios( 

1037 bound.ConnectionGeometry.SurfaceOnRelatingElement.BasisSurface. 

1038 Position.RefDirection) 

1039 

1040 @staticmethod 

1041 def _check_inst_sb(inst: ifcos.entity_instance): 

1042 """Check association of an instance. 

1043 

1044 Check that an instance has associated space boundaries (space or 

1045 building element). 

1046 

1047 Args: 

1048 inst: IFC instance 

1049 

1050 Returns: 

1051 True: if check succeeds 

1052 False: if check fails 

1053 """ 

1054 blacklist = ['IfcBuilding', 'IfcSite', 'IfcBuildingStorey', 

1055 'IfcMaterial', 'IfcMaterialLayer', 'IfcMaterialLayerSet'] 

1056 if inst.is_a() in blacklist: 

1057 return True 

1058 elif inst.is_a('IfcSpace') or inst.is_a('IfcExternalSpatialElement'): 

1059 return len(inst.BoundedBy) > 0 

1060 else: 

1061 if len(inst.ProvidesBoundaries) > 0: 

1062 return True 

1063 decompose = [] 

1064 if hasattr(inst, 'Decomposes') and len(inst.Decomposes): 

1065 decompose = [decomp.RelatingObject for decomp in 

1066 inst.Decomposes] 

1067 elif hasattr(inst, 'IsDecomposedBy') and len(inst.IsDecomposedBy): 

1068 decompose = [] 

1069 for decomp in inst.IsDecomposedBy: 

1070 for inst_ifc in decomp.RelatedObjects: 

1071 decompose.append(inst_ifc) 

1072 for inst_decomp in decompose: 

1073 if len(inst_decomp.ProvidesBoundaries): 

1074 return True 

1075 return False 

1076 

1077 @staticmethod 

1078 def _check_inst_materials(inst: ifcos.entity_instance): 

1079 """Check that an instance has associated materials. 

1080 

1081 Args: 

1082 inst: IFC instance 

1083 

1084 Returns: 

1085 True: if check succeeds 

1086 False: if check fails 

1087 """ 

1088 blacklist = [ 

1089 'IfcBuilding', 'IfcSite', 'IfcBuildingStorey', 'IfcSpace', 

1090 'IfcExternalSpatialElement'] 

1091 if not (inst.is_a() in blacklist): 

1092 return len(get_layers_ifc(inst)) > 0 

1093 return True 

1094 

1095 def _check_inst_properties(self, inst: ifcos.entity_instance): 

1096 """Check existence of necessary property sets and properties. 

1097 

1098 Check that an instance has the property sets and properties 

1099 necessaries to the plugin. 

1100 Args: 

1101 inst: IFC instance 

1102 

1103 Returns: 

1104 True: if check succeeds 

1105 False: if check fails 

1106 """ 

1107 inst_prop2check = self.ps_summary.get(inst.is_a(), {}) 

1108 inst_prop = get_property_sets(inst, self.ifc_units) 

1109 inst_prop_errors = [] 

1110 for prop2check, ps2check in inst_prop2check.items(): 

1111 ps = inst_prop.get(ps2check[0], None) 

1112 if ps: 

1113 if not ps.get(ps2check[1], None): 

1114 inst_prop_errors.append( 

1115 prop2check+' - '+', '.join(ps2check)) 

1116 else: 

1117 inst_prop_errors.append(prop2check+' - '+', '.join(ps2check)) 

1118 if inst_prop_errors: 

1119 key = inst.GlobalId + ' ' + inst.is_a() 

1120 self.error_summary_prop.update({key: inst_prop_errors}) 

1121 return False 

1122 return True 

1123 

1124 @staticmethod 

1125 def _check_inst_contained_in_structure(inst: ifcos.entity_instance): 

1126 """Check that an instance is contained in an structure. 

1127 

1128 Args: 

1129 inst: IFC instance 

1130 

1131 Returns: 

1132 True: if check succeeds 

1133 False: if check fails 

1134 """ 

1135 blacklist = [ 

1136 'IfcBuilding', 'IfcSite', 'IfcBuildingStorey', 'IfcSpace', 

1137 'IfcExternalSpatialElement', 'IfcMaterial', 'IfcMaterialLayer', 

1138 'IfcMaterialLayerSet' 

1139 ] 

1140 if not (inst.is_a() in blacklist): 

1141 return len(inst.ContainedInStructure) > 0 

1142 if hasattr(inst, 'Decomposes'): 

1143 return len(inst.Decomposes) > 0 

1144 else: 

1145 return True 

1146 

1147 @staticmethod 

1148 def _check_inst_representation(inst: ifcos.entity_instance): 

1149 """ 

1150 Check that an instance has a correct geometric representation. 

1151 

1152 Args: 

1153 inst: IFC instance 

1154 

1155 Returns: 

1156 True: if check succeeds 

1157 False: if check fails 

1158 """ 

1159 blacklist = [ 

1160 'IfcBuilding', 'IfcBuildingStorey', 'IfcMaterial', 

1161 'IfcMaterialLayer', 'IfcMaterialLayerSet' 

1162 ] 

1163 if not (inst.is_a() in blacklist): 

1164 return inst.Representation is not None 

1165 return True 

1166 

1167 def validate_sub_inst(self, bound: ifcos.entity_instance) -> list: 

1168 """Validate space boundary. 

1169 

1170 Validation function for a space boundary that compiles all validation 

1171 functions. 

1172 

1173 Args: 

1174 bound: ifc space boundary entity 

1175 

1176 Returns: 

1177 error: list of errors found in the ifc space boundaries 

1178 """ 

1179 error = [] 

1180 # print(bound) 

1181 self.apply_validation_function(self._check_rel_space(bound), 

1182 'RelatingSpace - ' 

1183 'The space boundary does not have a ' 

1184 'relating space associated', error) 

1185 self.apply_validation_function(self._check_rel_building_elem(bound), 

1186 'RelatedBuildingElement - ' 

1187 'The space boundary does not have a ' 

1188 'related building element associated', 

1189 error) 

1190 self.apply_validation_function(self._check_conn_geom(bound), 

1191 'ConnectionGeometry - ' 

1192 'The space boundary does not have a ' 

1193 'connection geometry', error) 

1194 self.apply_validation_function(self._check_on_relating_elem(bound), 

1195 'SurfaceOnRelatingElement - ' 

1196 'The space boundary does not have a ' 

1197 'surface on the relating element', 

1198 error) 

1199 self.apply_validation_function(self._check_on_related_elem(bound), 

1200 'SurfaceOnRelatedElement - ' 

1201 'The space boundary does not have a ' 

1202 'surface on the related element', error) 

1203 self.apply_validation_function(self._check_basis_surface(bound), 

1204 'BasisSurface - ' 

1205 'The space boundary surface on ' 

1206 'relating element geometry is missing', 

1207 error) 

1208 self.apply_validation_function(self._check_inner_boundaries(bound), 

1209 'InnerBoundaries - ' 

1210 'The space boundary surface on ' 

1211 'relating element inner boundaries are ' 

1212 'missing', error) 

1213 if bound.ConnectionGeometry is not None: 

1214 if hasattr( 

1215 bound.ConnectionGeometry.SurfaceOnRelatingElement. 

1216 OuterBoundary, 'Segments'): 

1217 self.apply_validation_function( 

1218 self._check_outer_boundary_composite(bound), 

1219 'OuterBoundary - ' 

1220 'The space boundary surface on relating element outer ' 

1221 'boundary is missing', error) 

1222 self.apply_validation_function(self._check_segments(bound), 

1223 'OuterBoundary Segments - ' 

1224 'The space boundary surface on ' 

1225 'relating element outer ' 

1226 'boundary ' 

1227 'geometry is missing', error) 

1228 self.apply_validation_function( 

1229 self._check_segments_poly_coord(bound), 

1230 'OuterBoundary Coordinates - ' 

1231 'The space boundary surface on relating element outer ' 

1232 'boundary coordinates are missing', error) 

1233 else: 

1234 self.apply_validation_function( 

1235 self._check_outer_boundary_poly(bound), 

1236 'OuterBoundary - ' 

1237 'The space boundary surface on relating element outer ' 

1238 'boundary is missing', error) 

1239 self.apply_validation_function( 

1240 self._check_outer_boundary_poly_coord(bound), 

1241 'OuterBoundary Coordinates - ' 

1242 'The space boundary surface on relating element outer ' 

1243 'boundary coordinates are missing', error) 

1244 self.apply_validation_function(self._check_plane_position(bound), 

1245 'Position - ' 

1246 'The space boundary surface on' 

1247 'relating ' 

1248 'element plane position is missing', 

1249 error) 

1250 self.apply_validation_function(self._check_location(bound), 

1251 'Location - ' 

1252 'The space boundary surface on ' 

1253 'relating element location is ' 

1254 'missing', error) 

1255 self.apply_validation_function(self._check_axis(bound), 

1256 'Axis - ' 

1257 'The space boundary surface on' 

1258 'relating ' 

1259 'element axis are missing', 

1260 error) 

1261 self.apply_validation_function(self._check_refdirection(bound), 

1262 'RefDirection - ' 

1263 'The space boundary surface on ' 

1264 'relating ' 

1265 'element reference direction is ' 

1266 'missing', error) 

1267 self.apply_validation_function(self._check_location_coord(bound), 

1268 'LocationCoordinates - ' 

1269 'The space boundary surface on' 

1270 'relating ' 

1271 'element location coordinates are ' 

1272 'missing', error) 

1273 self.apply_validation_function(self._check_axis_dir_ratios(bound), 

1274 'AxisDirectionRatios - ' 

1275 'The space boundary surface on ' 

1276 'relating ' 

1277 'element axis direction ratios are ' 

1278 'missing', error) 

1279 self.apply_validation_function( 

1280 self._check_refdirection_dir_ratios(bound), 

1281 'RefDirectionDirectionRatios - ' 

1282 'The space boundary surface on relating element position ' 

1283 'reference direction is missing', error) 

1284 return error 

1285 

1286 def validate_elements(self, inst: ifcos.entity_instance) -> list: 

1287 """Validate elements. 

1288 

1289 Validation function for an instance that compiles all instance 

1290 validation functions. 

1291 Args: 

1292 inst:IFC instance being checked 

1293 

1294 Returns: 

1295 error: list of elements error 

1296 

1297 """ 

1298 error = [] 

1299 self.apply_validation_function(self._check_inst_sb(inst), 

1300 'SpaceBoundaries - ' 

1301 'The instance space boundaries are ' 

1302 'missing', error) 

1303 self.apply_validation_function(self._check_inst_materials(inst), 

1304 'MaterialLayers - ' 

1305 'The instance materials are missing', 

1306 error) 

1307 self.apply_validation_function(self._check_inst_properties(inst), 

1308 'Missing Property_Sets - ' 

1309 'One or more instance\'s necessary ' 

1310 'property sets are missing', error) 

1311 self.apply_validation_function( 

1312 self._check_inst_contained_in_structure(inst), 

1313 'ContainedInStructure - ' 

1314 'The instance is not contained in any ' 

1315 'structure', error) 

1316 self.apply_validation_function(self._check_inst_representation(inst), 

1317 'Representation - ' 

1318 'The instance has no geometric ' 

1319 'representation', error) 

1320 return error 

1321 

1322 

1323class CheckLogicHVAC(CheckLogicBase): 

1324 """Provides additional logic for ifc files checking regarding HVAC.""" 

1325 

1326 @staticmethod 

1327 def _check_assignments(inst: ifcos.entity_instance) -> bool: 

1328 """Check that the inst (also spec. port) has at least one assignment. 

1329 

1330 Args: 

1331 port: port ifc entity 

1332 

1333 Returns: 

1334 True: if check succeeds 

1335 False: if check fails 

1336 """ 

1337 return any(assign.is_a('IfcRelAssignsToGroup') for assign in 

1338 inst.HasAssignments) 

1339 

1340 @staticmethod 

1341 def _check_connection(port: ifcos.entity_instance) -> bool: 

1342 """Check that the port is: "connected_to" or "connected_from". 

1343 

1344 Args: 

1345 port: port ifc entity 

1346 

1347 Returns: 

1348 True: if check succeeds 

1349 False: if check fails 

1350 """ 

1351 return len(port.ConnectedTo) > 0 or len(port.ConnectedFrom) > 0 

1352 

1353 @staticmethod 

1354 def _check_contained_in(port: ifcos.entity_instance) -> bool: 

1355 """ 

1356 Check that the port is "contained_in". 

1357 

1358 Args: 

1359 port: port ifc entity 

1360 

1361 Returns: 

1362 True: if check succeeds 

1363 False: if check fails 

1364 """ 

1365 return len(port.ContainedIn) > 0 

1366 

1367 # elements check 

1368 @staticmethod 

1369 def _check_inst_ports(inst: ifcos.entity_instance) -> bool: 

1370 """Check that an instance has associated ports. 

1371 

1372 Args: 

1373 inst: IFC instance 

1374 

1375 Returns: 

1376 True: if check succeeds 

1377 False: if check fails 

1378 """ 

1379 ports = get_ports(inst) 

1380 if ports: 

1381 return True 

1382 else: 

1383 return False 

1384 

1385 @staticmethod 

1386 def _check_contained_in_structure(inst: ifcos.entity_instance) -> bool: 

1387 """Check that an instance is contained in an structure. 

1388 

1389 Args: 

1390 inst: IFC instance 

1391 

1392 Returns: 

1393 True: if check succeeds 

1394 False: if check fails 

1395 """ 

1396 if hasattr(inst, 'ContainedInStructure'): 

1397 return len(inst.ContainedInStructure) > 0 

1398 else: 

1399 return False 

1400 

1401 def _check_inst_properties(self, inst: ifcos.entity_instance): 

1402 """Check necessaries property sets and properties. 

1403 

1404 Check that an instance has the property sets and properties 

1405 necessaries to the plugin. 

1406 Args: 

1407 inst: IFC instance 

1408 

1409 Returns: 

1410 True: if check succeeds 

1411 False: if check fails 

1412 """ 

1413 inst_prop2check = self.ps_summary.get(inst.is_a(), {}) 

1414 inst_prop = get_property_sets(inst, self.ifc_units) 

1415 inst_prop_errors = [] 

1416 for prop2check, ps2check in inst_prop2check.items(): 

1417 ps = inst_prop.get(ps2check[0], None) 

1418 if ps: 

1419 if not ps.get(ps2check[1], None): 

1420 inst_prop_errors.append( 

1421 prop2check+' - '+', '.join(ps2check)) 

1422 else: 

1423 inst_prop_errors.append(prop2check+' - '+', '.join(ps2check)) 

1424 if inst_prop_errors: 

1425 key = inst.GlobalId + ' ' + inst.is_a() 

1426 self.error_summary_prop.update({key: inst_prop_errors}) 

1427 return False 

1428 return True 

1429 

1430 @staticmethod 

1431 def _check_inst_representation(inst: ifcos.entity_instance): 

1432 """Check that an instance has a correct geometric representation. 

1433 

1434 Args: 

1435 inst: IFC instance 

1436 

1437 Returns: 

1438 True: if check succeeds 

1439 False: if check fails 

1440 """ 

1441 if hasattr(inst, 'Representation'): 

1442 return inst.Representation is not None 

1443 else: 

1444 return False 

1445 

1446 def validate_sub_inst(self, port: ifcos.entity_instance) -> list: 

1447 """Run validation functions for a port. 

1448 

1449 Args: 

1450 port: IFC port entity 

1451 

1452 Returns: 

1453 error: list of errors found in the IFC port 

1454 

1455 """ 

1456 error = [] 

1457 self.apply_validation_function(self._check_assignments(port), 

1458 'Assignments - ' 

1459 'The port assignments are missing', 

1460 error) 

1461 self.apply_validation_function(self._check_connection(port), 

1462 'Connections - ' 

1463 'The port has no connections', error) 

1464 self.apply_validation_function(self._check_contained_in(port), 

1465 'ContainedIn - ' 

1466 'The port is not contained in', error) 

1467 return error 

1468 

1469 def validate_elements(self, inst: ifcos.entity_instance) -> list: 

1470 """Validate elements (carrier function). 

1471 

1472 Validation function for an instance that compiles all instance 

1473 validation functions. 

1474 

1475 Args: 

1476 inst: IFC instance being checked 

1477 

1478 Returns: 

1479 error: list of elements error 

1480 

1481 """ 

1482 error = [] 

1483 

1484 self.apply_validation_function(self._check_inst_ports(inst), 

1485 'Ports - ' 

1486 'The instance ports are missing', error) 

1487 self.apply_validation_function( 

1488 self._check_contained_in_structure(inst), 

1489 'ContainedInStructure - ' 

1490 'The instance is not contained in any ' 

1491 'structure', error) 

1492 self.apply_validation_function(self._check_inst_properties(inst), 

1493 'Missing Property_Sets - ' 

1494 'One or more instance\'s necessary ' 

1495 'property sets are missing', error) 

1496 self.apply_validation_function(self._check_inst_representation(inst), 

1497 'Representation - ' 

1498 'The instance has no geometric ' 

1499 'representation', error) 

1500 self.apply_validation_function(self._check_assignments(inst), 

1501 'Assignments - ' 

1502 'The instance assignments are missing', 

1503 error) 

1504 return error 

1505 

1506 

1507if __name__ == '__main__': 

1508 pass