First Commit

This commit is contained in:
2026-05-31 10:17:09 +07:00
commit 17a9c69379
4547 changed files with 1170384 additions and 0 deletions
@@ -0,0 +1,489 @@
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:
if mvd_node.tag == "AttributeRule":
try:
values_from_attribute = getattr(ifc_data, mvd_node.attribute)
return [{mvd_node: values_from_attribute}]
except:
return [{mvd_node: "Invalid Attribute"}]
else:
return [{mvd_node: ifc_data}]
if mvd_node.tag == 'AttributeRule':
data_from_attribute = []
try:
values_from_attribute = getattr(ifc_data, mvd_node.attribute)
if values_from_attribute is None:
return [{mvd_node:"Nonexistent value"}]
except:
return [{mvd_node:"Invalid attribute rule"}]
if isinstance(values_from_attribute, (list, tuple)):
if len(values_from_attribute) == 0:
return [{mvd_node: 'empty data structure'}]
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):
ifc_type = type(ifc_data[0])
typed_node = (ifc_type)(on_node)
if ifc_data[0] == typed_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) or isinstance(v, tuple):
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.
"""
if not os.path.isdir("spreadsheet_output/"):
os.mkdir("spreadsheet_output/")
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.
"""
if not os.path.isdir("spreadsheet_output/"):
os.mkdir("spreadsheet_output/")
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.entity)
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
if len(concept.template().rules) > 1:
attribute_rules = []
for rule in concept.template().rules:
attribute_rules.append(rule)
rules_root = ifcopenshell.mvd.rule("EntityRule", mvd_concept.entity, attribute_rules)
else:
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)
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)
return non_respecting
def get_respecting_entities(file, verification_matrix):
respecting = []
for k, v in verification_matrix.items():
entity = file.by_id(k)
print(list(v.values()))
if sum(v.values()) == 0:
respecting.append(entity)
return 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()
other_entities = [x for x in file.by_type("IfcBuildingElement") if x.is_a() != str(entity_type)]
set_of_entities = set(not_respecting_entities) | set(file.by_type(entity_type))
set_to_display = set_of_entities.union(set(other_entities))
for el in set_to_display:
if el in not_respecting_entities:
c = (1, 0, 0, 1)
elif el in other_entities:
c = (1, 1, 1, 0)
else:
c = (0, 1, 0.5, 1)
try:
shape = ifcopenshell.geom.create_shape(s, el)
# OCC.BRepTools.breptools_Write(shape.geometry, "test.brep")
ds = ifcopenshell.geom.utils.display_shape(shape, clr=c)
except:
pass
viewer.FitAll()
ifcopenshell.geom.utils.main_loop()
def validate_data(concept, data):
import io
import ast
import operator
from functools import reduce, partial
rules = [x[0] for x in concept.rules() if not isinstance(x, str)]
def transform_data(d):
"""
Transform dictionary keys from tree nodes to rule ids
"""
return {(k.parent if k.bind is None and k.parent.bind is not None else k).bind: v for k, v in d.items()}
def parse_mvdxml_token(v):
# @todo make more permissive and tolerant
return ast.literal_eval(v)
data = list(map(transform_data, data))
output = io.StringIO()
# https://stackoverflow.com/a/70227259
def operation_reduce(x, y):
"""
Takes alternating value and function as input and
reduces while applying function
"""
if callable(x):
return x(y)
else:
return partial(y, x)
def apply_rules():
for r in rules:
def apply_data():
for d in data:
def translate(v):
if isinstance(v, str):
return getattr(operator, v.lower() + "_")
else:
if v.b == "Value":
return d.get(v.a) == parse_mvdxml_token(v.c)
elif v.b == "Type":
return d.get(v.a) and d.get(v.a).is_a(parse_mvdxml_token(v.c))
r2 = list(map(translate, r))
yield reduce(operation_reduce, r2)
v = any(list(apply_data()))
print(("Met:" if v else "Not met:"), r, file=output)
yield v
valid = all(list(apply_rules()))
return valid, output.getvalue()
if __name__ == '__main__':
print('functions to parse MVD rules and extract IFC data/filter IFC entities from them')