Last active
November 26, 2021 07:00
-
-
Save Moult/009bd98b45fc25307f5d9c7bfa48b6f7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import bpy | |
import bmesh | |
import time | |
import mathutils | |
import ifcopenshell | |
import multiprocessing | |
from itertools import chain, accumulate | |
class MeshRacer: | |
def execute(self): | |
self.item_cache = {} | |
#self.file = ifcopenshell.open("/home/dion/cube.ifc") | |
t = time.time() | |
#self.file = ifcopenshell.open("/home/dion/drive/bim/ifcs/IFC Schependomlaan.ifc") | |
self.file = ifcopenshell.open("/home/dion/test.ifc") | |
#self.file = ifcopenshell.open("/home/dion/drive/bim/ifcs/goba.blend.ifc") | |
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) | |
self.body_contexts = [ | |
c.id() | |
for c in self.file.by_type("IfcGeometricRepresentationSubContext") | |
if c.ContextIdentifier in ["Body", "Facetation"] | |
] | |
print("Load file:", time.time() - t) | |
t = time.time() | |
self.elements = self.get_native_elements() | |
print(f"Get native elements ({len(self.elements)}):", time.time() - t) | |
t = time.time() | |
self.process_using_iterator() | |
print("Using iterator:", time.time() - t) | |
t = time.time() | |
self.process_using_python() | |
print("Using Python:", time.time() - t) | |
def get_native_elements(self): | |
results = set() | |
for element in self.file.by_type("IfcElement"): | |
if self.is_basic_faceted_brep(element): | |
results.add(element) | |
return results | |
def is_basic_faceted_brep(self, element): | |
if ( | |
not element.Representation | |
or not element.Representation.Representations | |
or getattr(element, "HasOpenings", None) | |
): | |
return False | |
body = None | |
for rep in element.Representation.Representations: | |
if rep.RepresentationIdentifier == "Body": | |
body = rep | |
break | |
if not body: | |
return False | |
for subelement in self.file.traverse(body): | |
if subelement.is_a("IfcShapeRepresentation") and [i for i in subelement.Items if i.is_a() not in ["IfcFacetedBrep", "IfcMappedItem"]]: | |
return False | |
return True | |
def process_using_iterator(self): | |
settings = ifcopenshell.geom.settings() | |
iterator = ifcopenshell.geom.iterator(settings, self.file, multiprocessing.cpu_count(), include=self.elements) | |
if self.body_contexts: | |
settings.set_context_ids(self.body_contexts) | |
valid_file = iterator.initialize() | |
if not valid_file: | |
return | |
while True: | |
shape = iterator.get() | |
if shape: | |
geometry = shape.geometry | |
verts = geometry.verts | |
if geometry.faces: | |
mesh = bpy.data.meshes.new("Iterator") | |
num_vertices = len(verts) // 3 | |
total_faces = len(geometry.faces) | |
loop_start = range(0, total_faces, 3) | |
num_loops = total_faces // 3 | |
loop_total = [3] * num_loops | |
num_vertex_indices = len(geometry.faces) | |
mesh.vertices.add(num_vertices) | |
mesh.vertices.foreach_set("co", verts) | |
mesh.loops.add(num_vertex_indices) | |
mesh.loops.foreach_set("vertex_index", geometry.faces) | |
mesh.polygons.add(num_loops) | |
mesh.polygons.foreach_set("loop_start", loop_start) | |
mesh.polygons.foreach_set("loop_total", loop_total) | |
mesh.update() | |
if not iterator.next(): | |
break | |
def process_using_python(self): | |
for element in self.elements: | |
self.create_native_faceted_brep(element) | |
def create_native_faceted_brep(self, element): | |
self.mesh_data = { | |
"co": [], | |
"vertex_index": [], | |
"loop_start": [], | |
"loop_total": [], | |
"total_verts": 0, | |
"total_polygons": 0, | |
"materials": [], | |
"material_ids": [], | |
} | |
for representation in element.Representation.Representations: | |
if representation.ContextOfItems.id() not in self.body_contexts: | |
continue | |
self.convert_representation(representation) | |
mesh = bpy.data.meshes.new("Native") | |
mesh.vertices.add(self.mesh_data["total_verts"]) | |
mesh.vertices.foreach_set("co", [c * self.unit_scale for c in self.mesh_data["co"]]) | |
mesh.loops.add(len(self.mesh_data["vertex_index"])) | |
mesh.loops.foreach_set("vertex_index", self.mesh_data["vertex_index"]) | |
mesh.polygons.add(self.mesh_data["total_polygons"]) | |
mesh.polygons.foreach_set("loop_start", self.mesh_data["loop_start"]) | |
mesh.polygons.foreach_set("loop_total", self.mesh_data["loop_total"]) | |
mesh.update() | |
def convert_representation(self, representation): | |
for item in representation.Items: | |
self.convert_representation_item(item) | |
def convert_representation_item(self, item): | |
if item.is_a("IfcMappedItem"): | |
# Do something clever here with matrices, but for this demo code, do nothing | |
self.convert_representation(item.MappingSource.MappedRepresentation) | |
elif item.is_a() == "IfcFacetedBrep": | |
self.convert_representation_item_faceted_brep(item) | |
def convert_representation_item_faceted_brep(self, item): | |
# This "cache" actually makes things worse? | |
cache = self.item_cache.get(item.id(), None) | |
if not cache: | |
# On a few occasions, we flatten a list. This seems to be the most efficient way to do it. | |
# https://stackoverflow.com/questions/20112776/how-do-i-flatten-a-list-of-lists-nested-lists | |
mesh = item.get_info_2(recursive=True) | |
#bounds = (f["Bounds"] for f in mesh["Outer"]["CfsFaces"]) | |
# For huge face sets it might be better to do a "flatmap" instead of sum() | |
#bounds = sum((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1), ()) | |
# Here are some untested alternatives, are they faster? | |
# bounds = tuple((f["Bounds"] for f in mesh["Outer"]["CfsFaces"]))[0] | |
# After testing, this seems to be the fastest | |
bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1)) | |
polygons = [[(p["id"], p["Coordinates"]) for p in b["Bound"]["Polygon"]] for b in bounds] | |
for face in mesh["Outer"]["CfsFaces"]: | |
# Blender cannot handle faces with holes. | |
if len(face["Bounds"]) > 1: | |
inner_bounds = [] | |
inner_bound_point_ids = [] | |
for bound in face["Bounds"]: | |
if bound["type"] == "IfcFaceOuterBound": | |
outer_bound = [[p["Coordinates"] for p in bound["Bound"]["Polygon"]]] | |
outer_bound_point_ids = [[p["id"] for p in bound["Bound"]["Polygon"]]] | |
else: | |
inner_bounds.append([p["Coordinates"] for p in bound["Bound"]["Polygon"]]) | |
inner_bound_point_ids.append([p["id"] for p in bound["Bound"]["Polygon"]]) | |
points = outer_bound[0].copy() | |
[points.extend(p) for p in inner_bounds] | |
point_ids = outer_bound_point_ids[0].copy() | |
[point_ids.extend(p) for p in inner_bound_point_ids] | |
tessellated_polygons = mathutils.geometry.tessellate_polygon(outer_bound + inner_bounds) | |
polygons.extend([[(point_ids[pi], points[pi]) for pi in t] for t in tessellated_polygons]) | |
# Clever vertex welding algorithm by Thomas Krijnen. See #841. | |
# by id | |
di0 = {} | |
# by coords | |
di1 = {} | |
vertex_index_offset = self.mesh_data["total_verts"] | |
def lookup(id_coords): | |
idx = di0.get(id_coords[0]) | |
if idx is None: | |
idx = di1.get(id_coords[1]) | |
if idx is None: | |
l = len(di0) | |
di0[id_coords[0]] = l | |
di1[id_coords[1]] = l | |
return l + vertex_index_offset | |
else: | |
return idx + vertex_index_offset | |
else: | |
return idx + vertex_index_offset | |
mapped_polygons = [list(map(lookup, p)) for p in polygons] | |
cache = { | |
"di1_keys": di1.keys(), | |
"mapped_polygons": mapped_polygons, | |
} | |
self.item_cache[item.id()] = cache | |
mapped_polygons = cache["mapped_polygons"] | |
di1_keys = cache["di1_keys"] | |
self.mesh_data["vertex_index"].extend(chain.from_iterable(mapped_polygons)) | |
# Flattened vertex coords | |
self.mesh_data["co"].extend(chain.from_iterable(di1_keys)) | |
self.mesh_data["total_verts"] += len(di1_keys) | |
loop_total = [len(p) for p in mapped_polygons] | |
total_polygons = len(mapped_polygons) | |
self.mesh_data["total_polygons"] += total_polygons | |
self.mesh_data["materials"].append(self.get_representation_item_material_name(item) or "NULLMAT") | |
material_index = len(self.mesh_data["materials"]) - 1 | |
if self.mesh_data["materials"][material_index] == "NULLMAT": | |
# Magic number -1 represents no material, until this has a better approach | |
self.mesh_data["material_ids"] += [-1] * total_polygons | |
else: | |
self.mesh_data["material_ids"] += [material_index] * total_polygons | |
if self.mesh_data["loop_start"]: | |
loop_start_offset = self.mesh_data["loop_start"][-1] + self.mesh_data["loop_total"][-1] | |
else: | |
loop_start_offset = 0 | |
loop_start = [loop_start_offset] + [loop_start_offset + i for i in list(accumulate(loop_total[0:-1]))] | |
self.mesh_data["loop_total"].extend(loop_total) | |
self.mesh_data["loop_start"].extend(loop_start) | |
def get_representation_item_material_name(self, item): | |
if not item.StyledByItem: | |
return | |
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 | |
MeshRacer().execute() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment