Source code for simetri.stars.stars

"""This module contains classes and functions for creating stars and rosettes."""

from math import pi, sin, cos
from typing import Union

from ..graphics.batch import Batch
from ..graphics.shape import Shape

from ..graphics.common import common_properties, axis_x, Line
from ..graphics.all_enums import Types
from ..geometry.geometry import intersect, distance


[docs] def rosette( n: int, kernel: Union[Shape, Batch], cyclic: bool = False, axis: Line = axis_x, merge: bool = True, ) -> Batch: """Returns a pattern with cyclic or dihedral symmetry with n petals. Args: n (int): Number of petals. kernel (Union[Shape, Batch]): The base shape or batch to be used as a petal. cyclic (bool, optional): If True, creates a cyclic pattern. Defaults to False. axis (Line, optional): The axis for mirroring. Defaults to axis_x. merge (bool, optional): If True, merges shapes. Defaults to True. Returns: Batch: The resulting pattern with n petals. """ if cyclic: petal = kernel else: petal = kernel.mirror(axis, reps=1) if merge: petal = petal.merge_shapes() petal = petal.rotate(2 * pi / n, reps=n - 1) return petal
[docs] class Star(Batch): """Represents a star shape with n points. Args: n (int): Number of points of the star. inner_radius (float, optional): Inner radius of the star. Defaults to None. circumradius (float, optional): Circumradius of the star. Defaults to None. **kwargs: Additional keyword arguments. """ def __init__( self, n: int, inner_radius: float = None, circumradius: float = None, **kwargs ): if circumradius is not None and inner_radius is not None: raise ValueError( "Only one of circumradius or inner_radius can be specified." ) self.n = n self.circumradius = circumradius self.inner_radius = inner_radius if circumradius is None and inner_radius is None: self.inner_radius = 50 self.center = (0, 0) self.subtype = Types.STAR common_properties(self) self._initialize(n) super().__init__(**kwargs) def _initialize(self, n): """Initializes the star with n points. Args: n (int): Number of points of the star. Raises: ValueError: If n is less than 7. """ if n < 7: raise ValueError("n must be greater than 6") if self.inner_radius is None: r = 50 else: r = self.inner_radius # start with a reasonable value alpha = (n - 2) * pi / n beta = (pi - alpha) / 2 gamma = 2 * pi / n theta = 3 * pi / n t = r * sin(theta) x = t / cos(beta) x1, y1 = r * cos(theta), r * sin(theta) up1 = (x1, y1) up2_ = 100, r * sin(theta) line1 = Shape([up1, up2_]) lp1 = x1, -y1 lp2_ = 100, -r * sin(theta) line2 = Shape([lp1, lp2_]) up1_ = intersect(line1, line2.copy().rotate(gamma)) up2 = up1_[0] + r, y1 up3 = up2[0] + x * sin(beta), 0 self._kernel2 = Shape([up1, up2, up3]) self._petal2 = self._kernel2.copy().mirror(axis_x, reps=1) self._level2 = self._petal2.rotate(gamma, reps=n - 1) self._r2 = distance((0, 0), up1) self._circum2 = up3[0] p1 = line1.copy().rotate(-gamma)[0] p2 = intersect(line2.copy().rotate(gamma), line1.copy().rotate(-gamma)) self._kernel0 = Shape([p1, p2]) self._petal0 = self._kernel0.copy().mirror(axis_x, reps=1) self._level0 = self._petal0.rotate(gamma, reps=n - 1) self._r0 = distance((0, 0), p1) self._circum0 = p2[0] line3 = Shape([up1, up1_]) self._kernel1 = line3.rotate(-gamma / 2) self._petal1 = self._kernel1.copy().mirror(axis_x, reps=1) self._level1 = self._petal1.rotate(gamma, reps=n - 1) self._r1 = distance((0, 0), up1) self._circum1 = up1_[0] def _calc_kernel(self, segments, n): """Calculates the kernel shape for the star. Args: segments (Shape): The segments to be used for calculation. n (int): Number of points of the star. Returns: tuple: A tuple containing the kernel shape, inner radius, and circumradius. """ segments = segments.copy() segments.rotate(pi / n) segments = segments.mirror(axis_x, reps=1) p3 = intersect(segments[0].vertex_pairs[1], segments[1].vertex_pairs[1]) p1, p2 = segments[0].vertex_pairs[0] kernel = Shape([p1, p2, p3]) inner_radius = distance((0, 0), p1) circumradius = distance((0, 0), p3) return (kernel, inner_radius, circumradius) def _get_kernel(self, level): """Gets the kernel shape for the specified level. Args: level (int): The level of the star. Returns: tuple: A tuple containing the kernel shape, inner radius, and circumradius. """ kernel = self._kernel2.copy() for _ in range(level - 2): kernel, inner_radius, circumradius = self._calc_kernel(kernel, self.n) return kernel, inner_radius, circumradius def _get_scale_factor(self, level, inner_radius=None, circumradius=None): """Calculates the scale factor for the specified level. Args: level (int): The level of the star. inner_radius (float, optional): Inner radius of the star. Defaults to None. circumradius (float, optional): Circumradius of the star. Defaults to None. Returns: float: The scale factor. """ if self.inner_radius is None: if level == 0: denom = self._circum0 elif level == 1: denom = self._circum1 elif level == 2: denom = self._circum2 else: denom = circumradius scale_factor = self.circumradius / denom else: if level == 0: denom = self._r0 elif level == 1: denom = self._r1 elif level == 2: denom = self._r2 else: denom = inner_radius scale_factor = self.inner_radius / denom return scale_factor
[docs] def kernel(self, level: int) -> Shape: """Returns the kernel of the star at the specified level. Args: level (int): The level of the star. Returns: Shape: The kernel shape of the star. Raises: ValueError: If level is not a positive integer or zero. """ if level < 0 or not isinstance(level, int): raise ValueError("level must be a positive integer or zero.") if level == 0: scale_factor = self._get_scale_factor(0, self._r0, self._circum0) kernel = self._kernel0.copy().scale(scale_factor) elif level == 1: scale_factor = self._get_scale_factor(1, self._r1, self._circum1) kernel = self._kernel1.copy().scale(scale_factor) elif level == 2: scale_factor = self._get_scale_factor(2, self._r2, self._circum2) kernel = self._kernel2.copy().scale(scale_factor) else: kernel, inner_radius, circumradius = self._get_kernel(level) scale_factor = self._get_scale_factor(level, inner_radius, circumradius) kernel = kernel.scale(scale_factor) return kernel
[docs] def petal(self, level: int) -> Shape: """Returns the petal of the star at the specified level. Args: level (int): The level of the star. Returns: Shape: The petal shape of the star. Raises: ValueError: If level is not a positive integer or zero. """ if level < 0 or not isinstance(level, int): raise ValueError("level must be a positive integer or zero.") if level == 0: scale_factor = self._get_scale_factor(0, self._r0, self._circum0) petal = self._kernel0.copy().mirror(axis_x, reps=1).scale(scale_factor) elif level == 1: scale_factor = self._get_scale_factor(1, self._r1, self._circum1) petal = self._kernel1.copy().mirror(axis_x, reps=1).scale(scale_factor) elif level == 2: scale_factor = self._get_scale_factor(2, self._r2, self._circum2) petal = self._kernel2.copy().mirror(axis_x, reps=1).scale(scale_factor) else: kernel, inner_radius, circumradius = self._get_kernel(level) scale_factor = self._get_scale_factor(level, inner_radius, circumradius) petal = kernel.mirror(axis_x, reps=1).scale(scale_factor) return petal
[docs] def level(self, n: int) -> Batch: """Returns the star at the specified level. Args: n (int): The level of the star. Returns: Batch: The star shape at the specified level. Raises: ValueError: If level is not a positive integer or zero. """ if n < 0: raise ValueError("level must be a positive integer or zero.") if n == 0: scale_factor = self._get_scale_factor(0) star = self._level0.copy().scale(scale_factor) star.subtype = Types.STAR star.circumradius = self._circum0 * scale_factor star.inner_radius = self._r0 * scale_factor elif n == 1: scale_factor = self._get_scale_factor(1) star = self._level1.copy().scale(scale_factor) star.subtype = Types.STAR star.circumradius = self._circum1 * scale_factor star.inner_radius = self._r0 * scale_factor elif n == 2: scale_factor = self._get_scale_factor(2) star = self._level2.copy().scale(scale_factor) star.subtype = Types.STAR star.circumradius = self._circum2 * scale_factor star.inner_radius = self._r0 * scale_factor else: kernel, inner_radius, circumradius = self._get_kernel(n) scale_factor = self._get_scale_factor(n, inner_radius, circumradius) petal = kernel.mirror(axis_x, reps=1) star = petal.rotate(2 * pi / self.n, reps=self.n - 1) scale_factor = self._get_scale_factor(n, inner_radius, circumradius) star = star.scale(scale_factor) star.subtype = Types.STAR star.circumradius = circumradius star.inner_radius = inner_radius return star