mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Fix #5538. Purge native mesh handling in Blender. This is now obsolete with CGAL-Hybrid.
This commit is contained in:
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user