mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 06:32:09 +00:00
Optimise import process. See commit notes.
* When there is only one material slot, there is no need to assign materials as the slot becomes the default * Don't loop through collections to find the collection you need, we now store aggregate collcetions * The project collection is not added to the scene collection until the very end. Apparently linking objects to collections when the collection is enabled in the view_layer causes it to be twice as slow. Therefore all view layer operations happen at the end. * Don't print the progress bar, as excessive prints and calls to the iterator.progress() makes it very, very slow. Like, so slow you wouldn't even wait for the file to finish you'd just give up. See #895. * Only create materials once per mesh. Nobody seems to be using styled items properly as an object override anyways. * Use foreach_set to create meshes instead of mesh.from_pydata. This is much faster. * Similarly use foreach_set to set material indexes. This leads to a crazy speed increase on big meshes.
This commit is contained in:
@@ -102,20 +102,19 @@ class MaterialCreator():
|
|||||||
def assign_material_slots_to_faces(self, obj, mesh):
|
def assign_material_slots_to_faces(self, obj, mesh):
|
||||||
if 'ios_materials' not in mesh or not mesh['ios_materials']:
|
if 'ios_materials' not in mesh or not mesh['ios_materials']:
|
||||||
return
|
return
|
||||||
|
if len(obj.material_slots) == 1:
|
||||||
|
return
|
||||||
slots = [s.name for s in obj.material_slots]
|
slots = [s.name for s in obj.material_slots]
|
||||||
for index, polygon in enumerate(mesh.polygons):
|
material_to_slot = {}
|
||||||
material_id = mesh['ios_material_ids'][index]
|
for i, material in enumerate(mesh['ios_materials']):
|
||||||
# Magic number 999999 represents no material, until this has a better approach
|
|
||||||
if material_id == 999999:
|
|
||||||
continue
|
|
||||||
material = mesh['ios_materials'][material_id]
|
|
||||||
if 'surface-style-' in material:
|
if 'surface-style-' in material:
|
||||||
material = material.split('-')[2]
|
material = material.split('-')[2]
|
||||||
try:
|
material_to_slot[i] = slots.index(material)
|
||||||
polygon.material_index = slots.index(material)
|
|
||||||
except:
|
if len(mesh.polygons) == len(mesh['ios_material_ids']):
|
||||||
self.ifc_import_settings.logger.error(
|
material_index = [(material_to_slot[mat_id] if mat_id != 999999
|
||||||
'Failed to assign material {} to object {}'.format(material, obj.name))
|
else 0) for mat_id in mesh['ios_material_ids']]
|
||||||
|
mesh.polygons.foreach_set('material_index', material_index)
|
||||||
|
|
||||||
def parse_material(self, element):
|
def parse_material(self, element):
|
||||||
for association in element.HasAssociations:
|
for association in element.HasAssociations:
|
||||||
@@ -259,6 +258,7 @@ class IfcImporter():
|
|||||||
self.native_data = {}
|
self.native_data = {}
|
||||||
self.groups = {}
|
self.groups = {}
|
||||||
self.aggregates = {}
|
self.aggregates = {}
|
||||||
|
self.aggregate_collections = {}
|
||||||
|
|
||||||
self.material_creator = MaterialCreator(ifc_import_settings)
|
self.material_creator = MaterialCreator(ifc_import_settings)
|
||||||
|
|
||||||
@@ -282,8 +282,7 @@ class IfcImporter():
|
|||||||
self.create_type_products()
|
self.create_type_products()
|
||||||
if self.ifc_import_settings.should_import_aggregates:
|
if self.ifc_import_settings.should_import_aggregates:
|
||||||
self.create_aggregates()
|
self.create_aggregates()
|
||||||
if self.ifc_import_settings.should_import_opening_elements:
|
self.create_openings_collection()
|
||||||
self.create_openings_collection()
|
|
||||||
self.process_element_filter()
|
self.process_element_filter()
|
||||||
if self.ifc_import_settings.should_import_native:
|
if self.ifc_import_settings.should_import_native:
|
||||||
self.parse_native_elements()
|
self.parse_native_elements()
|
||||||
@@ -311,6 +310,7 @@ class IfcImporter():
|
|||||||
or (self.ifc_import_settings.should_auto_set_workarounds \
|
or (self.ifc_import_settings.should_auto_set_workarounds \
|
||||||
and len(self.material_creator.materials) > 300):
|
and len(self.material_creator.materials) > 300):
|
||||||
self.merge_materials_by_colour()
|
self.merge_materials_by_colour()
|
||||||
|
self.add_project_to_scene()
|
||||||
if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type('IfcElement')) < 10000:
|
if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type('IfcElement')) < 10000:
|
||||||
self.clean_mesh()
|
self.clean_mesh()
|
||||||
|
|
||||||
@@ -569,7 +569,6 @@ class IfcImporter():
|
|||||||
self.project['blender'].children.link(self.type_collection)
|
self.project['blender'].children.link(self.type_collection)
|
||||||
for type_product in type_products:
|
for type_product in type_products:
|
||||||
self.create_type_product(type_product)
|
self.create_type_product(type_product)
|
||||||
bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[self.type_collection.name].hide_viewport = True
|
|
||||||
|
|
||||||
def create_type_product(self, element):
|
def create_type_product(self, element):
|
||||||
self.ifc_import_settings.logger.info('Creating object {}'.format(element))
|
self.ifc_import_settings.logger.info('Creating object {}'.format(element))
|
||||||
@@ -616,24 +615,26 @@ class IfcImporter():
|
|||||||
def create_native_products(self):
|
def create_native_products(self):
|
||||||
if not self.native_elements:
|
if not self.native_elements:
|
||||||
return
|
return
|
||||||
|
# TODO: the iterator is kind of useless here, rewrite this
|
||||||
iterator = ifcopenshell.geom.iterator(
|
iterator = ifcopenshell.geom.iterator(
|
||||||
self.settings_native, self.file, multiprocessing.cpu_count(),
|
self.settings_native, self.file, multiprocessing.cpu_count(),
|
||||||
include=[self.file.by_guid(guid) for guid in self.native_elements.keys()] or None)
|
include=[self.file.by_guid(guid) for guid in self.native_elements.keys()] or None)
|
||||||
valid_file = iterator.initialize()
|
valid_file = iterator.initialize()
|
||||||
|
total = 0
|
||||||
|
checkpoint = time.time()
|
||||||
if not valid_file:
|
if not valid_file:
|
||||||
return False
|
return False
|
||||||
old_progress = -1
|
|
||||||
while True:
|
while True:
|
||||||
progress = iterator.progress() // 2
|
total += 1
|
||||||
if progress > old_progress:
|
if total % 250 == 0:
|
||||||
print("\r[" + "#" * progress + " " * (50 - progress) + "]", end="")
|
print('{} elements processed in {:.2f}s ...'.format(total, time.time() - checkpoint))
|
||||||
old_progress = progress
|
checkpoint = time.time()
|
||||||
shape = iterator.get()
|
shape = iterator.get()
|
||||||
if shape:
|
if shape:
|
||||||
self.create_product(self.file.by_id(shape.guid), shape)
|
self.create_product(self.file.by_id(shape.guid), shape)
|
||||||
if not iterator.next():
|
if not iterator.next():
|
||||||
break
|
break
|
||||||
print("\rDone creating geometry" + " " * 30)
|
print('Done creating geometry')
|
||||||
|
|
||||||
def create_products(self):
|
def create_products(self):
|
||||||
if self.ifc_import_settings.should_use_cpu_multiprocessing:
|
if self.ifc_import_settings.should_use_cpu_multiprocessing:
|
||||||
@@ -650,18 +651,19 @@ class IfcImporter():
|
|||||||
valid_file = iterator.initialize()
|
valid_file = iterator.initialize()
|
||||||
if not valid_file:
|
if not valid_file:
|
||||||
return False
|
return False
|
||||||
old_progress = -1
|
checkpoint = time.time()
|
||||||
|
total = 0
|
||||||
while True:
|
while True:
|
||||||
progress = iterator.progress() // 2
|
total += 1
|
||||||
if progress > old_progress:
|
if total % 250 == 0:
|
||||||
print("\r[" + "#" * progress + " " * (50 - progress) + "]", end="")
|
print('{} elements processed in {:.2f}s ...'.format(total, time.time() - checkpoint))
|
||||||
old_progress = progress
|
checkpoint = time.time()
|
||||||
shape = iterator.get()
|
shape = iterator.get()
|
||||||
if shape:
|
if shape:
|
||||||
self.create_product(self.file.by_id(shape.guid), shape)
|
self.create_product(self.file.by_id(shape.guid), shape)
|
||||||
if not iterator.next():
|
if not iterator.next():
|
||||||
break
|
break
|
||||||
print("\rDone creating geometry" + " " * 30)
|
print('Done creating geometry')
|
||||||
|
|
||||||
def create_product(self, element, shape=None):
|
def create_product(self, element, shape=None):
|
||||||
if element is None:
|
if element is None:
|
||||||
@@ -680,6 +682,7 @@ class IfcImporter():
|
|||||||
|
|
||||||
self.ifc_import_settings.logger.info('Creating object {}'.format(element))
|
self.ifc_import_settings.logger.info('Creating object {}'.format(element))
|
||||||
|
|
||||||
|
is_fresh_mesh = False
|
||||||
if shape:
|
if shape:
|
||||||
# TODO: make names more meaningful
|
# TODO: make names more meaningful
|
||||||
mesh_name = f'mesh-{shape.geometry.id}'
|
mesh_name = f'mesh-{shape.geometry.id}'
|
||||||
@@ -690,6 +693,7 @@ class IfcImporter():
|
|||||||
if mesh is None:
|
if mesh is None:
|
||||||
mesh = self.create_mesh(element, shape)
|
mesh = self.create_mesh(element, shape)
|
||||||
self.meshes[mesh_name] = mesh
|
self.meshes[mesh_name] = mesh
|
||||||
|
is_fresh_mesh = True
|
||||||
else:
|
else:
|
||||||
mesh = None
|
mesh = None
|
||||||
|
|
||||||
@@ -703,7 +707,8 @@ class IfcImporter():
|
|||||||
[m[9], m[10], m[11], 1]))
|
[m[9], m[10], m[11], 1]))
|
||||||
mat.transpose()
|
mat.transpose()
|
||||||
obj.matrix_world = mat
|
obj.matrix_world = mat
|
||||||
self.material_creator.create(element, obj, mesh)
|
if is_fresh_mesh:
|
||||||
|
self.material_creator.create(element, obj, mesh)
|
||||||
elif hasattr(element, 'ObjectPlacement'):
|
elif hasattr(element, 'ObjectPlacement'):
|
||||||
obj.matrix_world = self.get_element_matrix(element)
|
obj.matrix_world = self.get_element_matrix(element)
|
||||||
|
|
||||||
@@ -733,7 +738,11 @@ class IfcImporter():
|
|||||||
items = []
|
items = []
|
||||||
for representation in self.get_body_representations(element.Representation.Representations):
|
for representation in self.get_body_representations(element.Representation.Representations):
|
||||||
for item in representation['raw'].Items:
|
for item in representation['raw'].Items:
|
||||||
materials.append(self.get_representation_item_material_name(item))
|
material_name = self.get_representation_item_material_name(item)
|
||||||
|
if not material_name:
|
||||||
|
# Magic string NULLMAT represents no material, unless this has a better approach
|
||||||
|
material_name = 'NULLMAT'
|
||||||
|
materials.append(material_name)
|
||||||
if item.id() in data:
|
if item.id() in data:
|
||||||
item = data[item.id()]
|
item = data[item.id()]
|
||||||
if item.is_a() == 'IfcExtrudedAreaSolid':
|
if item.is_a() == 'IfcExtrudedAreaSolid':
|
||||||
@@ -783,13 +792,12 @@ class IfcImporter():
|
|||||||
merged_bm = item['blender']
|
merged_bm = item['blender']
|
||||||
else:
|
else:
|
||||||
self.merge_bmeshes(merged_bm, item['blender'])
|
self.merge_bmeshes(merged_bm, item['blender'])
|
||||||
if materials[i]:
|
# Magic string NULLMAT represents no material, unless this has a better approach
|
||||||
material_ids += [i] * total_polygons
|
if materials[i] == 'NULLMAT':
|
||||||
else:
|
|
||||||
# Magic string NULLMAT represents no material, unless this has a better approach
|
|
||||||
materials[i] = 'NULLMAT'
|
|
||||||
# Magic number 999999 represents no material, until this has a better approach
|
# Magic number 999999 represents no material, until this has a better approach
|
||||||
material_ids += [999999] * total_polygons
|
material_ids += [999999] * total_polygons
|
||||||
|
else:
|
||||||
|
material_ids += [i] * total_polygons
|
||||||
if merged_curve:
|
if merged_curve:
|
||||||
return merged_curve
|
return merged_curve
|
||||||
# TODO: handle both curve and bmeshes combined
|
# TODO: handle both curve and bmeshes combined
|
||||||
@@ -888,9 +896,7 @@ class IfcImporter():
|
|||||||
|
|
||||||
def merge_objects_inside_aggregates(self):
|
def merge_objects_inside_aggregates(self):
|
||||||
global_ids_to_delete = []
|
global_ids_to_delete = []
|
||||||
for collection in bpy.data.collections:
|
for collection in self.aggregate_collections.values():
|
||||||
if 'IfcRelAggregates/' not in collection.name:
|
|
||||||
continue
|
|
||||||
obs = []
|
obs = []
|
||||||
for i, ob in enumerate(collection.objects):
|
for i, ob in enumerate(collection.objects):
|
||||||
if ob.type == 'MESH':
|
if ob.type == 'MESH':
|
||||||
@@ -965,6 +971,13 @@ class IfcImporter():
|
|||||||
for material in self.material_creator.materials.values():
|
for material in self.material_creator.materials.values():
|
||||||
bpy.data.materials.remove(material)
|
bpy.data.materials.remove(material)
|
||||||
|
|
||||||
|
def add_project_to_scene(self):
|
||||||
|
bpy.context.scene.collection.children.link(self.project['blender'])
|
||||||
|
for collection in self.aggregate_collections.values():
|
||||||
|
bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[collection.name].hide_viewport = True
|
||||||
|
bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[self.opening_collection.name].hide_viewport = True
|
||||||
|
bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[self.type_collection.name].hide_viewport = True
|
||||||
|
|
||||||
def clean_mesh(self):
|
def clean_mesh(self):
|
||||||
obj = None
|
obj = None
|
||||||
last_obj = None
|
last_obj = None
|
||||||
@@ -1185,7 +1198,6 @@ class IfcImporter():
|
|||||||
self.project['blender'] = self.existing_elements[self.project['ifc'].GlobalId].users_collection[0]
|
self.project['blender'] = self.existing_elements[self.project['ifc'].GlobalId].users_collection[0]
|
||||||
return
|
return
|
||||||
self.project['blender'] = bpy.data.collections.new('IfcProject/{}'.format(self.project['ifc'].Name))
|
self.project['blender'] = bpy.data.collections.new('IfcProject/{}'.format(self.project['ifc'].Name))
|
||||||
bpy.context.scene.collection.children.link(self.project['blender'])
|
|
||||||
obj = self.create_product(self.project['ifc'])
|
obj = self.create_product(self.project['ifc'])
|
||||||
if obj:
|
if obj:
|
||||||
self.project['blender'].objects.link(obj)
|
self.project['blender'].objects.link(obj)
|
||||||
@@ -1308,7 +1320,6 @@ class IfcImporter():
|
|||||||
def create_aggregate(self, rel_aggregate):
|
def create_aggregate(self, rel_aggregate):
|
||||||
collection = bpy.data.collections.new(f'IfcRelAggregates/{rel_aggregate.id()}')
|
collection = bpy.data.collections.new(f'IfcRelAggregates/{rel_aggregate.id()}')
|
||||||
self.project['blender'].children.link(collection)
|
self.project['blender'].children.link(collection)
|
||||||
bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[collection.name].hide_viewport = True
|
|
||||||
element = rel_aggregate.RelatingObject
|
element = rel_aggregate.RelatingObject
|
||||||
|
|
||||||
obj = bpy.data.objects.new('{}/{}'.format(element.is_a(), element.Name), None)
|
obj = bpy.data.objects.new('{}/{}'.format(element.is_a(), element.Name), None)
|
||||||
@@ -1321,11 +1332,11 @@ class IfcImporter():
|
|||||||
self.add_defines_by_type_relation(element, obj)
|
self.add_defines_by_type_relation(element, obj)
|
||||||
self.add_product_definitions(element, obj)
|
self.add_product_definitions(element, obj)
|
||||||
self.aggregates[element.GlobalId] = obj
|
self.aggregates[element.GlobalId] = obj
|
||||||
|
self.aggregate_collections[rel_aggregate.id()] = collection
|
||||||
|
|
||||||
def create_openings_collection(self):
|
def create_openings_collection(self):
|
||||||
self.opening_collection = bpy.data.collections.new('IfcOpeningElements')
|
self.opening_collection = bpy.data.collections.new('IfcOpeningElements')
|
||||||
self.project['blender'].children.link(self.opening_collection)
|
self.project['blender'].children.link(self.opening_collection)
|
||||||
bpy.context.view_layer.layer_collection.children[self.project['blender'].name].children[self.opening_collection.name].hide_viewport = True
|
|
||||||
|
|
||||||
def get_name(self, element):
|
def get_name(self, element):
|
||||||
return '{}/{}'.format(element.is_a(), element.Name)
|
return '{}/{}'.format(element.is_a(), element.Name)
|
||||||
@@ -1420,23 +1431,21 @@ class IfcImporter():
|
|||||||
and element.ContainedInStructure[0].RelatingStructure:
|
and element.ContainedInStructure[0].RelatingStructure:
|
||||||
container = element.ContainedInStructure[0].RelatingStructure
|
container = element.ContainedInStructure[0].RelatingStructure
|
||||||
if container.is_a('IfcSpace'):
|
if container.is_a('IfcSpace'):
|
||||||
if container.GlobalId in self.added_data:
|
if self.ifc_import_settings.should_import_spaces and container.GlobalId in self.added_data:
|
||||||
obj.BIMObjectProperties.relating_structure = self.added_data[container.GlobalId]
|
obj.BIMObjectProperties.relating_structure = self.added_data[container.GlobalId]
|
||||||
return self.place_object_in_spatial_tree(container, obj)
|
return self.place_object_in_spatial_tree(container, obj)
|
||||||
relating_structure_global_id = container.GlobalId
|
self.spatial_structure_elements[container.GlobalId]['blender'].objects.link(obj)
|
||||||
if relating_structure_global_id in self.spatial_structure_elements:
|
|
||||||
self.spatial_structure_elements[relating_structure_global_id]['blender'].objects.link(obj)
|
|
||||||
elif hasattr(element, 'Decomposes') \
|
elif hasattr(element, 'Decomposes') \
|
||||||
and element.Decomposes:
|
and element.Decomposes:
|
||||||
collection = None
|
collection = None
|
||||||
if element.Decomposes[0].RelatingObject.is_a('IfcProject'):
|
if element.Decomposes[0].RelatingObject.is_a('IfcProject'):
|
||||||
collection = bpy.data.collections.get(f'IfcProject/{element.Decomposes[0].RelatingObject.Name}')
|
collection = self.project['blender']
|
||||||
elif element.Decomposes[0].RelatingObject.is_a('IfcSpatialStructureElement'):
|
elif element.Decomposes[0].RelatingObject.is_a('IfcSpatialStructureElement'):
|
||||||
global_id = element.Decomposes[0].RelatingObject.GlobalId
|
global_id = element.Decomposes[0].RelatingObject.GlobalId
|
||||||
if global_id in self.spatial_structure_elements:
|
if global_id in self.spatial_structure_elements:
|
||||||
collection = self.spatial_structure_elements[global_id]['blender']
|
collection = self.spatial_structure_elements[global_id]['blender']
|
||||||
elif self.ifc_import_settings.should_import_aggregates:
|
elif self.ifc_import_settings.should_import_aggregates:
|
||||||
collection = bpy.data.collections.get(f'IfcRelAggregates/{element.Decomposes[0].id()}')
|
collection = self.aggregate_collections[element.Decomposes[0].id()]
|
||||||
else:
|
else:
|
||||||
return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj)
|
return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj)
|
||||||
if collection:
|
if collection:
|
||||||
@@ -1607,19 +1616,25 @@ class IfcImporter():
|
|||||||
return self.create_curve(geometry)
|
return self.create_curve(geometry)
|
||||||
|
|
||||||
mesh = bpy.data.meshes.new(geometry.id)
|
mesh = bpy.data.meshes.new(geometry.id)
|
||||||
f = geometry.faces
|
|
||||||
e = geometry.edges
|
vertices = geometry.verts
|
||||||
v = geometry.verts
|
num_vertices = len(vertices) // 3
|
||||||
vertices = [[v[i], v[i + 1], v[i + 2]]
|
vertex_index = geometry.faces
|
||||||
for i in range(0, len(v), 3)]
|
total_faces = len(geometry.faces)
|
||||||
faces = [[f[i], f[i + 1], f[i + 2]]
|
loop_start = range(0, total_faces, 3)
|
||||||
for i in range(0, len(f), 3)]
|
num_loops = total_faces // 3
|
||||||
if faces:
|
loop_total = [3] * num_loops
|
||||||
edges = []
|
num_vertex_indices = len(vertex_index)
|
||||||
else:
|
|
||||||
edges = [[e[i], e[i + 1]]
|
mesh.vertices.add(num_vertices)
|
||||||
for i in range(0, len(e), 2)]
|
mesh.vertices.foreach_set('co', vertices)
|
||||||
mesh.from_pydata(vertices, edges, faces)
|
mesh.loops.add(num_vertex_indices)
|
||||||
|
mesh.loops.foreach_set('vertex_index', vertex_index)
|
||||||
|
mesh.polygons.add(num_loops)
|
||||||
|
mesh.polygons.foreach_set('loop_start', loop_start)
|
||||||
|
mesh.polygons.foreach_set('loop_total', loop_total)
|
||||||
|
mesh.update()
|
||||||
|
|
||||||
ios_materials = []
|
ios_materials = []
|
||||||
for mat in geometry.materials:
|
for mat in geometry.materials:
|
||||||
if mat.original_name():
|
if mat.original_name():
|
||||||
|
|||||||
Reference in New Issue
Block a user