added geometry module

This commit is contained in:
saji 2024-09-19 14:32:02 -05:00
parent 66b492e147
commit fbb39a85e5

124
src/groovylight/geom.py Normal file
View file

@ -0,0 +1,124 @@
# Geometry-related classes and functions. Manipulations and
# generation of panel-layout metadata for use in the HDL.
from enum import Enum
from dataclasses import dataclass
@dataclass(frozen=True, order=True)
class Coord:
"""Coordinate class. Uses computer-graphics standard coordinate system,
where X=0, Y=0 is top left. +X goes right. +Y goes down.
"""
x: int
y: int
def __post_init__(self):
if self.x < 0 or self.y < 0:
raise RuntimeError("x and y must both be >= 0")
@dataclass(frozen=True)
class BBox:
"""Bounding box class. Captures the top left coordinate and bottom right coordinate
of an object"""
topleft: Coord
bottomright: Coord
def __post_init__(self):
if not self.topleft < self.bottomright:
raise RuntimeError("topleft must be strictly less than bottomright")
def contains(self, c: Coord) -> bool:
return c > self.topleft and c < self.bottomright
@dataclass(frozen=True)
class DisplayDimensions:
"""Represents the dimensions of a display string, in length x height. Notably
this is in local coordinates to the display. The display top left is 0,0.
Uses length/height notation to separate it from coord
"""
length: int
height: int
class DisplayRotation(Enum):
"""Display rotation enums. The names indicate the general direction
of data flow. most normal displays (LCDs, CRTs) are LEFTRIGHT,
since they scan left-to-right, top to bottom.
Note that the direction of subsequent lines is not always clear.
They are enumerated below:
LEFTRIGHT -> next line is below it (-Y)
UPDOWN -> next line is to the LEFT (-X)
DOWNUP -> next line is to the RIGHT (+X)
RIGHTLEFT -> next line is above it (+Y)
Generally, prefer LEFTRIGHT/UPDOWN over other rotations.
"""
LEFTRIGHT = 0
UPDOWN = 1
DOWNUP = 2
RIGHTLEFT = 3 # why are you like this.
@dataclass(frozen=True)
class _DisplayString:
"""Internal class to represent a string of HUB75 displays.
position: (X,Y) coordinates of the local top-left of the display
dimensions: (length, height) local-coordinate dimensions of the display.
rotation: DisplayRotation: the orientation of the display.
"""
position: Coord
dimensions: DisplayDimensions
rotation: DisplayRotation
@property
def bbox(self) -> BBox:
"""Returns the bounding box of the display based on the dimensions, position, and rotation."""
x = self.position.x
y = self.position.y
l = self.dimensions.length
h = self.dimensions.height
match self.rotation:
case DisplayRotation.LEFTRIGHT:
return BBox(Coord(x, y), Coord(x + l, y + h))
case DisplayRotation.UPDOWN:
return BBox(Coord(x - h, y), Coord(x, y + l))
case DisplayRotation.DOWNUP:
return BBox(Coord(x, y + l), Coord(x + h, y))
case DisplayRotation.RIGHTLEFT:
return BBox(Coord(x - l, y - h), Coord(x, y))
def contains_pix(self, coord: Coord) -> bool:
"""Checks if the given coordinate is inside this display."""
return self.bbox.contains(coord)
class DisplayGeometry:
"""Represents a display based on several strings in different positions.
"""
def __init__(self, *, strict: bool = False):
self.strict = strict
pass
def add_string(self, position: (int, int), rot: int, dimensions: (int, int)):
"""Add a new string to the display. This new string is located at
a specific point, and has a direction, along with dimension that reveal
the number of address lines (typically 64, with 1:32 selection so 5 address
bits) and the total length of the string which is used to size the line
buffers.
When in strict mode, this method may throw an exception if this new string
will overlap with an existing string.
"""