From 4f250916dc7bc091578e2224944be132886b5c45 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 30 Sep 2019 17:51:32 +0200 Subject: [PATCH 01/73] adding change basis --- .../transformations/transformation.py | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index 8c7075d5d589..d41e5fcca5ea 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -164,11 +164,11 @@ def from_frame(cls, frame): @classmethod def from_frame_to_frame(cls, frame_from, frame_to): - """Computes a change of basis transformation between two frames. + """Computes a transformation between two frames. - This transformation maps geometry from one Cartesian coordinate system - defined by "frame_from" to the other Cartesian coordinate system - defined by "frame_to". + This transformation allows to transform geometry from one Cartesian + coordinate system defined by "frame_from" to another Cartesian + coordinate system defined by "frame_to". Args: frame_from (:class:`Frame`): a frame defining the original @@ -190,6 +190,39 @@ def from_frame_to_frame(cls, frame_from, frame_to): return cls(multiply_matrices(T2.matrix, inverse(T1.matrix))) + @classmethod + def change_basis(cls, frame_from, frame_to): + """Computes a change of basis transformation between two frames. + + A basis change is essentially a remapping of geometry from one + coordinate system to another. + + Args: + frame_from (:class:`Frame`): a frame defining the original + Cartesian coordinate system + frame_to (:class:`Frame`): a frame defining the targeted + Cartesian coordinate system + + Example: + >>> from compas.geometry import Point, Frame + >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) + >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = Transformation.change_basis(f1, f2) + >>> p_f1 = Point(1, 1, 1) # point in f1 + >>> p_f2 = p_f1.transformed(T) # same point represented in f2 + >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates + >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates + >>> print(p_w1) + Point(0.733, 2.492, 3.074) + >>> print(p_w2) + Point(0.733, 2.492, 3.074) + """ + + T1 = cls.from_frame(frame_from) + T2 = cls.from_frame(frame_to) + + return cls(multiply_matrices(inverse(T2.matrix), T1.matrix)) + def inverse(self): """Returns the inverse transformation. From d8dd4ed350b24fb1839653289dc125d8bda188c1 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Wed, 2 Oct 2019 13:56:27 +0200 Subject: [PATCH 02/73] update represent local/global with change_basis --- src/compas/geometry/primitives/frame.py | 45 ++++++++----------- .../transformations/transformation.py | 4 +- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 21c00ae95cc5..4a6788993425 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -663,19 +663,18 @@ def represent_point_in_local_coordinates(self, point): Examples -------- - >>> from compas.geometry import Frame + >>> from compas.geometry import Point, Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] + >>> pw1 = Point(2, 2, 2) >>> pf = f.represent_point_in_local_coordinates(pw1) >>> pw2 = f.represent_point_in_global_coordinates(pf) >>> allclose(pw1, pw2) True """ - pt = Point(*subtract_vectors(point, self.point)) - T = inverse(matrix_from_basis_vectors(self.xaxis, self.yaxis)) - pt.transform(T) - return pt + point = Point(*point) + T = Transformation.change_basis(Frame.worldXY(), self) + return point.transformed(T) def represent_point_in_global_coordinates(self, point): """Represents a point from local coordinates in the world coordinate system. @@ -701,10 +700,9 @@ def represent_point_in_global_coordinates(self, point): True """ - T = matrix_from_frame(self) - pt = Point(*point) - pt.transform(T) - return pt + point = Point(*point) + T = Transformation.change_basis(self, Frame.worldXY()) + return point.transformed(T) def represent_vector_in_local_coordinates(self, vector): """Represents a vector in the frame's local coordinate system. @@ -730,10 +728,9 @@ def represent_vector_in_local_coordinates(self, vector): True """ - T = inverse(matrix_from_basis_vectors(self.xaxis, self.yaxis)) - vec = Vector(*vector) - vec.transform(T) - return vec + vector = Vector(*vector) + T = Transformation.change_basis(Frame.worldXY(), self) + return vector.transformed(T) def represent_vector_in_global_coordinates(self, vector): """Represents a vector in local coordinates in the world coordinate system. @@ -759,10 +756,9 @@ def represent_vector_in_global_coordinates(self, vector): True """ - T = matrix_from_frame(self) - vec = Vector(*vector) - vec.transform(T) - return vec + vector = Vector(*vector) + T = Transformation.change_basis(self, Frame.worldXY()) + return vector.transformed(T) def represent_frame_in_local_coordinates(self, frame): """Represents another frame in the frame's local coordinate system. @@ -791,10 +787,9 @@ def represent_frame_in_local_coordinates(self, frame): True """ - T = Transformation.from_frame(self).inverse() - f = frame.copy() - f.transform(T) - return f + T = Transformation.change_basis(Frame.worldXY(), self) + return frame.transformed(T) + def represent_frame_in_global_coordinates(self, frame): """Represents another frame in the local coordinate system in the world @@ -825,10 +820,8 @@ def represent_frame_in_global_coordinates(self, frame): True """ - T = Transformation.from_frame(self) - f = frame.copy() - f.transform(T) - return f + T = Transformation.change_basis(self, Frame.worldXY()) + return frame.transformed(T) # ========================================================================== # transformations diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index d41e5fcca5ea..55a6184c3842 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -142,12 +142,12 @@ def from_list(cls, numbers): @classmethod def from_frame(cls, frame): - """Computes a change of basis transformation from world XY to frame. + """Computes a transformation from world XY to frame. It is the same as from_frame_to_frame(Frame.worldXY(), frame). Args: - frame (:class:`Frame`): a frame describing the targeted Cartesian + frame (:class:`Frame`): a frame defining the targeted Cartesian coordinate system Example: From a1484ff117d0e0d77981aa160595533f5b0b53fc Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Thu, 3 Oct 2019 18:52:20 +0200 Subject: [PATCH 03/73] small update on quaternion --- src/compas/geometry/primitives/quaternion.py | 47 +++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/compas/geometry/primitives/quaternion.py b/src/compas/geometry/primitives/quaternion.py index 4562638ac14e..ce26199f5383 100644 --- a/src/compas/geometry/primitives/quaternion.py +++ b/src/compas/geometry/primitives/quaternion.py @@ -1,3 +1,5 @@ +import math + from compas.geometry import quaternion_multiply from compas.geometry import quaternion_conjugate from compas.geometry import quaternion_unitize @@ -5,7 +7,6 @@ from compas.geometry import quaternion_norm from compas.geometry import quaternion_is_unit -from compas.geometry import allclose from compas.geometry.primitives import Primitive @@ -93,10 +94,7 @@ def __init__(self, w, x, y, z): self.z = float(z) def __iter__(self): - return iter([self.w, self.x, self.y, self.z]) - - def __str__(self): - return "Quaternion = %s" % list(self) + return iter(self.wxyz) def __repr__(self): return 'Quaternion({:.{prec}f}, {:.{prec}f}, {:.{prec}f}, {:.{prec}f})'.format(*self, prec=6) @@ -213,11 +211,50 @@ def conjugate(self): qc = quaternion_conjugate(self) return Quaternion(*qc) + # ========================================================================== + # access + # ========================================================================== + + def __getitem__(self, key): + if key == 0: + return self.w + if key == 1: + return self.x + if key == 2: + return self.y + if key == 3: + return self.z + raise KeyError + + def __setitem__(self, key, value): + if key == 0: + self.w = value + return + if key == 1: + self.x = value + return + if key == 2: + self.y = value + if key == 3: + self.z = value + raise KeyError + + # ========================================================================== + # comparison + # ========================================================================== + + def __eq__(self, other, tol=1e-05): + for v1, v2 in zip(self, other): + if math.fabs(v1 - v2) > tol: + return False + return True + # ============================================================================== # Main # ============================================================================== if __name__ == "__main__": + from compas.geometry import allclose import doctest doctest.testmod(globs=globals()) From 1e92bd0e6e57e5f4206c7e66f633118578ede330 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 10:38:24 +0200 Subject: [PATCH 04/73] return quaternion --- src/compas/geometry/primitives/frame.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 4a6788993425..1faddfd35c2f 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -25,6 +25,7 @@ from compas.geometry.primitives import Point from compas.geometry.primitives import Vector from compas.geometry.primitives import Plane +from compas.geometry.primitives import Quaternion __all__ = ['Frame'] @@ -530,10 +531,10 @@ def zaxis(self): @property def quaternion(self): - """:obj:`list` of :obj:`float` : The 4 quaternion coefficients from the rotation given by the frame. + """:class:`Quaternion` : The quaternion from the rotation given by the frame. """ rotation = matrix_from_basis_vectors(self.xaxis, self.yaxis) - return quaternion_from_matrix(rotation) + return Quaternion(*quaternion_from_matrix(rotation)) @property def axis_angle_vector(self): From 0ba2c029f442a718fd5969db82286fd6d3832ddb Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 10:50:00 +0200 Subject: [PATCH 05/73] returning vector from axis angle vector --- src/compas/geometry/primitives/frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 1faddfd35c2f..47b902ff8fe3 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -540,7 +540,7 @@ def quaternion(self): def axis_angle_vector(self): """vector : The axis-angle vector from the rotation given by the frame.""" R = matrix_from_basis_vectors(self.xaxis, self.yaxis) - return axis_angle_vector_from_matrix(R) + return Vector(*axis_angle_vector_from_matrix(R)) # ========================================================================== # representation From 7ceaac3e05c739dcc383000d9b87fdc105a635e5 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 11:50:27 +0200 Subject: [PATCH 06/73] adding unitized and scaled to vector --- src/compas/geometry/primitives/vector.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/compas/geometry/primitives/vector.py b/src/compas/geometry/primitives/vector.py index 28e86022c159..d924020639c2 100644 --- a/src/compas/geometry/primitives/vector.py +++ b/src/compas/geometry/primitives/vector.py @@ -444,6 +444,18 @@ def unitize(self): self.y = self.y / l self.z = self.z / l + def unitized(self): + """Returns a unitized copy of this ``Vector``. + + Returns + ------- + :class:`Vector` + + """ + v = self.copy() + v.unitize() + return v + def scale(self, n): """Scale this ``Vector`` by a factor n. @@ -457,6 +469,23 @@ def scale(self, n): self.y *= n self.z *= n + def scaled(self, n): + """Returns a scaled copy of this ``Vector``. + + Parameters + ---------- + n : float + The scaling factor. + + Returns + ------- + :class:`Vector` + + """ + v = self.copy() + v.scale(n) + return v + def dot(self, other): """The dot product of this ``Vector`` and another vector. From e24021227e41e74df40e7318cebafd6545747348 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 12:12:22 +0200 Subject: [PATCH 07/73] fixing returns and docstrings --- .../geometry/transformations/rotation.py | 338 ++++++++++-------- 1 file changed, 188 insertions(+), 150 deletions(-) diff --git a/src/compas/geometry/transformations/rotation.py b/src/compas/geometry/transformations/rotation.py index 83bf86dc6a4b..c408792bd2c9 100644 --- a/src/compas/geometry/transformations/rotation.py +++ b/src/compas/geometry/transformations/rotation.py @@ -39,19 +39,20 @@ class Rotation(Transformation): The class contains methods for converting rotation matrices to axis-angle representations, Euler angles, quaternion and basis vectors. - Example: - >>> from compas.geometry import Frame - >>> f1 = Frame([0, 0, 0], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> R = Rotation.from_frame(f1) - >>> args = False, 'xyz' - >>> alpha, beta, gamma = R.euler_angles(*args) - >>> xaxis, yaxis, zaxis = [1, 0, 0], [0, 1, 0], [0, 0, 1] - >>> Rx = Rotation.from_axis_and_angle(xaxis, alpha) - >>> Ry = Rotation.from_axis_and_angle(yaxis, beta) - >>> Rz = Rotation.from_axis_and_angle(zaxis, gamma) - >>> f2 = Frame.worldXY() - >>> f1 == f2.transformed(Rx * Ry * Rz) - True + Examples + -------- + >>> from compas.geometry import Frame + >>> f1 = Frame([0, 0, 0], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> R = Rotation.from_frame(f1) + >>> args = False, 'xyz' + >>> alpha, beta, gamma = R.euler_angles(*args) + >>> xaxis, yaxis, zaxis = [1, 0, 0], [0, 1, 0], [0, 0, 1] + >>> Rx = Rotation.from_axis_and_angle(xaxis, alpha) + >>> Ry = Rotation.from_axis_and_angle(yaxis, beta) + >>> Rz = Rotation.from_axis_and_angle(zaxis, gamma) + >>> f2 = Frame.worldXY() + >>> f1 == f2.transformed(Rx * Ry * Rz) + True """ @@ -59,14 +60,18 @@ class Rotation(Transformation): def from_basis_vectors(cls, xaxis, yaxis): """Creates a ``Rotation`` from basis vectors (= orthonormal vectors). - Args: - xaxis (:obj:`list` oof :obj:`float`): The x-axis of the frame. - yaxis (:obj:`list` oof :obj:`float`): The y-axis of the frame. + Parameters + ---------- + xaxis : :class:`Vector` + The x-axis of the frame. + yaxis : :class:`Vector` + The y-axis of the frame. - Example: - >>> xaxis = [0.68, 0.68, 0.27] - >>> yaxis = [-0.67, 0.73, -0.15] - >>> R = Rotation.from_basis_vectors(xaxis, yaxis) + Examples + -------- + >>> xaxis = [0.68, 0.68, 0.27] + >>> yaxis = [-0.67, 0.73, -0.15] + >>> R = Rotation.from_basis_vectors(xaxis, yaxis) """ xaxis = normalize_vector(list(xaxis)) @@ -86,17 +91,19 @@ def from_frame(cls, frame): It is the same as from_frame_to_frame(Frame.worldXY(), frame). - Args: - frame (:class:`Frame`): a frame describing the targeted Cartesian - coordinate system - - Example: - >>> from compas.geometry import Frame - >>> f1 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> T = Transformation.from_frame(f1) - >>> f2 = Frame.from_transformation(T) - >>> f1 == f2 - True + Parameters + ---------- + frame : :class:`Frame` + A frame describing the targeted Cartesian coordinate system. + + Examples + -------- + >>> from compas.geometry import Frame + >>> f1 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = Transformation.from_frame(f1) + >>> f2 = Frame.from_transformation(T) + >>> f1 == f2 + True """ R = cls() R.matrix = matrix_from_frame(frame) @@ -107,16 +114,18 @@ def from_frame(cls, frame): def from_quaternion(cls, quaternion): """Calculates a ``Rotation`` from quaternion coefficients. - Args: - quaternion (:obj:`list` of :obj:`float`): Four numbers that - represents the four coefficient values of a quaternion. - - Example: - >>> q1 = [0.945, -0.021, -0.125, 0.303] - >>> R = Rotation.from_quaternion(q1) - >>> q2 = R.quaternion - >>> allclose(q1, q2, tol=1e-3) - True + Parameters + ---------- + quaternion : :class:`Quaternion` + Four numbers that represents the four coefficient values of a quaternion. + + Examples + -------- + >>> q1 = [0.945, -0.021, -0.125, 0.303] + >>> R = Rotation.from_quaternion(q1) + >>> q2 = R.quaternion + >>> allclose(q1, q2, tol=1e-3) + True """ R = matrix_from_quaternion(quaternion) return cls(R) @@ -125,19 +134,20 @@ def from_quaternion(cls, quaternion): def from_axis_angle_vector(cls, axis_angle_vector, point=[0, 0, 0]): """Calculates a ``Rotation`` from an axis-angle vector. - Args: - axis_angle_vector (:obj:`list` of :obj:`float`): Three numbers - that represent the axis of rotation and angle of rotation - through the vector's magnitude. - point (:obj:`list` of :obj:`float`, optional): A point to - perform a rotation around an origin other than [0, 0, 0]. - - Example: - >>> aav1 = [-0.043, -0.254, 0.617] - >>> R = Rotation.from_axis_angle_vector(aav1) - >>> aav2 = R.axis_angle_vector - >>> allclose(aav1, aav2) - True + Parameters + ---------- + axis_angle_vector : list of float + Three numbers that represent the axis of rotation and angle of rotation through the vector's magnitude. + point : list of float, optional) + A point to perform a rotation around an origin other than [0, 0, 0]. + + Examples + -------- + >>> aav1 = [-0.043, -0.254, 0.617] + >>> R = Rotation.from_axis_angle_vector(aav1) + >>> aav2 = R.axis_angle_vector + >>> allclose(aav1, aav2) + True """ axis_angle_vector = list(axis_angle_vector) @@ -146,29 +156,30 @@ def from_axis_angle_vector(cls, axis_angle_vector, point=[0, 0, 0]): @classmethod def from_axis_and_angle(cls, axis, angle, point=[0, 0, 0]): - """Calculates a ``Rotation`` from a rotation axis and an angle and \ - an optional point of rotation. - - Note: - The rotation is based on the right hand rule, i.e. anti-clockwise - if the axis of rotation points towards the observer. - - Args: - axis (:obj:`list` of :obj:`float`): Three numbers that represent - the axis of rotation - angle (:obj:`float`): The rotation angle in radians. - point (:obj:`list` of :obj:`float`, optional): A point to - perform a rotation around an origin other than [0, 0, 0]. - - Example: - >>> axis1 = normalize_vector([-0.043, -0.254, 0.617]) - >>> angle1 = 0.1 - >>> R = Rotation.from_axis_and_angle(axis1, angle1) - >>> axis2, angle2 = R.axis_and_angle - >>> allclose(axis1, axis2) - True - >>> allclose([angle1], [angle2]) - True + """Calculates a ``Rotation`` from a rotation axis and an angle and an optional point of rotation. + + The rotation is based on the right hand rule, i.e. anti-clockwise if the + axis of rotation points towards the observer. + + Parameters + ---------- + axis : list of float + Three numbers that represent the axis of rotation. + angle : float + The rotation angle in radians. + point : :class:`Point` or list of float + A point to perform a rotation around an origin other than [0, 0, 0]. + + Examples + -------- + >>> axis1 = normalize_vector([-0.043, -0.254, 0.617]) + >>> angle1 = 0.1 + >>> R = Rotation.from_axis_and_angle(axis1, angle1) + >>> axis2, angle2 = R.axis_and_angle + >>> allclose(axis1, axis2) + True + >>> allclose([angle1], [angle2]) + True """ M = matrix_from_axis_and_angle(axis, angle, point) return cls(M) @@ -183,111 +194,138 @@ def from_euler_angles(cls, euler_angles, static=True, axes='xyz'): depends on if the rotations are applied to a static (extrinsic) or rotating (intrinsic) frame and the order of axes. - Args: - euler_angles(:obj:`list` of :obj:`float`): Three numbers that - represent the angles of rotations about the defined axes. - static(:obj:`bool`, optional): If true the rotations are applied to - a static frame. If not, to a rotational. Defaults to true. - axes(:obj:`str`, optional): A 3 character string specifying order - of the axes. Defaults to 'xyz'. - - Example: - >>> ea1 = 1.4, 0.5, 2.3 - >>> args = False, 'xyz' - >>> R1 = Rotation.from_euler_angles(ea1, *args) - >>> ea2 = R1.euler_angles(*args) - >>> allclose(ea1, ea2) - True - >>> alpha, beta, gamma = ea1 - >>> xaxis, yaxis, zaxis = [1, 0, 0], [0, 1, 0], [0, 0, 1] - >>> Rx = Rotation.from_axis_and_angle(xaxis, alpha) - >>> Ry = Rotation.from_axis_and_angle(yaxis, beta) - >>> Rz = Rotation.from_axis_and_angle(zaxis, gamma) - >>> R2 = Rx * Ry * Rz - >>> R1 == R2 - True + Parameters + ---------- + euler_angles: list of float + Three numbers that represent the angles of rotations about the + defined axes. + static: bool, optional + If true the rotations are applied to a static frame. If not, to a + rotational. Defaults to true. + axes: str, optional + A 3 character string specifying order of the axes. Defaults to 'xyz'. + + Examples + -------- + >>> ea1 = 1.4, 0.5, 2.3 + >>> args = False, 'xyz' + >>> R1 = Rotation.from_euler_angles(ea1, *args) + >>> ea2 = R1.euler_angles(*args) + >>> allclose(ea1, ea2) + True + >>> alpha, beta, gamma = ea1 + >>> xaxis, yaxis, zaxis = [1, 0, 0], [0, 1, 0], [0, 0, 1] + >>> Rx = Rotation.from_axis_and_angle(xaxis, alpha) + >>> Ry = Rotation.from_axis_and_angle(yaxis, beta) + >>> Rz = Rotation.from_axis_and_angle(zaxis, gamma) + >>> R2 = Rx * Ry * Rz + >>> R1 == R2 + True """ - M = matrix_from_euler_angles(euler_angles, static, axes) return Rotation(M) @property def quaternion(self): - """Returns the 4 quaternion coefficients from the ``Rotation``. - - Example: - >>> q1 = [0.945, -0.021, -0.125, 0.303] - >>> R = Rotation.from_quaternion(q1) - >>> q2 = R.quaternion - >>> allclose(q1, q2, tol=1e-3) - True + """Returns the Quaternion from the ``Rotation``. + + Returns + ------- + :class:`Quaternion` + + Examples + -------- + >>> q1 = [0.945, -0.021, -0.125, 0.303] + >>> R = Rotation.from_quaternion(q1) + >>> q2 = R.quaternion + >>> allclose(q1, q2, tol=1e-3) + True """ - return quaternion_from_matrix(self.matrix) + from compas.geometry.primitives import Quaternion + return Quaternion(*quaternion_from_matrix(self.matrix)) @property def axis_and_angle(self): """Returns the axis and the angle of the ``Rotation``. - Example: - >>> axis1 = normalize_vector([-0.043, -0.254, 0.617]) - >>> angle1 = 0.1 - >>> R = Rotation.from_axis_and_angle(axis1, angle1) - >>> axis2, angle2 = R.axis_and_angle - >>> allclose(axis1, axis2) - True - >>> allclose([angle1], [angle2]) - True + Returns + ------- + tuple: (:class:`Vector`, float) + + Examples + -------- + >>> axis1 = normalize_vector([-0.043, -0.254, 0.617]) + >>> angle1 = 0.1 + >>> R = Rotation.from_axis_and_angle(axis1, angle1) + >>> axis2, angle2 = R.axis_and_angle + >>> allclose(axis1, axis2) + True + >>> allclose([angle1], [angle2]) + True """ - return axis_and_angle_from_matrix(self.matrix) + from compas.geometry.primitives import Vector + axis, angle = axis_and_angle_from_matrix(self.matrix) + return Vector(*axis), angle @property def axis_angle_vector(self): """Returns the axis-angle vector of the ``Rotation``. - Returns: - (:obj:`list` of :obj:`float`): Three numbers that represent the - axis of rotation and angle of rotation through the vector's - magnitude. - - Example: - >>> aav1 = [-0.043, -0.254, 0.617] - >>> R = Rotation.from_axis_angle_vector(aav1) - >>> aav2 = R.axis_angle_vector - >>> allclose(aav1, aav2) - True + Returns + ------- + :class:`Vector` + + Examples + -------- + >>> aav1 = [-0.043, -0.254, 0.617] + >>> R = Rotation.from_axis_angle_vector(aav1) + >>> aav2 = R.axis_angle_vector + >>> allclose(aav1, aav2) + True """ axis, angle = self.axis_and_angle - return scale_vector(axis, angle) + return axis.scaled(angle) def euler_angles(self, static=True, axes='xyz'): """Returns Euler angles from the ``Rotation`` according to specified axis sequence and rotation type. - Args: - static(:obj:`bool`, optional): If true the rotations are applied to - a static frame. If not, to a rotational. Defaults to True. - axes(:obj:`str`, optional): A 3 character string specifying the - order of the axes. Defaults to 'xyz'. - - Returns: - (:obj:`list` of :obj:`float`): The 3 Euler angles. - - Example: - >>> ea1 = 1.4, 0.5, 2.3 - >>> args = False, 'xyz' - >>> R1 = Rotation.from_euler_angles(ea1, *args) - >>> ea2 = R1.euler_angles(*args) - >>> allclose(ea1, ea2) - True + Parameters + ---------- + static : bool, optional + If true the rotations are applied to a static frame. If not, to a + rotational. Defaults to True. + axes : str, optional + A 3 character string specifying the order of the axes. Defaults to + 'xyz'. + + Returns + ------- + list of float: The 3 Euler angles. + + Examples + -------- + >>> ea1 = 1.4, 0.5, 2.3 + >>> args = False, 'xyz' + >>> R1 = Rotation.from_euler_angles(ea1, *args) + >>> ea2 = R1.euler_angles(*args) + >>> allclose(ea1, ea2) + True """ - return euler_angles_from_matrix(self.matrix, static, axes) @property def basis_vectors(self): """Returns the basis vectors of the ``Rotation``. + + Returns + ------- + tuple: (:class:`Vector`, :class:`Vector`) + """ - return basis_vectors_from_matrix(self.matrix) + from compas.geometry.primitives import Vector + xaxis, yaxis = basis_vectors_from_matrix(self.matrix) + return Vector(*xaxis), Vector(*yaxis) # ============================================================================== From 49fbf5bcc2fead98cf9a76a0783a36975c3ed1f8 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Wed, 9 Oct 2019 11:59:37 +0200 Subject: [PATCH 08/73] added transform_frames, fix dehomogenize_numpy --- src/compas/geometry/primitives/__init__.py | 2 +- src/compas/geometry/primitives/primitive.py | 2 - .../geometry/transformations/helpers.py | 250 +++++++++++++++++- 3 files changed, 244 insertions(+), 10 deletions(-) diff --git a/src/compas/geometry/primitives/__init__.py b/src/compas/geometry/primitives/__init__.py index 3fc329583436..c7ddd094d2ad 100644 --- a/src/compas/geometry/primitives/__init__.py +++ b/src/compas/geometry/primitives/__init__.py @@ -7,6 +7,7 @@ from .point import Point from .line import Line from .plane import Plane +from .quaternion import Quaternion from .frame import Frame from .polyline import Polyline @@ -14,7 +15,6 @@ from .circle import Circle from .curve import Bezier -from .quaternion import Quaternion from .shapes import * diff --git a/src/compas/geometry/primitives/primitive.py b/src/compas/geometry/primitives/primitive.py index 429c28b6062f..995ced3682bf 100644 --- a/src/compas/geometry/primitives/primitive.py +++ b/src/compas/geometry/primitives/primitive.py @@ -2,8 +2,6 @@ from __future__ import division from __future__ import print_function -from compas.geometry import Transformation - __all__ = ['Primitive'] diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index 257f16a95f35..959b9b4fe34d 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -48,14 +48,79 @@ def transform_points(points, T): + """Transform multiple points with one Transformation. + + Parameters + ---------- + points : list of :class:`Point` + A list of points to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> points = [Point(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.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. + + Parameters + ---------- + vectors : list of :class:`Vector` + A list of vectors to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> vectors = [Vector(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.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. + + 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]), [[0,2,0], [5,2,1], [0,2,1]]] + >>> T = Rotation.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 transform_points_numpy(points, T): + """Transform multiple points with one Transformation using numpy. + + Parameters + ---------- + points : list of :class:`Point` + A list of points to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> points = [Point(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) + >>> points_transformed = transform_points_numpy(points, T) + """ from numpy import asarray T = asarray(T) points = homogenize_numpy(points, w=1.0) @@ -63,12 +128,49 @@ def transform_points_numpy(points, T): def transform_vectors_numpy(vectors, T): + """Transform multiple vectors with one Transformation using numpy. + + Parameters + ---------- + vectors : list of :class:`Vector` + A list of vectors to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> vectors = [Vector(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) + >>> vectors_transformed = transform_vectors_numpy(vectors, T) + """ from numpy import asarray T = asarray(T) vectors = homogenize_numpy(vectors, w=0.0) return dehomogenize_numpy(vectors.dot(T.T)) +def transform_frames_numpy(frames, T): + """Transform multiple frames with one Transformation usig numpy. + + 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]), [[0,2,0], [5,2,1], [0,2,1]]] + >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) + >>> transformed_frames = transform_frames_numpy(frames, T) + """ + from numpy import asarray + T = asarray(T) + points_and_vectors = homogenize_and_flatten_frames_numpy(frames) + return dehomogenize_and_unflatten_frames_numpy(points_and_vectors.dot(T.T)) + + # ============================================================================== # helping helpers # ============================================================================== @@ -111,12 +213,12 @@ def dehomogenize(vectors): Parameters ---------- - vectors : list + vectors : list of float A list of vectors. Returns ------- - list + list of float Dehomogenised vectors. Examples @@ -127,7 +229,118 @@ def dehomogenize(vectors): 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)] + + +def homogenize_and_flatten_frames_numpy(frames): + """Homogenize a list of frames and flatten the 3D list into a 2D list using numpy. + + The frame consists of a point and 2 orthonormal vectors. + + Parameters + ---------- + frames: list of :class:`Frame` + + Returns + ------- + :class:`numpy.ndarray` + An array of points and vectors. + + Examples + -------- + >>> import numpy as np + >>> frames = [Frame((1, 1, 1), (0, 1, 0), (1, 0, 0))] + >>> res = homogenize_and_flatten_frames_numpy(frames) + >>> np.allclose(res, [[1.0, 1.0, 1.0, 1.0], [0.0, 1.0, 0.0, 0.0], [1.0, -0.0, 0.0, 0.0]]) + True + """ + from numpy import asarray + from numpy import tile + from numpy import hstack + n = len(frames) + frames = asarray(frames).reshape(n * 3, 3) + extend = tile(asarray([1, 0, 0]).reshape(3, 1), (n, 1)) + return hstack((frames, extend)) + + +def dehomogenize_and_unflatten_frames_numpy(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 + ------- + :class:`numpy.ndarray` + The frames. + + Examples + -------- + >>> import numpy as np + >>> points_and_vectors = [(1., 1., 1., 1.), (0., 1., 0., 0.), (1., 0., 0., 0.)] + >>> res = dehomogenize_and_unflatten_frames_numpy(points_and_vectors) + >>> np.allclose(res, [[1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]) + True + """ + frames = dehomogenize_numpy(points_and_vectors) + return frames.reshape((int(frames.shape[0]/3.), 3, 3)) + + def homogenize_numpy(points, w=1.0): + """Dehomogenizes points or vectors. + + Parameters + ---------- + points: list of :class:`Points` or list of :class:`Vectors` + + Returns + ------- + :class:`numpy.ndarray` + """ from numpy import asarray from numpy import hstack from numpy import ones @@ -138,10 +351,25 @@ def homogenize_numpy(points, w=1.0): def dehomogenize_numpy(points): + """Dehomogenizes points or vectors. + + Parameters + ---------- + points: list of :class:`Points` or list of :class:`Vectors` + + Returns + ------- + :class:`numpy.ndarray` + """ from numpy import asarray + from numpy import vectorize + + def func(a): + return a if a else 1. + func = vectorize(func) points = asarray(points) - return points[:, :-1] / points[:, -1].reshape((-1, 1)) + return points[:, :-1] / func(points[:, -1]).reshape((-1, 1)) # this function will not always work @@ -288,12 +516,11 @@ def inverse(M): Returns ------- - list of list of float + list of list of float The inverted matrix. Examples -------- - >>> from compas.geometry import Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = matrix_from_frame(f) >>> I = multiply_matrices(T, inverse(T)) @@ -535,5 +762,14 @@ def compose_matrix(scale=None, shear=None, angles=None, # ============================================================================== if __name__ == "__main__": - - pass + import math + import doctest + from compas.geometry import allclose + from compas.geometry import matrix_from_frame + from compas.geometry import identity_matrix + from compas.geometry import Point + from compas.geometry import Vector + from compas.geometry import Frame + from compas.geometry import Transformation + from compas.geometry import Rotation + doctest.testmod(globs=globals()) From 60a985f6bd991de7cc6853ec36efaa1b2da6a50a Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Wed, 9 Oct 2019 16:11:57 +0200 Subject: [PATCH 09/73] adding correct_axis_vectors --- .../geometry/transformations/helpers.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index 959b9b4fe34d..b42d8dde955b 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -382,6 +382,36 @@ def local_axes(a, b, c): return normalize_vector(u), normalize_vector(v), normalize_vector(w) +def correct_axis_vectors(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. + + Examples + -------- + >>> xaxis = [1, 4, 5] + >>> yaxis = [1, 0, -2] + >>> xaxis, yaxis = correct_axis_vectors(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 + """ + # TODO use this in Frame + xaxis = normalize_vector(xaxis) + yaxis = normalize_vector(yaxis) + zaxis = normalize_vector(cross_vectors(xaxis, yaxis)) + yaxis = cross_vectors(zaxis, xaxis) + return xaxis, yaxis + # this should be defined somewhere else # and should have a python equivalent # there is an implementation available in frame @@ -406,6 +436,17 @@ def local_coords_numpy(origin, uvw, xyz): ----- ``origin`` and ``uvw`` together form the frame of local coordinates. + Examples + -------- + >>> import numpy as np + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> xyz = [Point(2, 3, 5)] + >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> np.allclose(rst, [[3.72620657, 4.08804176, 1.55025779]]) + True + >>> f.represent_point_in_local_coordinates(xyz[0]) + Point(3.726, 4.088, 1.550) """ from numpy import asarray from scipy.linalg import solve @@ -440,6 +481,15 @@ def global_coords_numpy(origin, uvw, rst): ----- ``origin`` and ``uvw`` together form the frame of local coordinates. + Examples + -------- + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> xyz = [Point(2, 3, 5)] + >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> xyz2 = global_coords_numpy(origin, uvw, rst) + >>> numpy.allclose(xyz, xyz2) + True """ from numpy import asarray @@ -764,6 +814,7 @@ def compose_matrix(scale=None, shear=None, angles=None, if __name__ == "__main__": import math import doctest + import numpy from compas.geometry import allclose from compas.geometry import matrix_from_frame from compas.geometry import identity_matrix From f6dbb9d69c8953272ab7d4d8e27a8259f740e5b2 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Thu, 10 Oct 2019 17:28:20 +0200 Subject: [PATCH 10/73] adding matrix_from_frame_to_frame and matrix_change_basis --- .../geometry/transformations/helpers.py | 25 +++++++- .../geometry/transformations/matrices.py | 61 ++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index b42d8dde955b..2752d0a065f4 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -395,6 +395,10 @@ def correct_axis_vectors(xaxis, yaxis): tuple: (xaxis, yaxis) The corrected axes. + Raises + ------ + ValueError: If xaxis and yaxis cannot span a plane. + Examples -------- >>> xaxis = [1, 4, 5] @@ -408,13 +412,30 @@ def correct_axis_vectors(xaxis, yaxis): # TODO use this in Frame xaxis = normalize_vector(xaxis) yaxis = normalize_vector(yaxis) - zaxis = normalize_vector(cross_vectors(xaxis, yaxis)) - yaxis = cross_vectors(zaxis, xaxis) + 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 # this should be defined somewhere else # and should have a python equivalent # there is an implementation available in frame + +def local_coords(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. + """ + #T = Transformation.change_basis(Frame.worldXY(), self) + pass + + def local_coords_numpy(origin, uvw, xyz): """Convert global coordinates to local coordinates. diff --git a/src/compas/geometry/transformations/matrices.py b/src/compas/geometry/transformations/matrices.py index fe42af3d298e..bdd7671c6bc2 100644 --- a/src/compas/geometry/transformations/matrices.py +++ b/src/compas/geometry/transformations/matrices.py @@ -14,11 +14,12 @@ from compas.geometry.basic import multiply_matrix_vector from compas.geometry.basic import length_vector from compas.geometry.basic import allclose +from compas.geometry.basic import multiply_matrices from compas.geometry.transformations import _EPS from compas.geometry.transformations import _SPEC2TUPLE from compas.geometry.transformations import _NEXT_SPEC - +from compas.geometry.transformations import inverse __all__ = [ 'identity_matrix', @@ -80,6 +81,64 @@ def matrix_from_frame(frame): M[0][3], M[1][3], M[2][3] = frame.point return M +def matrix_from_frame_to_frame(frame_from, frame_to): + """Computes a transformation between two frames. + + This transformation allows to transform geometry from one Cartesian + coordinate system defined by "frame_from" to another Cartesian + coordinate system defined by "frame_to". + + Parameters + ---------- + frame_from : :class:`Frame` + A frame defining the original Cartesian coordinate system + frame_to : :class:`Frame` + A frame defining the targeted Cartesian coordinate system + + Examples + -------- + >>> from compas.geometry import Frame + >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) + >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = matrix_from_frame_to_frame(frame_from, frame_to) + >>> f1.transform(T) + >>> f1 == f2 + True + """ + T1 = matrix_from_frame(frame_from) + T2 = matrix_from_frame(frame_to) + return multiply_matrices(T2, inverse(T1)) + +def matrix_change_basis(frame_from, frame_to): + """Computes a change of basis transformation between two frames. + + A basis change is essentially a remapping of geometry from one + coordinate system to another. + + Parameters + ---------- + frame_from : :class:`Frame` + A frame defining the original Cartesian coordinate system + frame_to : :class:`Frame` + A frame defining the targeted Cartesian coordinate system + + Example: + >>> from compas.geometry import Point, Frame + >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) + >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = change_basis(f1, f2) + >>> p_f1 = Point(1, 1, 1) # point in f1 + >>> p_f2 = p_f1.transformed(T) # same point represented in f2 + >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates + >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates + >>> print(p_w1) + Point(0.733, 2.492, 3.074) + >>> print(p_w2) + Point(0.733, 2.492, 3.074) + """ + T1 = matrix_from_frame(frame_from) + T2 = matrix_from_frame(frame_to) + return multiply_matrices(inverse(T2), T1) def matrix_from_euler_angles(euler_angles, static=True, axes='xyz'): """Calculates a rotation matrix from Euler angles. From 5b1e60018f89c650136c7d4dc997a4733f8061ae Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Fri, 11 Oct 2019 16:31:56 +0200 Subject: [PATCH 11/73] Update matrices.py --- .../geometry/transformations/matrices.py | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/compas/geometry/transformations/matrices.py b/src/compas/geometry/transformations/matrices.py index bdd7671c6bc2..f211176b2595 100644 --- a/src/compas/geometry/transformations/matrices.py +++ b/src/compas/geometry/transformations/matrices.py @@ -19,7 +19,6 @@ from compas.geometry.transformations import _EPS from compas.geometry.transformations import _SPEC2TUPLE from compas.geometry.transformations import _NEXT_SPEC -from compas.geometry.transformations import inverse __all__ = [ 'identity_matrix', @@ -97,14 +96,11 @@ def matrix_from_frame_to_frame(frame_from, frame_to): Examples -------- - >>> from compas.geometry import Frame >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> T = matrix_from_frame_to_frame(frame_from, frame_to) - >>> f1.transform(T) - >>> f1 == f2 - True + >>> T = matrix_from_frame_to_frame(f1, f2) """ + from compas.geometry.transformations import inverse T1 = matrix_from_frame(frame_from) T2 = matrix_from_frame(frame_to) return multiply_matrices(T2, inverse(T1)) @@ -126,16 +122,9 @@ def matrix_change_basis(frame_from, frame_to): >>> from compas.geometry import Point, Frame >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> T = change_basis(f1, f2) - >>> p_f1 = Point(1, 1, 1) # point in f1 - >>> p_f2 = p_f1.transformed(T) # same point represented in f2 - >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates - >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates - >>> print(p_w1) - Point(0.733, 2.492, 3.074) - >>> print(p_w2) - Point(0.733, 2.492, 3.074) + >>> T = matrix_change_basis(f1, f2) """ + from compas.geometry.transformations import inverse T1 = matrix_from_frame(frame_from) T2 = matrix_from_frame(frame_to) return multiply_matrices(inverse(T2), T1) @@ -1024,5 +1013,7 @@ def axis_angle_from_quaternion(q): # ============================================================================== if __name__ == "__main__": - - pass + import doctest + from compas.geometry import Frame + from compas.geometry import Transformation + doctest.testmod(globs=globals()) From c7be7f4508d36bf25e4fe63a086a88c1c019eace Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 12:57:56 +0200 Subject: [PATCH 12/73] represent_(frames\points\vectors)_in_(local\global)_coordinate_system with local\global_coordinates --- src/compas/geometry/primitives/frame.py | 175 +++++------------------- 1 file changed, 33 insertions(+), 142 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 47b902ff8fe3..26c93e4410b8 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -649,180 +649,71 @@ def euler_angles(self, static=True, axes='xyz'): R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return euler_angles_from_matrix(R, static, axes) - def represent_point_in_local_coordinates(self, point): - """Represents a point in the frame's local coordinate system. + def local_coordinates(self, coords_wcs): + """Returns the object's coordinates in the frame's local coordinate system. Parameters ---------- - point : :obj:`list` of :obj:`float` or :class:`Point` - A point in world XY. + coords_wcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + A coordinate object in world XY. Returns ------- :class:`Point` A point in the local coordinate system of the frame. + Notes + ----- + If you pass a list of float, it is assumed to represent a point. + Examples -------- >>> from compas.geometry import Point, Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = Point(2, 2, 2) - >>> pf = f.represent_point_in_local_coordinates(pw1) - >>> pw2 = f.represent_point_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - + >>> pw = Point(2, 2, 2) + >>> pl = f.local_coordinates(pw) + >>> f.global_coordinates(pl) + Point(2.000, 2.000, 2.000) """ - point = Point(*point) T = Transformation.change_basis(Frame.worldXY(), self) - return point.transformed(T) - - def represent_point_in_global_coordinates(self, point): - """Represents a point from local coordinates in the world coordinate system. - - Parameters - ---------- - point : :obj:`list` of :obj:`float` or :class:`Point` - A point in local coordinates. - - Returns - ------- - :class:`Point` - A point in the world coordinate system. - - Examples - -------- - >>> from compas.geometry import Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] - >>> pf = f.represent_point_in_local_coordinates(pw1) - >>> pw2 = f.represent_point_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - - """ - point = Point(*point) - T = Transformation.change_basis(self, Frame.worldXY()) - return point.transformed(T) + if isinstance(coords_wcs, list): + point = Point(*coords_wcs) + return point.transformed(T) + else: + return coords_wcs.transformed(T) - def represent_vector_in_local_coordinates(self, vector): - """Represents a vector in the frame's local coordinate system. + def global_coordinates(self, coords_lcs): + """Returns the frame's object's coordinates in the global coordinate system. Parameters ---------- - vector : :obj:`list` of :obj:`float` or :class:`Vector` - A vector in world XY. + coords_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + A coordinate object in the frames coordinate system. Returns ------- :class:`Vector` A vector in the local coordinate system of the frame. - Examples - -------- - >>> from compas.geometry import Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] - >>> pf = f.represent_vector_in_local_coordinates(pw1) - >>> pw2 = f.represent_vector_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - - """ - vector = Vector(*vector) - T = Transformation.change_basis(Frame.worldXY(), self) - return vector.transformed(T) - - def represent_vector_in_global_coordinates(self, vector): - """Represents a vector in local coordinates in the world coordinate system. - - Parameters - ---------- - vector: :obj:`list` of :obj:`float` or :class:`Vector` - A vector in local coordinates. - - Returns - ------- - :class:`Vector` - A vector in the world coordinate system. - - Examples - -------- - >>> from compas.geometry import Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] - >>> pf = f.represent_vector_in_local_coordinates(pw1) - >>> pw2 = f.represent_vector_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - - """ - vector = Vector(*vector) - T = Transformation.change_basis(self, Frame.worldXY()) - return vector.transformed(T) - - def represent_frame_in_local_coordinates(self, frame): - """Represents another frame in the frame's local coordinate system. - - Parameters - ---------- - frame: :class:`Frame` - A frame in the world coordinate system. - - Returns - ------- - :class:`Frame` - A frame in the frame's local coordinate system. - - Examples - -------- - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = Frame([1, 1, 1], [0.707, 0.707, 0], [-0.707, 0.707, 0]) - >>> pf = f.represent_frame_in_local_coordinates(pw1) - >>> pw2 = f.represent_frame_in_global_coordinates(pf) - >>> allclose(pw1.point, pw2.point) - True - >>> allclose(pw1.xaxis, pw2.xaxis) - True - >>> allclose(pw1.yaxis, pw2.yaxis) - True - - """ - T = Transformation.change_basis(Frame.worldXY(), self) - return frame.transformed(T) - - - def represent_frame_in_global_coordinates(self, frame): - """Represents another frame in the local coordinate system in the world - coordinate system. - - Parameters - ---------- - frame: :class:`Frame` - A frame in the local coordinate system. - - Returns - ------- - :class:`Frame` - A frame in the world coordinate system. + Notes + ----- + If you pass a list of float, it is assumed to represent a point. Examples -------- >>> from compas.geometry import Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = Frame([1, 1, 1], [0.707, 0.707, 0], [-0.707, 0.707, 0]) - >>> pf = f.represent_frame_in_local_coordinates(pw1) - >>> pw2 = f.represent_frame_in_global_coordinates(pf) - >>> allclose(pw1.point, pw2.point) - True - >>> allclose(pw1.xaxis, pw2.xaxis) - True - >>> allclose(pw1.yaxis, pw2.yaxis) - True - + >>> pl = Point(1.632, -0.090, 0.573) + >>> pw = f.global_coordinates(pl) + >>> f.local_coordinates(pw) + Point(1.632, -0.090, 0.573) """ T = Transformation.change_basis(self, Frame.worldXY()) - return frame.transformed(T) + if isinstance(coords_lcs, list): + point = Point(*coords_lcs) + return point.transformed(T) + else: + return coords_lcs.transformed(T) # ========================================================================== # transformations From a042b4b3d942e6cb4d4fe1e0ac25f35fbeb05a7d Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 13:28:41 +0200 Subject: [PATCH 13/73] adding python equivalent of local/global_coords_numpy --- .../geometry/transformations/helpers.py | 67 ++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index 2752d0a065f4..bf97cc5b5bb4 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -21,7 +21,7 @@ from compas.geometry.transformations.matrices import matrix_from_euler_angles from compas.geometry.transformations.matrices import matrix_from_shear_entries from compas.geometry.transformations.matrices import matrix_from_scale_factors - +from compas.geometry.transformations.matrices import matrix_change_basis __all__ = [ 'transform_points', @@ -418,9 +418,7 @@ def correct_axis_vectors(xaxis, yaxis): yaxis = cross_vectors(normalize_vector(zaxis), xaxis) return xaxis, yaxis -# this should be defined somewhere else -# and should have a python equivalent -# there is an implementation available in frame + def local_coords(frame, xyz): """Convert global coordinates to local coordinates. @@ -431,9 +429,24 @@ def local_coords(frame, xyz): 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(*local_coords(f, xyz)[0]) + Point(3.726, 4.088, 1.550) """ - #T = Transformation.change_basis(Frame.worldXY(), self) - pass + T = matrix_change_basis(Frame.worldXY(), frame) + return transform_points(xyz, T) + def local_coords_numpy(origin, uvw, xyz): @@ -464,10 +477,8 @@ def local_coords_numpy(origin, uvw, xyz): >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] >>> xyz = [Point(2, 3, 5)] >>> rst = local_coords_numpy(origin, uvw, xyz) - >>> np.allclose(rst, [[3.72620657, 4.08804176, 1.55025779]]) + >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) True - >>> f.represent_point_in_local_coordinates(xyz[0]) - Point(3.726, 4.088, 1.550) """ from numpy import asarray from scipy.linalg import solve @@ -478,9 +489,34 @@ def local_coords_numpy(origin, uvw, xyz): return rst.T -# this should be defined somewhere else -# and should have a python equivalent -# there is an implementation available in frame +def global_coords(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(*global_coords(f, xyz)[0]) + Point(2.000, 3.000, 5.000) + """ + T = matrix_change_basis(frame, Frame.worldXY()) + return transform_points(xyz, T) + + def global_coords_numpy(origin, uvw, rst): """Convert local coordinates to global (world) coordinates. @@ -506,10 +542,9 @@ def global_coords_numpy(origin, uvw, rst): -------- >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] - >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(origin, uvw, xyz) - >>> xyz2 = global_coords_numpy(origin, uvw, rst) - >>> numpy.allclose(xyz, xyz2) + >>> rst = [Point(3.726, 4.088, 1.550)] + >>> xyz = global_coords_numpy(origin, uvw, rst) + >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) True """ from numpy import asarray From 8ac768077ff6a2e0573e0bdf92179575b8cb248a Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 13:28:58 +0200 Subject: [PATCH 14/73] change coordinates to coords --- src/compas/geometry/primitives/frame.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 26c93e4410b8..da2304cd72b1 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -649,7 +649,7 @@ def euler_angles(self, static=True, axes='xyz'): R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return euler_angles_from_matrix(R, static, axes) - def local_coordinates(self, coords_wcs): + def local_coords(self, coords_wcs): """Returns the object's coordinates in the frame's local coordinate system. Parameters @@ -671,8 +671,8 @@ def local_coordinates(self, coords_wcs): >>> from compas.geometry import Point, Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pw = Point(2, 2, 2) - >>> pl = f.local_coordinates(pw) - >>> f.global_coordinates(pl) + >>> pl = f.local_coords(pw) + >>> f.global_coords(pl) Point(2.000, 2.000, 2.000) """ T = Transformation.change_basis(Frame.worldXY(), self) @@ -682,7 +682,7 @@ def local_coordinates(self, coords_wcs): else: return coords_wcs.transformed(T) - def global_coordinates(self, coords_lcs): + def global_coords(self, coords_lcs): """Returns the frame's object's coordinates in the global coordinate system. Parameters @@ -704,8 +704,8 @@ def global_coordinates(self, coords_lcs): >>> from compas.geometry import Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pl = Point(1.632, -0.090, 0.573) - >>> pw = f.global_coordinates(pl) - >>> f.local_coordinates(pw) + >>> pw = f.global_coords(pl) + >>> f.local_coords(pw) Point(1.632, -0.090, 0.573) """ T = Transformation.change_basis(self, Frame.worldXY()) From 6431f0de915e7ffc850808087dffb1474ce57ed9 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 14:06:41 +0200 Subject: [PATCH 15/73] moving methods from helpers into coordinate_systems --- .../geometry/transformations/__init__.py | 1 + .../transformations/coordinate_systems.py | 217 ++++++++++++++++++ .../geometry/transformations/helpers.py | 193 +--------------- 3 files changed, 221 insertions(+), 190 deletions(-) create mode 100644 src/compas/geometry/transformations/coordinate_systems.py diff --git a/src/compas/geometry/transformations/__init__.py b/src/compas/geometry/transformations/__init__.py index 7f170b30b709..5810614ecb53 100644 --- a/src/compas/geometry/transformations/__init__.py +++ b/src/compas/geometry/transformations/__init__.py @@ -22,6 +22,7 @@ # todo: separate the numpy version inot separate modules from .helpers import * +from .coordinate_systems import * from .matrices import * from .transformations import * diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py new file mode 100644 index 000000000000..3a6c58c7317c --- /dev/null +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -0,0 +1,217 @@ +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division + +import math + +from compas.geometry.basic import normalize_vector +from compas.geometry.basic import cross_vectors +from compas.geometry.basic import norm_vector + +from compas.geometry.transformations.matrices import matrix_change_basis +from compas.geometry.transformations.helpers import transform_points + +__all__ = [ + 'local_axes', + 'correct_axis_vectors', + 'local_coords', + 'local_coords_numpy', + 'global_coords', + 'global_coords_numpy', +] + + +# this function will not always work +# it is also a duplicate of stuff found in matrices and frame +def local_axes(a, b, c): + # TODO: is this used somewhere? + u = b - a + v = c - a + u, v = correct_axis_vectors(v, v) + w = cross_vectors(u, v) + return u, v, w + + +def correct_axis_vectors(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 = correct_axis_vectors(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 + """ + # TODO use this in Frame + 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 local_coords(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(*local_coords(f, xyz)[0]) + Point(3.726, 4.088, 1.550) + """ + from compas.geometry.primitives import Frame + T = matrix_change_basis(Frame.worldXY(), frame) + return transform_points(xyz, T) + + +def local_coords_numpy(origin, uvw, xyz): + """Convert global coordinates to local coordinates. + + Parameters + ---------- + origin : array-like + The global (XYZ) coordinates of the origin of the local coordinate system. + uvw : array-like + The global coordinate difference vectors of the axes of the local coordinate system. + xyz : array-like + The global coordinates of the points to convert. + + Returns + ------- + array + The coordinates of the given points in the local coordinate system. + + Notes + ----- + ``origin`` and ``uvw`` together form the frame of local coordinates. + + Examples + -------- + >>> import numpy as np + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> xyz = [Point(2, 3, 5)] + >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) + True + """ + from numpy import asarray + from scipy.linalg import solve + + uvw = asarray(uvw).T + xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) + rst = solve(uvw, xyz) + return rst.T + + +def global_coords(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(*global_coords(f, xyz)[0]) + Point(2.000, 3.000, 5.000) + """ + T = matrix_change_basis(frame, Frame.worldXY()) + return transform_points(xyz, T) + + +def global_coords_numpy(origin, uvw, rst): + """Convert local coordinates to global (world) coordinates. + + Parameters + ---------- + origin : array-like + The origin of the local coordinate system. + uvw : array-like + The coordinate axes of the local coordinate system. + rst : array-like + The coordinates of the points wrt the local coordinate system. + + Returns + ------- + array + The world coordinates of the given points. + + Notes + ----- + ``origin`` and ``uvw`` together form the frame of local coordinates. + + Examples + -------- + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> rst = [Point(3.726, 4.088, 1.550)] + >>> xyz = global_coords_numpy(origin, uvw, rst) + >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) + True + """ + from numpy import asarray + + uvw = asarray(uvw).T + rst = asarray(rst).T + xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) + return xyz.T + +# ============================================================================== +# Main +# ============================================================================== + + +if __name__ == "__main__": + import doctest + import numpy + from compas.geometry import Point + from compas.geometry import Frame + from compas.geometry import allclose + doctest.testmod(globs=globals()) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index bf97cc5b5bb4..433f0f2a3817 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -6,7 +6,6 @@ from copy import deepcopy -from compas.geometry.basic import normalize_vector from compas.geometry.basic import cross_vectors from compas.geometry.basic import dot_vectors from compas.geometry.basic import multiply_matrix_vector @@ -21,7 +20,6 @@ from compas.geometry.transformations.matrices import matrix_from_euler_angles from compas.geometry.transformations.matrices import matrix_from_shear_entries from compas.geometry.transformations.matrices import matrix_from_scale_factors -from compas.geometry.transformations.matrices import matrix_change_basis __all__ = [ 'transform_points', @@ -30,15 +28,14 @@ 'transform_vectors', 'transform_vectors_numpy', + 'transform_frames', + 'transform_frames_numpy', + 'homogenize', 'dehomogenize', 'homogenize_numpy', 'dehomogenize_numpy', - 'local_axes', - 'local_coords_numpy', - 'global_coords_numpy', - 'determinant', 'inverse', @@ -372,189 +369,6 @@ def func(a): return points[:, :-1] / func(points[:, -1]).reshape((-1, 1)) -# 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 correct_axis_vectors(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 = correct_axis_vectors(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 - """ - # TODO use this in Frame - 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 local_coords(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(*local_coords(f, xyz)[0]) - Point(3.726, 4.088, 1.550) - """ - T = matrix_change_basis(Frame.worldXY(), frame) - return transform_points(xyz, T) - - - -def local_coords_numpy(origin, uvw, xyz): - """Convert global coordinates to local coordinates. - - Parameters - ---------- - origin : array-like - The global (XYZ) coordinates of the origin of the local coordinate system. - uvw : array-like - The global coordinate difference vectors of the axes of the local coordinate system. - xyz : array-like - The global coordinates of the points to convert. - - Returns - ------- - array - The coordinates of the given points in the local coordinate system. - - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - - Examples - -------- - >>> import numpy as np - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] - >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(origin, uvw, xyz) - >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) - True - """ - from numpy import asarray - from scipy.linalg import solve - - uvw = asarray(uvw).T - xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) - rst = solve(uvw, xyz) - return rst.T - - -def global_coords(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(*global_coords(f, xyz)[0]) - Point(2.000, 3.000, 5.000) - """ - T = matrix_change_basis(frame, Frame.worldXY()) - return transform_points(xyz, T) - - -def global_coords_numpy(origin, uvw, rst): - """Convert local coordinates to global (world) coordinates. - - Parameters - ---------- - origin : array-like - The origin of the local coordinate system. - uvw : array-like - The coordinate axes of the local coordinate system. - rst : array-like - The coordinates of the points wrt the local coordinate system. - - Returns - ------- - array - The world coordinates of the given points. - - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - - Examples - -------- - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] - >>> rst = [Point(3.726, 4.088, 1.550)] - >>> xyz = global_coords_numpy(origin, uvw, rst) - >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) - True - """ - from numpy import asarray - - uvw = asarray(uvw).T - rst = asarray(rst).T - xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) - return xyz.T - - def determinant(M, check=True): """Calculates the determinant of a square matrix M. @@ -868,7 +682,6 @@ def compose_matrix(scale=None, shear=None, angles=None, # ============================================================================== if __name__ == "__main__": - import math import doctest import numpy from compas.geometry import allclose From 5297d705bf901511ace8f92baa440559051317bc Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 14:51:57 +0200 Subject: [PATCH 16/73] adding possiblility of passing another cs than worldXY --- src/compas/geometry/primitives/frame.py | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index da2304cd72b1..187a06029816 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -649,13 +649,16 @@ def euler_angles(self, static=True, axes='xyz'): R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return euler_angles_from_matrix(R, static, axes) - def local_coords(self, coords_wcs): + def local_coords(self, coords_rcs, rcs=None): """Returns the object's coordinates in the frame's local coordinate system. Parameters ---------- - coords_wcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float - A coordinate object in world XY. + coords_rcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + Coordinates in world XY or rcs. + rcs : :class:`Frame`, optional + The other coordinate system, defaults to world XY. If rcs is not `None`, + coords_rcs are assumed to be in rcs. Returns ------- @@ -675,20 +678,23 @@ def local_coords(self, coords_wcs): >>> f.global_coords(pl) Point(2.000, 2.000, 2.000) """ - T = Transformation.change_basis(Frame.worldXY(), self) - if isinstance(coords_wcs, list): - point = Point(*coords_wcs) - return point.transformed(T) + if not rcs: + rcs = Frame.worldXY() + T = Transformation.change_basis(rcs, self) + if isinstance(coords_rcs, list): + return Point(*coords_rcs).transformed(T) else: - return coords_wcs.transformed(T) + return coords_rcs.transformed(T) - def global_coords(self, coords_lcs): + def global_coords(self, coords_lcs, rcs=None): """Returns the frame's object's coordinates in the global coordinate system. Parameters ---------- - coords_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + coords_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float A coordinate object in the frames coordinate system. + rcs : :class:`Frame`, optional + The other coordinate system, defaults to world XY. Returns ------- @@ -708,10 +714,11 @@ def global_coords(self, coords_lcs): >>> f.local_coords(pw) Point(1.632, -0.090, 0.573) """ - T = Transformation.change_basis(self, Frame.worldXY()) + if not rcs: + rcs = Frame.worldXY() + T = Transformation.change_basis(self, rcs) if isinstance(coords_lcs, list): - point = Point(*coords_lcs) - return point.transformed(T) + return Point(*coords_lcs).transformed(T) else: return coords_lcs.transformed(T) From 1a7b68e1f2cad97e719e511be9502da5f805facd Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 14:52:14 +0200 Subject: [PATCH 17/73] fixing examples --- .../transformations/transformations.py | 93 ++++++------------- 1 file changed, 28 insertions(+), 65 deletions(-) diff --git a/src/compas/geometry/transformations/transformations.py b/src/compas/geometry/transformations/transformations.py index c18a223898f2..550f402cc6a7 100644 --- a/src/compas/geometry/transformations/transformations.py +++ b/src/compas/geometry/transformations/transformations.py @@ -531,7 +531,7 @@ def project_point_plane(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, 3.0] + [3.0, 3.0, 0.0] """ base, normal = plane @@ -773,7 +773,7 @@ def reflect_line_triangle(line, triangle, tol=1e-6): >>> 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], [1.0, 1.0, 0]) + ([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) """ x = intersection_line_triangle(line, triangle, tol=tol) @@ -827,32 +827,29 @@ def orient_points(points, reference_plane, target_plane): Examples -------- - .. code-block:: python - - 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] + >>> 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) - - print(points[0]) - + >>> + >>> 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]) @@ -873,42 +870,8 @@ def orient_points(points, reference_plane, target_plane): if __name__ == "__main__": - 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) - - print(points[0]) - - # points = [ - # [ 1.0, 1.0, 0.0], - # [-1.0, 1.0, 0.0], - # [-1.0, -1.0, 0.0], - # [ 1.0, -1.0, 0.0] - # ] - - # refplane = ([0, 0, 0], [0, 0, -1.0]) - # tarplane = ([0, 0, 0], [0, 0, 1.0]) - - # points = orient_points(points, refplane, tarplane) - - # print(points) + import doctest + from compas.geometry import allclose + from compas.geometry import Point + doctest.testmod(globs=globals()) From fe9c65900f514728a5cae2a11a86cc86516fa37f Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:16:17 +0200 Subject: [PATCH 18/73] changing global/local_coords_numpy to use frame instead of origin, uvw --- src/compas/geometry/bbox/bbox_numpy.py | 40 +++++-------------- src/compas/geometry/bestfit/bestfit_numpy.py | 5 ++- .../transformations/coordinate_systems.py | 25 +++++++----- src/compas_viewers/core/drawing.py | 9 ++--- 4 files changed, 30 insertions(+), 49 deletions(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index 1822cb712265..041e7cc687ea 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -15,7 +15,7 @@ from scipy.spatial import ConvexHull # from scipy.spatial import QhullError -from compas.geometry import local_axes +from compas.geometry import correct_axes from compas.geometry import local_coords_numpy from compas.geometry import global_coords_numpy @@ -85,8 +85,8 @@ def oriented_bounding_box_numpy(points): >>> a = length_vector(subtract_vectors(bbox[1], bbox[0])) >>> b = length_vector(subtract_vectors(bbox[3], bbox[0])) >>> c = length_vector(subtract_vectors(bbox[4], bbox[0])) - >>> a * b * c - 30.0 + >>> allclose([a * b * c], [30.]) + True """ points = asarray(points) @@ -112,9 +112,10 @@ def oriented_bounding_box_numpy(points): # this can be vectorised! for simplex in hull.simplices: a, b, c = points[simplex] - uvw = local_axes(a, b, c) + u, v = correct_axes(b - a, c - a) xyz = points[hull.vertices] - rst = local_coords_numpy(a, uvw, xyz) + frame = [a, u, v] + rst = local_coords_numpy(frame, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt @@ -131,7 +132,7 @@ def oriented_bounding_box_numpy(points): [rmax, smax, tmax], [rmin, smax, tmax], ] - bbox = global_coords_numpy(a, uvw, bbox) + bbox = global_coords_numpy(frame, bbox) volume = v return bbox @@ -246,28 +247,5 @@ def oriented_bounding_box_xy_numpy(points): from compas.geometry import transform_points_numpy from compas.geometry import allclose - points = numpy.random.rand(10000, 3) - bottom = numpy.array([[0.0,0.0,0.0], [1.0,0.0,0.0], [0.0,1.0,0.0], [1.0,1.0,0.0]]) - top = numpy.array([[0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) - points = numpy.concatenate((points, bottom, top)) - points[:, 0] *= 10 - points[:, 2] *= 3 - - bbox = bounding_box(points) - a = length_vector(subtract_vectors(bbox[1], bbox[0])) - b = length_vector(subtract_vectors(bbox[3], bbox[0])) - c = length_vector(subtract_vectors(bbox[4], bbox[0])) - v1 = a * b * c - - R = Rotation.from_axis_and_angle([1.0, 1.0, 0.0], 0.5 * 3.14159) - points = transform_points_numpy(points, R.matrix) - - bbox = oriented_bounding_box_numpy(points) - - a = length_vector(subtract_vectors(bbox[1], bbox[0])) - b = length_vector(subtract_vectors(bbox[3], bbox[0])) - c = length_vector(subtract_vectors(bbox[4], bbox[0])) - v2 = a * b * c - - print(v1, v2) - print(allclose([v1], [v2])) + import doctest + doctest.testmod(globs=globals()) diff --git a/src/compas/geometry/bestfit/bestfit_numpy.py b/src/compas/geometry/bestfit/bestfit_numpy.py index 1b74668723ff..24499dc69789 100644 --- a/src/compas/geometry/bestfit/bestfit_numpy.py +++ b/src/compas/geometry/bestfit/bestfit_numpy.py @@ -151,7 +151,8 @@ def bestfit_circle_numpy(points): """ o, uvw, _ = pca_numpy(points) - rst = local_coords_numpy(o, uvw, points) + frame = [o, uvw[1], uvw[2]] + rst = local_coords_numpy(frame, points) x = rst[:, 0] y = rst[:, 1] @@ -172,7 +173,7 @@ def f(c): print(residu) - xyz = global_coords_numpy(o, uvw, [[c[0], c[1], 0.0]])[0] + xyz = global_coords_numpy(frame, [[c[0], c[1], 0.0]])[0] o = xyz.tolist() u, v, w = uvw.tolist() diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 3a6c58c7317c..0c9126968314 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -13,7 +13,7 @@ __all__ = [ 'local_axes', - 'correct_axis_vectors', + 'correct_axes', 'local_coords', 'local_coords_numpy', 'global_coords', @@ -27,12 +27,12 @@ def local_axes(a, b, c): # TODO: is this used somewhere? u = b - a v = c - a - u, v = correct_axis_vectors(v, v) + u, v = correct_axes(u, v) w = cross_vectors(u, v) return u, v, w -def correct_axis_vectors(xaxis, yaxis): +def correct_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. Parameters @@ -98,7 +98,7 @@ def local_coords(frame, xyz): return transform_points(xyz, T) -def local_coords_numpy(origin, uvw, xyz): +def local_coords_numpy(frame, xyz): """Convert global coordinates to local coordinates. Parameters @@ -122,16 +122,17 @@ def local_coords_numpy(origin, uvw, xyz): Examples -------- >>> import numpy as np - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> rst = local_coords_numpy(frame, xyz) >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) True """ from numpy import asarray from scipy.linalg import solve + origin = frame[0] + uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] uvw = asarray(uvw).T xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) rst = solve(uvw, xyz) @@ -166,7 +167,7 @@ def global_coords(frame, xyz): return transform_points(xyz, T) -def global_coords_numpy(origin, uvw, rst): +def global_coords_numpy(frame, rst): """Convert local coordinates to global (world) coordinates. Parameters @@ -189,15 +190,17 @@ def global_coords_numpy(origin, uvw, rst): Examples -------- - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> rst = [Point(3.726, 4.088, 1.550)] - >>> xyz = global_coords_numpy(origin, uvw, rst) + >>> xyz = global_coords_numpy(frame, rst) >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) True """ from numpy import asarray + origin = frame[0] + uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] + uvw = asarray(uvw).T rst = asarray(rst).T xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) diff --git a/src/compas_viewers/core/drawing.py b/src/compas_viewers/core/drawing.py index 78f96d6e9bca..274a45eb192a 100644 --- a/src/compas_viewers/core/drawing.py +++ b/src/compas_viewers/core/drawing.py @@ -197,9 +197,7 @@ def draw_circle(circle, color=None, n=100): u = -1.0, 0.0, a v = 0.0, -1.0, b - w = cross_vectors(u, v) - - uvw = [normalize_vector(u), normalize_vector(v), normalize_vector(w)] + frame = [center, normalize_vector(u), normalize_vector(v)] color = color if color else (1.0, 0.0, 0.0, 0.5) sector = 2 * pi / n @@ -212,7 +210,8 @@ def draw_circle(circle, color=None, n=100): x = radius * cos(a) y = radius * sin(a) z = 0 - x, y, z = global_coords_numpy(center, uvw, [[x, y, z]]).tolist()[0] + + x, y, z = global_coords_numpy(frame, [[x, y, z]]).tolist()[0] glVertex3f(x, y, z) glEnd() @@ -222,7 +221,7 @@ def draw_circle(circle, color=None, n=100): x = radius * cos(a) y = radius * sin(a) z = 0 - x, y, z = global_coords_numpy(center, uvw, [[x, y, z]]).tolist()[0] + x, y, z = global_coords_numpy(frame, [[x, y, z]]).tolist()[0] glVertex3f(x, y, z) glEnd() From 4727666f7b43db3a3261d2bee9f5689a88b5fc64 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:17:16 +0200 Subject: [PATCH 19/73] removing unused local axes --- .../geometry/transformations/coordinate_systems.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 0c9126968314..91f761ca4f19 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -12,7 +12,6 @@ from compas.geometry.transformations.helpers import transform_points __all__ = [ - 'local_axes', 'correct_axes', 'local_coords', 'local_coords_numpy', @@ -21,17 +20,6 @@ ] -# this function will not always work -# it is also a duplicate of stuff found in matrices and frame -def local_axes(a, b, c): - # TODO: is this used somewhere? - u = b - a - v = c - a - u, v = correct_axes(u, v) - w = cross_vectors(u, v) - return u, v, w - - def correct_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. From 5fbc34f175510fa5f4ad8840d614498e4457d225 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:25:13 +0200 Subject: [PATCH 20/73] fixing examples --- .../transformations/transformation.py | 42 ++----------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index 55a6184c3842..78938e2c7ba5 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -209,13 +209,10 @@ def change_basis(cls, frame_from, frame_to): >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = Transformation.change_basis(f1, f2) >>> p_f1 = Point(1, 1, 1) # point in f1 - >>> p_f2 = p_f1.transformed(T) # same point represented in f2 - >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates - >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates - >>> print(p_w1) - Point(0.733, 2.492, 3.074) - >>> print(p_w2) - Point(0.733, 2.492, 3.074) + >>> p_f1.transformed(T) # point represented in f2 + Point(1.395, 0.955, 1.934) + >>> f1.global_coords(p_f1, f2) + Point(1.395, 0.955, 1.934) """ T1 = cls.from_frame(frame_from) @@ -329,36 +326,5 @@ def concatenate(self, other): from compas.geometry import Scale from compas.geometry import Frame - # F1 = Frame([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]) - # F2 = Frame([0.0, 0.0, 0.0], [1.0, -1.0, 0.0], [1.0, 1.0, 0.0]) - - # F3 = Frame([1.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]) - # F4 = Frame([1.0, 1.0, 0.0], [1.0, -1.0, 0.0], [1.0, 1.0, 0.0]) - - # T1 = Transformation.from_frame(F1) - # T2 = Transformation.from_frame(F2) - # T3 = Transformation.from_frame(F3) - # T4 = Transformation.from_frame(F4) - - # R1 = Rotation.from_frame(F1) - # R2 = Rotation.from_frame(F2) - # R3 = Rotation.from_frame(F3) - # R4 = Rotation.from_frame(F4) - - # print(R1) - # print(R2) - # print(R3) - # print(R4) - - # # print(T1) - # print(T2) - # # print(T3) - # print(T4) - - # # print(T1.inverse()) - # print(T2.inverse()) - # # print(T3.inverse()) - # print(T4.inverse()) - import doctest doctest.testmod(globs=globals()) From bbc0daa7ce1ca23ab603809e2d932b2a4c4d3283 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:25:21 +0200 Subject: [PATCH 21/73] Update coordinate_systems.py --- src/compas/geometry/transformations/coordinate_systems.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 91f761ca4f19..05a6030c1458 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -41,13 +41,12 @@ def correct_axes(xaxis, yaxis): -------- >>> xaxis = [1, 4, 5] >>> yaxis = [1, 0, -2] - >>> xaxis, yaxis = correct_axis_vectors(xaxis, yaxis) + >>> xaxis, yaxis = correct_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 """ - # TODO use this in Frame xaxis = normalize_vector(xaxis) yaxis = normalize_vector(yaxis) zaxis = cross_vectors(xaxis, yaxis) From 1b49b4a15cdd5dc9d211e9477b4d0b601c8d16e8 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:29:05 +0200 Subject: [PATCH 22/73] Update coordinate_systems.py --- src/compas/geometry/transformations/coordinate_systems.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 05a6030c1458..bc557a157297 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -150,6 +150,7 @@ def global_coords(frame, xyz): >>> Point(*global_coords(f, xyz)[0]) Point(2.000, 3.000, 5.000) """ + from compas.geometry.primitives import Frame T = matrix_change_basis(frame, Frame.worldXY()) return transform_points(xyz, T) From 794b89176c9df70fa717a1fdf1a9b9984408817d Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:31:50 +0200 Subject: [PATCH 23/73] fixing docstrings --- .../transformations/coordinate_systems.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index bc557a157297..b4f5ae7f1e33 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -90,10 +90,8 @@ def local_coords_numpy(frame, xyz): Parameters ---------- - origin : array-like - The global (XYZ) coordinates of the origin of the local coordinate system. - uvw : array-like - The global coordinate difference vectors of the axes of the local coordinate system. + frame : :class:`Frame` or [point, xaxis, yaxis] + The local coordinate system. xyz : array-like The global coordinates of the points to convert. @@ -102,10 +100,6 @@ def local_coords_numpy(frame, xyz): array The coordinates of the given points in the local coordinate system. - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - Examples -------- >>> import numpy as np @@ -160,10 +154,8 @@ def global_coords_numpy(frame, rst): Parameters ---------- - origin : array-like - The origin of the local coordinate system. - uvw : array-like - The coordinate axes of the local coordinate system. + frame : :class:`Frame` or [point, xaxis, yaxis] + The local coordinate system. rst : array-like The coordinates of the points wrt the local coordinate system. From c30cce235211c3491fa8863fcdf3baea7f19f01a Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:51:00 +0200 Subject: [PATCH 24/73] fixes because running tests --- src/compas/geometry/bbox/bbox_numpy.py | 15 +++++++++------ .../transformations/coordinate_systems.py | 8 ++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index 041e7cc687ea..746a321e328b 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -15,7 +15,7 @@ from scipy.spatial import ConvexHull # from scipy.spatial import QhullError -from compas.geometry import correct_axes +from compas.geometry import local_axes from compas.geometry import local_coords_numpy from compas.geometry import global_coords_numpy @@ -62,7 +62,7 @@ def oriented_bounding_box_numpy(points): Examples -------- Generate a random set of points with - :math:`x \in [0, 10]`, :math:`y \in [0, 1]` and :math:`z \in [0, 3]`. + :math:`x in [0, 10]`, :math:`y in [0, 1]` and :math:`z in [0, 3]`. Add the corners of the box such that we now the volume is supposed to be :math:`30.0`. >>> points = numpy.random.rand(10000, 3) @@ -112,9 +112,9 @@ def oriented_bounding_box_numpy(points): # this can be vectorised! for simplex in hull.simplices: a, b, c = points[simplex] - u, v = correct_axes(b - a, c - a) + uvw = local_axes(a, b, c) xyz = points[hull.vertices] - frame = [a, u, v] + frame = [a, uvw[0], uvw[1]] rst = local_coords_numpy(frame, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt @@ -192,7 +192,7 @@ def oriented_bounding_box_xy_numpy(points): p1 = points[simplex[1]] # s direction - s = p1 - p0 + s = p1 - p0 sl = sum(s ** 2) ** 0.5 su = s / sl vn = xy_hull - p0 @@ -205,7 +205,7 @@ def oriented_bounding_box_xy_numpy(points): b1 = p0 + sc[scmax] * su # t direction - t = array([-s[1], s[0]]) + t = array([-s[1], s[0]]) tl = sum(t ** 2) ** 0.5 tu = t / tl vn = xy_hull - p0 @@ -249,3 +249,6 @@ def oriented_bounding_box_xy_numpy(points): import doctest doctest.testmod(globs=globals()) + + coords = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]] + print(oriented_bounding_box_numpy(coords).tolist()) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index b4f5ae7f1e33..f7037b195a49 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -12,6 +12,7 @@ from compas.geometry.transformations.helpers import transform_points __all__ = [ + 'local_axes', 'correct_axes', 'local_coords', 'local_coords_numpy', @@ -19,6 +20,13 @@ 'global_coords_numpy', ] +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 correct_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. From 4310dc97ef9fbb0ce41bcfd15cc0b7a948b334d3 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 16:02:06 +0200 Subject: [PATCH 25/73] fix math backslash --- src/compas/geometry/bbox/bbox_numpy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index 746a321e328b..6246a001ffc5 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -62,7 +62,7 @@ def oriented_bounding_box_numpy(points): Examples -------- Generate a random set of points with - :math:`x in [0, 10]`, :math:`y in [0, 1]` and :math:`z in [0, 3]`. + :math:`x \\in [0, 10]`, :math:`y \\in [0, 1]` and :math:`z \\in [0, 3]`. Add the corners of the box such that we now the volume is supposed to be :math:`30.0`. >>> points = numpy.random.rand(10000, 3) From 0a1b913e43b2e9e2cb2a4e59497cd01ae1f219c3 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 30 Sep 2019 17:51:32 +0200 Subject: [PATCH 26/73] adding change basis --- .../transformations/transformation.py | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index de6d419306b9..20152e74e718 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -182,11 +182,11 @@ def from_frame(cls, frame): @classmethod def from_frame_to_frame(cls, frame_from, frame_to): - """Computes a change of basis transformation between two frames. + """Computes a transformation between two frames. - This transformation maps geometry from one Cartesian coordinate system - defined by "frame_from" to the other Cartesian coordinate system - defined by "frame_to". + This transformation allows to transform geometry from one Cartesian + coordinate system defined by "frame_from" to another Cartesian + coordinate system defined by "frame_to". Parameters ---------- @@ -216,6 +216,39 @@ def from_frame_to_frame(cls, frame_from, frame_to): return cls(multiply_matrices(T2.matrix, matrix_inverse(T1.matrix))) + @classmethod + def change_basis(cls, frame_from, frame_to): + """Computes a change of basis transformation between two frames. + + A basis change is essentially a remapping of geometry from one + coordinate system to another. + + Args: + frame_from (:class:`Frame`): a frame defining the original + Cartesian coordinate system + frame_to (:class:`Frame`): a frame defining the targeted + Cartesian coordinate system + + Example: + >>> from compas.geometry import Point, Frame + >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) + >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = Transformation.change_basis(f1, f2) + >>> p_f1 = Point(1, 1, 1) # point in f1 + >>> p_f2 = p_f1.transformed(T) # same point represented in f2 + >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates + >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates + >>> print(p_w1) + Point(0.733, 2.492, 3.074) + >>> print(p_w2) + Point(0.733, 2.492, 3.074) + """ + + T1 = cls.from_frame(frame_from) + T2 = cls.from_frame(frame_to) + + return cls(multiply_matrices(inverse(T2.matrix), T1.matrix)) + def inverse(self): """Returns the inverse transformation. From 8daa9885f0ed9ff3fb2806d4281d09562877d904 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Wed, 2 Oct 2019 13:56:27 +0200 Subject: [PATCH 27/73] update represent local/global with change_basis --- src/compas/geometry/primitives/frame.py | 45 ++++++++----------- .../transformations/transformation.py | 2 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 9bbced64bda8..ed4f71eb4568 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -663,19 +663,18 @@ def represent_point_in_local_coordinates(self, point): Examples -------- - >>> from compas.geometry import Frame + >>> from compas.geometry import Point, Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] + >>> pw1 = Point(2, 2, 2) >>> pf = f.represent_point_in_local_coordinates(pw1) >>> pw2 = f.represent_point_in_global_coordinates(pf) >>> allclose(pw1, pw2) True """ - pt = Point(*subtract_vectors(point, self.point)) - T = inverse(matrix_from_basis_vectors(self.xaxis, self.yaxis)) - pt.transform(T) - return pt + point = Point(*point) + T = Transformation.change_basis(Frame.worldXY(), self) + return point.transformed(T) def represent_point_in_global_coordinates(self, point): """Represents a point from local coordinates in the world coordinate system. @@ -701,10 +700,9 @@ def represent_point_in_global_coordinates(self, point): True """ - T = matrix_from_frame(self) - pt = Point(*point) - pt.transform(T) - return pt + point = Point(*point) + T = Transformation.change_basis(self, Frame.worldXY()) + return point.transformed(T) def represent_vector_in_local_coordinates(self, vector): """Represents a vector in the frame's local coordinate system. @@ -730,10 +728,9 @@ def represent_vector_in_local_coordinates(self, vector): True """ - T = inverse(matrix_from_basis_vectors(self.xaxis, self.yaxis)) - vec = Vector(*vector) - vec.transform(T) - return vec + vector = Vector(*vector) + T = Transformation.change_basis(Frame.worldXY(), self) + return vector.transformed(T) def represent_vector_in_global_coordinates(self, vector): """Represents a vector in local coordinates in the world coordinate system. @@ -759,10 +756,9 @@ def represent_vector_in_global_coordinates(self, vector): True """ - T = matrix_from_frame(self) - vec = Vector(*vector) - vec.transform(T) - return vec + vector = Vector(*vector) + T = Transformation.change_basis(self, Frame.worldXY()) + return vector.transformed(T) def represent_frame_in_local_coordinates(self, frame): """Represents another frame in the frame's local coordinate system. @@ -791,10 +787,9 @@ def represent_frame_in_local_coordinates(self, frame): True """ - T = Transformation.from_frame(self).inverse() - f = frame.copy() - f.transform(T) - return f + T = Transformation.change_basis(Frame.worldXY(), self) + return frame.transformed(T) + def represent_frame_in_global_coordinates(self, frame): """Represents another frame in the local coordinate system in the world @@ -825,10 +820,8 @@ def represent_frame_in_global_coordinates(self, frame): True """ - T = Transformation.from_frame(self) - f = frame.copy() - f.transform(T) - return f + T = Transformation.change_basis(self, Frame.worldXY()) + return frame.transformed(T) # ========================================================================== # transformations diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index 20152e74e718..705fcc578d0a 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -155,7 +155,7 @@ def from_list(cls, numbers): @classmethod def from_frame(cls, frame): - """Computes a change of basis transformation from world XY to frame. + """Computes a transformation from world XY to frame. Parameters ---------- From a42588bf0b475b18419d014551408bda795654b8 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Thu, 3 Oct 2019 18:52:20 +0200 Subject: [PATCH 28/73] small update on quaternion --- src/compas/geometry/primitives/quaternion.py | 47 +++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/compas/geometry/primitives/quaternion.py b/src/compas/geometry/primitives/quaternion.py index 4562638ac14e..ce26199f5383 100644 --- a/src/compas/geometry/primitives/quaternion.py +++ b/src/compas/geometry/primitives/quaternion.py @@ -1,3 +1,5 @@ +import math + from compas.geometry import quaternion_multiply from compas.geometry import quaternion_conjugate from compas.geometry import quaternion_unitize @@ -5,7 +7,6 @@ from compas.geometry import quaternion_norm from compas.geometry import quaternion_is_unit -from compas.geometry import allclose from compas.geometry.primitives import Primitive @@ -93,10 +94,7 @@ def __init__(self, w, x, y, z): self.z = float(z) def __iter__(self): - return iter([self.w, self.x, self.y, self.z]) - - def __str__(self): - return "Quaternion = %s" % list(self) + return iter(self.wxyz) def __repr__(self): return 'Quaternion({:.{prec}f}, {:.{prec}f}, {:.{prec}f}, {:.{prec}f})'.format(*self, prec=6) @@ -213,11 +211,50 @@ def conjugate(self): qc = quaternion_conjugate(self) return Quaternion(*qc) + # ========================================================================== + # access + # ========================================================================== + + def __getitem__(self, key): + if key == 0: + return self.w + if key == 1: + return self.x + if key == 2: + return self.y + if key == 3: + return self.z + raise KeyError + + def __setitem__(self, key, value): + if key == 0: + self.w = value + return + if key == 1: + self.x = value + return + if key == 2: + self.y = value + if key == 3: + self.z = value + raise KeyError + + # ========================================================================== + # comparison + # ========================================================================== + + def __eq__(self, other, tol=1e-05): + for v1, v2 in zip(self, other): + if math.fabs(v1 - v2) > tol: + return False + return True + # ============================================================================== # Main # ============================================================================== if __name__ == "__main__": + from compas.geometry import allclose import doctest doctest.testmod(globs=globals()) From 4032c798120fd6bfad85d94ccd9795fc3788d7ba Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 10:38:24 +0200 Subject: [PATCH 29/73] return quaternion --- src/compas/geometry/primitives/frame.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index ed4f71eb4568..640a59a3d367 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -25,6 +25,7 @@ from compas.geometry.primitives import Point from compas.geometry.primitives import Vector from compas.geometry.primitives import Plane +from compas.geometry.primitives import Quaternion __all__ = ['Frame'] @@ -530,10 +531,10 @@ def zaxis(self): @property def quaternion(self): - """:obj:`list` of :obj:`float` : The 4 quaternion coefficients from the rotation given by the frame. + """:class:`Quaternion` : The quaternion from the rotation given by the frame. """ rotation = matrix_from_basis_vectors(self.xaxis, self.yaxis) - return quaternion_from_matrix(rotation) + return Quaternion(*quaternion_from_matrix(rotation)) @property def axis_angle_vector(self): From 561a84e9cbd239b555a7a128b8bc41dda2864d70 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 10:50:00 +0200 Subject: [PATCH 30/73] returning vector from axis angle vector --- src/compas/geometry/primitives/frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 640a59a3d367..e9bea43a3fd1 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -540,7 +540,7 @@ def quaternion(self): def axis_angle_vector(self): """vector : The axis-angle vector from the rotation given by the frame.""" R = matrix_from_basis_vectors(self.xaxis, self.yaxis) - return axis_angle_vector_from_matrix(R) + return Vector(*axis_angle_vector_from_matrix(R)) # ========================================================================== # representation From eea3101233d1e5db170501b31f96163a60e7ab13 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 11:50:27 +0200 Subject: [PATCH 31/73] adding unitized and scaled to vector --- src/compas/geometry/primitives/vector.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/compas/geometry/primitives/vector.py b/src/compas/geometry/primitives/vector.py index 4740fbf7f631..2c288d359fef 100644 --- a/src/compas/geometry/primitives/vector.py +++ b/src/compas/geometry/primitives/vector.py @@ -444,6 +444,18 @@ def unitize(self): self.y = self.y / l self.z = self.z / l + def unitized(self): + """Returns a unitized copy of this ``Vector``. + + Returns + ------- + :class:`Vector` + + """ + v = self.copy() + v.unitize() + return v + def scale(self, n): """Scale this ``Vector`` by a factor n. @@ -457,6 +469,23 @@ def scale(self, n): self.y *= n self.z *= n + def scaled(self, n): + """Returns a scaled copy of this ``Vector``. + + Parameters + ---------- + n : float + The scaling factor. + + Returns + ------- + :class:`Vector` + + """ + v = self.copy() + v.scale(n) + return v + def dot(self, other): """The dot product of this ``Vector`` and another vector. From b2461ec637e73b8bd745e7adc2e0832a3fe4ac41 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 8 Oct 2019 12:12:22 +0200 Subject: [PATCH 32/73] fixing returns and docstrings --- .../geometry/transformations/rotation.py | 89 +++++++++++-------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/src/compas/geometry/transformations/rotation.py b/src/compas/geometry/transformations/rotation.py index 733ffce5c52e..e405a76e52ae 100644 --- a/src/compas/geometry/transformations/rotation.py +++ b/src/compas/geometry/transformations/rotation.py @@ -62,9 +62,9 @@ def from_basis_vectors(cls, xaxis, yaxis): Parameters ---------- - xaxis : :obj:`list` of :obj:`float` + xaxis : :class:`Vector` The x-axis of the frame. - yaxis : :obj:`list` of :obj:`float` + yaxis : :class:`Vector` The y-axis of the frame. Examples @@ -116,7 +116,7 @@ def from_quaternion(cls, quaternion): Parameters ---------- - quaternion : :obj:`list` of :obj:`float` + quaternion : :class:`Quaternion` Four numbers that represents the four coefficient values of a quaternion. Examples @@ -136,10 +136,9 @@ def from_axis_angle_vector(cls, axis_angle_vector, point=[0, 0, 0]): Parameters ---------- - axis_angle_vector : :obj:`list` of :obj:`float` - Three numbers that represent the axis of rotation and angle of rotation - through the vector's magnitude. - point : :obj:`list` of :obj:`float`, optional + axis_angle_vector : list of float + Three numbers that represent the axis of rotation and angle of rotation through the vector's magnitude. + point : list of float, optional A point to perform a rotation around an origin other than [0, 0, 0]. Examples @@ -157,16 +156,19 @@ def from_axis_angle_vector(cls, axis_angle_vector, point=[0, 0, 0]): @classmethod def from_axis_and_angle(cls, axis, angle, point=[0, 0, 0]): - """Calculates a ``Rotation`` from a rotation axis and an angle and \ - an optional point of rotation. + """Calculates a ``Rotation`` from a rotation axis and an angle and an optional point of rotation. + + The rotation is based on the right hand rule, i.e. anti-clockwise if the + axis of rotation points towards the observer. Parameters ---------- - axis (:obj:`list` of :obj:`float`): Three numbers that represent - the axis of rotation - angle (:obj:`float`): The rotation angle in radians. - point (:obj:`list` of :obj:`float`, optional): A point to - perform a rotation around an origin other than [0, 0, 0]. + axis : list of float + Three numbers that represent the axis of rotation. + angle : float + The rotation angle in radians. + point : :class:`Point` or list of float + A point to perform a rotation around an origin other than [0, 0, 0]. Examples -------- @@ -200,12 +202,13 @@ def from_euler_angles(cls, euler_angles, static=True, axes='xyz'): Parameters ---------- - euler_angles : :obj:`list` of :obj:`float` - Three numbers that represent the angles of rotations about the defined axes. - static : :obj:`bool`, optional - If true the rotations are applied to a static frame. - If not, to a rotational. Defaults to true. - axes : :obj:`str`, optional + euler_angles: list of float + Three numbers that represent the angles of rotations about the + defined axes. + static: bool, optional + If true the rotations are applied to a static frame. If not, to a + rotational. Defaults to true. + axes: str, optional A 3 character string specifying order of the axes. Defaults to 'xyz'. Examples @@ -230,7 +233,11 @@ def from_euler_angles(cls, euler_angles, static=True, axes='xyz'): @property def quaternion(self): - """Returns the 4 quaternion coefficients from the ``Rotation``. + """Returns the Quaternion from the ``Rotation``. + + Returns + ------- + :class:`Quaternion` Examples -------- @@ -240,12 +247,17 @@ def quaternion(self): >>> allclose(q1, q2, tol=1e-3) True """ - return quaternion_from_matrix(self.matrix) + from compas.geometry.primitives import Quaternion + return Quaternion(*quaternion_from_matrix(self.matrix)) @property def axis_and_angle(self): """Returns the axis and the angle of the ``Rotation``. + Returns + ------- + tuple: (:class:`Vector`, float) + Examples -------- >>> axis1 = normalize_vector([-0.043, -0.254, 0.617]) @@ -257,7 +269,9 @@ def axis_and_angle(self): >>> allclose([angle1], [angle2]) True """ - return axis_and_angle_from_matrix(self.matrix) + from compas.geometry.primitives import Vector + axis, angle = axis_and_angle_from_matrix(self.matrix) + return Vector(*axis), angle @property def axis_angle_vector(self): @@ -265,9 +279,7 @@ def axis_angle_vector(self): Returns ------- - :obj:`list` of :obj:`float` - Three numbers that represent the axis of rotation and angle of rotation - through the vector's magnitude. + :class:`Vector` Examples -------- @@ -278,7 +290,7 @@ def axis_angle_vector(self): True """ axis, angle = self.axis_and_angle - return scale_vector(axis, angle) + return axis.scaled(angle) def euler_angles(self, static=True, axes='xyz'): """Returns Euler angles from the ``Rotation`` according to specified @@ -286,16 +298,16 @@ def euler_angles(self, static=True, axes='xyz'): Parameters ---------- - static : :obj:`bool`, optional - If true the rotations are applied to a static frame. - If not, to a rotational. Defaults to True. - axes : :obj:`str`, optional - A 3 character string specifying the order of the axes. Defaults to 'xyz'. + static : bool, optional + If true the rotations are applied to a static frame. If not, to a + rotational. Defaults to True. + axes : str, optional + A 3 character string specifying the order of the axes. Defaults to + 'xyz'. Returns ------- - :obj:`list` of :obj:`float` - The 3 Euler angles. + list of float: The 3 Euler angles. Examples -------- @@ -311,8 +323,15 @@ def euler_angles(self, static=True, axes='xyz'): @property def basis_vectors(self): """Returns the basis vectors of the ``Rotation``. + + Returns + ------- + tuple: (:class:`Vector`, :class:`Vector`) + """ - return basis_vectors_from_matrix(self.matrix) + from compas.geometry.primitives import Vector + xaxis, yaxis = basis_vectors_from_matrix(self.matrix) + return Vector(*xaxis), Vector(*yaxis) # ============================================================================== From b1f4db4a15c4169f4d182f67f0626f6b260c6275 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Wed, 9 Oct 2019 11:59:37 +0200 Subject: [PATCH 33/73] added transform_frames, fix dehomogenize_numpy --- src/compas/geometry/primitives/__init__.py | 2 +- src/compas/geometry/primitives/primitive.py | 2 - .../geometry/transformations/helpers.py | 775 ++++++++++++++++++ 3 files changed, 776 insertions(+), 3 deletions(-) create mode 100644 src/compas/geometry/transformations/helpers.py diff --git a/src/compas/geometry/primitives/__init__.py b/src/compas/geometry/primitives/__init__.py index 3fc329583436..c7ddd094d2ad 100644 --- a/src/compas/geometry/primitives/__init__.py +++ b/src/compas/geometry/primitives/__init__.py @@ -7,6 +7,7 @@ from .point import Point from .line import Line from .plane import Plane +from .quaternion import Quaternion from .frame import Frame from .polyline import Polyline @@ -14,7 +15,6 @@ from .circle import Circle from .curve import Bezier -from .quaternion import Quaternion from .shapes import * diff --git a/src/compas/geometry/primitives/primitive.py b/src/compas/geometry/primitives/primitive.py index 429c28b6062f..995ced3682bf 100644 --- a/src/compas/geometry/primitives/primitive.py +++ b/src/compas/geometry/primitives/primitive.py @@ -2,8 +2,6 @@ from __future__ import division from __future__ import print_function -from compas.geometry import Transformation - __all__ = ['Primitive'] diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py new file mode 100644 index 000000000000..959b9b4fe34d --- /dev/null +++ b/src/compas/geometry/transformations/helpers.py @@ -0,0 +1,775 @@ +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division + +import math + +from copy import deepcopy + +from compas.geometry.basic import normalize_vector +from compas.geometry.basic import cross_vectors +from compas.geometry.basic import dot_vectors +from compas.geometry.basic import multiply_matrix_vector +from compas.geometry.basic import multiply_matrices +from compas.geometry.basic import transpose_matrix +from compas.geometry.basic import norm_vector + +from compas.geometry.transformations import _EPS + +from compas.geometry.transformations.matrices import matrix_from_perspective_entries +from compas.geometry.transformations.matrices import matrix_from_translation +from compas.geometry.transformations.matrices import matrix_from_euler_angles +from compas.geometry.transformations.matrices import matrix_from_shear_entries +from compas.geometry.transformations.matrices import matrix_from_scale_factors + + +__all__ = [ + 'transform_points', + 'transform_points_numpy', + + 'transform_vectors', + 'transform_vectors_numpy', + + 'homogenize', + 'dehomogenize', + 'homogenize_numpy', + 'dehomogenize_numpy', + + 'local_axes', + 'local_coords_numpy', + 'global_coords_numpy', + + 'determinant', + 'inverse', + + 'compose_matrix', + 'decompose_matrix', +] + + +def transform_points(points, T): + """Transform multiple points with one Transformation. + + Parameters + ---------- + points : list of :class:`Point` + A list of points to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> points = [Point(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.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. + + Parameters + ---------- + vectors : list of :class:`Vector` + A list of vectors to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> vectors = [Vector(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.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. + + 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]), [[0,2,0], [5,2,1], [0,2,1]]] + >>> T = Rotation.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 transform_points_numpy(points, T): + """Transform multiple points with one Transformation using numpy. + + Parameters + ---------- + points : list of :class:`Point` + A list of points to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> points = [Point(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) + >>> points_transformed = transform_points_numpy(points, T) + """ + from numpy import asarray + T = asarray(T) + points = homogenize_numpy(points, w=1.0) + return dehomogenize_numpy(points.dot(T.T)) + + +def transform_vectors_numpy(vectors, T): + """Transform multiple vectors with one Transformation using numpy. + + Parameters + ---------- + vectors : list of :class:`Vector` + A list of vectors to be transformed. + T : :class:`Transformation` + The transformation to apply. + + Examples + -------- + >>> vectors = [Vector(1,0,0), (1,2,4), [4,7,1]] + >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) + >>> vectors_transformed = transform_vectors_numpy(vectors, T) + """ + from numpy import asarray + T = asarray(T) + vectors = homogenize_numpy(vectors, w=0.0) + return dehomogenize_numpy(vectors.dot(T.T)) + + +def transform_frames_numpy(frames, T): + """Transform multiple frames with one Transformation usig numpy. + + 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]), [[0,2,0], [5,2,1], [0,2,1]]] + >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) + >>> transformed_frames = transform_frames_numpy(frames, T) + """ + from numpy import asarray + T = asarray(T) + points_and_vectors = homogenize_and_flatten_frames_numpy(frames) + return dehomogenize_and_unflatten_frames_numpy(points_and_vectors.dot(T.T)) + + +# ============================================================================== +# helping helpers +# ============================================================================== + + +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 of float + A list of vectors. + + Returns + ------- + list of float + 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)] + + +def homogenize_and_flatten_frames_numpy(frames): + """Homogenize a list of frames and flatten the 3D list into a 2D list using numpy. + + The frame consists of a point and 2 orthonormal vectors. + + Parameters + ---------- + frames: list of :class:`Frame` + + Returns + ------- + :class:`numpy.ndarray` + An array of points and vectors. + + Examples + -------- + >>> import numpy as np + >>> frames = [Frame((1, 1, 1), (0, 1, 0), (1, 0, 0))] + >>> res = homogenize_and_flatten_frames_numpy(frames) + >>> np.allclose(res, [[1.0, 1.0, 1.0, 1.0], [0.0, 1.0, 0.0, 0.0], [1.0, -0.0, 0.0, 0.0]]) + True + """ + from numpy import asarray + from numpy import tile + from numpy import hstack + n = len(frames) + frames = asarray(frames).reshape(n * 3, 3) + extend = tile(asarray([1, 0, 0]).reshape(3, 1), (n, 1)) + return hstack((frames, extend)) + + +def dehomogenize_and_unflatten_frames_numpy(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 + ------- + :class:`numpy.ndarray` + The frames. + + Examples + -------- + >>> import numpy as np + >>> points_and_vectors = [(1., 1., 1., 1.), (0., 1., 0., 0.), (1., 0., 0., 0.)] + >>> res = dehomogenize_and_unflatten_frames_numpy(points_and_vectors) + >>> np.allclose(res, [[1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]) + True + """ + frames = dehomogenize_numpy(points_and_vectors) + return frames.reshape((int(frames.shape[0]/3.), 3, 3)) + + +def homogenize_numpy(points, w=1.0): + """Dehomogenizes points or vectors. + + Parameters + ---------- + points: list of :class:`Points` or list of :class:`Vectors` + + Returns + ------- + :class:`numpy.ndarray` + """ + from numpy import asarray + from numpy import hstack + from numpy import ones + + points = asarray(points) + points = hstack((points, w * ones((points.shape[0], 1)))) + return points + + +def dehomogenize_numpy(points): + """Dehomogenizes points or vectors. + + Parameters + ---------- + points: list of :class:`Points` or list of :class:`Vectors` + + Returns + ------- + :class:`numpy.ndarray` + """ + from numpy import asarray + from numpy import vectorize + + def func(a): + return a if a else 1. + func = vectorize(func) + + points = asarray(points) + return points[:, :-1] / func(points[:, -1]).reshape((-1, 1)) + + +# 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) + + +# this should be defined somewhere else +# and should have a python equivalent +# there is an implementation available in frame +def local_coords_numpy(origin, uvw, xyz): + """Convert global coordinates to local coordinates. + + Parameters + ---------- + origin : array-like + The global (XYZ) coordinates of the origin of the local coordinate system. + uvw : array-like + The global coordinate difference vectors of the axes of the local coordinate system. + xyz : array-like + The global coordinates of the points to convert. + + Returns + ------- + array + The coordinates of the given points in the local coordinate system. + + Notes + ----- + ``origin`` and ``uvw`` together form the frame of local coordinates. + + """ + from numpy import asarray + from scipy.linalg import solve + + uvw = asarray(uvw).T + xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) + rst = solve(uvw, xyz) + return rst.T + + +# this should be defined somewhere else +# and should have a python equivalent +# there is an implementation available in frame +def global_coords_numpy(origin, uvw, rst): + """Convert local coordinates to global (world) coordinates. + + Parameters + ---------- + origin : array-like + The origin of the local coordinate system. + uvw : array-like + The coordinate axes of the local coordinate system. + rst : array-like + The coordinates of the points wrt the local coordinate system. + + Returns + ------- + array + The world coordinates of the given points. + + Notes + ----- + ``origin`` and ``uvw`` together form the frame of local coordinates. + + """ + from numpy import asarray + + uvw = asarray(uvw).T + rst = asarray(rst).T + xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) + return xyz.T + + +def determinant(M, check=True): + """Calculates the determinant of a square matrix M. + + Parameters + ---------- + M : :obj:`list` of :obj:`list` of :obj:`float` + The square matrix of any dimension. + check : bool + If true checks if matrix is squared. Defaults to True. + + Raises + ------ + ValueError + If matrix is not a square matrix. + + Returns + ------- + float + The determinant. + + """ + dim = len(M) + + if check: + for c in M: + if len(c) != dim: + raise ValueError("Not a square matrix") + + if (dim == 2): + return M[0][0] * M[1][1] - M[0][1] * M[1][0] + else: + i = 1 + t = 0 + sum = 0 + for t in range(dim): + d = {} + for t1 in range(1, dim): + m = 0 + d[t1] = [] + for m in range(dim): + if (m != t): + d[t1].append(M[t1][m]) + M1 = [d[x] for x in d] + sum = sum + i * M[0][t] * determinant(M1, check=False) + i = i * (-1) + return sum + + +def inverse(M): + """Calculates the inverse of a square matrix M. + + Parameters + ---------- + M : :obj:`list` of :obj:`list` of :obj:`float` + The square matrix of any dimension. + + Raises + ------ + ValueError + If the matrix is not squared + ValueError + If the matrix is singular. + ValueError + If the matrix is not invertible. + + Returns + ------- + list of list of float + The inverted matrix. + + Examples + -------- + >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = matrix_from_frame(f) + >>> I = multiply_matrices(T, inverse(T)) + >>> I2 = identity_matrix(4) + >>> allclose(I[0], I2[0]) + True + >>> allclose(I[1], I2[1]) + True + >>> allclose(I[2], I2[2]) + True + >>> allclose(I[3], I2[3]) + True + + """ + def matrix_minor(m, i, j): + return [row[:j] + row[j + 1:] for row in (m[:i] + m[i + 1:])] + + detM = determinant(M) # raises ValueError if matrix is not squared + + if detM == 0: + ValueError("The matrix is singular.") + + if len(M) == 2: + return [[M[1][1] / detM, -1 * M[0][1] / detM], + [-1 * M[1][0] / detM, M[0][0] / detM]] + else: + cofactors = [] + for r in range(len(M)): + cofactor_row = [] + for c in range(len(M)): + minor = matrix_minor(M, r, c) + cofactor_row.append(((-1) ** (r + c)) * determinant(minor)) + cofactors.append(cofactor_row) + cofactors = transpose_matrix(cofactors) + for r in range(len(cofactors)): + for c in range(len(cofactors)): + cofactors[r][c] = cofactors[r][c] / detM + return cofactors + + +def decompose_matrix(M): + """Calculates the components of rotation, translation, scale, shear, and + perspective of a given transformation matrix M. + + Parameters + ---------- + M : :obj:`list` of :obj:`list` of :obj:`float` + The square matrix of any dimension. + + Raises + ------ + ValueError + If matrix is singular or degenerative. + + Returns + ------- + scale : :obj:`list` of :obj:`float` + The 3 scale factors in x-, y-, and z-direction. + shear : :obj:`list` of :obj:`float` + The 3 shear factors for x-y, x-z, and y-z axes. + angles : :obj:`list` of :obj:`float` + The rotation specified through the 3 Euler angles about static x, y, z axes. + translation : :obj:`list` of :obj:`float` + The 3 values of translation. + perspective : :obj:`list` of :obj:`float` + The 4 perspective entries of the matrix. + + Examples + -------- + >>> trans1 = [1, 2, 3] + >>> angle1 = [-2.142, 1.141, -0.142] + >>> scale1 = [0.123, 2, 0.5] + >>> T = matrix_from_translation(trans1) + >>> R = matrix_from_euler_angles(angle1) + >>> S = matrix_from_scale_factors(scale1) + >>> M = multiply_matrices(multiply_matrices(T, R), S) + >>> # M = compose_matrix(scale1, None, angle1, trans1, None) + >>> scale2, shear2, angle2, trans2, persp2 = decompose_matrix(M) + >>> allclose(scale1, scale2) + True + >>> allclose(angle1, angle2) + True + >>> allclose(trans1, trans2) + True + + References + ---------- + .. [1] Slabaugh, 1999. *Computing Euler angles from a rotation matrix*. + Available at: http://www.gregslabaugh.net/publications/euler.pdf + """ + + detM = determinant(M) # raises ValueError if matrix is not squared + + if detM == 0: + ValueError("The matrix is singular.") + + Mt = transpose_matrix(M) + + if abs(Mt[3][3]) < _EPS: + raise ValueError('The element [3,3] of the matrix is zero.') + + for i in range(4): + for j in range(4): + Mt[i][j] /= Mt[3][3] + + translation = [M[0][3], M[1][3], M[2][3]] + + # scale, shear, rotation + # copy Mt[:3, :3] into row + scale = [0.0, 0.0, 0.0] + shear = [0.0, 0.0, 0.0] + angles = [0.0, 0.0, 0.0] + + row = [[0, 0, 0] for i in range(3)] + for i in range(3): + for j in range(3): + row[i][j] = Mt[i][j] + + scale[0] = norm_vector(row[0]) + for i in range(3): + row[0][i] /= scale[0] + shear[0] = dot_vectors(row[0], row[1]) + for i in range(3): + row[1][i] -= row[0][i] * shear[0] + scale[1] = norm_vector(row[1]) + for i in range(3): + row[1][i] /= scale[1] + shear[0] /= scale[1] + shear[1] = dot_vectors(row[0], row[2]) + for i in range(3): + row[2][i] -= row[0][i] * shear[1] + shear[2] = dot_vectors(row[1], row[2]) + for i in range(3): + row[2][i] -= row[0][i] * shear[2] + scale[2] = norm_vector(row[2]) + for i in range(3): + row[2][i] /= scale[2] + shear[1] /= scale[2] + shear[2] /= scale[2] + + if dot_vectors(row[0], cross_vectors(row[1], row[2])) < 0: + scale = [-x for x in scale] + row = [[-x for x in y] for y in row] + + # angles + if row[0][2] != -1. and row[0][2] != 1.: + + beta1 = math.asin(-row[0][2]) + beta2 = math.pi - beta1 + + alpha1 = math.atan2(row[1][2] / math.cos(beta1), row[2][2] / math.cos(beta1)) + alpha2 = math.atan2(row[1][2] / math.cos(beta2), row[2][2] / math.cos(beta2)) + + gamma1 = math.atan2(row[0][1] / math.cos(beta1), row[0][0] / math.cos(beta1)) + gamma2 = math.atan2(row[0][1] / math.cos(beta2), row[0][0] / math.cos(beta2)) + + angles = [alpha1, beta1, gamma1] + # TODO: check for alpha2, beta2, gamma2 needed? + else: + gamma = 0. + if row[0][2] == -1.: + beta = math.pi / 2. + alpha = gamma + math.atan2(row[1][0], row[2][0]) + else: # row[0][2] == 1 + beta = -math.pi / 2. + alpha = -gamma + math.atan2(-row[1][0], -row[2][0]) + angles = [alpha, beta, gamma] + + # perspective + if math.fabs(Mt[0][3]) > _EPS and math.fabs(Mt[1][3]) > _EPS and math.fabs(Mt[2][3]) > _EPS: + P = deepcopy(Mt) + P[0][3], P[1][3], P[2][3], P[3][3] = 0.0, 0.0, 0.0, 1.0 + Ptinv = inverse(transpose_matrix(P)) + perspective = multiply_matrix_vector(Ptinv, [Mt[0][3], Mt[1][3], + Mt[2][3], Mt[3][3]]) + else: + perspective = [0.0, 0.0, 0.0, 1.0] + + return scale, shear, angles, translation, perspective + + +def compose_matrix(scale=None, shear=None, angles=None, + translation=None, perspective=None): + """Calculates a matrix from the components of scale, shear, euler_angles, + translation and perspective. + + Parameters + ---------- + scale : :obj:`list` of :obj:`float` + The 3 scale factors in x-, y-, and z-direction. + shear : :obj:`list` of :obj:`float` + The 3 shear factors for x-y, x-z, and y-z axes. + angles : :obj:`list` of :obj:`float` + The rotation specified through the 3 Euler angles about static x, y, z axes. + translation : :obj:`list` of :obj:`float` + The 3 values of translation. + perspective : :obj:`list` of :obj:`float` + The 4 perspective entries of the matrix. + + Examples + -------- + >>> trans1 = [1, 2, 3] + >>> angle1 = [-2.142, 1.141, -0.142] + >>> scale1 = [0.123, 2, 0.5] + >>> M = compose_matrix(scale1, None, angle1, trans1, None) + >>> scale2, shear2, angle2, trans2, persp2 = decompose_matrix(M) + >>> allclose(scale1, scale2) + True + >>> allclose(angle1, angle2) + True + >>> allclose(trans1, trans2) + True + + """ + M = [[1. if i == j else 0. for i in range(4)] for j in range(4)] + if perspective is not None: + P = matrix_from_perspective_entries(perspective) + M = multiply_matrices(M, P) + if translation is not None: + T = matrix_from_translation(translation) + M = multiply_matrices(M, T) + if angles is not None: + R = matrix_from_euler_angles(angles, static=True, axes="xyz") + M = multiply_matrices(M, R) + if shear is not None: + Sh = matrix_from_shear_entries(shear) + M = multiply_matrices(M, Sh) + if scale is not None: + Sc = matrix_from_scale_factors(scale) + M = multiply_matrices(M, Sc) + for i in range(4): + for j in range(4): + M[i][j] /= M[3][3] + return M + + +# ============================================================================== +# Main +# ============================================================================== + +if __name__ == "__main__": + import math + import doctest + from compas.geometry import allclose + from compas.geometry import matrix_from_frame + from compas.geometry import identity_matrix + from compas.geometry import Point + from compas.geometry import Vector + from compas.geometry import Frame + from compas.geometry import Transformation + from compas.geometry import Rotation + doctest.testmod(globs=globals()) From ac8a20ea18ae276086848b58f6c07b3428480977 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Wed, 9 Oct 2019 16:11:57 +0200 Subject: [PATCH 34/73] adding correct_axis_vectors --- .../geometry/transformations/helpers.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index 959b9b4fe34d..b42d8dde955b 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -382,6 +382,36 @@ def local_axes(a, b, c): return normalize_vector(u), normalize_vector(v), normalize_vector(w) +def correct_axis_vectors(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. + + Examples + -------- + >>> xaxis = [1, 4, 5] + >>> yaxis = [1, 0, -2] + >>> xaxis, yaxis = correct_axis_vectors(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 + """ + # TODO use this in Frame + xaxis = normalize_vector(xaxis) + yaxis = normalize_vector(yaxis) + zaxis = normalize_vector(cross_vectors(xaxis, yaxis)) + yaxis = cross_vectors(zaxis, xaxis) + return xaxis, yaxis + # this should be defined somewhere else # and should have a python equivalent # there is an implementation available in frame @@ -406,6 +436,17 @@ def local_coords_numpy(origin, uvw, xyz): ----- ``origin`` and ``uvw`` together form the frame of local coordinates. + Examples + -------- + >>> import numpy as np + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> xyz = [Point(2, 3, 5)] + >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> np.allclose(rst, [[3.72620657, 4.08804176, 1.55025779]]) + True + >>> f.represent_point_in_local_coordinates(xyz[0]) + Point(3.726, 4.088, 1.550) """ from numpy import asarray from scipy.linalg import solve @@ -440,6 +481,15 @@ def global_coords_numpy(origin, uvw, rst): ----- ``origin`` and ``uvw`` together form the frame of local coordinates. + Examples + -------- + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> xyz = [Point(2, 3, 5)] + >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> xyz2 = global_coords_numpy(origin, uvw, rst) + >>> numpy.allclose(xyz, xyz2) + True """ from numpy import asarray @@ -764,6 +814,7 @@ def compose_matrix(scale=None, shear=None, angles=None, if __name__ == "__main__": import math import doctest + import numpy from compas.geometry import allclose from compas.geometry import matrix_from_frame from compas.geometry import identity_matrix From e9a42bfc38ee07d78cd9de5ad57c1b8d2d7f3ef1 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Thu, 10 Oct 2019 17:28:20 +0200 Subject: [PATCH 35/73] adding matrix_from_frame_to_frame and matrix_change_basis --- .../geometry/transformations/helpers.py | 25 +++++++- .../geometry/transformations/matrices.py | 62 ++++++++++++++++++- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index b42d8dde955b..2752d0a065f4 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -395,6 +395,10 @@ def correct_axis_vectors(xaxis, yaxis): tuple: (xaxis, yaxis) The corrected axes. + Raises + ------ + ValueError: If xaxis and yaxis cannot span a plane. + Examples -------- >>> xaxis = [1, 4, 5] @@ -408,13 +412,30 @@ def correct_axis_vectors(xaxis, yaxis): # TODO use this in Frame xaxis = normalize_vector(xaxis) yaxis = normalize_vector(yaxis) - zaxis = normalize_vector(cross_vectors(xaxis, yaxis)) - yaxis = cross_vectors(zaxis, xaxis) + 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 # this should be defined somewhere else # and should have a python equivalent # there is an implementation available in frame + +def local_coords(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. + """ + #T = Transformation.change_basis(Frame.worldXY(), self) + pass + + def local_coords_numpy(origin, uvw, xyz): """Convert global coordinates to local coordinates. diff --git a/src/compas/geometry/transformations/matrices.py b/src/compas/geometry/transformations/matrices.py index 4fbcf1e9ce66..6f54dba6383b 100644 --- a/src/compas/geometry/transformations/matrices.py +++ b/src/compas/geometry/transformations/matrices.py @@ -14,14 +14,12 @@ from compas.geometry.basic import multiply_matrix_vector from compas.geometry.basic import length_vector from compas.geometry.basic import allclose -from compas.geometry.basic import transpose_matrix from compas.geometry.basic import multiply_matrices -from compas.geometry.basic import norm_vector from compas.geometry.transformations import _EPS from compas.geometry.transformations import _SPEC2TUPLE from compas.geometry.transformations import _NEXT_SPEC - +from compas.geometry.transformations import inverse __all__ = [ 'matrix_determinant', @@ -396,6 +394,64 @@ def matrix_from_frame(frame): M[0][3], M[1][3], M[2][3] = frame.point return M +def matrix_from_frame_to_frame(frame_from, frame_to): + """Computes a transformation between two frames. + + This transformation allows to transform geometry from one Cartesian + coordinate system defined by "frame_from" to another Cartesian + coordinate system defined by "frame_to". + + Parameters + ---------- + frame_from : :class:`Frame` + A frame defining the original Cartesian coordinate system + frame_to : :class:`Frame` + A frame defining the targeted Cartesian coordinate system + + Examples + -------- + >>> from compas.geometry import Frame + >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) + >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = matrix_from_frame_to_frame(frame_from, frame_to) + >>> f1.transform(T) + >>> f1 == f2 + True + """ + T1 = matrix_from_frame(frame_from) + T2 = matrix_from_frame(frame_to) + return multiply_matrices(T2, inverse(T1)) + +def matrix_change_basis(frame_from, frame_to): + """Computes a change of basis transformation between two frames. + + A basis change is essentially a remapping of geometry from one + coordinate system to another. + + Parameters + ---------- + frame_from : :class:`Frame` + A frame defining the original Cartesian coordinate system + frame_to : :class:`Frame` + A frame defining the targeted Cartesian coordinate system + + Example: + >>> from compas.geometry import Point, Frame + >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) + >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> T = change_basis(f1, f2) + >>> p_f1 = Point(1, 1, 1) # point in f1 + >>> p_f2 = p_f1.transformed(T) # same point represented in f2 + >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates + >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates + >>> print(p_w1) + Point(0.733, 2.492, 3.074) + >>> print(p_w2) + Point(0.733, 2.492, 3.074) + """ + T1 = matrix_from_frame(frame_from) + T2 = matrix_from_frame(frame_to) + return multiply_matrices(inverse(T2), T1) def matrix_from_euler_angles(euler_angles, static=True, axes='xyz'): """Calculates a rotation matrix from Euler angles. From c95973e1b726c1b2c5ab448692d52a524273696b Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Fri, 11 Oct 2019 16:31:56 +0200 Subject: [PATCH 36/73] Update matrices.py --- .../geometry/transformations/matrices.py | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/compas/geometry/transformations/matrices.py b/src/compas/geometry/transformations/matrices.py index 6f54dba6383b..a49df0bc08ad 100644 --- a/src/compas/geometry/transformations/matrices.py +++ b/src/compas/geometry/transformations/matrices.py @@ -19,7 +19,6 @@ from compas.geometry.transformations import _EPS from compas.geometry.transformations import _SPEC2TUPLE from compas.geometry.transformations import _NEXT_SPEC -from compas.geometry.transformations import inverse __all__ = [ 'matrix_determinant', @@ -410,14 +409,11 @@ def matrix_from_frame_to_frame(frame_from, frame_to): Examples -------- - >>> from compas.geometry import Frame >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> T = matrix_from_frame_to_frame(frame_from, frame_to) - >>> f1.transform(T) - >>> f1 == f2 - True + >>> T = matrix_from_frame_to_frame(f1, f2) """ + from compas.geometry.transformations import inverse T1 = matrix_from_frame(frame_from) T2 = matrix_from_frame(frame_to) return multiply_matrices(T2, inverse(T1)) @@ -439,16 +435,9 @@ def matrix_change_basis(frame_from, frame_to): >>> from compas.geometry import Point, Frame >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> T = change_basis(f1, f2) - >>> p_f1 = Point(1, 1, 1) # point in f1 - >>> p_f2 = p_f1.transformed(T) # same point represented in f2 - >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates - >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates - >>> print(p_w1) - Point(0.733, 2.492, 3.074) - >>> print(p_w2) - Point(0.733, 2.492, 3.074) + >>> T = matrix_change_basis(f1, f2) """ + from compas.geometry.transformations import inverse T1 = matrix_from_frame(frame_from) T2 = matrix_from_frame(frame_to) return multiply_matrices(inverse(T2), T1) @@ -1337,5 +1326,7 @@ def axis_angle_from_quaternion(q): # ============================================================================== if __name__ == "__main__": - - pass + import doctest + from compas.geometry import Frame + from compas.geometry import Transformation + doctest.testmod(globs=globals()) From 35cf920cad905043e1c630f87fc022c3eef143dd Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 12:57:56 +0200 Subject: [PATCH 37/73] represent_(frames\points\vectors)_in_(local\global)_coordinate_system with local\global_coordinates --- src/compas/geometry/primitives/frame.py | 175 +++++------------------- 1 file changed, 33 insertions(+), 142 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index e9bea43a3fd1..15c2ffa979ff 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -649,180 +649,71 @@ def euler_angles(self, static=True, axes='xyz'): R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return euler_angles_from_matrix(R, static, axes) - def represent_point_in_local_coordinates(self, point): - """Represents a point in the frame's local coordinate system. + def local_coordinates(self, coords_wcs): + """Returns the object's coordinates in the frame's local coordinate system. Parameters ---------- - point : :obj:`list` of :obj:`float` or :class:`Point` - A point in world XY. + coords_wcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + A coordinate object in world XY. Returns ------- :class:`Point` A point in the local coordinate system of the frame. + Notes + ----- + If you pass a list of float, it is assumed to represent a point. + Examples -------- >>> from compas.geometry import Point, Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = Point(2, 2, 2) - >>> pf = f.represent_point_in_local_coordinates(pw1) - >>> pw2 = f.represent_point_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - + >>> pw = Point(2, 2, 2) + >>> pl = f.local_coordinates(pw) + >>> f.global_coordinates(pl) + Point(2.000, 2.000, 2.000) """ - point = Point(*point) T = Transformation.change_basis(Frame.worldXY(), self) - return point.transformed(T) - - def represent_point_in_global_coordinates(self, point): - """Represents a point from local coordinates in the world coordinate system. - - Parameters - ---------- - point : :obj:`list` of :obj:`float` or :class:`Point` - A point in local coordinates. - - Returns - ------- - :class:`Point` - A point in the world coordinate system. - - Examples - -------- - >>> from compas.geometry import Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] - >>> pf = f.represent_point_in_local_coordinates(pw1) - >>> pw2 = f.represent_point_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - - """ - point = Point(*point) - T = Transformation.change_basis(self, Frame.worldXY()) - return point.transformed(T) + if isinstance(coords_wcs, list): + point = Point(*coords_wcs) + return point.transformed(T) + else: + return coords_wcs.transformed(T) - def represent_vector_in_local_coordinates(self, vector): - """Represents a vector in the frame's local coordinate system. + def global_coordinates(self, coords_lcs): + """Returns the frame's object's coordinates in the global coordinate system. Parameters ---------- - vector : :obj:`list` of :obj:`float` or :class:`Vector` - A vector in world XY. + coords_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + A coordinate object in the frames coordinate system. Returns ------- :class:`Vector` A vector in the local coordinate system of the frame. - Examples - -------- - >>> from compas.geometry import Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] - >>> pf = f.represent_vector_in_local_coordinates(pw1) - >>> pw2 = f.represent_vector_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - - """ - vector = Vector(*vector) - T = Transformation.change_basis(Frame.worldXY(), self) - return vector.transformed(T) - - def represent_vector_in_global_coordinates(self, vector): - """Represents a vector in local coordinates in the world coordinate system. - - Parameters - ---------- - vector: :obj:`list` of :obj:`float` or :class:`Vector` - A vector in local coordinates. - - Returns - ------- - :class:`Vector` - A vector in the world coordinate system. - - Examples - -------- - >>> from compas.geometry import Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = [2, 2, 2] - >>> pf = f.represent_vector_in_local_coordinates(pw1) - >>> pw2 = f.represent_vector_in_global_coordinates(pf) - >>> allclose(pw1, pw2) - True - - """ - vector = Vector(*vector) - T = Transformation.change_basis(self, Frame.worldXY()) - return vector.transformed(T) - - def represent_frame_in_local_coordinates(self, frame): - """Represents another frame in the frame's local coordinate system. - - Parameters - ---------- - frame: :class:`Frame` - A frame in the world coordinate system. - - Returns - ------- - :class:`Frame` - A frame in the frame's local coordinate system. - - Examples - -------- - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = Frame([1, 1, 1], [0.707, 0.707, 0], [-0.707, 0.707, 0]) - >>> pf = f.represent_frame_in_local_coordinates(pw1) - >>> pw2 = f.represent_frame_in_global_coordinates(pf) - >>> allclose(pw1.point, pw2.point) - True - >>> allclose(pw1.xaxis, pw2.xaxis) - True - >>> allclose(pw1.yaxis, pw2.yaxis) - True - - """ - T = Transformation.change_basis(Frame.worldXY(), self) - return frame.transformed(T) - - - def represent_frame_in_global_coordinates(self, frame): - """Represents another frame in the local coordinate system in the world - coordinate system. - - Parameters - ---------- - frame: :class:`Frame` - A frame in the local coordinate system. - - Returns - ------- - :class:`Frame` - A frame in the world coordinate system. + Notes + ----- + If you pass a list of float, it is assumed to represent a point. Examples -------- >>> from compas.geometry import Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw1 = Frame([1, 1, 1], [0.707, 0.707, 0], [-0.707, 0.707, 0]) - >>> pf = f.represent_frame_in_local_coordinates(pw1) - >>> pw2 = f.represent_frame_in_global_coordinates(pf) - >>> allclose(pw1.point, pw2.point) - True - >>> allclose(pw1.xaxis, pw2.xaxis) - True - >>> allclose(pw1.yaxis, pw2.yaxis) - True - + >>> pl = Point(1.632, -0.090, 0.573) + >>> pw = f.global_coordinates(pl) + >>> f.local_coordinates(pw) + Point(1.632, -0.090, 0.573) """ T = Transformation.change_basis(self, Frame.worldXY()) - return frame.transformed(T) + if isinstance(coords_lcs, list): + point = Point(*coords_lcs) + return point.transformed(T) + else: + return coords_lcs.transformed(T) # ========================================================================== # transformations From afcf5b29aa5a7fac84136811c0dcc4e879c5f3d9 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 13:28:41 +0200 Subject: [PATCH 38/73] adding python equivalent of local/global_coords_numpy --- .../geometry/transformations/helpers.py | 67 ++++++++++++++----- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index 2752d0a065f4..bf97cc5b5bb4 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -21,7 +21,7 @@ from compas.geometry.transformations.matrices import matrix_from_euler_angles from compas.geometry.transformations.matrices import matrix_from_shear_entries from compas.geometry.transformations.matrices import matrix_from_scale_factors - +from compas.geometry.transformations.matrices import matrix_change_basis __all__ = [ 'transform_points', @@ -418,9 +418,7 @@ def correct_axis_vectors(xaxis, yaxis): yaxis = cross_vectors(normalize_vector(zaxis), xaxis) return xaxis, yaxis -# this should be defined somewhere else -# and should have a python equivalent -# there is an implementation available in frame + def local_coords(frame, xyz): """Convert global coordinates to local coordinates. @@ -431,9 +429,24 @@ def local_coords(frame, xyz): 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(*local_coords(f, xyz)[0]) + Point(3.726, 4.088, 1.550) """ - #T = Transformation.change_basis(Frame.worldXY(), self) - pass + T = matrix_change_basis(Frame.worldXY(), frame) + return transform_points(xyz, T) + def local_coords_numpy(origin, uvw, xyz): @@ -464,10 +477,8 @@ def local_coords_numpy(origin, uvw, xyz): >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] >>> xyz = [Point(2, 3, 5)] >>> rst = local_coords_numpy(origin, uvw, xyz) - >>> np.allclose(rst, [[3.72620657, 4.08804176, 1.55025779]]) + >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) True - >>> f.represent_point_in_local_coordinates(xyz[0]) - Point(3.726, 4.088, 1.550) """ from numpy import asarray from scipy.linalg import solve @@ -478,9 +489,34 @@ def local_coords_numpy(origin, uvw, xyz): return rst.T -# this should be defined somewhere else -# and should have a python equivalent -# there is an implementation available in frame +def global_coords(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(*global_coords(f, xyz)[0]) + Point(2.000, 3.000, 5.000) + """ + T = matrix_change_basis(frame, Frame.worldXY()) + return transform_points(xyz, T) + + def global_coords_numpy(origin, uvw, rst): """Convert local coordinates to global (world) coordinates. @@ -506,10 +542,9 @@ def global_coords_numpy(origin, uvw, rst): -------- >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] - >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(origin, uvw, xyz) - >>> xyz2 = global_coords_numpy(origin, uvw, rst) - >>> numpy.allclose(xyz, xyz2) + >>> rst = [Point(3.726, 4.088, 1.550)] + >>> xyz = global_coords_numpy(origin, uvw, rst) + >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) True """ from numpy import asarray From b4fb2fbb889cab7f45c12159b2564fcf275f4e7f Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 13:28:58 +0200 Subject: [PATCH 39/73] change coordinates to coords --- src/compas/geometry/primitives/frame.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 15c2ffa979ff..82c7571c24a4 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -649,7 +649,7 @@ def euler_angles(self, static=True, axes='xyz'): R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return euler_angles_from_matrix(R, static, axes) - def local_coordinates(self, coords_wcs): + def local_coords(self, coords_wcs): """Returns the object's coordinates in the frame's local coordinate system. Parameters @@ -671,8 +671,8 @@ def local_coordinates(self, coords_wcs): >>> from compas.geometry import Point, Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pw = Point(2, 2, 2) - >>> pl = f.local_coordinates(pw) - >>> f.global_coordinates(pl) + >>> pl = f.local_coords(pw) + >>> f.global_coords(pl) Point(2.000, 2.000, 2.000) """ T = Transformation.change_basis(Frame.worldXY(), self) @@ -682,7 +682,7 @@ def local_coordinates(self, coords_wcs): else: return coords_wcs.transformed(T) - def global_coordinates(self, coords_lcs): + def global_coords(self, coords_lcs): """Returns the frame's object's coordinates in the global coordinate system. Parameters @@ -704,8 +704,8 @@ def global_coordinates(self, coords_lcs): >>> from compas.geometry import Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pl = Point(1.632, -0.090, 0.573) - >>> pw = f.global_coordinates(pl) - >>> f.local_coordinates(pw) + >>> pw = f.global_coords(pl) + >>> f.local_coords(pw) Point(1.632, -0.090, 0.573) """ T = Transformation.change_basis(self, Frame.worldXY()) From ab592a7381c8572ba838eb412a122555c38d4832 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 14:06:41 +0200 Subject: [PATCH 40/73] moving methods from helpers into coordinate_systems --- .../geometry/transformations/__init__.py | 4 + .../transformations/coordinate_systems.py | 217 ++++++++++++++++++ .../geometry/transformations/helpers.py | 193 +--------------- 3 files changed, 224 insertions(+), 190 deletions(-) create mode 100644 src/compas/geometry/transformations/coordinate_systems.py diff --git a/src/compas/geometry/transformations/__init__.py b/src/compas/geometry/transformations/__init__.py index 7e0bbbff6280..9405ccf08b5c 100644 --- a/src/compas/geometry/transformations/__init__.py +++ b/src/compas/geometry/transformations/__init__.py @@ -21,6 +21,10 @@ _NEXT_SPEC = [1, 2, 0, 1] +# todo: separate the numpy version inot separate modules + +from .helpers import * +from .coordinate_systems import * from .matrices import * # migrated from xforms diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py new file mode 100644 index 000000000000..3a6c58c7317c --- /dev/null +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -0,0 +1,217 @@ +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division + +import math + +from compas.geometry.basic import normalize_vector +from compas.geometry.basic import cross_vectors +from compas.geometry.basic import norm_vector + +from compas.geometry.transformations.matrices import matrix_change_basis +from compas.geometry.transformations.helpers import transform_points + +__all__ = [ + 'local_axes', + 'correct_axis_vectors', + 'local_coords', + 'local_coords_numpy', + 'global_coords', + 'global_coords_numpy', +] + + +# this function will not always work +# it is also a duplicate of stuff found in matrices and frame +def local_axes(a, b, c): + # TODO: is this used somewhere? + u = b - a + v = c - a + u, v = correct_axis_vectors(v, v) + w = cross_vectors(u, v) + return u, v, w + + +def correct_axis_vectors(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 = correct_axis_vectors(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 + """ + # TODO use this in Frame + 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 local_coords(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(*local_coords(f, xyz)[0]) + Point(3.726, 4.088, 1.550) + """ + from compas.geometry.primitives import Frame + T = matrix_change_basis(Frame.worldXY(), frame) + return transform_points(xyz, T) + + +def local_coords_numpy(origin, uvw, xyz): + """Convert global coordinates to local coordinates. + + Parameters + ---------- + origin : array-like + The global (XYZ) coordinates of the origin of the local coordinate system. + uvw : array-like + The global coordinate difference vectors of the axes of the local coordinate system. + xyz : array-like + The global coordinates of the points to convert. + + Returns + ------- + array + The coordinates of the given points in the local coordinate system. + + Notes + ----- + ``origin`` and ``uvw`` together form the frame of local coordinates. + + Examples + -------- + >>> import numpy as np + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> xyz = [Point(2, 3, 5)] + >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) + True + """ + from numpy import asarray + from scipy.linalg import solve + + uvw = asarray(uvw).T + xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) + rst = solve(uvw, xyz) + return rst.T + + +def global_coords(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(*global_coords(f, xyz)[0]) + Point(2.000, 3.000, 5.000) + """ + T = matrix_change_basis(frame, Frame.worldXY()) + return transform_points(xyz, T) + + +def global_coords_numpy(origin, uvw, rst): + """Convert local coordinates to global (world) coordinates. + + Parameters + ---------- + origin : array-like + The origin of the local coordinate system. + uvw : array-like + The coordinate axes of the local coordinate system. + rst : array-like + The coordinates of the points wrt the local coordinate system. + + Returns + ------- + array + The world coordinates of the given points. + + Notes + ----- + ``origin`` and ``uvw`` together form the frame of local coordinates. + + Examples + -------- + >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> rst = [Point(3.726, 4.088, 1.550)] + >>> xyz = global_coords_numpy(origin, uvw, rst) + >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) + True + """ + from numpy import asarray + + uvw = asarray(uvw).T + rst = asarray(rst).T + xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) + return xyz.T + +# ============================================================================== +# Main +# ============================================================================== + + +if __name__ == "__main__": + import doctest + import numpy + from compas.geometry import Point + from compas.geometry import Frame + from compas.geometry import allclose + doctest.testmod(globs=globals()) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py index bf97cc5b5bb4..433f0f2a3817 100644 --- a/src/compas/geometry/transformations/helpers.py +++ b/src/compas/geometry/transformations/helpers.py @@ -6,7 +6,6 @@ from copy import deepcopy -from compas.geometry.basic import normalize_vector from compas.geometry.basic import cross_vectors from compas.geometry.basic import dot_vectors from compas.geometry.basic import multiply_matrix_vector @@ -21,7 +20,6 @@ from compas.geometry.transformations.matrices import matrix_from_euler_angles from compas.geometry.transformations.matrices import matrix_from_shear_entries from compas.geometry.transformations.matrices import matrix_from_scale_factors -from compas.geometry.transformations.matrices import matrix_change_basis __all__ = [ 'transform_points', @@ -30,15 +28,14 @@ 'transform_vectors', 'transform_vectors_numpy', + 'transform_frames', + 'transform_frames_numpy', + 'homogenize', 'dehomogenize', 'homogenize_numpy', 'dehomogenize_numpy', - 'local_axes', - 'local_coords_numpy', - 'global_coords_numpy', - 'determinant', 'inverse', @@ -372,189 +369,6 @@ def func(a): return points[:, :-1] / func(points[:, -1]).reshape((-1, 1)) -# 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 correct_axis_vectors(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 = correct_axis_vectors(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 - """ - # TODO use this in Frame - 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 local_coords(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(*local_coords(f, xyz)[0]) - Point(3.726, 4.088, 1.550) - """ - T = matrix_change_basis(Frame.worldXY(), frame) - return transform_points(xyz, T) - - - -def local_coords_numpy(origin, uvw, xyz): - """Convert global coordinates to local coordinates. - - Parameters - ---------- - origin : array-like - The global (XYZ) coordinates of the origin of the local coordinate system. - uvw : array-like - The global coordinate difference vectors of the axes of the local coordinate system. - xyz : array-like - The global coordinates of the points to convert. - - Returns - ------- - array - The coordinates of the given points in the local coordinate system. - - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - - Examples - -------- - >>> import numpy as np - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] - >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(origin, uvw, xyz) - >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) - True - """ - from numpy import asarray - from scipy.linalg import solve - - uvw = asarray(uvw).T - xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) - rst = solve(uvw, xyz) - return rst.T - - -def global_coords(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(*global_coords(f, xyz)[0]) - Point(2.000, 3.000, 5.000) - """ - T = matrix_change_basis(frame, Frame.worldXY()) - return transform_points(xyz, T) - - -def global_coords_numpy(origin, uvw, rst): - """Convert local coordinates to global (world) coordinates. - - Parameters - ---------- - origin : array-like - The origin of the local coordinate system. - uvw : array-like - The coordinate axes of the local coordinate system. - rst : array-like - The coordinates of the points wrt the local coordinate system. - - Returns - ------- - array - The world coordinates of the given points. - - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - - Examples - -------- - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] - >>> rst = [Point(3.726, 4.088, 1.550)] - >>> xyz = global_coords_numpy(origin, uvw, rst) - >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) - True - """ - from numpy import asarray - - uvw = asarray(uvw).T - rst = asarray(rst).T - xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) - return xyz.T - - def determinant(M, check=True): """Calculates the determinant of a square matrix M. @@ -868,7 +682,6 @@ def compose_matrix(scale=None, shear=None, angles=None, # ============================================================================== if __name__ == "__main__": - import math import doctest import numpy from compas.geometry import allclose From e2f507a8eeeeebb75ec03ffa60f1b10a053445b5 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 14:51:57 +0200 Subject: [PATCH 41/73] adding possiblility of passing another cs than worldXY --- src/compas/geometry/primitives/frame.py | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 82c7571c24a4..2ba1338fc4b8 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -649,13 +649,16 @@ def euler_angles(self, static=True, axes='xyz'): R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return euler_angles_from_matrix(R, static, axes) - def local_coords(self, coords_wcs): + def local_coords(self, coords_rcs, rcs=None): """Returns the object's coordinates in the frame's local coordinate system. Parameters ---------- - coords_wcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float - A coordinate object in world XY. + coords_rcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + Coordinates in world XY or rcs. + rcs : :class:`Frame`, optional + The other coordinate system, defaults to world XY. If rcs is not `None`, + coords_rcs are assumed to be in rcs. Returns ------- @@ -675,20 +678,23 @@ def local_coords(self, coords_wcs): >>> f.global_coords(pl) Point(2.000, 2.000, 2.000) """ - T = Transformation.change_basis(Frame.worldXY(), self) - if isinstance(coords_wcs, list): - point = Point(*coords_wcs) - return point.transformed(T) + if not rcs: + rcs = Frame.worldXY() + T = Transformation.change_basis(rcs, self) + if isinstance(coords_rcs, list): + return Point(*coords_rcs).transformed(T) else: - return coords_wcs.transformed(T) + return coords_rcs.transformed(T) - def global_coords(self, coords_lcs): + def global_coords(self, coords_lcs, rcs=None): """Returns the frame's object's coordinates in the global coordinate system. Parameters ---------- - coords_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + coords_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float A coordinate object in the frames coordinate system. + rcs : :class:`Frame`, optional + The other coordinate system, defaults to world XY. Returns ------- @@ -708,10 +714,11 @@ def global_coords(self, coords_lcs): >>> f.local_coords(pw) Point(1.632, -0.090, 0.573) """ - T = Transformation.change_basis(self, Frame.worldXY()) + if not rcs: + rcs = Frame.worldXY() + T = Transformation.change_basis(self, rcs) if isinstance(coords_lcs, list): - point = Point(*coords_lcs) - return point.transformed(T) + return Point(*coords_lcs).transformed(T) else: return coords_lcs.transformed(T) From 9fa984dec6f222450e5934492a6f7602d2176f00 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 14:52:14 +0200 Subject: [PATCH 42/73] fixing examples --- .../transformations/transformations.py | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/compas/geometry/transformations/transformations.py b/src/compas/geometry/transformations/transformations.py index 46b3c3eb65c3..33e94c01354e 100644 --- a/src/compas/geometry/transformations/transformations.py +++ b/src/compas/geometry/transformations/transformations.py @@ -611,7 +611,7 @@ def project_point_plane(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, 3.0] + [3.0, 3.0, 0.0] """ base, normal = plane @@ -853,7 +853,7 @@ def reflect_line_triangle(line, triangle, tol=1e-6): >>> 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], [1.0, 1.0, 0]) + ([0.0, 0.0, 0.0], [1.0, 1.0, 0.0]) """ x = intersection_line_triangle(line, triangle, tol=tol) @@ -907,32 +907,29 @@ def orient_points(points, reference_plane, target_plane): Examples -------- - .. code-block:: python - - 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] + >>> 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) - - print(points[0]) - + >>> + >>> 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]) @@ -953,4 +950,8 @@ def orient_points(points, reference_plane, target_plane): if __name__ == "__main__": - pass + import doctest + from compas.geometry import allclose + from compas.geometry import Point + doctest.testmod(globs=globals()) + From d5c283cd0351cbbe6d6fd64155b9964451dbd02b Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:16:17 +0200 Subject: [PATCH 43/73] changing global/local_coords_numpy to use frame instead of origin, uvw --- src/compas/geometry/bbox/bbox_numpy.py | 40 +-- src/compas/geometry/bestfit/bestfit_numpy.py | 5 +- .../transformations/coordinate_systems.py | 25 +- src/compas_viewers/core/drawing.py | 332 ++++++++++++++++++ 4 files changed, 358 insertions(+), 44 deletions(-) create mode 100644 src/compas_viewers/core/drawing.py diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index 1822cb712265..041e7cc687ea 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -15,7 +15,7 @@ from scipy.spatial import ConvexHull # from scipy.spatial import QhullError -from compas.geometry import local_axes +from compas.geometry import correct_axes from compas.geometry import local_coords_numpy from compas.geometry import global_coords_numpy @@ -85,8 +85,8 @@ def oriented_bounding_box_numpy(points): >>> a = length_vector(subtract_vectors(bbox[1], bbox[0])) >>> b = length_vector(subtract_vectors(bbox[3], bbox[0])) >>> c = length_vector(subtract_vectors(bbox[4], bbox[0])) - >>> a * b * c - 30.0 + >>> allclose([a * b * c], [30.]) + True """ points = asarray(points) @@ -112,9 +112,10 @@ def oriented_bounding_box_numpy(points): # this can be vectorised! for simplex in hull.simplices: a, b, c = points[simplex] - uvw = local_axes(a, b, c) + u, v = correct_axes(b - a, c - a) xyz = points[hull.vertices] - rst = local_coords_numpy(a, uvw, xyz) + frame = [a, u, v] + rst = local_coords_numpy(frame, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt @@ -131,7 +132,7 @@ def oriented_bounding_box_numpy(points): [rmax, smax, tmax], [rmin, smax, tmax], ] - bbox = global_coords_numpy(a, uvw, bbox) + bbox = global_coords_numpy(frame, bbox) volume = v return bbox @@ -246,28 +247,5 @@ def oriented_bounding_box_xy_numpy(points): from compas.geometry import transform_points_numpy from compas.geometry import allclose - points = numpy.random.rand(10000, 3) - bottom = numpy.array([[0.0,0.0,0.0], [1.0,0.0,0.0], [0.0,1.0,0.0], [1.0,1.0,0.0]]) - top = numpy.array([[0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) - points = numpy.concatenate((points, bottom, top)) - points[:, 0] *= 10 - points[:, 2] *= 3 - - bbox = bounding_box(points) - a = length_vector(subtract_vectors(bbox[1], bbox[0])) - b = length_vector(subtract_vectors(bbox[3], bbox[0])) - c = length_vector(subtract_vectors(bbox[4], bbox[0])) - v1 = a * b * c - - R = Rotation.from_axis_and_angle([1.0, 1.0, 0.0], 0.5 * 3.14159) - points = transform_points_numpy(points, R.matrix) - - bbox = oriented_bounding_box_numpy(points) - - a = length_vector(subtract_vectors(bbox[1], bbox[0])) - b = length_vector(subtract_vectors(bbox[3], bbox[0])) - c = length_vector(subtract_vectors(bbox[4], bbox[0])) - v2 = a * b * c - - print(v1, v2) - print(allclose([v1], [v2])) + import doctest + doctest.testmod(globs=globals()) diff --git a/src/compas/geometry/bestfit/bestfit_numpy.py b/src/compas/geometry/bestfit/bestfit_numpy.py index 1b74668723ff..24499dc69789 100644 --- a/src/compas/geometry/bestfit/bestfit_numpy.py +++ b/src/compas/geometry/bestfit/bestfit_numpy.py @@ -151,7 +151,8 @@ def bestfit_circle_numpy(points): """ o, uvw, _ = pca_numpy(points) - rst = local_coords_numpy(o, uvw, points) + frame = [o, uvw[1], uvw[2]] + rst = local_coords_numpy(frame, points) x = rst[:, 0] y = rst[:, 1] @@ -172,7 +173,7 @@ def f(c): print(residu) - xyz = global_coords_numpy(o, uvw, [[c[0], c[1], 0.0]])[0] + xyz = global_coords_numpy(frame, [[c[0], c[1], 0.0]])[0] o = xyz.tolist() u, v, w = uvw.tolist() diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 3a6c58c7317c..0c9126968314 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -13,7 +13,7 @@ __all__ = [ 'local_axes', - 'correct_axis_vectors', + 'correct_axes', 'local_coords', 'local_coords_numpy', 'global_coords', @@ -27,12 +27,12 @@ def local_axes(a, b, c): # TODO: is this used somewhere? u = b - a v = c - a - u, v = correct_axis_vectors(v, v) + u, v = correct_axes(u, v) w = cross_vectors(u, v) return u, v, w -def correct_axis_vectors(xaxis, yaxis): +def correct_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. Parameters @@ -98,7 +98,7 @@ def local_coords(frame, xyz): return transform_points(xyz, T) -def local_coords_numpy(origin, uvw, xyz): +def local_coords_numpy(frame, xyz): """Convert global coordinates to local coordinates. Parameters @@ -122,16 +122,17 @@ def local_coords_numpy(origin, uvw, xyz): Examples -------- >>> import numpy as np - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(origin, uvw, xyz) + >>> rst = local_coords_numpy(frame, xyz) >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) True """ from numpy import asarray from scipy.linalg import solve + origin = frame[0] + uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] uvw = asarray(uvw).T xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) rst = solve(uvw, xyz) @@ -166,7 +167,7 @@ def global_coords(frame, xyz): return transform_points(xyz, T) -def global_coords_numpy(origin, uvw, rst): +def global_coords_numpy(frame, rst): """Convert local coordinates to global (world) coordinates. Parameters @@ -189,15 +190,17 @@ def global_coords_numpy(origin, uvw, rst): Examples -------- - >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> origin, uvw = f.point, [f.xaxis, f.yaxis, f.zaxis] + >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> rst = [Point(3.726, 4.088, 1.550)] - >>> xyz = global_coords_numpy(origin, uvw, rst) + >>> xyz = global_coords_numpy(frame, rst) >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) True """ from numpy import asarray + origin = frame[0] + uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] + uvw = asarray(uvw).T rst = asarray(rst).T xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) diff --git a/src/compas_viewers/core/drawing.py b/src/compas_viewers/core/drawing.py new file mode 100644 index 000000000000..274a45eb192a --- /dev/null +++ b/src/compas_viewers/core/drawing.py @@ -0,0 +1,332 @@ +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division + +from math import cos +from math import sin +from math import pi + +from OpenGL.GLUT import * +from OpenGL.GLU import * +from OpenGL.GLE import * +from OpenGL.GL import * + +from compas.geometry import normalize_vector +from compas.geometry import cross_vectors + +from compas.geometry import global_coords_numpy + + +__all__ = [ + 'draw_points', + 'draw_lines', + 'draw_faces', + 'draw_sphere', + 'draw_points', + 'draw_lines', + 'draw_polygons', + 'draw_cylinders', + 'draw_spheres', + 'draw_texts', +] + + +# ============================================================================== +# arrays +# ------ +# http://www.songho.ca/opengl/gl_vertexarray.html +# https://gist.github.com/ousttrue/c4ae334fc1505cdf4cd7 +# ============================================================================== + + +def draw_arrays(vertices, arrays): + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_COLOR_ARRAY) + + # vertex coordinates flattened + glVertexPointer(3, GL_FLOAT, 0, vertices) + + for primitive, indices, colors, flag_on in arrays: + # primitive => GL_POINTS, GL_LINES, GL_TRIANGLES, GL_QUADS + # colors => RGB colors flattened + # indices => element vertex indices flattened + # flag_on => True or False + if not flag_on: + continue + + glColorPointer(3, GL_FLOAT, 0, colors) + glDrawElements(primitive, len(indices), GL_UNSIGNED_INT, indices) + + glDisableClientState(GL_COLOR_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + + +def draw_triangle_array(vertices, indices, colors): + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_COLOR_ARRAY) + glVertexPointer(3, GL_FLOAT, 0, vertices) + glColorPointer(3, GL_FLOAT, 0, colors) + glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, indices) + glDisableClientState(GL_COLOR_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + + +def draw_line_array(vertices, indices, colors): + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_COLOR_ARRAY) + glVertexPointer(3, GL_FLOAT, 0, vertices) + glColorPointer(3, GL_FLOAT, 0, colors) + glDrawElements(GL_LINES, len(indices), GL_UNSIGNED_INT, indices) + glDisableClientState(GL_COLOR_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + + +# ============================================================================== +# buffers +# ------- +# http://www.songho.ca/opengl/gl_vbo.html +# https://gist.github.com/ousttrue/c4ae334fc1505cdf4cd7 +# ============================================================================== + + +# ============================================================================== +# display lists +# ------------- +# http://www.songho.ca/opengl/gl_displaylist.html +# ============================================================================== + + +# def _make_lists(self): +# self._clear_lists() +# key_xyz = {key: self.mesh.vertex_coordinates(key) for key in self.mesh.vertices()} +# self._make_faces_list(key_xyz) +# self._make_edges_list(key_xyz) +# self._make_vertices_list(key_xyz) + +# def _make_faces_list(self, key_xyz): +# faces = [] +# front = hex_to_rgb(self.settings['faces.color:front']) +# front = list(front) + [1.0] +# back = hex_to_rgb(self.settings['faces.color:back']) +# back = list(back) + [1.0] +# for fkey in self.mesh.faces(): +# faces.append({'points' : [key_xyz[key] for key in self.mesh.face_vertices(fkey)], +# 'color.front' : front, +# 'color.back' : back}) +# self.view.faces = glGenLists(1) +# glNewList(self.view.faces, GL_COMPILE) +# draw_polygons(faces) +# glEndList() + +# def _make_edges_list(self, key_xyz): +# lines = [] +# color = hex_to_rgb(self.settings['edges.color']) +# width = self.settings['edges.width'] +# for u, v in self.mesh.edges(): +# lines.append({'start' : key_xyz[u], +# 'end' : key_xyz[v], +# 'color' : color, +# 'width' : width}) +# self.view.edges = glGenLists(1) +# glNewList(self.view.edges, GL_COMPILE) +# draw_cylinders(lines) +# glEndList() + +# def _make_vertices_list(self, key_xyz): +# points = [] +# color = hex_to_rgb(self.settings['vertices.color']) +# size = self.settings['vertices.size'] +# for key in self.mesh.vertices(): +# points.append({'pos' : key_xyz[key], +# 'color' : color, +# 'size' : size}) +# self.view.vertices = glGenLists(1) +# glNewList(self.view.vertices, GL_COMPILE) +# draw_spheres(points) +# glEndList() + + +# ============================================================================== +# draw +# ============================================================================== + + +def draw_points(points, color=None, size=1): + color = color if color else (0.0, 0.0, 0.0) + glColor3f(*color) + glPointSize(size) + glBegin(GL_POINTS) + for x, y, z in iter(points): + glVertex3f(x, y, z) + glEnd() + + +def draw_lines(lines, color=None, linewidth=1): + color = color if color else (0.0, 0.0, 0.0) + glColor3f(*color) + glLineWidth(linewidth) + glBegin(GL_LINES) + for a, b in iter(lines): + glVertex3f(*a) + glVertex3f(*b) + glEnd() + + +def draw_faces(faces, color=None): + color = color if color else (1.0, 0.0, 0.0, 0.5) + glColor4f(*color) + for face in faces: + glBegin(GL_POLYGON) + for xyz in face: + glVertex3f(*xyz) + glEnd() + + +def draw_sphere(r=1.0): + slices = 17 + stacks = 17 + glColor4f(0.8, 0.8, 0.8, 0.5) + glLineWidth(0.1) + glutWireSphere(r, slices, stacks) + + +def draw_circle(circle, color=None, n=100): + (center, normal), radius = circle + cx, cy, cz = center + a, b, c = normal + + u = -1.0, 0.0, a + v = 0.0, -1.0, b + frame = [center, normalize_vector(u), normalize_vector(v)] + + color = color if color else (1.0, 0.0, 0.0, 0.5) + sector = 2 * pi / n + + glColor4f(*color) + + glBegin(GL_POLYGON) + for i in range(n): + a = i * sector + x = radius * cos(a) + y = radius * sin(a) + z = 0 + + x, y, z = global_coords_numpy(frame, [[x, y, z]]).tolist()[0] + glVertex3f(x, y, z) + glEnd() + + glBegin(GL_POLYGON) + for i in range(n): + a = -i * sector + x = radius * cos(a) + y = radius * sin(a) + z = 0 + x, y, z = global_coords_numpy(frame, [[x, y, z]]).tolist()[0] + glVertex3f(x, y, z) + glEnd() + + +# ============================================================================== +# draw +# ============================================================================== + + +def draw_points(points): + for attr in points: + pos = attr['pos'] + color = attr['color'] + size = attr['size'] + glColor3f(*color) + glPointSize(size) + glBegin(GL_POINTS) + glVertex3f(*pos) + glEnd() + pass + + +def draw_lines(lines): + for attr in lines: + start = attr['start'] + end = attr['end'] + color = attr['color'] + width = attr['width'] + glColor3f(*color) + glLineWidth(width) + glBegin(GL_LINES) + glVertex3f(*start) + glVertex3f(*end) + glEnd() + + +def draw_polygons(polygons): + for attr in polygons: + points = attr['points'] + color_front = attr['color.front'] + color_back = attr['color.back'] + color_wires = attr.get('color.wires', (0.0, 0.0, 0.0, 1.0)) + wires_on = attr.get('wires_on', False) + # front faces + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) + glColor4f(*color_front) + glBegin(GL_POLYGON) + for xyz in points: + glVertex3f(*xyz) + glEnd() + if wires_on: + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) + glColor4f(*color_wires) + glBegin(GL_POLYGON) + for xyz in points: + glVertex3f(*xyz) + glEnd() + # back faces + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) + glColor4f(*color_back) + glBegin(GL_POLYGON) + for xyz in points[::-1]: + glVertex3f(*xyz) + glEnd() + if wires_on: + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) + glColor4f(*color_wires) + glBegin(GL_POLYGON) + for xyz in points[::-1]: + glVertex3f(*xyz) + glEnd() + + +def draw_texts(texts): + for attr in texts: + text = attr['text'] + pos = attr['pos'] + color = attr['color'] + shift = attr['shift'] + glColor4f(color[0], color[1], color[2], color[3]) + glRasterPos3f(pos[0] + shift[0], pos[1] + shift[1], pos[2] + shift[2]) + font = GLUT_BITMAP_HELVETICA_18 + for char in text: + glutBitmapCharacter(font, ord(char)) + + +def draw_spheres(spheres): + for attr in spheres: + glPushMatrix() + glTranslatef(* attr['pos']) + glColor3f(* attr['color']) + glutSolidSphere(attr['size'], 24, 24) + glPopMatrix() + + +def draw_cylinders(cylinders): + for attr in cylinders: + points = [attr['start'], attr['start'], attr['end'], attr['end']] + colors = [attr['color'], attr['color'], attr['color'], attr['color']] + glePolyCylinder(points, colors, attr['width']) + + +# ============================================================================== +# Main +# ============================================================================== + +if __name__ == '__main__': + pass From 3cbb689af7a45278e2343ec0f2cd70a8c747e5ac Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:17:16 +0200 Subject: [PATCH 44/73] removing unused local axes --- .../geometry/transformations/coordinate_systems.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 0c9126968314..91f761ca4f19 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -12,7 +12,6 @@ from compas.geometry.transformations.helpers import transform_points __all__ = [ - 'local_axes', 'correct_axes', 'local_coords', 'local_coords_numpy', @@ -21,17 +20,6 @@ ] -# this function will not always work -# it is also a duplicate of stuff found in matrices and frame -def local_axes(a, b, c): - # TODO: is this used somewhere? - u = b - a - v = c - a - u, v = correct_axes(u, v) - w = cross_vectors(u, v) - return u, v, w - - def correct_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. From 7137fb654302dcb36242995994f54968be429af2 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:25:13 +0200 Subject: [PATCH 45/73] fixing examples --- src/compas/geometry/transformations/transformation.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index 705fcc578d0a..867b3a79fc31 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -235,13 +235,10 @@ def change_basis(cls, frame_from, frame_to): >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = Transformation.change_basis(f1, f2) >>> p_f1 = Point(1, 1, 1) # point in f1 - >>> p_f2 = p_f1.transformed(T) # same point represented in f2 - >>> p_w1 = f1.represent_point_in_global_coordinates(p_f1) # point in world coordinates - >>> p_w2 = f2.represent_point_in_global_coordinates(p_f2) # point in world coordinates - >>> print(p_w1) - Point(0.733, 2.492, 3.074) - >>> print(p_w2) - Point(0.733, 2.492, 3.074) + >>> p_f1.transformed(T) # point represented in f2 + Point(1.395, 0.955, 1.934) + >>> f1.global_coords(p_f1, f2) + Point(1.395, 0.955, 1.934) """ T1 = cls.from_frame(frame_from) From 26d8650682abd8c8889e893c55e2a48c25b8caa9 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:25:21 +0200 Subject: [PATCH 46/73] Update coordinate_systems.py --- src/compas/geometry/transformations/coordinate_systems.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 91f761ca4f19..05a6030c1458 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -41,13 +41,12 @@ def correct_axes(xaxis, yaxis): -------- >>> xaxis = [1, 4, 5] >>> yaxis = [1, 0, -2] - >>> xaxis, yaxis = correct_axis_vectors(xaxis, yaxis) + >>> xaxis, yaxis = correct_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 """ - # TODO use this in Frame xaxis = normalize_vector(xaxis) yaxis = normalize_vector(yaxis) zaxis = cross_vectors(xaxis, yaxis) From 0051ca3b2063b97bd7cd35a708a86ac92c6d1b78 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:29:05 +0200 Subject: [PATCH 47/73] Update coordinate_systems.py --- src/compas/geometry/transformations/coordinate_systems.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index 05a6030c1458..bc557a157297 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -150,6 +150,7 @@ def global_coords(frame, xyz): >>> Point(*global_coords(f, xyz)[0]) Point(2.000, 3.000, 5.000) """ + from compas.geometry.primitives import Frame T = matrix_change_basis(frame, Frame.worldXY()) return transform_points(xyz, T) From fe1a9067542850856cb6393adbbdb35c617baa5a Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:31:50 +0200 Subject: [PATCH 48/73] fixing docstrings --- .../transformations/coordinate_systems.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index bc557a157297..b4f5ae7f1e33 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -90,10 +90,8 @@ def local_coords_numpy(frame, xyz): Parameters ---------- - origin : array-like - The global (XYZ) coordinates of the origin of the local coordinate system. - uvw : array-like - The global coordinate difference vectors of the axes of the local coordinate system. + frame : :class:`Frame` or [point, xaxis, yaxis] + The local coordinate system. xyz : array-like The global coordinates of the points to convert. @@ -102,10 +100,6 @@ def local_coords_numpy(frame, xyz): array The coordinates of the given points in the local coordinate system. - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - Examples -------- >>> import numpy as np @@ -160,10 +154,8 @@ def global_coords_numpy(frame, rst): Parameters ---------- - origin : array-like - The origin of the local coordinate system. - uvw : array-like - The coordinate axes of the local coordinate system. + frame : :class:`Frame` or [point, xaxis, yaxis] + The local coordinate system. rst : array-like The coordinates of the points wrt the local coordinate system. From 33556b10bf9fb41a2dcb97c694f1bdc6952d86c0 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 15:51:00 +0200 Subject: [PATCH 49/73] fixes because running tests --- src/compas/geometry/bbox/bbox_numpy.py | 15 +++++++++------ .../transformations/coordinate_systems.py | 8 ++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index 041e7cc687ea..746a321e328b 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -15,7 +15,7 @@ from scipy.spatial import ConvexHull # from scipy.spatial import QhullError -from compas.geometry import correct_axes +from compas.geometry import local_axes from compas.geometry import local_coords_numpy from compas.geometry import global_coords_numpy @@ -62,7 +62,7 @@ def oriented_bounding_box_numpy(points): Examples -------- Generate a random set of points with - :math:`x \in [0, 10]`, :math:`y \in [0, 1]` and :math:`z \in [0, 3]`. + :math:`x in [0, 10]`, :math:`y in [0, 1]` and :math:`z in [0, 3]`. Add the corners of the box such that we now the volume is supposed to be :math:`30.0`. >>> points = numpy.random.rand(10000, 3) @@ -112,9 +112,9 @@ def oriented_bounding_box_numpy(points): # this can be vectorised! for simplex in hull.simplices: a, b, c = points[simplex] - u, v = correct_axes(b - a, c - a) + uvw = local_axes(a, b, c) xyz = points[hull.vertices] - frame = [a, u, v] + frame = [a, uvw[0], uvw[1]] rst = local_coords_numpy(frame, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt @@ -192,7 +192,7 @@ def oriented_bounding_box_xy_numpy(points): p1 = points[simplex[1]] # s direction - s = p1 - p0 + s = p1 - p0 sl = sum(s ** 2) ** 0.5 su = s / sl vn = xy_hull - p0 @@ -205,7 +205,7 @@ def oriented_bounding_box_xy_numpy(points): b1 = p0 + sc[scmax] * su # t direction - t = array([-s[1], s[0]]) + t = array([-s[1], s[0]]) tl = sum(t ** 2) ** 0.5 tu = t / tl vn = xy_hull - p0 @@ -249,3 +249,6 @@ def oriented_bounding_box_xy_numpy(points): import doctest doctest.testmod(globs=globals()) + + coords = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]] + print(oriented_bounding_box_numpy(coords).tolist()) diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py index b4f5ae7f1e33..f7037b195a49 100644 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ b/src/compas/geometry/transformations/coordinate_systems.py @@ -12,6 +12,7 @@ from compas.geometry.transformations.helpers import transform_points __all__ = [ + 'local_axes', 'correct_axes', 'local_coords', 'local_coords_numpy', @@ -19,6 +20,13 @@ 'global_coords_numpy', ] +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 correct_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. From 86b3431a3d14e93c25dc3a1298d614dbf92ee60b Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 16:02:06 +0200 Subject: [PATCH 50/73] fix math backslash --- src/compas/geometry/bbox/bbox_numpy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index 746a321e328b..6246a001ffc5 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -62,7 +62,7 @@ def oriented_bounding_box_numpy(points): Examples -------- Generate a random set of points with - :math:`x in [0, 10]`, :math:`y in [0, 1]` and :math:`z in [0, 3]`. + :math:`x \\in [0, 10]`, :math:`y \\in [0, 1]` and :math:`z \\in [0, 3]`. Add the corners of the box such that we now the volume is supposed to be :math:`30.0`. >>> points = numpy.random.rand(10000, 3) From 8e0739eae5fff1e7545491a8950ddc2944246332 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 16:37:22 +0200 Subject: [PATCH 51/73] removing local\global coords now found in coordinate_systems --- .../transformations/transformations_numpy.py | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/src/compas/geometry/transformations/transformations_numpy.py b/src/compas/geometry/transformations/transformations_numpy.py index 55747dfe705e..fd28f417f5f1 100644 --- a/src/compas/geometry/transformations/transformations_numpy.py +++ b/src/compas/geometry/transformations/transformations_numpy.py @@ -16,8 +16,6 @@ 'homogenize_numpy', 'dehomogenize_numpy', - 'local_coords_numpy', - 'global_coords_numpy', ] @@ -49,68 +47,6 @@ def dehomogenize_numpy(points): return points[:, :-1] / points[:, -1].reshape((-1, 1)) -# this should be defined somewhere else -# and should have a python equivalent -# there is an implementation available in frame -def local_coords_numpy(origin, uvw, xyz): - """Convert global coordinates to local coordinates. - - Parameters - ---------- - origin : array-like - The global (XYZ) coordinates of the origin of the local coordinate system. - uvw : array-like - The global coordinate difference vectors of the axes of the local coordinate system. - xyz : array-like - The global coordinates of the points to convert. - - Returns - ------- - array - The coordinates of the given points in the local coordinate system. - - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - - """ - uvw = asarray(uvw).T - xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) - rst = solve(uvw, xyz) - return rst.T - - -# this should be defined somewhere else -# and should have a python equivalent -# there is an implementation available in frame -def global_coords_numpy(origin, uvw, rst): - """Convert local coordinates to global (world) coordinates. - - Parameters - ---------- - origin : array-like - The origin of the local coordinate system. - uvw : array-like - The coordinate axes of the local coordinate system. - rst : array-like - The coordinates of the points wrt the local coordinate system. - - Returns - ------- - array - The world coordinates of the given points. - - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - - """ - uvw = asarray(uvw).T - rst = asarray(rst).T - xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) - return xyz.T - - # ============================================================================== # Main # ============================================================================== From 41306829bf0a6282dff2aee3fa732a247bafd3f9 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 16:37:31 +0200 Subject: [PATCH 52/73] small update --- src/compas/geometry/bbox/bbox_numpy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index 6246a001ffc5..f4ef0bc5b3e7 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -250,5 +250,3 @@ def oriented_bounding_box_xy_numpy(points): import doctest doctest.testmod(globs=globals()) - coords = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]] - print(oriented_bounding_box_numpy(coords).tolist()) From ac2948596bbaa28ee90348f0a64563068f14043b Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Sun, 13 Oct 2019 16:46:47 +0200 Subject: [PATCH 53/73] small fixes --- src/compas/geometry/transformations/matrices.py | 2 ++ src/compas/geometry/transformations/transformation.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/compas/geometry/transformations/matrices.py b/src/compas/geometry/transformations/matrices.py index a49df0bc08ad..74b5b51643bf 100644 --- a/src/compas/geometry/transformations/matrices.py +++ b/src/compas/geometry/transformations/matrices.py @@ -15,6 +15,8 @@ from compas.geometry.basic import length_vector from compas.geometry.basic import allclose from compas.geometry.basic import multiply_matrices +from compas.geometry.basic import transpose_matrix +from compas.geometry.basic import norm_vector from compas.geometry.transformations import _EPS from compas.geometry.transformations import _SPEC2TUPLE diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index 867b3a79fc31..f60cd8826cce 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -244,7 +244,7 @@ def change_basis(cls, frame_from, frame_to): T1 = cls.from_frame(frame_from) T2 = cls.from_frame(frame_to) - return cls(multiply_matrices(inverse(T2.matrix), T1.matrix)) + return cls(multiply_matrices(matrix_inverse(T2.matrix), T1.matrix)) def inverse(self): """Returns the inverse transformation. From 988a423bf4933c882661cf282b3c9fc9ab844d65 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 13:23:10 +0200 Subject: [PATCH 54/73] adding matix_from_frame_to_frame and matrix_change_basis --- src/compas/geometry/transformations/matrices.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compas/geometry/transformations/matrices.py b/src/compas/geometry/transformations/matrices.py index 74b5b51643bf..bed3a3ce3769 100644 --- a/src/compas/geometry/transformations/matrices.py +++ b/src/compas/geometry/transformations/matrices.py @@ -31,6 +31,8 @@ 'identity_matrix', 'matrix_from_frame', + 'matrix_from_frame_to_frame', + 'matrix_change_basis', 'matrix_from_euler_angles', 'matrix_from_axis_and_angle', 'matrix_from_axis_angle_vector', @@ -415,10 +417,9 @@ def matrix_from_frame_to_frame(frame_from, frame_to): >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = matrix_from_frame_to_frame(f1, f2) """ - from compas.geometry.transformations import inverse T1 = matrix_from_frame(frame_from) T2 = matrix_from_frame(frame_to) - return multiply_matrices(T2, inverse(T1)) + return multiply_matrices(T2, matrix_inverse(T1)) def matrix_change_basis(frame_from, frame_to): """Computes a change of basis transformation between two frames. @@ -439,10 +440,9 @@ def matrix_change_basis(frame_from, frame_to): >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = matrix_change_basis(f1, f2) """ - from compas.geometry.transformations import inverse T1 = matrix_from_frame(frame_from) T2 = matrix_from_frame(frame_to) - return multiply_matrices(inverse(T2), T1) + return multiply_matrices(matrix_inverse(T2), T1) def matrix_from_euler_angles(euler_angles, static=True, axes='xyz'): """Calculates a rotation matrix from Euler angles. From aa36b19ee970bc01507b48cfd7fbe242d99b8b04 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 13:23:38 +0200 Subject: [PATCH 55/73] removing helpers and coodinate_systems --- .../geometry/transformations/__init__.py | 2 - .../transformations/coordinate_systems.py | 208 ------ .../geometry/transformations/helpers.py | 695 ------------------ 3 files changed, 905 deletions(-) delete mode 100644 src/compas/geometry/transformations/coordinate_systems.py delete mode 100644 src/compas/geometry/transformations/helpers.py diff --git a/src/compas/geometry/transformations/__init__.py b/src/compas/geometry/transformations/__init__.py index 9405ccf08b5c..beb54002139f 100644 --- a/src/compas/geometry/transformations/__init__.py +++ b/src/compas/geometry/transformations/__init__.py @@ -23,8 +23,6 @@ # todo: separate the numpy version inot separate modules -from .helpers import * -from .coordinate_systems import * from .matrices import * # migrated from xforms diff --git a/src/compas/geometry/transformations/coordinate_systems.py b/src/compas/geometry/transformations/coordinate_systems.py deleted file mode 100644 index f7037b195a49..000000000000 --- a/src/compas/geometry/transformations/coordinate_systems.py +++ /dev/null @@ -1,208 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division - -import math - -from compas.geometry.basic import normalize_vector -from compas.geometry.basic import cross_vectors -from compas.geometry.basic import norm_vector - -from compas.geometry.transformations.matrices import matrix_change_basis -from compas.geometry.transformations.helpers import transform_points - -__all__ = [ - 'local_axes', - 'correct_axes', - 'local_coords', - 'local_coords_numpy', - 'global_coords', - 'global_coords_numpy', -] - -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 correct_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 = correct_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 local_coords(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(*local_coords(f, xyz)[0]) - Point(3.726, 4.088, 1.550) - """ - from compas.geometry.primitives import Frame - T = matrix_change_basis(Frame.worldXY(), frame) - return transform_points(xyz, T) - - -def local_coords_numpy(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 - ------- - array - The coordinates of the given points in the local coordinate system. - - Examples - -------- - >>> import numpy as np - >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(frame, xyz) - >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) - True - """ - from numpy import asarray - from scipy.linalg import solve - - origin = frame[0] - uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] - uvw = asarray(uvw).T - xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) - rst = solve(uvw, xyz) - return rst.T - - -def global_coords(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(*global_coords(f, xyz)[0]) - Point(2.000, 3.000, 5.000) - """ - from compas.geometry.primitives import Frame - T = matrix_change_basis(frame, Frame.worldXY()) - return transform_points(xyz, T) - - -def global_coords_numpy(frame, rst): - """Convert local coordinates to global (world) coordinates. - - Parameters - ---------- - frame : :class:`Frame` or [point, xaxis, yaxis] - The local coordinate system. - rst : array-like - The coordinates of the points wrt the local coordinate system. - - Returns - ------- - array - The world coordinates of the given points. - - Notes - ----- - ``origin`` and ``uvw`` together form the frame of local coordinates. - - Examples - -------- - >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) - >>> rst = [Point(3.726, 4.088, 1.550)] - >>> xyz = global_coords_numpy(frame, rst) - >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) - True - """ - from numpy import asarray - - origin = frame[0] - uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] - - uvw = asarray(uvw).T - rst = asarray(rst).T - xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) - return xyz.T - -# ============================================================================== -# Main -# ============================================================================== - - -if __name__ == "__main__": - import doctest - import numpy - from compas.geometry import Point - from compas.geometry import Frame - from compas.geometry import allclose - doctest.testmod(globs=globals()) diff --git a/src/compas/geometry/transformations/helpers.py b/src/compas/geometry/transformations/helpers.py deleted file mode 100644 index 433f0f2a3817..000000000000 --- a/src/compas/geometry/transformations/helpers.py +++ /dev/null @@ -1,695 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division - -import math - -from copy import deepcopy - -from compas.geometry.basic import cross_vectors -from compas.geometry.basic import dot_vectors -from compas.geometry.basic import multiply_matrix_vector -from compas.geometry.basic import multiply_matrices -from compas.geometry.basic import transpose_matrix -from compas.geometry.basic import norm_vector - -from compas.geometry.transformations import _EPS - -from compas.geometry.transformations.matrices import matrix_from_perspective_entries -from compas.geometry.transformations.matrices import matrix_from_translation -from compas.geometry.transformations.matrices import matrix_from_euler_angles -from compas.geometry.transformations.matrices import matrix_from_shear_entries -from compas.geometry.transformations.matrices import matrix_from_scale_factors - -__all__ = [ - 'transform_points', - 'transform_points_numpy', - - 'transform_vectors', - 'transform_vectors_numpy', - - 'transform_frames', - 'transform_frames_numpy', - - 'homogenize', - 'dehomogenize', - 'homogenize_numpy', - 'dehomogenize_numpy', - - 'determinant', - 'inverse', - - 'compose_matrix', - 'decompose_matrix', -] - - -def transform_points(points, T): - """Transform multiple points with one Transformation. - - Parameters - ---------- - points : list of :class:`Point` - A list of points to be transformed. - T : :class:`Transformation` - The transformation to apply. - - Examples - -------- - >>> points = [Point(1,0,0), (1,2,4), [4,7,1]] - >>> T = Rotation.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. - - Parameters - ---------- - vectors : list of :class:`Vector` - A list of vectors to be transformed. - T : :class:`Transformation` - The transformation to apply. - - Examples - -------- - >>> vectors = [Vector(1,0,0), (1,2,4), [4,7,1]] - >>> T = Rotation.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. - - 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]), [[0,2,0], [5,2,1], [0,2,1]]] - >>> T = Rotation.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 transform_points_numpy(points, T): - """Transform multiple points with one Transformation using numpy. - - Parameters - ---------- - points : list of :class:`Point` - A list of points to be transformed. - T : :class:`Transformation` - The transformation to apply. - - Examples - -------- - >>> points = [Point(1,0,0), (1,2,4), [4,7,1]] - >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) - >>> points_transformed = transform_points_numpy(points, T) - """ - from numpy import asarray - T = asarray(T) - points = homogenize_numpy(points, w=1.0) - return dehomogenize_numpy(points.dot(T.T)) - - -def transform_vectors_numpy(vectors, T): - """Transform multiple vectors with one Transformation using numpy. - - Parameters - ---------- - vectors : list of :class:`Vector` - A list of vectors to be transformed. - T : :class:`Transformation` - The transformation to apply. - - Examples - -------- - >>> vectors = [Vector(1,0,0), (1,2,4), [4,7,1]] - >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) - >>> vectors_transformed = transform_vectors_numpy(vectors, T) - """ - from numpy import asarray - T = asarray(T) - vectors = homogenize_numpy(vectors, w=0.0) - return dehomogenize_numpy(vectors.dot(T.T)) - - -def transform_frames_numpy(frames, T): - """Transform multiple frames with one Transformation usig numpy. - - 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]), [[0,2,0], [5,2,1], [0,2,1]]] - >>> T = Rotation.from_axis_and_angle((0,2,0), math.radians(45), point=(4,5,6)) - >>> transformed_frames = transform_frames_numpy(frames, T) - """ - from numpy import asarray - T = asarray(T) - points_and_vectors = homogenize_and_flatten_frames_numpy(frames) - return dehomogenize_and_unflatten_frames_numpy(points_and_vectors.dot(T.T)) - - -# ============================================================================== -# helping helpers -# ============================================================================== - - -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 of float - A list of vectors. - - Returns - ------- - list of float - 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)] - - -def homogenize_and_flatten_frames_numpy(frames): - """Homogenize a list of frames and flatten the 3D list into a 2D list using numpy. - - The frame consists of a point and 2 orthonormal vectors. - - Parameters - ---------- - frames: list of :class:`Frame` - - Returns - ------- - :class:`numpy.ndarray` - An array of points and vectors. - - Examples - -------- - >>> import numpy as np - >>> frames = [Frame((1, 1, 1), (0, 1, 0), (1, 0, 0))] - >>> res = homogenize_and_flatten_frames_numpy(frames) - >>> np.allclose(res, [[1.0, 1.0, 1.0, 1.0], [0.0, 1.0, 0.0, 0.0], [1.0, -0.0, 0.0, 0.0]]) - True - """ - from numpy import asarray - from numpy import tile - from numpy import hstack - n = len(frames) - frames = asarray(frames).reshape(n * 3, 3) - extend = tile(asarray([1, 0, 0]).reshape(3, 1), (n, 1)) - return hstack((frames, extend)) - - -def dehomogenize_and_unflatten_frames_numpy(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 - ------- - :class:`numpy.ndarray` - The frames. - - Examples - -------- - >>> import numpy as np - >>> points_and_vectors = [(1., 1., 1., 1.), (0., 1., 0., 0.), (1., 0., 0., 0.)] - >>> res = dehomogenize_and_unflatten_frames_numpy(points_and_vectors) - >>> np.allclose(res, [[1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]) - True - """ - frames = dehomogenize_numpy(points_and_vectors) - return frames.reshape((int(frames.shape[0]/3.), 3, 3)) - - -def homogenize_numpy(points, w=1.0): - """Dehomogenizes points or vectors. - - Parameters - ---------- - points: list of :class:`Points` or list of :class:`Vectors` - - Returns - ------- - :class:`numpy.ndarray` - """ - from numpy import asarray - from numpy import hstack - from numpy import ones - - points = asarray(points) - points = hstack((points, w * ones((points.shape[0], 1)))) - return points - - -def dehomogenize_numpy(points): - """Dehomogenizes points or vectors. - - Parameters - ---------- - points: list of :class:`Points` or list of :class:`Vectors` - - Returns - ------- - :class:`numpy.ndarray` - """ - from numpy import asarray - from numpy import vectorize - - def func(a): - return a if a else 1. - func = vectorize(func) - - points = asarray(points) - return points[:, :-1] / func(points[:, -1]).reshape((-1, 1)) - - -def determinant(M, check=True): - """Calculates the determinant of a square matrix M. - - Parameters - ---------- - M : :obj:`list` of :obj:`list` of :obj:`float` - The square matrix of any dimension. - check : bool - If true checks if matrix is squared. Defaults to True. - - Raises - ------ - ValueError - If matrix is not a square matrix. - - Returns - ------- - float - The determinant. - - """ - dim = len(M) - - if check: - for c in M: - if len(c) != dim: - raise ValueError("Not a square matrix") - - if (dim == 2): - return M[0][0] * M[1][1] - M[0][1] * M[1][0] - else: - i = 1 - t = 0 - sum = 0 - for t in range(dim): - d = {} - for t1 in range(1, dim): - m = 0 - d[t1] = [] - for m in range(dim): - if (m != t): - d[t1].append(M[t1][m]) - M1 = [d[x] for x in d] - sum = sum + i * M[0][t] * determinant(M1, check=False) - i = i * (-1) - return sum - - -def inverse(M): - """Calculates the inverse of a square matrix M. - - Parameters - ---------- - M : :obj:`list` of :obj:`list` of :obj:`float` - The square matrix of any dimension. - - Raises - ------ - ValueError - If the matrix is not squared - ValueError - If the matrix is singular. - ValueError - If the matrix is not invertible. - - Returns - ------- - list of list of float - The inverted matrix. - - Examples - -------- - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> T = matrix_from_frame(f) - >>> I = multiply_matrices(T, inverse(T)) - >>> I2 = identity_matrix(4) - >>> allclose(I[0], I2[0]) - True - >>> allclose(I[1], I2[1]) - True - >>> allclose(I[2], I2[2]) - True - >>> allclose(I[3], I2[3]) - True - - """ - def matrix_minor(m, i, j): - return [row[:j] + row[j + 1:] for row in (m[:i] + m[i + 1:])] - - detM = determinant(M) # raises ValueError if matrix is not squared - - if detM == 0: - ValueError("The matrix is singular.") - - if len(M) == 2: - return [[M[1][1] / detM, -1 * M[0][1] / detM], - [-1 * M[1][0] / detM, M[0][0] / detM]] - else: - cofactors = [] - for r in range(len(M)): - cofactor_row = [] - for c in range(len(M)): - minor = matrix_minor(M, r, c) - cofactor_row.append(((-1) ** (r + c)) * determinant(minor)) - cofactors.append(cofactor_row) - cofactors = transpose_matrix(cofactors) - for r in range(len(cofactors)): - for c in range(len(cofactors)): - cofactors[r][c] = cofactors[r][c] / detM - return cofactors - - -def decompose_matrix(M): - """Calculates the components of rotation, translation, scale, shear, and - perspective of a given transformation matrix M. - - Parameters - ---------- - M : :obj:`list` of :obj:`list` of :obj:`float` - The square matrix of any dimension. - - Raises - ------ - ValueError - If matrix is singular or degenerative. - - Returns - ------- - scale : :obj:`list` of :obj:`float` - The 3 scale factors in x-, y-, and z-direction. - shear : :obj:`list` of :obj:`float` - The 3 shear factors for x-y, x-z, and y-z axes. - angles : :obj:`list` of :obj:`float` - The rotation specified through the 3 Euler angles about static x, y, z axes. - translation : :obj:`list` of :obj:`float` - The 3 values of translation. - perspective : :obj:`list` of :obj:`float` - The 4 perspective entries of the matrix. - - Examples - -------- - >>> trans1 = [1, 2, 3] - >>> angle1 = [-2.142, 1.141, -0.142] - >>> scale1 = [0.123, 2, 0.5] - >>> T = matrix_from_translation(trans1) - >>> R = matrix_from_euler_angles(angle1) - >>> S = matrix_from_scale_factors(scale1) - >>> M = multiply_matrices(multiply_matrices(T, R), S) - >>> # M = compose_matrix(scale1, None, angle1, trans1, None) - >>> scale2, shear2, angle2, trans2, persp2 = decompose_matrix(M) - >>> allclose(scale1, scale2) - True - >>> allclose(angle1, angle2) - True - >>> allclose(trans1, trans2) - True - - References - ---------- - .. [1] Slabaugh, 1999. *Computing Euler angles from a rotation matrix*. - Available at: http://www.gregslabaugh.net/publications/euler.pdf - """ - - detM = determinant(M) # raises ValueError if matrix is not squared - - if detM == 0: - ValueError("The matrix is singular.") - - Mt = transpose_matrix(M) - - if abs(Mt[3][3]) < _EPS: - raise ValueError('The element [3,3] of the matrix is zero.') - - for i in range(4): - for j in range(4): - Mt[i][j] /= Mt[3][3] - - translation = [M[0][3], M[1][3], M[2][3]] - - # scale, shear, rotation - # copy Mt[:3, :3] into row - scale = [0.0, 0.0, 0.0] - shear = [0.0, 0.0, 0.0] - angles = [0.0, 0.0, 0.0] - - row = [[0, 0, 0] for i in range(3)] - for i in range(3): - for j in range(3): - row[i][j] = Mt[i][j] - - scale[0] = norm_vector(row[0]) - for i in range(3): - row[0][i] /= scale[0] - shear[0] = dot_vectors(row[0], row[1]) - for i in range(3): - row[1][i] -= row[0][i] * shear[0] - scale[1] = norm_vector(row[1]) - for i in range(3): - row[1][i] /= scale[1] - shear[0] /= scale[1] - shear[1] = dot_vectors(row[0], row[2]) - for i in range(3): - row[2][i] -= row[0][i] * shear[1] - shear[2] = dot_vectors(row[1], row[2]) - for i in range(3): - row[2][i] -= row[0][i] * shear[2] - scale[2] = norm_vector(row[2]) - for i in range(3): - row[2][i] /= scale[2] - shear[1] /= scale[2] - shear[2] /= scale[2] - - if dot_vectors(row[0], cross_vectors(row[1], row[2])) < 0: - scale = [-x for x in scale] - row = [[-x for x in y] for y in row] - - # angles - if row[0][2] != -1. and row[0][2] != 1.: - - beta1 = math.asin(-row[0][2]) - beta2 = math.pi - beta1 - - alpha1 = math.atan2(row[1][2] / math.cos(beta1), row[2][2] / math.cos(beta1)) - alpha2 = math.atan2(row[1][2] / math.cos(beta2), row[2][2] / math.cos(beta2)) - - gamma1 = math.atan2(row[0][1] / math.cos(beta1), row[0][0] / math.cos(beta1)) - gamma2 = math.atan2(row[0][1] / math.cos(beta2), row[0][0] / math.cos(beta2)) - - angles = [alpha1, beta1, gamma1] - # TODO: check for alpha2, beta2, gamma2 needed? - else: - gamma = 0. - if row[0][2] == -1.: - beta = math.pi / 2. - alpha = gamma + math.atan2(row[1][0], row[2][0]) - else: # row[0][2] == 1 - beta = -math.pi / 2. - alpha = -gamma + math.atan2(-row[1][0], -row[2][0]) - angles = [alpha, beta, gamma] - - # perspective - if math.fabs(Mt[0][3]) > _EPS and math.fabs(Mt[1][3]) > _EPS and math.fabs(Mt[2][3]) > _EPS: - P = deepcopy(Mt) - P[0][3], P[1][3], P[2][3], P[3][3] = 0.0, 0.0, 0.0, 1.0 - Ptinv = inverse(transpose_matrix(P)) - perspective = multiply_matrix_vector(Ptinv, [Mt[0][3], Mt[1][3], - Mt[2][3], Mt[3][3]]) - else: - perspective = [0.0, 0.0, 0.0, 1.0] - - return scale, shear, angles, translation, perspective - - -def compose_matrix(scale=None, shear=None, angles=None, - translation=None, perspective=None): - """Calculates a matrix from the components of scale, shear, euler_angles, - translation and perspective. - - Parameters - ---------- - scale : :obj:`list` of :obj:`float` - The 3 scale factors in x-, y-, and z-direction. - shear : :obj:`list` of :obj:`float` - The 3 shear factors for x-y, x-z, and y-z axes. - angles : :obj:`list` of :obj:`float` - The rotation specified through the 3 Euler angles about static x, y, z axes. - translation : :obj:`list` of :obj:`float` - The 3 values of translation. - perspective : :obj:`list` of :obj:`float` - The 4 perspective entries of the matrix. - - Examples - -------- - >>> trans1 = [1, 2, 3] - >>> angle1 = [-2.142, 1.141, -0.142] - >>> scale1 = [0.123, 2, 0.5] - >>> M = compose_matrix(scale1, None, angle1, trans1, None) - >>> scale2, shear2, angle2, trans2, persp2 = decompose_matrix(M) - >>> allclose(scale1, scale2) - True - >>> allclose(angle1, angle2) - True - >>> allclose(trans1, trans2) - True - - """ - M = [[1. if i == j else 0. for i in range(4)] for j in range(4)] - if perspective is not None: - P = matrix_from_perspective_entries(perspective) - M = multiply_matrices(M, P) - if translation is not None: - T = matrix_from_translation(translation) - M = multiply_matrices(M, T) - if angles is not None: - R = matrix_from_euler_angles(angles, static=True, axes="xyz") - M = multiply_matrices(M, R) - if shear is not None: - Sh = matrix_from_shear_entries(shear) - M = multiply_matrices(M, Sh) - if scale is not None: - Sc = matrix_from_scale_factors(scale) - M = multiply_matrices(M, Sc) - for i in range(4): - for j in range(4): - M[i][j] /= M[3][3] - return M - - -# ============================================================================== -# Main -# ============================================================================== - -if __name__ == "__main__": - import doctest - import numpy - from compas.geometry import allclose - from compas.geometry import matrix_from_frame - from compas.geometry import identity_matrix - from compas.geometry import Point - from compas.geometry import Vector - from compas.geometry import Frame - from compas.geometry import Transformation - from compas.geometry import Rotation - doctest.testmod(globs=globals()) From c8791ee79480067d69465f58f40bdda87d0f76de Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 13:24:01 +0200 Subject: [PATCH 56/73] removing duplicate change_basis --- .../transformations/transformation.py | 31 +------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index 3eb8383dfcc8..06cd8f400c02 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -245,36 +245,7 @@ def change_basis(cls, frame_from, frame_to): T2 = cls.from_frame(frame_to) return cls(multiply_matrices(matrix_inverse(T2.matrix), T1.matrix)) - - @classmethod - def change_basis(cls, frame_from, frame_to): - """Computes a change of basis transformation between two frames. - - A basis change is essentially a remapping of geometry from one - coordinate system to another. - - Args: - frame_from (:class:`Frame`): a frame defining the original - Cartesian coordinate system - frame_to (:class:`Frame`): a frame defining the targeted - Cartesian coordinate system - - Example: - >>> from compas.geometry import Point, Frame - >>> f1 = Frame([2, 2, 2], [0.12, 0.58, 0.81], [-0.80, 0.53, -0.26]) - >>> f2 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> T = Transformation.change_basis(f1, f2) - >>> p_f1 = Point(1, 1, 1) # point in f1 - >>> p_f1.transformed(T) # point represented in f2 - Point(1.395, 0.955, 1.934) - >>> f1.global_coords(p_f1, f2) - Point(1.395, 0.955, 1.934) - """ - - T1 = cls.from_frame(frame_from) - T2 = cls.from_frame(frame_to) - - return cls(multiply_matrices(inverse(T2.matrix), T1.matrix)) + def inverse(self): """Returns the inverse transformation. From 45e99a59d4b843e9dd348fae7ea7d1b5ee190104 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 13:24:37 +0200 Subject: [PATCH 57/73] adding stuff from helpers and coordinate_systems in transformations --- .../transformations/transformations.py | 194 +++++++++++++++++- 1 file changed, 192 insertions(+), 2 deletions(-) diff --git a/src/compas/geometry/transformations/transformations.py b/src/compas/geometry/transformations/transformations.py index 33e94c01354e..470e4902213c 100644 --- a/src/compas/geometry/transformations/transformations.py +++ b/src/compas/geometry/transformations/transformations.py @@ -21,6 +21,7 @@ from compas.geometry.basic import multiply_matrix_vector from compas.geometry.basic import multiply_matrices from compas.geometry.basic import transpose_matrix +from compas.geometry.basic import norm_vector from compas.geometry.angles import angle_vectors @@ -37,11 +38,12 @@ 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_axis_and_angle +from compas.geometry.transformations import matrix_change_basis __all__ = [ 'local_axes', + 'correct_axes', 'transform_points', 'transform_vectors', @@ -87,6 +89,42 @@ def local_axes(a, b, c): return normalize_vector(u), normalize_vector(v), normalize_vector(w) +def correct_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 = correct_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. @@ -140,19 +178,171 @@ def dehomogenize(vectors): 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 local_coords(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(*local_coords(f, xyz)[0]) + Point(3.726, 4.088, 1.550) + """ + from compas.geometry.primitives import Frame + T = matrix_change_basis(Frame.worldXY(), frame) + return transform_points(xyz, T) + + +def global_coords(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(*global_coords(f, xyz)[0]) + Point(2.000, 3.000, 5.000) + """ + from compas.geometry.primitives import Frame + T = matrix_change_basis(frame, Frame.worldXY()) + return transform_points(xyz, T) + + # ============================================================================== # translate # ============================================================================== @@ -953,5 +1143,5 @@ def orient_points(points, reference_plane, target_plane): import doctest from compas.geometry import allclose from compas.geometry import Point + from compas.geometry import Frame doctest.testmod(globs=globals()) - From 1e200e54a4e1aa26ff2378f230fdeed807381149 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 13:24:50 +0200 Subject: [PATCH 58/73] adding stuff from helpers and coordinate_systems in transformations_numpy --- .../transformations/transformations_numpy.py | 231 +++++++++++++++++- 1 file changed, 228 insertions(+), 3 deletions(-) diff --git a/src/compas/geometry/transformations/transformations_numpy.py b/src/compas/geometry/transformations/transformations_numpy.py index fd28f417f5f1..ddce9c4313bc 100644 --- a/src/compas/geometry/transformations/transformations_numpy.py +++ b/src/compas/geometry/transformations/transformations_numpy.py @@ -5,6 +5,8 @@ from numpy import asarray from numpy import hstack from numpy import ones +from numpy import vectorize +from numpy import tile from scipy.linalg import solve @@ -16,20 +18,150 @@ 'homogenize_numpy', 'dehomogenize_numpy', + 'homogenize_and_flatten_frames_numpy', + 'dehomogenize_and_unflatten_frames_numpy', + + 'local_coords_numpy', + 'global_coords_numpy', + ] def transform_points_numpy(points, T): + """Transform multiple points with one Transformation using numpy. + + 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_numpy(points, T) + """ T = asarray(T) points = homogenize_numpy(points, w=1.0) return dehomogenize_numpy(points.dot(T.T)) def transform_vectors_numpy(vectors, T): + """Transform multiple vectors with one Transformation using numpy. + + Parameters + ---------- + vectors : list of :class:`Vector` + A list of vectors to be transformed. + T : :class:`Transformation` + 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_numpy(vectors, T) + """ T = asarray(T) vectors = homogenize_numpy(vectors, w=0.0) return dehomogenize_numpy(vectors.dot(T.T)) +def transform_frames_numpy(frames, T): + """Transform multiple frames with one Transformation usig numpy. + + 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_numpy(frames, T) + """ + from numpy import asarray + T = asarray(T) + points_and_vectors = homogenize_and_flatten_frames_numpy(frames) + return dehomogenize_and_unflatten_frames_numpy(points_and_vectors.dot(T.T)) + + +def local_coords_numpy(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 + ------- + array + The coordinates of the given points in the local coordinate system. + + Examples + -------- + >>> import numpy as np + >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> xyz = [Point(2, 3, 5)] + >>> rst = local_coords_numpy(frame, xyz) + >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) + True + """ + from numpy import asarray + from scipy.linalg import solve + + origin = frame[0] + uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] + uvw = asarray(uvw).T + xyz = asarray(xyz).T - asarray(origin).reshape((-1, 1)) + rst = solve(uvw, xyz) + return rst.T + + +def global_coords_numpy(frame, rst): + """Convert local coordinates to global (world) coordinates. + + Parameters + ---------- + frame : :class:`Frame` or [point, xaxis, yaxis] + The local coordinate system. + rst : array-like + The coordinates of the points wrt the local coordinate system. + + Returns + ------- + array + The world coordinates of the given points. + + Notes + ----- + ``origin`` and ``uvw`` together form the frame of local coordinates. + + Examples + -------- + >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) + >>> rst = [Point(3.726, 4.088, 1.550)] + >>> xyz = global_coords_numpy(frame, rst) + >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) + True + """ + from numpy import asarray + + origin = frame[0] + uvw = [frame[1], frame[2], cross_vectors(frame[1], frame[2])] + + uvw = asarray(uvw).T + rst = asarray(rst).T + xyz = uvw.dot(rst) + asarray(origin).reshape((-1, 1)) + return xyz.T + # ============================================================================== # helping helpers @@ -37,14 +169,103 @@ def transform_vectors_numpy(vectors, T): def homogenize_numpy(points, w=1.0): + """Dehomogenizes points or vectors. + + Parameters + ---------- + points: list of :class:`Points` or list of :class:`Vectors` + + Returns + ------- + :class:`numpy.ndarray` + + Examples + -------- + >>> points = [[1, 1, 1], [0, 1, 0], [1, 0, 0]] + >>> res = homogenize_numpy(points, w=1.0) + >>> numpy.allclose(res, [[1.0, 1.0, 1.0, 1.0], [0.0, 1.0, 0.0, 1.0], [1.0, -0.0, 0.0, 1.0]]) + True + """ points = asarray(points) points = hstack((points, w * ones((points.shape[0], 1)))) return points def dehomogenize_numpy(points): + """Dehomogenizes points or vectors. + + Parameters + ---------- + points: list of :class:`Points` or list of :class:`Vectors` + + Returns + ------- + :class:`numpy.ndarray` + + Examples + -------- + >>> points = [[1, 1, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0]] + >>> res = dehomogenize_numpy(points) + >>> numpy.allclose(res, [[1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, -0.0, 0.0]]) + True + """ + def func(a): + return a if a else 1. + func = vectorize(func) + points = asarray(points) - return points[:, :-1] / points[:, -1].reshape((-1, 1)) + return points[:, :-1] / func(points[:, -1]).reshape((-1, 1)) + + +def homogenize_and_flatten_frames_numpy(frames): + """Homogenize a list of frames and flatten the 3D list into a 2D list using numpy. + + The frame consists of a point and 2 orthonormal vectors. + + Parameters + ---------- + frames: list of :class:`Frame` + + Returns + ------- + :class:`numpy.ndarray` + An array of points and vectors. + + Examples + -------- + >>> frames = [Frame((1, 1, 1), (0, 1, 0), (1, 0, 0))] + >>> res = homogenize_and_flatten_frames_numpy(frames) + >>> numpy.allclose(res, [[1.0, 1.0, 1.0, 1.0], [0.0, 1.0, 0.0, 0.0], [1.0, -0.0, 0.0, 0.0]]) + True + """ + n = len(frames) + frames = asarray(frames).reshape(n * 3, 3) + extend = tile(asarray([1, 0, 0]).reshape(3, 1), (n, 1)) + return hstack((frames, extend)) + + +def dehomogenize_and_unflatten_frames_numpy(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 + ------- + :class:`numpy.ndarray` + The frames. + + Examples + -------- + >>> points_and_vectors = [(1., 1., 1., 1.), (0., 1., 0., 0.), (1., 0., 0., 0.)] + >>> res = dehomogenize_and_unflatten_frames_numpy(points_and_vectors) + >>> numpy.allclose(res, [[1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]) + True + """ + frames = dehomogenize_numpy(points_and_vectors) + return frames.reshape((int(frames.shape[0]/3.), 3, 3)) # ============================================================================== @@ -52,5 +273,9 @@ def dehomogenize_numpy(points): # ============================================================================== if __name__ == "__main__": - - pass + import doctest + import numpy + import math + from compas.geometry import Frame + from compas.geometry import matrix_from_axis_and_angle + doctest.testmod(globs=globals()) From 19fc662696114eab4e80f21f350950995ffba165 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 13:25:44 +0200 Subject: [PATCH 59/73] fixing local/global_coords and adding static method local_to_local_coords --- src/compas/geometry/primitives/frame.py | 103 +++++++++++++++--------- 1 file changed, 66 insertions(+), 37 deletions(-) diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index 2ba1338fc4b8..f819a15e2794 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -649,21 +649,22 @@ def euler_angles(self, static=True, axes='xyz'): R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return euler_angles_from_matrix(R, static, axes) - def local_coords(self, coords_rcs, rcs=None): - """Returns the object's coordinates in the frame's local coordinate system. + # ========================================================================== + # coordinate frames + # ========================================================================== + + def local_coords(self, object_in_wcf): + """Returns the object's coordinates in the local coordinate system of the frame. Parameters ---------- - coords_rcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float - Coordinates in world XY or rcs. - rcs : :class:`Frame`, optional - The other coordinate system, defaults to world XY. If rcs is not `None`, - coords_rcs are assumed to be in rcs. + object_in_wcf : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + An object in the world coordinate frame. Returns ------- - :class:`Point` - A point in the local coordinate system of the frame. + :class:`Point` or :class:`Vector` or :class:`Frame` + The object in the local coordinate system of the frame. Notes ----- @@ -672,34 +673,30 @@ def local_coords(self, coords_rcs, rcs=None): Examples -------- >>> from compas.geometry import Point, Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pw = Point(2, 2, 2) - >>> pl = f.local_coords(pw) - >>> f.global_coords(pl) + >>> frame = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> pw = Point(2, 2, 2) # point in wcf + >>> pl = frame.local_coords(pw) # point in frame + >>> frame.global_coords(pl) Point(2.000, 2.000, 2.000) """ - if not rcs: - rcs = Frame.worldXY() - T = Transformation.change_basis(rcs, self) - if isinstance(coords_rcs, list): - return Point(*coords_rcs).transformed(T) + T = Transformation.change_basis(Frame.worldXY(), self) + if isinstance(object_in_wcf, list): + return Point(*object_in_wcf).transformed(T) else: - return coords_rcs.transformed(T) + return object_in_wcf.transformed(T) - def global_coords(self, coords_lcs, rcs=None): - """Returns the frame's object's coordinates in the global coordinate system. + def global_coords(self, object_in_lcs): + """Returns the object's coordinates in the global coordinate frame. Parameters ---------- - coords_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float - A coordinate object in the frames coordinate system. - rcs : :class:`Frame`, optional - The other coordinate system, defaults to world XY. + object_in_lcs : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + An object in local coordinate system of the frame. Returns ------- - :class:`Vector` - A vector in the local coordinate system of the frame. + :class:`Point` or :class:`Vector` or :class:`Frame` + The object in the world coordinate frame. Notes ----- @@ -708,19 +705,51 @@ def global_coords(self, coords_lcs, rcs=None): Examples -------- >>> from compas.geometry import Frame - >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) - >>> pl = Point(1.632, -0.090, 0.573) - >>> pw = f.global_coords(pl) - >>> f.local_coords(pw) + >>> frame = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> pl = Point(1.632, -0.090, 0.573) # point in frame + >>> pw = frame.global_coords(pl) # point in wcf + >>> frame.local_coords(pw) Point(1.632, -0.090, 0.573) """ - if not rcs: - rcs = Frame.worldXY() - T = Transformation.change_basis(self, rcs) - if isinstance(coords_lcs, list): - return Point(*coords_lcs).transformed(T) + T = Transformation.change_basis(self, Frame.worldXY()) + if isinstance(object_in_lcs, list): + return Point(*object_in_lcs).transformed(T) + else: + return object_in_lcs.transformed(T) + + @staticmethod + def local_to_local_coords(frame1, frame2, object_in_frame1): + """Returns the object's coordinates in frame1 in the local coordinates of frame2. + + Parameters + ---------- + frame1 : :class:`Frame` + A frame representing one local coordinate system. + frame2 : :class:`Frame` + A frame representing another local coordinate system. + object_in_frame1 : :class:`Point` or :class:`Vector` or :class:`Frame` or list of float + An object in the coordinate frame1. If you pass a list of float, it is assumed to represent a point. + + Returns + ------- + :class:`Point` or :class:`Vector` or :class:`Frame` + The object in the local coordinate system of frame2. + + Examples + -------- + >>> from compas.geometry import Point, Frame + >>> frame1 = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) + >>> frame2 = Frame([2, 1, 3], [1., 0., 0.], [0., 1., 0.]) + >>> p1 = Point(2, 2, 2) # point in frame1 + >>> p2 = Frame.local_to_local_coords(frame1, frame2, p1) # point in frame2 + >>> Frame.local_to_local_coords(frame2, frame1, p2) + Point(2.000, 2.000, 2.000) + """ + T = Transformation.change_basis(frame1, frame2) + if isinstance(object_in_frame1, list): + return Point(*object_in_frame1).transformed(T) else: - return coords_lcs.transformed(T) + return object_in_frame1.transformed(T) # ========================================================================== # transformations From ae1279ca81c3a1fd302e78f755ae7b534817e07e Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 14:33:13 +0200 Subject: [PATCH 60/73] autopep8 --- src/compas/geometry/transformations/matrices.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compas/geometry/transformations/matrices.py b/src/compas/geometry/transformations/matrices.py index bed3a3ce3769..d440982d52d1 100644 --- a/src/compas/geometry/transformations/matrices.py +++ b/src/compas/geometry/transformations/matrices.py @@ -62,6 +62,7 @@ 'translation_from_matrix', ] + def matrix_determinant(M, check=True): """Calculates the determinant of a square matrix M. @@ -397,6 +398,7 @@ def matrix_from_frame(frame): M[0][3], M[1][3], M[2][3] = frame.point return M + def matrix_from_frame_to_frame(frame_from, frame_to): """Computes a transformation between two frames. @@ -421,6 +423,7 @@ def matrix_from_frame_to_frame(frame_from, frame_to): T2 = matrix_from_frame(frame_to) return multiply_matrices(T2, matrix_inverse(T1)) + def matrix_change_basis(frame_from, frame_to): """Computes a change of basis transformation between two frames. @@ -444,6 +447,7 @@ def matrix_change_basis(frame_from, frame_to): T2 = matrix_from_frame(frame_to) return multiply_matrices(matrix_inverse(T2), T1) + def matrix_from_euler_angles(euler_angles, static=True, axes='xyz'): """Calculates a rotation matrix from Euler angles. From 824e2ff1ec4629abc265c1aa6a55351c043a5d96 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 14:33:29 +0200 Subject: [PATCH 61/73] match to new frame method --- src/compas/geometry/transformations/transformation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compas/geometry/transformations/transformation.py b/src/compas/geometry/transformations/transformation.py index 06cd8f400c02..3da02996d96e 100644 --- a/src/compas/geometry/transformations/transformation.py +++ b/src/compas/geometry/transformations/transformation.py @@ -97,7 +97,7 @@ def copy(self): return cls.from_matrix(self.matrix) def __repr__(self): - s = "[[%s],\n" % ",".join([("%.4f" % n).rjust(10) for n in self.matrix[0]]) + s = "[[%s],\n" % ",".join([("%.4f" % n).rjust(10) for n in self.matrix[0]]) s += " [%s],\n" % ",".join([("%.4f" % n).rjust(10) for n in self.matrix[1]]) s += " [%s],\n" % ",".join([("%.4f" % n).rjust(10) for n in self.matrix[2]]) s += " [%s]]\n" % ",".join([("%.4f" % n).rjust(10) for n in self.matrix[3]]) @@ -237,7 +237,7 @@ def change_basis(cls, frame_from, frame_to): >>> p_f1 = Point(1, 1, 1) # point in f1 >>> p_f1.transformed(T) # point represented in f2 Point(1.395, 0.955, 1.934) - >>> f1.global_coords(p_f1, f2) + >>> Frame.local_to_local_coords(f1, f2, p_f1) Point(1.395, 0.955, 1.934) """ @@ -245,7 +245,6 @@ def change_basis(cls, frame_from, frame_to): T2 = cls.from_frame(frame_to) return cls(multiply_matrices(matrix_inverse(T2.matrix), T1.matrix)) - def inverse(self): """Returns the inverse transformation. From a2ace6c747ef22a0787583ca01cbcec184809f5f Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 14:33:44 +0200 Subject: [PATCH 62/73] fix doctests --- src/compas/geometry/transformations/transformations_numpy.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compas/geometry/transformations/transformations_numpy.py b/src/compas/geometry/transformations/transformations_numpy.py index ddce9c4313bc..680232f6e042 100644 --- a/src/compas/geometry/transformations/transformations_numpy.py +++ b/src/compas/geometry/transformations/transformations_numpy.py @@ -10,6 +10,8 @@ from scipy.linalg import solve +from compas.geometry.basic import cross_vectors + __all__ = [ 'transform_points_numpy', @@ -68,6 +70,7 @@ def transform_vectors_numpy(vectors, T): vectors = homogenize_numpy(vectors, w=0.0) return dehomogenize_numpy(vectors.dot(T.T)) + def transform_frames_numpy(frames, T): """Transform multiple frames with one Transformation usig numpy. @@ -277,5 +280,6 @@ def dehomogenize_and_unflatten_frames_numpy(points_and_vectors): import numpy import math from compas.geometry import Frame + from compas.geometry import Point from compas.geometry import matrix_from_axis_and_angle doctest.testmod(globs=globals()) From 28fd1ef8c8fe704f6917d56cc706f9bb5ea8b2a1 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 14:53:53 +0200 Subject: [PATCH 63/73] removed based on change previous in time --- src/compas_viewers/core/drawing.py | 332 ----------------------------- 1 file changed, 332 deletions(-) delete mode 100644 src/compas_viewers/core/drawing.py diff --git a/src/compas_viewers/core/drawing.py b/src/compas_viewers/core/drawing.py deleted file mode 100644 index 274a45eb192a..000000000000 --- a/src/compas_viewers/core/drawing.py +++ /dev/null @@ -1,332 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division - -from math import cos -from math import sin -from math import pi - -from OpenGL.GLUT import * -from OpenGL.GLU import * -from OpenGL.GLE import * -from OpenGL.GL import * - -from compas.geometry import normalize_vector -from compas.geometry import cross_vectors - -from compas.geometry import global_coords_numpy - - -__all__ = [ - 'draw_points', - 'draw_lines', - 'draw_faces', - 'draw_sphere', - 'draw_points', - 'draw_lines', - 'draw_polygons', - 'draw_cylinders', - 'draw_spheres', - 'draw_texts', -] - - -# ============================================================================== -# arrays -# ------ -# http://www.songho.ca/opengl/gl_vertexarray.html -# https://gist.github.com/ousttrue/c4ae334fc1505cdf4cd7 -# ============================================================================== - - -def draw_arrays(vertices, arrays): - glEnableClientState(GL_VERTEX_ARRAY) - glEnableClientState(GL_COLOR_ARRAY) - - # vertex coordinates flattened - glVertexPointer(3, GL_FLOAT, 0, vertices) - - for primitive, indices, colors, flag_on in arrays: - # primitive => GL_POINTS, GL_LINES, GL_TRIANGLES, GL_QUADS - # colors => RGB colors flattened - # indices => element vertex indices flattened - # flag_on => True or False - if not flag_on: - continue - - glColorPointer(3, GL_FLOAT, 0, colors) - glDrawElements(primitive, len(indices), GL_UNSIGNED_INT, indices) - - glDisableClientState(GL_COLOR_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - - -def draw_triangle_array(vertices, indices, colors): - glEnableClientState(GL_VERTEX_ARRAY) - glEnableClientState(GL_COLOR_ARRAY) - glVertexPointer(3, GL_FLOAT, 0, vertices) - glColorPointer(3, GL_FLOAT, 0, colors) - glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, indices) - glDisableClientState(GL_COLOR_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - - -def draw_line_array(vertices, indices, colors): - glEnableClientState(GL_VERTEX_ARRAY) - glEnableClientState(GL_COLOR_ARRAY) - glVertexPointer(3, GL_FLOAT, 0, vertices) - glColorPointer(3, GL_FLOAT, 0, colors) - glDrawElements(GL_LINES, len(indices), GL_UNSIGNED_INT, indices) - glDisableClientState(GL_COLOR_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - - -# ============================================================================== -# buffers -# ------- -# http://www.songho.ca/opengl/gl_vbo.html -# https://gist.github.com/ousttrue/c4ae334fc1505cdf4cd7 -# ============================================================================== - - -# ============================================================================== -# display lists -# ------------- -# http://www.songho.ca/opengl/gl_displaylist.html -# ============================================================================== - - -# def _make_lists(self): -# self._clear_lists() -# key_xyz = {key: self.mesh.vertex_coordinates(key) for key in self.mesh.vertices()} -# self._make_faces_list(key_xyz) -# self._make_edges_list(key_xyz) -# self._make_vertices_list(key_xyz) - -# def _make_faces_list(self, key_xyz): -# faces = [] -# front = hex_to_rgb(self.settings['faces.color:front']) -# front = list(front) + [1.0] -# back = hex_to_rgb(self.settings['faces.color:back']) -# back = list(back) + [1.0] -# for fkey in self.mesh.faces(): -# faces.append({'points' : [key_xyz[key] for key in self.mesh.face_vertices(fkey)], -# 'color.front' : front, -# 'color.back' : back}) -# self.view.faces = glGenLists(1) -# glNewList(self.view.faces, GL_COMPILE) -# draw_polygons(faces) -# glEndList() - -# def _make_edges_list(self, key_xyz): -# lines = [] -# color = hex_to_rgb(self.settings['edges.color']) -# width = self.settings['edges.width'] -# for u, v in self.mesh.edges(): -# lines.append({'start' : key_xyz[u], -# 'end' : key_xyz[v], -# 'color' : color, -# 'width' : width}) -# self.view.edges = glGenLists(1) -# glNewList(self.view.edges, GL_COMPILE) -# draw_cylinders(lines) -# glEndList() - -# def _make_vertices_list(self, key_xyz): -# points = [] -# color = hex_to_rgb(self.settings['vertices.color']) -# size = self.settings['vertices.size'] -# for key in self.mesh.vertices(): -# points.append({'pos' : key_xyz[key], -# 'color' : color, -# 'size' : size}) -# self.view.vertices = glGenLists(1) -# glNewList(self.view.vertices, GL_COMPILE) -# draw_spheres(points) -# glEndList() - - -# ============================================================================== -# draw -# ============================================================================== - - -def draw_points(points, color=None, size=1): - color = color if color else (0.0, 0.0, 0.0) - glColor3f(*color) - glPointSize(size) - glBegin(GL_POINTS) - for x, y, z in iter(points): - glVertex3f(x, y, z) - glEnd() - - -def draw_lines(lines, color=None, linewidth=1): - color = color if color else (0.0, 0.0, 0.0) - glColor3f(*color) - glLineWidth(linewidth) - glBegin(GL_LINES) - for a, b in iter(lines): - glVertex3f(*a) - glVertex3f(*b) - glEnd() - - -def draw_faces(faces, color=None): - color = color if color else (1.0, 0.0, 0.0, 0.5) - glColor4f(*color) - for face in faces: - glBegin(GL_POLYGON) - for xyz in face: - glVertex3f(*xyz) - glEnd() - - -def draw_sphere(r=1.0): - slices = 17 - stacks = 17 - glColor4f(0.8, 0.8, 0.8, 0.5) - glLineWidth(0.1) - glutWireSphere(r, slices, stacks) - - -def draw_circle(circle, color=None, n=100): - (center, normal), radius = circle - cx, cy, cz = center - a, b, c = normal - - u = -1.0, 0.0, a - v = 0.0, -1.0, b - frame = [center, normalize_vector(u), normalize_vector(v)] - - color = color if color else (1.0, 0.0, 0.0, 0.5) - sector = 2 * pi / n - - glColor4f(*color) - - glBegin(GL_POLYGON) - for i in range(n): - a = i * sector - x = radius * cos(a) - y = radius * sin(a) - z = 0 - - x, y, z = global_coords_numpy(frame, [[x, y, z]]).tolist()[0] - glVertex3f(x, y, z) - glEnd() - - glBegin(GL_POLYGON) - for i in range(n): - a = -i * sector - x = radius * cos(a) - y = radius * sin(a) - z = 0 - x, y, z = global_coords_numpy(frame, [[x, y, z]]).tolist()[0] - glVertex3f(x, y, z) - glEnd() - - -# ============================================================================== -# draw -# ============================================================================== - - -def draw_points(points): - for attr in points: - pos = attr['pos'] - color = attr['color'] - size = attr['size'] - glColor3f(*color) - glPointSize(size) - glBegin(GL_POINTS) - glVertex3f(*pos) - glEnd() - pass - - -def draw_lines(lines): - for attr in lines: - start = attr['start'] - end = attr['end'] - color = attr['color'] - width = attr['width'] - glColor3f(*color) - glLineWidth(width) - glBegin(GL_LINES) - glVertex3f(*start) - glVertex3f(*end) - glEnd() - - -def draw_polygons(polygons): - for attr in polygons: - points = attr['points'] - color_front = attr['color.front'] - color_back = attr['color.back'] - color_wires = attr.get('color.wires', (0.0, 0.0, 0.0, 1.0)) - wires_on = attr.get('wires_on', False) - # front faces - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) - glColor4f(*color_front) - glBegin(GL_POLYGON) - for xyz in points: - glVertex3f(*xyz) - glEnd() - if wires_on: - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) - glColor4f(*color_wires) - glBegin(GL_POLYGON) - for xyz in points: - glVertex3f(*xyz) - glEnd() - # back faces - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) - glColor4f(*color_back) - glBegin(GL_POLYGON) - for xyz in points[::-1]: - glVertex3f(*xyz) - glEnd() - if wires_on: - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) - glColor4f(*color_wires) - glBegin(GL_POLYGON) - for xyz in points[::-1]: - glVertex3f(*xyz) - glEnd() - - -def draw_texts(texts): - for attr in texts: - text = attr['text'] - pos = attr['pos'] - color = attr['color'] - shift = attr['shift'] - glColor4f(color[0], color[1], color[2], color[3]) - glRasterPos3f(pos[0] + shift[0], pos[1] + shift[1], pos[2] + shift[2]) - font = GLUT_BITMAP_HELVETICA_18 - for char in text: - glutBitmapCharacter(font, ord(char)) - - -def draw_spheres(spheres): - for attr in spheres: - glPushMatrix() - glTranslatef(* attr['pos']) - glColor3f(* attr['color']) - glutSolidSphere(attr['size'], 24, 24) - glPopMatrix() - - -def draw_cylinders(cylinders): - for attr in cylinders: - points = [attr['start'], attr['start'], attr['end'], attr['end']] - colors = [attr['color'], attr['color'], attr['color'], attr['color']] - glePolyCylinder(points, colors, attr['width']) - - -# ============================================================================== -# Main -# ============================================================================== - -if __name__ == '__main__': - pass From 887e2ee0ede2530d9d2e5caff62be4b7eaa722e1 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 14:54:06 +0200 Subject: [PATCH 64/73] note not needed anymore --- src/compas/geometry/transformations/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compas/geometry/transformations/__init__.py b/src/compas/geometry/transformations/__init__.py index beb54002139f..7e0bbbff6280 100644 --- a/src/compas/geometry/transformations/__init__.py +++ b/src/compas/geometry/transformations/__init__.py @@ -21,8 +21,6 @@ _NEXT_SPEC = [1, 2, 0, 1] -# todo: separate the numpy version inot separate modules - from .matrices import * # migrated from xforms From 7bb7372e383abc022d3af316b8b4b112cdac2bb4 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 14:54:26 +0200 Subject: [PATCH 65/73] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 799d82d06581..c04d07084144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added `matrix_change_basis`, `Transformation.change_basis` +- Added `matrix_from_frame_to_frame` +- Added non-numpy versions of `global_coords`, `local_coords` +- Added static method `Frame.local_to_local_coords` +- Added `__getitem__`, `__setitem__` and `__eq__` to `Quaternion` +- Added `Vector.scaled` +- Added `transform_frames` and respective helper functions `dehomogenize_and_unflatten_frames`, `homogenize_and_flatten_frames` +- Added `transform_frames_numpy` and respective helper functions `dehomogenize_and_unflatten_frames_numpy`, `homogenize_and_flatten_frames_numpy` + ### Changed - Fixed unguarded import of `numpy` based transformations in mesh package. +- Changed parameters `origin` `uvw` of `global_coords_numpy` and `local_coords_numpy` to `frame` +- Fixed some returns of `Frame` and `Rotation` to use `Vector` or `Quaternion` +- Methods `Frame.represent_point/vector/frame_in_global_coordinates` and `Frame.represent_point/vector/frame_in_local_coordinates` are now `Frame.local_coords` and `Frame.global_coords` ### Removed From 2ed3c6c6aa919b767aede03cdadbb65e01d728d2 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 14:59:36 +0200 Subject: [PATCH 66/73] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c04d07084144..a7c8cac35795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added non-numpy versions of `global_coords`, `local_coords` - Added static method `Frame.local_to_local_coords` - Added `__getitem__`, `__setitem__` and `__eq__` to `Quaternion` -- Added `Vector.scaled` +- Added `Vector.scaled` and `Vector.unitized` - Added `transform_frames` and respective helper functions `dehomogenize_and_unflatten_frames`, `homogenize_and_flatten_frames` - Added `transform_frames_numpy` and respective helper functions `dehomogenize_and_unflatten_frames_numpy`, `homogenize_and_flatten_frames_numpy` From 7aa3f1ea825977104f8ae5d34fa0ab233d511bf1 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Mon, 14 Oct 2019 15:05:40 +0200 Subject: [PATCH 67/73] Update CHANGELOG.md --- CHANGELOG.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7c8cac35795..a22d19587ef0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -### Changed - -### Removed - - -## [0.8.1] 2019-10-01 - -### Added - - Added `matrix_change_basis`, `Transformation.change_basis` - Added `matrix_from_frame_to_frame` - Added non-numpy versions of `global_coords`, `local_coords` @@ -29,7 +20,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Fixed unguarded import of `numpy` based transformations in mesh package. - Changed parameters `origin` `uvw` of `global_coords_numpy` and `local_coords_numpy` to `frame` - Fixed some returns of `Frame` and `Rotation` to use `Vector` or `Quaternion` - Methods `Frame.represent_point/vector/frame_in_global_coordinates` and `Frame.represent_point/vector/frame_in_local_coordinates` are now `Frame.local_coords` and `Frame.global_coords` @@ -37,6 +27,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +## [0.8.1] 2019-10-01 + +### Added + +### Changed + +- Fixed unguarded import of `numpy` based transformations in mesh package. + +### Removed + + ## [0.8.0] 2019-10-01 ### Added From 9dd1fb233619f99bf1c7e274eed6d787eb93a70c Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 15 Oct 2019 15:46:31 +0200 Subject: [PATCH 68/73] r""" instead of """ at the start --- src/compas/geometry/bbox/bbox_numpy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index f4ef0bc5b3e7..c134f4174a0e 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -27,7 +27,7 @@ def oriented_bounding_box_numpy(points): - """Compute the oriented minimum bounding box of a set of points in 3D space. + r"""Compute the oriented minimum bounding box of a set of points in 3D space. Parameters ---------- @@ -62,7 +62,7 @@ def oriented_bounding_box_numpy(points): Examples -------- Generate a random set of points with - :math:`x \\in [0, 10]`, :math:`y \\in [0, 1]` and :math:`z \\in [0, 3]`. + :math:`x \in [0, 10]`, :math:`y \in [0, 1]` and :math:`z \in [0, 3]`. Add the corners of the box such that we now the volume is supposed to be :math:`30.0`. >>> points = numpy.random.rand(10000, 3) From 61583d94e28f6ff8ea9d27fd1f659abf02be3716 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 15 Oct 2019 15:58:30 +0200 Subject: [PATCH 69/73] adding close to basic --- src/compas/geometry/basic.py | 22 ++++++++++++++++++++++ src/compas/geometry/bbox/bbox_numpy.py | 4 +++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/compas/geometry/basic.py b/src/compas/geometry/basic.py index 62c2ae433052..91b364bc7198 100644 --- a/src/compas/geometry/basic.py +++ b/src/compas/geometry/basic.py @@ -8,6 +8,7 @@ from random import uniform __all__ = [ + 'close', 'allclose', 'add_vectors', 'add_vectors_xy', @@ -59,6 +60,27 @@ ] +def close(value1, value2, tol=1e-05): + """Returns True if two values are equal within a tolerance. + + Parameters + ---------- + value1 : float or int + value2 : float or int + tol : float, optional + The tolerance for comparing values. + Default is ``1e-05``. + + Examples + -------- + >>> close(1., 1.001) + False + >>> close(1., 1.001, tol=1e-2) + True + """ + return fabs(value1 - value2) < tol + + def allclose(l1, l2, tol=1e-05): """Returns True if two lists are element-wise equal within a tolerance. diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index c134f4174a0e..b90df5f4acea 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -85,7 +85,7 @@ def oriented_bounding_box_numpy(points): >>> a = length_vector(subtract_vectors(bbox[1], bbox[0])) >>> b = length_vector(subtract_vectors(bbox[3], bbox[0])) >>> c = length_vector(subtract_vectors(bbox[4], bbox[0])) - >>> allclose([a * b * c], [30.]) + >>> close(a * b * c, 30.) True """ @@ -240,12 +240,14 @@ def oriented_bounding_box_xy_numpy(points): if __name__ == "__main__": import numpy + import math from compas.geometry import bounding_box from compas.geometry import subtract_vectors from compas.geometry import length_vector from compas.geometry import Rotation from compas.geometry import transform_points_numpy from compas.geometry import allclose + from compas.geometry import close import doctest doctest.testmod(globs=globals()) From 0a91c502247c6e2f6fb42e375cfae69d18399db4 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 15 Oct 2019 16:06:17 +0200 Subject: [PATCH 70/73] changing global_coords to world_coords --- src/compas/geometry/bbox/bbox_numpy.py | 4 ++-- src/compas/geometry/bestfit/bestfit_numpy.py | 4 ++-- src/compas/geometry/primitives/frame.py | 6 +++--- src/compas/geometry/transformations/transformations.py | 4 ++-- .../geometry/transformations/transformations_numpy.py | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index b90df5f4acea..ca1a59e4b5a8 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -17,7 +17,7 @@ from compas.geometry import local_axes from compas.geometry import local_coords_numpy -from compas.geometry import global_coords_numpy +from compas.geometry import world_coords_numpy __all__ = [ @@ -132,7 +132,7 @@ def oriented_bounding_box_numpy(points): [rmax, smax, tmax], [rmin, smax, tmax], ] - bbox = global_coords_numpy(frame, bbox) + bbox = world_coords_numpy(frame, bbox) volume = v return bbox diff --git a/src/compas/geometry/bestfit/bestfit_numpy.py b/src/compas/geometry/bestfit/bestfit_numpy.py index 24499dc69789..d287de02a875 100644 --- a/src/compas/geometry/bestfit/bestfit_numpy.py +++ b/src/compas/geometry/bestfit/bestfit_numpy.py @@ -14,7 +14,7 @@ # should this not be defined in a different location? from compas.geometry import local_coords_numpy -from compas.geometry import global_coords_numpy +from compas.geometry import world_coords_numpy from compas.numerical import pca_numpy @@ -173,7 +173,7 @@ def f(c): print(residu) - xyz = global_coords_numpy(frame, [[c[0], c[1], 0.0]])[0] + xyz = world_coords_numpy(frame, [[c[0], c[1], 0.0]])[0] o = xyz.tolist() u, v, w = uvw.tolist() diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index f819a15e2794..a478cf8df3d9 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -676,7 +676,7 @@ def local_coords(self, object_in_wcf): >>> frame = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pw = Point(2, 2, 2) # point in wcf >>> pl = frame.local_coords(pw) # point in frame - >>> frame.global_coords(pl) + >>> frame.world_coords(pl) Point(2.000, 2.000, 2.000) """ T = Transformation.change_basis(Frame.worldXY(), self) @@ -685,7 +685,7 @@ def local_coords(self, object_in_wcf): else: return object_in_wcf.transformed(T) - def global_coords(self, object_in_lcs): + def world_coords(self, object_in_lcs): """Returns the object's coordinates in the global coordinate frame. Parameters @@ -707,7 +707,7 @@ def global_coords(self, object_in_lcs): >>> from compas.geometry import Frame >>> frame = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pl = Point(1.632, -0.090, 0.573) # point in frame - >>> pw = frame.global_coords(pl) # point in wcf + >>> pw = frame.world_coords(pl) # point in wcf >>> frame.local_coords(pw) Point(1.632, -0.090, 0.573) """ diff --git a/src/compas/geometry/transformations/transformations.py b/src/compas/geometry/transformations/transformations.py index 470e4902213c..78b9b5460e27 100644 --- a/src/compas/geometry/transformations/transformations.py +++ b/src/compas/geometry/transformations/transformations.py @@ -314,7 +314,7 @@ def local_coords(frame, xyz): return transform_points(xyz, T) -def global_coords(frame, xyz): +def world_coords(frame, xyz): """Convert local coordinates to global coordinates. Parameters @@ -335,7 +335,7 @@ def global_coords(frame, xyz): >>> import numpy as np >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> xyz = [Point(3.726, 4.088, 1.550)] - >>> Point(*global_coords(f, xyz)[0]) + >>> Point(*world_coords(f, xyz)[0]) Point(2.000, 3.000, 5.000) """ from compas.geometry.primitives import Frame diff --git a/src/compas/geometry/transformations/transformations_numpy.py b/src/compas/geometry/transformations/transformations_numpy.py index 680232f6e042..af611b51f8c7 100644 --- a/src/compas/geometry/transformations/transformations_numpy.py +++ b/src/compas/geometry/transformations/transformations_numpy.py @@ -24,7 +24,7 @@ 'dehomogenize_and_unflatten_frames_numpy', 'local_coords_numpy', - 'global_coords_numpy', + 'world_coords_numpy', ] @@ -128,7 +128,7 @@ def local_coords_numpy(frame, xyz): return rst.T -def global_coords_numpy(frame, rst): +def world_coords_numpy(frame, rst): """Convert local coordinates to global (world) coordinates. Parameters @@ -151,7 +151,7 @@ def global_coords_numpy(frame, rst): -------- >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> rst = [Point(3.726, 4.088, 1.550)] - >>> xyz = global_coords_numpy(frame, rst) + >>> xyz = world_coords_numpy(frame, rst) >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) True """ From 807e68aecc1c899036ed13f55fb61f0935f87b41 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Tue, 15 Oct 2019 16:17:17 +0200 Subject: [PATCH 71/73] rename correct_axes to orthonormalize_axes --- src/compas/geometry/transformations/transformations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compas/geometry/transformations/transformations.py b/src/compas/geometry/transformations/transformations.py index 78b9b5460e27..770125e2db62 100644 --- a/src/compas/geometry/transformations/transformations.py +++ b/src/compas/geometry/transformations/transformations.py @@ -43,7 +43,7 @@ __all__ = [ 'local_axes', - 'correct_axes', + 'orthonormalize_axes', 'transform_points', 'transform_vectors', @@ -89,7 +89,7 @@ def local_axes(a, b, c): return normalize_vector(u), normalize_vector(v), normalize_vector(w) -def correct_axes(xaxis, yaxis): +def orthonormalize_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. Parameters @@ -110,7 +110,7 @@ def correct_axes(xaxis, yaxis): -------- >>> xaxis = [1, 4, 5] >>> yaxis = [1, 0, -2] - >>> xaxis, yaxis = correct_axes(xaxis, yaxis) + >>> 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) From fbbfaea2ecc3038c79df56a3c76d8e6dbc7bd8f1 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Fri, 18 Oct 2019 07:44:29 +0200 Subject: [PATCH 72/73] change naming frame, world, local --- src/compas/geometry/bbox/bbox_numpy.py | 8 ++++---- src/compas/geometry/bestfit/bestfit_numpy.py | 8 ++++---- src/compas/geometry/primitives/frame.py | 12 ++++++------ .../geometry/transformations/transformations.py | 8 ++++---- .../transformations/transformations_numpy.py | 12 ++++++------ 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/compas/geometry/bbox/bbox_numpy.py b/src/compas/geometry/bbox/bbox_numpy.py index ca1a59e4b5a8..c952c5e27384 100644 --- a/src/compas/geometry/bbox/bbox_numpy.py +++ b/src/compas/geometry/bbox/bbox_numpy.py @@ -16,8 +16,8 @@ # from scipy.spatial import QhullError from compas.geometry import local_axes -from compas.geometry import local_coords_numpy -from compas.geometry import world_coords_numpy +from compas.geometry import world_to_local_coords_numpy +from compas.geometry import local_to_world_coords_numpy __all__ = [ @@ -115,7 +115,7 @@ def oriented_bounding_box_numpy(points): uvw = local_axes(a, b, c) xyz = points[hull.vertices] frame = [a, uvw[0], uvw[1]] - rst = local_coords_numpy(frame, xyz) + rst = world_to_local_coords_numpy(frame, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt @@ -132,7 +132,7 @@ def oriented_bounding_box_numpy(points): [rmax, smax, tmax], [rmin, smax, tmax], ] - bbox = world_coords_numpy(frame, bbox) + bbox = local_to_world_coords_numpy(frame, bbox) volume = v return bbox diff --git a/src/compas/geometry/bestfit/bestfit_numpy.py b/src/compas/geometry/bestfit/bestfit_numpy.py index d287de02a875..28229122e370 100644 --- a/src/compas/geometry/bestfit/bestfit_numpy.py +++ b/src/compas/geometry/bestfit/bestfit_numpy.py @@ -13,8 +13,8 @@ from scipy.optimize import leastsq # should this not be defined in a different location? -from compas.geometry import local_coords_numpy -from compas.geometry import world_coords_numpy +from compas.geometry import world_to_local_coords_numpy +from compas.geometry import local_to_world_coords_numpy from compas.numerical import pca_numpy @@ -152,7 +152,7 @@ def bestfit_circle_numpy(points): """ o, uvw, _ = pca_numpy(points) frame = [o, uvw[1], uvw[2]] - rst = local_coords_numpy(frame, points) + rst = world_to_local_coords_numpy(frame, points) x = rst[:, 0] y = rst[:, 1] @@ -173,7 +173,7 @@ def f(c): print(residu) - xyz = world_coords_numpy(frame, [[c[0], c[1], 0.0]])[0] + xyz = local_to_world_coords_numpy(frame, [[c[0], c[1], 0.0]])[0] o = xyz.tolist() u, v, w = uvw.tolist() diff --git a/src/compas/geometry/primitives/frame.py b/src/compas/geometry/primitives/frame.py index a478cf8df3d9..c8097c08a893 100644 --- a/src/compas/geometry/primitives/frame.py +++ b/src/compas/geometry/primitives/frame.py @@ -653,7 +653,7 @@ def euler_angles(self, static=True, axes='xyz'): # coordinate frames # ========================================================================== - def local_coords(self, object_in_wcf): + def to_local_coords(self, object_in_wcf): """Returns the object's coordinates in the local coordinate system of the frame. Parameters @@ -675,8 +675,8 @@ def local_coords(self, object_in_wcf): >>> from compas.geometry import Point, Frame >>> frame = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pw = Point(2, 2, 2) # point in wcf - >>> pl = frame.local_coords(pw) # point in frame - >>> frame.world_coords(pl) + >>> pl = frame.to_local_coords(pw) # point in frame + >>> frame.to_world_coords(pl) Point(2.000, 2.000, 2.000) """ T = Transformation.change_basis(Frame.worldXY(), self) @@ -685,7 +685,7 @@ def local_coords(self, object_in_wcf): else: return object_in_wcf.transformed(T) - def world_coords(self, object_in_lcs): + def to_world_coords(self, object_in_lcs): """Returns the object's coordinates in the global coordinate frame. Parameters @@ -707,8 +707,8 @@ def world_coords(self, object_in_lcs): >>> from compas.geometry import Frame >>> frame = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pl = Point(1.632, -0.090, 0.573) # point in frame - >>> pw = frame.world_coords(pl) # point in wcf - >>> frame.local_coords(pw) + >>> pw = frame.to_world_coords(pl) # point in wcf + >>> frame.to_local_coords(pw) Point(1.632, -0.090, 0.573) """ T = Transformation.change_basis(self, Frame.worldXY()) diff --git a/src/compas/geometry/transformations/transformations.py b/src/compas/geometry/transformations/transformations.py index 770125e2db62..1560e25aa195 100644 --- a/src/compas/geometry/transformations/transformations.py +++ b/src/compas/geometry/transformations/transformations.py @@ -285,7 +285,7 @@ def transform_frames(frames, T): return dehomogenize_and_unflatten_frames(multiply_matrices(points_and_vectors, transpose_matrix(T))) -def local_coords(frame, xyz): +def world_to_local_coords(frame, xyz): """Convert global coordinates to local coordinates. Parameters @@ -306,7 +306,7 @@ def local_coords(frame, xyz): >>> import numpy as np >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> xyz = [Point(2, 3, 5)] - >>> Point(*local_coords(f, xyz)[0]) + >>> Point(*world_to_local_coords(f, xyz)[0]) Point(3.726, 4.088, 1.550) """ from compas.geometry.primitives import Frame @@ -314,7 +314,7 @@ def local_coords(frame, xyz): return transform_points(xyz, T) -def world_coords(frame, xyz): +def local_to_world_coords(frame, xyz): """Convert local coordinates to global coordinates. Parameters @@ -335,7 +335,7 @@ def world_coords(frame, xyz): >>> import numpy as np >>> f = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> xyz = [Point(3.726, 4.088, 1.550)] - >>> Point(*world_coords(f, xyz)[0]) + >>> Point(*local_to_world_coords(f, xyz)[0]) Point(2.000, 3.000, 5.000) """ from compas.geometry.primitives import Frame diff --git a/src/compas/geometry/transformations/transformations_numpy.py b/src/compas/geometry/transformations/transformations_numpy.py index af611b51f8c7..fdecce465453 100644 --- a/src/compas/geometry/transformations/transformations_numpy.py +++ b/src/compas/geometry/transformations/transformations_numpy.py @@ -23,8 +23,8 @@ 'homogenize_and_flatten_frames_numpy', 'dehomogenize_and_unflatten_frames_numpy', - 'local_coords_numpy', - 'world_coords_numpy', + 'world_to_local_coords_numpy', + 'local_to_world_coords_numpy', ] @@ -93,7 +93,7 @@ def transform_frames_numpy(frames, T): return dehomogenize_and_unflatten_frames_numpy(points_and_vectors.dot(T.T)) -def local_coords_numpy(frame, xyz): +def world_to_local_coords_numpy(frame, xyz): """Convert global coordinates to local coordinates. Parameters @@ -113,7 +113,7 @@ def local_coords_numpy(frame, xyz): >>> import numpy as np >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> xyz = [Point(2, 3, 5)] - >>> rst = local_coords_numpy(frame, xyz) + >>> rst = world_to_local_coords_numpy(frame, xyz) >>> np.allclose(rst, [[3.726, 4.088, 1.550]], rtol=1e-3) True """ @@ -128,7 +128,7 @@ def local_coords_numpy(frame, xyz): return rst.T -def world_coords_numpy(frame, rst): +def local_to_world_coords_numpy(frame, rst): """Convert local coordinates to global (world) coordinates. Parameters @@ -151,7 +151,7 @@ def world_coords_numpy(frame, rst): -------- >>> frame = Frame([0, 1, 0], [3, 4, 1], [1, 5, 9]) >>> rst = [Point(3.726, 4.088, 1.550)] - >>> xyz = world_coords_numpy(frame, rst) + >>> xyz = local_to_world_coords_numpy(frame, rst) >>> numpy.allclose(xyz, [[2.000, 3.000, 5.000]], rtol=1e-3) True """ From ce8b53dde0542a004aa2dd2072ec13dc313a9b01 Mon Sep 17 00:00:00 2001 From: Romana Rust Date: Fri, 18 Oct 2019 07:51:41 +0200 Subject: [PATCH 73/73] addind functions to __all__ --- src/compas/geometry/transformations/transformations.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compas/geometry/transformations/transformations.py b/src/compas/geometry/transformations/transformations.py index 1560e25aa195..b819efb4611d 100644 --- a/src/compas/geometry/transformations/transformations.py +++ b/src/compas/geometry/transformations/transformations.py @@ -47,6 +47,10 @@ 'transform_points', 'transform_vectors', + 'transform_frames', + + 'local_to_world_coords', + 'world_to_local_coords', 'translate_points', 'translate_points_xy',