New support for specifying collection modes. Spatial elements now support 2D representations as well.

This commit is contained in:
Dion Moult
2021-09-21 10:14:07 +10:00
parent 090aae5ff6
commit f83d13b97c
4 changed files with 323 additions and 180 deletions
+144 -179
View File
@@ -163,9 +163,8 @@ class IfcImporter:
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
self.iterator_elements = set()
self.project = None
self.spatial_structure_elements = {}
self.collections = {}
self.elements = set()
self.type_collection = None
self.type_products = {}
@@ -177,7 +176,6 @@ class IfcImporter:
self.added_data = {}
self.native_elements = set()
self.native_data = {}
self.aggregates = {}
self.material_creator = MaterialCreator(ifc_import_settings, self)
@@ -217,24 +215,24 @@ class IfcImporter:
self.profile_code("Create materials")
self.create_styles()
self.profile_code("Create styles")
self.parse_native_elements()
self.profile_code("Parsing native elements")
self.create_grids()
self.profile_code("Create grids")
self.create_native_products()
self.profile_code("Create native products")
self.create_products()
self.profile_code("Create products")
self.create_empty_and_2d_elements()
self.profile_code("Create empty products")
self.create_type_products()
self.profile_code("Create type products")
self.create_annotation()
self.profile_code("Create annotation")
self.create_structural_elements()
self.profile_code("Create structural elements")
self.place_objects_in_spatial_tree()
self.profile_code("Placing objects in spatial tree")
self.parse_native_elements()
self.profile_code("Parsing native elements")
self.create_native_elements()
self.profile_code("Create native elements")
self.create_elements()
self.profile_code("Create elements")
self.create_grids()
self.profile_code("Create grids")
self.create_spatial_elements()
self.profile_code("Create spatial elements")
self.create_structural_items()
self.profile_code("Create structural items")
self.create_type_products()
self.profile_code("Create type products")
self.place_objects_in_collections()
self.profile_code("Place objects in collections")
if self.ifc_import_settings.should_merge_by_class:
self.merge_by_class()
self.profile_code("Merging by class")
@@ -271,15 +269,30 @@ class IfcImporter:
def process_element_filter(self):
if self.ifc_import_settings.has_filter:
self.elements = set(self.ifc_import_settings.elements)
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
else:
self.elements = set(self.file.by_type("IfcElement"))
self.iterator_elements = set(self.elements)
if self.file.schema == "IFC2X3":
self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement"))
else:
self.spatial_elements = set(self.file.by_type("IfcSpatialElement"))
def get_spatial_elements_filtered_by_elements(self, elements):
leaf_spatial_elements = set([ifcopenshell.util.element.get_container(e) for e in elements])
results = set()
for spatial_element in leaf_spatial_elements:
while True:
results.add(spatial_element)
spatial_element = ifcopenshell.util.element.get_aggregate(spatial_element)
if not spatial_element or spatial_element.is_a("IfcContext"):
break
return results
def parse_native_elements(self):
for element in self.elements:
if self.is_native(element):
self.native_elements.add(element)
self.iterator_elements -= self.native_elements
self.elements -= self.native_elements
def is_native(self, element):
if (
@@ -468,14 +481,7 @@ class IfcImporter:
grid_collection.objects.link(obj)
def create_type_products(self):
for collection in self.project["blender"].children:
if collection.name == "Types":
self.type_collection = collection
break
if not self.type_collection:
self.type_collection = bpy.data.collections.new("Types")
self.project["blender"].children.link(self.type_collection)
# TODO allow filtering of spatial elements too
if self.ifc_import_settings.has_filter:
type_products = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
else:
@@ -518,7 +524,7 @@ class IfcImporter:
):
return representation_map
def create_native_products(self):
def create_native_elements(self):
total = 0
checkpoint = time.time()
bm = bmesh.new()
@@ -551,39 +557,51 @@ class IfcImporter:
bm.to_mesh(mesh)
bm.clear()
bm.free()
print("Done creating geometry")
def create_products(self):
if self.ifc_import_settings.has_filter and not self.iterator_elements:
return
def create_spatial_elements(self):
products = self.create_products(self.spatial_elements)
self.spatial_elements -= products
products = self.create_curve_products(self.spatial_elements)
self.spatial_elements -= products
for element in self.spatial_elements:
self.create_product(element)
def create_elements(self):
products = self.create_products(self.elements)
self.elements -= products
products = self.create_curve_products(self.elements)
self.elements -= products
for element in self.elements:
self.create_product(element)
def create_products(self, products):
results = set()
if not products:
return results
if self.ifc_import_settings.should_use_cpu_multiprocessing:
iterator = ifcopenshell.geom.iterator(
self.settings,
self.file,
multiprocessing.cpu_count(),
include=self.iterator_elements or None,
self.settings, self.file, multiprocessing.cpu_count(), include=products
)
else:
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=self.iterator_elements or None)
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
valid_file = iterator.initialize()
if not valid_file:
return False
return results
checkpoint = time.time()
total_created = 0
approx_total_products = len(self.iterator_elements) or len(self.file.by_type("IfcElement"))
total = 0
start_progress = self.progress
progress_range = 85 - start_progress
while True:
if total_created % 250 == 0:
total += 1
if total % 250 == 0:
print(
"{} / ~{} elements processed in {:.2f}s ...".format(
total_created, approx_total_products, time.time() - checkpoint
"{} ({}%) elements processed in {:.2f}s ...".format(
total, iterator.progress(), time.time() - checkpoint
)
)
checkpoint = time.time()
if approx_total_products:
self.update_progress(((total_created / approx_total_products) * progress_range) + start_progress)
#self.update_progress(((total / approx_total_products) * progress_range) + start_progress)
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
@@ -592,35 +610,18 @@ class IfcImporter:
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
# We only load a single context, and we prioritise the Body context. See #1290.
pass
elif product.is_a("IfcAnnotation") and product.ObjectType == "DRAWING":
# We have already processed this during the create_annotation step
pass
else:
self.create_product(product, shape)
total_created += 1
results.add(product)
if not iterator.next():
break
print("Done creating geometry")
def create_empty_and_2d_elements(self):
curve_products = []
unadded_element_ids = set([e.id() for e in self.elements]) - set(self.added_data.keys())
for element_id in unadded_element_ids:
element = self.file.by_id(element_id)
if element.is_a("IfcPort"):
continue
if not element.Representation:
self.create_product(element)
else:
curve_products.append(element)
if curve_products:
self.create_curve_products(curve_products)
return results
def create_annotation(self):
self.create_curve_products(self.file.by_type("IfcAnnotation"))
def create_structural_elements(self):
def create_structural_items(self):
# Create structural collections
self.structural_member_collection = bpy.data.collections.new("Members")
self.structural_connection_collection = bpy.data.collections.new("Connections")
@@ -659,6 +660,9 @@ class IfcImporter:
self.link_element(product, obj)
def create_curve_products(self, products):
results = set()
if not products:
return results
if self.ifc_import_settings.should_use_cpu_multiprocessing:
iterator = ifcopenshell.geom.iterator(
self.settings_2d, self.file, multiprocessing.cpu_count(), include=products
@@ -667,7 +671,7 @@ class IfcImporter:
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
valid_file = iterator.initialize()
if not valid_file:
return False
return results
checkpoint = time.time()
total = 0
while True:
@@ -677,10 +681,13 @@ class IfcImporter:
checkpoint = time.time()
shape = iterator.get()
if shape:
product = self.file.by_id(shape.guid)
self.create_product(self.file.by_id(shape.guid), shape)
results.add(product)
if not iterator.next():
break
print("Done creating geometry")
return results
def create_product(self, element, shape=None, mesh=None):
if element is None:
@@ -731,7 +738,7 @@ class IfcImporter:
def get_representation_item_material_name(self, item):
if not item.StyledByItem:
return
style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
style_ids = [e.id() for e in self.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
return style_ids[0] if style_ids else None
def create_native_faceted_brep(self, element, mesh_name):
@@ -982,38 +989,32 @@ class IfcImporter:
self.create_aggregate_collections()
def create_spatial_decomposition_collections(self):
containers = set([ifcopenshell.util.element.get_container(e) for e in self.elements])
self.decomposition_containers = set()
for container in containers:
while container:
self.decomposition_containers.add(container)
container = ifcopenshell.util.element.get_aggregate(container)
if container and container.is_a("IfcContext"):
container = None
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
self.add_related_objects(self.project["blender"], rel_aggregate.RelatedObjects)
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
self.create_type_collection()
def add_related_objects(self, parent, related_objects):
def create_type_collection(self):
for collection in self.project["blender"].children:
if collection.name == "Types":
self.type_collection = collection
break
if not self.type_collection:
self.type_collection = bpy.data.collections.new("Types")
self.project["blender"].children.link(self.type_collection)
def create_spatial_decomposition_collection(self, parent, related_objects):
for element in related_objects:
if element not in self.decomposition_containers:
if element not in self.spatial_elements:
continue
global_id = element.GlobalId
collection = bpy.data.collections.new(self.get_name(element))
self.spatial_structure_elements[global_id] = {"blender": collection}
self.collections[global_id] = collection
parent.children.link(collection)
obj = self.create_product(element)
if obj:
self.spatial_structure_elements[global_id]["blender_obj"] = obj
collection.objects.link(obj)
if element.IsDecomposedBy:
for rel_aggregate in element.IsDecomposedBy:
self.add_related_objects(collection, rel_aggregate.RelatedObjects)
self.create_spatial_decomposition_collection(collection, rel_aggregate.RelatedObjects)
def create_aggregate_collections(self):
self.create_aggregates()
self.create_aggregate_tree()
def create_aggregates(self):
if self.ifc_import_settings.has_filter:
rel_aggregates = [e.IsDecomposedBy[0] for e in self.elements if e.IsDecomposedBy]
else:
@@ -1022,39 +1023,24 @@ class IfcImporter:
if len(rel_aggregates) > 10000:
# More than 10,000 collections makes Blender unhappy
print("Skipping aggregate collections for performance.")
self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION"
return
aggregates = {}
for rel_aggregate in rel_aggregates:
self.create_aggregate(rel_aggregate)
element = rel_aggregate.RelatingObject
collection = bpy.data.collections.new(self.get_name(element))
aggregates[element.GlobalId] = {"element": element, "collection": collection}
self.collections[element.GlobalId] = collection
def create_aggregate_tree(self):
for aggregate in self.aggregates.values():
if aggregate["container"].is_a("IfcSpatialStructureElement"):
self.spatial_structure_elements[aggregate["container"].GlobalId]["blender"].children.link(
aggregate["blender"]
)
else:
self.aggregates[aggregate["container"].GlobalId]["blender"].children.link(aggregate["blender"])
def create_aggregate(self, rel_aggregate):
element = rel_aggregate.RelatingObject
obj = bpy.data.objects.new("{}/{}".format(element.is_a(), element.Name), None)
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
self.link_element(element, obj)
collection = bpy.data.collections.new(obj.name)
collection.objects.link(obj)
self.aggregates[element.GlobalId] = {
"blender": collection,
"blender_obj": obj,
"container": self.get_aggregate_container(element),
}
def get_aggregate_container(self, element):
if hasattr(element, "ContainedInStructure") and element.ContainedInStructure:
container = element.ContainedInStructure[0].RelatingStructure
elif hasattr(element, "Decomposes") and element.Decomposes:
container = element.Decomposes[0].RelatingObject
return container
for global_id, aggregate in aggregates.items():
parent = ifcopenshell.util.element.get_aggregate(aggregate["element"])
if parent:
self.collections[parent.GlobalId].children.link(aggregate["collection"])
continue
parent = ifcopenshell.util.element.get_container(aggregate["element"])
if parent:
self.collections[parent.GlobalId].children.link(aggregate["collection"])
def create_openings_collection(self):
self.opening_collection = bpy.data.collections.new("IfcOpeningElements")
@@ -1115,74 +1101,41 @@ class IfcImporter:
def get_name(self, element):
return "{}/{}".format(element.is_a(), element.Name)
def place_objects_in_spatial_tree(self):
def place_objects_in_collections(self):
for ifc_definition_id, obj in self.added_data.items():
if isinstance(obj, bpy.types.Object):
self.place_object_in_spatial_tree(self.file.by_id(ifc_definition_id), obj)
self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj)
def place_object_in_spatial_tree(self, element, obj):
if element.is_a() in ["IfcProject", "IfcProjectLibrary"]:
def place_object_in_collection(self, element, obj):
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
self.place_object_in_decomposition_collection(element, obj)
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
self.place_object_in_spatial_decomposition_collection(element, obj)
def place_object_in_decomposition_collection(self, element, obj):
if element.is_a("IfcProject"):
return
elif element.GlobalId in self.collections:
return self.collections[element.GlobalId].objects.link(obj)
elif getattr(element, "Decomposes", None):
aggregate = ifcopenshell.util.element.get_aggregate(element)
return self.collections[aggregate.GlobalId].objects.link(obj)
else:
return self.place_object_in_spatial_decomposition_collection(element, obj)
def place_object_in_spatial_decomposition_collection(self, element, obj):
if element.is_a("IfcProject"):
return
elif element.GlobalId in self.collections:
return self.collections[element.GlobalId].objects.link(obj)
elif element.is_a("IfcTypeObject"):
self.type_collection.objects.link(obj)
elif element.GlobalId in self.aggregates:
return
elif element.GlobalId in self.spatial_structure_elements:
if not obj.data:
return
# Since spatial structure elements are generated as empties, we'll replace it with the representation
spatial_obj = self.spatial_structure_elements[element.GlobalId]["blender_obj"]
spatial_collection = self.spatial_structure_elements[element.GlobalId]["blender"]
spatial_name = spatial_obj.name
spatial_collection.objects.link(obj)
bpy.data.objects.remove(spatial_obj)
obj.name = spatial_name
elif (
hasattr(element, "ContainedInStructure")
and element.ContainedInStructure
and element.ContainedInStructure[0].RelatingStructure
):
container = element.ContainedInStructure[0].RelatingStructure
if element.is_a("IfcGrid"):
grid_collection = bpy.data.collections.get(obj.name)
if grid_collection: # Just in case we ran into invalid grids from Revit
self.spatial_structure_elements[container.GlobalId]["blender"].children.link(grid_collection)
grid_collection.objects.link(obj)
else:
self.spatial_structure_elements[container.GlobalId]["blender"].objects.link(obj)
elif hasattr(element, "Decomposes") and element.Decomposes:
collection = None
if element.Decomposes[0].RelatingObject.is_a("IfcProject"):
collection = self.project["blender"]
elif element.Decomposes[0].RelatingObject.is_a("IfcSpatialStructureElement"):
if element.is_a("IfcSpatialStructureElement"):
global_id = element.GlobalId
if global_id in self.spatial_structure_elements:
if (
element.is_a("IfcSpatialStructureElement")
and "blender_obj" in self.spatial_structure_elements[global_id]
):
bpy.data.objects.remove(self.spatial_structure_elements[global_id]["blender_obj"])
collection = self.spatial_structure_elements[global_id]["blender"]
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
# TODO: refactor this to a more holistic collection mode feature
return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj)
else:
aggregate = element.Decomposes[0].RelatingObject
aggregate_data = self.aggregates.get(aggregate.GlobalId)
if not aggregate_data:
return self.place_object_in_spatial_tree(aggregate, obj)
collection = aggregate_data["blender"]
if collection:
collection.objects.link(obj)
else:
self.ifc_import_settings.logger.error("An element could not be placed in the spatial tree %s", element)
return self.type_collection.objects.link(obj)
elif element.is_a("IfcOpeningElement"):
self.opening_collection.objects.link(obj)
return self.opening_collection.objects.link(obj)
elif element.is_a("IfcStructuralMember"):
self.structural_member_collection.objects.link(obj)
return self.structural_member_collection.objects.link(obj)
elif element.is_a("IfcStructuralConnection"):
self.structural_connection_collection.objects.link(obj)
return self.structural_connection_collection.objects.link(obj)
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
view_collection = bpy.data.collections.get("Views")
if not view_collection:
@@ -1192,6 +1145,18 @@ class IfcImporter:
drawing_collection = bpy.data.collections.new("IfcGroup/" + group.Name)
view_collection.children.link(drawing_collection)
drawing_collection.objects.link(obj)
return
container = ifcopenshell.util.element.get_container(element)
if container:
if element.is_a("IfcGrid"): # TODO: refactor into a more holistic collection mode feature
grid_collection = bpy.data.collections.get(obj.name)
if grid_collection: # Just in case we run into invalid grids from Revit
self.collections[container.GlobalId].children.link(grid_collection)
grid_collection.objects.link(obj)
else:
self.collections[container.GlobalId].objects.link(obj)
else:
self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element)
bpy.context.scene.collection.objects.link(obj)
@@ -377,7 +377,7 @@ class AppendLibraryElement(bpy.types.Operator):
self.import_type_materials(element, ifc_importer)
self.import_type_styles(element, ifc_importer)
ifc_importer.create_type_product(element)
ifc_importer.place_objects_in_spatial_tree()
ifc_importer.place_objects_in_collections()
def import_type_materials(self, element, ifc_importer):
for rel in element.HasAssociations:
@@ -569,6 +569,7 @@ class LoadProjectElements(bpy.types.Operator):
settings.elements = self.get_whitelist_elements()
elif self.props.filter_mode == "BLACKLIST":
settings.elements = self.get_blacklist_elements()
settings.collection_mode = self.props.collection_mode
settings.should_use_cpu_multiprocessing = self.props.should_use_cpu_multiprocessing
settings.should_merge_by_class = self.props.should_merge_by_class
settings.should_merge_by_material = self.props.should_merge_by_material
@@ -216,6 +216,61 @@ class TestLoadProjectElements(test.bim.bootstrap.NewFile):
And the object "IfcWall/Wall" does not exist
"""
@test.bim.bootstrap.scenario
def test_loading_with_the_decomposition_collection_mode(self):
return """
Given I press "bim.load_project(filepath='{cwd}/test/files/decomposition.ifc', is_advanced=True)"
When I set "scene.BIMProjectProperties.collection_mode" to "DECOMPOSITION"
And I set "scene.BIMProjectProperties.filter_mode" to "NONE"
And I press "bim.load_project_elements"
Then the object "IfcProject/My Project" is an "IfcProject"
And the object "IfcSite/My Site" is an "IfcSite"
And the object "IfcBuilding/My Building" is an "IfcBuilding"
And the object "IfcBuildingStorey/My Storey" is an "IfcBuildingStorey"
And the object "IfcSpace/Space" is an "IfcSpace"
And the object "IfcElementAssembly/Assembly" is an "IfcElementAssembly"
And the object "IfcBeam/Beam" is an "IfcBeam"
And the object "IfcSite/My Site" is in the collection "IfcSite/My Site"
And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building"
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
And the object "IfcSpace/Space" is in the collection "IfcSpace/Space"
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Assembly"
And the collection "IfcSite/My Site" is in the collection "IfcProject/My Project"
And the collection "IfcBuilding/My Building" is in the collection "IfcSite/My Site"
And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcBuilding/My Building"
And the collection "IfcSpace/Space" is in the collection "IfcBuildingStorey/My Storey"
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcSpace/Space"
And "scene.BIMProjectProperties.is_loading" is "False"
"""
@test.bim.bootstrap.scenario
def test_loading_with_the_spatial_decomposition_collection_mode(self):
return """
Given I press "bim.load_project(filepath='{cwd}/test/files/decomposition.ifc', is_advanced=True)"
When I set "scene.BIMProjectProperties.collection_mode" to "SPATIAL_DECOMPOSITION"
And I set "scene.BIMProjectProperties.filter_mode" to "NONE"
And I press "bim.load_project_elements"
Then the object "IfcProject/My Project" is an "IfcProject"
And the object "IfcSite/My Site" is an "IfcSite"
And the object "IfcBuilding/My Building" is an "IfcBuilding"
And the object "IfcBuildingStorey/My Storey" is an "IfcBuildingStorey"
And the object "IfcSpace/Space" is an "IfcSpace"
And the object "IfcElementAssembly/Assembly" is an "IfcElementAssembly"
And the object "IfcBeam/Beam" is an "IfcBeam"
And the object "IfcSite/My Site" is in the collection "IfcSite/My Site"
And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building"
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
And the object "IfcSpace/Space" is in the collection "IfcSpace/Space"
And the object "IfcElementAssembly/Assembly" is in the collection "IfcSpace/Space"
And the object "IfcBeam/Beam" is in the collection "IfcSpace/Space"
And the collection "IfcSite/My Site" is in the collection "IfcProject/My Project"
And the collection "IfcBuilding/My Building" is in the collection "IfcSite/My Site"
And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcBuilding/My Building"
And the collection "IfcSpace/Space" is in the collection "IfcBuildingStorey/My Storey"
And "scene.BIMProjectProperties.is_loading" is "False"
"""
@test.bim.bootstrap.scenario
def test_manual_offset_of_object_placements(self):
return """
+122
View File
@@ -0,0 +1,122 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
FILE_NAME('decomposition.ifc','2021-09-21T09:50:31+10:00',(),(),'IfcOpenShell 0.6.0b0','BlenderBIM 0.0.999999','Nobody');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$);
#2=IFCORGANIZATION('APTR','Aperture Science',$,$,$);
#3=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$);
#4=IFCTELECOMADDRESS(.USERDEFINED.,'The main webpage of the software collection.','WEBPAGE',$,$,$,$,'https://ifcopenshell.org',$);
#5=IFCTELECOMADDRESS(.USERDEFINED.,'The BlenderBIM Add-on webpage of the software collection.','WEBPAGE',$,$,$,$,'https://blenderbim.org',$);
#6=IFCTELECOMADDRESS(.USERDEFINED.,'The source code repository of the software collection.','REPOSITORY',$,$,$,$,'https://github.com/IfcOpenShell/IfcOpenShell.git',$);
#7=IFCORGANIZATION($,'IfcOpenShell','IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.',(#3),(#4,#5,#6));
#8=IFCAPPLICATION(#7,'0.0.999999','BlenderBIM Add-on','BlenderBIM');
#9=IFCPERSONANDORGANIZATION(#1,#2,$);
#10=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704);
#11=IFCPROJECT('25mhbqrQzBIhdUUHrZK_q3',#10,'My Project',$,$,$,$,(#20,#27),#15);
#12=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#13=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#14=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#15=IFCUNITASSIGNMENT((#12,#13,#14));
#16=IFCCARTESIANPOINT((0.,0.,0.));
#17=IFCDIRECTION((0.,0.,1.));
#18=IFCDIRECTION((1.,0.,0.));
#19=IFCAXIS2PLACEMENT3D(#16,#17,#18);
#20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#19,$);
#21=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$);
#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#20,$,.MODEL_VIEW.,$);
#23=IFCCARTESIANPOINT((0.,0.,0.));
#24=IFCDIRECTION((0.,0.,1.));
#25=IFCDIRECTION((1.,0.,0.));
#26=IFCAXIS2PLACEMENT3D(#23,#24,#25);
#27=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#26,$);
#28=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#27,$,.PLAN_VIEW.,$);
#29=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181704,#9,#8,1632181704);
#30=IFCSITE('2nSisauITAw99Ipgpxnuc1',#29,'My Site',$,$,#56,$,$,$,$,$,$,$,$);
#36=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181704,#9,#8,1632181704);
#37=IFCBUILDING('3VLkNpYOP6c92$32FgVhz1',#36,'My Building',$,$,#63,$,$,$,$,$,$);
#43=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181704,#9,#8,1632181704);
#44=IFCBUILDINGSTOREY('2EXifXJjf7IvZ04BvDaAfA',#43,'My Storey',$,$,#70,$,$,$,$);
#50=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704);
#51=IFCRELAGGREGATES('0riie8ARHD2P_3P8pg2s0e',#50,$,$,#11,(#30));
#52=IFCCARTESIANPOINT((0.,0.,0.));
#53=IFCDIRECTION((0.,0.,1.));
#54=IFCDIRECTION((1.,0.,0.));
#55=IFCAXIS2PLACEMENT3D(#52,#53,#54);
#56=IFCLOCALPLACEMENT($,#55);
#57=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704);
#58=IFCRELAGGREGATES('3HMkYYf2j01up2RzoG1K$S',#57,$,$,#30,(#37));
#59=IFCCARTESIANPOINT((0.,0.,0.));
#60=IFCDIRECTION((0.,0.,1.));
#61=IFCDIRECTION((1.,0.,0.));
#62=IFCAXIS2PLACEMENT3D(#59,#60,#61);
#63=IFCLOCALPLACEMENT(#56,#62);
#64=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181704,#9,#8,1632181704);
#65=IFCRELAGGREGATES('1r5yHIAoX7ExW8Fb$KK6Rv',#64,$,$,#37,(#44));
#66=IFCCARTESIANPOINT((0.,0.,0.));
#67=IFCDIRECTION((0.,0.,1.));
#68=IFCDIRECTION((1.,0.,0.));
#69=IFCAXIS2PLACEMENT3D(#66,#67,#68);
#70=IFCLOCALPLACEMENT(#63,#69);
#71=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181716,#9,#8,1632181716);
#72=IFCSPACE('13wXihTNXCyASBfO8n3YgZ',#71,'Space',$,$,#102,#90,$,$,.EXTERNAL.,$);
#78=IFCINDEXEDPOLYGONALFACE((1,5,7,3));
#79=IFCINDEXEDPOLYGONALFACE((4,3,7,8));
#80=IFCINDEXEDPOLYGONALFACE((8,7,5,6));
#81=IFCINDEXEDPOLYGONALFACE((6,2,4,8));
#82=IFCINDEXEDPOLYGONALFACE((2,1,3,4));
#83=IFCINDEXEDPOLYGONALFACE((6,5,1,2));
#84=IFCCARTESIANPOINTLIST3D(((1.,1.,1.),(1.,1.,-1.),(1.,-1.,1.),(1.,-1.,-1.),(-1.,1.,1.),(-1.,1.,-1.),(-1.,-1.,1.),(-1.,-1.,-1.)));
#85=IFCPOLYGONALFACESET(#84,$,(#78,#79,#80,#81,#82,#83),$);
#86=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#85));
#87=IFCCARTESIANPOINT((-1.,-1.,-1.));
#88=IFCBOUNDINGBOX(#87,2.,2.,2.);
#89=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#88));
#90=IFCPRODUCTDEFINITIONSHAPE($,$,(#89,#86));
#91=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#92=IFCCOLOURRGB($,0.800000011920929,0.800000011920929,0.800000011920929);
#93=IFCSURFACESTYLERENDERING(#91,0.,#92,$,$,$,$,$,.NOTDEFINED.);
#94=IFCSURFACESTYLE('Material',.BOTH.,(#93));
#95=IFCSTYLEDITEM(#85,(#94),'Material');
#96=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181716,#9,#8,1632181716);
#97=IFCRELAGGREGATES('2NuKgx$4L8OBX9R$MwP3Bf',#96,$,$,#44,(#72));
#98=IFCCARTESIANPOINT((0.,0.,0.));
#99=IFCDIRECTION((0.,0.,1.));
#100=IFCDIRECTION((1.,0.,0.));
#101=IFCAXIS2PLACEMENT3D(#98,#99,#100);
#102=IFCLOCALPLACEMENT(#70,#101);
#103=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181774,#9,#8,1632181774);
#104=IFCELEMENTASSEMBLY('0hCUjYJgv6FwS2fAvN21mm',#103,'Assembly',$,$,#120,$,$,$,.ACCESSORY_ASSEMBLY.);
#110=IFCRELCONTAINEDINSPATIALSTRUCTURE('2zdJYp1Tr8qvLmsWlFRgz0',#151,$,$,(#104),#72);
#116=IFCCARTESIANPOINT((0.,0.,0.));
#117=IFCDIRECTION((0.,0.,1.));
#118=IFCDIRECTION((1.,0.,0.));
#119=IFCAXIS2PLACEMENT3D(#116,#117,#118);
#120=IFCLOCALPLACEMENT(#102,#119);
#121=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1632181807,#9,#8,1632181786);
#122=IFCBEAM('1KiE3gs9z1sfj9c0spBIGv',#121,'Beam',$,$,#158,#140,$,.BEAM.);
#128=IFCINDEXEDPOLYGONALFACE((1,2,4,3));
#129=IFCINDEXEDPOLYGONALFACE((3,4,8,7));
#130=IFCINDEXEDPOLYGONALFACE((7,8,6,5));
#131=IFCINDEXEDPOLYGONALFACE((5,6,2,1));
#132=IFCINDEXEDPOLYGONALFACE((3,7,5,1));
#133=IFCINDEXEDPOLYGONALFACE((8,4,2,6));
#134=IFCCARTESIANPOINTLIST3D(((-1.,-1.,-1.),(-1.,-1.,1.),(-1.,1.,-1.),(-1.,1.,1.),(1.,-1.,-1.),(1.,-1.,1.),(1.,1.,-1.),(1.,1.,1.)));
#135=IFCPOLYGONALFACESET(#134,$,(#128,#129,#130,#131,#132,#133),$);
#136=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#135));
#137=IFCCARTESIANPOINT((-1.,-1.,-1.));
#138=IFCBOUNDINGBOX(#137,2.,2.,2.);
#139=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#138));
#140=IFCPRODUCTDEFINITIONSHAPE($,$,(#139,#136));
#151=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181798,#9,#8,1632181798);
#152=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1632181807,#9,#8,1632181807);
#153=IFCRELAGGREGATES('1N$QRzghfDgA1Hw$swtu4S',#152,$,$,#104,(#122));
#154=IFCCARTESIANPOINT((0.,0.,0.));
#155=IFCDIRECTION((0.,0.,1.));
#156=IFCDIRECTION((1.,0.,0.));
#157=IFCAXIS2PLACEMENT3D(#154,#155,#156);
#158=IFCLOCALPLACEMENT(#120,#157);
ENDSEC;
END-ISO-10303-21;