Source code for compas.geometry.transformations.transformations


from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

import math

from compas.geometry import scale_vector
from compas.geometry import scale_vector_xy
from compas.geometry import normalize_vector
from compas.geometry import add_vectors
from compas.geometry import add_vectors_xy
from compas.geometry import subtract_vectors
from compas.geometry import subtract_vectors_xy
from compas.geometry import dot_vectors
from compas.geometry import cross_vectors
from compas.geometry import vector_component
from compas.geometry import vector_component_xy
from compas.geometry import multiply_matrix_vector
from compas.geometry import multiply_matrices
from compas.geometry import transpose_matrix
from compas.geometry import norm_vector
from compas.geometry import angle_vectors
from compas.geometry import closest_point_on_plane
from compas.geometry import closest_point_on_line
from compas.geometry import closest_point_on_line_xy
from compas.geometry import intersection_line_plane
from compas.geometry import intersection_line_triangle

from compas.geometry.transformations import matrix_from_axis_and_angle
from compas.geometry.transformations import matrix_from_scale_factors
from compas.geometry.transformations import matrix_from_change_of_basis


__all__ = [
    'local_axes',
    'orthonormalize_axes',

    'transform_points',
    'transform_vectors',
    'transform_frames',

    'local_to_world_coordinates',
    'world_to_local_coordinates',

    'translate_points',
    'translate_points_xy',

    'scale_points',
    'scale_points_xy',

    'rotate_points',
    'rotate_points_xy',

    'mirror_vector_vector',
    'mirror_points_point',
    'mirror_points_point_xy',
    'mirror_points_line',
    'mirror_points_line_xy',
    'mirror_points_plane',

    'project_point_plane',

    'project_points_plane',
    'project_point_line',
    'project_point_line_xy',
    'project_points_line',
    'project_points_line_xy',

    'reflect_line_plane',
    'reflect_line_triangle',

    'orient_points',
]


# this function will not always work
# it is also a duplicate of stuff found in matrices and frame
def local_axes(a, b, c):
    u = b - a
    v = c - a
    w = cross_vectors(u, v)
    v = cross_vectors(w, u)
    return normalize_vector(u), normalize_vector(v), normalize_vector(w)


def orthonormalize_axes(xaxis, yaxis):
    """Corrects xaxis and yaxis to be unit vectors and orthonormal.

    Parameters
    ----------
    xaxis: :class:`Vector` or list of float
    yaxis: :class:`Vector` or list of float

    Returns
    -------
    tuple: (xaxis, yaxis)
        The corrected axes.

    Raises
    ------
    ValueError: If xaxis and yaxis cannot span a plane.

    Examples
    --------
    >>> xaxis = [1, 4, 5]
    >>> yaxis = [1, 0, -2]
    >>> xaxis, yaxis = orthonormalize_axes(xaxis, yaxis)
    >>> allclose(xaxis, [0.1543, 0.6172, 0.7715], tol=0.001)
    True
    >>> allclose(yaxis, [0.6929, 0.4891, -0.5298], tol=0.001)
    True
    """
    xaxis = normalize_vector(xaxis)
    yaxis = normalize_vector(yaxis)
    zaxis = cross_vectors(xaxis, yaxis)
    if not norm_vector(zaxis):
        raise ValueError("Xaxis and yaxis cannot span a plane.")
    yaxis = cross_vectors(normalize_vector(zaxis), xaxis)
    return xaxis, yaxis


def homogenize(vectors, w=1.0):
    """Homogenise a list of vectors.

    Parameters
    ----------
    vectors : list
        A list of vectors.
    w : float, optional
        Homogenisation parameter.
        Defaults to ``1.0``.

    Returns
    -------
    list
        Homogenised vectors.

    Notes
    -----
    Vectors described by XYZ components are homogenised by appending a homogenisation
    parameter to the components, and by dividing each component by that parameter.
    Homogenisatioon of vectors is often used in relation to transformations.

    Examples
    --------
    >>> vectors = [[1.0, 0.0, 0.0]]
    >>> homogenize(vectors)
    [[1.0, 0.0, 0.0, 1.0]]

    """
    return [[x * w, y * w, z * w, w] if w else [x, y, z, 0.0] for x, y, z in vectors]


def dehomogenize(vectors):
    """Dehomogenise a list of vectors.

    Parameters
    ----------
    vectors : list
        A list of vectors.

    Returns
    -------
    list
        Dehomogenised vectors.

    Examples
    --------
    >>>

    """
    return [[x / w, y / w, z / w] if w else [x, y, z] for x, y, z, w in vectors]


def homogenize_and_flatten_frames(frames):
    """Homogenize a list of frames and flatten the 3D list into a 2D list.

    Parameters
    ----------
    frames: list of :class:`Frame`

    Returns
    -------
    list of list of float

    Examples
    --------
    >>> frames = [Frame((1, 1, 1), (0, 1, 0), (1, 0, 0))]
    >>> homogenize_and_flatten_frames(frames)
    [[1.0, 1.0, 1.0, 1.0], [0.0, 1.0, 0.0, 0.0], [1.0, -0.0, 0.0, 0.0]]
    """
    def homogenize_frame(frame):
        return homogenize([frame[0]], w=1.0) + homogenize([frame[1], frame[2]], w=0.0)
    return [v for frame in frames for v in homogenize_frame(frame)]


def dehomogenize_and_unflatten_frames(points_and_vectors):
    """Dehomogenize a list of vectors and unflatten the 2D list into a 3D list.

    Parameters
    ----------
    points_and_vectors: list of list of float
        Homogenized points and vectors.

    Returns
    -------
    list of list of list of float
        The frames.

    Examples
    --------
    >>> points_and_vectors = [(1., 1., 1., 1.), (0., 1., 0., 0.), (1., 0., 0., 0.)]
    >>> dehomogenize_and_unflatten_frames(points_and_vectors)
    [[[1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]]
    """
    frames = dehomogenize(points_and_vectors)
    return [frames[i:i+3] for i in range(0, len(frames), 3)]


# ==============================================================================
# transform
# ==============================================================================


def transform_points(points, T):
    """Transform multiple points with one transformation matrix.

    Parameters
    ----------
    points : list of :class:`Point` or list of list of float
        A list of points to be transformed.
    T : :class:`Transformation` or list of list of float
        The transformation to apply.

    Examples
    --------
    >>> points = [[1, 0, 0], [1, 2, 4], [4, 7, 1]]
    >>> T = matrix_from_axis_and_angle([0, 2, 0], math.radians(45), point=[4, 5, 6])
    >>> points_transformed = transform_points(points, T)
    """
    return dehomogenize(multiply_matrices(homogenize(points, w=1.0), transpose_matrix(T)))


def transform_vectors(vectors, T):
    """Transform multiple vectors with one transformation matrix.

    Parameters
    ----------
    vectors : list of :class:`Vector` or list of list of float
        A list of vectors to be transformed.
    T : :class:`Transformation` list of list of float
        The transformation to apply.

    Examples
    --------
    >>> vectors = [[1, 0, 0], [1, 2, 4], [4, 7, 1]]
    >>> T = matrix_from_axis_and_angle([0, 2, 0], math.radians(45), point=[4, 5, 6])
    >>> vectors_transformed = transform_vectors(vectors, T)
    """
    return dehomogenize(multiply_matrices(homogenize(vectors, w=0.0), transpose_matrix(T)))


def transform_frames(frames, T):
    """Transform multiple frames with one transformation matrix.

    Parameters
    ----------
    frames : list of :class:`Frame`
        A list of frames to be transformed.
    T : :class:`Transformation`
        The transformation to apply on the frames.

    Examples
    --------
    >>> frames = [Frame([1, 0, 0], [1, 2, 4], [4, 7, 1]), Frame([0, 2, 0], [5, 2, 1], [0, 2, 1])]
    >>> T = matrix_from_axis_and_angle([0, 2, 0], math.radians(45), point=[4, 5, 6])
    >>> transformed_frames = transform_frames(frames, T)
    """
    points_and_vectors = homogenize_and_flatten_frames(frames)
    return dehomogenize_and_unflatten_frames(multiply_matrices(points_and_vectors, transpose_matrix(T)))


def world_to_local_coordinates(frame, xyz):
    """Convert global coordinates to local coordinates.

    Parameters
    ----------
    frame : :class:`Frame` or [point, xaxis, yaxis]
        The local coordinate system.
    xyz : array-like
        The global coordinates of the points to convert.

    Returns
    -------
    list of list of float
        The coordinates of the given points in the local coordinate system.


    Examples
    --------
    >>> import numpy as np
    >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9])
    >>> xyz = [Point(2, 3, 5)]
    >>> Point(*world_to_local_coordinates(f, xyz)[0])
    Point(3.726, 4.088, 1.550)
    """
    from compas.geometry import Frame  # noqa: F811
    T = matrix_from_change_of_basis(Frame.worldXY(), frame)
    return transform_points(xyz, T)


def local_to_world_coordinates(frame, xyz):
    """Convert local coordinates to global coordinates.

    Parameters
    ----------
    frame : :class:`Frame` or [point, xaxis, yaxis]
        The local coordinate system.
    xyz : list of `Points` or list of list of float
        The global coordinates of the points to convert.

    Returns
    -------
    list of list of float
        The coordinates of the given points in the local coordinate system.


    Examples
    --------
    >>> import numpy as np
    >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9])
    >>> xyz = [Point(3.726, 4.088, 1.550)]
    >>> Point(*local_to_world_coordinates(f, xyz)[0])
    Point(2.000, 3.000, 5.000)
    """
    from compas.geometry import Frame  # noqa: F811
    T = matrix_from_change_of_basis(frame, Frame.worldXY())
    return transform_points(xyz, T)


# ==============================================================================
# translate
# ==============================================================================


[docs]def translate_points(points, vector): """Translate points. Parameters ---------- points : list of point A list of points. vector : vector A translation vector. Returns ------- list of point The translated points. Examples -------- >>> """ return [add_vectors(point, vector) for point in points]
[docs]def translate_points_xy(points, vector): """Translate points and in the XY plane. Parameters ---------- points : list of point A list of points. vector : vector A translation vector. Returns ------- list of point The translated points in the XY plane (Z=0). Examples -------- >>> """ return [add_vectors_xy(point, vector) for point in points]
# ============================================================================== # scale # ==============================================================================
[docs]def scale_points(points, scale): """Scale points. Parameters ---------- points : list of point A list of points. scale : float A scaling factor. Returns ------- list of point The scaled points. Examples -------- >>> """ T = matrix_from_scale_factors([scale, scale, scale]) return transform_points(points, T)
def scale_points_xy(points, scale): """Scale points in the XY plane. Parameters ---------- points : list of point A list of points. scale : float A scaling factor. Returns ------- list of point The scaled points in the XY plane (Z=0). Examples -------- >>> """ T = matrix_from_scale_factors([scale, scale, 0]) return transform_points(points, T) # ============================================================================== # rotate # ==============================================================================
[docs]def rotate_points(points, angle, axis=None, origin=None): """Rotates points around an arbitrary axis in 3D. Parameters ---------- points : list of point A list of points. angle : float The angle of rotation in radians. axis : vector, optional The rotation axis. Default is ``[0.0, 0.0, 1.0]`` origin : point, optional The origin of the rotation axis. Default is ``[0.0, 0.0, 0.0]``. Returns ------- list of point The rotated points Examples -------- >>> Notes ----- For more info, see [1]_. References ---------- .. [1] Wikipedia. *Rotation matrix*. Available at: https://en.wikipedia.org/wiki/Rotation_matrix. """ if axis is None: axis = [0.0, 0.0, 1.0] if origin is None: origin = [0.0, 0.0, 0.0] R = matrix_from_axis_and_angle(axis, angle, origin) points = transform_points(points, R) return points
[docs]def rotate_points_xy(points, angle, origin=None): """Rotates points in the XY plane around the Z axis at a specific origin. Parameters ---------- points : list of point A list of points. angle : float The angle of rotation in radians. origin : point, optional The origin of the rotation axis. Default is ``[0.0, 0.0, 0.0]``. Returns ------- list The rotated points in the XY plane (Z=0). Examples -------- >>> """ if not origin: origin = [0.0, 0.0, 0.0] cosa = math.cos(angle) sina = math.sin(angle) R = [[cosa, -sina, 0.0], [sina, cosa, 0.0], [0.0, 0.0, 1.0]] # translate points points = translate_points_xy(points, scale_vector_xy(origin, -1.0)) # rotate points points = [multiply_matrix_vector(R, point) for point in points] # translate points back points = translate_points_xy(points, origin) return points
# ============================================================================== # mirror # ============================================================================== def mirror_vector_vector(v1, v2): """Mirrors vector about vector. Parameters ---------- v1 : list of float The vector. v2 : list of float The normalized vector as mirror axis Returns ------- list of float The mirrored vector. Notes ----- For more info, see [1]_. References ---------- .. [1] Math Stack Exchange. *How to get a reflection vector?* Available at: https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector. """ return subtract_vectors(v1, scale_vector(v2, 2 * dot_vectors(v1, v2))) def mirror_point_point(point, mirror): """Mirror a point about a point. Parameters ---------- point : list of float XYZ coordinates of the point to mirror. mirror : list of float XYZ coordinates of the mirror point. Returns ------- list of float The mirrored point. """ return add_vectors(mirror, subtract_vectors(mirror, point)) def mirror_point_point_xy(point, mirror): """Mirror a point about a point. Parameters ---------- point : list of float XY(Z) coordinates of the point to mirror. mirror : list of float XY(Z) coordinates of the mirror point. Returns ------- list of float The mirrored point, with Z=0. """ return add_vectors_xy(mirror, subtract_vectors_xy(mirror, point))
[docs]def mirror_points_point(points, mirror): """Mirror multiple points about a point. Parameters ---------- points : list of list of float List of points. mirror : list of float The mirror point. Returns ------- list of list float The mirrored points, with Z=0. """ return [mirror_point_point(point, mirror) for point in points]
[docs]def mirror_points_point_xy(points, mirror): """Mirror multiple points about a point. Parameters ---------- points : list of list of float List of points with XY(Z) coordinates. mirror : list of float The XY(Z) coordinates of the mirror point. Returns ------- list of list float The mirrored points, with Z=0. """ return [mirror_point_point_xy(point, mirror) for point in points]
def mirror_point_line(point, line): """Mirror a point about a line. Parameters ---------- point : list of float XYZ coordinates of the point to mirror. line : tuple Two points defining the mirror line. Returns ------- list of float The mirrored point. """ closest = closest_point_on_line(point, line) return add_vectors(closest, subtract_vectors(closest, point)) def mirror_point_line_xy(point, line): """Mirror a point about a line. Parameters ---------- point : list of float XY(Z) coordinates of the point to mirror. line : tuple Two points defining the line. XY(Z) coordinates of the two points defining the mirror line. Returns ------- list of float The mirrored point, with Z=0. """ closest = closest_point_on_line_xy(point, line) return add_vectors_xy(closest, subtract_vectors_xy(closest, point))
[docs]def mirror_points_line(points, line): """Mirror a point about a line. Parameters ---------- points : list of point List of points to mirror. line : tuple Two points defining the mirror line. Returns ------- list of point The mirrored points. """ return [mirror_point_line(point, line) for point in points]
[docs]def mirror_points_line_xy(points, line): """Mirror a point about a line. Parameters ---------- points : list of point List of points to mirror. line : tuple Two points defining the mirror line. Returns ------- list of point The mirrored points. """ return [mirror_point_line_xy(point, line) for point in points]
def mirror_point_plane(point, plane): """Mirror a point about a plane. Parameters ---------- point : list of float XYZ coordinates of mirror point. plane : tuple Base point and normal defining the mirror plane. Returns ------- list of float XYZ coordinates of the mirrored point. """ closest = closest_point_on_plane(point, plane) return add_vectors(closest, subtract_vectors(closest, point))
[docs]def mirror_points_plane(points, plane): """Mirror a point about a plane. Parameters ---------- points : list of point List of points to mirror. plane : tuple Base point and normal defining the mirror plane. Returns ------- list of point The mirrored points. """ return [mirror_point_plane(point, plane) for point in points]
# ============================================================================== # project # specify orhtogonal # add perspective # ============================================================================== def project_point_plane(point, plane): """Project a point onto a plane. Parameters ---------- point : list of float XYZ coordinates of the point. plane : tuple Base point and normal vector defining the projection plane. Returns ------- list XYZ coordinates of the projected point. Notes ----- The projection is in the direction perpendicular to the plane. The projected point is thus the closest point on the plane to the original point [1]_. References ---------- .. [1] Math Stack Exchange. *Project a point in 3D on a given plane*. Available at: https://math.stackexchange.com/questions/444968/project-a-point-in-3d-on-a-given-plane. Examples -------- >>> from compas.geometry import project_point_plane >>> point = [3.0, 3.0, 3.0] >>> plane = ([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) # the XY plane >>> project_point_plane(point, plane) [3.0, 3.0, 0.0] """ base, normal = plane normal = normalize_vector(normal) vector = subtract_vectors(point, base) snormal = scale_vector(normal, dot_vectors(vector, normal)) return subtract_vectors(point, snormal)
[docs]def project_points_plane(points, plane): """Project multiple points onto a plane. Parameters ---------- points : list of point List of points. plane : tuple Base point and normal vector defining the projection plane. Returns ------- list of point The projected points. See Also -------- :func:`project_point_plane` """ return [project_point_plane(point, plane) for point in points]
def project_point_line(point, line): """Project a point onto a line. Parameters ---------- point : list of float XYZ coordinates of the point. line : tuple Two points defining the projection line. Returns ------- list XYZ coordinates of the projected point. Notes ----- For more info, see [1]_. References ---------- .. [1] Wiki Books. *Linear Algebra/Orthogonal Projection Onto a Line*. Available at: https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line. """ a, b = line ab = subtract_vectors(b, a) ap = subtract_vectors(point, a) c = vector_component(ap, ab) return add_vectors(a, c) def project_point_line_xy(point, line): """Project a point onto a line in the XY plane. Parameters ---------- point : list of float XY(Z) coordinates of the point. line : tuple Two points defining the projection line. Returns ------- list XYZ coordinates of the projected point, with Z=0. Notes ----- For more info, see [1]_. References ---------- .. [1] Wiki Books. *Linear Algebra/Orthogonal Projection Onto a Line*. Available at: https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line. """ a, b = line ab = subtract_vectors_xy(b, a) ap = subtract_vectors_xy(point, a) c = vector_component_xy(ap, ab) return add_vectors_xy(a, c)
[docs]def project_points_line(points, line): """Project points onto a line. Parameters ---------- points : list of point XYZ coordinates of the points. line : tuple Two points defining the projection line. Returns ------- list of point XYZ coordinates of the projected points. Notes ----- For more info, see [1]_. References ---------- .. [1] Wiki Books. *Linear Algebra/Orthogonal Projection Onto a Line*. Available at: https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line. """ return [project_point_line(point, line) for point in points]
[docs]def project_points_line_xy(points, line): """Project points onto a line in the XY plane. Parameters ---------- point : list of float XY(Z) coordinates of the point. line : tuple Two points defining the projection line. Returns ------- list XYZ coordinates of the projected point, with Z=0. Notes ----- For more info, see [1]_. References ---------- .. [1] Wiki Books. *Linear Algebra/Orthogonal Projection Onto a Line*. Available at: https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line. """ return [project_point_line_xy(point, line) for point in points]
# ============================================================================== # reflection # ==============================================================================
[docs]def reflect_line_plane(line, plane, tol=1e-6): """Bounce a line of a reflection plane. Parameters ---------- line : tuple Two points defining the line. plane : tuple Base point and normal vector of the plane. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- tuple The reflected line defined by the intersection point of the line and plane and the mirrored start point of the line with respect to a line perpendicular to the plane through the intersection. Notes ----- The direction of the line and plane are important. The line is only reflected if it points towards the front of the plane. This is true if the dot product of the direction vector of the line and the normal vector of the plane is smaller than zero. Examples -------- >>> plane = [0, 0, 0], [0, 1, 0] >>> line = [-1, 1, 0], [-0.5, 0.5, 0] >>> reflect_line_plane(line, plane) ([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) """ x = intersection_line_plane(line, plane, tol=tol) if not x: return a, b = line o, n = plane ab = subtract_vectors(b, a) if dot_vectors(ab, n) > 0: # the line does not point towards the front of the plane return mirror = x, add_vectors(x, n) return x, mirror_point_line(a, mirror)
[docs]def reflect_line_triangle(line, triangle, tol=1e-6): """Bounce a line of a reflection triangle. Parameters ---------- line : tuple Two points defining the line. triangle : tuple The triangle vertices. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- tuple The reflected line defined by the intersection point of the line and triangle and the mirrored start point of the line with respect to a line perpendicular to the triangle through the intersection. Notes ----- The direction of the line and triangle are important. The line is only reflected if it points towards the front of the triangle. This is true if the dot product of the direction vector of the line and the normal vector of the triangle is smaller than zero. Examples -------- >>> triangle = [1.0, 0, 0], [-1.0, 0, 0], [0, 0, 1.0] >>> line = [-1, 1, 0], [-0.5, 0.5, 0] >>> reflect_line_triangle(line, triangle) ([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) """ x = intersection_line_triangle(line, triangle, tol=tol) if not x: return a, b = line t1, t2, t3 = triangle ab = subtract_vectors(b, a) n = cross_vectors(subtract_vectors(t2, t1), subtract_vectors(t3, t1)) if dot_vectors(ab, n) > 0: # the line does not point towards the front of the triangle return mirror = x, add_vectors(x, n) return x, mirror_point_line(a, mirror)
# ============================================================================== # shear # ============================================================================== # ============================================================================== # orientation # ============================================================================== def orient_points(points, reference_plane, target_plane): """Orient points from one plane to another. Parameters ---------- points : list of points XYZ coordinates of the points. reference_plane : plane Base point and normal defining a reference plane. target_plane : plane Base point and normal defining a target plane. Returns ------- points : list of point XYZ coordinates of the oriented points. Notes ----- This function is useful to orient a planar problem in the xy-plane to simplify the calculation (see example). Examples -------- >>> from compas.geometry import orient_points >>> from compas.geometry import intersection_segment_segment_xy >>> >>> refplane = ([0.57735, 0.57735, 0.57735], [1.0, 1.0, 1.0]) >>> tarplane = ([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) >>> >>> points = [\ [0.288675, 0.288675, 1.1547],\ [0.866025, 0.866025, 0.0],\ [1.077350, 0.077350, 0.57735],\ [0.077350, 1.077350, 0.57735]\ ] >>> >>> points = orient_points(points, refplane, tarplane) >>> >>> ab = points[0], points[1] >>> cd = points[2], points[3] >>> >>> point = intersection_segment_segment_xy(ab, cd) >>> >>> points = orient_points([point], tarplane, refplane) >>> Point(*points[0]) Point(0.577, 0.577, 0.577) """ axis = cross_vectors(reference_plane[1], target_plane[1]) angle = angle_vectors(reference_plane[1], target_plane[1]) origin = reference_plane[0] if angle: points = rotate_points(points, angle, axis, origin) vector = subtract_vectors(target_plane[0], reference_plane[0]) points = translate_points(points, vector) return points # ============================================================================== # Main # ============================================================================== if __name__ == "__main__": import doctest from compas.geometry import allclose # noqa: F401 from compas.geometry import Point # noqa: F401 from compas.geometry import Frame # noqa: F401 doctest.testmod(globs=globals())