Experimental optimisation attempt for native faceted brep handling by aothms. See #841.

This commit is contained in:
Dion Moult
2022-02-09 14:36:14 +11:00
parent 05f3cd41e2
commit caf04200ce
+129 -77
View File
@@ -33,6 +33,7 @@ import ifcopenshell.util.element
import ifcopenshell.util.selector
import ifcopenshell.util.geolocation
import blenderbim.tool as tool
from itertools import chain, accumulate
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.drawing.prop import get_diagram_scales
@@ -346,12 +347,12 @@ class IfcImporter:
# FacetedBreps (without voids) are meshes. See #841.
# Commented out as seems currently too slow.
# if self.is_native_faceted_brep(representations):
# self.native_data[element.GlobalId] = {
# "representations": representations,
# "representation": self.get_body_representation(element.Representation.Representations),
# "type": "IfcFacetedBrep",
# }
# return True
# self.native_data[element.GlobalId] = {
# "representations": representations,
# "representation": self.get_body_representation(element.Representation.Representations),
# "type": "IfcFacetedBrep",
# }
# return True
def is_native_swept_disk_solid(self, representations):
for representation in representations:
@@ -562,7 +563,6 @@ class IfcImporter:
def create_native_elements(self):
total = 0
checkpoint = time.time()
bm = bmesh.new()
for element in self.native_elements:
total += 1
if total % 250 == 0:
@@ -584,14 +584,6 @@ class IfcImporter:
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
self.create_product(element, mesh=mesh)
if native_data["type"] == "IfcFacetedBrep":
# The current implementation doesn't reuse vertices, so we weld it after assigning materials.
# This welding isn't true to the representation, but is easy and seems inexpensive.
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bm.to_mesh(mesh)
bm.clear()
bm.free()
print("Done creating geometry")
def create_spatial_elements(self):
@@ -853,78 +845,138 @@ class IfcImporter:
def create_native_faceted_brep(self, element, mesh_name):
# TODO: georeferencing?
# Note: to make this algorithm simpler (it's already confusing) we don't reuse / weld verts
# co [x y z x y z x y z ...]
# vertex_index [i i i i i ...]
# loop_start [0 3 6 9 ...] (for tris)
# loop_total [3 3 3 3 ...] (for tris)
co = []
vertex_index = []
loop_start = []
loop_total = []
total_verts = 0
total_polygons = 0
materials = []
material_ids = []
item_index = 0
self.mesh_data = {
"co": [],
"vertex_index": [],
"loop_start": [],
"loop_total": [],
"total_verts": 0,
"total_polygons": 0,
"materials": [],
"material_ids": [],
}
for representation in self.native_data[element.GlobalId]["representations"]:
for item in representation["raw"].Items:
# TODO: if I reimplement native faceted breps, recheck this material implementation
materials.append(self.get_representation_item_material_name(item) or "NULLMAT")
mesh = item.get_info_2(recursive=True) # See bug #841
total_item_polygons = 0
for face in mesh["Outer"]["CfsFaces"]:
# Blender cannot handle faces with holes.
if len(face["Bounds"]) > 1:
inner_bounds = []
for bound in face["Bounds"]:
if bound["type"] == "IfcFaceOuterBound":
outer_bound = [[p["Coordinates"] for p in bound["Bound"]["Polygon"]]]
else:
inner_bounds.append([p["Coordinates"] for p in bound["Bound"]["Polygon"]])
points = outer_bound[0].copy()
[points.extend(p) for p in inner_bounds]
tessellated_polygons = mathutils.geometry.tessellate_polygon(outer_bound + inner_bounds)
tessellated_faces = [({"Coordinates": points[pi]} for pi in t) for t in tessellated_polygons]
else:
tessellated_faces = [face["Bounds"][0]["Bound"]["Polygon"]]
for representation in element.Representation.Representations:
if representation.ContextOfItems.id() not in self.body_contexts:
continue
self.convert_representation(representation)
for tessellated_face in tessellated_faces:
loop_start.append(total_verts)
loop_count = 0
total_polygons += 1
total_item_polygons += 1
for point in tessellated_face:
co.extend(
representation["matrix"]
@ mathutils.Vector((c * self.unit_scale for c in point["Coordinates"]))
)
total_verts += 1
loop_count += 1
loop_total.append(loop_count)
vertex_index = range(0, total_verts)
if materials[item_index] == "NULLMAT":
# Magic number -1 represents no material, until this has a better approach
material_ids += [-1] * total_item_polygons
else:
material_ids += [item_index] * total_item_polygons
item_index += 1
mesh = bpy.data.meshes.new("Tester")
mesh.vertices.add(total_verts)
mesh.vertices.foreach_set("co", co)
mesh.loops.add(total_verts)
mesh.loops.foreach_set("vertex_index", vertex_index)
mesh.polygons.add(total_polygons)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
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.vertices.foreach_set("co", 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()
mesh["ios_materials"] = materials
mesh["ios_material_ids"] = material_ids
mesh["ios_materials"] = self.mesh_data["materials"]
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
return mesh
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"):
# mapping_target = matrix
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):
# 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)
# 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), ())
bounds = tuple(chain.from_iterable(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"] if len(f["Bounds"]) == 1))[0]
# bounds = 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]
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)
# list(di1.keys())
def create_native_swept_disk_solid(self, element, mesh_name):
# TODO: georeferencing?
curve = bpy.data.curves.new(mesh_name, type="CURVE")