Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update docs and add convenience methods to coordinate classes. #60

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 264 additions & 11 deletions pyems/coordinate.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,33 @@ def val_inside(val: float, bound1: float, bound2: float) -> bool:


class Coordinate2:
""""""
""" A 2 dimentional coordinate
"""

def __init__(self, x: float, y: float):
""""""
self._x = x
self._y = y

@property
def x(self) -> float:
""""""
return self._x

@property
def y(self) -> float:
""""""
return self._y

@x.setter
def x(self, value: float) -> None:
""""""
self._x = value

@y.setter
def y(self, value: float) -> None:
""""""
self._y = value

def __getitem__(self, key):
""""""
return self._int_to_coord(key)

def __setitem__(self, key, val):
""""""
if key == 0:
self.x = val
elif key == 1:
Expand All @@ -58,13 +52,105 @@ def __setitem__(self, key, val):
raise ValueError("Invalid index.")

def __eq__(self, other: Coordinate2) -> bool:
""""""
if self.x == other.x and self.y == other.y:
return True
return False

def __add__(self, other: Coordinate2) -> Coordinate2:
"""
Positive translation by another Coordinate2.
This does not modify the coordinate.

:param other: The coordinate to translate by
:type other: Coordinate2
:return: The translated coordinate.
:rtype: Coordinate2
"""
t = CSTransform()
t.Translate((other.x, other.y, 0))
return self.transform(t)
Comment on lines +59 to +71
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of overloading operators for coordinate2, etc. but my personal preference is that it be done without transforms. Transforms are a bit overkill here and make it a bit harder to read IMO. So, something like

    def __add__(self, other: Coordinate2) -> Coordinate2:
        """
        Positive translation by another Coordinate2.
        This does not modify the coordinate.
        :param other: The coordinate to translate by
        :type other: Coordinate2
        :return: The translated coordinate.
        :rtype: Coordinate2
        """
        return Coordinate2(self.x + other.x, self.y + other.y)

Same goes for the rest.


def __sub__(self, other: Coordinate2) -> Coordinate2:
"""
Negative translation by another Coordinate2.
This does not modify the coordinate.

:param other: The coordinate to translate by
:type other: Coordinate2
:return: The translated coordinate.
:rtype: Coordinate2
"""
t = CSTransform()
t.Translate((-other.x, -other.y, 0))
return self.transform(t)

def __mul__(self, scale: float | Coordinate2) -> Coordinate2:
"""
Scale by a specified value.
This does not modify the coordinate.

:param scale: The scale factor. If a float, scale x and y by this value.
If a Coordinate2, use separate x and y scale factors.
:type scale: float | Coordinate2
:return: The scaled coordinate.
:rtype: Coordinate2
"""
if isinstance(scale, Coordinate2):
scale = scale.x, scale.y, 1
t = CSTransform()
t.Scale(scale)
return self.transform(t)

def __truediv__(self, scale: float | Coordinate2) -> Coordinate2:
"""
Scale but a specified value. This is equivalent to A * (1 / scale)
This does not modify the coordinate.

:param scale: The scale factor. If a float, scale x and y by this value.
If a Coordinate2, use separate x and y scale factors.
:type scale: float | Coordinate2
:return: The scaled coordinate.
:rtype: Coordinate2
"""
if isinstance(scale, Coordinate2):
scale = 1/scale.x, 1/scale.y, 1
else:
scale = 1/scale
t = CSTransform()
t.Scale(scale)
return self.transform(t)

def __neg__(self) -> Coordinate2:
"""
Negative both coordinate values.
This does not modify the coordinate.

:return: The negated coordinate
:rtype: Coordinate2
"""
return Coordinate2(-self.x, -self.y)

def __abs__(self) -> Coordinate2:
"""
Absolute value of both coodinate values.
This does not modify the coordinate.

:return: The new coordinate.
:rtype: Coordinate2
"""
return Coordinate2(abs(self.x), abs(self.y))
Comment on lines +133 to +141
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine. I was initially a bit on the fence because I normally think of the absolute value of a coordinate being its distance from the origin, but I think you're right this makes sense here.


def __round__(self, ndigits: int=0) -> Coordinate2:
"""
Round to a specified precision. Calls round_prec()
This does not modify the coordinate.

:return: A new coordinate with the specified precision.
:rtype: Coordinate2
"""
return self.round_prec(ndigits)
Comment on lines +143 to +151
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, but let's get rid of round_prec altogether as its no longer necessary. Just inline it's functionality here and replaces calls to round_prec with round elsewhere. Are you fine doing that?


def _int_to_coord(self, val) -> float:
""""""
if val == 0:
return self.x
elif val == 1:
Expand All @@ -76,6 +162,9 @@ def coordinate_list(self) -> np.ndarray:
"""
Retrieve an array of coordinate values for use by openems,
which requires coordinates in a form that can be indexed.

:return: The x, y coordinates
:rtype: np.ndarray
"""
return np.array([self._x, self._y])

Expand All @@ -85,17 +174,33 @@ def transform(self, transform: CSTransform) -> Coordinate2:
simply returns a new transformed coordinate. If you want to
replace the old coordinate you must assign it to the result of
this function.

:param transform: The transformation
:type transform: CSTransform
:return: The new coordinate
:rtype: Coordinate2
"""
clist = list(self.coordinate_list())
clist.append(0)
tclist = transform.Transform(clist)
return Coordinate2(tclist[0], tclist[1])

def round_prec(self, prec: int) -> Coordinate2:
""""""
"""
Round the coordinate to a specified precision.
This does not modify the coordinate.

:param prec: The number of decimal places to round.
:type prec: int
:return: A new coordinate with the rounded values.
:rtype: Coordinate2
"""
clist = self.coordinate_list()
clist = np.around(clist, prec)
return Coordinate2(clist[0], clist[1])

def __repr__(self) -> str:
return f'Coordinate2({self.x}, {self.y})'


def list_center2(coords: List[Coordinate2]) -> Coordinate2:
Expand Down Expand Up @@ -189,6 +294,100 @@ def __eq__(self, other: Coordinate3):
if self.x == other.x and self.y == other.y and self.z == other.z:
return True
return False

def __add__(self, other: Coordinate3) -> Coordinate3:
"""
Positive translation by another Coordinate3.
This does not modify the coordinate.

:param other: The coordinate to translate by
:type other: Coordinate3
:return: The translated coordinate.
:rtype: Coordinate3
"""
t = CSTransform()
t.Translate((other.x, other.y, other.z))
return self.transform(t)

def __sub__(self, other: Coordinate3) -> Coordinate3:
"""
Negative translation by another Coordinate3.
This does not modify the coordinate.

:param other: The coordinate to translate by
:type other: Coordinate3
:return: The translated coordinate.
:rtype: Coordinate3
"""
t = CSTransform()
t.Translate((-other.x, -other.y, -other.z))
return self.transform(t)

def __mul__(self, scale: float | Coordinate3) -> Coordinate3:
"""
Scale by a specified value.
This does not modify the coordinate.

:param scale: The scale factor. If a float, scale x and y by this value.
If a Coordinate3, use separate x, y, and z scale factors.
:type scale: float | Coordinate3
:return: The scaled coordinate.
:rtype: Coordinate3
"""
if isinstance(scale, Coordinate3):
scale = scale.x, scale.y, scale.z
t = CSTransform()
t.Scale(scale)
return self.transform(t)

def __truediv__(self, scale: float | Coordinate3) -> Coordinate3:
"""
Scale but a specified value. This is equivalent to A * (1 / scale)
This does not modify the coordinate.

:param scale: The scale factor. If a float, scale x and y by this value.
If a Coordinate3, use separate x, y, and z scale factors.
:type scale: float | Coordinate3
:return: The scaled coordinate.
:rtype: Coordinate3
"""
if isinstance(scale, Coordinate3):
scale = 1/scale.x, 1/scale.y, 1/scale.z
else:
scale = 1/scale
t = CSTransform()
t.Scale(scale)
return self.transform(t)

def __neg__(self) -> Coordinate3:
"""
Negative both coordinate values.
This does not modify the coordinate.

:return: The negated coordinate
:rtype: Coordinate3
"""
return Coordinate3(-self.x, -self.y, -self.z)

def __abs__(self) -> Coordinate3:
"""
Absolute value of both coodinate values.
This does not modify the coordinate.

:return: The new coordinate.
:rtype: Coordinate3
"""
return Coordinate3(abs(self.x), abs(self.y), abs(self.z))

def __round__(self, ndigits: int=0) -> Coordinate3:
"""
Round to a specified precision. Calls round_prec()
This does not modify the coordinate.

:return: A new coordinate with the specified precision.
:rtype: Coordinate3
"""
return self.round_prec(ndigits)

def _int_to_coord(self, val):
""""""
Expand All @@ -200,6 +399,9 @@ def _int_to_coord(self, val):
return self.z
else:
raise ValueError("Invalid index.")

def __repr__(self) -> str:
return f'Coordinate3({self.x}, {self.y}, {self.z})'


class Axis:
Expand Down Expand Up @@ -355,6 +557,9 @@ def __init__(self, min_corner: C2Tuple, max_corner: C2Tuple):
""""""
self._min_corner = c2_maybe_tuple(min_corner)
self._max_corner = c2_maybe_tuple(max_corner)

def __repr__(self) -> str:
return f'Box2({self.min_corner}, {self.max_corner})'

@property
def min_corner(self) -> Coordinate2:
Expand Down Expand Up @@ -439,6 +644,30 @@ def has_zero_dim(self) -> bool:
):
return True
return False

def __add__(self, coord: Coordinate2) -> Box2:
"""
Translate by a specified coordinate.
This does not modify the box

:param coord: The coordinate to transform
:type coord: Coordinate2
:return: The translated box
:rtype: Box2
"""
return Box2(self.min_corner + coord, self.max_corner + coord)

def __sub__(self, coord: Coordinate2) -> Box2:
"""
Translate by a specified coordinate.
This does not modify the box

:param coord: The coordinate to transform
:type coord: Coordinate2
:return: The translated box
:rtype: Box2
"""
return Box2(self.min_corner - coord, self.max_corner - coord)


class Box3:
Expand Down Expand Up @@ -618,6 +847,30 @@ def has_zero_dim(self) -> bool:
):
return True
return False

def __add__(self, coord: Coordinate3) -> Box3:
"""
Translate by a specified coordinate.
This does not modify the box

:param coord: The coordinate to transform
:type coord: Coordinate3
:return: The translated box
:rtype: Box3
"""
return Box3(self.min_corner + coord, self.max_corner + coord)

def __sub__(self, coord: Coordinate3) -> Box3:
"""
Translate by a specified coordinate.
This does not modify the box

:param coord: The coordinate to transform
:type coord: Coordinate3
:return: The translated box
:rtype: Box3
"""
return Box3(self.min_corner - coord, self.max_corner - coord)


def box_overlap(box1: Box3, box2: Box3) -> bool:
Expand Down