mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-30 16:43:00 +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.profile_code("Process context filter")
|
||||||
self.calculate_model_offset()
|
self.calculate_model_offset()
|
||||||
self.profile_code("Calculate model offset")
|
self.profile_code("Calculate model offset")
|
||||||
self.predict_dense_mesh()
|
|
||||||
self.profile_code("Predict dense mesh")
|
|
||||||
self.set_units()
|
self.set_units()
|
||||||
self.profile_code("Set units")
|
self.profile_code("Set units")
|
||||||
self.create_project()
|
self.create_project()
|
||||||
@@ -426,28 +424,6 @@ class IfcImporter:
|
|||||||
if not self.ifc_import_settings.should_use_native_meshes:
|
if not self.ifc_import_settings.should_use_native_meshes:
|
||||||
return False # Performance improvements only occur on edge cases currently
|
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(
|
def is_native_swept_disk_solid(
|
||||||
self, element: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
self, element: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -466,55 +442,6 @@ class IfcImporter:
|
|||||||
return True
|
return True
|
||||||
return False
|
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:
|
def calculate_model_offset(self) -> None:
|
||||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||||
if self.ifc_import_settings.false_origin_mode == "MANUAL":
|
if self.ifc_import_settings.false_origin_mode == "MANUAL":
|
||||||
@@ -621,10 +548,6 @@ class IfcImporter:
|
|||||||
if mesh is None:
|
if mesh is None:
|
||||||
if native_data["type"] == "IfcSweptDiskSolid":
|
if native_data["type"] == "IfcSweptDiskSolid":
|
||||||
mesh = self.create_native_swept_disk_solid(element, mesh_name, native_data)
|
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)
|
tool.Ifc.link(tool.Ifc.get().by_id(native_data["geometry_id"]), mesh)
|
||||||
mesh.name = mesh_name
|
mesh.name = mesh_name
|
||||||
self.meshes[mesh_name] = mesh
|
self.meshes[mesh_name] = mesh
|
||||||
@@ -923,180 +846,6 @@ class IfcImporter:
|
|||||||
def load_existing_meshes(self) -> None:
|
def load_existing_meshes(self) -> None:
|
||||||
self.meshes.update({m.name: m for m in bpy.data.meshes})
|
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(
|
def create_native_swept_disk_solid(
|
||||||
self, element: ifcopenshell.entity_instance, mesh_name: str, native_data: dict[str, Any]
|
self, element: ifcopenshell.entity_instance, mesh_name: str, native_data: dict[str, Any]
|
||||||
) -> bpy.types.Curve:
|
) -> bpy.types.Curve:
|
||||||
@@ -1294,38 +1043,6 @@ class IfcImporter:
|
|||||||
result[2][3] *= self.unit_scale
|
result[2][3] *= self.unit_scale
|
||||||
return result
|
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(
|
def create_curve(
|
||||||
self,
|
self,
|
||||||
element: ifcopenshell.entity_instance,
|
element: ifcopenshell.entity_instance,
|
||||||
@@ -1411,42 +1128,6 @@ class IfcImporter:
|
|||||||
|
|
||||||
print(traceback.format_exc())
|
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):
|
def set_default_context(self):
|
||||||
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
|
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
|
||||||
if subcontext.ContextIdentifier == "Body":
|
if subcontext.ContextIdentifier == "Body":
|
||||||
|
|||||||
Reference in New Issue
Block a user