Source code for aerocaps.geom.tools

import numpy as np
import shapely
import triangle
from scipy.optimize import minimize_scalar

from aerocaps.geom.curves import BezierCurve3D, PCurve2D, PCurve3D, Line3D
from aerocaps.geom.point import Point3D, Point2D
from aerocaps.geom.transformation import Transformation3D
from aerocaps.geom.vector import Vector3D
from aerocaps.units.angle import Angle


__all__ = [
    "measure_distance_between_points",
    "measure_pitch_angle",
    "measure_distance_point_line",
    "add_vector_to_point",
    "project_point_onto_line",
    "find_t_corresponding_to_minimum_distance_to_point2d",
    "find_t_corresponding_to_minimum_distance_to_point3d",
    "sweep_along_curve",
    "rotate_about_axis",
    "rotate_point_about_axis",
    "concave_hull"
]


[docs] def measure_distance_between_points(point_1: Point2D or Point3D or np.ndarray, point_2: Point2D or Point3D or np.ndarray) -> float: if isinstance(point_1, Point2D) or isinstance(point_1, Point3D): point_1 = point_1.as_array() if isinstance(point_2, Point2D) or isinstance(point_2, Point3D): point_2 = point_2.as_array() if len(point_1) != len(point_2): raise ValueError("Cannot calculate distance between two points of different dimensionality") if len(point_1) == 2 and len(point_2) == 2: return np.sqrt((point_2[0] - point_1[0]) ** 2 + (point_2[1] - point_1[1]) ** 2) elif len(point_1) == 3 and len(point_2) == 3: return np.sqrt((point_2[0] - point_1[0]) ** 2 + (point_2[1] - point_1[1]) ** 2 + (point_2[2] - point_1[2]) ** 2) else: raise ValueError("Points must have dimension 2 or 3")
[docs] def measure_pitch_angle(point_1: Point3D or np.ndarray, point_2: Point3D or np.ndarray) -> Angle: """ Translates the two points such that the first point is at the origin and then measures the angle the line connecting the two points makes on the X-Z plane """ if isinstance(point_1, Point3D): point_1 = point_1.as_array() if isinstance(point_2, Point3D): point_2 = point_2.as_array() point_2_translated = np.array([point_2[0] - point_1[0], point_2[1] - point_1[1], point_2[2] - point_1[2]]) return Angle(rad=np.arctan2(point_2_translated[2], point_2_translated[0]))
[docs] def measure_distance_point_line(point: Point3D, line: Line3D) -> float: return measure_distance_between_points(point, project_point_onto_line(point, line))
[docs] def add_vector_to_point(vector: Vector3D, point: Point3D) -> Point3D: return point + (vector.p1 - vector.p0)
[docs] def project_point_onto_line(point: Point3D, line: Line3D) -> Point3D: vAB = Vector3D(p0=line.p0, p1=line.p1) vAC = Vector3D(p0=line.p0, p1=point) vAD_value = vAB.as_array() * (vAB.dot(vAC) / vAB.dot(vAB)) vAD = Vector3D.from_array(vAD_value) return add_vector_to_point(vector=vAD, point=line.p0)
[docs] def find_t_corresponding_to_minimum_distance_to_point2d(curve: PCurve2D, point: np.ndarray or Point2D) -> ( float, float): point = point.as_array() if isinstance(point, Point2D) else point def minimize_func(t): curve_point = curve.evaluate(t) return (curve_point[0] - point[0]) ** 2 + (curve_point[1] - point[1]) ** 2 res = minimize_scalar(minimize_func, bounds=[0.0, 1.0]) return res.x, np.sqrt(res.fun)
[docs] def find_t_corresponding_to_minimum_distance_to_point3d(curve: PCurve3D, point: np.ndarray or Point3D) -> ( float, float): point = point.as_array() if isinstance(point, Point3D) else point def minimize_func(t): curve_point = curve.evaluate(t) return (curve_point[0] - point[0]) ** 2 + (curve_point[1] - point[1]) ** 2 + (curve_point[2] - point[2]) ** 2 res = minimize_scalar(minimize_func, bounds=[0.0, 1.0]) return res.x, np.sqrt(res.fun)
[docs] def sweep_along_curve(primary_curve: BezierCurve3D, guide_curve: BezierCurve3D): point_list_3d = [primary_curve.control_points] for prev_point, current_point in zip(guide_curve.control_points[:-1], guide_curve.control_points[1:]): point_list_3d.append([primary_point + current_point - prev_point for primary_point in point_list_3d[-1]]) return np.array([[[point.x.m, point.y.m, point.z.m] for point in v] for v in point_list_3d])
[docs] def rotate_about_axis(points: np.ndarray, axis: Vector3D, angle: Angle) -> np.ndarray: ux, uy, uz = axis.normalized_value() c = np.cos(angle.rad) s = np.sin(angle.rad) rotation_matrix = np.array([ [c + ux**2 * (1 - c), ux * uy * (1 - c) - uz * s, ux * uz * (1 - c) + uy * s], [uy * ux * (1 - c) + uz * s, c + uy**2 * (1 - c), uy * uz * (1 - c) - ux * s], [uz * ux * (1 - c) - uy * s, uz * uy * (1 - c) + ux * s, c + uz**2 * (1 - c)] ]) return (rotation_matrix @ points.T).T
[docs] def rotate_point_about_axis(p: Point3D, ax: Line3D, angle: Angle) -> Point3D: reverse_transformation = Transformation3D(tx=-ax.p0.x.m, ty=-ax.p0.y.m, tz=-ax.p0.z.m) forward_transformation = Transformation3D(tx=ax.p0.x.m, ty=ax.p0.y.m, tz=ax.p0.z.m) p_mat = np.array([p.as_array()]) p_mat = reverse_transformation.transform(p_mat) p_mat = rotate_about_axis(p_mat, ax.get_vector(), angle) p_mat = forward_transformation.transform(p_mat) return Point3D.from_array(p_mat[0])
[docs] def concave_hull(poly: np.ndarray) -> (np.ndarray, np.ndarray): r""" Gets the concave hull of points of a polygon. Has a worst-case time complexity of :math:`\mathcal{O}\left(3n^2 \right)` but uses ``shapely.prepare`` for increased performance. Some algorithms exist that are :math:`\mathcal{O}\left( n \log{n} \right)`, but this algorithm seems to be more robust and is fast enough in many cases. Parameters ---------- poly: numpy.ndarray Array of size :math:`2 \times N` representing the polygon. Does not need to form a closed loop Returns ------- numpy.ndarray, numpy.ndarray The vertices of the triangles as an :math:`N \times 3` index array and the points of the triangles as an :math:`N \times 3` float array """ segments = np.array([[i, i + 1] for i in range(poly.shape[0] - 1)]) tri = triangle.triangulate({"vertices": poly, "segments": segments}) vertices = tri['vertices'] triangles = tri['triangles'] # Get a buffered version of a polygon defined by the airfoil points. # This helps avoid floating point precision issues with the shapely `contains` method shapely_poly = shapely.Polygon(poly).buffer(1e-11) shapely.prepare(shapely_poly) # Decreases runtime by about 60% # Get the triangles outside the airfoil polygon triangles_to_remove = [] for tri_idx, tri_indices in enumerate(triangles): for edge_pair_combo in [[0, 1], [1, 2], [2, 0]]: xy = np.mean((vertices[tri_indices[edge_pair_combo[0]]], vertices[tri_indices[edge_pair_combo[1]]]), axis=0) if not shapely.contains(shapely_poly, shapely.Point(xy[0], xy[1])): triangles_to_remove.append(tri_idx) break # Remove these triangles to obtain a triangulated concave hull for triangles_to_remove in triangles_to_remove[::-1]: triangles = np.delete(triangles, triangles_to_remove, axis=0) return vertices, triangles