Skip to content

Commit 5965dc2

Browse files
authored
Merge pull request #2 from johltn/master
Initial commit
2 parents fdb36b7 + 255427a commit 5965dc2

File tree

2 files changed

+392
-0
lines changed

2 files changed

+392
-0
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
11
# python-mvdxml
22
A mvdXML checker and w3c SPARQL converter, as an IfcOpenShell submodule or stand-alone.
3+
4+
5+
#### Quickstart
6+
```python
7+
import ifcopenshell
8+
from ifcopenshell.mvd import mvd
9+
10+
mvd_concept = mvd.open_mvd(wall_extraction.mvdxml)
11+
file = ifcopenshell.open(your_ifc_file.ifc)
12+
13+
all_data = mvd.get_data(mvd_concept, file, spreadsheet_export=True)
14+
```
15+

mvd.py

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

0 commit comments

Comments
 (0)