####################################################################################################
#
# PyValentina - A Python implementation of Valentina Pattern Drafting Software
# 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 ArithmeticInterval import Interval2D, IntervalInt2D
####################################################################################################
from Valentina.Math.Functions import sign, trignometric_clamp #, is_in_trignometric_range
from .Primitive import Primitive2D
####################################################################################################
[docs]class Vector2DBase(Primitive2D):
__data_type__ = None
##############################################
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
##############################################
[docs] 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])
@x.setter
def x(self, x):
self._v[0] = x
@y.setter
def y(self, y):
self._v[1] = y
##############################################
[docs] def copy(self):
""" Return a copy of self """
return self.__class__(self._v)
##############################################
def __repr__(self):
return self.__class__.__name__ + str(self.v)
##############################################
def __nonzero__(self):
return bool(self._v.any())
##############################################
def __len__(self):
return 2
##############################################
def __iter__(self):
return iter(self._v)
##############################################
def __getitem__(self, a_slice):
return self._v[a_slice]
##############################################
def __setitem__(self, index, value):
self._v[index] = value
##############################################
def __eq__(v1, v2):
""" self == other """
return np.array_equal(v1.v, v2.v)
##############################################
def __add__(self, other):
""" Return a new vector equal to the addition of self and other """
return self.__class__(self._v + other.v)
##############################################
def __iadd__(self, other):
""" Add other to self """
self._v += other.v
return self
##############################################
def __sub__(self, other):
""" Return a new vector """
return self.__class__(self._v - other.v)
##############################################
def __isub__(self, other):
""" Return a new vector equal to the subtraction of self and other """
self._v -= other.v
return self
##############################################
def __pos__(self):
""" Return a new vector equal to self """
return self.__class__(self._v)
##############################################
def __neg__(self):
""" Return a new vector equal to the negation of self """
return self.__class__(-self._v)
##############################################
def __abs__(self):
""" Return a new vector equal to abs of self """
return self.__class__(np.abs(self._v))
##############################################
[docs] def to_int_list(self):
return [int(x) for x in self._v]
####################################################################################################
[docs]class Vector2DInt(Vector2DBase):
__data_type__ = np.int
##############################################
[docs] def bounding_box(self):
x, y = self.x, self.y
return IntervalInt2D((x, x) , (y, y))
####################################################################################################
[docs]class Vector2DFloatBase(Vector2DBase):
__data_type__ = np.float
##############################################
[docs] def bounding_box(self):
x, y = self.x, self.y
return Interval2D((x, x) , (y, y))
##############################################
[docs] 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)
##############################################
[docs] def magnitude_square(self):
""" Return the square of the magnitude of the vector """
return np.dot(self._v, self._v)
##############################################
[docs] def magnitude(self):
""" Return the magnitude of the vector """
return math.sqrt(self.magnitude_square())
##############################################
[docs] 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
##############################################
[docs] 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)
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))
##############################################
[docs] def normal(self):
""" Return a new vector equal to self rotated of 90 degree in the counter clockwise
direction
"""
xp = -self._v[1]
yp = self._v[0]
return self.__class__((xp, yp))
##############################################
[docs] def anti_normal(self):
""" Return a new vector equal to self rotated of 90 degree in the clockwise direction
"""
xp = self._v[1]
yp = -self._v[0]
return self.__class__((xp, yp))
##############################################
[docs] 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))
##############################################
[docs] def tan(self):
""" Return the tangent """
# RuntimeWarning: divide by zero encountered in double_scalars
return self.y / self.x
##############################################
[docs] def inverse_tan(self):
""" Return the inverse tangent """
return self.x / self.y
##############################################
[docs] def dot(self, other):
""" Return the dot product of self with other """
return float(np.dot(self._v, other.v))
##############################################
[docs] def cross(self, other):
""" Return the cross product of self with other """
return float(np.cross(self._v, other.v))
##############################################
[docs] def is_parallel(self, other):
""" Self is parallel with other """
return round(self.cross(other), 7) == 0
##############################################
[docs] def is_orthogonal(self, other):
""" Self is orthogonal with other """
return round(self.dot(other), 7) == 0
##############################################
[docs] def cos_with(self, direction):
""" Return the cosinus of self with direction """
cos = direction.dot(self) / (direction.magnitude() * self.magnitude())
return trignometric_clamp(cos)
##############################################
[docs] def projection_on(self, direction):
""" Return the projection of self on direction """
return direction.dot(self) / direction.magnitude()
##############################################
[docs] 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)
##############################################
[docs] def deviation_with(self, direction):
""" Return the deviation of self with other """
return direction.cross(self) / direction.magnitude()
##############################################
[docs] 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)
####################################################################################################
[docs]class Vector2D(Vector2DFloatBase):
""" 2D Vector """
##############################################
@staticmethod
[docs] 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)))
##############################################
@staticmethod
[docs] def middle(p0, p1):
""" Return the middle point. """
return Vector2D(p0 + p1) * .5
##############################################
def __mul__(self, scale):
""" Return a new vector equal to the self scaled by scale """
return self.__class__(scale * self._v)
##############################################
def __imul__(self, scale):
""" Scale self by scale """
self._v *= scale
return self
##############################################
def __truediv__(self, scale):
""" Return a new vector equal to the self dvivided by scale """
return self.__class__(self._v / scale)
##############################################
def __itruediv__(self, scale):
""" Scale self by 1/scale """
self._v /= scale
return self
##############################################
[docs] def normalise(self):
""" Normalise the vector """
self._v /= self.magnitude()
##############################################
[docs] def to_normalised(self):
""" Return a normalised vector """
return NormalisedVector2D(self._v / self.magnitude())
##############################################
[docs] def rint(self):
return Vector2DInt(np.rint(self._v))
####################################################################################################
[docs]class NormalisedVector2D(Vector2DFloatBase):
""" 2D Normalised Vector """
##############################################
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")
##############################################
def __mul__(self, scale):
""" Return a new vector equal to the self scaled by scale """
return Vector2D(scale * self._v)
####################################################################################################
[docs]class HomogeneousVector2D(Vector2D):
""" 2D Homogeneous Coordinate Vector """
##############################################
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
##############################################
[docs] def to_vector(self):
return Vector2D(self._v)