Source code for pygerber.mathclasses

# -*- coding: utf-8 -*-
from __future__ import annotations

from dataclasses import dataclass
from math import acos, floor, inf, sqrt, tau
from typing import Dict, Tuple

UNIT_MAP_TYPE = Dict[Tuple[float, float], str]

_ByteUnits = {
    (0, 1024): "B",
    (1024, 1024 ** 2): "KiB",
    (1024 ** 2, 1024 ** 3): "MiB",
    (1024 ** 3, 1024 ** 4): "GiB",
    (1024 ** 4, inf): "TiB",
}


[docs]def format_bytes( val: float, ) -> str: abs_val = abs(val) for range, unit in _ByteUnits.items(): if range[0] <= abs_val < range[1]: return f"{val/range[0]:.1f} {unit}"
[docs]@dataclass class Vector2D: x: float y: float
[docs] def as_tuple(self): return (self.x, self.y)
[docs] def as_tuple_3D(self, z: float = 0.0): return (self.x, self.y, z)
def __add__(self, other: Vector2D) -> Vector2D: return Vector2D(other.x + self.x, other.y + self.y) def __sub__(self, other: Vector2D) -> Vector2D: return Vector2D(self.x - other.x, self.y - other.y) def __mul__(self, other: float) -> Vector2D: return Vector2D(self.x * other, self.y * other) def __truediv__(self, other: float) -> Vector2D: return Vector2D(self.x / other, self.y / other)
[docs] def length(self): return sqrt(self.x ** 2 + self.y ** 2)
[docs] def dot(self, other: Vector2D) -> float: return self.x * other.x + self.y * other.y
[docs] def normalize(self): length = self.length() return Vector2D( self.x / length, self.y / length, )
[docs] def floor(self): return Vector2D(floor(self.x), int(self.y))
UNIT_VECTOR_X = Vector2D(1, 0) UNIT_VECTOR_Y = Vector2D(0, 1)
[docs]def angle_from_zero(vector: Vector2D) -> float: raw_angle = acos(vector.dot(UNIT_VECTOR_X) / vector.length()) if vector.y > 0: return raw_angle else: return tau - raw_angle
[docs]@dataclass class BoundingBox: left: float upper: float right: float lower: float def __init__(self, x0: float, y0: float, x1: float, y1: float) -> None: """ Coordinates are called x0, x1, y0, y1 on purpose - any of x's can be left. During assignment smaller one will be picked to be left, bigger one to be right. Same thing applies to y's. Order of coordinates doesn't mether as long as axes are preserved. """ self.left = min(x0, x1) self.right = max(x0, x1) self.upper = max(y0, y1) self.lower = min(y0, y1)
[docs] def as_tuple(self) -> Tuple[float]: """Tuple (left, upper, right, lower)""" return self.left, self.upper, self.right, self.lower
[docs] def as_tuple_y_inverse(self) -> Tuple[float]: return self.left, self.lower, self.right, self.upper
[docs] def contains(self, other: BoundingBox) -> bool: return ( self.left <= other.left and self.upper >= other.upper and self.right >= other.upper and self.lower <= other.lower )
[docs] def padded(self, delta) -> BoundingBox: return BoundingBox( self.left - delta, self.upper + delta, self.right + delta, self.lower - delta, )
[docs] def include_point(self, point: Vector2D) -> BoundingBox: return BoundingBox( min(self.left, point.x), max(self.upper, point.y), min(self.right, point.x), max(self.lower, point.y), )
[docs] def height(self) -> float: return abs(self.upper - self.lower)
[docs] def width(self) -> float: return abs(self.right - self.left)
[docs] def transform(self, vector: Vector2D) -> BoundingBox: return BoundingBox( self.left + vector.x, self.upper + vector.y, self.right + vector.x, self.lower + vector.y, )
def __add__(self, other): if isinstance(other, BoundingBox): return BoundingBox( min(self.left, other.left), max(self.upper, other.upper), max(self.right, other.right), min(self.lower, other.lower), ) elif isinstance(other, Vector2D): return self.transform(other) else: raise TypeError(f"Type {type(other).__qualname__} addition not supported.")