# Source code for coordsys

    #
    ##
    ##  This file is part of pyFormex 1.0.5  (Sat Feb 16 10:40:32 CET 2019)
    ##  pyFormex is a tool for generating, manipulating and transforming 3D
    ##  geometrical models by sequences of mathematical operations.
    ##  Home page: http://pyformex.org
    ##  Project page:  http://savannah.nongnu.org/projects/pyformex/
    ##  Copyright 2004-2018 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
    ##  Distributed under the GNU General Public License version 3 or later.
    ##
    ##  This program is free software: you can redistribute it and/or modify
    ##  it under the terms of the GNU General Public License as published by
    ##  the Free Software Foundation, either version 3 of the License, or
    ##  (at your option) any later version.
    ##
    ##  This program is distributed in the hope that it will be useful,
    ##  but WITHOUT ANY WARRANTY; without even the implied warranty of
    ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ##  GNU General Public License for more details.
    ##
    ##  You should have received a copy of the GNU General Public License
    ##  along with this program.  If not, see http://www.gnu.org/licenses/.
    ##
    """Coordinate Systems.

    """
    from __future__ import absolute_import, division, print_function

    import numpy as np

    from pyformex import utils
    from pyformex import arraytools as at
    from pyformex.coords import Coords

    ###########################################################################
    ##
    ##   class CoordSys
    ##
    #########################
    #

    #
    # TODO: This could be improved:
    #   - generalized: cartesian, cylindrical, spherical
    #

    class CoordSys(object):
        """A Cartesian coordinate system in 3D space.

        The coordinate system is stored as a rotation matrix and a translation
        vector, which transform the global coordinate axes into the axes of the
        CoordSys.
        Clearly, the translation vector is the origin of the CoordSys, and the
        rows of the rotation matrix are the unit vectors along the three axes.

        The CoordSys can be initialized in different ways and has only optional
        parameters to achieve this. If none are specified at all, the global
        coordinate axis results.

        Parameters
        ----------
        oab: float array-like (3,3), optional
            Three non-collinear points O, A and B, that define the CoordSys
            in the following way: O is the origin of the coordinate system,
            A is a point along the positive first axis and B is a point B in
            the plane of the first two axes.
        points: float array-like (4,3), optional
            The CoordSys is specified by four points: the first three are
            points on the three coordinate axes, at distance +1 from the
            origin; the fourth point is the origin. The use of this parameter
            is deprecated. It can be replaced with `oab=points[3,0,1]`.
        rot: float array-like (3,3), optional
            Rotation matrix that transforms the global global axes to be
            parallel to the constructed CoordSys. The user is responsible to
            make sure that `rot` is a proper orthonormal rotation matrix.
        trl: float array-like (3,)
            Translation vector that moves the global origin to the origin of the
            CoordSys, in other words, this is the origin of the new CoordSys
            in global coordinates.

        Notes
        -----
        If `oab` is provided, it takes precedence and the other parameters
        are ignored. If not, and `points` is provided, this takes precedence
        and the remaining are ignored. If neither `oab` nor `points` are
        provided, `rot` and `trl` are used and have default values equal
        to the rotation matrix and translation vector of the global coordinatee
        axes.

        A Coords object has a number of attributes that provide quick acces
        to its internal data. Of these, `trl` and `rot` can be used to set
        the data of the CoordSys and thus change the CoordSys in-place.

        Attributes
        ----------
        trl: float array (3,)
            The origin of the CoordSys
        rot: float array (3,3)
            The rotation matrix of the CoordSys
        u: float array (3,)
            The unit vector along the first axis (0).
        v: float array (3,)
            The unit vector along the second axis (1).
        w: float array (3,)
            The unit vector along the third axis (2).
        o: float array (3,)
            The origin of the CoordSys.

        Examples
        --------
        Three points O,A,B in the xy-plane, first two parallel to x-axis,
        third at higher y-value. The resulting CoordSys is global axes
        translated to point O.

        >>> OAB = Coords([[ 2.,1.,0.],[ 5.,1.,0.],[ 0.,3.,0.]])
        >>> print(CoordSys(oab=OAB))
        CoordSys: trl=[ 2. 1. 0.]; rot=[[ 1. 0. 0.]
                                        [ 0. 1. 0.]
                                        [ 0. 0. 1.]]

        Now translate the points so that O is on the z-axis, and rotate the
        points 30 degrees around z-axis.

        >>> OAB = OAB.trl([-2.,-1.,5.]).rot(30)
        >>> print(OAB)
        [[ 0.    0.    5.  ]
         [ 2.6   1.5   5.  ]
         [-2.73  0.73  5.  ]]
        >>> C = CoordSys(oab=OAB)
        >>> print(C)
        CoordSys: trl=[ 0. 0. 5.]; rot=[[ 0.87  0.5   0.  ]
                                        [-0.5   0.87  0.  ]
                                        [ 0.   -0.    1.  ]]

        Reverse axes x and y. The resulting CoordSys is still righthanded. This is
        equivalent to a rotation over 180 degrees around z-axis.

        >>> print(C.reverse(0,1))
        CoordSys: trl=[ 0. 0. 5.]; rot=[[-0.87 -0.5  -0.  ]
                                        [ 0.5  -0.87 -0.  ]
                                        [ 0.   -0.    1.  ]]

        Now rotate C over 150 degrees around z-axis to become parallel with
        the global axes.

        >>> print(C.rotate(150,2))
        CoordSys: trl=[ 0.  0.  5.]; rot=[[ 1.  0.  0.]
                                          [-0.  1.  0.]
                                          [ 0.  0.  1.]]

        """
        def __init__(self,oab=None,points=None,rot=None,trl=None):
            """Initialize the CoordSys"""
            if oab is not None:
                oab = at.checkArray(oab,(3,3),'f')
                rot = at.rotmat(oab)
                trl = oab[0]
            elif points is not None:
                points = at.checkArray(points,(4,3),'f')
                rot = at.normalize(points[:3] - points[3])
                trl = points[3]
            else:
                if rot is None:
                    rot = np.eye(3, 3)
                if trl is None:
                    trl = np.zeros((3,))

            self.rot = rot
            self.trl = trl

        @property
        def trl(self):
            """Return the origin as a (3,) vector"""
            return self._trl

        @trl.setter
        def trl(self, value):
            """Set the origin to (3,) value"""
            self._trl =  at.checkArray(value,shape=(3,),kind='f')

        @property
        def rot(self):
            """Return the (3,3) rotation matrix"""
            return self._rot

        @rot.setter
        def rot(self, value):
            """Set the rotation matrix to (3,3) value"""
            self._rot = at.checkArray(value,shape=(3,3),kind='f')

        @property
        def u(self):
            """Return unit vector along axis 0 (x)"""
            return self.axis(0)

        @property
        def v(self):
            """Return unit vector along axis 1 (y)"""
            return self.axis(1)

        @property
        def w(self):
            """Return unit vector along axis 2 (z)"""
            return self.axis(2)

        @property
        def o(self):
            """Return the origin"""
            return self.trl

        # Some aliases
        origin = o
        axes = rot

        def axis(self,i):
            """Return the unit vector along an axis.

            Parameters
            ----------
            i: int (0,1,2)
                The axis number.

            Returns
            -------
            float array (3,)
                A unit vector along axis `i`.

            Notes
            -----
            If the axis is known in advance, it is more appropriate
            to use one of the u, v or w attributes

            Examples
            --------
            >>> CoordSys().rotate(30).axis(1)
            array([-0.5 ,  0.87,  0.  ], dtype=float32)
            """
            return self.rot[i]

        def points(self):
            """Return origin and endpoints of unit vectors along axes.

            Returns
            -------
            Coords (4,3)
                A Coords object with four points: the endpoints of the
                unit vectors along the three axes of the CoordSys, and
                the origin of the CoordSys.

            Examples
            --------
            >>> CoordSys().rotate(30).points()
            Coords([[ 0.87,  0.5 ,  0.  ],
                    [-0.5 ,  0.87,  0.  ],
                    [ 0.  ,  0.  ,  1.  ],
                    [ 0.  ,  0.  ,  0.  ]])

            """
            return Coords.concatenate([self.axes+self.trl, self.trl])

        # Simple transformation methods
        # These methods modify the object inplace
        # They still return self, so that they can be concatenated

        def _translate(self,trl):
            """Translate the CoordSys.

            Translates the origin of the CoordSys, keeping its axes directions.

            This changes the CoordSys in place!
            """
            trl = checkArray(rot,shape=(3,),kind='f',allow='i')
            self.trl += trl
            return self

        def _rotate(self,rot):
            """Rotate the CoordSys.

            Parameters: (3,3) rotation matrix

            This changes the CoordSys in place!
            """
            rot = checkArray(rot,shape=(3,3),kind='f',allow='i')
            self.rot = np.dot(self.rot,rot)
            return self

        def translate(self,*args,**kargs):
            """Translate the CoordSys like a Coords object.

            Parameters are like :meth:`Coords.translate`.

            Returns
            -------
                A new CoordSys obtained by giving `self` a translation.

            Examples
            --------
            >>> print(CoordSys().translate([-2.,-1.,5.]))
            CoordSys: trl=[-2. -1.  5.]; rot=[[ 1.  0.  0.]
                                              [ 0.  1.  0.]
                                              [ 0.  0.  1.]]

            """
            return CoordSys(points=self.points().translate(*args,**kargs))

        def rotate(self,*args,**kargs):
            """Rotate the CoordSys like a Coords object.

            Parameters are like :meth:`Coords.rotate`.

            Returns
            -------
                A new CoordSys obtained by giving `self` a rotation.

            Examples
            --------
            >>> print(CoordSys().rotate(30))
            CoordSys: trl=[ 0.  0.  0.]; rot=[[ 0.87  0.5   0.  ]
                                              [-0.5   0.87  0.  ]
                                              [ 0.    0.    1.  ]]

            """
            return CoordSys(points=self.points().rotate(*args,**kargs))

        def reverse(self,*axes):
            """Reverse some axes of the CoordSys.

            Parameters
            ----------
            axes: int (0,1,2) or tuple of ints
                The axes to be reversed.

            Note
            ----
            The reversing a single axis (or all three axes) will change a
            right-hand-sided CoordSys into a left-hand-sided one. Therefore,
            this method is normally used only with two axes.

            Examples
            --------
            >>> print(CoordSys().reverse(0,1))
            CoordSys: trl=[ 0.  0.  0.]; rot=[[-1. -0. -0.]
                                              [-0. -1. -0.]
                                              [ 0.  0.  1.]]

            """
            for axis in axes:
                self.rot[axis] *= -1.0
            return self

        def __str__(self):
            """User friendly string representation"""
            return at.stringar("CoordSys: trl=%s; rot=" % self.trl,self.rot)

    class CoordinateSystem(CoordSys):
        @utils.deprecated_by('CoordinateSystem','CoordSys')
        def __init__(coords):
            """Initialize the CoordinateSystem"""
            CoordSys.init(self,points=coords)

    ### End