"""
Defines the base classes used in the bool2d submodule
* SubSetR2: Base class for all classes
* EmptyShape: Represents an empty set
* WholeShape: Represents the entire plane
"""
from __future__ import annotations
from abc import abstractmethod
from copy import copy
from typing import Iterable, Tuple, Union
from ..geometry.point import Point2D
from ..scalar.angle import Angle
from ..scalar.reals import Real
class SubSetR2:
"""
Base class for all SubSetR2 classes
"""
def __init__(self):
pass
@abstractmethod
def __invert__(self) -> SubSetR2:
"""Invert shape"""
def __or__(self, other: SubSetR2) -> SubSetR2:
"""Union shapes"""
return Future.unite((self, other))
def __and__(self, other: SubSetR2) -> SubSetR2:
"""Intersection shapes"""
return Future.intersect((self, other))
@abstractmethod
def __copy__(self) -> SubSetR2:
"""Creates a shallow copy"""
@abstractmethod
def __deepcopy__(self, memo) -> SubSetR2:
"""Creates a deep copy"""
def __neg__(self) -> SubSetR2:
"""Invert the SubSetR2"""
return ~self
def __add__(self, other: SubSetR2):
"""Union of SubSetR2"""
return self | other
def __mul__(self, value: SubSetR2):
"""Intersection of SubSetR2"""
return self & value
def __sub__(self, value: SubSetR2):
"""Subtraction of SubSetR2"""
return self & (~value)
def __xor__(self, other: SubSetR2):
"""XOR of SubSetR2"""
return (self - other) | (other - self)
def __repr__(self) -> str: # pragma: no cover
return str(self)
@abstractmethod
def move(self, vector: Point2D) -> SubSetR2:
"""
Moves/translate entire shape by an amount
Parameters
----------
point : Point2D
The amount to move
:return: The same instance
:rtype: SubSetR2
Example use
-----------
>>> from shapepy import Primitive
>>> circle = Primitive.circle()
>>> circle.move(1, 2)
"""
raise NotImplementedError
@abstractmethod
def scale(self, amount: Union[Real, Tuple[Real, Real]]) -> SubSetR2:
"""
Scales entire subset by an amount
Parameters
----------
amount : Real | Tuple[Real, Real]
The amount to scale in horizontal and vertical direction
:return: The same instance
:rtype: SubSetR2
Example use
-----------
>>> from shapepy import Primitive
>>> circle = Primitive.circle()
>>> circle.scale((2, 3))
"""
raise NotImplementedError
@abstractmethod
def rotate(self, angle: Angle) -> SubSetR2:
"""
Rotates entire shape around the origin by an amount
Parameters
----------
angle : Angle
The amount to rotate around origin
:return: The same instance
:rtype: SubSetR2
Example use
-----------
>>> from shapepy import Primitive
>>> circle = Primitive.circle()
>>> circle.rotate(Angle.degrees(90))
"""
raise NotImplementedError
[docs]
class EmptyShape(SubSetR2):
"""EmptyShape is a singleton class to represent an empty shape
Example use
-----------
>>> from shapepy import EmptyShape
>>> empty = EmptyShape()
>>> print(empty)
EmptyShape
>>> print(float(empty)) # Area
0.0
>>> (0, 0) in empty
False
"""
__instance = None
def __new__(cls):
if cls.__instance is None:
cls.__instance = super(EmptyShape, cls).__new__(cls)
return cls.__instance
def __copy__(self) -> EmptyShape:
return self
def __deepcopy__(self, memo) -> EmptyShape:
return self
def __or__(self, other: SubSetR2) -> SubSetR2:
return copy(other)
def __and__(self, other: SubSetR2) -> SubSetR2:
return self
def __sub__(self, other: SubSetR2) -> SubSetR2:
return self
def __invert__(self) -> SubSetR2:
return WholeShape()
def __contains__(self, other: SubSetR2) -> bool:
return self is other
def __str__(self) -> str:
return "EmptyShape"
def __bool__(self) -> bool:
return False
[docs]
def move(self, _):
return self
[docs]
def scale(self, _):
return self
[docs]
def rotate(self, _):
return self
class WholeShape(SubSetR2):
"""WholeShape is a singleton class to represent all plane
Example use
-----------
>>> from shapepy import WholeShape
>>> whole = WholeShape()
>>> print(whole)
WholeShape
>>> print(float(whole)) # Area
inf
>>> (0, 0) in whole
True
"""
__instance = None
def __new__(cls):
if cls.__instance is None:
cls.__instance = super(WholeShape, cls).__new__(cls)
return cls.__instance
def __copy__(self) -> WholeShape:
return self
def __deepcopy__(self, memo) -> WholeShape:
return self
def __or__(self, other: SubSetR2) -> WholeShape:
return self
def __and__(self, other: SubSetR2) -> SubSetR2:
return copy(other)
def __invert__(self) -> SubSetR2:
return EmptyShape()
def __contains__(self, other: SubSetR2) -> bool:
return True
def __str__(self) -> str:
return "WholeShape"
def __sub__(self, other: SubSetR2) -> SubSetR2:
return ~other
def __bool__(self) -> bool:
return True
def move(self, _):
return self
def scale(self, _):
return self
def rotate(self, _):
return self
class Future:
"""
Class that stores methods that are further defined.
They are overrided by other methods in __init__.py file
Although the classes EmptyShape and WholeShape don't need the
child classes to make the union/intersection, or to verify
if a SubSetR2 instance is inside WholeShape for example,
the command bellow
>>> (0, 0) in WholeShape()
that checks if a point is inside WholeShape, needs the conversion
to a SinglePoint instance, which is not defined in this file
Another example is the definition of `__add__` method,
which must call the function `simplify` after the `__or__`.
The function `simplify` must know all the childs classes of SubSetR2,
but it would lead to a circular import.
A solution, which was considered worst is:
* Place all the classes and the functions in a single file,
so all the classes know all the other classes and we avoid
a circular import.
"""
@staticmethod
def unite(subsets: Iterable[SubSetR2]) -> SubSetR2:
"""
Computes the union of some SubSetR2 instances
This function is overrided by a function defined
in the `shapepy.bool2d.boolean.py` file
"""
raise NotImplementedError
@staticmethod
def intersect(subsets: Iterable[SubSetR2]) -> SubSetR2:
"""
Computes the intersection of some SubSetR2 instances
This function is overrided by a function defined
in the `shapepy.bool2d.boolean.py` file
"""
raise NotImplementedError