Newer
Older
####################################################################################################
#
# Patro - A Python library to make patterns for fashion design
#
# 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
####################################################################################################
from Patro.Common.Math.Functions import sign, trignometric_clamp #, is_in_trignometric_range
from .Primitive import Primitive2D
####################################################################################################
class Vector2DBase(Primitive2D):
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
##############################################
def clone(self):
return self.__class__(self)
##############################################
@property
def v(self):
return self._v
# @v.setter
# def v(self, value):
# self._v = value
return self.__data_type__(self._v[0])
return self.__data_type__(self._v[1])
def x(self, x):
self._v[0] = x
def y(self, y):
self._v[1] = y
def copy(self):
""" Return a copy of self """
return self.__class__(self._v)
return self.__class__.__name__ + str(self.v)
return bool(self._v.any())
def __len__(self):
return 2
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
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
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))
##############################################
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))
def almost_equal(v1, v2, rtol=1e-05, atol=1e-08, equal_nan=False):
return np.allclose(v1, v2, rtol, atol, equal_nan)
def magnitude_square(self):
""" Return the square of the magnitude of the vector """
return np.dot(self._v, self._v)
def magnitude(self):
""" Return the magnitude of the vector """
return math.sqrt(self.magnitude_square())
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
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
""" Return a new vector equal to self rotated of angle degree in the counter clockwise
direction
"""
radians = math.radians(angle)
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))
""" 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))
""" 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))
""" 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))
def tan(self):
""" Return the tangent """
# RuntimeWarning: divide by zero encountered in double_scalars
return self.y / self.x
def inverse_tan(self):
""" Return the inverse tangent """
return self.x / self.y
def dot(self, other):
""" Return the dot product of self with other """
return float(np.dot(self._v, other.v))
def cross(self, other):
""" Return the cross product of self with other """
return float(np.cross(self._v, other.v))
def is_parallel(self, other):
""" Self is parallel with other """
return round(self.cross(other), 7) == 0
def is_orthogonal(self, other):
""" Self is orthogonal with other """
return round(self.dot(other), 7) == 0
def cos_with(self, direction):
""" Return the cosinus of self with direction """
cos = direction.dot(self) / (direction.magnitude() * self.magnitude())
return trignometric_clamp(cos)
def projection_on(self, direction):
""" Return the projection of self on direction """
return direction.dot(self) / direction.magnitude()
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)
def deviation_with(self, direction):
""" Return the deviation of self with other """
return direction.cross(self) / direction.magnitude()
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 """
@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)))
@staticmethod
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
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
def normalise(self):
""" Normalise the vector """
self._v /= self.magnitude()
def to_normalised(self):
""" Return a normalised vector """
return NormalisedVector2D(self._v / self.magnitude())
return Vector2DInt(np.rint(self._v))
####################################################################################################
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)
####################################################################################################
class HomogeneousVector2D(Vector2D):
""" 2D Homogeneous Coordinate Vector """
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
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)