mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 14:31:39 +00:00
2235 lines
100 KiB
Python
2235 lines
100 KiB
Python
import bpy
|
|
import csv
|
|
import json
|
|
import time
|
|
import datetime
|
|
import os
|
|
from pathlib import Path
|
|
from mathutils import Vector, Matrix
|
|
from .helper import SIUnitHelper
|
|
from . import schema
|
|
import ifcopenshell
|
|
import addon_utils
|
|
|
|
class ArrayModifier:
|
|
count: int
|
|
offset: Vector
|
|
|
|
|
|
class QtoCalculator():
|
|
def get_units(self, o, vg_index):
|
|
return len([v for v in o.data.vertices if vg_index in [g.group for g in v.groups]])
|
|
|
|
def get_length(self, o, vg_index):
|
|
length = 0
|
|
edges = [e for e in o.data.edges if (
|
|
vg_index in [g.group for g in o.data.vertices[e.vertices[0]].groups] and
|
|
vg_index in [g.group for g in o.data.vertices[e.vertices[1]].groups]
|
|
)]
|
|
for e in edges:
|
|
length += self.get_edge_distance(o, e)
|
|
return length
|
|
|
|
def get_edge_distance(self, obj, edge):
|
|
return (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length
|
|
|
|
def get_area(self, o, vg_index):
|
|
area = 0
|
|
vertices_in_vg = [v.index for v in o.data.vertices if vg_index in [g.group for g in v.groups]]
|
|
for polygon in o.data.polygons:
|
|
if self.is_polygon_in_vg(polygon, vertices_in_vg):
|
|
area += polygon.area
|
|
return area
|
|
|
|
def is_polygon_in_vg(self, polygon, vertices_in_vg):
|
|
for v in polygon.vertices:
|
|
if v not in vertices_in_vg:
|
|
return False
|
|
return True
|
|
|
|
def get_volume(self, o, vg_index):
|
|
volume = 0
|
|
ob_mat = o.matrix_world
|
|
me = o.data
|
|
me.calc_loop_triangles()
|
|
for tf in me.loop_triangles:
|
|
tfv = tf.vertices
|
|
if len(tf.vertices) == 3:
|
|
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),
|
|
else:
|
|
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]), \
|
|
(me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]])
|
|
|
|
for tf_iter in tf_tris:
|
|
v1 = ob_mat @ tf_iter[0].co
|
|
v2 = ob_mat @ tf_iter[1].co
|
|
v3 = ob_mat @ tf_iter[2].co
|
|
|
|
volume += v1.dot(v2.cross(v3)) / 6.0
|
|
return volume
|
|
|
|
|
|
class IfcParser():
|
|
def __init__(self, ifc_export_settings):
|
|
self.data_dir = ifc_export_settings.data_dir
|
|
|
|
self.ifc_export_settings = ifc_export_settings
|
|
|
|
self.selected_products = []
|
|
|
|
self.product_index = 0
|
|
self.product_name_index_map = {}
|
|
|
|
self.units = {}
|
|
self.people = []
|
|
self.organisations = []
|
|
self.psets = {}
|
|
self.documents = {}
|
|
self.classifications = []
|
|
self.classification_references = {}
|
|
self.objectives = {}
|
|
self.qtos = {}
|
|
self.aggregates = {}
|
|
self.materials = {}
|
|
self.spatial_structure_elements = []
|
|
self.spatial_structure_elements_tree = []
|
|
self.rel_contained_in_spatial_structure = {}
|
|
self.rel_nests = {}
|
|
self.rel_space_boundaries = {}
|
|
self.rel_defines_by_type = {}
|
|
self.rel_defines_by_qto = {}
|
|
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 = {}
|
|
self.rel_associates_material = {}
|
|
self.rel_associates_material_layer_set = {}
|
|
self.rel_associates_material_constituent_set = {}
|
|
self.rel_associates_material_profile_set = {}
|
|
self.rel_associates_constraint_objective_object = {}
|
|
self.rel_associates_constraint_objective_type = {}
|
|
self.rel_aggregates = {}
|
|
self.rel_voids_elements = {}
|
|
self.rel_fills_elements = {}
|
|
self.representations = {}
|
|
self.type_products = []
|
|
self.door_attributes = {}
|
|
self.window_attributes = {}
|
|
self.project = {}
|
|
self.libraries = []
|
|
self.products = []
|
|
|
|
def parse(self):
|
|
self.units = self.get_units()
|
|
self.unit_scale = self.get_unit_scale()
|
|
self.people = self.get_people()
|
|
self.organisations = self.get_organisations()
|
|
self.convert_selected_objects_into_products(bpy.context.selected_objects)
|
|
self.psets = self.get_psets()
|
|
self.material_psets = self.get_material_psets()
|
|
self.documents = self.get_documents()
|
|
self.classifications = self.get_classifications()
|
|
self.classification_references = self.get_classification_references()
|
|
self.objectives = self.get_objectives()
|
|
self.load_representations()
|
|
self.materials = self.get_materials()
|
|
self.styled_items = self.get_styled_items()
|
|
self.qtos = self.get_qtos()
|
|
self.spatial_structure_elements = self.get_spatial_structure_elements()
|
|
|
|
self.collection_name_filter = []
|
|
|
|
self.project = self.get_project()
|
|
self.libraries = self.get_libraries()
|
|
self.door_attributes = self.get_door_attributes()
|
|
self.window_attributes = self.get_window_attributes()
|
|
self.type_products = self.get_type_products()
|
|
self.get_products()
|
|
self.resolve_boolean_modifiers()
|
|
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 = self.get_spatial_structure_elements_tree(
|
|
self.project['raw'].children, self.collection_name_filter)
|
|
|
|
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
|
|
},
|
|
'area': {
|
|
'ifc': None,
|
|
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
|
|
'raw': bpy.context.scene.unit_settings.length_unit
|
|
},
|
|
'volume': {
|
|
'ifc': None,
|
|
'is_metric': bpy.context.scene.unit_settings.system == 'METRIC',
|
|
'raw': bpy.context.scene.unit_settings.length_unit
|
|
}}
|
|
|
|
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
|
|
|
|
def get_object_attributes(self, obj):
|
|
attributes = {'Name': self.get_ifc_name(obj.name)}
|
|
if obj.BIMObjectProperties.attributes.find('GlobalId') == -1:
|
|
global_id = obj.BIMObjectProperties.attributes.add()
|
|
global_id.name = 'GlobalId'
|
|
global_id.string_value = ifcopenshell.guid.new()
|
|
attributes.update({a.name: a.string_value for a in obj.BIMObjectProperties.attributes})
|
|
return attributes
|
|
|
|
def get_products(self):
|
|
for product in self.selected_products:
|
|
obj = product['raw']
|
|
self.add_product(self.get_product(product))
|
|
self.resolve_modifiers(product)
|
|
|
|
def resolve_modifiers(self, product):
|
|
obj = product['raw']
|
|
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
|
|
)
|
|
}
|
|
)
|
|
)
|
|
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_boolean_modifiers(self):
|
|
for i, product in enumerate(self.products):
|
|
obj = product['raw']
|
|
for m in obj.modifiers:
|
|
if m.type == 'BOOLEAN' and m.object is not None:
|
|
void = self.get_product_index_from_raw_name(m.object.name)
|
|
if void is not None:
|
|
if i not in self.rel_voids_elements:
|
|
self.rel_voids_elements[i] = []
|
|
self.rel_voids_elements[i].append(void)
|
|
if m.object.parent:
|
|
fill = self.get_product_index_from_raw_name(m.object.parent.name)
|
|
if fill is not None:
|
|
if void not in self.rel_fills_elements:
|
|
self.rel_fills_elements[void] = []
|
|
self.rel_fills_elements[void].append(fill)
|
|
|
|
def get_axis(self, matrix, axis):
|
|
return matrix.col[axis].to_3d().normalized()
|
|
|
|
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
|
|
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
|
|
|
|
def get_product(self, selected_product, metadata_override={}, attribute_override={}):
|
|
obj = selected_product['raw']
|
|
product = {
|
|
'ifc': None,
|
|
'raw': obj,
|
|
'location': obj.matrix_world.translation,
|
|
'up_axis': self.get_axis(obj.matrix_world, 2),
|
|
'forward_axis': self.get_axis(obj.matrix_world, 0),
|
|
'right_axis': self.get_axis(obj.matrix_world, 1),
|
|
'has_scale': obj.scale != Vector((1, 1, 1)),
|
|
'has_mirror': False,
|
|
'array_offset': Vector((0, 0, 0)),
|
|
'scale': obj.scale,
|
|
'class': self.get_ifc_class(obj.name),
|
|
'relating_structure': None,
|
|
'relating_host': None,
|
|
'relating_qtos_key': None,
|
|
'representations': self.get_object_representation_names(obj),
|
|
'attributes': self.get_object_attributes(obj),
|
|
'has_boundary_condition': obj.BIMObjectProperties.has_boundary_condition,
|
|
'boundary_condition_class': None,
|
|
'boundary_condition_attributes': {}
|
|
}
|
|
product['attributes'].update(attribute_override)
|
|
product.update(metadata_override)
|
|
|
|
if obj.parent \
|
|
and self.is_a_type(self.get_ifc_class(obj.parent.name)):
|
|
reference = self.get_type_product_reference(obj.parent.name)
|
|
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}
|
|
|
|
for collection in product['raw'].users_collection:
|
|
self.parse_product_collection(product, collection)
|
|
|
|
if 'IfcRelNests' in obj.constraints:
|
|
parent_product_index = self.get_product_index_from_raw_name(
|
|
obj.constraints['IfcRelNests'].target.name)
|
|
self.rel_nests.setdefault(parent_product_index, []).append(product)
|
|
product['relating_host'] = parent_product_index
|
|
|
|
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]
|
|
}
|
|
})
|
|
|
|
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']
|
|
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)
|
|
product['right_axis'] = self.get_axis(inverted @ obj.matrix_world, 1)
|
|
self.aggregates.setdefault(relating_object.name, []).append(self.product_index)
|
|
|
|
if obj.name in self.qtos:
|
|
self.rel_defines_by_qto.setdefault(obj.name, []).append(product)
|
|
|
|
for pset in obj.BIMObjectProperties.psets:
|
|
self.rel_defines_by_pset.setdefault(
|
|
'{}/{}'.format(pset.name, pset.file), []).append(product)
|
|
|
|
for pset in obj.BIMObjectProperties.override_psets:
|
|
pset_key = '{}/{}'.format(pset.name, obj.name)
|
|
raw = {p.name: p.string_value for p in pset.properties if p.string_value}
|
|
if not raw:
|
|
continue
|
|
self.psets[pset_key] = {
|
|
'ifc': None,
|
|
'raw': raw,
|
|
'attributes': { 'Name': pset.name }
|
|
}
|
|
self.rel_defines_by_pset.setdefault(pset_key, []).append(product)
|
|
|
|
for document in obj.BIMObjectProperties.documents:
|
|
self.rel_associates_document_object.setdefault(
|
|
document.file, []).append(product)
|
|
|
|
for classification in obj.BIMObjectProperties.classifications:
|
|
self.rel_associates_classification_object.setdefault(
|
|
classification.identification, []).append(product)
|
|
|
|
for key in obj.keys():
|
|
if key[0:9] == 'Objective':
|
|
self.rel_associates_constraint_objective_object.setdefault(
|
|
obj[key], []).append(product)
|
|
|
|
for slot in obj.material_slots:
|
|
if slot.link == 'OBJECT':
|
|
continue
|
|
if obj.BIMObjectProperties.material_type == 'IfcMaterialLayerSet':
|
|
self.rel_associates_material_layer_set.setdefault(self.product_index, []).append(
|
|
slot.material.name)
|
|
elif obj.BIMObjectProperties.material_type == 'IfcMaterialConstituentSet':
|
|
self.rel_associates_material_constituent_set.setdefault(self.product_index, []).append(
|
|
slot.material.name)
|
|
elif obj.BIMObjectProperties.material_type == 'IfcMaterialProfileSet':
|
|
self.rel_associates_material_profile_set.setdefault(self.product_index, []).append(
|
|
slot.material.name)
|
|
else:
|
|
self.rel_associates_material.setdefault(slot.material.name, []).append(product)
|
|
|
|
return product
|
|
|
|
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):
|
|
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
|
|
self.collection_name_filter.append(collection.name)
|
|
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 convert_selected_objects_into_products(self, objects_to_sort, metadata=None):
|
|
if not metadata:
|
|
metadata = {}
|
|
for obj in objects_to_sort:
|
|
if obj.name[0:3] != 'Ifc':
|
|
continue
|
|
if not self.is_a_library(self.get_ifc_class(obj.users_collection[0].name)):
|
|
self.selected_products.append({'raw': obj, 'metadata': metadata})
|
|
if obj.instance_type == 'COLLECTION':
|
|
self.convert_selected_objects_into_products(
|
|
obj.instance_collection.objects,
|
|
{'rel_aggregates_relating_object': obj}
|
|
)
|
|
|
|
def get_psets(self):
|
|
psets = {}
|
|
for filename in Path(self.data_dir + 'pset/').glob('**/*.csv'):
|
|
with open(filename, 'r') as f:
|
|
name = filename.parts[-2]
|
|
description = filename.stem
|
|
psets['{}/{}'.format(name, description)] = {
|
|
'ifc': None,
|
|
'raw': {x[0]: x[1] for x in list(csv.reader(f))},
|
|
'attributes': {
|
|
'Name': name,
|
|
'Description': description}
|
|
}
|
|
return psets
|
|
|
|
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
|
|
|
|
def get_door_attributes(self):
|
|
return self.get_predefined_attributes('door')
|
|
|
|
def get_window_attributes(self):
|
|
return self.get_predefined_attributes('window')
|
|
|
|
def get_predefined_attributes(self, attr):
|
|
results = {}
|
|
for filename in Path(self.data_dir + attr + '/').glob('**/*.csv'):
|
|
with open(filename, 'r') as f:
|
|
type_name = filename.parts[-2]
|
|
pset_name = filename.stem
|
|
results.setdefault(type_name, []).append({
|
|
'ifc': None,
|
|
'raw': {x[0]: x[1] for x in list(csv.reader(f))},
|
|
'pset_name': pset_name.split('.')[0]
|
|
})
|
|
return results
|
|
|
|
def get_classifications(self):
|
|
results = []
|
|
class_path = self.data_dir + 'class/'
|
|
with open(class_path + 'classifications.csv', 'r') as f:
|
|
data = list(csv.reader(f))
|
|
keys = data.pop(0)
|
|
for row in data:
|
|
row[-1] = json.loads(row[-1])
|
|
results.append({
|
|
'ifc': None,
|
|
'raw': row,
|
|
'attributes': dict(zip(keys, row))
|
|
})
|
|
return results
|
|
|
|
def get_classification_references(self):
|
|
results = {}
|
|
class_path = self.data_dir + 'class/'
|
|
with open(class_path + 'references.csv', 'r') as f:
|
|
data = list(csv.reader(f))
|
|
keys = data.pop(0)
|
|
for row in data:
|
|
results[row[0]] = {
|
|
'ifc': None,
|
|
'raw': row,
|
|
'referenced_source': int(row.pop()),
|
|
'attributes': dict(zip(keys, row))
|
|
}
|
|
return results
|
|
|
|
def get_objectives(self):
|
|
results = {}
|
|
class_path = self.data_dir + 'constraint/'
|
|
with open(class_path + 'objectives.csv', 'r') as f:
|
|
data = list(csv.reader(f))
|
|
keys = data.pop(0)
|
|
for row in data:
|
|
results[row[0]] = {
|
|
'ifc': None,
|
|
'raw': row,
|
|
'attributes': dict(zip(keys, row))
|
|
}
|
|
return results
|
|
|
|
def get_people(self):
|
|
with open(self.data_dir + 'owner/person.json') as file:
|
|
return [{'raw': p} for p in json.load(file)]
|
|
|
|
def get_organisations(self):
|
|
with open(self.data_dir + 'owner/organisation.json') as file:
|
|
return [{'raw': o} for o in json.load(file)]
|
|
|
|
def get_documents(self):
|
|
documents = {}
|
|
doc_path = self.data_dir + 'doc/'
|
|
for filename in Path(doc_path).glob('**/*'):
|
|
uri = str(filename.relative_to(doc_path).as_posix())
|
|
documents[uri] = {
|
|
'ifc': None,
|
|
'raw': filename,
|
|
'attributes': {
|
|
'Location': uri,
|
|
'Name': filename.stem
|
|
}}
|
|
return documents
|
|
|
|
def get_project(self):
|
|
for collection in bpy.data.collections:
|
|
if self.is_a_project(self.get_ifc_class(collection.name)):
|
|
return {
|
|
'ifc': None,
|
|
'raw': collection,
|
|
'class': self.get_ifc_class(collection.name),
|
|
'attributes': self.get_object_attributes(collection)
|
|
}
|
|
|
|
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
|
|
if not scene.BIMProperties.has_georeferencing:
|
|
return {}
|
|
return {
|
|
'ifc': None,
|
|
'attributes': {
|
|
'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)
|
|
}
|
|
}
|
|
|
|
def get_target_crs(self):
|
|
scene = bpy.context.scene
|
|
if not scene.BIMProperties.has_georeferencing:
|
|
return {}
|
|
return {
|
|
'ifc': None,
|
|
'attributes': {
|
|
'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
|
|
}
|
|
}
|
|
|
|
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 collection in bpy.data.collections:
|
|
if self.is_a_spatial_structure_element(self.get_ifc_class(collection.name)):
|
|
elements.append({
|
|
'ifc': None,
|
|
'raw': collection,
|
|
'class': self.get_ifc_class(collection.name),
|
|
'attributes': self.get_object_attributes(collection)
|
|
})
|
|
return elements
|
|
|
|
def load_representations(self):
|
|
if not self.ifc_export_settings.has_representations:
|
|
return
|
|
for product in self.selected_products + self.type_products:
|
|
self.load_product_representations(product)
|
|
|
|
def load_product_representations(self, product):
|
|
obj = product['raw']
|
|
if obj.data and obj.data.name in self.representations:
|
|
return
|
|
self.append_representation_per_context(obj)
|
|
|
|
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,
|
|
'raw_object': obj,
|
|
'context': context,
|
|
'subcontext': subcontext,
|
|
'target_view': target_view,
|
|
'is_curve': isinstance(mesh, bpy.types.Curve),
|
|
'is_point_cloud': self.is_point_cloud(obj),
|
|
'is_structural': self.is_structural(obj),
|
|
'is_wireframe': mesh.BIMMeshProperties.is_wireframe if hasattr(mesh, 'BIMMeshProperties') else False,
|
|
'is_swept_solid': mesh.BIMMeshProperties.is_swept_solid if hasattr(mesh, 'BIMMeshProperties') else False,
|
|
'is_generated': False,
|
|
'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
|
|
|
|
def get_materials(self):
|
|
results = {}
|
|
if not self.ifc_export_settings.has_representations:
|
|
return results
|
|
for product in self.selected_products + self.type_products:
|
|
obj = product['raw']
|
|
if obj.data is None:
|
|
continue
|
|
for slot in obj.material_slots:
|
|
if slot.material is None:
|
|
continue
|
|
if slot.material.name in results or slot.link == 'OBJECT':
|
|
continue
|
|
results[slot.material.name] = {
|
|
'ifc': None,
|
|
'part_ifc': None,
|
|
'raw': slot.material,
|
|
'material_type': obj.BIMObjectProperties.material_type,
|
|
'attributes': self.get_material_attributes(slot.material)
|
|
}
|
|
return results
|
|
|
|
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
|
|
for product in self.selected_products + self.type_products:
|
|
obj = product['raw']
|
|
if obj.data is None:
|
|
continue
|
|
for slot in obj.material_slots:
|
|
if slot.material is None:
|
|
continue
|
|
if not self.ifc_export_settings.should_export_all_materials_as_styled_items and (
|
|
slot.material.name in results or slot.link == 'DATA'):
|
|
continue
|
|
results.append({
|
|
'ifc': None,
|
|
'raw': slot.material,
|
|
'related_product_name': product['raw'].name,
|
|
'attributes': {'Name': slot.material.name},
|
|
})
|
|
return results
|
|
|
|
def get_qtos(self):
|
|
if not self.ifc_export_settings.has_quantities:
|
|
return {}
|
|
results = {}
|
|
for product in self.selected_products + self.type_products:
|
|
obj = product['raw']
|
|
if not obj.data:
|
|
continue
|
|
for property in obj.keys():
|
|
if property[0:4] != 'Qto_':
|
|
continue
|
|
results[obj.name] = {
|
|
'ifc': None,
|
|
'raw': obj,
|
|
'class': property,
|
|
'attributes': {
|
|
'Name': property,
|
|
'MethodOfMeasurement': obj[property]
|
|
}
|
|
}
|
|
return results
|
|
|
|
def get_type_products(self):
|
|
results = []
|
|
index = 0
|
|
for library in self.libraries:
|
|
for obj in library['raw'].objects:
|
|
if not self.is_a_type(self.get_ifc_class(obj.name)):
|
|
continue
|
|
try:
|
|
type_product = {
|
|
'ifc': None,
|
|
'raw': obj,
|
|
'location': obj.matrix_world.translation,
|
|
'up_axis': self.get_axis(obj.matrix_world, 2),
|
|
'forward_axis': self.get_axis(obj.matrix_world, 0),
|
|
'psets': ['{}/{}'.format(pset.name, pset.file) for pset in
|
|
obj.BIMObjectProperties.psets],
|
|
'class': self.get_ifc_class(obj.name),
|
|
'representations': self.get_object_representation_names(obj),
|
|
'attributes': self.get_object_attributes(obj)
|
|
}
|
|
results.append(type_product)
|
|
library['rel_declares_type_products'].append(index)
|
|
|
|
for key in obj.keys():
|
|
if key[0:3] == 'Doc':
|
|
self.rel_associates_document_type.setdefault(
|
|
obj[key], []).append(type_product)
|
|
elif key[0:5] == 'Class':
|
|
self.rel_associates_classification_type.setdefault(
|
|
obj[key], []).append(type_product)
|
|
elif key[0:9] == 'Objective':
|
|
self.rel_associates_constraint_objective_type.setdefault(
|
|
obj[key], []).append(type_product)
|
|
|
|
index += 1
|
|
except Exception as e:
|
|
self.ifc_export_settings.logger.error(
|
|
'The type product "{}" could not be parsed: {}'.format(obj.name, e.args))
|
|
return results
|
|
|
|
def get_object_representation_names(self, obj):
|
|
names = []
|
|
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
|
|
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, collections, name_filter):
|
|
collection_tree = []
|
|
|
|
for collection in collections:
|
|
if not self.is_a_spatial_structure_element(self.get_ifc_class(collection.name)):
|
|
continue
|
|
children = self.get_spatial_structure_elements_tree(
|
|
collection.children, name_filter)
|
|
if collection.name in name_filter \
|
|
or children:
|
|
collection_tree.append({
|
|
'reference': self.get_spatial_structure_element_reference(collection.name),
|
|
'children': children
|
|
})
|
|
|
|
return collection_tree
|
|
|
|
def get_spatial_structure_element_reference(self, name):
|
|
return ['{}/{}'.format(e['class'], e['attributes']['Name'])
|
|
for e in self.spatial_structure_elements].index(name)
|
|
|
|
def get_type_product_reference(self, name):
|
|
return [p['attributes']['Name']
|
|
for p in self.type_products].index(self.get_ifc_name(name))
|
|
|
|
def get_ifc_class(self, name):
|
|
return name.split('/')[0]
|
|
|
|
def get_ifc_name(self, name):
|
|
try:
|
|
return name.split('/')[1]
|
|
except IndexError:
|
|
self.ifc_export_settings.logger.error(
|
|
'Name "{}" does not follow the format of "IfcClass/Name"'.format(name))
|
|
|
|
def is_a_spatial_structure_element(self, class_name):
|
|
# We assume that any collection we can't identify is a spatial structure
|
|
return class_name[0:3] == 'Ifc' \
|
|
and not self.is_a_project(class_name) \
|
|
and not self.is_a_library(class_name) \
|
|
and not self.is_a_rel_aggregates(class_name)
|
|
|
|
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_type(self, class_name):
|
|
return class_name[0:3] == 'Ifc' and class_name[-4:] == 'Type'
|
|
|
|
|
|
class IfcExporter():
|
|
def __init__(self, ifc_export_settings, ifc_parser, qto_calculator):
|
|
self.template_file = '{}template.ifc'.format(ifc_export_settings.schema_dir)
|
|
self.ifc_export_settings = ifc_export_settings
|
|
self.ifc_parser = ifc_parser
|
|
self.qto_calculator = qto_calculator
|
|
|
|
def export(self):
|
|
self.file = ifcopenshell.open(self.template_file)
|
|
self.ifc_parser.parse()
|
|
self.create_units()
|
|
self.create_people()
|
|
self.create_organisations()
|
|
self.set_common_definitions()
|
|
self.create_rep_context()
|
|
self.create_project()
|
|
self.create_library_information()
|
|
self.create_documents()
|
|
self.create_classifications()
|
|
self.create_classification_references()
|
|
self.create_objectives()
|
|
self.create_psets()
|
|
self.create_libraries()
|
|
self.create_map_conversion()
|
|
self.create_representations()
|
|
self.create_materials()
|
|
self.create_type_products()
|
|
self.create_spatial_structure_elements(self.ifc_parser.spatial_structure_elements_tree)
|
|
self.create_qtos()
|
|
self.create_products()
|
|
self.create_styled_items()
|
|
self.relate_definitions_to_contexts()
|
|
self.relate_objects_to_objects()
|
|
self.relate_elements_to_spatial_structures()
|
|
self.relate_nested_elements_to_hosted_elements()
|
|
self.relate_objects_to_types()
|
|
self.relate_objects_to_qtos()
|
|
self.relate_objects_to_psets()
|
|
self.relate_objects_to_opening_elements()
|
|
self.relate_opening_elements_to_fillings()
|
|
self.relate_objects_to_materials()
|
|
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_objectives(self.ifc_parser.rel_associates_constraint_objective_object)
|
|
self.relate_to_objectives(self.ifc_parser.rel_associates_constraint_objective_type)
|
|
self.file.write(self.ifc_export_settings.output_file)
|
|
|
|
def set_common_definitions(self):
|
|
# Owner history doesn't actually work like this, but for now, it does :)
|
|
self.origin = self.file.by_type('IfcAxis2Placement3D')[0]
|
|
self.create_owner_history()
|
|
self.set_header()
|
|
|
|
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.owner_history.OwningApplication.ApplicationFullName,
|
|
self.owner_history.OwningApplication.Version,
|
|
)
|
|
self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Identification
|
|
|
|
def create_owner_history(self):
|
|
for person in self.ifc_parser.people:
|
|
if 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
|
|
person_and_organisation = self.file.create_entity('IfcPersonAndOrganization', **{
|
|
'ThePerson': person['ifc'],
|
|
'TheOrganization': organisation['ifc'],
|
|
'Roles': None # TODO
|
|
})
|
|
version = '.'.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]])
|
|
developer_organisation = [o for o in self.ifc_parser.organisations if o['ifc'].Name == 'IfcOpenShell'][0]['ifc']
|
|
application = self.file.create_entity('IfcApplication', **{
|
|
'ApplicationDeveloper': developer_organisation,
|
|
'Version': version,
|
|
'ApplicationFullName': 'BlenderBIM',
|
|
'ApplicationIdentifier': 'BlenderBIM'
|
|
})
|
|
self.owner_history = self.file.create_entity('IfcOwnerHistory', **{
|
|
'OwningUser': person_and_organisation,
|
|
'OwningApplication': application,
|
|
'State': 'READWRITE',
|
|
'ChangeAction': 'NOTDEFINED',
|
|
'LastModifiedDate': int(time.time()),
|
|
'LastModifyingUser': person_and_organisation,
|
|
'LastModifyingApplication': application,
|
|
'CreationDate': int(time.time()) # illegal, but better than nothing ...
|
|
})
|
|
|
|
def create_units(self):
|
|
for unit_type, data in self.ifc_parser.units.items():
|
|
if data['is_metric']:
|
|
data['ifc'] = self.create_metric_unit(unit_type, data)
|
|
else:
|
|
data['ifc'] = self.create_imperial_unit(unit_type, data)
|
|
self.file.createIfcUnitAssignment([u['ifc'] for u in self.ifc_parser.units.values()])
|
|
|
|
def create_metric_unit(self, unit_type, data):
|
|
type_prefix = ''
|
|
if unit_type == 'area':
|
|
type_prefix = 'SQUARE_'
|
|
elif unit_type == 'volume':
|
|
type_prefix = 'CUBIC_'
|
|
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':
|
|
dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0)
|
|
name_prefix = ''
|
|
elif unit_type == 'area':
|
|
dimensional_exponents = self.file.createIfcDimensionalExponents(2, 0, 0, 0, 0, 0, 0)
|
|
name_prefix = 'square'
|
|
elif unit_type == 'volume':
|
|
dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0)
|
|
name_prefix = 'cubic'
|
|
si_unit = self.file.createIfcSIUnit(
|
|
None,
|
|
'{}UNIT'.format(unit_type.upper()),
|
|
None,
|
|
'{}METRE'.format(name_prefix.upper() + '_' if name_prefix else '')
|
|
)
|
|
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 '')
|
|
value_component = self.file.create_entity(
|
|
'IfcReal',
|
|
**{'wrappedValue': SIUnitHelper.si_conversions[name]}
|
|
)
|
|
conversion_factor = self.file.createIfcMeasureWithUnit(value_component, si_unit)
|
|
return self.file.createIfcConversionBasedUnit(
|
|
dimensional_exponents,
|
|
'{}UNIT'.format(unit_type.upper()),
|
|
name,
|
|
conversion_factor
|
|
)
|
|
|
|
def create_people(self):
|
|
for person in self.ifc_parser.people:
|
|
data = person['raw'].copy()
|
|
if data['Roles']:
|
|
data['Roles'] = self.create_roles(data['Roles'])
|
|
if data['Addresses']:
|
|
data['Addresses'] = self.create_addresses(data['Addresses'])
|
|
person['ifc'] = self.file.create_entity('IfcPerson', **data)
|
|
|
|
def create_organisations(self):
|
|
for organisation in self.ifc_parser.organisations:
|
|
data = organisation['raw'].copy()
|
|
if data['Roles']:
|
|
data['Roles'] = self.create_roles(data['Roles'])
|
|
if data['Addresses']:
|
|
data['Addresses'] = self.create_addresses(data['Addresses'])
|
|
organisation['ifc'] = self.file.create_entity('IfcOrganization', **data)
|
|
|
|
def create_roles(self, roles):
|
|
results = []
|
|
for role in roles:
|
|
results.append(self.file.create_entity('IfcActorRole', **role))
|
|
return results
|
|
|
|
def create_addresses(self, addresses):
|
|
results = []
|
|
for address in addresses:
|
|
is_postal_address = False
|
|
for key in ['InternalLocation', 'AddressLines', 'PostalBox', 'Town',
|
|
'Region', 'PostalCode', 'Country']:
|
|
if key in address:
|
|
is_postal_address = True
|
|
if is_postal_address:
|
|
results.append(self.file.create_entity('IfcPostalAddress', **address))
|
|
else:
|
|
results.append(self.file.create_entity('IfcTelecomAddress', **address))
|
|
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_documents(self):
|
|
for document in self.ifc_parser.documents.values():
|
|
document['ifc'] = self.file.create_entity(
|
|
'IfcDocumentReference', **document['attributes'])
|
|
self.file.createIfcRelAssociatesDocument(
|
|
ifcopenshell.guid.new(), None, None, None,
|
|
[self.ifc_parser.project['ifc']], document['ifc'])
|
|
|
|
def create_classifications(self):
|
|
for classification in self.ifc_parser.classifications:
|
|
classification['ifc'] = self.file.create_entity(
|
|
'IfcClassification',
|
|
**classification['attributes']
|
|
)
|
|
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['attributes']['ReferencedSource'] = \
|
|
self.ifc_parser.classifications[reference['referenced_source']]['ifc']
|
|
reference['ifc'] = self.file.create_entity(
|
|
'IfcClassificationReference', **reference['attributes'])
|
|
|
|
def create_objectives(self):
|
|
for objective in self.ifc_parser.objectives.values():
|
|
objective['ifc'] = self.file.create_entity(
|
|
'IfcObjective', **objective['attributes'])
|
|
|
|
def create_psets(self):
|
|
for pset in self.ifc_parser.psets.values():
|
|
properties = self.create_pset_properties(pset)
|
|
if not properties:
|
|
self.ifc_export_settings.logger.error(
|
|
'No properties could be detected for the pset {}/{}'.format(
|
|
pset['attributes']['Name'],
|
|
pset['attributes']['Description']))
|
|
continue
|
|
pset['attributes'].update({
|
|
'GlobalId': ifcopenshell.guid.new(),
|
|
'OwnerHistory': self.owner_history,
|
|
'HasProperties': properties
|
|
})
|
|
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_pset_properties(self, pset):
|
|
if pset['attributes']['Name'] in schema.ifc.psets:
|
|
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)
|
|
}))
|
|
return properties
|
|
|
|
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
|
|
}))
|
|
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(
|
|
pset['attributes']['Name'],
|
|
pset['attributes']['Description'],
|
|
invalid_pset_keys))
|
|
return properties
|
|
|
|
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 = {}
|
|
self.ifc_rep_context['Model'] = {
|
|
'ifc': self.file.createIfcGeometricRepresentationContext(
|
|
None, 'Model', 3, 1.0E-05, self.origin)}
|
|
if 'Plan' in self.ifc_export_settings.contexts:
|
|
self.ifc_rep_context['Plan'] = {
|
|
'ifc': self.file.createIfcGeometricRepresentationContext(
|
|
None, 'Plan', 2, 1.0E-05, self.origin)}
|
|
for context in self.ifc_export_settings.context_tree:
|
|
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)}
|
|
|
|
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]
|
|
})
|
|
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'])
|
|
self.file.createIfcRelDeclares(
|
|
ifcopenshell.guid.new(), self.owner_history,
|
|
None, None,
|
|
self.ifc_parser.project['ifc'], [l['ifc'] for l in self.ifc_parser.libraries])
|
|
|
|
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']
|
|
self.ifc_parser.map_conversion['ifc'] = self.file.create_entity(
|
|
'IfcMapConversion',
|
|
**self.ifc_parser.map_conversion['attributes']
|
|
)
|
|
|
|
def create_target_crs(self):
|
|
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(
|
|
'IfcProjectedCRS',
|
|
**self.ifc_parser.target_crs['attributes']
|
|
)
|
|
|
|
def create_type_products(self):
|
|
for product in self.ifc_parser.type_products:
|
|
placement = self.create_ifc_axis_2_placement_3d(
|
|
product['location'],
|
|
product['up_axis'],
|
|
product['forward_axis']
|
|
)
|
|
|
|
if product['representations']:
|
|
maps = []
|
|
for representation in product['representations']:
|
|
maps.append(self.file.createIfcRepresentationMap(
|
|
placement, self.ifc_parser.representations[representation]['ifc']))
|
|
product['attributes']['RepresentationMaps'] = maps
|
|
|
|
if product['psets']:
|
|
product['attributes'].update({'HasPropertySets':[
|
|
self.ifc_parser.psets[pset]['ifc']
|
|
for pset in product['psets']]
|
|
})
|
|
|
|
if product['class'] == 'IfcDoorType' \
|
|
and product['attributes']['Name'] in self.ifc_parser.door_attributes:
|
|
self.add_predefined_attributes_to_type_product(
|
|
product,
|
|
self.ifc_parser.door_attributes[product['attributes']['Name']]
|
|
)
|
|
elif product['class'] == 'IfcWindowType' \
|
|
and product['attributes']['Name'] in self.ifc_parser.window_attributes:
|
|
self.add_predefined_attributes_to_type_product(
|
|
product,
|
|
self.ifc_parser.window_attributes[product['attributes']['Name']]
|
|
)
|
|
|
|
try:
|
|
product['ifc'] = self.file.create_entity(product['class'], **product['attributes'])
|
|
except RuntimeError as e:
|
|
self.ifc_export_settings.logger.error(
|
|
'The type product "{}/{}" could not be created: {}'.format(
|
|
product['class'],
|
|
product['attributes']['Name'],
|
|
e.args
|
|
)
|
|
)
|
|
|
|
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):
|
|
for attribute in attributes:
|
|
attribute['ifc'] = self.file.create_entity(
|
|
attribute['pset_name'],
|
|
**{k: float(v) if v.replace('.', '', 1).isdigit() else v
|
|
for k, v in attribute['raw'].items()}
|
|
)
|
|
|
|
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]
|
|
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']]
|
|
element['attributes'].update({
|
|
'OwnerHistory': self.owner_history, # TODO: unhardcode
|
|
'ObjectPlacement': self.file.createIfcLocalPlacement(placement_rel_to, self.origin)
|
|
})
|
|
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 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'])]['ifc']
|
|
representation_items = []
|
|
if product.Representation:
|
|
for representation in product.Representation.Representations:
|
|
for item in representation.Items:
|
|
representation_items.append(item)
|
|
for representation_item in representation_items:
|
|
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',
|
|
**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.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_materials(self):
|
|
for material in self.ifc_parser.materials.values():
|
|
styled_item = self.create_styled_item(material)
|
|
styled_representation = self.file.createIfcStyledRepresentation(
|
|
self.ifc_rep_context['Model']['Body']['MODEL_VIEW']['ifc'], None, None, [styled_item])
|
|
material['ifc'] = self.file.createIfcMaterial(material['raw'].name, None, None)
|
|
self.create_material_psets(material)
|
|
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)
|
|
|
|
def cast_attributes(self, ifc_class, attributes):
|
|
for key, value in attributes.items():
|
|
complex_attribute = self.cast_complex_attribute(ifc_class, key, value)
|
|
if complex_attribute:
|
|
attributes[key] = complex_attribute
|
|
continue
|
|
|
|
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)
|
|
|
|
def create_surface_style_rendering(self, styled_item):
|
|
surface_colour = self.create_colour_rgb(styled_item['raw'].diffuse_color)
|
|
rendering_attributes = {'SurfaceColour': surface_colour}
|
|
rendering_attributes.update(self.get_rendering_attributes(styled_item['raw']))
|
|
return self.file.create_entity('IfcSurfaceStyleRendering', **rendering_attributes)
|
|
|
|
def get_rendering_attributes(self, material):
|
|
if not hasattr(material.node_tree, 'nodes') \
|
|
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)
|
|
}
|
|
|
|
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():
|
|
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):
|
|
for object_name, qto in self.ifc_parser.qtos.items():
|
|
quantities = self.calculate_quantities(qto['class'], qto['raw'])
|
|
qto['attributes'].update({
|
|
'GlobalId': ifcopenshell.guid.new(),
|
|
'OwnerHistory': self.owner_history,
|
|
'Quantities': quantities
|
|
})
|
|
qto['ifc'] = self.file.create_entity('IfcElementQuantity', **qto['attributes'])
|
|
|
|
def create_product(self, product):
|
|
if product['relating_structure']:
|
|
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
|
|
|
|
if product['has_scale']:
|
|
placement = self.file.createIfcLocalPlacement(placement_rel_to, self.origin)
|
|
else:
|
|
placement = self.file.createIfcLocalPlacement(placement_rel_to,
|
|
self.create_ifc_axis_2_placement_3d(product['location'],
|
|
product['up_axis'],
|
|
product['forward_axis']))
|
|
|
|
self.cast_attributes(product['class'], product['attributes'])
|
|
|
|
product['attributes'].update({
|
|
'OwnerHistory': self.owner_history, # TODO: unhardcode
|
|
'ObjectPlacement': placement,
|
|
'Representation': self.get_product_shape(product)
|
|
})
|
|
|
|
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:
|
|
self.ifc_export_settings.logger.error(
|
|
'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:
|
|
shape = self.file.createIfcProductDefinitionShape(None, None,
|
|
self.get_product_shape_representations(product))
|
|
except:
|
|
shape = None
|
|
return shape
|
|
|
|
def get_product_shape_representations(self, product):
|
|
results = []
|
|
for representation_name in product['representations']:
|
|
results.append(self.get_product_mapped_geometry(product, representation_name))
|
|
return results
|
|
|
|
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)
|
|
))
|
|
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,
|
|
self.create_direction(product['up_axis']),
|
|
product['scale'].y,
|
|
product['scale'].z)
|
|
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))))
|
|
mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target)
|
|
return self.file.createIfcShapeRepresentation(
|
|
shape_representation.ContextOfItems,
|
|
shape_representation.RepresentationIdentifier,
|
|
'MappedRepresentation',
|
|
[mapped_item])
|
|
|
|
def calculate_quantities(self, qto_name, obj):
|
|
quantities = []
|
|
for index, vg in enumerate(obj.vertex_groups):
|
|
if qto_name not in vg.name:
|
|
continue
|
|
if 'length' in vg.name.lower():
|
|
quantity = float(self.qto_calculator.get_length(obj, index))
|
|
quantities.append(self.file.createIfcQuantityLength(
|
|
vg.name.split('/')[1], None,
|
|
self.ifc_parser.units['length']['ifc'], quantity))
|
|
elif 'area' in vg.name.lower():
|
|
quantity = float(self.qto_calculator.get_area(obj, index))
|
|
quantities.append(self.file.createIfcQuantityArea(
|
|
vg.name.split('/')[1], None,
|
|
self.ifc_parser.units['area']['ifc'], quantity))
|
|
elif 'volume' in vg.name.lower():
|
|
quantity = float(self.qto_calculator.get_volume(obj, index))
|
|
quantities.append(self.file.createIfcQuantityVolume(
|
|
vg.name.split('/')[1], None,
|
|
self.ifc_parser.units['volume']['ifc'], quantity))
|
|
if not quantity:
|
|
self.ifc_export_settings.logger.warning('The calculated quantity {} for {} is zero.'.format(
|
|
vg.name, obj.name))
|
|
return quantities
|
|
|
|
def create_ifc_axis_2_placement_3d(self, point, up, forward):
|
|
return self.file.createIfcAxis2Placement3D(
|
|
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)))
|
|
|
|
def create_representation(self, representation):
|
|
self.ifc_vertices = []
|
|
self.ifc_edges = []
|
|
self.ifc_faces = []
|
|
if representation['is_generated'] \
|
|
and representation['subcontext'] == 'Box':
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_box_representation(representation))
|
|
elif representation['subcontext'] == 'CoG':
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_cog_representation(representation))
|
|
elif representation['is_curve'] \
|
|
and representation['context'] == 'Model' \
|
|
and representation['subcontext'] == 'Axis' \
|
|
and representation['target_view'] == 'GRAPH_VIEW':
|
|
return self.file.createIfcRepresentationMap(
|
|
self.origin, self.create_curve_axis_representation(representation))
|
|
elif representation['is_structural'] \
|
|
and representation['context'] == 'Model' \
|
|
and representation['subcontext'] == 'Reference' \
|
|
and representation['target_view'] == 'GRAPH_VIEW':
|
|
return self.file.createIfcRepresentationMap(
|
|
self.origin, self.create_structural_reference_representation(representation))
|
|
elif representation['context'] == 'Plan' \
|
|
or representation['subcontext'] == 'Axis' \
|
|
or representation['is_wireframe']:
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_wireframe_representation(representation))
|
|
elif representation['subcontext'] == 'SurveyPoints':
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_geometric_curve_set_representation(representation))
|
|
elif representation['is_curve']:
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_curve_representation(representation))
|
|
elif representation['is_swept_solid']:
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_swept_solid_representation(representation))
|
|
elif representation['is_point_cloud']:
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_point_cloud_representation(representation))
|
|
return self.file.createIfcRepresentationMap(self.origin,
|
|
self.create_solid_representation(representation))
|
|
|
|
def create_box_representation(self, representation):
|
|
obj = representation['raw_object']
|
|
bounding_box = self.file.createIfcBoundingBox(
|
|
self.create_cartesian_point(
|
|
obj.bound_box[0][0],
|
|
obj.bound_box[0][1],
|
|
obj.bound_box[0][2]
|
|
),
|
|
obj.dimensions[0],
|
|
obj.dimensions[1],
|
|
obj.dimensions[2]
|
|
)
|
|
return self.file.createIfcShapeRepresentation(
|
|
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(
|
|
self.ifc_rep_context[representation['context']][representation['subcontext']][
|
|
representation['target_view']]['ifc'],
|
|
representation['subcontext'],
|
|
'BoundingBox',
|
|
[cog])
|
|
|
|
def create_wireframe_representation(self, representation):
|
|
mesh = representation['raw']
|
|
self.create_vertices(mesh.vertices)
|
|
for edge in mesh.edges:
|
|
self.ifc_edges.append(self.file.createIfcPolyline([
|
|
self.ifc_vertices[v] for v in edge.vertices]))
|
|
return self.file.createIfcShapeRepresentation(
|
|
self.ifc_rep_context[representation['context']][representation['subcontext']][
|
|
representation['target_view']]['ifc'],
|
|
representation['subcontext'],
|
|
'Curve',
|
|
self.ifc_edges)
|
|
|
|
def create_geometric_curve_set_representation(self, representation):
|
|
mesh = representation['raw']
|
|
self.create_vertices(mesh.vertices)
|
|
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]))
|
|
geometric_curve_set = self.file.createIfcGeometricCurveSet(loops)
|
|
return self.file.createIfcShapeRepresentation(
|
|
self.ifc_rep_context[representation['context']][representation['subcontext']][
|
|
representation['target_view']]['ifc'],
|
|
representation['subcontext'], 'GeometricCurveSet', [geometric_curve_set])
|
|
|
|
# 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_curve_axis_representation(self, representation):
|
|
return self.file.createIfcShapeRepresentation(
|
|
self.ifc_rep_context[representation['context']][representation['subcontext']][
|
|
representation['target_view']]['ifc'],
|
|
representation['subcontext'], 'Curve3D',
|
|
[self.create_curve(representation['raw'])])
|
|
|
|
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):
|
|
# TODO: support unclosed surfaces
|
|
swept_area = self.file.createIfcArbitraryClosedProfileDef('AREA', None,
|
|
self.create_curve(representation['raw'].bevel_object.data))
|
|
swept_area_solids = []
|
|
for spline in representation['raw'].splines:
|
|
direction = spline.bezier_points[1].co - spline.bezier_points[0].co
|
|
unit_direction = direction.normalized()
|
|
|
|
# This can be used in the future when dealing with non vector curves
|
|
# curr_point = spline.bezier_points[0]
|
|
# next_point = spline.bezier_points[1]
|
|
# j_percent = 0
|
|
# direction = self.bezier_tangent(
|
|
# pt0=curr_point.co,
|
|
# pt1=curr_point.handle_right,
|
|
# pt2=next_point.handle_left,
|
|
# pt3=next_point.co,
|
|
# step=j_percent)
|
|
tilt_matrix = Matrix.Rotation(-spline.bezier_points[0].tilt, 4, 'Z')
|
|
x_axis = unit_direction.to_track_quat('-Y', 'Z') @ Vector((1, 0, 0)) @ tilt_matrix
|
|
|
|
position = self.create_ifc_axis_2_placement_3d(
|
|
spline.bezier_points[0].co, unit_direction, x_axis)
|
|
swept_area_solids.append(self.file.createIfcExtrudedAreaSolid(
|
|
swept_area, position,
|
|
self.file.createIfcDirection((0., 0., 1.)),
|
|
self.convert_si_to_unit(direction.length)))
|
|
# TODO: support other types of swept areas
|
|
# swept_area_solid = self.file.createIfcFixedReferenceSweptAreaSolid(
|
|
# swept_area, self.origin, self.create_curve(representation['raw']),
|
|
# 0., 1., self.file.createIfcDirection((0.0, -1.0, 0.0)))
|
|
return self.file.createIfcShapeRepresentation(
|
|
self.ifc_rep_context[representation['context']][representation['subcontext']][
|
|
representation['target_view']]['ifc'],
|
|
representation['subcontext'], 'AdvancedSweptSolid',
|
|
swept_area_solids)
|
|
|
|
def create_vertex_point(self, point):
|
|
return self.file.createIfcVertexPoint(
|
|
self.create_cartesian_point(point.x, point.y, point.z))
|
|
|
|
def create_edge(self, curve):
|
|
if curve.splines[0].bezier_points:
|
|
points = curve.splines[0].bezier_points
|
|
elif curve.splines[0].points:
|
|
points = curve.splines[0].points
|
|
if not points:
|
|
return
|
|
return self.file.createIfcEdge(
|
|
self.create_vertex_point(points[0].co),
|
|
self.create_vertex_point(points[1].co))
|
|
|
|
def create_curve(self, curve):
|
|
# TODO: support interpolated curves, not just polylines
|
|
points = []
|
|
for point in curve.splines[0].bezier_points:
|
|
points.append(self.create_cartesian_point(
|
|
point.co.x, point.co.y, point.co.z))
|
|
for point in curve.splines[0].points:
|
|
points.append(self.create_cartesian_point(
|
|
point.co.x, point.co.y, point.co.z))
|
|
if curve.splines[0].use_cyclic_u:
|
|
points.append(points[0])
|
|
return self.file.createIfcPolyline(points)
|
|
|
|
def create_swept_solid_representation(self, representation):
|
|
obj = representation['raw_object']
|
|
mesh = representation['raw']
|
|
items = []
|
|
for swept_solid in mesh.BIMMeshProperties.swept_solids:
|
|
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):
|
|
loop = self.get_loop_from_v_indices(obj, indices)
|
|
curve_ucs = self.get_curve_profile_coordinate_system(obj, loop)
|
|
inner_curves.append(
|
|
self.create_polyline_from_loop(obj, loop, curve_ucs))
|
|
|
|
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,
|
|
outer_curve, inner_curves)
|
|
else:
|
|
curve = self.file.createIfcArbitraryClosedProfileDef('AREA', None, outer_curve)
|
|
|
|
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(
|
|
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])
|
|
|
|
def get_curve_profile_coordinate_system(self, obj, loop):
|
|
profile_face = bpy.data.meshes.new('profile_face')
|
|
profile_verts = [(
|
|
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
|
|
x_axis = (obj.data.vertices[loop[0]].co - center).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,
|
|
'matrix': matrix.to_4x4() @ Matrix.Translation(-center)
|
|
}
|
|
|
|
def create_polyline_from_loop(self, obj, loop, curve_ucs):
|
|
points = []
|
|
for point in loop:
|
|
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)
|
|
|
|
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)
|
|
return curve_ucs['matrix'] @ (
|
|
curve_ucs['center'] + (obj.data.vertices[end].co - obj.data.vertices[start].co))
|
|
|
|
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
|
|
|
|
def get_edges_in_v_indices(self, obj, indices):
|
|
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:
|
|
currentEdge = edges.pop()
|
|
startVert = currentEdge.vertices[0]
|
|
endVert = currentEdge.vertices[1]
|
|
polyLine = [startVert, endVert]
|
|
ok = 1
|
|
while ok:
|
|
ok = 0
|
|
i = len(edges)
|
|
while i:
|
|
i -= 1
|
|
ed = edges[i]
|
|
if ed.vertices[0] == endVert:
|
|
polyLine.append(ed.vertices[1])
|
|
endVert = polyLine[-1]
|
|
ok = 1
|
|
del edges[i]
|
|
elif ed.vertices[1] == endVert:
|
|
polyLine.append(ed.vertices[0])
|
|
endVert = polyLine[-1]
|
|
ok = 1
|
|
del edges[i]
|
|
elif ed.vertices[0] == startVert:
|
|
polyLine.insert(0, ed.vertices[1])
|
|
startVert = polyLine[0]
|
|
ok = 1
|
|
del edges[i]
|
|
elif ed.vertices[1] == startVert:
|
|
polyLine.insert(0, ed.vertices[0])
|
|
startVert = polyLine[0]
|
|
ok = 1
|
|
del edges[i]
|
|
return polyLine
|
|
|
|
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']
|
|
self.create_vertices(mesh.vertices)
|
|
for polygon in mesh.polygons:
|
|
self.ifc_faces.append(self.file.createIfcFace([
|
|
self.file.createIfcFaceOuterBound(
|
|
self.file.createIfcPolyLoop([self.ifc_vertices[vertice] for vertice in polygon.vertices]),
|
|
True)]))
|
|
return self.file.createIfcShapeRepresentation(
|
|
self.ifc_rep_context[representation['context']][representation['subcontext']][
|
|
representation['target_view']]['ifc'],
|
|
representation['subcontext'], 'Brep',
|
|
[self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(self.ifc_faces))])
|
|
|
|
def create_vertices(self, vertices):
|
|
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):
|
|
x = self.convert_si_to_unit(x)
|
|
y = self.convert_si_to_unit(y)
|
|
if z is None:
|
|
return self.file.createIfcCartesianPoint((x, y))
|
|
z = self.convert_si_to_unit(z)
|
|
return self.file.createIfcCartesianPoint((x, y, z))
|
|
|
|
def create_direction(self, vector):
|
|
return self.file.createIfcDirection((vector.x, vector.y, vector.z))
|
|
|
|
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:
|
|
self.file.createIfcRelVoidsElement(
|
|
ifcopenshell.guid.new(), self.owner_history, None, None,
|
|
self.ifc_parser.products[relating_building_element]['ifc'],
|
|
self.ifc_parser.products[related_opening_element]['ifc']
|
|
)
|
|
|
|
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:
|
|
self.file.createIfcRelFillsElement(
|
|
ifcopenshell.guid.new(), self.owner_history, None, None,
|
|
self.ifc_parser.products[relating_opening_element]['ifc'],
|
|
self.ifc_parser.products[related_building_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,
|
|
[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'],
|
|
[o['ifc'] for o in related_objects])
|
|
|
|
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,
|
|
[self.ifc_parser.products[o]['ifc'] for o in related_objects],
|
|
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],
|
|
self.ifc_parser.qtos[relating_property_key]['ifc'])
|
|
|
|
def relate_objects_to_psets(self):
|
|
for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_pset.items():
|
|
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'])
|
|
|
|
def relate_objects_to_materials(self):
|
|
if not self.ifc_export_settings.has_representations:
|
|
return
|
|
for relating_material_key, related_objects in self.ifc_parser.rel_associates_material.items():
|
|
self.file.createIfcRelAssociatesMaterial(
|
|
ifcopenshell.guid.new(), self.owner_history, None, None,
|
|
[o['ifc'] for o in related_objects],
|
|
self.ifc_parser.materials[relating_material_key]['ifc'])
|
|
|
|
def relate_objects_to_material_sets(self, set_type):
|
|
if not self.ifc_export_settings.has_representations:
|
|
return
|
|
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]
|
|
})
|
|
self.file.createIfcRelAssociatesMaterial(
|
|
ifcopenshell.guid.new(), self.owner_history, None, None,
|
|
[self.ifc_parser.products[product_index]['ifc']],
|
|
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(
|
|
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.documents[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_objectives(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.objectives[relating_key]['ifc'])
|
|
|
|
def convert_si_to_unit(self, co):
|
|
return co / self.ifc_parser.unit_scale
|
|
|
|
|
|
class IfcExportSettings:
|
|
def __init__(self):
|
|
self.logger = None
|
|
self.schema_dir = None
|
|
self.data_dir = None
|
|
self.output_file = None
|
|
self.has_representations = True
|
|
self.has_quantities = True
|
|
self.contexts = ['Model', 'Plan']
|
|
self.subcontexts = ['Axis', 'FootPrint', 'Reference', 'Body', 'Clearance', 'CoG', 'SurveyPoints']
|
|
self.generated_subcontexts = ['Box']
|
|
self.target_views = ['GRAPH_VIEW', 'SKETCH_VIEW', 'MODEL_VIEW', 'PLAN_VIEW', 'REFLECTED_PLAN_VIEW',
|
|
'SECTION_VIEW', 'ELEVATION_VIEW', 'USERDEFINED', 'NOTDEFINED']
|
|
self.should_export_all_materials_as_styled_items = False
|
|
self.should_use_presentation_style_assignment = False
|
|
self.context_tree = []
|