Source code for KicadModTree.Vector

# KicadModTree 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.
#
# KicadModTree 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 kicad-footprint-generator. If not, see < http://www.gnu.org/licenses/ >.
#
# (C) 2016-2018 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>

from __future__ import division
from builtins import round

import warnings

from KicadModTree.util.kicad_util import formatFloat
from math import sqrt, sin, cos, hypot, atan2, degrees, radians


[docs]class Vector2D(object): r"""Representation of a 2D Vector in space :Example: >>> from KicadModTree import * >>> Vector2D(0, 0) >>> Vector2D([0, 0]) >>> Vector2D((0, 0)) >>> Vector2D({'x': 0, 'y':0}) >>> Vector2D(Vector2D(0, 0)) """
[docs] def __init__(self, coordinates=None, y=None): # parse constructor if coordinates is None: coordinates = {} elif type(coordinates) in [int, float]: if y is not None: coordinates = [coordinates, y] else: raise TypeError('you have to give x and y coordinate') elif isinstance(coordinates, Vector2D): # convert Vector2D as well as Vector3D to dict coordinates = coordinates.to_dict() # parse vectors with format: Vector2D({'x':0, 'y':0}) if type(coordinates) is dict: self.x = float(coordinates.get('x', 0.)) self.y = float(coordinates.get('y', 0.)) return # parse vectors with format: Vector2D([0, 0]) or Vector2D((0, 0)) if type(coordinates) in [list, tuple]: if len(coordinates) == 2: self.x = float(coordinates[0]) self.y = float(coordinates[1]) return else: raise TypeError('invalid list size (2 elements expected)') raise TypeError('invalid parameters given')
[docs] def round_to(self, base): r"""Round to a specific base (like it's required for a grid) :param base: base we want to round to :return: rounded point >>> from KicadModTree import * >>> Vector2D(0.1234, 0.5678).round_to(0.01) """ if base == 0 or base is None: return self.__copy__() return Vector2D([round(v / base) * base for v in self])
[docs] def distance_to(self, value): r"""Distance between this and another point :param value: the other point :return: distance between self and other point """ other = Vector2D.__arithmetic_parse(value) d = other - self return hypot(d.x, d.y)
@staticmethod def __arithmetic_parse(value): if isinstance(value, Vector2D): return value elif type(value) in [int, float]: return Vector2D([value, value]) else: return Vector2D(value)
[docs] def __eq__(self, other): if not isinstance(self, other.__class__): return False return self.x == other.x and self.y == other.y
[docs] def __ne__(self, other): return not self.__eq__(other)
[docs] def __add__(self, value): other = Vector2D.__arithmetic_parse(value) return Vector2D({'x': self.x + other.x, 'y': self.y + other.y})
[docs] def __iadd__(self, value): other = Vector2D.__arithmetic_parse(value) self.x += other.x self.y += other.y return self
[docs] def __neg__(self): return Vector2D({'x': -self.x, 'y': -self.y})
[docs] def __sub__(self, value): other = Vector2D.__arithmetic_parse(value) return Vector2D({'x': self.x - other.x, 'y': self.y - other.y})
[docs] def __isub__(self, value): other = Vector2D.__arithmetic_parse(value) self.x -= other.x self.y -= other.y return self
[docs] def __mul__(self, value): other = Vector2D.__arithmetic_parse(value) return Vector2D({'x': self.x * other.x, 'y': self.y * other.y})
[docs] def __div__(self, value): other = Vector2D.__arithmetic_parse(value) return Vector2D({'x': self.x / other.x, 'y': self.y / other.y})
[docs] def __truediv__(self, obj): return self.__div__(obj)
[docs] def to_dict(self): return {'x': self.x, 'y': self.y}
[docs] def render(self, formatcode): warnings.warn( "render is deprecated, read values directly instead", DeprecationWarning ) return formatcode.format(x=formatFloat(self.x), y=formatFloat(self.y))
[docs] def __repr__(self): return "Vector2D (x={x}, y={y})".format(**self.to_dict())
[docs] def __str__(self): return "(x={x}, y={y})".format(**self.to_dict())
[docs] def __getitem__(self, key): if key == 0 or key == 'x': return self.x if key == 1 or key == 'y': return self.y raise IndexError('Index {} is out of range'.format(key))
[docs] def __setitem__(self, key, item): if key == 0 or key == 'x': self.x = item elif key == 1 or key == 'y': self.y = item else: raise IndexError('Index {} is out of range'.format(key))
[docs] def __len__(self): return 2
[docs] def __iter__(self): yield self.x yield self.y
[docs] def __copy__(self): return Vector2D(self.x, self.y)
[docs] def rotate(self, angle, origin=(0, 0), use_degrees=True): r""" Rotate vector around given origin :params: * *angle* (``float``) rotation angle * *origin* (``Vector2D``) origin point for the rotation. default: (0, 0) * *use_degrees* (``boolean``) rotation angle is given in degrees. default:True """ op = Vector2D(origin) if use_degrees: angle = radians(angle) temp = op.x + cos(angle) * (self.x - op.x) - sin(angle) * (self.y - op.y) self.y = op.y + sin(angle) * (self.x - op.x) + cos(angle) * (self.y - op.y) self.x = temp return self
[docs] def to_polar(self, origin=(0, 0), use_degrees=True): r""" Get polar representation of the vector :params: * *origin* (``Vector2D``) origin point for polar conversion. default: (0, 0) * *use_degrees* (``boolean``) angle in degrees. default:True """ op = Vector2D(origin) diff = self - op radius = hypot(diff.x, diff.y) angle = atan2(diff.y, diff.x) if use_degrees: angle = degrees(angle) return (radius, angle)
[docs] @staticmethod def from_polar(radius, angle, origin=(0, 0), use_degrees=True): r""" Generate a vector from its polar representation :params: * *radius* (``float``) lenght of the vector * *angle* (``float``) angle of the vector * *origin* (``Vector2D``) origin point for polar conversion. default: (0, 0) * *use_degrees* (``boolean``) angle in degrees. default:True """ if use_degrees: angle = radians(angle) x = radius * cos(angle) y = radius * sin(angle) return Vector2D({'x': x, 'y': y})+Vector2D(origin)
[docs] def to_homogeneous(self): r""" Get homogeneous representation """ return Vector3D(self.x, self.y, 1)
[docs] @staticmethod def from_homogeneous(source): r""" Recover 2d vector from its homogeneous representation :params: * *source* (``Vector3D``) 3d homogeneous representation """ return Vector2D(source.x/source.z, source.y/source.z)
[docs]class Vector3D(Vector2D): r"""Representation of a 3D Vector in space :Example: >>> from KicadModTree import * >>> Vector3D(0, 0, 0) >>> Vector3D([0, 0, 0]) >>> Vector3D((0, 0, 0)) >>> Vector3D({'x': 0, 'y':0, 'z':0}) >>> Vector3D(Vector2D(0, 0)) >>> Vector3D(Vector3D(0, 0, 0)) """
[docs] def __init__(self, coordinates=None, y=None, z=None): # we don't need a super constructor here # parse constructor if coordinates is None: coordinates = {} elif type(coordinates) in [int, float]: if y is not None: if z is not None: coordinates = [coordinates, y, z] else: coordinates = [coordinates, y] else: raise TypeError('you have to give at least x and y coordinate') elif isinstance(coordinates, Vector2D): # convert Vector2D as well as Vector3D to dict coordinates = coordinates.to_dict() # parse vectors with format: Vector2D({'x':0, 'y':0}) if type(coordinates) is dict: self.x = float(coordinates.get('x', 0.)) self.y = float(coordinates.get('y', 0.)) self.z = float(coordinates.get('z', 0.)) return # parse vectors with format: Vector3D([0, 0]), Vector3D([0, 0, 0]) or Vector3D((0, 0)), Vector3D((0, 0, 0)) if type(coordinates) in [list, tuple]: if len(coordinates) >= 2: self.x = float(coordinates[0]) self.y = float(coordinates[1]) else: raise TypeError('invalid list size (to small)') if len(coordinates) == 3: self.z = float(coordinates[2]) else: self.z = 0. if len(coordinates) > 3: raise TypeError('invalid list size (to big)') else: raise TypeError('dict or list type required')
[docs] def round_to(self, base): r"""Round to a specific base (like it's required for a grid) :param base: base we want to round to :return: rounded point >>> from KicadModTree import * >>> Vector3D(0.123, 0.456, 0.789).round_to(0.01) """ if base == 0 or base is None: return self.__copy__() return Vector3D([round(v / base) * base for v in self])
[docs] def cross_product(self, other): other = Vector3D.__arithmetic_parse(other) return Vector3D({ 'x': self.y*other.z - self.z*other.y, 'y': self.z*other.x - self.x*other.z, 'z': self.x*other.y - self.y*other.x})
[docs] def dot_product(self, other): other = Vector3D.__arithmetic_parse(other) return self.x*other.x + self.y*other.y + self.z*other.z
@staticmethod def __arithmetic_parse(value): if isinstance(value, Vector3D): return value elif type(value) in [int, float]: return Vector3D([value, value, value]) else: return Vector3D(value)
[docs] def __eq__(self, other): if not isinstance(self, other.__class__): return False return self.x == other.x and self.y == other.y and self.z == other.z
[docs] def __ne__(self, other): return not self.__eq__(other)
[docs] def __add__(self, value): other = Vector3D.__arithmetic_parse(value) return Vector3D({'x': self.x + other.x, 'y': self.y + other.y, 'z': self.z + other.z})
[docs] def __iadd__(self, value): other = Vector2D.__arithmetic_parse(value) self.x += other.x self.y += other.y self.z += other.z return self
[docs] def __neg__(self): return Vector2D({'x': -self.x, 'y': -self.y, 'z': -self.z})
[docs] def __sub__(self, value): other = Vector3D.__arithmetic_parse(value) return Vector3D({'x': self.x - other.x, 'y': self.y - other.y, 'z': self.z - other.z})
[docs] def __isub__(self, value): other = Vector2D.__arithmetic_parse(value) self.x -= other.x self.y -= other.y self.z -= other.z return self
[docs] def __mul__(self, value): other = Vector3D.__arithmetic_parse(value) return Vector3D({'x': self.x * other.x, 'y': self.y * other.y, 'z': self.z * other.z})
[docs] def __div__(self, value): other = Vector3D.__arithmetic_parse(value) return Vector3D({'x': self.x / other.x, 'y': self.y / other.y, 'z': self.z / other.z})
[docs] def __truediv__(self, obj): return self.__div__(obj)
[docs] def to_dict(self): return {'x': self.x, 'y': self.y, 'z': self.z}
[docs] def render(self, formatcode): warnings.warn( "render is deprecated, read values directly instead", DeprecationWarning ) return formatcode.format(x=formatFloat(self.x), y=formatFloat(self.y), z=formatFloat(self.z))
[docs] def __repr__(self): return "Vector3D (x={x}, y={y}, z={z})".format(**self.to_dict())
[docs] def __str__(self): return "(x={x}, y={y}, z={z})".format(**self.to_dict())
[docs] def __getitem__(self, key): if key == 0 or key == 'x': return self.x if key == 1 or key == 'y': return self.y if key == 2 or key == 'z': return self.z raise IndexError('Index {} is out of range'.format(key))
[docs] def __setitem__(self, key, item): if key == 0 or key == 'x': self.x = item elif key == 1 or key == 'y': self.y = item elif key == 2 or key == 'z': self.z = item else: raise IndexError('Index {} is out of range'.format(key))
[docs] def __len__(self): return 3
[docs] def __iter__(self): yield self.x yield self.y yield self.z
[docs] def __copy__(self): return Vector3D(self.x, self.y, self.z)