forked from opensourceBIM/python-mvdxml
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmvd.py
More file actions
379 lines (290 loc) · 11.3 KB
/
mvd.py
File metadata and controls
379 lines (290 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
import ifcopenshell
import ifcopenshell.geom
import itertools
import os
import xlsxwriter
import csv
def is_applicability(concept):
"""
Check whether the Concept created has a filtering purpose.
Actually, MvdXML has a specific Applicability node.
:param concept: mvdXML Concept object
"""
return concept.name.startswith("AP")
def merge_dictionaries(dicts):
d = {}
for e in dicts:
d.update(e)
return d
def extract_data(mvd_node, ifc_data):
"""
Recursively traverses mvdXML Concept tree structure.
This tree is made of different mvdXML Rule nodes: AttributesRule
and EntityRule.
:param mvd_node: an mvdXML Concept
:param ifc_data: an IFC instance or an IFC value
"""
to_combine = []
return_value = []
if len(mvd_node.nodes) == 0:
return [{mvd_node: ifc_data}]
if mvd_node.tag == 'AttributeRule':
data_from_attribute = []
values_from_attribute = getattr(ifc_data, mvd_node.attribute)
if isinstance(values_from_attribute, (list, tuple)):
data_from_attribute.extend(values_from_attribute)
else:
data_from_attribute.append(values_from_attribute)
for child in mvd_node.nodes:
for data in data_from_attribute:
child_values = extract_data(child, data)
if isinstance(child_values, (list, tuple)):
return_value.extend(child_values)
else:
return_value.append(child_values)
return return_value
elif mvd_node.tag == 'EntityRule':
# Avoid things like Quantities on Psets
if len(mvd_node.nodes):
if isinstance(ifc_data, ifcopenshell.entity_instance) and not ifc_data.is_a(mvd_node.attribute):
return []
for child in mvd_node.nodes:
if child.tag == "Constraint":
on_node = child.attribute[0].c
on_node = on_node.replace("'", "")
if isinstance(ifc_data, ifcopenshell.entity_instance):
if str(ifc_data[0]) == on_node:
return [{mvd_node: ifc_data}]
elif ifc_data == on_node:
return [{mvd_node: ifc_data}]
else:
to_combine.append(extract_data(child, ifc_data))
if len(to_combine):
return_value = list(map(merge_dictionaries, itertools.product(*to_combine)))
return return_value
def open_mvd(filename):
"""
Open an mvdXML file.
:param filename: Path of the mvdXML file.
:return: mvdXML Concept instance.
"""
my_concept_object = list(ifcopenshell.mvd.concept_root.parse(filename))[0]
return my_concept_object
def format_data_from_nodes(recurse_output):
"""
Enable to format data collected such that the value to be exported is extracted.
:param recurse_output: Data extracted from the recursive function
"""
if len(recurse_output) > 1:
output = []
for resulting_dict in recurse_output:
intermediate_storing = []
for value in resulting_dict.values():
intermediate_storing.append(value)
output.extend(intermediate_storing)
return output
elif len(recurse_output) == 1:
return_list = []
intermediate_list = list(recurse_output[0].values())
if len(intermediate_list) > 1:
returned_value = intermediate_list
for element in intermediate_list:
# In case of a property that comes with all its path
# (like ['PSet_WallCommon, 'IsExternal', IfcBoolean(.F.)
# return only the list element which is not of string type
# todo: check above condition with ifcopenshell type
if not isinstance(element, str):
returned_value = element
if returned_value != intermediate_list:
return returned_value
else:
return intermediate_list
else:
return intermediate_list[0]
else:
return []
def get_data_from_mvd(entities, tree, filtering=False):
"""
Apply the recursive function on the entities to return
the values extracted.
:param entities: IFC instances to be processed.
:param tree: mvdXML Concept instance tree root.
:param filtering: Indicates whether the mvdXML tree is an applicability.
"""
filtered_entities = []
extracted_entities_data = {}
for entity in entities:
entity_id = entity.GlobalId
combinations = extract_data(tree, entity)
desired_results = []
for dictionary in combinations:
desired_results.append(dictionary)
output = format_data_from_nodes(desired_results)
if filtering:
if len(output):
extracted_entities_data[entity_id] = output
else:
extracted_entities_data[entity_id] = output
return extracted_entities_data
def correct_for_export(all_data):
"""
Process the data for spreadsheet export.
"""
for d in all_data:
for k, v in d.items():
if isinstance(v, list):
if len(v):
new_list = []
for data in v:
new_list.append(str(data))
d[k] = ','.join(new_list)
if len(v) == 0:
d[k] = 0
elif isinstance(v, ifcopenshell.entity_instance):
d[k] = v[0]
return all_data
def export_to_xlsx(xlsx_name, concepts, all_data):
"""
Export data towards XLSX spreadsheet format.
:param xlsx_name: Name of the outputted file.
:param concepts: List of mvdXML Concept instances.
:param all_data: Data extracted.
"""
workbook = xlsxwriter.Workbook("spreadsheet_output/" + xlsx_name)
worksheet = workbook.add_worksheet()
# Formats
bold_format = workbook.add_format()
bold_format.set_bold()
bold_format.set_center_across()
# Write first row
column_index = 0
for concept in concepts:
worksheet.write(0, column_index, concept.name, bold_format)
column_index += 1
col = 0
for feature in all_data:
row = 1
for d in feature.values():
worksheet.write(row, col, d)
row += 1
col += 1
workbook.close()
def export_to_csv(csv_name, concepts, all_data):
"""
Export data towards CSV spreadsheet format.
:param csv_name: Name of the file outputted file.
:param concepts: List of mvdXML Concept instances.
:param all_data: Data extracted.
"""
with open('spreadsheet_output/' + csv_name, 'w', newline='') as f:
writer = csv.writer(f)
header = [concept.name for concept in concepts]
first_row = writer.writerow(header)
values_by_row = []
for val in all_data:
values_by_row.append(list(val.values()))
entities_number = len(all_data[0].keys())
for i in range(0, entities_number):
row_to_write = []
for r in values_by_row:
row_to_write.append(r[i])
f = writer.writerow(row_to_write)
def get_data(mvd_concept, ifc_file, spreadsheet_export=True):
"""
Use the majority of all the other functions to return the data
queried by the mvdXML file in python format.
:param mvd_concept: mvdXML Concept instance.
:param ifc_file: IFC file from any schema.
:param spreadsheet_export: The spreadsheet export is carried out when set to True.
"""
# Check if IFC entities have been filtered at least once
filtered = 0
entities = ifc_file.by_type(mvd_concept.name)
selected_entities = entities
verification_matrix = {}
for entity in selected_entities:
verification = dict()
verification_matrix[entity.GlobalId] = verification
# For each Concept(ConceptTemplate) in the ConceptRoot
concepts = sorted(mvd_concept.concepts(), key=is_applicability, reverse=True)
all_data = []
counter = 0
for concept in concepts:
if is_applicability(concept):
filtering = True
else:
filtering = False
# Access all the Rules of the ConceptTemplate
rules_root = concept.template().rules[0]
extracted_data = get_data_from_mvd(selected_entities, rules_root, filtering=filtering)
all_data.append(extracted_data)
if filtering:
filtered = 1
new_entities = []
for entity_id in all_data[counter].keys():
if len(all_data[counter][entity_id]) != 0:
entity = ifc_file.by_id(entity_id)
new_entities.append(entity)
selected_entities = new_entities
not_respecting_entities = [item for item in entities if item not in selected_entities]
for entity in entities:
val = 0
if entity in not_respecting_entities:
val = 1
verification_matrix[entity.GlobalId].update({concept.name:val})
counter += 1
all_data = correct_for_export(all_data)
if spreadsheet_export:
if filtered != 0:
export_name = "output_filtered"
else:
export_name = "output_non_filtered"
export_to_xlsx(export_name + '.xlsx', concepts, all_data)
export_to_csv(export_name + '.csv', concepts, all_data)
if filtered:
filtered_entities = []
for k,v in verification_matrix.items():
# print("K", k)
ent = ifc_file.by_id(k)
# print(ent)
# print("V ",v)
sum_result = 0
if sum(v.values()) > 0:
filtered_entities.append(ent)
# print(filtered_entities)
return (all_data, verification_matrix)
def get_non_respecting_entities(file, verification_matrix):
non_respecting = []
for k,v in verification_matrix.items():
entity = file.by_id(k)
print(list(v.values()))
if sum(v.values()) != 0:
non_respecting.append(entity)
print(non_respecting)
return non_respecting
def visualize(file, not_respecting_entities):
"""
Visualize the instances of the entity type targeted by the mvdXML ConceptRoot.
At display, a color differentiation is made between the entities which comply with
mvdXML requirements and the ones which don't.
:param file: IFC file from any schema.
:param not_respecting_entities: Entities which don't comply with mvdXML requirements.
"""
s = ifcopenshell.geom.main.settings()
s.set(s.USE_PYTHON_OPENCASCADE, True)
s.set(s.DISABLE_OPENING_SUBTRACTIONS, False)
viewer = ifcopenshell.geom.utils.initialize_display()
entity_type = not_respecting_entities[0].is_a()
set_to_display = set(not_respecting_entities)|set(file.by_type(entity_type))
for el in set_to_display:
if el in not_respecting_entities:
c = (1, 0.3, 0, 1)
else:
c = (0, 1, 0.5, 1)
shape = ifcopenshell.geom.create_shape(s, el)
# OCC.BRepTools.breptools_Write(shape.geometry, "test.brep")
ds = ifcopenshell.geom.utils.display_shape(shape, clr=c)
viewer.FitAll()
ifcopenshell.geom.utils.main_loop()
if __name__ == '__main__':
print('functions to parse MVD rules and extract IFC data/filter IFC entities from them')