Skip to content
Vector.py 14.7 KiB
Newer Older
####################################################################################################
#
Fabrice Salvaire's avatar
Fabrice Salvaire committed
# Patro - A Python implementation of Valentina Pattern Drafting Software
Fabrice Salvaire's avatar
Fabrice Salvaire committed
# Copyright (C) 2017 Fabrice Salvaire
#
# 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/>.
#
####################################################################################################

####################################################################################################

import math

import numpy as np

from IntervalArithmetic import Interval2D, IntervalInt2D
####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
from Patro.Math.Functions import sign, trignometric_clamp #, is_in_trignometric_range
from .Primitive import Primitive2D

####################################################################################################

class Vector2DBase(Primitive2D):

    __data_type__ = None

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __init__(self, *args):

        """
        Example of usage::

          Vector(1, 3)
          Vector((1, 3))
          Vector([1, 3])
          Vector(iterable)
          Vector(vector)

        """

        array = self._check_arguments(args)

        # call __getitem__ once
        self._v = np.array(array[:2], dtype=self.__data_type__)

    ##############################################

    def _check_arguments(self, args):

        size = len(args)
        if size == 1:
            array = args[0]
        elif size == 2:
            array = args
        else:
            raise ValueError("More than 2 arguments where given")

        if not (np.iterable(array) and len(array) == 2):
            raise ValueError("Argument must be iterable and of length 2")

        return array

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def clone(self):
        return self.__class__(self)

    ##############################################
    @property
    def v(self):
        return self._v

    # @v.setter
    # def v(self, value):
    #     self._v = value

    @property
    def x(self):
        return self.__data_type__(self._v[0])

    @property
    def y(self):
        return self.__data_type__(self._v[1])
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def copy(self):

        """ Return a copy of self """

        return self.__class__(self._v)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __repr__(self):

        return self.__class__.__name__ + str(self.v)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __nonzero__(self):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __len__(self):

        return 2

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __iter__(self):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __getitem__(self, a_slice):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __setitem__(self, index, value):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __eq__(v1, v2):

        """ self == other """

        return np.array_equal(v1.v, v2.v)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __add__(self, other):

        """ Return a new vector equal to the addition of self and other """

        return self.__class__(self._v + other.v)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __iadd__(self, other):

        """ Add other to self """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __sub__(self, other):

        """ Return a new vector """

        return self.__class__(self._v - other.v)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __isub__(self, other):

        """ Return a new vector equal to the subtraction of self and other """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __pos__(self):

        """ Return a new vector equal to self """

        return self.__class__(self._v)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __neg__(self):

        """ Return a new vector equal to the negation of self """

        return self.__class__(-self._v)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __abs__(self):

        """ Return a new vector equal to abs of self """

        return self.__class__(np.abs(self._v))

    ##############################################

    def to_int_list(self):

        return [int(x) for x in self._v]

####################################################################################################

class Vector2DInt(Vector2DBase):

    __data_type__ = np.int

    ##############################################

    def bounding_box(self):

        x, y = self.x, self.y
        return IntervalInt2D((x, x) , (y, y))

####################################################################################################

class Vector2DFloatBase(Vector2DBase):

    __data_type__ = np.float

    ##############################################

    def bounding_box(self):

        x, y = self.x, self.y
        return Interval2D((x, x) , (y, y))

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################
    def almost_equal(v1, v2, rtol=1e-05, atol=1e-08, equal_nan=False):

        """ self ~= other """

        return np.allclose(v1, v2, rtol, atol, equal_nan)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def magnitude_square(self):

        """ Return the square of the magnitude of the vector """

        return np.dot(self._v, self._v)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def magnitude(self):

        """ Return the magnitude of the vector """

        return math.sqrt(self.magnitude_square())

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def orientation(self):

        """ Return the orientation in degree """

        #
        # 2 | 1
        # - + -
        # 4 | 3
        #
        #       | 1    | 2         | 3    | 4         |
        # x     | +    | -         | +    | -         |
        # y     | +    | +         | -    | -         |
        # tan   | +    | -         | -    | +         |
        # atan  | +    | -         | -    | +         |
        # theta | atan | atan + pi | atan | atan - pi |
        #

        if not bool(self):
            raise NameError("Null Vector")
        if self.x == 0:
            return math.copysign(90, self.y)
        elif self.y == 0:
            return 0 if self.x >= 0 else 180
        else:
            orientation = math.degrees(math.atan(self.tan()))
            if self.x < 0:
                if self.y > 0:
                    orientation += 180
                else:
                    orientation -= 180
            return orientation

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def rotate(self, angle, counter_clockwise=True):

        """ Return a new vector equal to self rotated of angle degree in the counter clockwise
        direction
        """

        radians = math.radians(angle)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        if not counter_clockwise:
            radians = -radians
        c = math.cos(radians)
        s = math.sin(radians)

        # Fixme: np matrice
        xp = c * self._v[0] -s * self._v[1]
        yp = s * self._v[0] +c * self._v[1]

        return self.__class__((xp, yp))

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def normal(self):

        """ Return a new vector equal to self rotated of 90 degree in the counter clockwise
        direction
        """


        return self.__class__((xp, yp))

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def anti_normal(self):

        """ Return a new vector equal to self rotated of 90 degree in the clockwise direction
        """


        return self.__class__((xp, yp))

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def parity(self):

        """ Return a new vector equal to self rotated of 180 degree
        """

        # parity
        xp = -self._v[0]
        yp = -self._v[1]

        return self.__class__((xp, yp))

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def tan(self):

        """ Return the tangent """

        # RuntimeWarning: divide by zero encountered in double_scalars
        return self.y / self.x

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def inverse_tan(self):

        """ Return the inverse tangent """

        return self.x / self.y

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def dot(self, other):

        """ Return the dot product of self with other """

        return float(np.dot(self._v, other.v))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def cross(self, other):

        """ Return the cross product of self with other """

        return float(np.cross(self._v, other.v))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def is_parallel(self, other):

        """ Self is parallel with other """

        return round(self.cross(other), 7) == 0

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def is_orthogonal(self, other):

        """ Self is orthogonal with other """

        return round(self.dot(other), 7) == 0

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def cos_with(self, direction):

        """ Return the cosinus of self with direction """

        cos = direction.dot(self) / (direction.magnitude() * self.magnitude())

        return trignometric_clamp(cos)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def projection_on(self, direction):

        """ Return the projection of self on direction """

        return direction.dot(self) / direction.magnitude()

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def sin_with(self, direction):

        """ Return the sinus of self with other """

        # turn from direction to self
        sin = direction.cross(self) / (direction.magnitude() * self.magnitude())

        return trignometric_clamp(sin)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def deviation_with(self, direction):

        """ Return the deviation of self with other """

        return direction.cross(self) / direction.magnitude()

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def orientation_with(self, direction):

        # Fixme: check all cases
        # -> angle_with

        """ Return the angle of self on direction """

        angle = math.acos(self.cos_with(direction))
        angle_sign = sign(self.sin_with(direction))

        return angle_sign * math.degrees(angle)

####################################################################################################

class Vector2D(Vector2DFloatBase):

    """ 2D Vector """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    @staticmethod
    def from_angle(angle):

        """ Create the unitary vector (cos(angle), sin(angle)).  The *angle* is in degree. """

        rad = math.radians(angle)

        return Vector2D((math.cos(rad), math.sin(rad)))

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    @staticmethod
    def middle(p0, p1):

        """ Return the middle point. """

        return Vector2D(p0 + p1) * .5

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __mul__(self, scale):

        """ Return a new vector equal to the self scaled by scale """

        return self.__class__(scale * self._v)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __imul__(self, scale):

        """ Scale self by scale """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __truediv__(self, scale):

        """ Return a new vector equal to the self dvivided by scale """

        return self.__class__(self._v / scale)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __itruediv__(self, scale):

        """ Scale self by 1/scale """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def normalise(self):

        """ Normalise the vector """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def to_normalised(self):

        """ Return a normalised vector """

        return NormalisedVector2D(self._v / self.magnitude())
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################
        return Vector2DInt(np.rint(self._v))

####################################################################################################

class NormalisedVector2D(Vector2DFloatBase):

    """ 2D Normalised Vector """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __init__(self, *args):

        super(NormalisedVector2D, self).__init__(*args)

        #! if self.magnitude() != 1.:
        #!     raise ValueError("Magnitude != 1")

        # if not (is_in_trignometric_range(self.x) and
        #         is_in_trignometric_range(self.y)):
        #     raise ValueError("Values must be in trignometric range")

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __mul__(self, scale):

        """ Return a new vector equal to the self scaled by scale """

        return Vector2D(scale * self._v)

####################################################################################################

class HomogeneousVector2D(Vector2D):

    """ 2D Homogeneous Coordinate Vector """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __init__(self, vector):

        # self._v = np.ones((3), dtype=self.__data_type__)
        # self._v[:2] = vector.v[:2]

        self._v = np.array(vector[:2]) # to keep compatibility
        self._w = 1

    ##############################################

    @property
    def v(self):
        return np.array(((self.x), (self.y), (self._w)), dtype=self.__data_type__)

    # @v.setter
    # def v(self, value):
    #     self._v = value

    @property
    def w(self):
        return self._w

    @w.setter
    def w(self, value):
        self._w = value

    ##############################################

    def to_vector(self):

        return Vector2D(self._v)