Skip to content

Commit

Permalink
Typings, isort, docs, etc
Browse files Browse the repository at this point in the history
  • Loading branch information
simoncozens committed May 9, 2024
1 parent 6cfab6d commit 892d0d7
Show file tree
Hide file tree
Showing 18 changed files with 285 additions and 201 deletions.
48 changes: 33 additions & 15 deletions beziers/affinetransformation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import math
from typing import Optional

from beziers.point import Point
from beziers.utils import isclose


class AffineTransformation(object):
"""A 2D affine transformation represented as a 3x3 matrix."""

def __init__(self, matrix=None):
if not matrix:
self.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
Expand All @@ -23,7 +28,8 @@ def __str__(self):
m[2][2],
)

def apply(self, other):
def apply(self, other: "AffineTransformation") -> None:
"""Modify this transformation to have the effect of self x other."""
m1 = self.matrix
m2 = other.matrix
self.matrix = [
Expand All @@ -44,7 +50,8 @@ def apply(self, other):
],
]

def apply_backwards(self, other):
def apply_backwards(self, other: "AffineTransformation") -> None:
"""Modify this transformation to have the effect of other x self."""
m2 = self.matrix
m1 = other.matrix
self.matrix = [
Expand All @@ -66,30 +73,39 @@ def apply_backwards(self, other):
]

@classmethod
def translation(klass, vector):
def translation(klass, vector: Point) -> "AffineTransformation":
"""Create a transformation that translates by the given vector."""
return klass([[1, 0, vector.x], [0, 1, vector.y], [0, 0, 1]])

def translate(self, vector):
def translate(self, vector: Point):
"""Modify this transformation to include a translation by the given vector."""
self.apply_backwards(type(self).translation(vector))

@classmethod
def scaling(klass, factorX, factorY=None):
if not factorY:
factorY = factorX
return klass([[factorX, 0, 0], [0, factorY, 0], [0, 0, 1]])
def scaling(
klass, factor_x: float, factor_y: Optional[float] = None
) -> "AffineTransformation":
"""Create a transformation that scales by the given factor(s)."""
if not factor_y:
factor_y = factor_x
return klass([[factor_x, 0, 0], [0, factor_y, 0], [0, 0, 1]])

def scale(self, factorX, factorY=None):
self.apply_backwards(type(self).scaling(factorX, factorY))
def scale(self, factor_x: float, factor_y: Optional[float] = None) -> None:
"""Modify this transformation to include a scaling by the given factor(s)."""
self.apply_backwards(type(self).scaling(factor_x, factor_y))

@classmethod
def reflection(klass):
def reflection(klass) -> "AffineTransformation":
"""Create a transformation that reflects across the x-axis."""
return klass([[-1, 0, 0], [0, 1, 0], [0, 0, 1]])

def reflect(self):
def reflect(self) -> None:
"""Modify this transformation to include a reflection across the x-axis."""
self.apply_backwards(type(self).reflection)

@classmethod
def rotation(klass, angle):
def rotation(klass, angle: float) -> "AffineTransformation":
"""Create a transformation that rotates by the given angle (in radians)."""
return klass(
[
[math.cos(-angle), math.sin(-angle), 0],
Expand All @@ -98,10 +114,12 @@ def rotation(klass, angle):
]
)

def rotate(self, angle):
def rotate(self, angle: float):
"""Modify this transformation to include a rotation by the given angle (in radians)."""
self.apply_backwards(type(self).rotation(angle))

def invert(self):
def invert(self) -> None:
"""Modify this transformation to be its inverse."""
m = self.matrix
det = (
m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
Expand Down
46 changes: 28 additions & 18 deletions beziers/boundingbox.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,74 @@
from typing import Protocol, Union

from beziers.point import Point


class SupportsBounds(Protocol):
def bounds(self) -> "BoundingBox": ...


class BoundingBox:
"""A representation of a rectangle within the Beziers world,
used to store bounding boxes."""
used to store bounding boxes.
Args:
bl (Point): The bottom-left corner of the bounding box.
tr (Point): The top-right corner of the bounding box.
"""

def __init__(self):
self.bl = None
self.tr = None

def __str__(self):
return "BB[%s -> %s]" % (self.bl, self.tr)

"""Determine the bounding box - returns the bounding box itself."""
return f"BB[{self.bl} -> {self.tr}]"

def bounds(self):
def bounds(self) -> "BoundingBox":
"""Determine the bounding box - returns the bounding box itself."""
return self

@property
def area(self):
def area(self) -> float:
"""Returns the area of the bounding box."""
vec = self.tr - self.bl
return vec.x * vec.y

@property
def left(self):
def left(self) -> float:
"""Returns the X coordinate of the left edge of the box."""
return self.bl.x

@property
def right(self):
def right(self) -> float:
"""Returns the X coordinate of the right edge of the box."""
return self.tr.x

@property
def top(self):
def top(self) -> float:
"""Returns the Y coordinate of the top edge of the box."""
return self.tr.y

@property
def bottom(self):
def bottom(self) -> float:
"""Returns the Y coordinate of the bottom edge of the box."""
return self.bl.y

@property
def width(self):
def width(self) -> float:
"""Returns the width of the box."""
return self.tr.x - self.bl.x

@property
def height(self):
def height(self) -> float:
"""Returns the height of the box."""
return self.tr.y - self.bl.y

@property
def centroid(self):
def centroid(self) -> Point:
"""Returns a `Point` representing the centroid of the box."""
return Point((self.left + self.right) * 0.5, (self.top + self.bottom) * 0.5)

def extend(self, other):
def extend(self, other: Union["BoundingBox", Point, SupportsBounds]) -> None:
"""Add an object to the bounding box. Object can be a `Point`,
another `BoundingBox`, or something which has a `bounds()` method."""
if isinstance(other, Point):
Expand All @@ -81,14 +91,14 @@ def extend(self, other):
# Try getting its bb
self.extend(other.bounds())

def translated(self, point):
def translated(self, point: Point) -> "BoundingBox":
"""Returns a new BoundingBox translated by the vector"""
bb2 = BoundingBox()
bb2.bl = self.bl + point
bb2.tr = self.tr + point
return bb2

def includes(self, point):
def includes(self, point: Point) -> bool:
"""Returns True if the point is included in this bounding box."""
return (
self.bl.x >= point.x
Expand All @@ -97,7 +107,7 @@ def includes(self, point):
and self.tr.y <= point.y
)

def overlaps(self, other):
def overlaps(self, other: "BoundingBox") -> bool:
"""Returns True if the given bounding box overlaps with this bounding box."""
if other.left > self.right:
return False
Expand All @@ -109,7 +119,7 @@ def overlaps(self, other):
return False
return True

def addMargin(self, size):
def addMargin(self, size: float) -> None:
"""Adds a few units of margin around the edges of the bounding box."""
self.bl = self.bl + Point(-size, -size)
self.tr = self.tr + Point(size, size)
Loading

0 comments on commit 892d0d7

Please sign in to comment.