diff --git a/src/speckleifc/__main__.py b/src/speckleifc/__main__.py index fa61e3e..0e66af3 100644 --- a/src/speckleifc/__main__.py +++ b/src/speckleifc/__main__.py @@ -13,7 +13,8 @@ def main() -> Version: MODEL_ID = "0e23cfdea3" SERVER_URL = "app.speckle.systems" # FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\60mins.ifc" - FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\hillside_house_meters.ifc" + # FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\hillside_house_meters.ifc" + FILE = "C:\\Users\\Jedd\\Desktop\\openshell\\GRAPHISOFT_Archicad_Sample_Project-S-Office_v1.0_AC25.ifc" # Conversion data = convert_file(FILE) diff --git a/src/speckleifc/converter/geometry_converter.py b/src/speckleifc/converter/geometry_converter.py index 05a654e..ebc04a3 100644 --- a/src/speckleifc/converter/geometry_converter.py +++ b/src/speckleifc/converter/geometry_converter.py @@ -1,3 +1,4 @@ +from collections import defaultdict from collections.abc import Sequence from typing import cast @@ -12,74 +13,74 @@ def geometry_to_speckle(geometry: Triangulation, ifc_model: file) -> list[Base]: materials = cast(Sequence[int], geometry.materials) MESH_COUNT = max(len(materials), 1) - meshes: list[Mesh] = [ - Mesh(units="m", vertices=[], faces=[]) for i in range(MESH_COUNT) - ] - index_counters = [0] * MESH_COUNT - + material_ids = cast(Sequence[int], geometry.material_ids) faces = cast(Sequence[int], geometry.faces) verts = cast(Sequence[float], geometry.verts) - normals = cast(Sequence[float], geometry.normals) - uvs = cast(Sequence[float], geometry.uvs) - material_ids = cast(Sequence[int], geometry.material_ids) + mapped_meshes = _pre_alloc_mesh_lists(material_ids, MESH_COUNT) + mapped_faces_pointers = [0] * MESH_COUNT + mapped_vertices_pointers = [0] * MESH_COUNT + mapped_index_counters = [0] * MESH_COUNT FACE_COUNT = len(material_ids) assert len(faces) == FACE_COUNT * 3 - # assert len(normals) == len(verts) - # assert len(uvs) == len(verts) || i = 0 face_index = 0 while i < FACE_COUNT: - mesh: Mesh = meshes[material_ids[i]] + mesh_index = material_ids[i] + mesh: Mesh = mapped_meshes[mesh_index] + + face_ptr = mapped_faces_pointers[mesh_index] + vert_ptr = mapped_vertices_pointers[mesh_index] # Add triangle - mesh.faces.append(3) + mesh.faces[face_ptr] = 3 + for j in range(3): + # Add vert + mesh.faces[face_ptr + 1 + j] = mapped_index_counters[mesh_index] + j + vert_index = faces[face_index + j] * 3 + mapped_vert_offset = vert_ptr + (j * 3) - mesh.faces.append(index_counters[material_ids[i]]) - mesh.vertices.append(verts[faces[face_index] * 3]) - mesh.vertices.append(verts[faces[face_index] * 3 + 1]) - mesh.vertices.append(verts[faces[face_index] * 3 + 2]) + mesh.vertices[mapped_vert_offset] = verts[vert_index] + mesh.vertices[mapped_vert_offset + 1] = verts[vert_index + 1] + mesh.vertices[mapped_vert_offset + 2] = verts[vert_index + 2] - mesh.faces.append(index_counters[material_ids[i]] + 1) - mesh.vertices.append(verts[faces[face_index + 1] * 3]) - mesh.vertices.append(verts[faces[face_index + 1] * 3 + 1]) - mesh.vertices.append(verts[faces[face_index + 1] * 3 + 2]) - - mesh.faces.append(index_counters[material_ids[i]] + 2) - mesh.vertices.append(verts[faces[face_index + 2] * 3]) - mesh.vertices.append(verts[faces[face_index + 2] * 3 + 1]) - mesh.vertices.append(verts[faces[face_index + 2] * 3 + 2]) - - # Add normals - if len(normals) > 0: - mesh.vertexNormals.append(normals[faces[face_index] * 3]) - mesh.vertexNormals.append(normals[faces[face_index] * 3 + 1]) - mesh.vertexNormals.append(normals[faces[face_index] * 3 + 2]) - - mesh.vertexNormals.append(normals[faces[face_index + 1] * 3]) - mesh.vertexNormals.append(normals[faces[face_index + 1] * 3 + 1]) - mesh.vertexNormals.append(normals[faces[face_index + 1] * 3 + 2]) - - mesh.vertexNormals.append(normals[faces[face_index + 2] * 3]) - mesh.vertexNormals.append(normals[faces[face_index + 2] * 3 + 1]) - mesh.vertexNormals.append(normals[faces[face_index + 2] * 3 + 2]) - - # Add uvs - if len(uvs) > 0: - mesh.textureCoordinates.append(uvs[faces[face_index] * 3]) - mesh.textureCoordinates.append(uvs[faces[face_index] * 3 + 1]) - - mesh.textureCoordinates.append(uvs[faces[face_index + 1] * 3]) - mesh.textureCoordinates.append(uvs[faces[face_index + 1] * 3 + 1]) - - mesh.textureCoordinates.append(uvs[faces[face_index + 2] * 3]) - mesh.textureCoordinates.append(uvs[faces[face_index + 2] * 3 + 1]) - - index_counters[material_ids[i]] += 3 i += 1 - face_index += 3 + face_index += 3 # number of items in the faces list we just jumped over - return meshes # type: ignore + mapped_index_counters[ + mesh_index + ] += 3 # number of verts we just added to the mesh.vertices i.e. the next index + mapped_faces_pointers[ + mesh_index + ] += 4 # number of item's we've just added to the mesh.faces list + mapped_vertices_pointers[ + mesh_index + ] += 9 # number of item's we've just added to the mesh.vertices list + + return mapped_meshes # type: ignore + + +def _pre_alloc_mesh_lists(material_ids: Sequence[int], MESH_COUNT: int) -> list[Mesh]: + """ + This is a performance optimisation to pre-size the lists + since we're expecting potential hundreds of thousands of verts in a single model + This is very much in the hot path, so worth the extra bit of convoluted logic + """ + + material_face_counts = defaultdict(int) + for mat_id in material_ids: + material_face_counts[mat_id] += 1 + + meshes = [] + for mat_id in range(MESH_COUNT): + face_count = material_face_counts.get(mat_id, 0) + mesh = Mesh( + units="m", + vertices=[-1] * (face_count * 9), + faces=[-1] * (face_count * 4), # 1 marker + 3 vertex indices + ) + meshes.append(mesh) + return meshes diff --git a/src/speckleifc/ifc_iterator.py b/src/speckleifc/ifc_iterator.py index f728b66..0405577 100644 --- a/src/speckleifc/ifc_iterator.py +++ b/src/speckleifc/ifc_iterator.py @@ -10,7 +10,6 @@ def _create_settings() -> settings: ifc_settings.set("triangulation-type", ifcopenshell_wrapper.TRIANGLE_MESH) ifc_settings.set("weld-vertices", False) ifc_settings.set("use-world-coords", True) - ifc_settings.set("use-world-coords", True) return ifc_settings @@ -27,4 +26,4 @@ def open_ifc(file_path: str) -> file: def create_geometry_iterator(ifc_file: file | sqlite) -> iterator: settings = _create_settings() - return iterator(settings, ifc_file, multiprocessing.cpu_count()) + return iterator(settings, ifc_file, multiprocessing.cpu_count() // 2)