mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 15:08:51 +00:00
util.shape - optimize by using shape buffers and numpy
This commit is contained in:
@@ -33,6 +33,8 @@ VECTOR_3D = tuple[float, float, float]
|
|||||||
MatrixType = npt.NDArray[np.float64]
|
MatrixType = npt.NDArray[np.float64]
|
||||||
"""`npt.NDArray[np.float64]`"""
|
"""`npt.NDArray[np.float64]`"""
|
||||||
|
|
||||||
|
# NOTE: See IfcGeomRepresentation.h for ShapeType buffer types.
|
||||||
|
|
||||||
|
|
||||||
def is_x(value: float, x: float, tolerance: Optional[float] = None) -> bool:
|
def is_x(value: float, x: float, tolerance: Optional[float] = None) -> bool:
|
||||||
"""Checks whether a value is equivalent to X given a tolerance
|
"""Checks whether a value is equivalent to X given a tolerance
|
||||||
@@ -72,6 +74,7 @@ def get_volume(geometry: ShapeType) -> float:
|
|||||||
v123 = p1[0] * p2[1] * p3[2]
|
v123 = p1[0] * p2[1] * p3[2]
|
||||||
return (1.0 / 6.0) * (-v321 + v231 + v312 - v132 - v213 + v123)
|
return (1.0 / 6.0) * (-v321 + v231 + v312 - v132 - v213 + v123)
|
||||||
|
|
||||||
|
# Can't optimize it using buffers - performance seems to get only worse.
|
||||||
verts = geometry.verts
|
verts = geometry.verts
|
||||||
faces = geometry.faces
|
faces = geometry.faces
|
||||||
grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)]
|
grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)]
|
||||||
@@ -90,8 +93,8 @@ def get_x(geometry: ShapeType) -> float:
|
|||||||
:return: The X dimension
|
:return: The X dimension
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
x_values = [geometry.verts[i] for i in range(0, len(geometry.verts), 3)]
|
verts_flat = get_vertices(geometry).ravel()
|
||||||
return max(x_values) - min(x_values)
|
return np.max(verts_flat[0::3]) - np.min(verts_flat[0::3])
|
||||||
|
|
||||||
|
|
||||||
def get_y(geometry: ShapeType) -> float:
|
def get_y(geometry: ShapeType) -> float:
|
||||||
@@ -102,8 +105,8 @@ def get_y(geometry: ShapeType) -> float:
|
|||||||
:return: The Y dimension
|
:return: The Y dimension
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
y_values = [geometry.verts[i + 1] for i in range(0, len(geometry.verts), 3)]
|
verts_flat = get_vertices(geometry).ravel()
|
||||||
return max(y_values) - min(y_values)
|
return np.max(verts_flat[1::3]) - np.min(verts_flat[1::3])
|
||||||
|
|
||||||
|
|
||||||
def get_z(geometry: ShapeType) -> float:
|
def get_z(geometry: ShapeType) -> float:
|
||||||
@@ -114,8 +117,8 @@ def get_z(geometry: ShapeType) -> float:
|
|||||||
:return: The Z dimension
|
:return: The Z dimension
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
z_values = [geometry.verts[i + 2] for i in range(0, len(geometry.verts), 3)]
|
verts_flat = get_vertices(geometry).ravel()
|
||||||
return max(z_values) - min(z_values)
|
return np.max(verts_flat[2::3]) - np.min(verts_flat[2::3])
|
||||||
|
|
||||||
|
|
||||||
def get_max_xy(geometry: ShapeType) -> float:
|
def get_max_xy(geometry: ShapeType) -> float:
|
||||||
@@ -172,19 +175,18 @@ def get_bbox_centroid(geometry: ShapeType) -> tuple[float, float, float]:
|
|||||||
:return: A tuple representing the XYZ centroid
|
:return: A tuple representing the XYZ centroid
|
||||||
:rtype: tuple[float, float, float]
|
:rtype: tuple[float, float, float]
|
||||||
"""
|
"""
|
||||||
x_values = [geometry.verts[i] for i in range(0, len(geometry.verts), 3)]
|
verts_flat = get_vertices(geometry).ravel()
|
||||||
y_values = [geometry.verts[i + 1] for i in range(0, len(geometry.verts), 3)]
|
x_values = verts_flat[0::3]
|
||||||
z_values = [geometry.verts[i + 2] for i in range(0, len(geometry.verts), 3)]
|
y_values = verts_flat[1::3]
|
||||||
x_values: list[float]
|
z_values = verts_flat[2::3]
|
||||||
y_values: list[float]
|
|
||||||
z_values: list[float]
|
|
||||||
minx = min(x_values)
|
minx = min(x_values)
|
||||||
maxx = max(x_values)
|
maxx = max(x_values)
|
||||||
miny = min(y_values)
|
miny = min(y_values)
|
||||||
maxy = max(y_values)
|
maxy = max(y_values)
|
||||||
minz = min(z_values)
|
minz = min(z_values)
|
||||||
maxz = max(z_values)
|
maxz = max(z_values)
|
||||||
return (minx + ((maxx - minx) / 2), miny + ((maxy - miny) / 2), minz + ((maxz - minz) / 2))
|
res = np.array((minx + ((maxx - minx) / 2), miny + ((maxy - miny) / 2), minz + ((maxz - minz) / 2)))
|
||||||
|
return res.tolist()
|
||||||
|
|
||||||
|
|
||||||
def get_element_bbox_centroid(element: ifcopenshell.entity_instance, geometry) -> npt.NDArray[np.float64]:
|
def get_element_bbox_centroid(element: ifcopenshell.entity_instance, geometry) -> npt.NDArray[np.float64]:
|
||||||
@@ -236,8 +238,7 @@ def get_vertices(geometry: ShapeType) -> npt.NDArray[np.float64]:
|
|||||||
:return: A numpy array listing all the vertices. Each vertex is a numpy array with XYZ coordinates.
|
:return: A numpy array listing all the vertices. Each vertex is a numpy array with XYZ coordinates.
|
||||||
:rtype: np.array[np.array[float]]
|
:rtype: np.array[np.array[float]]
|
||||||
"""
|
"""
|
||||||
verts = geometry.verts
|
return np.frombuffer(geometry.verts_buffer, "d").reshape(-1, 3)
|
||||||
return np.array([np.array([verts[i], verts[i + 1], verts[i + 2]]) for i in range(0, len(verts), 3)])
|
|
||||||
|
|
||||||
|
|
||||||
def get_edges(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
def get_edges(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
||||||
@@ -254,8 +255,7 @@ def get_edges(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
|||||||
:return: A numpy array listing all the edges. Each edge is a numpy array with two vertex indices.
|
:return: A numpy array listing all the edges. Each edge is a numpy array with two vertex indices.
|
||||||
:rtype: np.array[np.array[int]]
|
:rtype: np.array[np.array[int]]
|
||||||
"""
|
"""
|
||||||
edges = geometry.edges
|
return np.frombuffer(geometry.edges_buffer, dtype="i").reshape(-1, 2)
|
||||||
return np.array([[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)])
|
|
||||||
|
|
||||||
|
|
||||||
def get_faces(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
def get_faces(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
||||||
@@ -271,8 +271,7 @@ def get_faces(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
|||||||
:return: A numpy array listing all the faces. Each face is a numpy array with three vertex indices.
|
:return: A numpy array listing all the faces. Each face is a numpy array with three vertex indices.
|
||||||
:rtype: np.array[np.array[int]]
|
:rtype: np.array[np.array[int]]
|
||||||
"""
|
"""
|
||||||
faces = geometry.faces
|
return np.frombuffer(geometry.faces_buffer, dtype="i").reshape(-1, 3)
|
||||||
return np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)])
|
|
||||||
|
|
||||||
|
|
||||||
def get_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
def get_representation_item_ids(geometry: ShapeType) -> npt.NDArray[np.int32]:
|
||||||
@@ -342,8 +341,8 @@ def get_top_elevation(geometry: ShapeType) -> float:
|
|||||||
:return: The Z value
|
:return: The Z value
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
z_values = [geometry.verts[i + 2] for i in range(0, len(geometry.verts), 3)]
|
verts_flat = get_vertices(geometry).ravel()
|
||||||
return max(z_values)
|
return np.max(verts_flat[2::3])
|
||||||
|
|
||||||
|
|
||||||
def get_shape_bottom_elevation(shape: ShapeType, geometry: ShapeType) -> float:
|
def get_shape_bottom_elevation(shape: ShapeType, geometry: ShapeType) -> float:
|
||||||
@@ -465,10 +464,8 @@ def get_area(geometry: ShapeType) -> float:
|
|||||||
:return: The surface area.
|
:return: The surface area.
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
verts = geometry.verts
|
vertices = get_vertices(geometry)
|
||||||
faces = geometry.faces
|
faces = get_faces(geometry)
|
||||||
vertices = np.array([[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)])
|
|
||||||
faces = np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)])
|
|
||||||
return get_area_vf(vertices, faces)
|
return get_area_vf(vertices, faces)
|
||||||
|
|
||||||
|
|
||||||
@@ -501,10 +498,8 @@ def get_side_area(
|
|||||||
if direction is None:
|
if direction is None:
|
||||||
direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis]
|
direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis]
|
||||||
|
|
||||||
verts = geometry.verts
|
vertices = get_vertices(geometry)
|
||||||
faces = geometry.faces
|
faces = get_faces(geometry)
|
||||||
vertices = np.array([[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)])
|
|
||||||
faces = np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)])
|
|
||||||
|
|
||||||
# Calculate the triangle normal vectors
|
# Calculate the triangle normal vectors
|
||||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||||
@@ -568,10 +563,8 @@ def get_footprint_area(
|
|||||||
if direction is None:
|
if direction is None:
|
||||||
direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis]
|
direction = {"X": (1.0, 0.0, 0.0), "Y": (0.0, 1.0, 0.0), "Z": (0.0, 0.0, 1.0)}[axis]
|
||||||
|
|
||||||
verts = geometry.verts
|
vertices = get_vertices(geometry)
|
||||||
faces = geometry.faces
|
faces = get_faces(geometry)
|
||||||
vertices = np.array([[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)])
|
|
||||||
faces = np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)])
|
|
||||||
|
|
||||||
# Calculate the triangle normal vectors
|
# Calculate the triangle normal vectors
|
||||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||||
@@ -590,6 +583,7 @@ def get_footprint_area(
|
|||||||
filtered_faces = faces[filtered_face_indices]
|
filtered_faces = faces[filtered_face_indices]
|
||||||
|
|
||||||
# Flatten vertices along the direction
|
# Flatten vertices along the direction
|
||||||
|
vertices = vertices.copy() # Buffers are read-only.
|
||||||
for idx in range(len(vertices)):
|
for idx in range(len(vertices)):
|
||||||
vertices[idx] = vertices[idx] - np.dot(vertices[idx], direction) * direction
|
vertices[idx] = vertices[idx] - np.dot(vertices[idx], direction) * direction
|
||||||
|
|
||||||
@@ -632,10 +626,8 @@ def get_outer_surface_area(geometry: ShapeType) -> float:
|
|||||||
:return: The surface area.
|
:return: The surface area.
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
verts = geometry.verts
|
vertices = get_vertices(geometry)
|
||||||
faces = geometry.faces
|
faces = get_faces(geometry)
|
||||||
vertices = np.array([[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)])
|
|
||||||
faces = np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)])
|
|
||||||
|
|
||||||
# Calculate the triangle normal vectors
|
# Calculate the triangle normal vectors
|
||||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||||
@@ -662,10 +654,8 @@ def get_footprint_perimeter(geometry: ShapeType) -> float:
|
|||||||
:return: The perimeter length
|
:return: The perimeter length
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
verts = geometry.verts
|
vertices = get_vertices(geometry)
|
||||||
faces = geometry.faces
|
faces = get_faces(geometry)
|
||||||
vertices = np.array([[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)])
|
|
||||||
faces = np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)])
|
|
||||||
|
|
||||||
# Calculate the triangle normal vectors
|
# Calculate the triangle normal vectors
|
||||||
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
v1 = vertices[faces[:, 1]] - vertices[faces[:, 0]]
|
||||||
|
|||||||
Reference in New Issue
Block a user