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

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

3566 lines
159 KiB
Python
Raw Normal View History

2019-08-29 23:11:12 +10:00
import bpy
import csv
import bmesh
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
import ifcopenshell
import ifcopenshell.util.pset
import ifcopenshell.util.schema
from pathlib import Path
2019-11-06 17:37:51 +11:00
from mathutils import Vector, Matrix
from .helper import SIUnitHelper
from . import schema
from . import ifc
import addon_utils
2019-11-17 13:25:41 +01:00
2020-11-01 20:08:48 +07:00
class ArrayModifier:
count: int
offset: Vector
2019-08-29 23:11:12 +10:00
2019-11-17 13:25:41 +01:00
2020-11-01 20:08:48 +07: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-08-14 18:23:24 +10:00
self.selected_grid_axes = []
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.material_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.styled_items = []
self.surface_styles = {}
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 = {}
self.rel_associates_material_layer_set = []
self.rel_associates_material_constituent_set = []
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 = {}
2020-08-14 18:23:24 +10:00
self.grid_axes = {}
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]
if not selected_objects:
2020-11-01 20:08:48 +07:00
selected_objects = self.get_all_objects_in_project(self.project["raw"])
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.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()
# TODO: migrate this into the product / type / spatial element loop
self.get_materials_and_surface_styles()
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()
2020-08-14 18:23:24 +10:00
self.grid_axes = self.get_grid_axes()
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):
units = {
2020-11-01 20:08:48 +07:00
"length": {
"ifc": None,
"is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
"raw": bpy.context.scene.unit_settings.length_unit,
},
"area": {
"ifc": None,
"is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
"raw": bpy.context.scene.unit_settings.length_unit,
2019-11-17 13:25:41 +01:00
},
2020-11-01 20:08:48 +07:00
"volume": {
"ifc": None,
"is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL",
"raw": bpy.context.scene.unit_settings.length_unit,
2019-11-17 13:25:41 +01:00
},
2020-11-01 20:08:48 +07:00
}
for data in units.values():
2020-11-01 20:08:48 +07:00
if data["raw"] == "ADAPTIVE":
if data["is_metric"]:
data["raw"] = "METERS"
else:
2020-11-01 20:08:48 +07:00
data["raw"] = "FEET"
return units
2019-09-10 17:08:45 +10:00
2019-11-07 16:40:57 +11:00
def get_unit_scale(self):
conversions = {
2020-11-01 20:08:48 +07:00
"KILOMETERS": 1e3,
"CENTIMETERS": 1e-2,
"MILLIMETERS": 1e-3,
"MICROMETERS": 1e-6,
"FEET": 0.3048,
"INCHES": 0.0254,
}
if bpy.context.scene.unit_settings.system in {"METRIC", "IMPERIAL"}:
scale = bpy.context.scene.unit_settings.scale_length
else:
scale = 1
2020-11-01 20:08:48 +07:00
if self.units["length"]["raw"] in conversions.keys():
scale *= conversions[self.units["length"]["raw"]]
return scale
2019-11-07 16:40:57 +11:00
2019-11-17 13:25:41 +01:00
def get_object_attributes(self, obj):
2020-11-01 20:08:48 +07:00
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()
2020-11-01 20:08:48 +07:00
global_id.name = "GlobalId"
2019-10-16 23:17:48 +11:00
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})
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
obj = product["raw"]
if obj.data and hasattr(obj.data, "BIMMeshProperties") and not obj.data.BIMMeshProperties.is_parametric:
return
2020-11-01 20:08:48 +07:00
instance_objects = [
(obj, {"location": obj.matrix_world.translation, "array_offset": Vector((0, 0, 0)), "scale": obj.scale})
]
for modifier in obj.modifiers:
created_instances = []
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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],
2020-11-01 20:08:48 +07:00
modifier.constant_offset_displace[2],
)
)
2020-11-01 20:08:48 +07:00
if modifier.fit_type == "FIXED_COUNT":
array.count = modifier.count
2020-11-01 20:08:48 +07:00
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()
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
{"raw": obj[0], "metadata": product["metadata"]},
metadata_override=override,
2020-11-01 20:08:48 +07:00
attribute_override={
"GlobalId": self.get_parametric_global_id(
product["raw"], len(instance_objects) + len(created_instances) - 1
)
2020-11-01 20:08:48 +07:00
},
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()
2020-11-01 20:08:48 +07:00
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()
2020-11-01 20:08:48 +07:00
unrotated_offset = world_rotation.inverted() @ override["array_offset"]
mirrored_offset = unrotated_offset @ Matrix.Scale(-1, 4, mirror_axis)
rotated_offset = world_rotation @ mirrored_offset
2020-11-01 20:08:48 +07:00
override["location"] = override["location"] - override["array_offset"] + rotated_offset
self.add_product(
self.get_product(
2020-11-01 20:08:48 +07:00
{"raw": obj[0], "metadata": product["metadata"]},
metadata_override=override,
2020-11-01 20:08:48 +07:00
attribute_override={
"GlobalId": self.get_parametric_global_id(
product["raw"], len(instance_objects) + len(created_instances) - 1
)
2020-11-01 20:08:48 +07:00
},
)
)
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):
2020-11-01 20:08:48 +07: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(
2020-11-01 20:08:48 +07:00
obj.BIMObjectProperties.structural_member_connection.name
)
def resolve_voids_and_fills(self, i, obj):
for m in obj.modifiers:
2020-11-01 20:08:48 +07:00
if m.type != "BOOLEAN" or m.object is None:
2020-02-02 20:28:51 +11:00
continue
void_or_projection = self.get_product_index_from_raw_name(m.object.name)
if void_or_projection is None:
continue
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
product.update(
{
"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))).length > 0.01,
"has_mirror": False,
"array_offset": Vector((0, 0, 0)),
"scale": obj.scale,
"representations": self.get_object_representation_names(obj),
}
)
2020-01-28 22:06:58 +11:00
def get_product(self, selected_product, metadata_override={}, attribute_override={}):
2020-11-01 20:08:48 +07:00
obj = selected_product["raw"]
2020-01-28 22:06:58 +11:00
product = {
2020-11-01 20:08:48 +07:00
"ifc": None,
"raw": obj,
"class": self.get_ifc_class(obj.name),
"attributes": self.get_object_attributes(obj),
"relating_structure": None,
"relating_host": None,
"relating_qtos_key": None,
"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)
2020-11-01 20:08:48 +07:00
product["attributes"].update(attribute_override)
product.update(metadata_override)
type_product = obj.BIMObjectProperties.relating_type
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
if "IfcRelNests" in obj.constraints:
# TODO: I think get_product_index_from_raw_name should not be used
2020-11-01 20:08:48 +07:00
parent_product_index = self.get_product_index_from_raw_name(obj.constraints["IfcRelNests"].target.name)
self.rel_nests.setdefault(parent_product_index, []).append(product)
2020-11-01 20:08:48 +07:00
product["relating_host"] = parent_product_index
2019-11-17 13:25:41 +01:00
for name, constraint in obj.constraints.items():
2020-11-01 20:08:48 +07:00
if "IfcRelSpaceBoundary" not in name:
continue
2020-11-01 20:08:48 +07:00
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
}
2020-11-01 20:08:48 +07:00
)
2020-11-01 20:08:48 +07:00
if obj.instance_type == "COLLECTION" and self.is_a_rel_aggregates(
self.get_ifc_class(obj.instance_collection.name)
):
2019-11-17 13:25:41 +01:00
self.rel_aggregates[self.product_index] = obj.name
2020-11-01 20:08:48 +07:00
if "rel_aggregates_relating_object" in selected_product["metadata"]:
relating_object = selected_product["metadata"]["rel_aggregates_relating_object"]
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)
self.get_styled_items_and_surface_styles(product, obj)
for reference in obj.BIMObjectProperties.document_references:
2020-11-01 20:08:48 +07:00
self.rel_associates_document_object.setdefault(reference.name, []).append(product)
2019-11-17 13:25:41 +01:00
for classification in obj.BIMObjectProperties.classifications:
2020-11-01 20:08:48 +07: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:
2020-11-01 20:08:48 +07:00
self.rel_associates_constraint_object.setdefault(constraint.name, []).append(product)
2019-09-08 19:02:00 +10:00
if obj.BIMObjectProperties.material_type == "IfcMaterial" and obj.BIMObjectProperties.material:
self.rel_associates_material.setdefault(obj.BIMObjectProperties.material.name, []).append(product)
elif obj.BIMObjectProperties.material_type == "IfcMaterialConstituentSet":
self.rel_associates_material_constituent_set.append((obj.BIMObjectProperties.material_set, product))
elif obj.BIMObjectProperties.material_type == "IfcMaterialLayerSet":
self.rel_associates_material_layer_set.append((obj.BIMObjectProperties.material_set, product))
elif obj.BIMObjectProperties.material_type == "IfcMaterialProfileSet":
self.rel_associates_material_profile_set.append((obj.BIMObjectProperties.material_set, 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:
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
results[item_key] = {"ifc": None, "raw": raw, "attributes": {"Name": item.name}}
relationships.setdefault(item_key, []).append(product)
def get_material_psets(self, material, obj):
psets = obj.BIMMaterialProperties.psets
results = self.material_psets
for item in psets:
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, "material": material, "attributes": {"Name": item.name}}
def add_automatic_qtos(self, ifc_class, obj):
if not obj.data:
return
qto_names = self.get_applicable_qtos(ifc_class)
for name in qto_names:
if name not in ifcopenshell.util.pset.qtos:
continue
has_automatic_value = False
props = ifcopenshell.util.pset.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(schema=self.ifc_export_settings.schema)
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)
2020-11-01 20:08:48 +07:00
product["relating_structure"] = reference
return
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
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):
# Aggregates are not handled here, since we don't know the order in
# which products are parsed.
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)
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
if obj.name[0:3] != "Ifc":
continue
2020-08-14 18:23:24 +10:00
elif self.is_a_grid_axis(self.get_ifc_class(obj.name)):
2020-11-01 20:08:48 +07:00
self.selected_grid_axes.append({"raw": obj, "metadata": metadata})
elif self.is_a_spatial_structure_element(self.get_ifc_class(obj.name)):
2020-11-01 20:08:48 +07:00
self.selected_spatial_structure_elements.append({"raw": obj, "metadata": metadata})
elif self.is_a_type(self.get_ifc_class(obj.name)):
2020-11-01 20:08:48 +07:00
self.selected_types.append({"raw": obj, "metadata": metadata})
elif self.is_a_group(self.get_ifc_class(obj.name)):
2020-11-01 20:08:48 +07:00
self.selected_groups.append({"raw": obj, "metadata": metadata})
elif obj.instance_type == "COLLECTION":
2020-01-28 22:06:58 +11:00
self.categorise_selected_objects(
2020-11-01 20:08:48 +07:00
obj.instance_collection.objects, {"rel_aggregates_relating_object": obj}
2019-11-18 03:45:24 +01:00
)
2020-11-01 20:08:48 +07:00
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)):
2020-11-01 20:08:48 +07:00
self.selected_products.append({"raw": obj, "metadata": metadata})
2019-09-01 17:23:48 +10:00
2019-09-29 22:12:51 +10:00
def get_door_attributes(self):
2020-11-01 20:08:48 +07:00
return self.get_predefined_attributes("door")
2019-09-29 22:29:06 +10:00
def get_window_attributes(self):
2020-11-01 20:08:48 +07:00
return self.get_predefined_attributes("window")
2019-09-29 22:29:06 +10:00
2019-11-18 05:46:52 +01:00
def get_predefined_attributes(self, attr):
2019-09-29 22:12:51 +10:00
results = {}
2020-11-01 20:08:48 +07:00
for filename in Path(self.data_dir + attr + "/").glob("**/*.csv"):
with open(filename, "r") as f:
2019-09-29 22:12:51 +10:00
type_name = filename.parts[-2]
pset_name = filename.stem
2020-11-01 20:08:48 +07:00
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],
}
)
2019-09-29 22:12:51 +10:00
return results
def get_classifications(self):
results = {}
for classification in bpy.context.scene.BIMProperties.classifications:
if classification.name not in schema.ifc.classification_files:
2020-11-01 20:08:48 +07:00
schema.ifc.classification_files[classification.name] = ifcopenshell.file.from_string(
classification.data
)
results[classification.name] = {
2020-11-01 20:08:48 +07:00
"ifc": None,
"raw": classification,
"raw_element": schema.ifc.classification_files[classification.name].by_type("IfcClassification")[0],
}
return results
def get_classification_reference_maps(self):
results = {}
for name, classification in self.classifications.items():
ifc_file = schema.ifc.classification_files[name]
2020-11-01 20:08:48 +07:00
if ifc_file.schema == "IFC2X3":
results[name] = {e.ItemReference: e for e in ifc_file.by_type("IfcClassificationReference")}
2020-05-09 12:47:26 +10:00
else:
2020-11-01 20:08:48 +07:00
results[name] = {e.Identification: e for e in ifc_file.by_type("IfcClassificationReference")}
return results
def get_classification_references(self):
results = {}
2020-11-01 20:08:48 +07:00
for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements:
for reference in product["raw"].BIMObjectProperties.classifications:
results[reference.name] = {
2020-11-01 20:08:48 +07:00
"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 = {
2020-11-01 20:08:48 +07:00
"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)
2020-11-01 20:08:48 +07:00
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 = {
2020-11-01 20:08:48 +07:00
"name": "Identification",
"family_name": "FamilyName",
"given_name": "GivenName",
}
list_data_map = {
2020-11-01 20:08:48 +07:00
"middle_names": "MiddleNames",
"prefix_titles": "PrefixTitles",
"suffix_titles": "SuffixTitles",
}
results = []
2020-11-01 20:08:48 +07:00
if self.ifc_export_settings.schema == "IFC2X3" and not bpy.context.scene.BIMProperties.people:
bpy.ops.bim.add_person()
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):
2020-11-01 20:08:48 +07:00
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 = {
2020-11-01 20:08:48 +07:00
"name": "Name",
"description": "Description",
}
results = []
2020-11-01 20:08:48 +07:00
if self.ifc_export_settings.schema == "IFC2X3" and not bpy.context.scene.BIMProperties.organisations:
bpy.ops.bim.add_organisation()
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)
2020-11-01 20:08:48 +07:00
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 = {
2020-11-01 20:08:48 +07:00
"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)
2020-11-01 20:08:48 +07:00
results.append({"ifc": None, "raw": role, "attributes": attributes})
return results
def get_addresses(self, addresses):
results = []
for address in addresses:
results.append(self.get_address(address))
return results
def get_address(self, address):
address_data_map = {
2020-11-01 20:08:48 +07:00
"purpose": "Purpose",
"description": "Description",
"user_defined_purpose": "UserDefinedPurpose",
}
postal_data_map = {
2020-11-01 20:08:48 +07:00
"internal_location": "InternalLocation",
"postal_box": "PostalBox",
"town": "Town",
"region": "Region",
"postal_code": "PostalCode",
"country": "Country",
}
telecom_data_map = {
2020-11-01 20:08:48 +07:00
"pager_number": "PagerNumber",
"www_home_page_url": "WWWHomePageURL",
}
telecom_list_data_map = {
2020-11-01 20:08:48 +07:00
"telephone_numbers": "TelephoneNumbers",
"fascimile_numbers": "FascimileNumbers",
"electronic_mail_addresses": "ElectronicMailAddresses",
"messaging_ids": "MessagingIDs",
}
attributes = {}
2020-11-01 20:08:48 +07:00
if "IfcPostalAddress" in address.name:
merged_data_map = {**address_data_map, **postal_data_map}
if address.address_lines:
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
attributes[value] = getattr(address, key).split(",")
for key, value in merged_data_map.items():
if getattr(address, key):
attributes[value] = getattr(address, key)
return {
2020-11-01 20:08:48 +07:00
"ifc": None,
"raw": address,
"is_postal": "IfcPostalAddress" in address.name,
"is_telecom": "IfcTelecomAddress" in address.name,
"attributes": attributes,
}
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 = {
2020-11-01 20:08:48 +07:00
"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] = {
2020-11-01 20:08:48 +07:00
"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 = {
2020-11-01 20:08:48 +07:00
"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)
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
results.append(
{
"ifc": None,
"raw": collection,
"class": self.get_ifc_class(collection.name),
"attributes": self.get_object_attributes(obj),
}
)
return results
def get_all_objects_in_project(self, collection):
results = []
results.extend(list(collection.objects))
for child in collection.children:
results.extend(self.get_all_objects_in_project(child))
return results
def setup_project(self):
bpy.ops.bim.quick_project_setup()
for collection in bpy.data.collections:
2020-11-01 20:08:48 +07:00
if collection.name == "IfcBuildingStorey/Ground Floor":
break
for obj in bpy.context.selected_objects:
2020-11-01 20:08:48 +07:00
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 = []
2020-11-01 20:08:48 +07:00
for collection in self.project["raw"].children:
if not self.is_a_library(self.get_ifc_class(collection.name)):
continue
2020-11-01 20:08:48 +07:00
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 {
2020-11-01 20:08:48 +07:00
"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),
},
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 {
2020-11-01 20:08:48 +07:00
"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,
},
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 {
2020-11-01 20:08:48 +07:00
"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:
2020-11-01 20:08:48 +07:00
obj = selected_element["raw"]
element = {
2020-11-01 20:08:48 +07:00
"ifc": None,
"raw": obj,
"class": self.get_ifc_class(obj.name),
"attributes": self.get_object_attributes(obj),
"address": self.get_address(obj.BIMObjectProperties.address),
}
self.append_product_attributes(element, obj)
self.get_product_psets_qtos(element, obj, is_pset=True)
self.get_product_psets_qtos(element, obj, is_qto=True)
self.get_styled_items_and_surface_styles(element, obj)
elements.append(element)
return elements
def get_groups(self):
elements = []
for selected_element in self.selected_groups:
2020-11-01 20:08:48 +07:00
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"] is False:
continue
self.presentation_layer_assignments.setdefault(representation["presentation_layer"], []).append(
representation
)
def load_representations(self):
if not self.ifc_export_settings.has_representations:
return
self.generated_subcontexts = []
for context in self.ifc_export_settings.context_tree:
2020-11-01 20:08:48 +07:00
for subcontext in context["subcontexts"]:
for target_view in subcontext["target_views"]:
if context["name"] == "Model" and subcontext["name"] == "Box" and target_view == "MODEL_VIEW":
self.generated_subcontexts = "/".join([context["name"], subcontext["name"], target_view])
for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements:
self.prevent_data_name_duplicates(product)
self.load_product_representations(product)
def prevent_data_name_duplicates(self, product):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
return hasattr(obj, "point_cloud_visualizer") and obj.point_cloud_visualizer.uuid
2020-01-12 20:47:06 +11:00
def is_structural(self, obj):
2020-11-01 20:08:48 +07:00
return "IfcStructural" in obj.name
def append_default_representation(self, obj):
2020-11-01 20:08:48 +07:00
self.representations["Model/Body/MODEL_VIEW/{}".format(obj.data.name)] = self.get_representation(
obj.data, obj, "Model", "Body", "MODEL_VIEW"
)
if "Model/Box/MODEL_VIEW" in self.generated_subcontexts:
self.representations["Model/Box/MODEL_VIEW/{}".format(obj.data.name)] = self.get_representation(
obj.data, obj, "Model", "Box", "MODEL_VIEW"
)
def append_point_cloud_representation(self, obj):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
if obj.type == "EMPTY":
self.representations["Model/Reference/GRAPH_VIEW/{}".format(obj.name)] = self.get_representation(
obj, obj, "Model", "Reference", "GRAPH_VIEW"
)
else:
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
for subcontext in context["subcontexts"]:
for target_view in subcontext["target_views"]:
rep_context = self.get_obj_representation_context(obj, context["name"], subcontext["name"], target_view)
if rep_context:
self.append_representation_in_context(obj, rep_context, name)
def get_obj_representation_context(self, obj, context, subcontext, target_view):
for c in obj.BIMObjectProperties.representation_contexts:
if c.context == context and c.name == subcontext and c.target_view == target_view:
return c
if obj.BIMObjectProperties.representation_contexts:
return
if context == "Model" and subcontext == "Body" and target_view == "MODEL_VIEW":
representation_context = obj.BIMObjectProperties.representation_contexts.add()
representation_context.context = "Model"
representation_context.name = "Body"
representation_context.target_view = "MODEL_VIEW"
return representation_context
def append_representation_in_context(self, obj, rep_context, name):
context = rep_context.context
subcontext = rep_context.name
target_view = rep_context.target_view
if self.ifc_export_settings.should_roundtrip_native and rep_context.ifc_definition_id:
self.representations[
"{}/{}/{}/{}".format(context, subcontext, target_view, name)
] = self.get_representation(obj.data, obj, context, subcontext, target_view)
return
2020-11-01 20:08:48 +07:00
context_prefix = "/".join([context, subcontext, target_view])
mesh_name = "/".join([context_prefix, name])
mesh = self.search_for_mesh_or_curve_data(mesh_name)
# TODO: if the search result is empty, we should check for ifc_definition_id
if mesh:
2020-11-01 20:08:48 +07:00
self.representations[mesh_name] = self.get_representation(mesh, obj, context, subcontext, target_view)
if "Model/Box/MODEL_VIEW" in self.generated_subcontexts and context_prefix == "Model/Body/MODEL_VIEW":
self.representations[
"Model/Box/MODEL_VIEW/{}".format(mesh_name.split("/")[3])
] = self.get_representation(obj.data, obj, "Model", "Box", "MODEL_VIEW")
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
elif context_prefix == "Model/Body/MODEL_VIEW" and self.is_point_cloud(obj):
self.append_point_cloud_representation(obj)
2020-11-01 20:08:48 +07:00
elif context_prefix == "Model/Reference/GRAPH_VIEW" and self.is_structural(obj):
self.append_structural_reference_representation(obj)
2020-11-01 20:08:48 +07:00
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):
rep_context = self.get_obj_representation_context(obj, context, subcontext, target_view)
return {
2020-11-01 20:08:48 +07:00
"ifc": None,
"raw": mesh,
"raw_object": obj,
"context": context,
"subcontext": subcontext,
"target_view": target_view,
"has_ifc_definition": rep_context and rep_context.ifc_definition_id,
2020-11-01 20:08:48 +07:00
"ifc_definition": mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, "BIMMeshProperties") else None,
"ifc_definition_id": rep_context.ifc_definition_id if rep_context else 0
2020-11-01 20:08:48 +07:00
if hasattr(mesh, "BIMMeshProperties")
else None,
"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": self.is_wireframe_mesh(mesh, obj),
"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,
"presentation_layer": mesh.BIMMeshProperties.presentation_layer_index
if hasattr(mesh, "BIMMeshProperties") and mesh.BIMMeshProperties.presentation_layer_index != -1
else False,
"attributes": {"Name": mesh.name if mesh else ""},
2019-11-17 13:25:41 +01:00
}
def is_wireframe_mesh(self, mesh, obj):
if isinstance(mesh, bpy.types.Mesh) and not mesh.polygons:
modifiers = [m.type for m in obj.modifiers]
# SCREW and SKIN can create faces, so it is not a wireframe mesh
2020-11-01 20:08:48 +07:00
if "SCREW" not in modifiers and "SKIN" not in modifiers:
return True
if isinstance(mesh, bpy.types.Curve) and not mesh.bevel_object and not mesh.bevel_depth:
return True
return False
def is_mesh_context_sensitive(self, name):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
return name.split("/")[3]
return name
def get_materials_and_surface_styles(self):
2019-09-09 15:11:07 +10:00
if not self.ifc_export_settings.has_representations:
return
for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements:
2020-11-01 20:08:48 +07:00
obj = product["raw"]
if obj.BIMObjectProperties.material_type == "IfcMaterial" and obj.BIMObjectProperties.material:
self.get_material(obj.BIMObjectProperties.material)
elif obj.BIMObjectProperties.material_type == "IfcMaterialConstituentSet":
for constituent in obj.BIMObjectProperties.material_set.material_constituents:
self.get_material(constituent.material)
elif obj.BIMObjectProperties.material_type == "IfcMaterialLayerSet":
for layer in obj.BIMObjectProperties.material_set.material_layers:
self.get_material(layer.material)
elif obj.BIMObjectProperties.material_type == "IfcMaterialProfileSet":
for profile in obj.BIMObjectProperties.material_set.material_profiles:
self.get_material(profile.material)
def get_material(self, material):
if material.name in self.materials:
return
data = {
"ifc": None,
"raw": material,
"attributes": self.get_material_attributes(material),
}
self.surface_styles[material.name] = {"ifc": None, "raw": material}
self.materials[material.name] = data
self.get_material_psets(data, material)
2019-09-09 15:11:07 +10:00
2020-01-14 15:38:22 +11:00
def get_material_attributes(self, material):
2020-11-01 20:08:48 +07:00
attributes = {"Name": material.name}
2020-01-14 15:38:22 +11:00
attributes.update({a.name: a.string_value for a in material.BIMMaterialProperties.attributes})
return attributes
def get_styled_items_and_surface_styles(self, element, obj):
if not self.ifc_export_settings.has_representations:
return
if obj.data is None:
return
for slot in obj.material_slots:
if slot.material is None:
continue
self.surface_styles[slot.material.name] = {"ifc": None, "raw": slot.material}
self.styled_items.append(
{
"ifc": None,
"raw": slot.material,
"related_element": element,
"attributes": {"Name": slot.material.name},
}
)
2020-08-14 18:23:24 +10:00
def get_grid_axes(self):
results = {}
for selected_axis in self.selected_grid_axes:
2020-11-01 20:08:48 +07:00
obj = selected_axis["raw"]
2020-08-14 18:23:24 +10:00
grid_raw = bpy.data.objects.get(self.get_parent_collection(obj.users_collection[0]).name)
if grid_raw.name not in results:
2020-11-01 20:08:48 +07:00
results[grid_raw.name] = {"UAxes": [], "VAxes": [], "WAxes": []}
if "UAxes" in obj.users_collection[0].name:
axis_type = "UAxes"
elif "VAxes" in obj.users_collection[0].name:
axis_type = "VAxes"
else:
2020-11-01 20:08:48 +07:00
axis_type = "WAxes"
results[grid_raw.name][axis_type].append(
{
"ifc": None,
"raw": obj,
"grid_raw": grid_raw,
"class": "IfcGridAxis",
"attributes": {a.name: a.string_value for a in obj.BIMObjectProperties.attributes},
}
)
2020-08-14 18:23:24 +10:00
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):
2020-11-01 20:08:48 +07:00
names.append("Model/Body/MODEL_VIEW/{}".format(obj.name))
return names
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
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 = []
2020-11-01 20:08:48 +07:00
if parent["raw"].name not in bpy.data.collections:
return children
for reference, element in enumerate(self.spatial_structure_elements):
2020-11-01 20:08:48 +07:00
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)})
return children
def get_spatial_structure_element_reference(self, name):
2020-11-01 20:08:48 +07:00
return [e["raw"].name for e in self.spatial_structure_elements].index(name)
def get_group_reference(self, name):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
return name.split("/")[0]
def get_ifc_name(self, name):
try:
2020-11-01 20:08:48 +07:00
return name.split("/")[1]
except IndexError:
2019-11-17 13:25:41 +01:00
self.ifc_export_settings.logger.error(
2020-11-01 20:08:48 +07:00
'Name "{}" does not follow the format of "IfcClass/Name"'.format(name)
)
def get_name_attribute(self, obj):
2020-11-01 20:08:48 +07:00
name = obj.BIMObjectProperties.attributes.get("Name")
if name:
return name.string_value
return self.get_ifc_name(obj.name)
2020-08-14 18:23:24 +10:00
def is_a_grid_axis(self, class_name):
2020-11-01 20:08:48 +07:00
return class_name == "IfcGridAxis"
2020-08-14 18:23:24 +10:00
def is_a_spatial_structure_element(self, class_name):
return class_name in [
2020-11-01 20:08:48 +07:00
"IfcBuilding",
"IfcBuildingStorey",
"IfcExternalSpatialElement",
"IfcSite",
"IfcSpace",
"IfcSpatialZone",
]
def is_a_rel_aggregates(self, class_name):
2020-11-01 20:08:48 +07:00
return class_name == "IfcRelAggregates"
def is_a_project(self, class_name):
2020-11-01 20:08:48 +07:00
return class_name == "IfcProject"
def is_a_library(self, class_name):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
class IfcExporter:
def __init__(self, ifc_export_settings, ifc_parser):
2020-11-01 20:08:48 +07:00
self.template_file = "{}template.ifc".format(ifc_export_settings.schema_dir)
self.ifc_export_settings = ifc_export_settings
self.ifc_parser = ifc_parser
self.migrator = ifcopenshell.util.schema.Migrator()
self.roundtrip_id_new_to_old = {}
2019-08-29 23:11:12 +10:00
2020-04-09 02:42:03 +02:00
def export(self, selected_objects):
self.file = ifc.IfcStore.get_file()
if self.file and self.ifc_export_settings.should_export_from_memory:
return self.write_ifc_file()
self.schema_version = self.ifc_export_settings.schema
self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.schema_version)
self.file = ifcopenshell.file(schema=self.schema_version)
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()
2020-08-14 18:23:24 +10:00
self.create_grid_axes()
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-11-01 20:08:48 +07:00
for set_type in ["constituent", "layer", "profile"]:
2020-01-14 16:11:11 +11:00
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(
2020-11-01 20:08:48 +07:00
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
self.file.createIfcDirection((0.0, 0.0, 1.0)),
self.file.createIfcDirection((1.0, 0.0, 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)
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
if self.schema_version == "IFC2X3":
self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id
else:
2020-11-01 20:08:48 +07:00
self.file.wrapped_data.header.file_name.authorization = (
self.owner_history.OwningUser.ThePerson.Identification
)
else:
2020-11-01 20:08:48 +07:00
self.file.wrapped_data.header.file_name.authorization = "Nobody"
def get_application_name(self):
2020-11-01 20:08:48 +07:00
return "BlenderBIM"
def get_application_version(self):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
if self.schema_version == "IFC2X3" and person["ifc"].Id == bpy.context.scene.BIMProperties.person:
break
2020-11-01 20:08:48 +07:00
elif person["ifc"].Identification == bpy.context.scene.BIMProperties.person:
break
for organisation in self.ifc_parser.organisations:
2020-11-01 20:08:48 +07:00
if organisation["ifc"].Name == bpy.context.scene.BIMProperties.organisation:
break
if not person or not organisation:
self.owner_history = None
return
2020-11-01 20:08:48 +07:00
person_and_organisation = self.file.create_entity(
"IfcPersonAndOrganization",
**{"ThePerson": person["ifc"], "TheOrganization": organisation["ifc"], "Roles": None}, # TODO
)
developer_organisation = self.get_application_organisation()
2020-11-01 20:08:48 +07:00
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():
2020-11-01 20:08:48 +07:00
if data["is_metric"]:
data["ifc"] = self.create_metric_unit(unit_type, data)
2019-09-10 17:08:45 +10:00
else:
2020-11-01 20:08:48 +07:00
data["ifc"] = self.create_imperial_unit(unit_type, data)
self.file.createIfcUnitAssignment([u["ifc"] for u in self.ifc_parser.units.values()])
2019-09-10 17:08:45 +10:00
2019-11-18 05:46:52 +01:00
def create_metric_unit(self, unit_type, data):
2020-11-01 20:08:48 +07:00
type_prefix = ""
if unit_type == "area":
type_prefix = "SQUARE_"
elif unit_type == "volume":
type_prefix = "CUBIC_"
2019-11-18 05:46:52 +01:00
return self.file.createIfcSIUnit(
None,
2020-11-01 20:08:48 +07:00
"{}UNIT".format(unit_type.upper()),
SIUnitHelper.get_prefix(data["raw"]),
type_prefix + SIUnitHelper.get_unit_name(data["raw"]),
2019-11-18 05:46:52 +01:00
)
def create_imperial_unit(self, unit_type, data):
2020-11-01 20:08:48 +07:00
if unit_type == "length":
2019-11-07 16:40:57 +11:00
dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0)
2020-11-01 20:08:48 +07:00
name_prefix = ""
elif unit_type == "area":
2019-11-07 16:40:57 +11:00
dimensional_exponents = self.file.createIfcDimensionalExponents(2, 0, 0, 0, 0, 0, 0)
2020-11-01 20:08:48 +07:00
name_prefix = "square"
elif unit_type == "volume":
2019-11-07 16:40:57 +11:00
dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0)
2020-11-01 20:08:48 +07:00
name_prefix = "cubic"
2019-11-18 05:46:52 +01:00
si_unit = self.file.createIfcSIUnit(
None,
2020-11-01 20:08:48 +07:00
"{}UNIT".format(unit_type.upper()),
2019-11-18 05:46:52 +01:00
None,
2020-11-01 20:08:48 +07:00
"{}METRE".format(name_prefix.upper() + "_" if name_prefix else ""),
2019-11-18 05:46:52 +01:00
)
2020-11-01 20:08:48 +07: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 "")
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(
2020-11-01 20:08:48 +07:00
dimensional_exponents, "{}UNIT".format(unit_type.upper()), name, conversion_factor
2019-11-18 05:46:52 +01:00
)
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:
2020-11-01 20:08:48 +07:00
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_version == "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:
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
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:
results.append(self.create_address(address))
2019-11-15 15:44:04 +11:00
return results
def create_address(self, address):
2020-11-01 20:08:48 +07:00
if self.schema_version == "IFC2X3" and "MessagingIDs" in address["attributes"]:
del address["attributes"]["MessagingIDs"]
return self.file.create_entity(
"IfcPostalAddress" if address["is_postal"] else "IfcTelecomAddress", **address["attributes"]
)
def create_library_information(self):
information = self.ifc_parser.library_information
if not information:
return
2020-11-01 20:08:48 +07:00
information["attributes"]["Publisher"] = self.owner_history.OwningUser
information["ifc"] = self.file.create_entity("IfcLibraryInformation", **information["attributes"])
self.file.createIfcRelAssociatesLibrary(
2020-11-01 20:08:48 +07:00
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():
2020-11-01 20:08:48 +07:00
information["ifc"] = self.file.create_entity("IfcDocumentInformation", **information["attributes"])
def create_document_references(self):
for reference in self.ifc_parser.document_references.values():
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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():
if self.file.schema == "IFC4":
classification["ifc"] = self.file.add(classification["raw_element"])
else:
# TODO: Check if we can use self.migrator instead
migrator = ifcopenshell.util.schema.Migrator()
classification["ifc"] = migrator.migrate(classification["raw_element"], self.file)
self.file.createIfcRelAssociatesClassification(
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
[self.ifc_parser.project["ifc"]],
classification["ifc"],
2020-11-01 20:08:48 +07:00
)
def create_classification_references(self):
for reference in self.ifc_parser.classification_references.values():
if self.file.schema == "IFC4":
reference["ifc"] = self.file.add(reference["raw_element"])
else:
# TODO: Check if we can use self.migrator instead
migrator = ifcopenshell.util.schema.Migrator()
reference["ifc"] = migrator.migrate(reference["raw_element"], self.file)
def create_constraints(self):
for constraint in self.ifc_parser.constraints.values():
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
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 in self.ifc_parser.material_psets.values():
properties = self.create_pset_properties(pset)
if not properties:
continue
pset["attributes"].update({"Properties": properties, "Material": pset["material"]["ifc"]})
pset["ifc"] = self.file.create_entity("IfcMaterialProperties", **pset["attributes"])
def create_qto_properties(self, qto):
if qto["attributes"]["Name"] in ifcopenshell.util.pset.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 ifcopenshell.util.pset.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 = []
2020-11-01 20:08:48 +07:00
for key, value in pset["raw"].items():
2019-10-19 14:45:07 +11:00
properties.append(
2020-11-01 20:08:48 +07:00
self.file.create_entity(
"IfcPropertySingleValue",
**{"Name": key, "NominalValue": self.file.create_entity("IfcLabel", value)},
)
)
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 = []
2020-11-01 20:08:48 +07:00
for key, value in qto["raw"].items():
if "Area" in key:
quantity_type = "Area"
elif "Volume" in key:
quantity_type = "Volume"
2020-05-14 21:37:33 +10:00
else:
2020-11-01 20:08:48 +07:00
quantity_type = "Length"
2020-05-14 21:37:33 +10:00
properties.append(
2020-11-01 20:08:48 +07:00
self.file.create_entity(
f"IfcQuantity{quantity_type}", **{"Name": key, f"{quantity_type}Value": float(value)}
)
)
2020-05-14 21:37:33 +10:00
return properties
2019-10-19 14:45:07 +11:00
def create_templated_pset_properties(self, pset):
properties = []
templates = ifcopenshell.util.pset.psets[pset["attributes"]["Name"]]["HasPropertyTemplates"]
for name, data in templates.items():
2020-11-01 20:08:48 +07:00
if name not in pset["raw"]:
continue
2020-11-01 20:08:48 +07:00
if data.TemplateType == "P_SINGLEVALUE" or data.TemplateType == "P_ENUMERATEDVALUE":
if data.PrimaryMeasureType:
value_type = data.PrimaryMeasureType
else:
# The IFC spec is missing some, so we provide a fallback
2020-11-01 20:08:48 +07:00
value_type = "IfcLabel"
nominal_value = self.file.create_entity(
2020-11-01 20:08:48 +07:00
value_type, self.cast_to_base_type(value_type, pset["raw"][name])
)
properties.append(
2020-11-01 20:08:48 +07:00
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()]
2019-10-07 22:14:52 +11:00
if invalid_pset_keys:
self.ifc_export_settings.logger.error(
2020-11-01 20:08:48 +07:00
"One or more properties were invalid in the pset {}: {}".format(
pset["attributes"]["Name"], invalid_pset_keys
)
)
return properties
def create_templated_qto_properties(self, qto):
properties = []
templates = ifcopenshell.util.pset.qtos[qto["attributes"]["Name"]]["HasPropertyTemplates"]
for name, data in templates.items():
2020-11-01 20:08:48 +07:00
if name not in qto["raw"]:
continue
2020-11-01 20:08:48 +07:00
if data.TemplateType[0:2] == "Q_":
value_basename = data.TemplateType[2:].title()
2020-11-01 20:08:48 +07:00
value_name = f"{value_basename}Value"
class_name = f"IfcQuantity{value_basename}"
properties.append(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
"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
2020-11-01 20:08:48 +07:00
elif schema.ifc.type_map[var_type] == "float":
return float(value)
2020-11-01 20:08:48 +07:00
elif schema.ifc.type_map[var_type] == "integer":
return int(value)
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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?
2020-11-01 20:08:48 +07:00
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"]
2019-11-18 05:46:52 +01:00
)
def create_target_crs(self):
2020-11-01 20:08:48 +07:00
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,
2020-11-01 20:08:48 +07:00
"LENGTHUNIT",
SIUnitHelper.get_prefix(self.ifc_parser.target_crs["attributes"]["MapUnit"]),
SIUnitHelper.get_unit_name(self.ifc_parser.target_crs["attributes"]["MapUnit"]),
)
2020-11-01 20:08:48 +07:00
self.ifc_parser.target_crs["ifc"] = self.file.create_entity(
"IfcProjectedCRS", **self.ifc_parser.target_crs["attributes"]
2019-11-18 05:46:52 +01:00
)
2019-09-01 17:23:48 +10:00
def create_type_products(self):
for product in self.ifc_parser.type_products:
2020-11-01 20:08:48 +07:00
self.cast_attributes(product["class"], product["attributes"])
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
product["ifc"] = self.file.create_entity(product["class"], **product["attributes"])
2019-09-01 17:23:48 +10:00
except RuntimeError as e:
product["ifc"] = self.create_ifc_entity(product)
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)
2020-11-01 20:08:48 +07:00
product["attributes"].setdefault("HasPropertySets", [])
2019-09-29 22:29:06 +10:00
for attribute in attributes:
2020-11-01 20:08:48 +07:00
product["attributes"]["HasPropertySets"].append(attribute["ifc"])
2019-09-29 22:29:06 +10:00
def create_predefined_attributes(self, attributes):
2019-09-29 22:12:51 +10:00
for attribute in attributes:
2020-11-01 20:08:48 +07:00
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()},
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(
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
related_objects = [
self.ifc_parser.products[o]["ifc"] for o in self.ifc_parser.aggregates[related_objects_reference]
]
self.file.createIfcRelAggregates(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
relating_object["attributes"]["Name"],
None,
relating_object["ifc"],
related_objects,
)
for obj in related_objects:
2020-11-01 20:08:48 +07:00
obj.ObjectPlacement.PlacementRelTo = relating_object["ifc"].ObjectPlacement
def create_spatial_structure_elements(self, element_tree, relating_object=None):
if relating_object == None:
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
element = self.ifc_parser.spatial_structure_elements[node["reference"]]
2020-11-01 20:08:48 +07:00
if element["has_scale"]:
# Omission of the relative placement here is not as per implementer agreements
placement = self.file.createIfcLocalPlacement(None, self.origin)
else:
placement = self.file.createIfcLocalPlacement(
2020-11-01 20:08:48 +07:00
placement_rel_to, self.get_relative_placement(element, placement_rel_to)
)
self.cast_attributes(element["class"], element["attributes"])
element["attributes"].update(
{
"OwnerHistory": self.owner_history, # TODO: unhardcode
"ObjectPlacement": placement,
"Representation": self.get_product_shape(element),
}
)
if element["class"] == "IfcSite":
element["attributes"].update({"SiteAddress": self.create_address(element["address"])})
elif element["class"] == "IfcBuilding":
element["attributes"].update({"BuildingAddress": self.create_address(element["address"])})
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(
2020-11-01 20:08:48 +07:00
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()
2020-11-01 20:08:48 +07:00
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),
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
return self.a2p(o, z, x)
def create_groups(self):
for group in self.ifc_parser.groups:
2020-11-01 20:08:48 +07:00
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:
self.process_styled_item(styled_item)
def process_styled_item(self, styled_item):
product = styled_item["related_element"]
if not product["ifc"].Representation:
return
material_slots = []
# This is a simplification, which works since we are currently in a controlled environment where the
# BlenderBIM Add-on controls how data is structured during export. When we implement full IFC
# round-tripping, this simplification can no longer apply.
for representation in product["ifc"].Representation.Representations:
# At the moment, we assume that styled items only apply to the body context.
if representation.RepresentationIdentifier != "Body":
continue
rep_context = self.ifc_parser.get_obj_representation_context(product["raw"], "Model", "Body", "MODEL_VIEW")
if self.ifc_export_settings.should_roundtrip_native and rep_context and rep_context.ifc_definition_id:
# For native roundtripping, each slot could be a one to many relationship to items
for item in self.get_geometric_representation_items(representation):
original_id = self.roundtrip_id_new_to_old[item.id()]
i = product["raw"].data.BIMMeshProperties.ifc_item_ids.get(str(original_id)).slot_index
material_slots.append((product["raw"].material_slots[i].name, item))
else:
# For Blender, each slot represents a geometric representation item
for i, item in enumerate(self.get_geometric_representation_items(representation)):
if i >= len(product["raw"].material_slots):
i = 0
material_slots.append((product["raw"].material_slots[i].name, item))
for styled_item_name, representation_item in material_slots:
if styled_item_name == styled_item["attributes"]["Name"]:
styled_item["ifc"] = self.create_styled_item(styled_item, representation_item)
def get_geometric_representation_items(self, representation):
results = []
for item in representation.Items:
if item.is_a("IfcGeometricRepresentationItem"):
results.append(item)
elif item.is_a("IfcMappedItem"):
results.extend(self.get_geometric_representation_items(item.MappingSource.MappedRepresentation))
return results
def create_styled_item(self, styled_item, representation_item=None):
surface_style = self.ifc_parser.surface_styles[styled_item["raw"].name]
if not surface_style["ifc"]:
styles = []
styles.append(self.create_surface_style_rendering(styled_item))
if styled_item["raw"].BIMMaterialProperties.is_external:
styles.append(
self.file.create_entity(
"IfcExternallyDefinedSurfaceStyle", **self.get_material_external_definition(styled_item["raw"])
)
2020-11-01 20:08:48 +07:00
)
# Name is filled out because Revit treats this incorrectly as the material name
surface_style["ifc"] = self.file.createIfcSurfaceStyle(styled_item["attributes"]["Name"], "BOTH", styles)
if self.schema_version == "IFC2X3" or self.ifc_export_settings.should_use_presentation_style_assignment:
surface_style["ifc"] = self.file.createIfcPresentationStyleAssignment([surface_style["ifc"]])
return self.file.createIfcStyledItem(
representation_item, [surface_style["ifc"]], styled_item["attributes"]["Name"]
)
def create_presentation_layer_assignments(self):
for layer_index, representations in self.ifc_parser.presentation_layer_assignments.items():
layer = bpy.context.scene.BIMProperties.presentation_layers[int(layer_index)]
assigned_items = []
for representation in representations:
assigned_items.append(representation["ifc"])
if layer.layer_on:
self.file.createIfcPresentationLayerAssignment(
layer.name, layer.description or None, assigned_items, layer.identifier or None,
)
else:
self.file.createIfcPresentationLayerWithStyle(
layer.name,
layer.description or None,
assigned_items,
layer.identifier or None,
layer.layer_on,
layer.layer_frozen,
layer.layer_blocked,
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(
2020-11-01 20:08:48 +07:00
self.ifc_rep_context["Model"]["Body"]["MODEL_VIEW"]["ifc"], None, None, [styled_item]
)
if self.schema_version == "IFC2X3":
material["ifc"] = self.file.createIfcMaterial(material["attributes"]["Name"])
else:
material["ifc"] = self.file.create_entity("IfcMaterial", **material["attributes"])
self.create_material_psets(material)
2019-09-09 15:11:07 +10:00
self.file.createIfcMaterialDefinitionRepresentation(
material["attributes"]["Name"], None, [styled_representation], material["ifc"]
2020-11-01 20:08:48 +07:00
)
def create_material_profile_def(self, profile):
ifc_class = profile.profile
attributes = {a.name: a.string_value for a in profile.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):
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
surface_colour = self.create_colour_rgb(styled_item["raw"].diffuse_color)
2020-08-09 17:13:01 +10:00
rendering_attributes = {
2020-11-01 20:08:48 +07:00
"SurfaceColour": surface_colour,
"Transparency": (styled_item["raw"].diffuse_color[3] - 1) * -1,
"ReflectanceMethod": "NOTDEFINED",
2020-08-09 17:13:01 +10:00
}
2020-11-01 20:08:48 +07:00
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-11-01 20:08:48 +07:00
if (
not material.use_nodes
or not hasattr(material.node_tree, "nodes")
or "Principled BSDF" not in material.node_tree.nodes
):
return {}
2020-11-01 20:08:48 +07:00
bsdf = material.node_tree.nodes["Principled BSDF"]
return {
2020-11-01 20:08:48 +07:00
"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 {
2020-11-01 20:08:48 +07:00
"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():
self.create_representation(representation)
2020-08-14 18:23:24 +10:00
def create_grid_axes(self):
for uvw in self.ifc_parser.grid_axes.values():
for axes in uvw.values():
for axis in axes:
self.create_grid_axis(axis)
def create_grid_axis(self, axis):
2020-11-01 20:08:48 +07:00
points = [
axis["grid_raw"].matrix_world.inverted() @ (axis["raw"].matrix_world @ v.co)
for v in axis["raw"].data.vertices[0:2]
]
self.cast_attributes("IfcGridAxis", axis["attributes"])
axis["attributes"]["AxisCurve"] = self.file.createIfcPolyline(
[
self.create_cartesian_point(points[0][0], points[0][1], points[0][2]),
self.create_cartesian_point(points[1][0], points[1][1], points[1][2]),
]
)
axis["ifc"] = self.file.create_entity("IfcGridAxis", **axis["attributes"])
2020-08-14 18:23:24 +10:00
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
2020-11-01 20:08:48 +07:00
qto["attributes"].update(
{"GlobalId": ifcopenshell.guid.new(), "OwnerHistory": self.owner_history, "Quantities": properties}
)
qto["ifc"] = self.file.create_entity("IfcElementQuantity", **qto["attributes"])
def create_product(self, product):
2020-11-01 20:08:48 +07:00
if self.schema.declaration_by_name(product["class"]).is_abstract():
self.ifc_export_settings.logger.error(
'The product "{}/{}" class is abstract and could not be created'.format(
2020-11-01 20:08:48 +07:00
product["class"], product["attributes"]["Name"]
)
)
return
2020-11-01 20:08:48 +07:00
if product["relating_structure"] is not None:
placement_rel_to = self.ifc_parser.spatial_structure_elements[product["relating_structure"]][
"ifc"
].ObjectPlacement
elif product["relating_host"] is not None:
# TODO: this could be unsafe if the host is not yet created, so we
# should consider migrating it such that the placement rel to is set
# as the relationship creation stage, like how IfcRelAggregates for
# object aggregates work.
2020-11-01 20:08:48 +07:00
placement_rel_to = self.ifc_parser.products[product["relating_host"]]["ifc"].ObjectPlacement
else:
placement_rel_to = None
2020-11-01 20:08:48 +07: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:
2020-11-01 20:08:48 +07:00
placement = self.file.createIfcLocalPlacement(
placement_rel_to, self.get_relative_placement(product, placement_rel_to)
)
2020-11-01 20:08:48 +07:00
self.cast_attributes(product["class"], product["attributes"])
2020-11-01 20:08:48 +07:00
product["attributes"].update(
{
"OwnerHistory": self.owner_history, # TODO: unhardcode
"ObjectPlacement": placement,
"Representation": self.get_product_shape(product),
}
)
2020-11-01 20:08:48 +07:00
if product["has_boundary_condition"]:
ifc_class = product["boundary_condition_class"]
attributes = product["boundary_condition_attributes"]
for key, value in attributes.items():
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
product["attributes"]["AppliedCondition"] = boundary_condition
2020-11-01 20:08:48 +07:00
if product["class"] == "IfcGrid":
name = "IfcGrid/" + product["attributes"]["Name"]
product["attributes"]["UAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["UAxes"]]
product["attributes"]["VAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["VAxes"]]
if self.ifc_parser.grid_axes[name]["WAxes"]:
product["attributes"]["WAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["WAxes"]]
2020-08-14 18:23:24 +10:00
try:
2020-11-01 20:08:48 +07:00
product["ifc"] = self.file.create_entity(product["class"], **product["attributes"])
except RuntimeError as e:
product["ifc"] = self.create_ifc_entity(product)
def create_ifc_entity(self, data):
result = self.file.create_entity(data["class"])
for key, value in data["attributes"].items():
try:
setattr(result, key, value)
except RuntimeError as e:
self.ifc_export_settings.logger.error(
'The entity "{}/{}" attribute {} with value {} could not be created: {}'.format(
data["class"], data["attributes"]["Name"], key, value, e.args
)
2020-11-01 20:08:48 +07:00
)
return result
def get_product_attribute_type(self, product_class, attribute_name):
element_schema = schema.ifc.elements[product_class]
2020-11-01 20:08:48 +07:00
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]
2020-11-01 20:08:48 +07:00
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 = []
2020-11-01 20:08:48 +07:00
for representation_name in product["representations"]:
representation = self.ifc_parser.representations[representation_name]
2020-11-01 20:08:48 +07:00
if self.ifc_export_settings.should_roundtrip_native and representation["has_ifc_definition"]:
pass
else:
self.get_product_mapped_geometry(product, representation)
results.append(representation["ifc"])
2019-11-18 03:45:24 +01:00
return results
def get_product_mapped_geometry(self, product, representation):
mapping_source = representation["ifc_map"]
2019-11-24 18:12:33 +11:00
shape_representation = mapping_source.MappedRepresentation
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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,
)
2019-11-24 18:12:33 +11:00
else:
mapping_target = self.file.createIfcCartesianTransformationOperator3D(
2020-11-01 20:08:48 +07:00
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)
representation["ifc"] = self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07:00
shape_representation.ContextOfItems,
shape_representation.RepresentationIdentifier,
"MappedRepresentation",
[mapped_item],
)
2019-11-18 03:45:24 +01:00
2020-08-22 19:11:17 +10:00
def create_ifc_axis_2_placement_2d(self, point, forward):
return self.file.createIfcAxis2Placement2D(
2020-11-01 20:08:48 +07:00
self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y))
)
2020-08-22 19:11:17 +10:00
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)),
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
if self.ifc_export_settings.should_roundtrip_native and representation["has_ifc_definition"]:
representation["ifc"] = self.create_representation_from_definition(representation)
return
2019-09-07 08:05:42 +10:00
self.ifc_vertices = []
self.ifc_edges = []
2020-11-01 20:08:48 +07:00
if representation["context"] == "Model":
representation["ifc_map"] = self.create_model_representation(representation)
2020-11-01 20:08:48 +07:00
elif representation["context"] == "Plan":
representation["ifc_map"] = self.create_plan_representation(representation)
2020-11-01 20:08:48 +07:00
elif representation["context"] == "NotDefined":
representation["ifc_map"] = self.create_variable_representation(representation)
def create_representation_from_definition(self, representation):
2020-11-01 20:08:48 +07:00
if representation["ifc_definition"]:
print("Authoring an IFC definition directly is not yet implemented")
return
if representation["ifc_definition_id"]:
return self.create_representation_from_definition_id(representation)
def create_representation_from_definition_id(self, representation):
if self.file.schema == ifc.IfcStore.get_file().schema:
entry = self.file.add(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]))
else:
entry = self.migrator.migrate(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]), self.file)
substitutions = {"contexts": []}
representation_elements = ifc.IfcStore.get_file().traverse(
ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"])
)
for element in representation_elements:
if self.file.schema == ifc.IfcStore.get_file().schema:
added_element = self.file.add(element)
else:
added_element = self.migrator.migrate(element, self.file)
if added_element.is_a("IfcGeometricRepresentationContext"):
substitutions["contexts"].append(added_element)
elif added_element.is_a("IfcGeometricRepresentationItem"):
self.roundtrip_id_new_to_old[added_element.id()] = element.id()
for element in substitutions["contexts"]:
new_element = self.ifc_rep_context[representation["context"]][representation["subcontext"]][
representation["target_view"]
]["ifc"]
for inverse in self.file.get_inverse(element):
ifcopenshell.util.element.replace_attribute(inverse, element, new_element)
# TODO: Work out how and when to purge this
# self.file.remove(element)
return entry
def create_model_representation(self, representation):
2020-11-01 20:08:48 +07:00
if representation["subcontext"] == "Annotation":
return self.file.createIfcRepresentationMap(
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "Box":
return self.file.createIfcRepresentationMap(self.origin, self.create_box_representation(representation))
elif representation["subcontext"] == "Clearance":
return self.create_variable_representation(representation)
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "CoG":
return self.file.createIfcRepresentationMap(self.origin, self.create_cog_representation(representation))
elif representation["subcontext"] == "FootPrint":
return self.create_variable_representation(representation)
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "Reference":
if representation["target_view"] == "GRAPH_VIEW":
return self.file.createIfcRepresentationMap(
2020-11-01 20:08:48 +07:00
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":
return self.file.createIfcRepresentationMap(
2020-11-01 20:08:48 +07:00
self.origin, self.create_geometric_curve_set_representation(representation)
)
def create_plan_representation(self, representation):
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
shape_representation.RepresentationType = "Annotation2D"
return self.file.createIfcRepresentationMap(self.origin, shape_representation)
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "Axis":
return self.file.createIfcRepresentationMap(self.origin, self.create_curve2d_representation(representation))
elif representation["subcontext"] == "Body":
pass
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "Box":
pass
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "Clearance":
pass
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "CoG":
pass
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "Profile":
pass
2020-11-01 20:08:48 +07:00
elif representation["subcontext"] == "SurveyPoints":
pass
def create_variable_representation(self, representation):
2020-11-01 20:08:48 +07:00
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"]:
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):
2020-11-01 20:08:48 +07:00
obj = representation["raw_object"]
bounding_box = self.file.createIfcBoundingBox(
2020-11-01 20:08:48 +07:00
self.create_cartesian_point(obj.bound_box[0][0], obj.bound_box[0][1], obj.bound_box[0][2]),
self.convert_si_to_unit(obj.dimensions[0]),
self.convert_si_to_unit(obj.dimensions[1]),
2020-11-01 20:08:48 +07:00
self.convert_si_to_unit(obj.dimensions[2]),
2019-11-18 05:46:52 +01:00
)
return self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07:00
self.ifc_rep_context[representation["context"]][representation["subcontext"]][
representation["target_view"]
]["ifc"],
representation["subcontext"],
"BoundingBox",
[bounding_box],
)
def create_cog_representation(self, representation):
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
self.ifc_rep_context[representation["context"]][representation["subcontext"]][
representation["target_view"]
]["ifc"],
representation["subcontext"],
"BoundingBox",
[cog],
)
def create_text_representation(self, representation):
return self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
self.ifc_rep_context[representation["context"]][representation["subcontext"]][
representation["target_view"]
]["ifc"],
representation["subcontext"],
"Curve",
self.create_curves(representation["raw"]),
)
def create_geometric_set_representation(self, representation, is_2d=False):
2020-11-01 20:08:48 +07:00
geometric_curve_set = self.file.createIfcGeometricSet(self.create_curves(representation["raw"], is_2d=is_2d))
return self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
geometric_curve_set = self.file.createIfcGeometricCurveSet(
self.create_curves(representation["raw"], is_2d=is_2d)
)
return self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07: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(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
if representation["raw_object"].type == "EMPTY":
return self.file.createIfcTopologyRepresentation(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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 = []
2020-11-01 20:08:48 +07:00
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:
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
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 = []
2020-11-01 20:08:48 +07:00
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-11-01 20:08:48 +07:00
tilt_matrix = Matrix.Rotation(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(points[1].co, unit_direction, x_axis)
swept_area_solids.append(
self.file.createIfcExtrudedAreaSolid(
swept_area,
position,
self.file.createIfcDirection((0.0, 0.0, 1.0)),
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):
2020-11-01 20:08:48 +07:00
if not element.is_a("IfcCartesianPoint"):
continue
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
if hasattr(curve, "splines"):
points = self.get_spline_points(curve.splines[0])
else:
points = curve.vertices
if not points:
return
2020-11-01 20:08:48 +07:00
return self.file.createIfcEdge(self.create_vertex_point(points[0].co), self.create_vertex_point(points[1].co))
def create_text(self, text):
2020-11-01 20:08:48 +07:00
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 ...
2020-11-01 20:08:48 +07:00
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):
curves = []
points = self.create_cartesian_point_list_from_vertices(mesh.vertices, is_2d=is_2d)
edge_loops = []
previous_edge = None
edge_loop = []
for edge in mesh.edges:
2020-11-01 20:08:48 +07:00
if (Vector(points.CoordList[edge.vertices[0]]) - Vector(points.CoordList[edge.vertices[1]])).length < 0.001:
# Maybe we should warn the user to weld vertices in this scenario?
continue
elif previous_edge is None:
2020-11-01 20:08:48 +07:00
edge_loop = [self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))]
elif edge.vertices[0] == previous_edge.vertices[1]:
2020-11-01 20:08:48 +07:00
edge_loop.append(self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1)))
else:
edge_loops.append(edge_loop)
2020-11-01 20:08:48 +07:00
edge_loop = [self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))]
previous_edge = edge
edge_loops.append(edge_loop)
for edge_loop in edge_loops:
curves.append(self.file.createIfcIndexedPolyCurve(points, edge_loop))
return curves
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:
2020-11-01 20:08:48 +07:00
points.append(self.create_cartesian_point(point.co.x, point.co.y))
else:
2020-11-01 20:08:48 +07:00
points.append(self.create_cartesian_point(point.co.x, point.co.y, point.co.z))
for point in spline.points:
if is_2d:
2020-11-01 20:08:48 +07:00
points.append(self.create_cartesian_point(point.co.x, point.co.y))
else:
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
obj = representation["raw_object"]
items = {}
for index, vg in enumerate(obj.vertex_groups):
2020-11-01 20:08:48 +07:00
components = vg.name.split("/")
key = components[1]
2020-11-01 20:08:48 +07:00
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():
2020-11-01 20:08:48 +07:00
if item["name"] == "IfcExtrudedAreaSolid":
ifc_items.append(self.create_native_extruded_area_solid(obj, item))
2020-11-01 20:08:48 +07:00
elif item["name"] == "IfcFacetedBrep":
2020-06-20 21:23:15 +10:00
# TODO: check if we allow representation item type mixing
return self.create_solid_representation(representation)
return self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
extrusion_edge = self.get_edges_in_v_indices(obj, item["subitems"]["ExtrudedDirection"])[0]
2020-11-01 20:08:48 +07:00
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)
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
(obj.data.vertices[outer_curve_loop[0]].co - obj.data.vertices[outer_curve_loop[1]].co).length
)
ydim = self.convert_si_to_unit(
2020-11-01 20:08:48 +07:00
(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)
elif "IfcCircleProfileDef" in item["subitems"]:
indices = item["subitems"]["IfcCircleProfileDef"]
2020-08-22 19:11:17 +10:00
outer_curve_loop = self.get_loop_from_v_indices(obj, indices)
curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop)
2020-11-01 20:08:48 +07:00
radius = self.convert_si_to_unit(
abs((obj.data.vertices[indices[0]].co - obj.data.vertices[indices[int(len(indices) / 2)]].co).length)
/ 2
)
2020-08-22 19:11:17 +10:00
center = Vector((0, 0))
position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0)))
2020-11-01 20:08:48 +07:00
curve = self.file.createIfcCircleProfileDef("AREA", None, position, radius)
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07: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)
2020-11-01 20:08:48 +07:00
inner_curves.append(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:
2020-11-01 20:08:48 +07:00
curve = self.file.createIfcArbitraryProfileDefWithVoids("AREA", None, outer_curve, inner_curves)
else:
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
curve_ucs["center"], curve_ucs["z_axis"], curve_ucs["x_axis"]
)
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07: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):
2020-11-01 20:08:48 +07:00
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
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 {
2020-11-01 20:08:48 +07:00
"center": center,
"x_axis": x_axis,
"y_axis": y_axis,
"z_axis": z_axis,
"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:
2020-11-01 20:08:48 +07: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)
2020-11-01 20:08:48 +07: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):
2020-11-01 20:08:48 +07: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
2020-11-01 20:08:48 +07:00
if representation["raw"].uuid not in pcv.PCVManager.cache:
2020-01-12 20:47:06 +11:00
return
return self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07:00
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()
)
],
)
2020-01-12 20:47:06 +11:00
def create_solid_representation(self, representation):
2020-11-01 20:08:48 +07:00
mesh = representation["raw"]
if not representation["is_parametric"]:
mesh = representation["raw_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
if self.ifc_export_settings.should_force_triangulation:
mesh = representation["raw_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
del bm
2020-11-01 20:08:48 +07:00
if self.schema_version == "IFC2X3" or self.ifc_export_settings.should_force_faceted_brep:
return self.create_faceted_brep(representation, mesh)
return self.create_polygonal_face_set(representation, mesh)
def create_polygonal_face_set(self, representation, mesh):
2020-11-01 20:08:48 +07: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-11-01 20:08:48 +07:00
ifc_raw_items[polygon.material_index % n_slots].append(
self.file.createIfcIndexedPolygonalFace([v + 1 for v in polygon.vertices])
)
coordinates = self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in mesh.vertices])
items = [self.file.createIfcPolygonalFaceSet(coordinates, None, i) for i in ifc_raw_items if i]
return self.file.createIfcShapeRepresentation(
2020-11-01 20:08:48 +07:00
self.ifc_rep_context[representation["context"]][representation["subcontext"]][
representation["target_view"]
]["ifc"],
representation["subcontext"],
"Tessellation",
items,
)
def create_faceted_brep(self, representation, mesh):
2019-09-07 08:05:42 +10:00
self.create_vertices(mesh.vertices)
2020-11-01 20:08:48 +07: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-11-01 20:08:48 +07:00
ifc_raw_items[polygon.material_index % n_slots].append(
self.file.createIfcFace(
[
self.file.createIfcFaceOuterBound(
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(
2020-11-01 20:08:48 +07: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_cartesian_point_list_from_vertices(self, vertices, is_2d=False):
if is_2d:
return self.file.createIfcCartesianPointList2D([self.convert_si_to_unit(v.co.xy) for v in vertices])
return self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in vertices])
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(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
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(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
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(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
self.ifc_parser.products[relating_building_element]["ifc"],
self.ifc_parser.products[related_projection_element]["ifc"],
2020-02-02 20:28:51 +11:00
)
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(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
self.ifc_parser.products[relating_object]["ifc"],
[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(
2020-11-01 20:08:48 +07:00
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"],
)
2019-09-01 18:40:20 +10:00
def relate_objects_to_qtos(self):
for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_qto.items():
self.file.createIfcRelDefinesByProperties(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
[o["ifc"] for o in related_objects],
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():
2020-11-01 20:08:48 +07:00
if self.ifc_parser.psets[relating_property_key]["ifc"]:
self.file.createIfcRelDefinesByProperties(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
[o["ifc"] for o in related_objects],
self.ifc_parser.materials[relating_material_key]["ifc"],
)
2019-09-09 15:11:07 +10:00
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
if self.file.schema == "IFC2X3":
return self.relate_objects_to_material_sets_ifc2x3(set_type)
for material_set, product in getattr(self.ifc_parser, f"rel_associates_material_{set_type}_set"):
if set_type == "constituent":
materials = self.create_material_constituents(material_set.material_constituents)
elif set_type == "layer":
materials = self.create_material_layers(material_set.material_layers)
elif set_type == "profile":
materials = self.create_material_profiles(material_set.material_profiles)
if not materials:
continue
attributes = {
f"Material{set_type.capitalize()}s": materials,
"Description": material_set.description or None,
}
if set_type == "layer":
attributes["LayerSetName"] = material_set.name or None
else:
attributes["Name"] = material_set.name or None
material_set = self.file.create_entity(f"IfcMaterial{set_type.capitalize()}Set", **attributes)
self.file.createIfcRelAssociatesMaterial(
2020-11-01 20:08:48 +07:00
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
[product["ifc"]],
2020-11-01 20:08:48 +07:00
material_set,
)
def relate_objects_to_material_sets_ifc2x3(self, set_type):
# IFC2X3 has a very different way of handling materials, so we have a dedicated function
for material_set, product in getattr(self.ifc_parser, f"rel_associates_material_{set_type}_set"):
if set_type == "constituent":
# IFC2X3 only supports lists, so we gracefully downgrade
material_select = self.file.create_entity(
"IfcMaterialList", **{"Materials": self.create_material_list(material_set.material_constituents)}
)
elif set_type == "layer":
material_select = self.file.create_entity(
"IfcMaterialLayerSet",
**{
"MaterialLayers": self.create_material_layers(material_set.material_layers),
"LayerSetName": material_set.name or None,
},
)
elif set_type == "profile":
material_select = None # Not supported in IFC2X3
if not material_select:
continue
self.file.createIfcRelAssociatesMaterial(
ifcopenshell.guid.new(),
self.owner_history,
None,
None,
[product["ifc"]],
material_select,
)
def create_material_layers(self, layers):
results = []
for layer in layers:
if layer.category == "None":
category = None
elif layer.category == "Custom":
category = layer.custom_category or None
else:
category = layer.category
is_ventilated = layer.is_ventilated == "TRUE" if layer.is_ventilated != "UNKNOWN" else None
attributes = {
"Material": self.ifc_parser.materials[layer.material.name]["ifc"] or None,
"LayerThickness": layer.layer_thickness,
"IsVentilated": is_ventilated,
"Name": layer.name or None,
"Description": layer.description or None,
"Category": category,
"Priority": layer.priority,
}
if self.file.schema == "IFC2X3":
del attributes["Name"]
del attributes["Description"]
del attributes["Category"]
del attributes["Priority"]
results.append(self.file.create_entity("IfcMaterialLayer", **attributes))
return results
def create_material_constituents(self, constituents):
results = []
# TODO: the correlation for IfcShapeAspect is not yet implemented
for constituent in constituents:
results.append(
self.file.create_entity(
"IfcMaterialConstituent",
**{
"Name": constituent.name or None,
"Description": constituent.description or None,
"Material": self.ifc_parser.materials[constituent.material.name]["ifc"],
"Fraction": constituent.fraction or None,
"Category": constituent.category or None,
},
)
)
return results
def create_material_list(self, materials):
return [self.ifc_parser.materials[m.material.name]["ifc"] for m in materials]
def create_material_profiles(self, profiles):
results = []
for profile in profiles:
results.append(
self.file.create_entity(
"IfcMaterialProfile",
**{
"Name": profile.name or None,
"Description": profile.description or None,
"Material": self.ifc_parser.materials[profile.material.name]["ifc"],
"Profile": self.create_material_profile_def(profile),
"Priority": profile.priority,
"Category": profile.category or None,
},
)
)
return results
def relate_spaces_to_boundary_elements(self):
for (relating_space_index, relationships,) in self.ifc_parser.rel_space_boundaries.items():
for relationship in relationships:
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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():
2020-11-01 20:08:48 +07:00
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(
2020-11-01 20:08:48 +07:00
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):
2020-11-01 20:08:48 +07:00
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))
2020-11-01 20:08:48 +07:00
tmp_name = "{}.ifc".format(filename)
tmp_file = os.path.join(unzipped_path, tmp_name)
self.file.write(tmp_file)
2020-11-01 20:08:48 +07:00
with zipfile.ZipFile(
self.ifc_export_settings.output_file, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9
) as zf:
zf.write(tmp_file)
2020-11-01 20:08:48 +07:00
elif extension == "ifc":
self.file.write(self.ifc_export_settings.output_file)
2020-11-01 20:08:48 +07:00
elif extension == "ifcjson":
import ifcjson
2020-11-01 20:08:48 +07:00
if self.ifc_export_settings.json_version == "4":
jsonData = ifcjson.IFC2JSON4(self.file, self.ifc_export_settings.json_compact).spf2Json()
2020-11-01 20:08:48 +07:00
with open(self.ifc_export_settings.output_file, "w") as outfile:
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
elif self.ifc_export_settings.json_version == "5a":
jsonData = ifcjson.IFC2JSON5a(self.file, self.ifc_export_settings.json_compact).spf2Json()
2020-11-01 20:08:48 +07:00
with open(self.ifc_export_settings.output_file, "w") as outfile:
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 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
2020-11-01 20:08:48 +07:00
self.contexts = ["Model", "Plan"]
self.subcontexts = [
"Annotation",
"Axis",
"Box",
"FootPrint",
"Reference",
"Body",
"Clearance",
"CoG",
"Profile",
"SurveyPoints",
]
self.schema_version = "IFC4"
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.should_export_from_memory = 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.json_version = scene_bim.export_json_version
settings.json_compact = scene_bim.export_json_compact
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
settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep
settings.should_force_triangulation = scene_bim.export_should_force_triangulation
settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native
settings.should_export_from_memory = scene_bim.export_should_export_from_memory
2020-04-08 15:40:12 +02:00
settings.context_tree = []
2020-11-01 20:08:48 +07:00
for ifc_context in ["model", "plan"]:
if getattr(scene_bim, "has_{}_context".format(ifc_context)):
2020-04-08 15:40:12 +02:00
subcontexts = {}
2020-11-01 20:08:48 +07:00
for subcontext in getattr(scene_bim, "{}_subcontexts".format(ifc_context)):
2020-04-08 15:40:12 +02:00
subcontexts.setdefault(subcontext.name, []).append(subcontext.target_view)
2020-11-01 20:08:48 +07:00
settings.context_tree.append(
{
"name": ifc_context.title(),
"subcontexts": [{"name": key, "target_views": value} for key, value in subcontexts.items()],
}
)
2020-04-08 15:40:12 +02:00
return settings