Fix #5538. Purge native mesh handling in Blender. This is now obsolete with CGAL-Hybrid.

This commit is contained in:
Dion Moult
2024-10-11 08:40:17 +11:00
parent 189bcd5ad6
commit 2e21ec749b
-319
View File
@@ -237,8 +237,6 @@ class IfcImporter:
self.profile_code("Process context filter")
self.calculate_model_offset()
self.profile_code("Calculate model offset")
self.predict_dense_mesh()
self.profile_code("Predict dense mesh")
self.set_units()
self.profile_code("Set units")
self.create_project()
@@ -426,28 +424,6 @@ class IfcImporter:
if not self.ifc_import_settings.should_use_native_meshes:
return False # Performance improvements only occur on edge cases currently
# FacetedBreps (without voids) are meshes. See #841.
if self.is_native_faceted_brep(resolved_representation):
self.native_data[element.GlobalId] = {
"matrix": matrix,
"context": context,
"geometry_id": representation_id,
"representation": resolved_representation,
"type": "IfcFacetedBrep",
}
return True
if self.is_native_face_based_surface_model(resolved_representation):
self.native_data[element.GlobalId] = {
"matrix": matrix,
"context": context,
"geometry_id": representation_id,
"representation": resolved_representation,
"type": "IfcFaceBasedSurfaceModel",
}
return True
return False
def is_native_swept_disk_solid(
self, element: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
) -> bool:
@@ -466,55 +442,6 @@ class IfcImporter:
return True
return False
def is_native_faceted_brep(self, representation: ifcopenshell.entity_instance) -> bool:
# TODO handle mapped items
for i in representation.Items:
if i.is_a() != "IfcFacetedBrep":
return False
return True
def is_native_face_based_surface_model(self, representation: ifcopenshell.entity_instance) -> bool:
for i in representation.Items:
if i.is_a() != "IfcFaceBasedSurfaceModel":
return False
return True
def get_products_from_shape_representation(self, element: ifcopenshell.entity_instance) -> None:
products = [pr.ShapeOfProduct[0] for pr in element.OfProductRepresentation]
for rep_map in element.RepresentationMap:
for usage in rep_map.MapUsage:
for inverse_element in self.file.get_inverse(usage):
if inverse_element.is_a("IfcShapeRepresentation"):
products.extend(self.get_products_from_shape_representation(inverse_element))
return products
def predict_dense_mesh(self) -> None:
if self.ifc_import_settings.should_use_native_meshes:
return
threshold = 10000 # Just from experience.
# The check for CfsFaces/Faces/CoordIndex accommodates invalid data from Cadwork
# 0 IfcClosedShell.CfsFaces
faces = [len(faces) for e in self.file.by_type("IfcClosedShell") if (faces := e[0])]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
if self.file.schema == "IFC2X3":
return
# 2 IfcPolygonalFaceSet.Faces
faces = [len(faces) for e in self.file.by_type("IfcPolygonalFaceSet") if (faces := e[2])]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
return
# 3 IfcTriangulatedFaceSet.CoordIndex
faces = [len(index) for e in self.file.by_type("IfcTriangulatedFaceSet") if (index := e[3])]
if faces and max(faces) > threshold:
self.ifc_import_settings.should_use_native_meshes = True
def calculate_model_offset(self) -> None:
props = bpy.context.scene.BIMGeoreferenceProperties
if self.ifc_import_settings.false_origin_mode == "MANUAL":
@@ -621,10 +548,6 @@ class IfcImporter:
if mesh is None:
if native_data["type"] == "IfcSweptDiskSolid":
mesh = self.create_native_swept_disk_solid(element, mesh_name, native_data)
elif native_data["type"] == "IfcFacetedBrep":
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
mesh = self.create_native_faceted_brep(element, mesh_name, native_data)
tool.Ifc.link(tool.Ifc.get().by_id(native_data["geometry_id"]), mesh)
mesh.name = mesh_name
self.meshes[mesh_name] = mesh
@@ -923,180 +846,6 @@ class IfcImporter:
def load_existing_meshes(self) -> None:
self.meshes.update({m.name: m for m in bpy.data.meshes})
def get_representation_item_material_name(self, item):
if not item.StyledByItem:
return
styles = list(item.StyledByItem[0].Styles)
while styles:
style = styles.pop()
if style.is_a("IfcSurfaceStyle"):
return style.id()
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
def create_native_faceted_brep(self, element, mesh_name, native_data):
# 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)
self.mesh_data = {
"co": [],
"vertex_index": [],
"loop_start": [],
"loop_total": [],
"total_verts": 0,
"total_polygons": 0,
"materials": [],
"material_ids": [],
}
for item in native_data["representation"].Items:
if item.is_a() == "IfcFacetedBrep":
self.convert_representation_item_faceted_brep(item)
elif item.is_a() == "IfcFaceBasedSurfaceModel":
self.convert_representation_item_face_based_surface_model(item)
mesh = bpy.data.meshes.new("Native")
props = bpy.context.scene.BIMGeoreferenceProperties
if props.has_blender_offset and tool.Loader.is_point_far_away(self.mesh_data["co"][0:3], is_meters=False):
verts_array = np.array(self.mesh_data["co"])
verts_array *= self.unit_scale
offset_x, offset_y, offset_z = verts_array[0:3]
offset = np.array([-offset_x, -offset_y, -offset_z])
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
if np.allclose(native_data["matrix"], np.identity(4), atol=1e-8):
verts = offset_verts.tolist()
else:
verts = self.apply_matrix_to_flat_coords(offset_verts, native_data["matrix"])
mesh["has_cartesian_point_offset"] = True
mesh["cartesian_point_offset"] = f"{offset_x},{offset_y},{offset_z}"
else:
verts_array = np.array(self.mesh_data["co"])
verts_array *= self.unit_scale
if np.allclose(native_data["matrix"], np.identity(4), atol=1e-8):
verts = verts_array.tolist()
else:
verts = self.apply_matrix_to_flat_coords(verts_array, native_data["matrix"])
mesh["has_cartesian_point_offset"] = False
mesh.vertices.add(self.mesh_data["total_verts"])
mesh.vertices.foreach_set("co", verts)
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.polygons.foreach_set("use_smooth", [0] * self.mesh_data["total_polygons"])
mesh.update()
mesh["ios_materials"] = self.mesh_data["materials"]
mesh["ios_material_ids"] = self.mesh_data["material_ids"]
return mesh
def apply_matrix_to_flat_coords(self, coords, matrix):
coords_array = np.array(coords).reshape(-1, 3)
ones = np.ones((coords_array.shape[0], 1))
homogeneous_coords = np.hstack([coords_array, ones])
transformed_coords = homogeneous_coords @ matrix.T
return transformed_coords[:, :3].flatten().tolist()
def convert_representation_item_face_based_surface_model(self, item):
mesh = item.get_info_2(recursive=True)
for face_set in mesh["FbsmFaces"]:
self.convert_representation_item_face_set(item, face_set)
def convert_representation_item_faceted_brep(self, item):
mesh = item.get_info_2(recursive=True)
return self.convert_representation_item_face_set(item, mesh["Outer"])
def convert_representation_item_face_set(self, item, mesh):
# 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
# 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["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["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: ifcopenshell.entity_instance, mesh_name: str, native_data: dict[str, Any]
) -> bpy.types.Curve:
@@ -1294,38 +1043,6 @@ class IfcImporter:
result[2][3] *= self.unit_scale
return result
def scale_matrix(self, matrix: np.array) -> np.array:
matrix[0][3] *= self.unit_scale
matrix[1][3] *= self.unit_scale
matrix[2][3] *= self.unit_scale
return matrix
def get_representation_id(self, element):
if not element.Representation:
return None
for representation in element.Representation.Representations:
if not representation.is_a("IfcShapeRepresentation"):
continue
if (
representation.RepresentationIdentifier == "Body"
and representation.RepresentationType != "MappedRepresentation"
):
return representation.id()
elif representation.RepresentationIdentifier == "Body":
return representation.Items[0].MappingSource.MappedRepresentation.id()
def get_representation_cartesian_transformation(self, element):
if not element.Representation:
return None
for representation in element.Representation.Representations:
if not representation.is_a("IfcShapeRepresentation"):
continue
if (
representation.RepresentationIdentifier == "Body"
and representation.RepresentationType == "MappedRepresentation"
):
return representation.Items[0].MappingTarget
def create_curve(
self,
element: ifcopenshell.entity_instance,
@@ -1411,42 +1128,6 @@ class IfcImporter:
print(traceback.format_exc())
def a2p(self, o: mathutils.Vector, z: mathutils.Vector, x: mathutils.Vector) -> mathutils.Matrix:
y = z.cross(x)
r = mathutils.Matrix((x, y, z, o))
r.resize_4x4()
r.transpose()
return r
def get_axis2placement(self, plc: ifcopenshell.entity_instance) -> mathutils.Matrix:
if plc.is_a("IfcAxis2Placement3D"):
z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1))
x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0))
o = plc.Location.Coordinates
else:
z = mathutils.Vector((0, 0, 1))
if plc.RefDirection:
x = mathutils.Vector(list(plc.RefDirection.DirectionRatios) + [0])
else:
x = mathutils.Vector((1, 0, 0))
o = list(plc.Location.Coordinates) + [0]
return self.a2p(o, z, x)
def get_cartesiantransformationoperator(self, plc):
x = mathutils.Vector(plc.Axis1.DirectionRatios if plc.Axis1 else (1, 0, 0))
z = x.cross(mathutils.Vector(plc.Axis2.DirectionRatios if plc.Axis2 else (0, 1, 0)))
o = plc.LocalOrigin.Coordinates
return self.a2p(o, z, x)
def get_local_placement(self, plc: Optional[ifcopenshell.entity_instance] = None) -> mathutils.Matrix:
if plc is None:
return mathutils.Matrix()
if plc.PlacementRelTo is None:
parent = mathutils.Matrix()
else:
parent = self.get_local_placement(plc.PlacementRelTo)
return parent @ self.get_axis2placement(plc.RelativePlacement)
def set_default_context(self):
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
if subcontext.ContextIdentifier == "Body":