Skip to content

Instantly share code, notes, and snippets.

@Moult
Last active November 26, 2021 07:00
Show Gist options
  • Save Moult/009bd98b45fc25307f5d9c7bfa48b6f7 to your computer and use it in GitHub Desktop.
Save Moult/009bd98b45fc25307f5d9c7bfa48b6f7 to your computer and use it in GitHub Desktop.
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