Files
IfcOpenShell/src/ifcblenderexport/blenderbim/bim/export_ifc.py
T

2920 lines
133 KiB
Python
Raw Normal View History

2019-08-29 23:11:12 +10:00
import bpy
import csv
import json
2019-09-03 20:51:58 +10:00
import time
2020-01-07 16:12:11 +11:00
import datetime
import os
import zipfile
import tempfile
from pathlib import Path
2019-11-06 17:37:51 +11:00
from mathutils import Vector, Matrix
2019-11-07 14:20:54 +11:00
from .helper import SIUnitHelper
from . import schema
2019-12-11 18:44:46 +11:00
import ifcopenshell
import addon_utils
2020-05-21 04:10:20 +02:00
from .ifc2json import IFC2JSON
2019-11-17 13:25:41 +01:00
class ArrayModifier:
count: int
offset: Vector
2019-08-29 23:11:12 +10:00
2019-11-17 13:25:41 +01:00
class IfcParser():
def __init__(self, ifc_export_settings, qto_calculator):
2019-10-05 21:15:53 +10:00
self.data_dir = ifc_export_settings.data_dir
self.qto_calculator = qto_calculator
self.ifc_export_settings = ifc_export_settings
2019-09-01 17:23:48 +10:00
self.selected_products = []
self.selected_types = []
2020-01-28 22:06:58 +11:00
self.selected_spatial_structure_elements = []
self.selected_groups = []
self.global_ids = []
2019-09-01 17:23:48 +10:00
self.product_index = 0
self.product_name_index_map = {}
2019-09-10 17:08:45 +10:00
self.units = {}
2019-11-15 15:44:04 +11:00
self.people = []
self.organisations = []
2019-09-08 19:02:00 +10:00
self.psets = {}
self.document_references = {}
self.classifications = []
self.classification_references = {}
self.constraints = {}
self.qtos = {}
self.aggregates = {}
2019-09-28 10:54:10 +10:00
self.materials = {}
self.spatial_structure_elements = []
self.spatial_structure_elements_tree = []
self.groups = []
self.rel_contained_in_spatial_structure = {}
self.rel_nests = {}
self.rel_space_boundaries = {}
2019-09-01 18:40:20 +10:00
self.rel_defines_by_type = {}
self.rel_defines_by_qto = {}
2019-09-08 19:02:00 +10:00
self.rel_defines_by_pset = {}
self.rel_associates_document_object = {}
self.rel_associates_document_type = {}
self.rel_associates_classification_object = {}
self.rel_associates_classification_type = {}
2019-09-09 15:11:07 +10:00
self.rel_associates_material = {}
2019-09-28 10:54:10 +10:00
self.rel_associates_material_layer_set = {}
self.rel_associates_material_constituent_set = {}
2020-01-14 16:11:11 +11:00
self.rel_associates_material_profile_set = {}
self.rel_associates_constraint_object = {}
self.rel_associates_constraint_type = {}
self.rel_aggregates = {}
2019-11-17 02:52:20 +01:00
self.rel_voids_elements = {}
self.rel_fills_elements = {}
2020-02-02 20:28:51 +11:00
self.rel_projects_elements = {}
self.rel_connects_structural_member = {}
self.rel_assigns_to_group = {}
self.presentation_layer_assignments = {}
self.representations = {}
2019-09-01 17:23:48 +10:00
self.type_products = []
2019-09-29 22:12:51 +10:00
self.door_attributes = {}
2019-09-29 22:29:06 +10:00
self.window_attributes = {}
self.project = {}
self.libraries = []
self.products = []
2020-04-09 02:42:03 +02:00
def parse(self, selected_objects):
self.projects = self.get_projects()
if not self.projects:
self.setup_project()
self.projects = self.get_projects()
self.project = self.projects[0]
2019-09-10 17:08:45 +10:00
self.units = self.get_units()
2019-11-07 16:40:57 +11:00
self.unit_scale = self.get_unit_scale()
2019-11-15 15:44:04 +11:00
self.people = self.get_people()
self.organisations = self.get_organisations()
selected_objects = self.add_spatial_elements_if_unselected(selected_objects)
self.add_type_elements_if_unselected(selected_objects)
self.categorise_selected_objects(selected_objects)
self.material_psets = self.get_material_psets()
self.document_information = self.get_document_information()
self.document_references = self.get_document_references()
self.classifications = self.get_classifications()
self.classification_reference_maps = self.get_classification_reference_maps()
self.classification_references = self.get_classification_references()
self.constraints = self.get_constraints()
self.load_representations()
self.load_presentation_layer_assignments()
2019-09-09 15:11:07 +10:00
self.materials = self.get_materials()
self.styled_items = self.get_styled_items()
self.spatial_structure_elements = self.get_spatial_structure_elements()
self.groups = self.get_groups()
self.libraries = self.get_libraries()
2019-09-29 22:12:51 +10:00
self.door_attributes = self.get_door_attributes()
2019-09-29 22:29:06 +10:00
self.window_attributes = self.get_window_attributes()
self.type_products = self.get_type_products()
self.get_products()
self.resolve_product_relationships()
self.map_conversion = self.get_map_conversion()
self.target_crs = self.get_target_crs()
self.library_information = self.get_library_information()
self.spatial_structure_elements_tree = []
for project in self.projects:
self.spatial_structure_elements_tree.extend(self.get_spatial_structure_elements_tree(project))
2019-09-10 17:08:45 +10:00
def get_units(self):
return {
'length': {
'ifc': None,
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
'raw': bpy.context.scene.unit_settings.length_unit
2019-11-17 13:25:41 +01:00
},
2019-09-10 17:08:45 +10:00
'area': {
'ifc': None,
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
'raw': bpy.context.scene.unit_settings.length_unit
2019-11-17 13:25:41 +01:00
},
2019-09-10 17:08:45 +10:00
'volume': {
'ifc': None,
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
'raw': bpy.context.scene.unit_settings.length_unit
}}
2019-11-07 16:40:57 +11:00
def get_unit_scale(self):
unit_settings = bpy.context.scene.unit_settings
conversions = {
'KILOMETERS': 1e3,
'CENTIMETERS': 1e-2,
'MILLIMETERS': 1e-3,
'MICROMETERS': 1e-6,
'FEET': 0.3048,
'INCHES': 0.0254}
if unit_settings.system in {'METRIC', 'IMPERIAL'}:
scale = unit_settings.scale_length
if unit_settings.length_unit in conversions.keys():
scale *= conversions[unit_settings.length_unit]
return scale
return 1
2019-11-17 13:25:41 +01:00
def get_object_attributes(self, obj):
attributes = {'Name': self.get_ifc_name(obj.name)}
global_id_index = obj.BIMObjectProperties.attributes.find('GlobalId')
if global_id_index == -1:
2019-11-17 13:25:41 +01:00
global_id = obj.BIMObjectProperties.attributes.add()
2019-10-16 23:17:48 +11:00
global_id.name = 'GlobalId'
global_id.string_value = ifcopenshell.guid.new()
elif obj.BIMObjectProperties.attributes[global_id_index].string_value in self.global_ids:
obj.BIMObjectProperties.attributes[global_id_index].string_value = ifcopenshell.guid.new()
2019-11-17 13:25:41 +01:00
attributes.update({a.name: a.string_value for a in obj.BIMObjectProperties.attributes})
self.global_ids.append(attributes['GlobalId'])
return attributes
def get_products(self):
for product in self.selected_products:
self.add_product(self.get_product(product))
self.resolve_modifiers(product)
2019-11-17 13:25:41 +01:00
def resolve_modifiers(self, product):
2019-11-17 13:25:41 +01:00
obj = product['raw']
2020-02-04 17:38:08 +11:00
if obj.data \
and hasattr(obj.data, 'BIMMeshProperties') \
and not obj.data.BIMMeshProperties.is_parametric:
return
instance_objects = [(obj, {
'location': obj.matrix_world.translation,
'array_offset': Vector((0, 0, 0)),
'scale': obj.scale
})]
for modifier in obj.modifiers:
created_instances = []
if modifier.type == 'ARRAY':
instance_objects.extend(
self.resolve_array_modifier(product, modifier, instance_objects)
)
elif modifier.type == 'MIRROR':
instance_objects.extend(
self.resolve_mirror_modifier(product, modifier, instance_objects)
)
def get_array_modifier(self, product, modifier):
obj = product['raw']
array = ArrayModifier()
world_rotation = obj.matrix_world.decompose()[1]
array.offset = world_rotation @ Vector(
(
modifier.constant_offset_displace[0],
modifier.constant_offset_displace[1],
modifier.constant_offset_displace[2]
)
)
if modifier.fit_type == 'FIXED_COUNT':
array.count = modifier.count
elif modifier.fit_type == 'FIT_LENGTH':
array.count = int(modifier.fit_length / array.offset.length)
return array
def resolve_array_modifier(self, product, modifier, instance_objects):
modifier = self.get_array_modifier(product, modifier)
created_instances = []
for obj in instance_objects:
for n in range(modifier.count - 1):
override = obj[1].copy()
override['array_offset'] = ((n + 1) * modifier.offset)
override['location'] = obj[1]['location'].copy()
location = override['location'] + ((n + 1) * modifier.offset)
override['location'] = location
self.add_product(
self.get_product(
{'raw': obj[0], 'metadata': product['metadata']},
metadata_override=override,
attribute_override={'GlobalId': self.get_parametric_global_id(
product['raw'],
len(instance_objects)+len(created_instances)-1
)
}
2019-11-18 03:49:04 +01:00
)
)
created_instances.append((obj[0], override))
return created_instances
def resolve_mirror_modifier(self, product, modifier, instance_objects):
created_instances = []
mirrors = []
for axis in [0, 1, 2]:
if modifier.use_axis[axis]:
mirrors.append(axis)
for mirror in mirrors:
axis_instances = []
for obj in instance_objects:
override = obj[1].copy()
override['has_scale'] = True
override['has_mirror'] = True
override['scale'] = obj[1]['scale'].copy()
override['scale'][mirror] *= -1
mirror_axis = Vector((0, 0, 0))
mirror_axis[mirror] = 1
world_rotation = obj[0].matrix_world.decompose()[1].to_matrix().to_4x4()
unrotated_offset = world_rotation.inverted() @ override['array_offset']
mirrored_offset = unrotated_offset @ Matrix.Scale(-1, 4, mirror_axis)
rotated_offset = world_rotation @ mirrored_offset
override['location'] = override['location'] - override['array_offset'] + rotated_offset
self.add_product(
self.get_product(
{'raw': obj[0], 'metadata': product['metadata']},
metadata_override=override,
attribute_override={'GlobalId': self.get_parametric_global_id(
product['raw'],
len(instance_objects)+len(created_instances)-1
)
}
)
)
created_instances.append((obj[0], override))
axis_instances.append((obj[0], override))
instance_objects.extend(axis_instances)
return created_instances
def resolve_product_relationships(self):
2019-11-18 07:51:09 +01:00
for i, product in enumerate(self.products):
2019-11-17 13:12:22 +01:00
obj = product['raw']
self.resolve_voids_and_fills(i, obj)
self.resolve_structural_connections(i, obj)
def resolve_structural_connections(self, i, obj):
if not obj.BIMObjectProperties.structural_member_connection:
return
self.rel_connects_structural_member[i] = self.get_product_index_from_raw_name(
obj.BIMObjectProperties.structural_member_connection.name)
def resolve_voids_and_fills(self, i, obj):
for m in obj.modifiers:
2020-02-02 20:28:51 +11:00
if m.type != 'BOOLEAN' or m.object is None:
continue
void_or_projection = self.get_product_index_from_raw_name(m.object.name)
if void_or_projection is None:
continue
if m.operation == 'DIFFERENCE' \
and self.get_ifc_class(m.object.name) == 'IfcOpeningElement':
2020-02-02 20:28:51 +11:00
self.rel_voids_elements.setdefault(i, []).append(void_or_projection)
if not m.object.parent:
continue
fill = self.get_product_index_from_raw_name(m.object.parent.name)
if fill:
self.rel_fills_elements.setdefault(void_or_projection, []).append(fill)
elif m.operation == 'UNION' \
and self.get_ifc_class(m.object.name) == 'IfcProjectionElement':
2020-02-02 20:28:51 +11:00
self.rel_projects_elements.setdefault(i, []).append(void_or_projection)
2019-11-17 13:25:41 +01:00
def get_axis(self, matrix, axis):
2019-11-18 07:51:09 +01:00
return matrix.col[axis].to_3d().normalized()
2019-11-17 13:25:41 +01:00
def get_parametric_global_id(self, obj, index):
global_ids = obj.BIMObjectProperties.global_ids
total_global_ids = len(global_ids)
if index < total_global_ids:
return global_ids[index].name
2019-11-17 13:25:41 +01:00
global_id = obj.BIMObjectProperties.global_ids.add()
global_id.name = ifcopenshell.guid.new()
return global_id.name
def add_product(self, product):
self.products.append(product)
self.product_name_index_map[product['raw'].name] = self.product_index
self.product_index += 1
def get_product_index_from_raw_name(self, name):
for index, product in enumerate(self.products):
if product['raw'].name == name:
return index
2019-11-17 13:25:41 +01:00
2020-01-28 22:06:58 +11:00
def append_product_attributes(self, product, obj):
product.update({
2019-11-17 14:00:05 +01:00
'location': obj.matrix_world.translation,
2019-11-17 13:25:41 +01:00
'up_axis': self.get_axis(obj.matrix_world, 2),
'forward_axis': self.get_axis(obj.matrix_world, 0),
2019-11-18 07:54:11 +01:00
'right_axis': self.get_axis(obj.matrix_world, 1),
2019-11-17 13:25:41 +01:00
'has_scale': obj.scale != Vector((1, 1, 1)),
'has_mirror': False,
'array_offset': Vector((0, 0, 0)),
2019-11-17 13:25:41 +01:00
'scale': obj.scale,
2020-01-28 22:06:58 +11:00
'representations': self.get_object_representation_names(obj)
})
def get_product(self, selected_product, metadata_override={}, attribute_override={}):
obj = selected_product['raw']
product = {
'ifc': None,
'raw': obj,
2019-11-17 13:25:41 +01:00
'class': self.get_ifc_class(obj.name),
'relating_structure': None,
'relating_host': None,
'relating_qtos_key': None,
'attributes': self.get_object_attributes(obj),
'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition,
'boundary_condition_class': None,
'boundary_condition_attributes': {},
'structural_member_connection': None
2019-11-17 13:25:41 +01:00
}
2020-01-28 22:06:58 +11:00
self.append_product_attributes(product, obj)
product['attributes'].update(attribute_override)
product.update(metadata_override)
type_product = obj.BIMObjectProperties.relating_type
if type_product \
and self.is_a_type(self.get_ifc_class(type_product.name)):
reference = self.get_type_product_reference(type_product.name)
2019-09-29 22:12:51 +10:00
self.rel_defines_by_type.setdefault(reference, []).append(self.product_index)
if product['has_boundary_condition']:
product['boundary_condition_class'] = obj.BIMObjectProperties.boundary_condition.name
product['boundary_condition_attributes'] = {a.name: a.string_value
for a in obj.BIMObjectProperties.boundary_condition.attributes}
self.get_product_relating_structure(product, obj)
2019-11-17 13:25:41 +01:00
if 'IfcRelNests' in obj.constraints:
# TODO: I think get_product_index_from_raw_name should not be used
parent_product_index = self.get_product_index_from_raw_name(
2019-11-17 13:25:41 +01:00
obj.constraints['IfcRelNests'].target.name)
self.rel_nests.setdefault(parent_product_index, []).append(product)
product['relating_host'] = parent_product_index
2019-11-17 13:25:41 +01:00
for name, constraint in obj.constraints.items():
if 'IfcRelSpaceBoundary' not in name:
continue
self.rel_space_boundaries.setdefault(self.product_index, []).append({
'ifc': None,
'class': self.get_ifc_class(name),
'related_building_element_raw_name': constraint.target.name,
'connection_geometry_face_index': name.split('/')[1],
'attributes': {
'PhysicalOrVirtualBoundary': name.split('/')[2],
'InternalOrExternalBoundary': name.split('/')[3]
2019-11-17 13:25:41 +01:00
}
})
2019-11-17 13:25:41 +01:00
if obj.instance_type == 'COLLECTION' \
and self.is_a_rel_aggregates(self.get_ifc_class(obj.instance_collection.name)):
self.rel_aggregates[self.product_index] = obj.name
if 'rel_aggregates_relating_object' in selected_product['metadata']:
relating_object = selected_product['metadata']['rel_aggregates_relating_object']
2019-11-17 13:25:41 +01:00
inverted = relating_object.matrix_world.inverted()
product['location'] = inverted @ product['location']
product['up_axis'] = self.get_axis(inverted @ obj.matrix_world, 2)
product['forward_axis'] = self.get_axis(inverted @ obj.matrix_world, 0)
2019-11-18 07:51:09 +01:00
product['right_axis'] = self.get_axis(inverted @ obj.matrix_world, 1)
self.aggregates.setdefault(relating_object.name, []).append(self.product_index)
2019-11-17 13:25:41 +01:00
if obj.name in self.qtos:
self.rel_defines_by_qto.setdefault(obj.name, []).append(product)
self.get_product_psets_qtos(product, obj, is_pset=True)
self.get_product_psets_qtos(product, obj, is_qto=True)
for reference in obj.BIMObjectProperties.document_references:
self.rel_associates_document_object.setdefault(
reference.name, []).append(product)
2019-11-17 13:25:41 +01:00
for classification in obj.BIMObjectProperties.classifications:
2019-11-01 17:44:10 +11:00
self.rel_associates_classification_object.setdefault(
classification.name, []).append(product)
2019-11-01 17:44:10 +11:00
for constraint in obj.BIMObjectProperties.constraints:
self.rel_associates_constraint_object.setdefault(
constraint.name, []).append(product)
2019-09-08 19:02:00 +10:00
2019-11-17 13:25:41 +01:00
for slot in obj.material_slots:
if slot.material is None or slot.link == 'OBJECT':
continue
if obj.BIMObjectProperties.material_type == 'IfcMaterialLayerSet':
2020-01-14 16:11:11 +11:00
self.rel_associates_material_layer_set.setdefault(self.product_index, []).append(
slot.material.name)
elif obj.BIMObjectProperties.material_type == 'IfcMaterialConstituentSet':
2019-11-17 13:25:41 +01:00
self.rel_associates_material_constituent_set.setdefault(self.product_index, []).append(
slot.material.name)
2020-01-14 16:11:11 +11:00
elif obj.BIMObjectProperties.material_type == 'IfcMaterialProfileSet':
self.rel_associates_material_profile_set.setdefault(self.product_index, []).append(
slot.material.name)
else:
2019-11-17 13:25:41 +01:00
self.rel_associates_material.setdefault(slot.material.name, []).append(product)
2019-09-09 15:11:07 +10:00
return product
def get_product_psets_qtos(self, product, obj, is_pset=False, is_qto=False):
if is_pset:
2020-04-24 20:54:24 +10:00
psets_qtos = obj.BIMObjectProperties.psets
results = self.psets
relationships = self.rel_defines_by_pset
if is_qto:
psets_qtos = obj.BIMObjectProperties.qtos
if not psets_qtos and self.ifc_export_settings.should_guess_quantities:
self.add_automatic_qtos(product['class'], obj)
psets_qtos = obj.BIMObjectProperties.qtos
results = self.qtos
relationships = self.rel_defines_by_qto
for item in psets_qtos:
item_key = '{}/{}'.format(item.name, obj.name)
raw = {p.name: p.string_value for p in item.properties if p.string_value}
if not raw:
continue
results[item_key] = {
'ifc': None,
'raw': raw,
'attributes': { 'Name': item.name }
}
relationships.setdefault(item_key, []).append(product)
def add_automatic_qtos(self, ifc_class, obj):
qto_names = self.get_applicable_qtos(ifc_class)
for name in qto_names:
if name not in schema.ifc.qtos:
continue
has_automatic_value = False
props = schema.ifc.qtos[name]['HasPropertyTemplates'].keys()
guessed_values = {}
for prop_name in props:
value = self.qto_calculator.guess_quantity(prop_name, props, obj)
if value:
guessed_values[prop_name] = value
has_automatic_value = True
if has_automatic_value:
qto = obj.BIMObjectProperties.qtos.add()
qto.name = name
for prop_name in props:
prop = qto.properties.add()
prop.name = prop_name
if prop_name in guessed_values:
prop.string_value = str(guessed_values[prop_name])
def get_applicable_qtos(self, ifc_class):
results = []
empty = ifcopenshell.file()
element = empty.create_entity(ifc_class)
for ifc_class, qto_names in schema.ifc.applicable_qtos.items():
if element.is_a(ifc_class):
results.extend(qto_names)
return results
def get_product_relating_structure(self, product, obj):
relating_structure = obj.BIMObjectProperties.relating_structure
if relating_structure:
reference = self.get_spatial_structure_element_reference(relating_structure.name)
self.rel_contained_in_spatial_structure.setdefault(reference, []).append(self.product_index)
product['relating_structure'] = reference
return
for collection in product['raw'].users_collection:
self.parse_product_collection(product, collection)
def parse_product_collection(self, product, collection):
if collection is None:
return
class_name = self.get_ifc_class(collection.name)
if self.is_a_spatial_structure_element(class_name):
2020-05-16 20:04:51 +10:00
reference = self.get_spatial_structure_element_reference(collection.name)
self.rel_contained_in_spatial_structure.setdefault(reference, []).append(self.product_index)
product['relating_structure'] = reference
elif self.is_a_group(class_name):
reference = self.get_group_reference(collection.name)
self.rel_assigns_to_group.setdefault(reference, []).append(self.product_index)
elif self.is_a_rel_aggregates(class_name):
pass
else:
self.parse_product_collection(product, self.get_parent_collection(collection))
def get_parent_collection(self, child_collection):
for parent_collection in bpy.data.collections:
for child in parent_collection.children:
if child.name == child_collection.name:
return parent_collection
def add_spatial_elements_if_unselected(self, selected_objects):
2020-04-09 02:42:03 +02:00
results = set(selected_objects)
base_collections = set()
added_objs = []
for obj in selected_objects:
for collection in obj.users_collection:
2020-04-09 02:42:03 +02:00
base_collections.add(collection)
for collection in base_collections:
spatial_obj = bpy.data.objects.get(collection.name)
if not spatial_obj or spatial_obj in added_objs:
2020-04-08 12:07:27 +10:00
continue
added_objs.append(spatial_obj)
parent_collection = self.get_parent_collection(collection)
while parent_collection:
spatial_obj = bpy.data.objects.get(parent_collection.name)
parent_collection = self.get_parent_collection(parent_collection)
if not spatial_obj or spatial_obj in added_objs:
2020-04-08 12:07:27 +10:00
continue
added_objs.append(spatial_obj)
2020-04-09 02:42:03 +02:00
results.update(added_objs)
return results
def add_type_elements_if_unselected(self, selected_objects):
added_objs = []
for obj in selected_objects:
if obj.BIMObjectProperties.relating_type:
added_objs.append(obj.BIMObjectProperties.relating_type)
if obj.instance_type == 'COLLECTION':
for obj2 in obj.instance_collection.objects:
if obj2.BIMObjectProperties.relating_type:
added_objs.append(obj2.BIMObjectProperties.relating_type)
selected_objects.update(added_objs)
selected_objects = set(selected_objects)
2020-01-28 22:06:58 +11:00
def categorise_selected_objects(self, objects_to_sort, metadata=None):
if not metadata:
metadata = {}
2019-11-17 13:25:41 +01:00
for obj in objects_to_sort:
if obj.name[0:3] != 'Ifc':
continue
elif self.is_a_spatial_structure_element(self.get_ifc_class(obj.name)):
2020-01-28 22:06:58 +11:00
self.selected_spatial_structure_elements.append({'raw': obj, 'metadata': metadata})
elif self.is_a_type(self.get_ifc_class(obj.name)):
self.selected_types.append({'raw': obj, 'metadata': metadata})
elif self.is_a_group(self.get_ifc_class(obj.name)):
self.selected_groups.append({'raw': obj, 'metadata': metadata})
2020-01-28 22:06:58 +11:00
elif obj.instance_type == 'COLLECTION':
self.categorise_selected_objects(
2019-11-18 03:45:24 +01:00
obj.instance_collection.objects,
{'rel_aggregates_relating_object': obj}
)
self.selected_products.append({'raw': obj, 'metadata': metadata})
elif self.is_a_project(self.get_ifc_class(obj.name)) \
or self.is_a_library(self.get_ifc_class(obj.name)):
pass
elif not self.is_a_library(self.get_ifc_class(obj.users_collection[0].name)):
self.selected_products.append({'raw': obj, 'metadata': metadata})
2019-09-01 17:23:48 +10:00
def get_material_psets(self):
psets = {}
for filename in Path(self.data_dir + 'material/').glob('**/*.csv'):
with open(filename, 'r') as f:
description = filename.parts[-2]
name = filename.stem
if description not in psets:
psets[description] = {}
psets[description][name] = {
'ifc': None,
'raw': {x[0]: x[1] for x in list(csv.reader(f))},
'attributes': {
'Name': name,
'Description': description}
}
return psets
2019-09-29 22:12:51 +10:00
def get_door_attributes(self):
2019-09-29 22:29:06 +10:00
return self.get_predefined_attributes('door')
def get_window_attributes(self):
return self.get_predefined_attributes('window')
2019-11-18 05:46:52 +01:00
def get_predefined_attributes(self, attr):
2019-09-29 22:12:51 +10:00
results = {}
2019-11-18 05:46:52 +01:00
for filename in Path(self.data_dir + attr + '/').glob('**/*.csv'):
2019-09-29 22:12:51 +10:00
with open(filename, 'r') as f:
type_name = filename.parts[-2]
pset_name = filename.stem
results.setdefault(type_name, []).append({
'ifc': None,
2019-11-17 13:25:41 +01:00
'raw': {x[0]: x[1] for x in list(csv.reader(f))},
2019-09-29 22:12:51 +10:00
'pset_name': pset_name.split('.')[0]
2019-11-17 13:25:41 +01:00
})
2019-09-29 22:12:51 +10:00
return results
def get_classifications(self):
results = {}
for classification in bpy.context.scene.BIMProperties.classifications:
schema.ifc.load_classification(classification.filename)
results[classification.filename] = {
'ifc': None,
'raw': classification,
'raw_element': schema.ifc.classification_files[classification.filename].by_type('IfcClassification')[0]
}
return results
def get_classification_reference_maps(self):
results = {}
for filename, classification in self.classifications.items():
2020-05-09 12:47:26 +10:00
ifc_file = schema.ifc.classification_files[filename]
if ifc_file.schema == 'IFC2X3':
results[filename] = { e.ItemReference: e for e in ifc_file.by_type('IfcClassificationReference')}
else:
results[filename] = { e.Identification: e for e in ifc_file.by_type('IfcClassificationReference')}
return results
def get_classification_references(self):
results = {}
for product in self.selected_products:
for reference in product['raw'].BIMObjectProperties.classifications:
results[reference.name] = {
'ifc': None,
'raw': reference,
'raw_element': self.classification_reference_maps[reference.referenced_source][reference.name]
2019-11-17 13:25:41 +01:00
}
return results
def get_constraints(self):
results = {}
data_map = {
'name': 'Name',
'description': 'Description',
'constraint_grade': 'ConstraintGrade',
'constraint_source': 'ConstraintSource',
'user_defined_grade': 'UserDefinedGrade',
'objective_qualifier': 'ObjectiveQualifier',
'user_defined_qualifier': 'UserDefinedQualifier',
}
for constraint in bpy.context.scene.BIMProperties.constraints:
attributes = {}
for key, value in data_map.items():
if getattr(constraint, key):
attributes[value] = getattr(constraint, key)
results[constraint.name] = {
'ifc': None,
'raw': constraint,
'attributes': attributes
}
return results
2019-11-15 15:44:04 +11:00
def get_people(self):
data_map = {
'name': 'Identification',
'family_name': 'FamilyName',
'given_name': 'GivenName',
}
list_data_map = {
'middle_names': 'MiddleNames',
'prefix_titles': 'PrefixTitles',
'suffix_titles': 'SuffixTitles',
}
results = []
for person in bpy.context.scene.BIMProperties.people:
attributes = {}
for key, value in data_map.items():
if getattr(person, key):
attributes[value] = getattr(person, key)
for key, value in list_data_map.items():
if getattr(person, key):
attributes[value] = getattr(person, key).split(',')
results.append({
'ifc': None,
'raw': person,
'attributes': attributes,
'roles': self.get_roles(person.roles),
'addresses': self.get_addresses(person.addresses)
})
return results
2019-11-15 15:44:04 +11:00
def get_organisations(self):
data_map = {
'name': 'Name',
'description': 'Description',
}
results = []
for organisation in bpy.context.scene.BIMProperties.organisations:
attributes = {}
for key, value in data_map.items():
if getattr(organisation, key):
attributes[value] = getattr(organisation, key)
results.append({
'ifc': None,
'raw': organisation,
'attributes': attributes,
'roles': self.get_roles(organisation.roles),
'addresses': self.get_addresses(organisation.addresses)
})
return results
def get_roles(self, roles):
data_map = {
'name': 'Role',
'user_defined_role': 'UserDefinedRole',
'description': 'Description',
}
results = []
for role in roles:
attributes = {}
for key, value in data_map.items():
if getattr(role, key):
attributes[value] = getattr(role, key)
results.append({
'ifc': None,
'raw': role,
'attributes': attributes
})
return results
def get_addresses(self, addresses):
results = []
address_data_map = {
'purpose': 'Purpose',
'description': 'Description',
'user_defined_purpose': 'UserDefinedPurpose',
}
postal_data_map = {
'internal_location': 'InternalLocation',
'postal_box': 'PostalBox',
'town': 'Town',
'region': 'Region',
'postal_code': 'PostalCode',
'country': 'Country',
}
telecom_data_map = {
'pager_number': 'PagerNumber',
'www_home_page_url': 'WWWHomePageURL',
}
telecom_list_data_map = {
'telephone_numbers': 'TelephoneNumbers',
'fascimile_numbers': 'FascimileNumbers',
'electronic_mail_addresses': 'ElectronicMailAddresses',
'messaging_ids': 'MessagingIDs',
}
for address in addresses:
attributes = {}
if 'IfcPostalAddress' in address.name:
merged_data_map = {**address_data_map, **postal_data_map}
if address.address_lines:
attributes['AddressLines'] = address.address_lines.split('/')
elif 'IfcTelecomAddress' in address.name:
merged_data_map = {**address_data_map, **telecom_data_map}
for key, value in telecom_list_data_map.items():
if getattr(address, key):
attributes[value] = getattr(address, key).split(',')
for key, value in merged_data_map.items():
if getattr(address, key):
attributes[value] = getattr(address, key)
results.append({
'ifc': None,
'raw': address,
'is_postal': 'IfcPostalAddress' in address.name,
'is_telecom': 'IfcTelecomAddress' in address.name,
'attributes': attributes
})
return results
2019-11-15 15:44:04 +11:00
def get_document_references(self):
results = {}
for reference in bpy.context.scene.BIMProperties.document_references:
data_map = {
'name': 'Identification',
'human_name': 'Name',
'description': 'Description',
'location': 'Location'
}
attributes = {}
for key, value in data_map.items():
if getattr(reference, key):
attributes[value] = getattr(reference, key)
results[reference.name] = {
'ifc': None,
'raw': reference,
'referenced_document': reference.referenced_document,
'attributes': attributes
}
return results
def get_document_information(self):
results = {}
for information in bpy.context.scene.BIMProperties.document_information:
data_map = {
'name': 'Identification',
'human_name': 'Name',
'description': 'Description',
'location': 'Location',
'purpose': 'Purpose',
'intended_use': 'IntendedUse',
'scope': 'Scope',
'revision': 'Revision',
'creation_time': 'CreationTime',
'last_revision_time': 'LastRevisionTime',
'electronic_format': 'ElectronicFormat',
'valid_from': 'ValidFrom',
'valid_until': 'ValidUntil',
'confidentiality': 'Confidentiality',
'status': 'Status'
}
attributes = {}
for key, value in data_map.items():
if getattr(information, key):
attributes[value] = getattr(information, key)
results[information.name] = {
'ifc': None,
'raw': information,
'attributes': attributes
}
return results
def get_projects(self):
results = []
for collection in bpy.data.collections:
if self.is_a_project(self.get_ifc_class(collection.name)):
obj = bpy.data.objects.get(collection.name)
results.append({
'ifc': None,
'raw': collection,
2019-09-01 17:23:48 +10:00
'class': self.get_ifc_class(collection.name),
'attributes': self.get_object_attributes(obj)
})
return results
def setup_project(self):
bpy.ops.bim.quick_project_setup()
for collection in bpy.data.collections:
if collection.name == 'IfcBuildingStorey/Ground Floor':
break
for obj in bpy.context.selected_objects:
if hasattr(obj, 'data') \
and isinstance(obj.data, bpy.types.Mesh) \
and '/' not in obj.name:
obj.name = 'IfcBuildingElementProxy/{}'.format(obj.name)
for user_collection in obj.users_collection:
user_collection.objects.unlink(obj)
collection.objects.link(obj)
def get_libraries(self):
results = []
for collection in self.project['raw'].children:
if not self.is_a_library(self.get_ifc_class(collection.name)):
continue
results.append({
'ifc': None,
'raw': collection,
'class': self.get_ifc_class(collection.name),
'rel_declares_type_products': [],
'attributes': self.get_object_attributes(collection)
})
return results
def get_map_conversion(self):
scene = bpy.context.scene
2019-10-19 15:15:42 +11:00
if not scene.BIMProperties.has_georeferencing:
return {}
return {
'ifc': None,
'attributes': {
2019-10-19 15:15:42 +11:00
'Eastings': float(scene.MapConversion.eastings),
'Northings': float(scene.MapConversion.northings),
'OrthogonalHeight': float(scene.MapConversion.orthogonal_height),
'XAxisAbscissa': float(scene.MapConversion.x_axis_abscissa),
'XAxisOrdinate': float(scene.MapConversion.x_axis_ordinate),
'Scale': float(scene.MapConversion.scale)
}
2019-11-17 13:25:41 +01:00
}
def get_target_crs(self):
scene = bpy.context.scene
2019-10-19 15:15:42 +11:00
if not scene.BIMProperties.has_georeferencing:
return {}
return {
'ifc': None,
'attributes': {
2019-10-19 15:15:42 +11:00
'Name': scene.TargetCRS.name,
'Description': scene.TargetCRS.description,
'GeodeticDatum': scene.TargetCRS.geodetic_datum,
'VerticalDatum': scene.TargetCRS.vertical_datum,
'MapProjection': scene.TargetCRS.map_projection,
'MapZone': str(scene.TargetCRS.map_zone),
'MapUnit': scene.TargetCRS.map_unit
}
2019-11-17 13:25:41 +01:00
}
def get_library_information(self):
scene = bpy.context.scene
if not scene.BIMProperties.has_library:
return {}
return {
'ifc': None,
'attributes': {
'Name': scene.BIMLibrary.name,
'Version': scene.BIMLibrary.version,
'VersionDate': scene.BIMLibrary.version_date,
'Location': scene.BIMLibrary.location,
'Description': scene.BIMLibrary.description
}
}
def get_spatial_structure_elements(self):
elements = []
for selected_element in self.selected_spatial_structure_elements:
obj = selected_element['raw']
element = {
'ifc': None,
'raw': obj,
'class': self.get_ifc_class(obj.name),
'attributes': self.get_object_attributes(obj)
}
self.append_product_attributes(element, obj)
self.get_product_psets_qtos(element, obj, is_pset=True)
elements.append(element)
return elements
def get_groups(self):
elements = []
for selected_element in self.selected_groups:
obj = selected_element['raw']
elements.append({
'ifc': None,
'raw': obj,
'class': self.get_ifc_class(obj.name),
'attributes': self.get_object_attributes(obj)
})
return elements
def load_presentation_layer_assignments(self):
for representation in self.representations.values():
if representation['presentation_layer']:
self.presentation_layer_assignments.setdefault(
representation['presentation_layer'], []).append(representation)
def load_representations(self):
if not self.ifc_export_settings.has_representations:
return
2020-01-28 22:06:58 +11:00
for product in self.selected_products \
+ self.selected_types \
2020-01-28 22:06:58 +11:00
+ self.selected_spatial_structure_elements:
self.prevent_data_name_duplicates(product)
self.load_product_representations(product)
def prevent_data_name_duplicates(self, product):
if product['raw'].data \
and bpy.data.meshes.get(product['raw'].data.name) \
and bpy.data.curves.get(product['raw'].data.name):
product['raw'].data.name += '~'
def load_product_representations(self, product):
obj = product['raw']
if obj.data and obj.data.name in self.representations:
return
if isinstance(obj.data, bpy.types.Camera):
return
self.append_representation_per_context(obj)
2020-01-12 20:47:06 +11:00
def is_point_cloud(self, obj):
return hasattr(obj, 'point_cloud_visualizer') \
and obj.point_cloud_visualizer.uuid
def is_structural(self, obj):
return 'IfcStructural' in obj.name
def append_default_representation(self, obj):
self.representations['Model/Body/MODEL_VIEW/{}'.format(obj.data.name)] = self.get_representation(
obj.data, obj, 'Model', 'Body', 'MODEL_VIEW')
def append_point_cloud_representation(self, obj):
self.representations['Model/Body/MODEL_VIEW/{}'.format(obj.name)] = self.get_representation(
obj.point_cloud_visualizer, obj, 'Model', 'Body', 'MODEL_VIEW')
def append_curve_axis_representation(self, obj):
self.representations['Model/Axis/GRAPH_VIEW/{}'.format(obj.data.name)] = self.get_representation(
obj.data, obj, 'Model', 'Axis', 'GRAPH_VIEW')
def append_structural_reference_representation(self, obj):
if obj.type == 'EMPTY':
self.representations['Model/Reference/GRAPH_VIEW/{}'.format(obj.name)] = self.get_representation(
obj, obj, 'Model', 'Reference', 'GRAPH_VIEW')
else:
self.representations['Model/Reference/GRAPH_VIEW/{}'.format(obj.data.name)] = self.get_representation(
obj.data, obj, 'Model', 'Reference', 'GRAPH_VIEW')
def append_representation_per_context(self, obj):
if obj.data:
name = self.get_ifc_representation_name(obj.data.name)
else:
name = obj.name
for context in self.ifc_export_settings.context_tree:
for subcontext in context['subcontexts']:
for target_view in subcontext['target_views']:
self.append_representation_in_context(obj, context['name'], subcontext['name'], target_view, name)
def append_representation_in_context(self, obj, context, subcontext, target_view, name):
context_prefix = '/'.join([context, subcontext, target_view])
mesh_name = '/'.join([context_prefix, name])
mesh = self.search_for_mesh_or_curve_data(mesh_name)
if mesh:
self.representations[mesh_name] = self.get_representation(
mesh, obj, context, subcontext, target_view)
elif context_prefix == 'Model/Body/MODEL_VIEW' \
and obj.data \
and not self.is_mesh_context_sensitive(obj.data.name):
self.append_default_representation(obj)
elif context_prefix == 'Model/Body/MODEL_VIEW' \
and self.is_point_cloud(obj):
self.append_point_cloud_representation(obj)
elif context_prefix == 'Model/Reference/GRAPH_VIEW' \
and self.is_structural(obj):
self.append_structural_reference_representation(obj)
elif context_prefix == 'Model/Axis/GRAPH_VIEW' \
and obj.type == 'CURVE':
self.append_curve_axis_representation(obj)
def search_for_mesh_or_curve_data(self, name):
data = bpy.data.meshes.get(name)
if not data:
data = bpy.data.curves.get(name)
return data
def get_representation(self, mesh, obj, context, subcontext, target_view):
return {
'ifc': None,
'raw': mesh,
2019-11-17 13:25:41 +01:00
'raw_object': obj,
'context': context,
'subcontext': subcontext,
'target_view': target_view,
'is_parametric': mesh.BIMMeshProperties.is_parametric if hasattr(mesh, 'BIMMeshProperties') else False,
'is_curve': isinstance(mesh, bpy.types.Curve),
'is_point_cloud': self.is_point_cloud(obj),
'is_structural': self.is_structural(obj),
'is_text': isinstance(mesh, bpy.types.TextCurve),
'is_wireframe': mesh.BIMMeshProperties.is_wireframe if hasattr(mesh, 'BIMMeshProperties') else False,
'is_native': mesh.BIMMeshProperties.is_native if hasattr(mesh, 'BIMMeshProperties') else False,
'is_swept_solid': mesh.BIMMeshProperties.is_swept_solid if hasattr(mesh, 'BIMMeshProperties') else False,
'is_generated': False,
2020-05-04 07:46:25 +10:00
'presentation_layer': mesh.BIMMeshProperties.presentation_layer if hasattr(mesh, 'BIMMeshProperties') else None,
2019-11-17 13:25:41 +01:00
'attributes': {'Name': mesh.name}
}
def is_mesh_context_sensitive(self, name):
return '/' in name \
and ( \
name[0:6] == 'Model/' \
or name[0:5] == 'Plan/' \
)
def get_ifc_representation_name(self, name):
if self.is_mesh_context_sensitive(name):
return name.split('/')[3]
return name
2019-09-09 15:11:07 +10:00
def get_materials(self):
results = {}
if not self.ifc_export_settings.has_representations:
return results
for product in self.selected_products + self.type_products:
2019-11-17 13:25:41 +01:00
obj = product['raw']
2019-11-18 03:45:24 +01:00
if obj.data is None:
2019-09-09 15:11:07 +10:00
continue
2019-11-17 13:25:41 +01:00
for slot in obj.material_slots:
2019-11-18 05:46:52 +01:00
if slot.material is None:
continue
if slot.material.name in results or slot.link == 'OBJECT':
2019-09-09 15:11:07 +10:00
continue
results[slot.material.name] = {
'ifc': None,
2020-01-14 15:38:22 +11:00
'part_ifc': None,
2019-09-09 15:11:07 +10:00
'raw': slot.material,
'material_type': obj.BIMObjectProperties.material_type,
2020-01-14 15:38:22 +11:00
'attributes': self.get_material_attributes(slot.material)
2019-11-17 13:25:41 +01:00
}
2019-09-09 15:11:07 +10:00
return results
2020-01-14 15:38:22 +11:00
def get_material_attributes(self, material):
attributes = {'Name': material.name}
attributes.update({a.name: a.string_value for a in material.BIMMaterialProperties.attributes})
return attributes
def get_styled_items(self):
results = []
if not self.ifc_export_settings.has_representations:
return results
parsed_data_names = []
for product in self.selected_products + self.type_products:
2019-11-17 13:25:41 +01:00
obj = product['raw']
if obj.data is None or obj.data.name in parsed_data_names:
continue
parsed_data_names.append(obj.data.name)
2019-11-17 13:25:41 +01:00
for slot in obj.material_slots:
2019-11-18 05:46:52 +01:00
if slot.material is None:
continue
results.append({
'ifc': None,
'raw': slot.material,
'related_product_name': product['raw'].name,
2019-11-17 13:25:41 +01:00
'attributes': {'Name': slot.material.name},
})
return results
2019-09-01 17:23:48 +10:00
def get_type_products(self):
results = []
for product in self.selected_types:
results.append(self.get_product(product))
return results
2019-09-01 17:23:48 +10:00
2019-11-17 13:25:41 +01:00
def get_object_representation_names(self, obj):
names = []
2020-01-12 20:47:06 +11:00
if self.is_point_cloud(obj):
names.append('Model/Body/MODEL_VIEW/{}'.format(obj.name))
return names
elif self.is_structural(obj) and obj.type == 'EMPTY':
names.append('Model/Reference/GRAPH_VIEW/{}'.format(obj.name))
return names
if not obj.data:
return names
2019-11-17 13:25:41 +01:00
name = self.get_ifc_representation_name(obj.data.name)
for context in self.ifc_export_settings.context_tree:
for subcontext in context['subcontexts']:
for target_view in subcontext['target_views']:
mesh_name = '/'.join([context['name'], subcontext['name'], target_view, name])
if mesh_name in self.representations:
names.append(mesh_name)
return names
def get_spatial_structure_elements_tree(self, parent):
children = []
if parent['raw'].name not in bpy.data.collections:
return children
for reference, element in enumerate(self.spatial_structure_elements):
if ( \
# A convention is established that spatial elements may be
# an object placed in a collection of the same name
element['raw'].name == element['raw'].users_collection[0].name \
and element['raw'].users_collection[0].name in [c.name \
for c in bpy.data.collections[parent['raw'].name].children] \
) or ( \
# We allow finer grain spatial elements such as IfcSpace to
# break the convention to prevent collection overload in Blender
element['raw'].name != element['raw'].users_collection[0].name \
and element['raw'].users_collection[0].name in [o.name \
for o in bpy.data.collections[parent['raw'].name].objects] \
):
children.append({
'reference': reference,
'children': self.get_spatial_structure_elements_tree(element)
2019-11-17 13:25:41 +01:00
})
return children
def get_spatial_structure_element_reference(self, name):
2020-05-16 20:04:51 +10:00
return [e['raw'].name for e in self.spatial_structure_elements].index(name)
def get_group_reference(self, name):
return ['{}/{}'.format(e['class'], e['attributes']['Name'])
for e in self.groups].index(name)
2019-09-01 18:40:20 +10:00
def get_type_product_reference(self, name):
return [p['raw'].name
for p in self.type_products].index(name)
2019-09-01 18:40:20 +10:00
def get_ifc_class(self, name):
return name.split('/')[0]
def get_ifc_name(self, name):
try:
return name.split('/')[1]
except IndexError:
2019-11-17 13:25:41 +01:00
self.ifc_export_settings.logger.error(
'Name "{}" does not follow the format of "IfcClass/Name"'.format(name))
def get_name_attribute(self, obj):
name = obj.BIMObjectProperties.attributes.get('Name')
if name:
return name.string_value
return self.get_ifc_name(obj.name)
def is_a_spatial_structure_element(self, class_name):
return class_name in [
'IfcBuilding',
'IfcBuildingStorey',
'IfcExternalSpatialElement',
'IfcSite',
'IfcSpace',
'IfcSpatialZone'
]
def is_a_rel_aggregates(self, class_name):
return class_name == 'IfcRelAggregates'
def is_a_project(self, class_name):
return class_name == 'IfcProject'
def is_a_library(self, class_name):
return class_name == 'IfcProjectLibrary'
def is_a_group(self, class_name):
return class_name in [g for g in schema.ifc.IfcGroup.keys()]
2019-09-01 18:40:20 +10:00
def is_a_type(self, class_name):
return (class_name[0:3] == 'Ifc' and class_name[-4:] == 'Type') \
or (class_name[0:3] == 'Ifc' and class_name[-5:] == 'Style')
2019-09-01 18:40:20 +10:00
2019-11-17 13:25:41 +01:00
class IfcExporter():
def __init__(self, ifc_export_settings, ifc_parser):
2019-10-05 21:15:53 +10:00
self.template_file = '{}template.ifc'.format(ifc_export_settings.schema_dir)
self.ifc_export_settings = ifc_export_settings
self.ifc_parser = ifc_parser
2019-08-29 23:11:12 +10:00
2020-04-09 02:42:03 +02:00
def export(self, selected_objects):
self.schema = self.ifc_export_settings.schema
2020-05-09 12:47:26 +10:00
self.file = ifcopenshell.file(schema=self.schema)
2020-04-09 02:42:03 +02:00
self.ifc_parser.parse(selected_objects)
2019-09-10 17:08:45 +10:00
self.create_units()
2019-11-15 15:44:04 +11:00
self.create_people()
self.create_organisations()
self.create_origin()
self.create_owner_history()
self.set_header()
self.create_rep_context()
self.create_project()
self.create_library_information()
self.create_document_information()
self.create_document_references()
self.create_classifications()
self.create_classification_references()
self.create_constraints()
self.create_psets()
self.create_libraries()
self.create_map_conversion()
self.create_representations()
2019-09-09 15:11:07 +10:00
self.create_materials()
2019-09-01 17:23:48 +10:00
self.create_type_products()
self.create_spatial_structure_elements(self.ifc_parser.spatial_structure_elements_tree)
self.create_groups()
self.create_qtos()
2019-08-31 17:37:53 +10:00
self.create_products()
self.create_styled_items()
self.create_presentation_layer_assignments()
self.relate_definitions_to_contexts()
self.relate_objects_to_objects()
self.relate_elements_to_spatial_structures()
self.relate_nested_elements_to_hosted_elements()
2019-09-01 18:40:20 +10:00
self.relate_objects_to_types()
self.relate_objects_to_qtos()
2019-09-08 19:02:00 +10:00
self.relate_objects_to_psets()
2019-11-18 05:46:52 +01:00
self.relate_objects_to_opening_elements()
self.relate_opening_elements_to_fillings()
2020-02-02 20:28:51 +11:00
self.relate_objects_to_projection_elements()
2019-09-09 15:11:07 +10:00
self.relate_objects_to_materials()
2020-01-14 16:11:11 +11:00
for set_type in ['constituent', 'layer', 'profile']:
self.relate_objects_to_material_sets(set_type)
self.relate_spaces_to_boundary_elements()
self.relate_to_documents(self.ifc_parser.rel_associates_document_object)
self.relate_to_documents(self.ifc_parser.rel_associates_document_type)
self.relate_to_classifications(self.ifc_parser.rel_associates_classification_object)
self.relate_to_classifications(self.ifc_parser.rel_associates_classification_type)
self.relate_to_constraints(self.ifc_parser.rel_associates_constraint_object)
self.relate_structural_members_to_connections()
self.relate_objects_to_groups()
self.write_ifc_file()
2019-08-29 23:11:12 +10:00
def create_origin(self):
self.origin = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint((0., 0., 0.)),
self.file.createIfcDirection((0., 0., 1.)),
self.file.createIfcDirection((1., 0., 0.)))
2020-01-07 16:12:11 +11:00
def set_header(self):
# TODO: add all metadata, pending bug #747
self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
self.file.wrapped_data.header.file_name.time_stamp = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
self.file.wrapped_data.header.file_name.preprocessor_version = 'IfcOpenShell {}'.format(ifcopenshell.version)
self.file.wrapped_data.header.file_name.originating_system = '{} {}'.format(
self.get_application_name(), self.get_application_version())
if self.owner_history:
if self.schema == 'IFC2X3':
self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id
else:
self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Identification
else:
self.file.wrapped_data.header.file_name.authorization = 'Nobody'
def get_application_name(self):
return 'BlenderBIM'
def get_application_version(self):
return '.'.join([str(x) for x in [addon.bl_info.get('version', (-1,-1,-1)) for addon in addon_utils.modules() if addon.bl_info['name'] == 'BlenderBIM'][0]])
def get_application_organisation(self):
self.application_organisation = self.file.create_entity('IfcOrganization', **{
"Name": "IfcOpenShell",
"Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.",
"Roles": [self.file.create_entity('IfcActorRole', **{
"Role": "USERDEFINED",
"UserDefinedRole": "CONTRIBUTOR"
})],
"Addresses": [
self.file.create_entity('IfcTelecomAddress', **{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "WEBPAGE",
"Description": "The main webpage of the software collection.",
"WWWHomePageURL": "https://ifcopenshell.org"
}),
self.file.create_entity('IfcTelecomAddress', **{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "WEBPAGE",
"Description": "The BlenderBIM Add-on webpage of the software collection.",
"WWWHomePageURL": "https://blenderbim.org"
}),
self.file.create_entity('IfcTelecomAddress', **{
"Purpose": "USERDEFINED",
"UserDefinedPurpose": "REPOSITORY",
"Description": "The source code repository of the software collection.",
"WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git"
})
]
})
return self.application_organisation
def create_owner_history(self):
person = None
organisation = None
for person in self.ifc_parser.people:
if self.schema == 'IFC2X3' and person['ifc'].Id == bpy.context.scene.BIMProperties.person:
break
elif person['ifc'].Identification == bpy.context.scene.BIMProperties.person:
break
for organisation in self.ifc_parser.organisations:
if organisation['ifc'].Name == bpy.context.scene.BIMProperties.organisation:
break
if not person or not organisation:
self.owner_history = None
return
person_and_organisation = self.file.create_entity('IfcPersonAndOrganization', **{
'ThePerson': person['ifc'],
'TheOrganization': organisation['ifc'],
'Roles': None # TODO
})
developer_organisation = self.get_application_organisation()
application = self.file.create_entity('IfcApplication', **{
'ApplicationDeveloper': developer_organisation,
'Version': self.get_application_version(),
'ApplicationFullName': self.get_application_name(),
'ApplicationIdentifier': self.get_application_name()
})
self.owner_history = self.file.create_entity('IfcOwnerHistory', **{
'OwningUser': person_and_organisation,
'OwningApplication': application,
'State': 'READWRITE',
'ChangeAction': 'NOCHANGE',
'LastModifiedDate': int(time.time()),
'LastModifyingUser': person_and_organisation,
'LastModifyingApplication': application,
'CreationDate': int(time.time()) # illegal, but better than nothing ...
})
2019-09-10 17:08:45 +10:00
def create_units(self):
2019-11-18 05:46:52 +01:00
for unit_type, data in self.ifc_parser.units.items():
2019-09-10 17:08:45 +10:00
if data['is_metric']:
2019-11-18 05:46:52 +01:00
data['ifc'] = self.create_metric_unit(unit_type, data)
2019-09-10 17:08:45 +10:00
else:
2019-11-18 05:46:52 +01:00
data['ifc'] = self.create_imperial_unit(unit_type, data)
2019-09-10 17:08:45 +10:00
self.file.createIfcUnitAssignment([u['ifc'] for u in self.ifc_parser.units.values()])
2019-11-18 05:46:52 +01:00
def create_metric_unit(self, unit_type, data):
2019-11-07 16:40:57 +11:00
type_prefix = ''
2019-11-18 05:46:52 +01:00
if unit_type == 'area':
2019-11-07 16:40:57 +11:00
type_prefix = 'SQUARE_'
2019-11-18 05:46:52 +01:00
elif unit_type == 'volume':
2019-11-07 16:40:57 +11:00
type_prefix = 'CUBIC_'
2019-11-18 05:46:52 +01:00
return self.file.createIfcSIUnit(
None,
'{}UNIT'.format(unit_type.upper()),
SIUnitHelper.get_prefix(data['raw']),
type_prefix + SIUnitHelper.get_unit_name(data['raw'])
)
def create_imperial_unit(self, unit_type, data):
if unit_type == 'length':
2019-11-07 16:40:57 +11:00
dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0)
name_prefix = ''
2019-11-18 05:46:52 +01:00
elif unit_type == 'area':
2019-11-07 16:40:57 +11:00
dimensional_exponents = self.file.createIfcDimensionalExponents(2, 0, 0, 0, 0, 0, 0)
name_prefix = 'square'
2019-11-18 05:46:52 +01:00
elif unit_type == 'volume':
2019-11-07 16:40:57 +11:00
dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0)
name_prefix = 'cubic'
2019-11-18 05:46:52 +01:00
si_unit = self.file.createIfcSIUnit(
None,
'{}UNIT'.format(unit_type.upper()),
None,
'{}METRE'.format(name_prefix.upper() + '_' if name_prefix else '')
)
2019-11-07 16:40:57 +11:00
if data['raw'] == 'INCHES':
name = '{}inch'.format(name_prefix + ' ' if name_prefix else '')
elif data['raw'] == 'FEET':
name = '{}foot'.format(name_prefix + ' ' if name_prefix else '')
2019-11-18 05:46:52 +01:00
value_component = self.file.create_entity(
'IfcReal',
**{'wrappedValue': SIUnitHelper.si_conversions[name]}
)
2019-11-07 16:40:57 +11:00
conversion_factor = self.file.createIfcMeasureWithUnit(value_component, si_unit)
return self.file.createIfcConversionBasedUnit(
2019-11-18 05:46:52 +01:00
dimensional_exponents,
'{}UNIT'.format(unit_type.upper()),
name,
conversion_factor
)
2019-08-29 23:11:12 +10:00
2019-11-15 15:44:04 +11:00
def create_people(self):
for person in self.ifc_parser.people:
if person['roles']:
person['attributes']['Roles'] = self.create_roles(person['roles'])
if person['addresses']:
person['attributes']['Addresses'] = self.create_addresses(person['addresses'])
if self.schema == 'IFC2X3' and 'Identification' in person['attributes']:
person['attributes']['Id'] = person['attributes']['Identification']
del person['attributes']['Identification']
person['ifc'] = self.file.create_entity('IfcPerson', **person['attributes'])
2019-11-15 15:44:04 +11:00
def create_organisations(self):
for organisation in self.ifc_parser.organisations:
if organisation['roles']:
organisation['attributes']['Roles'] = self.create_roles(organisation['roles'])
if organisation['addresses']:
organisation['attributes']['Addresses'] = self.create_addresses(organisation['addresses'])
organisation['ifc'] = self.file.create_entity('IfcOrganization', **organisation['attributes'])
2019-11-15 15:44:04 +11:00
def create_roles(self, roles):
results = []
for role in roles:
results.append(self.file.create_entity('IfcActorRole', **role['attributes']))
2019-11-15 15:44:04 +11:00
return results
def create_addresses(self, addresses):
results = []
for address in addresses:
if self.schema == 'IFC2X3' and 'MessagingIDs' in address['attributes']:
del address['attributes']['MessagingIDs']
results.append(self.file.create_entity('IfcPostalAddress' if
address['is_postal'] else 'IfcTelecomAddress', **address['attributes']))
2019-11-15 15:44:04 +11:00
return results
def create_library_information(self):
information = self.ifc_parser.library_information
if not information:
return
information['attributes']['Publisher'] = self.owner_history.OwningUser
information['ifc'] = self.file.create_entity('IfcLibraryInformation',
**information['attributes'])
self.file.createIfcRelAssociatesLibrary(
ifcopenshell.guid.new(),
self.owner_history,
information['attributes']['Name'],
information['attributes']['Description'],
[self.ifc_parser.project['ifc']],
information['ifc'])
def create_document_information(self):
for information in self.ifc_parser.document_information.values():
information['ifc'] = self.file.create_entity(
'IfcDocumentInformation', **information['attributes'])
def create_document_references(self):
for reference in self.ifc_parser.document_references.values():
if reference['referenced_document'] \
and reference['referenced_document'] in self.ifc_parser.document_information:
reference['attributes']['ReferencedDocument'] = self.ifc_parser.document_information[reference['referenced_document']]['ifc']
reference['ifc'] = self.file.create_entity(
'IfcDocumentReference', **reference['attributes'])
self.file.createIfcRelAssociatesDocument(
ifcopenshell.guid.new(), None, None, None,
[self.ifc_parser.project['ifc']], reference['ifc'])
def create_classifications(self):
for classification in self.ifc_parser.classifications.values():
classification['ifc'] = self.file.add(classification['raw_element'])
self.file.createIfcRelAssociatesClassification(
ifcopenshell.guid.new(), None, None, None,
[self.ifc_parser.project['ifc']], classification['ifc'])
def create_classification_references(self):
for reference in self.ifc_parser.classification_references.values():
reference['ifc'] = self.file.add(reference['raw_element'])
def create_constraints(self):
for constraint in self.ifc_parser.constraints.values():
constraint['ifc'] = self.file.create_entity(
'IfcObjective', **constraint['attributes'])
def create_psets(self):
2019-09-08 19:02:00 +10:00
for pset in self.ifc_parser.psets.values():
properties = self.create_pset_properties(pset)
if not properties:
continue
pset['attributes'].update({
'GlobalId': ifcopenshell.guid.new(),
'OwnerHistory': self.owner_history,
'HasProperties': properties
2019-11-17 13:25:41 +01:00
})
pset['ifc'] = self.file.create_entity('IfcPropertySet', **pset['attributes'])
def create_material_psets(self, material):
for pset_dir in material['raw'].BIMMaterialProperties.psets:
for name, properties in self.ifc_parser.material_psets[pset_dir.name].items():
self.file.create_entity('IfcMaterialProperties', **{
'Name': name,
'Description': pset_dir.name,
'Properties': self.create_pset_properties(properties),
'Material': material['ifc']
})
def create_qto_properties(self, qto):
if qto['attributes']['Name'] in schema.ifc.qtos:
return self.create_templated_qto_properties(qto)
2020-05-14 21:37:33 +10:00
return self.create_custom_qto_properties(qto)
def create_pset_properties(self, pset):
if pset['attributes']['Name'] in schema.ifc.psets:
2019-10-19 14:45:07 +11:00
return self.create_templated_pset_properties(pset)
return self.create_custom_pset_properties(pset)
def create_custom_pset_properties(self, pset):
properties = []
for key, value in pset['raw'].items():
properties.append(
self.file.create_entity('IfcPropertySingleValue', **{
'Name': key,
'NominalValue': self.file.create_entity('IfcLabel', value)
2019-11-17 13:25:41 +01:00
}))
2019-10-19 14:45:07 +11:00
return properties
2020-05-14 21:37:33 +10:00
def create_custom_qto_properties(self, qto):
properties = []
for key, value in qto['raw'].items():
if 'Area' in key:
quantity_type = 'Area'
elif 'Volume' in key:
quantity_type = 'Volume'
else:
quantity_type = 'Length'
properties.append(
self.file.create_entity(f'IfcQuantity{quantity_type}', **{
'Name': key,
f'{quantity_type}Value': float(value)
}))
return properties
2019-10-19 14:45:07 +11:00
def create_templated_pset_properties(self, pset):
properties = []
templates = schema.ifc.psets[pset['attributes']['Name']]['HasPropertyTemplates']
for name, data in templates.items():
if name not in pset['raw']:
continue
if data.TemplateType == 'P_SINGLEVALUE':
if data.PrimaryMeasureType:
value_type = data.PrimaryMeasureType
else:
# The IFC spec is missing some, so we provide a fallback
value_type = 'IfcLabel'
nominal_value = self.file.create_entity(
value_type,
self.cast_to_base_type(value_type, pset['raw'][name]))
properties.append(
self.file.create_entity('IfcPropertySingleValue', **{
'Name': name,
'NominalValue': nominal_value
2019-11-17 13:25:41 +01:00
}))
2019-10-07 22:14:52 +11:00
invalid_pset_keys = [k for k in pset['raw'].keys() if k not in templates.keys()]
if invalid_pset_keys:
self.ifc_export_settings.logger.error(
'One or more properties were invalid in the pset {}: {}'.format(
2019-10-07 22:14:52 +11:00
pset['attributes']['Name'],
invalid_pset_keys))
return properties
def create_templated_qto_properties(self, qto):
properties = []
templates = schema.ifc.qtos[qto['attributes']['Name']]['HasPropertyTemplates']
for name, data in templates.items():
if name not in qto['raw']:
continue
if data.TemplateType[0:2] == 'Q_':
value_basename = data.TemplateType[2:].title()
value_name = f'{value_basename}Value'
class_name = f'IfcQuantity{value_basename}'
properties.append(
self.file.create_entity(class_name, **{
'Name': name,
value_name: float(qto['raw'][name])
}))
invalid_qto_keys = [k for k in qto['raw'].keys() if k not in templates.keys()]
if invalid_qto_keys:
self.ifc_export_settings.logger.error(
'One or more properties were invalid in the qto {}/{}: {}'.format(
qto['attributes']['Name'],
qto['attributes']['Description'],
invalid_qto_keys))
return properties
2019-11-18 05:46:52 +01:00
def cast_to_base_type(self, var_type, value):
if var_type not in schema.ifc.type_map:
return value
elif schema.ifc.type_map[var_type] == 'float':
return float(value)
elif schema.ifc.type_map[var_type] == 'integer':
return int(value)
elif schema.ifc.type_map[var_type] == 'bool':
return True if value.lower() in ['1', 't', 'true', 'yes', 'y', 'uh-huh'] else False
return str(value)
def create_rep_context(self):
self.ifc_rep_context = {}
for context in self.ifc_export_settings.context_tree:
if context['name'] == 'Model':
self.ifc_rep_context['Model'] = {
'ifc': self.file.createIfcGeometricRepresentationContext(
None, 'Model', 3, 1.0E-05, self.origin)}
elif context['name'] == 'Plan':
self.ifc_rep_context['Plan'] = {
'ifc': self.file.createIfcGeometricRepresentationContext(
None, 'Plan', 2, 1.0E-05, self.origin)}
for subcontext in context['subcontexts']:
self.ifc_rep_context[context['name']][subcontext['name']] = {}
for target_view in subcontext['target_views']:
self.ifc_rep_context[context['name']][subcontext['name']][target_view] = {
'ifc': self.file.createIfcGeometricRepresentationSubContext(
subcontext['name'], context['name'], None, None, None, None,
self.ifc_rep_context[context['name']]['ifc'], None, target_view, None)}
2019-08-29 23:11:12 +10:00
def create_project(self):
self.ifc_parser.project['attributes'].update({
'RepresentationContexts': [c['ifc'] for c in self.ifc_rep_context.values()],
'UnitsInContext': self.file.by_type("IfcUnitAssignment")[0]
2019-11-17 13:25:41 +01:00
})
self.ifc_parser.project['ifc'] = self.file.create_entity(
self.ifc_parser.project['class'], **self.ifc_parser.project['attributes'])
def create_libraries(self):
for library in self.ifc_parser.libraries:
library['ifc'] = self.file.create_entity(library['class'], **library['attributes'])
libraries = [l['ifc'] for l in self.ifc_parser.libraries]
if libraries:
self.file.createIfcRelDeclares(
ifcopenshell.guid.new(), self.owner_history,
None, None,
self.ifc_parser.project['ifc'], libraries)
2019-08-29 23:11:12 +10:00
def create_map_conversion(self):
if not self.ifc_parser.map_conversion:
return
self.create_target_crs()
# TODO should this be hardcoded?
self.ifc_parser.map_conversion['attributes']['SourceCRS'] = self.ifc_rep_context['Model']['ifc']
self.ifc_parser.map_conversion['attributes']['TargetCRS'] = self.ifc_parser.target_crs['ifc']
2019-11-18 05:46:52 +01:00
self.ifc_parser.map_conversion['ifc'] = self.file.create_entity(
'IfcMapConversion',
**self.ifc_parser.map_conversion['attributes']
)
def create_target_crs(self):
for key, value in self.ifc_parser.target_crs['attributes'].items():
if not self.ifc_parser.target_crs['attributes'][key]:
self.ifc_parser.target_crs['attributes'][key] = None
if self.ifc_parser.target_crs['attributes']['MapUnit']:
self.ifc_parser.target_crs['attributes']['MapUnit'] = self.file.createIfcSIUnit(
None,
'LENGTHUNIT',
SIUnitHelper.get_prefix(self.ifc_parser.target_crs['attributes']['MapUnit']),
SIUnitHelper.get_unit_name(self.ifc_parser.target_crs['attributes']['MapUnit'])
)
self.ifc_parser.target_crs['ifc'] = self.file.create_entity(
2019-11-18 05:46:52 +01:00
'IfcProjectedCRS',
**self.ifc_parser.target_crs['attributes']
)
2019-09-01 17:23:48 +10:00
def create_type_products(self):
for product in self.ifc_parser.type_products:
self.cast_attributes(product['class'], product['attributes'])
product['attributes'].update({
'OwnerHistory': self.owner_history, # TODO: unhardcode
'RepresentationMaps': self.get_product_shape(product)
})
# TODO: re-implement psets, relationships, door/window properties
2019-09-29 22:12:51 +10:00
2019-09-01 17:23:48 +10:00
try:
product['ifc'] = self.file.create_entity(product['class'], **product['attributes'])
except RuntimeError as e:
2019-11-17 13:25:41 +01:00
self.ifc_export_settings.logger.error(
'The type product "{}/{}" could not be created: {}'.format(
2019-11-18 03:45:24 +01:00
product['class'],
product['attributes']['Name'],
e.args)
2019-11-18 03:45:24 +01:00
)
2019-09-01 17:23:48 +10:00
2019-09-29 22:29:06 +10:00
def add_predefined_attributes_to_type_product(self, product, attributes):
self.create_predefined_attributes(attributes)
product['attributes'].setdefault('HasPropertySets', [])
for attribute in attributes:
product['attributes']['HasPropertySets'].append(attribute['ifc'])
def create_predefined_attributes(self, attributes):
2019-09-29 22:12:51 +10:00
for attribute in attributes:
2019-11-18 03:45:24 +01:00
attribute['ifc'] = self.file.create_entity(
attribute['pset_name'],
2019-11-18 05:46:52 +01:00
**{k: float(v) if v.replace('.', '', 1).isdigit() else v
for k, v in attribute['raw'].items()}
2019-11-18 03:45:24 +01:00
)
2019-09-29 22:12:51 +10:00
def relate_definitions_to_contexts(self):
for library in self.ifc_parser.libraries:
self.file.createIfcRelDeclares(
ifcopenshell.guid.new(), self.owner_history, None, None,
library['ifc'],
[self.ifc_parser.type_products[t]['ifc'] for t in library['rel_declares_type_products']])
def relate_objects_to_objects(self):
for relating_object, related_objects_reference in self.ifc_parser.rel_aggregates.items():
relating_object = self.ifc_parser.products[relating_object]
if related_objects_reference not in self.ifc_parser.aggregates:
continue
2019-11-18 05:46:52 +01:00
related_objects = [self.ifc_parser.products[o]['ifc']
for o in self.ifc_parser.aggregates[related_objects_reference]]
self.file.createIfcRelAggregates(
ifcopenshell.guid.new(), self.owner_history, relating_object['attributes']['Name'], None,
relating_object['ifc'], related_objects)
def create_spatial_structure_elements(self, element_tree, relating_object=None):
if relating_object == None:
relating_object = self.ifc_parser.project['ifc']
placement_rel_to = None
else:
placement_rel_to = relating_object.ObjectPlacement
related_objects = []
for node in element_tree:
element = self.ifc_parser.spatial_structure_elements[node['reference']]
placement = self.file.createIfcLocalPlacement(
placement_rel_to, self.get_relative_placement(element, placement_rel_to))
self.cast_attributes(element['class'], element['attributes'])
element['attributes'].update({
2019-11-17 13:25:41 +01:00
'OwnerHistory': self.owner_history, # TODO: unhardcode
'ObjectPlacement': placement,
2020-01-28 22:06:58 +11:00
'Representation': self.get_product_shape(element)
})
element['ifc'] = self.file.create_entity(element['class'], **element['attributes'])
related_objects.append(element['ifc'])
self.create_spatial_structure_elements(node['children'], element['ifc'])
if related_objects:
self.file.createIfcRelAggregates(
ifcopenshell.guid.new(),
self.owner_history, None, None, relating_object, related_objects)
def get_relative_placement(self, element, placement_rel_to):
if placement_rel_to:
relating_object_matrix = self.get_local_placement(placement_rel_to)
relating_object_matrix[0][3] = self.convert_unit_to_si(relating_object_matrix[0][3])
relating_object_matrix[1][3] = self.convert_unit_to_si(relating_object_matrix[1][3])
relating_object_matrix[2][3] = self.convert_unit_to_si(relating_object_matrix[2][3])
else:
relating_object_matrix = Matrix()
z = Vector(element['up_axis'])
x = Vector(element['forward_axis'])
o = Vector(element['location'])
object_matrix = self.a2p(o, z, x)
relative_placement_matrix = relating_object_matrix.inverted() @ object_matrix
return self.create_ifc_axis_2_placement_3d(
relative_placement_matrix.translation,
self.get_axis(relative_placement_matrix, 2),
self.get_axis(relative_placement_matrix, 0))
def get_axis(self, matrix, axis):
return matrix.col[axis].to_3d().normalized()
def get_local_placement(self, plc):
if plc.PlacementRelTo is None:
parent = Matrix()
else:
parent = self.get_local_placement(plc.PlacementRelTo)
return parent @ self.get_axis2placement(plc.RelativePlacement)
def a2p(self, o, z, x):
y = z.cross(x)
r = Matrix((x, y, z, o))
r.resize_4x4()
r.transpose()
return r
def get_axis2placement(self, plc):
z = Vector(plc.Axis.DirectionRatios if plc.Axis else (0,0,1))
x = Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1,0,0))
o = plc.Location.Coordinates
return self.a2p(o,z,x)
def create_groups(self):
for group in self.ifc_parser.groups:
group['ifc'] = self.file.create_entity(group['class'], **group['attributes'])
self.file.createIfcRelDeclares(ifcopenshell.guid.new(),
self.owner_history, None, None, self.ifc_parser.project['ifc'], [group['ifc']])
def create_styled_items(self):
for styled_item in self.ifc_parser.styled_items:
product = self.ifc_parser.products[
self.ifc_parser.get_product_index_from_raw_name(
styled_item['related_product_name'])]
material_slots = {}
if product['ifc'].Representation:
for representation in product['ifc'].Representation.Representations:
for mapped_item in representation.Items:
items = mapped_item[0].MappedRepresentation.Items
for i, item in enumerate(items):
if i >= len(product['raw'].material_slots):
i = 0
material_slots[product['raw'].material_slots[i].name] = item
for styled_item_name, representation_item in material_slots.items():
if styled_item_name == styled_item['attributes']['Name']:
styled_item['ifc'] = self.create_styled_item(styled_item, representation_item)
def create_styled_item(self, item, representation_item=None):
styles = []
styles.append(self.create_surface_style_rendering(item))
if item['raw'].BIMMaterialProperties.is_external:
styles.append(self.file.create_entity('IfcExternallyDefinedSurfaceStyle',
2019-11-18 03:45:24 +01:00
**self.get_material_external_definition(item['raw'])))
# Name is filled out because Revit treats this incorrectly as the material name
surface_style = self.file.createIfcSurfaceStyle(item['attributes']['Name'], 'BOTH', styles)
if self.schema == 'IFC2X3' or self.ifc_export_settings.should_use_presentation_style_assignment:
surface_style = self.file.createIfcPresentationStyleAssignment([surface_style])
return self.file.createIfcStyledItem(representation_item, [surface_style], item['attributes']['Name'])
def create_presentation_layer_assignments(self):
for name, assigned_items in self.ifc_parser.presentation_layer_assignments.items():
self.file.createIfcPresentationLayerAssignment(
name, None, [i['ifc'].MappedRepresentation for i in assigned_items], None)
2019-09-09 15:11:07 +10:00
def create_materials(self):
for material in self.ifc_parser.materials.values():
styled_item = self.create_styled_item(material)
2019-09-09 15:11:07 +10:00
styled_representation = self.file.createIfcStyledRepresentation(
self.ifc_rep_context['Model']['Body']['MODEL_VIEW']['ifc'], None, None, [styled_item])
if self.schema == 'IFC2X3':
material['ifc'] = self.file.createIfcMaterial(material['raw'].name)
else:
material['ifc'] = self.file.createIfcMaterial(material['raw'].name, None, None)
self.create_material_psets(material)
2019-09-09 15:11:07 +10:00
self.file.createIfcMaterialDefinitionRepresentation(
material['raw'].name, None, [styled_representation], material['ifc'])
if material['material_type'] == 'IfcMaterial':
continue
material_type = material['material_type'][0:-3]
self.cast_attributes(material_type, material['attributes'])
material['attributes']['Material'] = material['ifc']
if material_type == 'IfcMaterialProfile':
material['attributes']['Profile'] = self.create_material_profile(material)
material['part_ifc'] = self.file.create_entity(material_type,
**material['attributes'])
def create_material_profile(self, material):
ifc_class = material['raw'].BIMMaterialProperties.profile_def
attributes = {a.name: a.string_value for a in material['raw'].BIMMaterialProperties.profile_attributes}
self.cast_attributes(ifc_class, attributes)
return self.file.create_entity(ifc_class, **attributes)
2020-01-14 15:38:22 +11:00
def cast_attributes(self, ifc_class, attributes):
for key, value in attributes.items():
edge_case_attribute = self.cast_edge_case(ifc_class, key, value)
if edge_case_attribute:
attributes[key] = edge_case_attribute
continue
complex_attribute = self.cast_complex_attribute(ifc_class, key, value)
if complex_attribute:
attributes[key] = complex_attribute
continue
2020-01-14 15:38:22 +11:00
var_type = self.get_product_attribute_type(ifc_class, key)
if var_type is None:
continue
attributes[key] = self.cast_to_base_type(var_type, value)
2019-09-09 15:11:07 +10:00
def cast_edge_case(self, ifc_class, key, value):
if key == 'RefLatitude' or key == 'RefLongitude':
return self.dd2dms(value)
# TODO: migrate to ifcopenshell.util
def dd2dms(self, dd):
dd = float(dd)
sign = 1 if dd >= 0 else -1
dd = abs(dd)
minutes, seconds = divmod(dd*3600, 60)
degrees, minutes = divmod(minutes, 60)
if dd < 0:
degrees = -degrees
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign)
def create_surface_style_rendering(self, styled_item):
surface_colour = self.create_colour_rgb(styled_item['raw'].diffuse_color)
2020-08-09 17:13:01 +10:00
rendering_attributes = {
'SurfaceColour': surface_colour,
'Transparency': (styled_item['raw'].diffuse_color[3] - 1) * -1,
'ReflectanceMethod': 'NOTDEFINED'
}
rendering_attributes.update(self.get_rendering_attributes(styled_item['raw']))
return self.file.create_entity('IfcSurfaceStyleRendering', **rendering_attributes)
def get_rendering_attributes(self, material):
2020-08-09 17:13:01 +10:00
if not material.use_nodes \
or not hasattr(material.node_tree, 'nodes') \
2019-11-17 13:25:41 +01:00
or 'Principled BSDF' not in material.node_tree.nodes:
return {}
bsdf = material.node_tree.nodes['Principled BSDF']
return {
'Transparency': (bsdf.inputs['Alpha'].default_value - 1) * -1,
'DiffuseColour': self.create_colour_rgb(bsdf.inputs['Base Color'].default_value)
2019-11-17 13:25:41 +01:00
}
def get_material_external_definition(self, material):
return {
'Location': material.BIMMaterialProperties.location,
'Identification': material.BIMMaterialProperties.identification if material.BIMMaterialProperties.identification else material.name,
'Name': material.BIMMaterialProperties.name if material.BIMMaterialProperties.name else material.name
}
def create_colour_rgb(self, colour):
return self.file.createIfcColourRgb(None, colour[0], colour[1], colour[2])
def create_representations(self):
for representation in self.ifc_parser.representations.values():
2019-09-07 08:05:42 +10:00
representation['ifc'] = self.create_representation(representation)
def create_products(self):
for product in self.ifc_parser.products:
self.create_product(product)
def create_qtos(self):
# TODO: re-introduce calculated quantities
for qto in self.ifc_parser.qtos.values():
properties = self.create_qto_properties(qto)
if not properties:
continue
qto['attributes'].update({
'GlobalId': ifcopenshell.guid.new(),
'OwnerHistory': self.owner_history,
'Quantities': properties
2019-11-17 13:25:41 +01:00
})
qto['ifc'] = self.file.create_entity('IfcElementQuantity', **qto['attributes'])
def create_product(self, product):
if product['relating_structure'] is not None:
2019-11-17 13:25:41 +01:00
placement_rel_to = self.ifc_parser.spatial_structure_elements[product['relating_structure']][
'ifc'].ObjectPlacement
elif product['relating_host'] is not None:
placement_rel_to = self.ifc_parser.products[product['relating_host']]['ifc'].ObjectPlacement
else:
placement_rel_to = None
2019-11-18 03:45:24 +01:00
if product['has_scale']:
# Omission of the relative placement here is not as per implementer agreements
placement = self.file.createIfcLocalPlacement(None, self.origin)
2019-11-18 03:45:24 +01:00
else:
placement = self.file.createIfcLocalPlacement(placement_rel_to,
self.get_relative_placement(product, placement_rel_to))
self.cast_attributes(product['class'], product['attributes'])
product['attributes'].update({
2019-11-17 13:25:41 +01:00
'OwnerHistory': self.owner_history, # TODO: unhardcode
'ObjectPlacement': placement,
'Representation': self.get_product_shape(product)
2019-11-17 13:25:41 +01:00
})
if product['has_boundary_condition']:
ifc_class = product['boundary_condition_class']
attributes = product['boundary_condition_attributes']
for key, value in attributes.items():
if value == 'True' or value == 'False':
attributes[key] = bool(value)
else:
attributes[key] = float(value)
self.cast_attributes(ifc_class, attributes)
boundary_condition = self.file.create_entity(ifc_class, **attributes)
product['attributes']['AppliedCondition'] = boundary_condition
try:
product['ifc'] = self.file.create_entity(product['class'], **product['attributes'])
except RuntimeError as e:
2019-11-17 13:25:41 +01:00
self.ifc_export_settings.logger.error(
2019-11-18 05:46:52 +01:00
'The product "{}/{}" could not be created: {}'.format(
product['class'],
product['attributes']['Name'],
e.args)
)
def get_product_attribute_type(self, product_class, attribute_name):
element_schema = schema.ifc.elements[product_class]
for a in element_schema['attributes']:
if a['name'] == attribute_name:
return a['type']
if element_schema['parent'] in schema.ifc.elements:
return self.get_product_attribute_type(element_schema['parent'], attribute_name)
def cast_complex_attribute(self, product_class, attribute_name, attribute_value):
element_schema = schema.ifc.elements[product_class]
for a in element_schema['complex_attributes']:
if a['name'] == attribute_name:
if not a['is_select']:
return a['type']
for select_type in a['select_types']:
try:
return self.file.create_entity(select_type, attribute_value)
except:
pass
def get_product_shape(self, product):
try:
2020-01-28 22:06:58 +11:00
representations = self.get_product_shape_representations(product)
if representations:
return self.file.createIfcProductDefinitionShape(None, None, representations)
except:
2020-01-28 22:06:58 +11:00
pass
return None
2019-11-18 03:45:24 +01:00
def get_product_shape_representations(self, product):
results = []
for representation_name in product['representations']:
2019-11-24 18:12:33 +11:00
results.append(self.get_product_mapped_geometry(product, representation_name))
2019-11-18 03:45:24 +01:00
return results
2019-11-24 18:12:33 +11:00
def get_product_mapped_geometry(self, product, representation_name):
mapping_source = self.ifc_parser.representations[representation_name]['ifc']
shape_representation = mapping_source.MappedRepresentation
if product['has_scale']:
if not product['has_mirror']:
product['scale'] = Vector((
abs(product['scale'].x),
abs(product['scale'].y),
abs(product['scale'].z)
))
2019-11-24 18:12:33 +11:00
mapping_target = self.file.createIfcCartesianTransformationOperator3DnonUniform(
self.create_direction(product['forward_axis']),
self.create_direction(product['right_axis']),
self.create_cartesian_point(
product['location'].x,
product['location'].y,
product['location'].z
),
product['scale'].x,
2019-11-24 18:12:33 +11:00
self.create_direction(product['up_axis']),
product['scale'].y,
product['scale'].z)
2019-11-24 18:12:33 +11:00
else:
mapping_target = self.file.createIfcCartesianTransformationOperator3D(
self.create_direction(Vector((1, 0, 0))),
self.create_direction(Vector((0, 1, 0))),
self.create_cartesian_point(0, 0, 0),
1, self.create_direction(Vector((0, 0, 1))))
2019-11-18 03:45:24 +01:00
mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target)
return self.file.createIfcShapeRepresentation(
shape_representation.ContextOfItems,
shape_representation.RepresentationIdentifier,
2020-01-12 20:47:06 +11:00
'MappedRepresentation',
2019-11-18 03:45:24 +01:00
[mapped_item])
def create_ifc_axis_2_placement_3d(self, point, up, forward):
2019-09-01 17:23:48 +10:00
return self.file.createIfcAxis2Placement3D(
2019-11-07 16:40:57 +11:00
self.create_cartesian_point(point.x, point.y, point.z),
self.file.createIfcDirection((up.x, up.y, up.z)),
self.file.createIfcDirection((forward.x, forward.y, forward.z)))
2019-09-01 17:23:48 +10:00
2019-09-07 08:05:42 +10:00
def create_representation(self, representation):
self.ifc_vertices = []
self.ifc_edges = []
if representation['context'] == 'Model':
return self.create_model_representation(representation)
elif representation['context'] == 'Plan':
return self.create_plan_representation(representation)
elif representation['context'] == 'NotDefined':
return self.create_variable_representation(representation)
def create_model_representation(self, representation):
if representation['subcontext'] == 'Annotation':
return self.file.createIfcRepresentationMap(self.origin,
self.create_geometric_set_representation(representation))
elif representation['subcontext'] == 'Axis':
return self.file.createIfcRepresentationMap(
self.origin, self.create_curve3d_representation(representation))
elif representation['subcontext'] == 'Body':
return self.create_variable_representation(representation)
elif representation['subcontext'] == 'Box':
2019-11-24 18:12:33 +11:00
return self.file.createIfcRepresentationMap(self.origin,
self.create_box_representation(representation))
elif representation['subcontext'] == 'Clearance':
return self.create_variable_representation(representation)
elif representation['subcontext'] == 'CoG':
2019-11-24 18:12:33 +11:00
return self.file.createIfcRepresentationMap(self.origin,
self.create_cog_representation(representation))
elif representation['subcontext'] == 'FootPrint':
return self.create_variable_representation(representation)
elif representation['subcontext'] == 'Reference':
if representation['target_view'] == 'GRAPH_VIEW':
return self.file.createIfcRepresentationMap(
self.origin, self.create_structural_reference_representation(representation))
elif representation['subcontext'] == 'Profile':
return self.file.createIfcRepresentationMap(
self.origin, self.create_curve3d_representation(representation))
elif representation['subcontext'] == 'SurveyPoints':
2019-11-24 18:12:33 +11:00
return self.file.createIfcRepresentationMap(self.origin,
self.create_geometric_curve_set_representation(representation))
def create_plan_representation(self, representation):
if representation['subcontext'] == 'Annotation':
if representation['is_text']:
shape_representation = self.create_text_representation(representation)
else:
shape_representation = self.create_geometric_curve_set_representation(representation, is_2d=True)
shape_representation.RepresentationType = 'Annotation2D'
return self.file.createIfcRepresentationMap(self.origin, shape_representation)
elif representation['subcontext'] == 'Axis':
return self.file.createIfcRepresentationMap(
self.origin, self.create_curve2d_representation(representation))
elif representation['subcontext'] == 'Body':
pass
elif representation['subcontext'] == 'Box':
pass
elif representation['subcontext'] == 'Clearance':
pass
elif representation['subcontext'] == 'CoG':
pass
elif representation['subcontext'] == 'FootPrint':
if representation['target_view'] in ['PLAN_VIEW', 'REFLECTED_PLAN_VIEW']:
return self.file.createIfcRepresentationMap(self.origin,
self.create_geometric_curve_set_representation(representation, is_2d=True))
elif representation['subcontext'] == 'Reference':
pass
elif representation['subcontext'] == 'Profile':
pass
elif representation['subcontext'] == 'SurveyPoints':
pass
def create_variable_representation(self, representation):
if representation['is_wireframe']:
return self.file.createIfcRepresentationMap(self.origin,
self.create_wireframe_representation(representation))
elif representation['is_curve']:
return self.file.createIfcRepresentationMap(self.origin,
self.create_curve_representation(representation))
elif representation['is_native']:
return self.file.createIfcRepresentationMap(self.origin,
self.create_native_representation(representation))
elif representation['is_swept_solid']:
2019-11-24 18:12:33 +11:00
return self.file.createIfcRepresentationMap(self.origin,
self.create_swept_solid_representation(representation))
2020-01-12 20:47:06 +11:00
elif representation['is_point_cloud']:
return self.file.createIfcRepresentationMap(self.origin,
self.create_point_cloud_representation(representation))
2019-11-24 18:12:33 +11:00
return self.file.createIfcRepresentationMap(self.origin,
self.create_solid_representation(representation))
def create_box_representation(self, representation):
2019-11-17 13:25:41 +01:00
obj = representation['raw_object']
bounding_box = self.file.createIfcBoundingBox(
self.create_cartesian_point(
2019-11-18 03:45:24 +01:00
obj.bound_box[0][0],
obj.bound_box[0][1],
2019-11-18 05:46:52 +01:00
obj.bound_box[0][2]
),
2019-11-18 03:45:24 +01:00
obj.dimensions[0],
obj.dimensions[1],
2019-11-18 05:46:52 +01:00
obj.dimensions[2]
)
return self.file.createIfcShapeRepresentation(
2019-11-17 13:25:41 +01:00
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'BoundingBox', [bounding_box])
def create_cog_representation(self, representation):
mesh = representation['raw']
cog = self.create_cartesian_point(
mesh.vertices[0].co.x, mesh.vertices[0].co.y, mesh.vertices[0].co.z)
return self.file.createIfcShapeRepresentation(
2019-11-17 13:25:41 +01:00
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
2019-11-18 05:46:52 +01:00
representation['subcontext'],
'BoundingBox',
[cog])
def create_text_representation(self, representation):
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'],
'Annotation2D',
[self.create_text(representation['raw'])])
def create_wireframe_representation(self, representation):
2019-09-07 08:05:42 +10:00
return self.file.createIfcShapeRepresentation(
2019-11-17 13:25:41 +01:00
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
2019-11-18 05:46:52 +01:00
representation['subcontext'],
'Curve',
self.create_curves(representation['raw']))
def create_geometric_set_representation(self, representation, is_2d=False):
geometric_curve_set = self.file.createIfcGeometricSet(self.create_curves(representation['raw'], is_2d=is_2d))
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'GeometricSet', [geometric_curve_set])
def create_geometric_curve_set_representation(self, representation, is_2d=False):
geometric_curve_set = self.file.createIfcGeometricCurveSet(self.create_curves(representation['raw'], is_2d=is_2d))
return self.file.createIfcShapeRepresentation(
2019-11-17 13:25:41 +01:00
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'GeometricCurveSet', [geometric_curve_set])
2019-11-06 17:37:51 +11:00
# https://medium.com/@behreajj/scripting-curves-in-blender-with-python-c487097efd13
# https://blender.stackexchange.com/questions/30597/python-up-vector-math-for-curve
def bezier_tangent(self, pt0=Vector(), pt1=Vector(), pt2=Vector(), pt3=Vector(), step=0.5):
# Return early if step is out of bounds [0, 1].
if step <= 0.0:
return pt1 - pt0
if step >= 1.0:
return pt3 - pt2
# Find coefficients.
u = 1.0 - step
ut6 = u * step * 6.0
tsq3 = step * step * 3.0
usq3 = u * u * 3.0
# Find tangent and return.
return (pt1 - pt0) * usq3 + (pt2 - pt1) * ut6 + (pt3 - pt2) * tsq3
def create_curve3d_representation(self, representation):
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'Curve3D',
self.create_curves(representation['raw']))
def create_curve2d_representation(self, representation):
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'Curve2D',
self.create_curves(representation['raw'], is_2d=True))
def create_structural_reference_representation(self, representation):
if representation['raw_object'].type == 'EMPTY':
return self.file.createIfcTopologyRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'Vertex',
[self.create_vertex_point(Vector((0, 0, 0)))])
return self.file.createIfcTopologyRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'Edge',
[self.create_edge(representation['raw'])])
def create_curve_representation(self, representation):
if representation['raw'].bevel_object:
swept_area_solids = self.create_extruded_area_solids(representation)
else:
swept_area_solids = self.create_swept_disk_solids(representation)
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'AdvancedSweptSolid',
swept_area_solids)
def create_swept_disk_solids(self, representation):
results = []
radius = self.convert_si_to_unit(representation['raw'].bevel_depth)
start_param = representation['raw'].bevel_factor_start
end_param = representation['raw'].bevel_factor_end
directrixes = self.create_curves(representation['raw'])
for directrix in directrixes:
results.append(self.file.createIfcSweptDiskSolid(
directrix, radius, None, start_param, end_param))
return results
def create_extruded_area_solids(self, representation):
# TODO: support unclosed surfaces
swept_area = self.file.createIfcArbitraryClosedProfileDef('AREA', None,
self.create_curves(representation['raw'].bevel_object.data)[0])
if (representation['raw'].bevel_object.scale - Vector((1, 1, 1))).length > 0.01:
self.scale_ifc_representation(swept_area, representation['raw'].bevel_object.scale)
swept_area_solids = []
for spline in representation['raw'].splines:
2020-01-30 15:33:01 +11:00
points = self.get_spline_points(spline)
if not points:
continue
# Intuitively, the direction below is reversed, but apparently
# Blender likes to extrude down (opposite of IFC) natively.
direction = (points[0].co - points[1].co).xyz
unit_direction = direction.normalized()
2019-11-06 17:37:51 +11:00
# This can be used in the future when dealing with non vector curves
2020-01-30 15:33:01 +11:00
# curr_point = points[0]
# next_point = points[1]
2019-11-17 13:25:41 +01:00
# j_percent = 0
# direction = self.bezier_tangent(
2019-11-06 17:37:51 +11:00
# pt0=curr_point.co,
# pt1=curr_point.handle_right,
# pt2=next_point.handle_left,
# pt3=next_point.co,
# step=j_percent)
2020-01-30 15:33:01 +11:00
tilt_matrix = Matrix.Rotation(points[0].tilt, 4, 'Z')
2019-11-06 17:37:51 +11:00
x_axis = unit_direction.to_track_quat('-Y', 'Z') @ Vector((1, 0, 0)) @ tilt_matrix
position = self.create_ifc_axis_2_placement_3d(
2020-01-30 15:33:01 +11:00
points[1].co, unit_direction, x_axis)
swept_area_solids.append(self.file.createIfcExtrudedAreaSolid(
2019-11-06 17:37:51 +11:00
swept_area, position,
self.file.createIfcDirection((0., 0., 1.)),
2019-11-07 16:40:57 +11:00
self.convert_si_to_unit(direction.length)))
# TODO: support other types of swept areas
2019-11-17 13:25:41 +01:00
# swept_area_solid = self.file.createIfcFixedReferenceSweptAreaSolid(
# swept_area, self.origin, # self.create_curves(representation['raw'])[0],
# 0., 1., self.file.createIfcDirection((0.0, -1.0, 0.0)))
return swept_area_solids
def scale_ifc_representation(self, rep, scale):
for element in self.file.traverse(rep):
if not element.is_a('IfcCartesianPoint'):
continue
element.Coordinates = tuple(Vector(element.Coordinates) @ Matrix((
(scale[0], 0, 0),
(0, scale[1], 0),
(0, 0, scale[2]))))
def create_vertex_point(self, point):
return self.file.createIfcVertexPoint(
self.create_cartesian_point(point.x, point.y, point.z))
2020-01-30 15:33:01 +11:00
def get_spline_points(self, spline):
return spline.bezier_points if spline.bezier_points else spline.points
def create_edge(self, curve):
if hasattr(curve, 'splines'):
points = self.get_spline_points(curve.splines[0])
else:
points = curve.vertices
if not points:
return
return self.file.createIfcEdge(
self.create_vertex_point(points[0].co),
self.create_vertex_point(points[1].co))
def create_text(self, text):
if text.align_y in ['TOP_BASELINE', 'BOTTOM_BASELINE', 'BOTTOM']:
y = 'bottom'
elif text.align_y == 'CENTER':
y = 'middle'
elif text.align_y == 'TOP':
y = 'top'
if text.align_x == 'LEFT':
x = 'left'
elif text.align_x == 'CENTER':
x = 'middle'
elif text.align_x == 'RIGHT':
x = 'right'
# TODO: Planar extent right now is wrong ...
return self.file.createIfcTextLiteralWithExtent(text.body, self.origin,
'RIGHT', self.file.createIfcPlanarExtent(1000, 1000), f'{y}-{x}')
def create_curves(self, curve, is_2d=False):
if isinstance(curve, bpy.types.Mesh):
return self.create_curves_from_mesh(curve, is_2d=is_2d)
elif isinstance(curve, bpy.types.Curve):
return self.create_curves_from_curve(curve, is_2d=is_2d)
def create_curves_from_mesh(self, mesh, is_2d=False):
self.create_vertices(mesh.vertices, is_2d=is_2d)
edges = list(mesh.edges)
loop_vertices = []
loops = []
# Not a fast algorithm, but easy
while edges:
for i, edge in enumerate(edges):
if edge.vertices[0] in loop_vertices \
and edge.vertices[1] in loop_vertices:
del edges[i]
loop_vertex_indices = self.get_loop_from_edges(edges)
loop_vertices.extend(loop_vertex_indices)
loops.append(self.file.createIfcPolyline([
self.ifc_vertices[i] for i in loop_vertex_indices]))
return loops
def create_curves_from_curve(self, curve, is_2d=False):
results = []
for spline in curve.splines:
# TODO: support interpolated curves, not just polylines
points = []
for point in spline.bezier_points:
if is_2d:
points.append(self.create_cartesian_point(
point.co.x, point.co.y))
else:
points.append(self.create_cartesian_point(
point.co.x, point.co.y, point.co.z))
for point in spline.points:
if is_2d:
points.append(self.create_cartesian_point(
point.co.x, point.co.y))
else:
points.append(self.create_cartesian_point(
point.co.x, point.co.y, point.co.z))
if spline.use_cyclic_u:
points.append(points[0])
results.append(self.file.createIfcPolyline(points))
return results
def create_native_representation(self, representation):
obj = representation['raw_object']
items = {}
for index, vg in enumerate(obj.vertex_groups):
components = vg.name.split('/')
key = components[1]
if components[0] == 'Item':
items[key] = {
'name': components[2],
'subitems': {}
}
elif components[0] == 'Subitem':
items[key]['subitems'][components[2]] = self.get_vertices_in_vertex_group(obj, index)
ifc_items = []
for item in items.values():
if item['name'] == 'IfcExtrudedAreaSolid':
ifc_items.append(self.create_native_extruded_area_solid(obj, item))
2020-06-20 21:23:15 +10:00
elif item['name'] == 'IfcFacetedBrep':
# TODO: check if we allow representation item type mixing
return self.create_solid_representation(representation)
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'SweptSolid', ifc_items)
def get_vertices_in_vertex_group(self, obj, vg_index):
return [v.index for v in obj.data.vertices if vg_index in [g.group for g in v.groups]]
def create_native_extruded_area_solid(self, obj, item):
extrusion_edge = self.get_edges_in_v_indices(obj, item['subitems']['ExtrudedDirection'])[0]
if 'IfcArbitraryClosedProfileDef' in item['subitems']:
outer_curve_loop = self.get_loop_from_v_indices(obj, item['subitems']['IfcArbitraryClosedProfileDef'])
curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop)
outer_curve = self.create_polyline_from_loop(obj, outer_curve_loop, curve_ucs)
curve = self.file.createIfcArbitraryClosedProfileDef('AREA', None, outer_curve)
elif 'IfcRectangleProfileDef' in item['subitems']:
outer_curve_loop = self.get_loop_from_v_indices(obj, item['subitems']['IfcRectangleProfileDef'])
curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop)
xdim = self.convert_si_to_unit(
(obj.data.vertices[outer_curve_loop[0]].co - obj.data.vertices[outer_curve_loop[1]].co).length)
ydim = self.convert_si_to_unit(
(obj.data.vertices[outer_curve_loop[1]].co - obj.data.vertices[outer_curve_loop[2]].co).length)
curve = self.file.createIfcRectangleProfileDef('AREA', None, None, xdim, ydim)
position = self.create_ifc_axis_2_placement_3d(
curve_ucs['center'], curve_ucs['z_axis'], curve_ucs['x_axis'])
direction = self.get_extrusion_direction(obj, outer_curve_loop, extrusion_edge, curve_ucs)
unit_direction = direction.normalized()
return self.file.createIfcExtrudedAreaSolid(
curve, position, self.file.createIfcDirection((
unit_direction.x, unit_direction.y, unit_direction.z)),
self.convert_si_to_unit(direction.length))
def create_swept_solid_representation(self, representation):
# TODO: deprecate this in favour of native representations
2019-11-17 13:25:41 +01:00
obj = representation['raw_object']
mesh = representation['raw']
items = []
for swept_solid in mesh.BIMMeshProperties.swept_solids:
2019-11-17 13:25:41 +01:00
extrusion_edge = self.get_edges_in_v_indices(obj, json.loads(swept_solid.extrusion))[0]
inner_curves = []
if swept_solid.inner_curves:
for indices in json.loads(swept_solid.inner_curves):
2019-11-17 13:25:41 +01:00
loop = self.get_loop_from_v_indices(obj, indices)
curve_ucs = self.get_curve_profile_coordinate_system(obj, loop)
inner_curves.append(
2019-11-17 13:25:41 +01:00
self.create_polyline_from_loop(obj, loop, curve_ucs))
2019-11-17 13:25:41 +01:00
outer_curve_loop = self.get_loop_from_v_indices(obj, json.loads(swept_solid.outer_curve))
curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop)
outer_curve = self.create_polyline_from_loop(obj, outer_curve_loop, curve_ucs)
if inner_curves:
curve = self.file.createIfcArbitraryProfileDefWithVoids('AREA', None,
2019-11-17 13:25:41 +01:00
outer_curve, inner_curves)
else:
curve = self.file.createIfcArbitraryClosedProfileDef('AREA', None, outer_curve)
2019-11-17 13:25:41 +01:00
direction = self.get_extrusion_direction(obj, outer_curve_loop, extrusion_edge, curve_ucs)
unit_direction = direction.normalized()
position = self.create_ifc_axis_2_placement_3d(
curve_ucs['center'], curve_ucs['z_axis'], curve_ucs['x_axis'])
items.append(self.file.createIfcExtrudedAreaSolid(
curve, position, self.file.createIfcDirection((
unit_direction.x, unit_direction.y, unit_direction.z)),
self.convert_si_to_unit(direction.length)))
return self.file.createIfcShapeRepresentation(
2019-11-17 13:25:41 +01:00
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'SweptSolid', items)
def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge):
if extrusion_edge.vertices[0] in profile_points:
return (extrusion_edge.vertices[0], extrusion_edge.vertices[1])
return (extrusion_edge.vertices[1], extrusion_edge.vertices[0])
2019-11-17 13:25:41 +01:00
def get_curve_profile_coordinate_system(self, obj, loop):
profile_face = bpy.data.meshes.new('profile_face')
profile_verts = [(
2019-11-17 13:25:41 +01:00
obj.data.vertices[p].co.x,
obj.data.vertices[p].co.y,
obj.data.vertices[p].co.z
) for p in loop]
profile_faces = [tuple(range(0, len(profile_verts)))]
profile_face.from_pydata(profile_verts, [], profile_faces)
center = profile_face.polygons[0].center
if (obj.data.vertices[loop[1]].co - obj.data.vertices[loop[0]].co).length < 0.01:
x_axis = (obj.data.vertices[loop[0]].co - center).normalized()
else:
x_axis = (obj.data.vertices[loop[1]].co - obj.data.vertices[loop[0]].co).normalized()
z_axis = profile_face.polygons[0].normal.normalized()
y_axis = z_axis.cross(x_axis).normalized()
matrix = Matrix((x_axis, y_axis, z_axis))
matrix.normalize()
return {
'center': center,
'x_axis': x_axis,
'y_axis': y_axis,
'z_axis': z_axis,
2019-11-14 07:51:47 +11:00
'matrix': matrix.to_4x4() @ Matrix.Translation(-center)
2019-11-17 13:25:41 +01:00
}
2019-11-17 13:25:41 +01:00
def create_polyline_from_loop(self, obj, loop, curve_ucs):
points = []
for point in loop:
2019-11-17 13:25:41 +01:00
transformed_point = curve_ucs['matrix'] @ obj.data.vertices[point].co
points.append(self.create_cartesian_point(
transformed_point.x, transformed_point.y))
points.append(points[0])
return self.file.createIfcPolyline(points)
2019-11-17 13:25:41 +01:00
def get_extrusion_direction(self, obj, outer_curve_loop, extrusion_edge, curve_ucs):
start, end = self.get_start_and_end_of_extrusion(outer_curve_loop, extrusion_edge)
2019-11-17 13:25:41 +01:00
return curve_ucs['matrix'] @ (
curve_ucs['center'] + (obj.data.vertices[end].co - obj.data.vertices[start].co))
2019-11-17 13:25:41 +01:00
def get_loop_from_v_indices(self, obj, indices):
edges = self.get_edges_in_v_indices(obj, indices)
loop = self.get_loop_from_edges(edges)
loop.pop(-1)
return loop
2019-11-17 13:25:41 +01:00
def get_edges_in_v_indices(self, obj, indices):
2019-11-18 05:46:52 +01:00
return [e for e in obj.data.edges
if (e.vertices[0] in indices and e.vertices[1] in indices)]
def get_loop_from_edges(self, edges):
while edges:
2019-11-17 13:25:41 +01:00
currentEdge = edges.pop()
startVert = currentEdge.vertices[0]
endVert = currentEdge.vertices[1]
polyLine = [startVert, endVert]
ok = 1
while ok:
2019-11-17 13:25:41 +01:00
ok = 0
i = len(edges)
while i:
2019-11-17 13:25:41 +01:00
i -= 1
ed = edges[i]
if ed.vertices[0] == endVert:
polyLine.append(ed.vertices[1])
2019-11-17 13:25:41 +01:00
endVert = polyLine[-1]
ok = 1
del edges[i]
elif ed.vertices[1] == endVert:
polyLine.append(ed.vertices[0])
2019-11-17 13:25:41 +01:00
endVert = polyLine[-1]
ok = 1
del edges[i]
elif ed.vertices[0] == startVert:
polyLine.insert(0, ed.vertices[1])
2019-11-17 13:25:41 +01:00
startVert = polyLine[0]
ok = 1
del edges[i]
elif ed.vertices[1] == startVert:
polyLine.insert(0, ed.vertices[0])
2019-11-17 13:25:41 +01:00
startVert = polyLine[0]
ok = 1
del edges[i]
return polyLine
2020-01-12 20:47:06 +11:00
def create_point_cloud_representation(self, representation):
import space_view3d_point_cloud_visualizer as pcv
if representation['raw'].uuid not in pcv.PCVManager.cache:
return
return self.file.createIfcShapeRepresentation(
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'PointCloud',
[self.file.createIfcCartesianPointList3D(
pcv.PCVManager.cache[representation['raw'].uuid]['points'].tolist())])
def create_solid_representation(self, representation):
mesh = representation['raw']
if not representation['is_parametric']:
mesh = representation['raw_object'].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
2019-09-07 08:05:42 +10:00
self.create_vertices(mesh.vertices)
2020-06-25 11:07:21 +02:00
n_slots = max(1, len(representation['raw_object'].material_slots))
ifc_raw_items = [None] * n_slots
for i, value in enumerate(ifc_raw_items):
ifc_raw_items[i] = []
for polygon in mesh.polygons:
2020-06-25 11:07:21 +02:00
ifc_raw_items[polygon.material_index % n_slots].append(self.file.createIfcFace([
self.file.createIfcFaceOuterBound(
2019-09-07 08:05:42 +10:00
self.file.createIfcPolyLoop([self.ifc_vertices[vertice] for vertice in polygon.vertices]),
True)]))
# TODO: May not actually be a closed shell, but who checks anyway?
items = [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(i)) for i in ifc_raw_items if i]
2019-09-01 17:23:48 +10:00
return self.file.createIfcShapeRepresentation(
2019-11-17 13:25:41 +01:00
self.ifc_rep_context[representation['context']][representation['subcontext']][
representation['target_view']]['ifc'],
representation['subcontext'], 'Brep', items)
2019-09-07 08:05:42 +10:00
def create_vertices(self, vertices, is_2d=False):
if is_2d:
for v in vertices:
co = self.convert_si_to_unit(v.co)
self.ifc_vertices.append(self.file.createIfcCartesianPoint((co[0], co[1])))
else:
self.ifc_vertices.extend(
[self.file.createIfcCartesianPoint(self.convert_si_to_unit(v.co)) for v in vertices]
)
def create_cartesian_point(self, x, y, z=None):
2019-11-07 16:40:57 +11:00
x = self.convert_si_to_unit(x)
y = self.convert_si_to_unit(y)
if z is None:
return self.file.createIfcCartesianPoint((x, y))
2019-11-07 16:40:57 +11:00
z = self.convert_si_to_unit(z)
return self.file.createIfcCartesianPoint((x, y, z))
2019-11-17 13:25:41 +01:00
2019-11-18 03:45:24 +01:00
def create_direction(self, vector):
return self.file.createIfcDirection((vector.x, vector.y, vector.z))
2019-11-18 05:46:52 +01:00
def relate_objects_to_opening_elements(self):
for relating_building_element, related_opening_elements in self.ifc_parser.rel_voids_elements.items():
for related_opening_element in related_opening_elements:
2019-11-17 02:52:20 +01:00
self.file.createIfcRelVoidsElement(
ifcopenshell.guid.new(), self.owner_history, None, None,
2019-11-18 07:51:09 +01:00
self.ifc_parser.products[relating_building_element]['ifc'],
self.ifc_parser.products[related_opening_element]['ifc']
2019-11-17 02:52:20 +01:00
)
2019-11-17 13:12:22 +01:00
2019-11-18 05:46:52 +01:00
def relate_opening_elements_to_fillings(self):
for relating_opening_element, related_building_elements in self.ifc_parser.rel_fills_elements.items():
for related_building_element in related_building_elements:
2019-11-17 02:52:20 +01:00
self.file.createIfcRelFillsElement(
ifcopenshell.guid.new(), self.owner_history, None, None,
2019-11-18 07:51:09 +01:00
self.ifc_parser.products[relating_opening_element]['ifc'],
self.ifc_parser.products[related_building_element]['ifc']
2019-11-17 02:52:20 +01:00
)
2020-02-02 20:28:51 +11:00
def relate_objects_to_projection_elements(self):
for relating_building_element, related_projection_elements in self.ifc_parser.rel_projects_elements.items():
for related_projection_element in related_projection_elements:
self.file.createIfcRelProjectsElement(
ifcopenshell.guid.new(), self.owner_history, None, None,
self.ifc_parser.products[relating_building_element]['ifc'],
self.ifc_parser.products[related_projection_element]['ifc']
)
def relate_elements_to_spatial_structures(self):
for relating_structure, related_elements in self.ifc_parser.rel_contained_in_spatial_structure.items():
self.file.createIfcRelContainedInSpatialStructure(
ifcopenshell.guid.new(), self.owner_history, None, None,
2019-11-17 13:25:41 +01:00
[self.ifc_parser.products[e]['ifc'] for e in related_elements],
self.ifc_parser.spatial_structure_elements[relating_structure]['ifc'])
def relate_nested_elements_to_hosted_elements(self):
for relating_object, related_objects in self.ifc_parser.rel_nests.items():
self.file.createIfcRelNests(
ifcopenshell.guid.new(), self.owner_history, None, None,
self.ifc_parser.products[relating_object]['ifc'],
2019-11-17 13:25:41 +01:00
[o['ifc'] for o in related_objects])
2019-09-01 18:40:20 +10:00
def relate_objects_to_types(self):
for relating_type, related_objects in self.ifc_parser.rel_defines_by_type.items():
self.file.createIfcRelDefinesByType(
ifcopenshell.guid.new(), self.owner_history, None, None,
2019-11-17 13:25:41 +01:00
[self.ifc_parser.products[o]['ifc'] for o in related_objects],
2019-09-01 18:40:20 +10:00
self.ifc_parser.type_products[relating_type]['ifc'])
def relate_objects_to_qtos(self):
for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_qto.items():
self.file.createIfcRelDefinesByProperties(
ifcopenshell.guid.new(), self.owner_history, None, None,
[o['ifc'] for o in related_objects],
2019-09-09 15:11:32 +10:00
self.ifc_parser.qtos[relating_property_key]['ifc'])
2019-09-08 19:02:00 +10:00
def relate_objects_to_psets(self):
for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_pset.items():
if self.ifc_parser.psets[relating_property_key]['ifc']:
self.file.createIfcRelDefinesByProperties(
ifcopenshell.guid.new(), self.owner_history, None, None,
[o['ifc'] for o in related_objects],
self.ifc_parser.psets[relating_property_key]['ifc'])
2019-09-08 19:02:00 +10:00
2019-09-09 15:11:07 +10:00
def relate_objects_to_materials(self):
if not self.ifc_export_settings.has_representations:
return
2019-09-09 15:11:07 +10:00
for relating_material_key, related_objects in self.ifc_parser.rel_associates_material.items():
self.file.createIfcRelAssociatesMaterial(
2019-09-09 15:11:07 +10:00
ifcopenshell.guid.new(), self.owner_history, None, None,
[o['ifc'] for o in related_objects],
self.ifc_parser.materials[relating_material_key]['ifc'])
2020-01-14 16:11:11 +11:00
def relate_objects_to_material_sets(self, set_type):
if not self.ifc_export_settings.has_representations:
return
2020-01-14 16:11:11 +11:00
for product_index, related_materials in getattr(self.ifc_parser, f'rel_associates_material_{set_type}_set').items():
material_set = self.file.create_entity(f'IfcMaterial{set_type.capitalize()}Set', **{
f'Material{set_type.capitalize()}s': [self.ifc_parser.materials[m]['part_ifc'] for m in related_materials]
2019-11-17 13:25:41 +01:00
})
self.file.createIfcRelAssociatesMaterial(
ifcopenshell.guid.new(), self.owner_history, None, None,
[self.ifc_parser.products[product_index]['ifc']],
2020-01-14 16:11:11 +11:00
material_set)
def relate_spaces_to_boundary_elements(self):
for relating_space_index, relationships, in self.ifc_parser.rel_space_boundaries.items():
for relationship in relationships:
relationship['attributes']['GlobalId'] = ifcopenshell.guid.new()
relationship['attributes']['RelatedBuildingElement'] = self.ifc_parser.products[
self.ifc_parser.get_product_index_from_raw_name(
relationship['related_building_element_raw_name'])]['ifc']
relationship['attributes']['RelatingSpace'] = self.ifc_parser.products[relating_space_index]['ifc']
relationship['attributes']['ConnectionGeometry'] = self.create_connection_geometry(
self.ifc_parser.products[relating_space_index],
relationship['connection_geometry_face_index'])
self.file.create_entity(relationship['class'], **relationship['attributes'])
def create_connection_geometry(self, product, face_index):
mesh = product['raw'].data
polygon = mesh.polygons[int(face_index)]
vertex_on_polygon = mesh.vertices[polygon.vertices[0]].co
center = polygon.center
normal = polygon.normal
forward = center - vertex_on_polygon
return self.file.createIfcFaceSurface([self.file.createIfcFaceOuterBound(
self.file.createIfcPolyLoop([
self.create_cartesian_point(
mesh.vertices[vertice].co.x,
mesh.vertices[vertice].co.y,
mesh.vertices[vertice].co.z)
for vertice in polygon.vertices]),
True)],
self.file.createIfcPlane(self.file.createIfcAxis2Placement3D(
2019-11-07 16:40:57 +11:00
self.create_cartesian_point(center.x, center.y, center.z),
self.file.createIfcDirection((normal.x, normal.y, normal.z)),
self.file.createIfcDirection((forward.x, forward.y, forward.z)))),
True)
def relate_to_documents(self, relationships):
for relating_document_key, related_objects in relationships.items():
self.file.createIfcRelAssociatesDocument(
ifcopenshell.guid.new(), self.owner_history, None, None,
[o['ifc'] for o in related_objects],
self.ifc_parser.document_references[relating_document_key]['ifc'])
def relate_to_classifications(self, relationships):
for relating_key, related_objects in relationships.items():
self.file.createIfcRelAssociatesClassification(
ifcopenshell.guid.new(), self.owner_history, None, None,
[o['ifc'] for o in related_objects],
self.ifc_parser.classification_references[relating_key]['ifc'])
def relate_to_constraints(self, relationships):
for relating_key, related_objects in relationships.items():
self.file.createIfcRelAssociatesConstraint(
ifcopenshell.guid.new(), self.owner_history, None, None,
[o['ifc'] for o in related_objects], None,
self.ifc_parser.constraints[relating_key]['ifc'])
def relate_structural_members_to_connections(self):
for relating_member, relating_connection in self.ifc_parser.rel_connects_structural_member.items():
self.file.create_entity('IfcRelConnectsStructuralMember', **{
'RelatingStructuralMember': self.ifc_parser.products[relating_member]['ifc'],
'RelatedStructuralConnection': self.ifc_parser.products[relating_connection]['ifc']
})
def relate_objects_to_groups(self):
for relating_group, related_objects in self.ifc_parser.rel_assigns_to_group.items():
self.file.createIfcRelAssignsToGroup(
ifcopenshell.guid.new(), self.owner_history, None, None,
[self.ifc_parser.products[o]['ifc'] for o in related_objects], None,
self.ifc_parser.groups[relating_group]['ifc'])
2019-11-07 16:40:57 +11:00
def convert_si_to_unit(self, co):
return co / self.ifc_parser.unit_scale
def convert_unit_to_si(self, co):
return co * self.ifc_parser.unit_scale
def write_ifc_file(self):
extension = self.ifc_export_settings.output_file.split('.')[-1]
if extension == 'ifczip':
with tempfile.TemporaryDirectory() as unzipped_path:
filename, ext = os.path.splitext(os.path.basename(self.ifc_export_settings.output_file))
tmp_name = '{}.ifc'.format(filename)
tmp_file = os.path.join(unzipped_path, tmp_name)
self.file.write(tmp_file)
with zipfile.ZipFile(self.ifc_export_settings.output_file,
mode='w', compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
zf.write(tmp_file)
elif extension == 'ifc':
self.file.write(self.ifc_export_settings.output_file)
2020-05-21 04:10:20 +02:00
elif extension == 'ifcjson':
ifc2json = IFC2JSON(self.file).convert()
with open(self.ifc_export_settings.output_file, 'w') as f:
f.write(json.dumps(ifc2json, indent = 4))
2019-11-17 13:25:41 +01:00
class IfcExportSettings:
def __init__(self):
self.logger = None
2019-10-05 21:15:53 +10:00
self.schema_dir = None
self.data_dir = None
2019-09-26 21:55:30 +10:00
self.output_file = None
self.has_representations = True
self.has_quantities = True
self.contexts = ['Model', 'Plan']
self.subcontexts = ['Annotation', 'Axis', 'Box', 'FootPrint', 'Reference', 'Body', 'Clearance', 'CoG', 'Profile', 'SurveyPoints']
self.generated_subcontexts = ['Box']
self.schema = 'IFC4'
2019-11-17 13:25:41 +01:00
self.target_views = ['GRAPH_VIEW', 'SKETCH_VIEW', 'MODEL_VIEW', 'PLAN_VIEW', 'REFLECTED_PLAN_VIEW',
'SECTION_VIEW', 'ELEVATION_VIEW', 'USERDEFINED', 'NOTDEFINED']
self.should_use_presentation_style_assignment = False
self.should_guess_quantities = False
self.context_tree = []
2020-04-08 15:40:12 +02:00
@staticmethod
2020-04-08 15:40:12 +02:00
def factory(context, output_file, logger):
scene_bim = context.scene.BIMProperties
settings = IfcExportSettings()
settings.output_file = output_file
settings.logger = logger
settings.data_dir = scene_bim.data_dir
settings.schema_dir = scene_bim.schema_dir
settings.has_representations = scene_bim.export_has_representations
settings.schema = scene_bim.export_schema
2020-04-08 15:40:12 +02:00
settings.should_use_presentation_style_assignment = scene_bim.export_should_use_presentation_style_assignment
settings.should_guess_quantities = scene_bim.export_should_guess_quantities
2020-04-08 15:40:12 +02:00
settings.context_tree = []
for ifc_context in ['model', 'plan']:
if getattr(scene_bim, 'has_{}_context'.format(ifc_context)):
subcontexts = {}
for subcontext in getattr(scene_bim, '{}_subcontexts'.format(ifc_context)):
subcontexts.setdefault(subcontext.name, []).append(subcontext.target_view)
settings.context_tree.append({
'name': ifc_context.title(),
'subcontexts': [
{'name': key, 'target_views': value}
for key, value in subcontexts.items()
]
})
return settings