Source code for svgen.cartesian.point

"""
A module for interfacing with points.
"""

# built-in
from math import isclose, sqrt
from typing import NamedTuple, Union

# internal
from svgen.attribute import SimpleAttribute
from svgen.cartesian.mutate import Translation


[docs] class PointAttrs(NamedTuple): """A grouping for 'x' and 'y' attributes.""" x: SimpleAttribute y: SimpleAttribute @property def x_val(self) -> float: """Get the 'x' coordinate for these attributes.""" return float(self.x.value) @property def y_val(self) -> float: """Get the 'y' coordinate for these attributes.""" return float(self.y.value)
[docs] class Point(NamedTuple): """A definition of a point in a Cartesian coordinate system.""" x: float = 0.0 y: float = 0.0 center: bool = False idx: int = -1
[docs] def translate(self, move: Union[Translation, float], *args) -> "Point": """Move a point by a given translation.""" move = ( Translation(move, *args) if not isinstance(move, Translation) else move ) return Point(self.x + move.dx, self.y + move.dy, self.center, self.idx)
[docs] def polar( self, count: float, radius: float = 1.0, degrees: bool = True, ccw: bool = True, ) -> "Point": """Create a new point from a polar-coordinate translation.""" return self.translate( Translation.polar(count, radius=radius, degrees=degrees, ccw=ccw) )
[docs] def with_index(self, idx: int) -> "Point": """Get a new point with the specified index, from an existing point.""" return Point(self.x, self.y, False, idx)
@property def x_attr(self) -> SimpleAttribute: """Get the 'x' attribute for this point.""" attr = "x" if self.center: attr = "c" + attr elif self.idx >= 0: attr += str(self.idx) return SimpleAttribute(attr, str(self.x)) @property def y_attr(self) -> SimpleAttribute: """Get the 'y' attribute for this point.""" attr = "y" if self.center: attr = "c" + attr elif self.idx >= 0: attr += str(self.idx) return SimpleAttribute(attr, str(self.y)) @property def attrs(self) -> PointAttrs: """Get the 'x' and 'y' attributes for this point.""" return PointAttrs(self.x_attr, self.y_attr) def __eq__(self, other: object) -> bool: """Determine if two points are the same.""" if not isinstance(other, Point): return NotImplemented # Use an absolute tolerance for comparisons with zero. return isclose(self.x, other.x, abs_tol=1e-09) and isclose( self.y, other.y, abs_tol=1e-09 )
[docs] def distance(self, point: "Point") -> float: """Compute the distance from this point to another.""" run = point.x - self.x rise = point.y - self.y return sqrt(run * run + rise * rise)
[docs] def to_center(self) -> "Point": """ Convert an existing point to one that is definitely a 'center' type. """ return Point(self.x, self.y, True)
[docs] def to_center(point: Point) -> Point: """Convert an existing point to one that is definitely a 'center' type.""" return point.to_center()
DEFAULT = Point() DEFAULT_CENTER = to_center(DEFAULT) NamedPoints = dict[str, Point]
[docs] class PointManager: """A class for managing points.""" def __init__(self, points: NamedPoints = None) -> None: """Initialize this point manager.""" if points is None: points = {} self.points = points
[docs] def get_point(self, name: str) -> Point: """Access a point object.""" return self.points[name]
[docs] def add_point(self, name: str, point: Union[Point, float], *args) -> Point: """Add a named point to this manager.""" if not isinstance(point, Point): point = Point(point, *args) assert name not in self.points, f"Already managing point '{name}'!" self.points[name] = point return self.points[name]
[docs] def translate(self, move: Union[Translation, float], *args) -> None: """Translate all entities in the plane by some amount.""" for key, val in self.points.items(): self.points[key] = val.translate(move, *args)