simetri.graphics.shapes

Shapes module contains classes and functions for creating shapes.

  1"""Shapes module contains classes and functions for creating shapes."""
  2
  3from math import pi, gcd, sin, cos, comb
  4from typing import List, Sequence, Union
  5import copy
  6
  7from numpy import ndarray
  8import numpy as np
  9
 10from ..graphics.batch import Batch
 11from ..graphics.bbox import BoundingBox
 12from ..graphics.shape import Shape, custom_attributes
 13from ..graphics.common import axis_x, get_defaults, Sequence, Point
 14from ..graphics.all_enums import Types
 15from ..helpers.utilities import decompose_transformations
 16from ..settings.settings import defaults
 17from .affine import scale_in_place_matrix, rotation_matrix
 18from ..geometry.ellipse import ellipse_points
 19from ..geometry.geometry import (
 20    side_len_to_radius,
 21    offset_polygon_points,
 22    distance,
 23    mid_point,
 24    close_points2,
 25)
 26
 27import simetri.colors.colors as colors
 28
 29Color = colors.Color
 30
 31
 32class Rectangle(Shape):
 33    """A rectangle defined by width and height."""
 34
 35    def __init__(self, center: Point, width: float, height: float, **kwargs) -> None:
 36        """Initialize a Rectangle object.
 37
 38        Args:
 39            center (Point): The center point of the rectangle.
 40            width (float): The width of the rectangle.
 41            height (float): The height of the rectangle.
 42            **kwargs: Additional keyword arguments.
 43        """
 44        x, y = center[:2]
 45        half_width = width / 2
 46        half_height = height / 2
 47        vertices = [
 48            (x - half_width, y - half_height),
 49            (x + half_width, y - half_height),
 50            (x + half_width, y + half_height),
 51            (x - half_width, y + half_height),
 52        ]
 53        super().__init__(vertices, closed=True, **kwargs)
 54        self.subtype = Types.RECTANGLE
 55
 56    def __setattr__(self, name, value):
 57        """Set an attribute of the rectangle.
 58
 59        Args:
 60            name (str): The name of the attribute.
 61            value (Any): The value of the attribute.
 62        """
 63        if name == "center":
 64            self._set_center(value)
 65        elif name == "width":
 66            self._set_width(value)
 67        elif name == "height":
 68            self._set_height(value)
 69        else:
 70            super().__setattr__(name, value)
 71
 72    def scale(
 73        self,
 74        scale_x: float,
 75        scale_y: Union[float, None] = None,
 76        about: Point = (0, 0),
 77        reps: int = 0,
 78    ):
 79        """Scale the rectangle by scale_x and scale_y.
 80        Rectangles cannot be scaled non-uniformly.
 81        scale_x changes the width and scale_y changes the height.
 82
 83        Args:
 84            scale_x (float): The scale factor for the width.
 85            scale_y (float, optional): The scale factor for the height. Defaults to None.
 86            about (Point, optional): The point to scale about. Defaults to (0, 0).
 87            reps (int, optional): The number of repetitions. Defaults to 0.
 88
 89        Returns:
 90            Rectangle: The scaled rectangle.
 91        """
 92        if scale_y is None:
 93            scale_y = scale_x
 94        center = self.center
 95        _, rotation, _ = decompose_transformations(self.xform_matrix)
 96        rm = rotation_matrix(-rotation, center)
 97        sm = scale_in_place_matrix(scale_x, scale_y, about)
 98        inv_rm = rotation_matrix(rotation, center)
 99        transform = rm @ sm @ inv_rm
100
101        return self._update(transform, reps=reps)
102
103    @property
104    def width(self):
105        """Return the width of the rectangle.
106
107        Returns:
108            float: The width of the rectangle.
109        """
110        return distance(self.vertices[0], self.vertices[1])
111
112    def _set_width(self, new_width: float):
113        """Set the width of the rectangle.
114
115        Args:
116            new_width (float): The new width of the rectangle.
117        """
118        scale_x = new_width / self.width
119        self.scale(scale_x, 1, about=self.center, reps=0)
120
121    @property
122    def height(self):
123        """Return the height of the rectangle.
124
125        Returns:
126            float: The height of the rectangle.
127        """
128        return distance(self.vertices[1], self.vertices[2])
129
130    def _set_height(self, new_height: float):
131        """Set the height of the rectangle.
132
133        Args:
134            new_height (float): The new height of the rectangle.
135        """
136        scale_y = new_height / self.height
137        self.scale(1, scale_y, about=self.center, reps=0)
138
139    @property
140    def center(self):
141        """Return the center of the rectangle.
142
143        Returns:
144            Point: The center of the rectangle.
145        """
146        return mid_point(self.vertices[0], self.vertices[2])
147
148    def _set_center(self, new_center: Point):
149        """Set the center of the rectangle.
150
151        Args:
152            new_center (Point): The new center of the rectangle.
153        """
154        center = self.center
155        x_diff = new_center[0] - center[0]
156        y_diff = new_center[1] - center[1]
157        for i in range(4):
158            x, y = self.vertices[i][:2]
159            self[i] = (x + x_diff, y + y_diff)
160
161    def copy(self):
162        """Return a copy of the rectangle.
163
164        Returns:
165            Rectangle: A copy of the rectangle.
166        """
167        center = self.center
168        width = self.width
169        height = self.height
170        rectangle = Rectangle(center, width, height)
171        _, rotation, _ = decompose_transformations(self.xform_matrix)
172        rectangle.rotate(rotation, about=center, reps=0)
173        style = copy.copy(self.style)
174        rectangle.style = style
175        rectangle._set_aliases()
176        custom_attribs = custom_attributes(self)
177        for attrib in custom_attribs:
178            setattr(rectangle, attrib, getattr(self, attrib))
179
180        return rectangle
181
182
183class Rectangle2(Rectangle):
184    """A rectangle defined by two opposite corners."""
185
186    def __init__(self, corner1: Point, corner2: Point, **kwargs) -> None:
187        """Initialize a Rectangle2 object.
188
189        Args:
190            corner1 (Point): The first corner of the rectangle.
191            corner2 (Point): The second corner of the rectangle.
192            **kwargs: Additional keyword arguments.
193        """
194        x1, y1 = corner1
195        x2, y2 = corner2
196        x_min, x_max = min(x1, x2), max(x1, x2)
197        y_min, y_max = min(y1, y2), max(y1, y2)
198        center = ((x_min + x_max) / 2, (y_min + y_max) / 2)
199        width = x_max - x_min
200        height = y_max - y_min
201        super().__init__(center, width, height, **kwargs)
202
203
204class Circle(Shape):
205    """A circle defined by a center point and a radius."""
206
207    def __init__(
208        self,
209        center: Point = (0, 0),
210        radius: float = None,
211        xform_matrix: np.array = None,
212        **kwargs,
213    ) -> None:
214        """Initialize a Circle object.
215
216        Args:
217            center (Point, optional): The center point of the circle. Defaults to (0, 0).
218            radius (float, optional): The radius of the circle. Defaults to None.
219            xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
220            **kwargs: Additional keyword arguments.
221        """
222        if radius is None:
223            radius = defaults["circle_radius"]
224
225        x, y = center[:2]
226        points = [[x, y]]
227        super().__init__(points, xform_matrix=xform_matrix, **kwargs)
228        self.subtype = Types.CIRCLE
229        self._radius = radius
230
231    def __setattr__(self, name, value):
232        """Set an attribute of the circle.
233
234        Args:
235            name (str): The name of the attribute.
236            value (Any): The value of the attribute.
237        """
238        if name == "center":
239            self[0] = value[:2]
240        elif name == "radius":
241            ratio = value / self.radius
242            self.scale(ratio, about=self.center, reps=0)
243        else:
244            super().__setattr__(name, value)
245
246    @property
247    def b_box(self):
248        """Return the bounding box of the shape.
249
250        Returns:
251            BoundingBox: The bounding box of the shape.
252        """
253        x, y = self.center[:2]
254        x1, y1 = x - self.radius, y - self.radius
255        x2, y2 = x + self.radius, y + self.radius
256        self._b_box = BoundingBox((x1, y1), (x2, y2))
257
258        return self._b_box
259
260    @property
261    def closed(self):
262        """Return True. Circles are closed.
263
264        Returns:
265            bool: True
266        """
267        return True
268
269    @closed.setter
270    def closed(self, value: bool):
271        pass
272
273    @property
274    def center(self):
275        """Return the center of the circle.
276
277        Returns:
278            Point: The center of the circle.
279        """
280        return self.vertices[0]
281
282    @center.setter
283    def center(self, value: Point):
284        """Set the center of the circle.
285
286        Args:
287            value (Point): The new center of the circle.
288        """
289        self[0] = value[:2]
290
291    @property
292    def radius(self):
293        """Return the radius of the circle.
294
295        Returns:
296            float: The radius of the circle.
297        """
298        scale_x = np.linalg.norm(self.xform_matrix[0, :2])  # only x scale is used
299        return self._radius * scale_x
300
301    def copy(self):
302        """Return a copy of the circle.
303
304        Returns:
305            Circle: A copy of the circle.
306        """
307        center = self.center
308        radius = self.radius
309        circle = Circle(center=center, radius=radius)
310        style = copy.deepcopy(self.style)
311        circle.style = style
312        circle._set_aliases()
313
314        custom_attribs = custom_attributes(self)
315        custom_attribs.remove("center")
316        custom_attribs.remove("_radius")
317        custom_attribs.remove("radius")
318        for attrib in custom_attribs:
319            setattr(circle, attrib, getattr(self, attrib))
320
321        return circle
322
323
324class Segment(Shape):
325    """A line segment defined by two points.
326    This is not used in the code-base, but is here for the API.
327    """
328
329    def __init__(self, start: Point, end: Point, **kwargs) -> None:
330        """Initialize a Segment object.
331
332        Args:
333            start (Point): The start point of the segment.
334            end (Point): The end point of the segment.
335            **kwargs: Additional keyword arguments.
336
337        Raises:
338            ValueError: If the start and end points are the same.
339        """
340        dist_tol2 = defaults["dist_tol"] ** 2
341        if close_points2(start, end, dist2=dist_tol2):
342            raise ValueError("Segment: start and end points are the same!")
343        points = [start, end]
344        super().__init__(points, **kwargs)
345        self.subtype = Types.SEGMENT
346
347    @property
348    def start(self):
349        """Return the start point of the segment.
350
351        Returns:
352            Point: The start point of the segment.
353        """
354        return self.vertices[0]
355
356    @property
357    def end(self):
358        """Return the end point of the segment.
359
360        Returns:
361            Point: The end point of the segment.
362        """
363        return self.vertices[1]
364
365    @property
366    def length(self):
367        """Return the length of the segment.
368
369        Returns:
370            float: The length of the segment.
371        """
372        return distance(self.start, self.end)
373
374    def copy(self) -> Shape:
375        """Return a copy of the segment.
376
377        Returns:
378            Shape: A copy of the segment.
379        """
380        return Segment(self.start, self.end, **self.kwargs)
381
382    def __str__(self):
383        """Return a string representation of the segment.
384
385        Returns:
386            str: The string representation of the segment.
387        """
388        return f"Segment({self.start}, {self.end})"
389
390    def __repr__(self):
391        """Return a string representation of the segment.
392
393        Returns:
394            str: The string representation of the segment.
395        """
396        return f"Segment({self.start}, {self.end})"
397
398    def __eq__(self, other):
399        """Check if the segment is equal to another segment.
400
401        Args:
402            other (Segment): The other segment to compare to.
403
404        Returns:
405            bool: True if the segments are equal, False otherwise.
406        """
407        return (
408            other.type == Types.SEGMENT
409            and self.start == other.start
410            and self.end == other.end
411        )
412
413
414class Mask(Shape):
415    """A mask is a closed shape that is used to clip other shapes.
416    All it has is points and a transformation matrix.
417    """
418
419    def __init__(self, points, reverse=False, xform_matrix=None):
420        """Initialize a Mask object.
421
422        Args:
423            points (Sequence[Point]): The points that make up the mask.
424            reverse (bool, optional): Whether to reverse the mask. Defaults to False.
425            xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
426        """
427        super().__init__(points, xform_matrix, subtype=Types.MASK, closed=True)
428        self.reverse: bool = reverse
429        # mask should be between \begin{scope} and \end{scope}
430        # canvas, batch, and shapes can have scope
431
432
433def circle_points(center: Point, radius: float, n: int = 30) -> list[Point]:
434    """Return a list of points that form a circle with the given parameters.
435
436    Args:
437        center (Point): The center point of the circle.
438        radius (float): The radius of the circle.
439        n (int, optional): The number of points in the circle. Defaults to 30.
440
441    Returns:
442        list[Point]: A list of points that form a circle.
443    """
444    return arc_points(center, radius, 0, 2 * pi, n=n)
445
446
447def arc_points(
448    center: Point,
449    radius: float,
450    start_angle: float,
451    end_angle: float,
452    clockwise: bool = False,
453    n: int = 20,
454) -> list[Point]:
455    """Return a list of points that form a circular arc with the given parameters.
456
457    Args:
458        center (Point): The center point of the arc.
459        radius (float): The radius of the arc.
460        start_angle (float): The starting angle of the arc.
461        end_angle (float): The ending angle of the arc.
462        clockwise (bool, optional): Whether the arc is drawn clockwise. Defaults to False.
463        n (int, optional): The number of points in the arc. Defaults to 20.
464
465    Returns:
466        list[Point]: A list of points that form a circular arc.
467    """
468    x, y = center[:2]
469    points = []
470    if clockwise:
471        start_angle, end_angle = end_angle, start_angle
472    step = (end_angle - start_angle) / n
473    for i in np.arange(start_angle, end_angle + 1, step):
474        points.append([x + radius * cos(i), y + radius * sin(i)])
475    return points
476
477
478def hex_points(side_length: float) -> List[List[float]]:
479    """Return a list of points that define a hexagon with a given side length.
480
481    Args:
482        side_length (float): The length of each side of the hexagon.
483
484    Returns:
485        list[list[float]]: A list of points that define the hexagon.
486    """
487    points = []
488    for i in range(6):
489        x = side_length * cos(i * 2 * pi / 6)
490        y = side_length * sin(i * 2 * pi / 6)
491        points.append((x, y))
492    return points
493
494
495def rectangle_points(
496    x: float, y: float, width: float, height: float, angle: float = 0
497) -> Sequence[Point]:
498    """Return a list of points that form a rectangle with the given parameters.
499
500    Args:
501        x (float): The x-coordinate of the center of the rectangle.
502        y (float): The y-coordinate of the center of the rectangle.
503        width (float): The width of the rectangle.
504        height (float): The height of the rectangle.
505        angle (float, optional): The rotation angle of the rectangle. Defaults to 0.
506
507    Returns:
508        Sequence[Point]: A list of points that form the rectangle.
509    """
510    from affine import rotate
511
512    points = []
513    points.append([x - width / 2, y - height / 2])
514    points.append([x + width / 2, y - height / 2])
515    points.append([x + width / 2, y + height / 2])
516    points.append([x - width / 2, y + height / 2])
517    if angle != 0:
518        points = rotate(points, angle, (x, y))
519    return points
520
521
522def reg_poly_points_side_length(pos: Point, n: int, side_len: float) -> Sequence[Point]:
523    """Return a regular polygon points list with n sides and side_len length.
524
525    Args:
526        pos (Point): The position of the center of the polygon.
527        n (int): The number of sides of the polygon.
528        side_len (float): The length of each side of the polygon.
529
530    Returns:
531        Sequence[Point]: A list of points that form the polygon.
532    """
533    rad = side_len_to_radius(n, side_len)
534    angle = 2 * pi / n
535    x, y = pos[:2]
536    points = [[cos(angle * i) * rad + x, sin(angle * i) * rad + y] for i in range(n)]
537    points.append(points[0])
538    return points
539
540
541def reg_poly_points(pos: Point, n: int, r: float) -> Sequence[Point]:
542    """Return a regular polygon points list with n sides and radius r.
543
544    Args:
545        pos (Point): The position of the center of the polygon.
546        n (int): The number of sides of the polygon.
547        r (float): The radius of the polygon.
548
549    Returns:
550        Sequence[Point]: A list of points that form the polygon.
551    """
552    angle = 2 * pi / n
553    x, y = pos[:2]
554    points = [[cos(angle * i) * r + x, sin(angle * i) * r + y] for i in range(n)]
555    points.append(points[0])
556    return points
557
558
559def di_star(points: Sequence[Point], n: int) -> Batch:
560    """Return a dihedral star with n petals.
561
562    Args:
563        points (Sequence[Point]): List of [x, y] points.
564        n (int): Number of petals.
565
566    Returns:
567        Batch: A Batch instance (dihedral star with n petals).
568    """
569    batch = Batch(Shape(points))
570    return batch.mirror(axis_x, reps=1).rotate(2 * pi / n, reps=n - 1)
571
572
573def hex_grid_centers(x, y, side_length, n_rows, n_cols):
574    """Return a list of points that define the centers of hexagons in a grid.
575
576    Args:
577        x (float): The x-coordinate of the starting point.
578        y (float): The y-coordinate of the starting point.
579        side_length (float): The length of each side of the hexagons.
580        n_rows (int): The number of rows in the grid.
581        n_cols (int): The number of columns in the grid.
582
583    Returns:
584        list[Point]: A list of points that define the centers of the hexagons.
585    """
586    centers = []
587    for row in range(n_rows):
588        for col in range(n_cols):
589            x_ = col * 3 * side_length + x
590            y_ = row * 2 * side_length + y
591            if col % 2:
592                y_ += side_length
593            centers.append((x_, y_))
594
595    centers = []
596    # first row
597    origin = Point(x, y)
598    grid = Batch(Point)
599    grid.transform()
600    return centers
601
602
603def rect_grid(x, y, cell_width, cell_height, n_rows, n_cols, pattern):
604    """Return a grid of rectangles with the given parameters.
605
606    Args:
607        x (float): The x-coordinate of the starting point.
608        y (float): The y-coordinate of the starting point.
609        cell_width (float): The width of each cell in the grid.
610        cell_height (float): The height of each cell in the grid.
611        n_rows (int): The number of rows in the grid.
612        n_cols (int): The number of columns in the grid.
613        pattern (list[list[bool]]): A pattern to fill the grid.
614
615    Returns:
616        Batch: A Batch object representing the grid.
617    """
618    width = cell_width * n_cols
619    height = cell_height * n_rows
620    horiz_line = line_shape((x, y), (x + width, y))
621    horiz_lines = Batch(horiz_line)
622    horiz_lines.translate(0, cell_height, reps=n_rows)
623    vert_line = line_shape((x, y), (x, y + height))
624    vert_lines = Batch(vert_line)
625    vert_lines.translate(cell_width, 0, reps=n_cols)
626    grid = Batch(horiz_lines, *vert_lines)
627    for row in range(n_rows):
628        for col in range(n_cols):
629            if pattern[row][col]:
630                x_, y_ = (col * cell_width + x, (n_rows - row - 1) * cell_height + y)
631                points = [
632                    (x_, y_),
633                    (x_ + cell_width, y_),
634                    (x_ + cell_width, y_ + cell_height),
635                    (x_, y_ + cell_height),
636                ]
637                cell = Shape(points, closed=True, fill_color=colors.gray)
638                grid.append(cell)
639    return grid
640
641
642def regular_star_polygon(n, step, rad):
643    """
644    Return a regular star polygon with the given parameters.
645
646    :param n: The number of vertices of the star polygon.
647    :type n: int
648    :param step: The step size for connecting vertices.
649    :type step: int
650    :param rad: The radius of the star polygon.
651    :type rad: float
652    :return: A Batch object representing the star polygon.
653    :rtype: Batch
654    """
655    angle = 2 * pi / n
656    points = [(cos(angle * i) * rad, sin(angle * i) * rad) for i in range(n)]
657    if n % step:
658        indices = [i % n for i in list(range(0, (n + 1) * step, step))]
659    else:
660        indices = [i % n for i in list(range(0, ((n // step) + 1) * step, step))]
661    vertices = [points[ind] for ind in indices]
662    return Batch(Shape(vertices)).rotate(angle, reps=gcd(n, step) - 1)
663
664
665def star_shape(points, reps=5, scale=1):
666    """Return a dihedral star from a list of points.
667
668    Args:
669        points (list[Point]): The list of points that form the star.
670        reps (int, optional): The number of repetitions. Defaults to 5.
671        scale (float, optional): The scale factor. Defaults to 1.
672
673    Returns:
674        Batch: A Batch object representing the star.
675    """
676    shape = Shape(points, subtype=Types.STAR)
677    batch = Batch(shape)
678    batch.mirror(axis_x, reps=1)
679    batch.rotate(2 * pi / (reps), reps=reps - 1)
680    batch.scale(scale)
681    return batch
682
683
684def dot_shape(
685    x,
686    y,
687    radius=1,
688    fill_color=None,
689    line_color=None,
690    line_width=None,
691):
692    """Return a Shape object with a single point.
693
694    Args:
695        x (float): The x-coordinate of the point.
696        y (float): The y-coordinate of the point.
697        radius (float, optional): The radius of the point. Defaults to 1.
698        fill_color (Color, optional): The fill color of the point. Defaults to None.
699        line_color (Color, optional): The line color of the point. Defaults to None.
700        line_width (float, optional): The line width of the point. Defaults to None.
701
702    Returns:
703        Shape: A Shape object with a single point.
704    """
705    fill_color, line_color, line_width = get_defaults(
706        ["fill_color", "line_color", "line_width"], [fill_color, line_color, line_width]
707    )
708    dot_shape = Shape(
709        [(x, y)],
710        closed=True,
711        fill_color=fill_color,
712        line_color=line_color,
713        line_width=line_width,
714        subtype=Types.D_o_t,
715    )
716    dot_shape.marker = radius
717    return dot_shape
718
719
720def rect_shape(
721    x: float,
722    y: float,
723    width: float,
724    height: float,
725    fill_color: Color = colors.white,
726    line_color: Color = defaults["line_color"],
727    line_width: float = defaults["line_width"],
728    fill: bool = True,
729    marker: "Marker" = None,
730) -> Shape:
731    """Given lower left corner position, width, and height,
732    return a Shape object with points that form a rectangle.
733
734    Args:
735        x (float): The x-coordinate of the lower left corner.
736        y (float): The y-coordinate of the lower left corner.
737        width (float): The width of the rectangle.
738        height (float): The height of the rectangle.
739        fill_color (Color, optional): The fill color of the rectangle. Defaults to colors.white.
740        line_color (Color, optional): The line color of the rectangle. Defaults to defaults["line_color"].
741        line_width (float, optional): The line width of the rectangle. Defaults to defaults["line_width"].
742        fill (bool, optional): Whether to fill the rectangle. Defaults to True.
743        marker (Marker, optional): The marker for the rectangle. Defaults to None.
744
745    Returns:
746        Shape: A Shape object with points that form a rectangle.
747    """
748    return Shape(
749        [(x, y), (x + width, y), (x + width, y + height), (x, y + height)],
750        closed=True,
751        fill_color=fill_color,
752        line_color=line_color,
753        fill=fill,
754        line_width=line_width,
755        marker=marker,
756        subtype=Types.RECTANGLE,
757    )
758
759
760def arc_shape(x, y, radius, start_angle, end_angle, clockwise=False, n=20):
761    """Return a Shape object with points that form a circular arc with the given parameters.
762
763    Args:
764        x (float): The x-coordinate of the center of the arc.
765        y (float): The y-coordinate of the center of the arc.
766        radius (float): The radius of the arc.
767        start_angle (float): The starting angle of the arc.
768        end_angle (float): The ending angle of the arc.
769        clockwise (bool, optional): Whether the arc is drawn clockwise. Defaults to False.
770        n (int, optional): The number of points to use for the arc. Defaults to 20.
771
772    Returns:
773        Shape: A Shape object with points that form a circular arc.
774    """
775    points = arc_points(x, y, radius, start_angle, end_angle, clockwise=clockwise, n=n)
776    return Shape(points, closed=False, subtype=Types.ARC)
777
778
779def circle_shape(x, y, radius, n=30):
780    """Return a Shape object with points that form a circle with the given parameters.
781
782    Args:
783        x (float): The x-coordinate of the center of the circle.
784        y (float): The y-coordinate of the center of the circle.
785        radius (float): The radius of the circle.
786        n (int, optional): The number of points to use for the circle. Defaults to 30.
787
788    Returns:
789        Shape: A Shape object with points that form a circle.
790    """
791    circ = arc_shape(x, y, radius, 0, 2 * pi, n=n)
792    circ.subtype = Types.CIRCLE
793    return circ
794
795
796def reg_poly_shape(pos, n, r=100, **kwargs):
797    """Return a regular polygon.
798
799    Args:
800        pos (Point): The position of the center of the polygon.
801        n (int): The number of sides of the polygon.
802        r (float, optional): The radius of the polygon. Defaults to 100.
803        kwargs (dict): Additional keyword arguments.
804
805    Returns:
806        Shape: A Shape object with points that form a regular polygon.
807    """
808    x, y = pos[:2]
809    points = reg_poly_points((x, y), n=n, r=r)
810    return Shape(points, closed=True, **kwargs)
811
812
813def ellipse_shape(x, y, width, height, angle, n_points=None):
814    """Return a Shape object with points that form an ellipse with the given parameters.
815
816    Args:
817        x (float): The x-coordinate of the center of the ellipse.
818        y (float): The y-coordinate of the center of the ellipse.
819        width (float): The width of the ellipse.
820        height (float): The height of the ellipse.
821        n (int, optional): The number of points to use for the ellipse. Defaults to 30.
822
823    Returns:
824        Shape: A Shape object with points that form an ellipse.
825    """
826    if n_points is None:
827        n_points = defaults["n_ellipse_points"]
828
829    points = ellipse_points((x, y), width, height, n_points=n)
830    return Shape(points, subtype=Types.ELLIPSE)
831
832
833def line_shape(p1, p2, line_width=1, line_color=colors.black, **kwargs):
834    """Return a Shape object with two points p1 and p2.
835
836    Args:
837        p1 (Point): The first point of the line.
838        p2 (Point): The second point of the line.
839        line_width (float, optional): The width of the line. Defaults to 1.
840        line_color (Color, optional): The color of the line. Defaults to colors.black.
841        kwargs (dict): Additional keyword arguments.
842
843    Returns:
844        Shape: A Shape object with two points that form a line.
845    """
846    x1, y1 = p1
847    x2, y2 = p2
848    return Shape(
849        [(x1, y1), (x2, y2)],
850        closed=False,
851        line_color=line_color,
852        line_width=line_width,
853        subtype=Types.L_i_n_e,
854        **kwargs,
855    )
856
857
858def offset_polygon_shape(
859    polygon_shape, offset: float = 1, dist_tol: float = defaults["dist_tol"]
860) -> list[Point]:
861    """Return a copy of a polygon with offset edges.
862
863    Args:
864        polygon_shape (Shape): The original polygon shape.
865        offset (float, optional): The offset distance. Defaults to 1.
866        dist_tol (float, optional): The distance tolerance. Defaults to defaults["dist_tol"].
867
868    Returns:
869        list[Point]: A list of points that form the offset polygon.
870    """
871    vertices = offset_polygon_points(polygon_shape.vertices, offset, dist_tol)
872
873    return Shape(vertices)
@dataclass
class Color:
116@dataclass
117class Color:
118    """A class representing an RGB or RGBA color.
119
120    This class represents a color in RGB or RGBA color space. The default values
121    for the components are normalized between 0.0 and 1.0. Values outside this range
122    are automatically converted from the 0-255 range.
123
124    Attributes:
125        red: The red component of the color (0.0 to 1.0).
126        green: The green component of the color (0.0 to 1.0).
127        blue: The blue component of the color (0.0 to 1.0).
128        alpha: The alpha (transparency) component (0.0 to 1.0), default is 1.
129        space: The color space, default is "rgb".
130
131    Examples:
132        >>> red = Color(1.0, 0.0, 0.0)
133        >>> transparent_blue = Color(0.0, 0.0, 1.0, 0.5)
134        >>> rgb255 = Color(255, 0, 128)  # Will be automatically normalized
135    """
136    red: int = 0
137    green: int = 0
138    blue: int = 0
139    alpha: int = 1
140    space: ColorSpace = "rgb"  # for future use
141
142    def __post_init__(self):
143        """Post-initialization to ensure color values are in the correct range."""
144        r, g, b = self.red, self.green, self.blue
145        if r < 0 or r > 1 or g < 0 or g > 1 or b < 0 or b > 1:
146            self.red = r / 255
147            self.green = g / 255
148            self.blue = b / 255
149        if self.alpha < 0 or self.alpha > 1:
150            self.alpha = self.alpha / 255
151        common_properties(self)
152
153    def __str__(self):
154        return f"Color({self.red}, {self.green}, {self.blue})"
155
156    def __repr__(self):
157        return f"Color({self.red}, {self.green}, {self.blue})"
158
159    def copy(self):
160        return Color(self.red, self.green, self.blue, self.alpha)
161
162    @property
163    def __key__(self):
164        return (self.red, self.green, self.blue)
165
166    def __hash__(self):
167        return hash(self.__key__)
168
169    @property
170    def name(self):
171        # search for the color in the named colors
172        pass
173
174    def __eq__(self, other):
175        if isinstance(other, Color):
176            return self.__key__ == other.__key__
177        else:
178            return False
179
180    @property
181    def rgb(self):
182        return (self.red, self.green, self.blue)
183
184    @property
185    def rgba(self):
186        return (self.red, self.green, self.blue, self.alpha)
187
188    @property
189    def rgb255(self):
190        r, g, b = self.rgb
191        if r > 1 or g > 1 or b > 1:
192            return (r, g, b)
193        return tuple(round(i * 255) for i in self.rgb)
194
195    @property
196    def rgba255(self):
197        return tuple(round(i * 255) for i in self.rgba)

A class representing an RGB or RGBA color.

This class represents a color in RGB or RGBA color space. The default values for the components are normalized between 0.0 and 1.0. Values outside this range are automatically converted from the 0-255 range.

Attributes:
  • red: The red component of the color (0.0 to 1.0).
  • green: The green component of the color (0.0 to 1.0).
  • blue: The blue component of the color (0.0 to 1.0).
  • alpha: The alpha (transparency) component (0.0 to 1.0), default is 1.
  • space: The color space, default is "rgb".
Examples:
>>> red = Color(1.0, 0.0, 0.0)
>>> transparent_blue = Color(0.0, 0.0, 1.0, 0.5)
>>> rgb255 = Color(255, 0, 128)  # Will be automatically normalized
Color( red: int = 0, green: int = 0, blue: int = 0, alpha: int = 1, space: simetri.graphics.all_enums.ColorSpace = 'rgb')
red: int = 0
green: int = 0
blue: int = 0
alpha: int = 1
def copy(self):
159    def copy(self):
160        return Color(self.red, self.green, self.blue, self.alpha)
name
169    @property
170    def name(self):
171        # search for the color in the named colors
172        pass
rgb
180    @property
181    def rgb(self):
182        return (self.red, self.green, self.blue)
rgba
184    @property
185    def rgba(self):
186        return (self.red, self.green, self.blue, self.alpha)
rgb255
188    @property
189    def rgb255(self):
190        r, g, b = self.rgb
191        if r > 1 or g > 1 or b > 1:
192            return (r, g, b)
193        return tuple(round(i * 255) for i in self.rgb)
rgba255
195    @property
196    def rgba255(self):
197        return tuple(round(i * 255) for i in self.rgba)
class Rectangle(simetri.graphics.shape.Shape):
 33class Rectangle(Shape):
 34    """A rectangle defined by width and height."""
 35
 36    def __init__(self, center: Point, width: float, height: float, **kwargs) -> None:
 37        """Initialize a Rectangle object.
 38
 39        Args:
 40            center (Point): The center point of the rectangle.
 41            width (float): The width of the rectangle.
 42            height (float): The height of the rectangle.
 43            **kwargs: Additional keyword arguments.
 44        """
 45        x, y = center[:2]
 46        half_width = width / 2
 47        half_height = height / 2
 48        vertices = [
 49            (x - half_width, y - half_height),
 50            (x + half_width, y - half_height),
 51            (x + half_width, y + half_height),
 52            (x - half_width, y + half_height),
 53        ]
 54        super().__init__(vertices, closed=True, **kwargs)
 55        self.subtype = Types.RECTANGLE
 56
 57    def __setattr__(self, name, value):
 58        """Set an attribute of the rectangle.
 59
 60        Args:
 61            name (str): The name of the attribute.
 62            value (Any): The value of the attribute.
 63        """
 64        if name == "center":
 65            self._set_center(value)
 66        elif name == "width":
 67            self._set_width(value)
 68        elif name == "height":
 69            self._set_height(value)
 70        else:
 71            super().__setattr__(name, value)
 72
 73    def scale(
 74        self,
 75        scale_x: float,
 76        scale_y: Union[float, None] = None,
 77        about: Point = (0, 0),
 78        reps: int = 0,
 79    ):
 80        """Scale the rectangle by scale_x and scale_y.
 81        Rectangles cannot be scaled non-uniformly.
 82        scale_x changes the width and scale_y changes the height.
 83
 84        Args:
 85            scale_x (float): The scale factor for the width.
 86            scale_y (float, optional): The scale factor for the height. Defaults to None.
 87            about (Point, optional): The point to scale about. Defaults to (0, 0).
 88            reps (int, optional): The number of repetitions. Defaults to 0.
 89
 90        Returns:
 91            Rectangle: The scaled rectangle.
 92        """
 93        if scale_y is None:
 94            scale_y = scale_x
 95        center = self.center
 96        _, rotation, _ = decompose_transformations(self.xform_matrix)
 97        rm = rotation_matrix(-rotation, center)
 98        sm = scale_in_place_matrix(scale_x, scale_y, about)
 99        inv_rm = rotation_matrix(rotation, center)
100        transform = rm @ sm @ inv_rm
101
102        return self._update(transform, reps=reps)
103
104    @property
105    def width(self):
106        """Return the width of the rectangle.
107
108        Returns:
109            float: The width of the rectangle.
110        """
111        return distance(self.vertices[0], self.vertices[1])
112
113    def _set_width(self, new_width: float):
114        """Set the width of the rectangle.
115
116        Args:
117            new_width (float): The new width of the rectangle.
118        """
119        scale_x = new_width / self.width
120        self.scale(scale_x, 1, about=self.center, reps=0)
121
122    @property
123    def height(self):
124        """Return the height of the rectangle.
125
126        Returns:
127            float: The height of the rectangle.
128        """
129        return distance(self.vertices[1], self.vertices[2])
130
131    def _set_height(self, new_height: float):
132        """Set the height of the rectangle.
133
134        Args:
135            new_height (float): The new height of the rectangle.
136        """
137        scale_y = new_height / self.height
138        self.scale(1, scale_y, about=self.center, reps=0)
139
140    @property
141    def center(self):
142        """Return the center of the rectangle.
143
144        Returns:
145            Point: The center of the rectangle.
146        """
147        return mid_point(self.vertices[0], self.vertices[2])
148
149    def _set_center(self, new_center: Point):
150        """Set the center of the rectangle.
151
152        Args:
153            new_center (Point): The new center of the rectangle.
154        """
155        center = self.center
156        x_diff = new_center[0] - center[0]
157        y_diff = new_center[1] - center[1]
158        for i in range(4):
159            x, y = self.vertices[i][:2]
160            self[i] = (x + x_diff, y + y_diff)
161
162    def copy(self):
163        """Return a copy of the rectangle.
164
165        Returns:
166            Rectangle: A copy of the rectangle.
167        """
168        center = self.center
169        width = self.width
170        height = self.height
171        rectangle = Rectangle(center, width, height)
172        _, rotation, _ = decompose_transformations(self.xform_matrix)
173        rectangle.rotate(rotation, about=center, reps=0)
174        style = copy.copy(self.style)
175        rectangle.style = style
176        rectangle._set_aliases()
177        custom_attribs = custom_attributes(self)
178        for attrib in custom_attribs:
179            setattr(rectangle, attrib, getattr(self, attrib))
180
181        return rectangle

A rectangle defined by width and height.

Rectangle(center: Sequence[float], width: float, height: float, **kwargs)
36    def __init__(self, center: Point, width: float, height: float, **kwargs) -> None:
37        """Initialize a Rectangle object.
38
39        Args:
40            center (Point): The center point of the rectangle.
41            width (float): The width of the rectangle.
42            height (float): The height of the rectangle.
43            **kwargs: Additional keyword arguments.
44        """
45        x, y = center[:2]
46        half_width = width / 2
47        half_height = height / 2
48        vertices = [
49            (x - half_width, y - half_height),
50            (x + half_width, y - half_height),
51            (x + half_width, y + half_height),
52            (x - half_width, y + half_height),
53        ]
54        super().__init__(vertices, closed=True, **kwargs)
55        self.subtype = Types.RECTANGLE

Initialize a Rectangle object.

Arguments:
  • center (Point): The center point of the rectangle.
  • width (float): The width of the rectangle.
  • height (float): The height of the rectangle.
  • **kwargs: Additional keyword arguments.
subtype
def scale( self, scale_x: float, scale_y: Optional[float] = None, about: Sequence[float] = (0, 0), reps: int = 0):
 73    def scale(
 74        self,
 75        scale_x: float,
 76        scale_y: Union[float, None] = None,
 77        about: Point = (0, 0),
 78        reps: int = 0,
 79    ):
 80        """Scale the rectangle by scale_x and scale_y.
 81        Rectangles cannot be scaled non-uniformly.
 82        scale_x changes the width and scale_y changes the height.
 83
 84        Args:
 85            scale_x (float): The scale factor for the width.
 86            scale_y (float, optional): The scale factor for the height. Defaults to None.
 87            about (Point, optional): The point to scale about. Defaults to (0, 0).
 88            reps (int, optional): The number of repetitions. Defaults to 0.
 89
 90        Returns:
 91            Rectangle: The scaled rectangle.
 92        """
 93        if scale_y is None:
 94            scale_y = scale_x
 95        center = self.center
 96        _, rotation, _ = decompose_transformations(self.xform_matrix)
 97        rm = rotation_matrix(-rotation, center)
 98        sm = scale_in_place_matrix(scale_x, scale_y, about)
 99        inv_rm = rotation_matrix(rotation, center)
100        transform = rm @ sm @ inv_rm
101
102        return self._update(transform, reps=reps)

Scale the rectangle by scale_x and scale_y. Rectangles cannot be scaled non-uniformly. scale_x changes the width and scale_y changes the height.

Arguments:
  • scale_x (float): The scale factor for the width.
  • scale_y (float, optional): The scale factor for the height. Defaults to None.
  • about (Point, optional): The point to scale about. Defaults to (0, 0).
  • reps (int, optional): The number of repetitions. Defaults to 0.
Returns:

Rectangle: The scaled rectangle.

width
104    @property
105    def width(self):
106        """Return the width of the rectangle.
107
108        Returns:
109            float: The width of the rectangle.
110        """
111        return distance(self.vertices[0], self.vertices[1])

Return the width of the rectangle.

Returns:

float: The width of the rectangle.

height
122    @property
123    def height(self):
124        """Return the height of the rectangle.
125
126        Returns:
127            float: The height of the rectangle.
128        """
129        return distance(self.vertices[1], self.vertices[2])

Return the height of the rectangle.

Returns:

float: The height of the rectangle.

center
140    @property
141    def center(self):
142        """Return the center of the rectangle.
143
144        Returns:
145            Point: The center of the rectangle.
146        """
147        return mid_point(self.vertices[0], self.vertices[2])

Return the center of the rectangle.

Returns:

Point: The center of the rectangle.

def copy(self):
162    def copy(self):
163        """Return a copy of the rectangle.
164
165        Returns:
166            Rectangle: A copy of the rectangle.
167        """
168        center = self.center
169        width = self.width
170        height = self.height
171        rectangle = Rectangle(center, width, height)
172        _, rotation, _ = decompose_transformations(self.xform_matrix)
173        rectangle.rotate(rotation, about=center, reps=0)
174        style = copy.copy(self.style)
175        rectangle.style = style
176        rectangle._set_aliases()
177        custom_attribs = custom_attributes(self)
178        for attrib in custom_attribs:
179            setattr(rectangle, attrib, getattr(self, attrib))
180
181        return rectangle

Return a copy of the rectangle.

Returns:

Rectangle: A copy of the rectangle.

class Rectangle2(Rectangle):
184class Rectangle2(Rectangle):
185    """A rectangle defined by two opposite corners."""
186
187    def __init__(self, corner1: Point, corner2: Point, **kwargs) -> None:
188        """Initialize a Rectangle2 object.
189
190        Args:
191            corner1 (Point): The first corner of the rectangle.
192            corner2 (Point): The second corner of the rectangle.
193            **kwargs: Additional keyword arguments.
194        """
195        x1, y1 = corner1
196        x2, y2 = corner2
197        x_min, x_max = min(x1, x2), max(x1, x2)
198        y_min, y_max = min(y1, y2), max(y1, y2)
199        center = ((x_min + x_max) / 2, (y_min + y_max) / 2)
200        width = x_max - x_min
201        height = y_max - y_min
202        super().__init__(center, width, height, **kwargs)

A rectangle defined by two opposite corners.

Rectangle2(corner1: Sequence[float], corner2: Sequence[float], **kwargs)
187    def __init__(self, corner1: Point, corner2: Point, **kwargs) -> None:
188        """Initialize a Rectangle2 object.
189
190        Args:
191            corner1 (Point): The first corner of the rectangle.
192            corner2 (Point): The second corner of the rectangle.
193            **kwargs: Additional keyword arguments.
194        """
195        x1, y1 = corner1
196        x2, y2 = corner2
197        x_min, x_max = min(x1, x2), max(x1, x2)
198        y_min, y_max = min(y1, y2), max(y1, y2)
199        center = ((x_min + x_max) / 2, (y_min + y_max) / 2)
200        width = x_max - x_min
201        height = y_max - y_min
202        super().__init__(center, width, height, **kwargs)

Initialize a Rectangle2 object.

Arguments:
  • corner1 (Point): The first corner of the rectangle.
  • corner2 (Point): The second corner of the rectangle.
  • **kwargs: Additional keyword arguments.
class Circle(simetri.graphics.shape.Shape):
205class Circle(Shape):
206    """A circle defined by a center point and a radius."""
207
208    def __init__(
209        self,
210        center: Point = (0, 0),
211        radius: float = None,
212        xform_matrix: np.array = None,
213        **kwargs,
214    ) -> None:
215        """Initialize a Circle object.
216
217        Args:
218            center (Point, optional): The center point of the circle. Defaults to (0, 0).
219            radius (float, optional): The radius of the circle. Defaults to None.
220            xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
221            **kwargs: Additional keyword arguments.
222        """
223        if radius is None:
224            radius = defaults["circle_radius"]
225
226        x, y = center[:2]
227        points = [[x, y]]
228        super().__init__(points, xform_matrix=xform_matrix, **kwargs)
229        self.subtype = Types.CIRCLE
230        self._radius = radius
231
232    def __setattr__(self, name, value):
233        """Set an attribute of the circle.
234
235        Args:
236            name (str): The name of the attribute.
237            value (Any): The value of the attribute.
238        """
239        if name == "center":
240            self[0] = value[:2]
241        elif name == "radius":
242            ratio = value / self.radius
243            self.scale(ratio, about=self.center, reps=0)
244        else:
245            super().__setattr__(name, value)
246
247    @property
248    def b_box(self):
249        """Return the bounding box of the shape.
250
251        Returns:
252            BoundingBox: The bounding box of the shape.
253        """
254        x, y = self.center[:2]
255        x1, y1 = x - self.radius, y - self.radius
256        x2, y2 = x + self.radius, y + self.radius
257        self._b_box = BoundingBox((x1, y1), (x2, y2))
258
259        return self._b_box
260
261    @property
262    def closed(self):
263        """Return True. Circles are closed.
264
265        Returns:
266            bool: True
267        """
268        return True
269
270    @closed.setter
271    def closed(self, value: bool):
272        pass
273
274    @property
275    def center(self):
276        """Return the center of the circle.
277
278        Returns:
279            Point: The center of the circle.
280        """
281        return self.vertices[0]
282
283    @center.setter
284    def center(self, value: Point):
285        """Set the center of the circle.
286
287        Args:
288            value (Point): The new center of the circle.
289        """
290        self[0] = value[:2]
291
292    @property
293    def radius(self):
294        """Return the radius of the circle.
295
296        Returns:
297            float: The radius of the circle.
298        """
299        scale_x = np.linalg.norm(self.xform_matrix[0, :2])  # only x scale is used
300        return self._radius * scale_x
301
302    def copy(self):
303        """Return a copy of the circle.
304
305        Returns:
306            Circle: A copy of the circle.
307        """
308        center = self.center
309        radius = self.radius
310        circle = Circle(center=center, radius=radius)
311        style = copy.deepcopy(self.style)
312        circle.style = style
313        circle._set_aliases()
314
315        custom_attribs = custom_attributes(self)
316        custom_attribs.remove("center")
317        custom_attribs.remove("_radius")
318        custom_attribs.remove("radius")
319        for attrib in custom_attribs:
320            setattr(circle, attrib, getattr(self, attrib))
321
322        return circle

A circle defined by a center point and a radius.

Circle( center: Sequence[float] = (0, 0), radius: float = None, xform_matrix: <built-in function array> = None, **kwargs)
208    def __init__(
209        self,
210        center: Point = (0, 0),
211        radius: float = None,
212        xform_matrix: np.array = None,
213        **kwargs,
214    ) -> None:
215        """Initialize a Circle object.
216
217        Args:
218            center (Point, optional): The center point of the circle. Defaults to (0, 0).
219            radius (float, optional): The radius of the circle. Defaults to None.
220            xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
221            **kwargs: Additional keyword arguments.
222        """
223        if radius is None:
224            radius = defaults["circle_radius"]
225
226        x, y = center[:2]
227        points = [[x, y]]
228        super().__init__(points, xform_matrix=xform_matrix, **kwargs)
229        self.subtype = Types.CIRCLE
230        self._radius = radius

Initialize a Circle object.

Arguments:
  • center (Point, optional): The center point of the circle. Defaults to (0, 0).
  • radius (float, optional): The radius of the circle. Defaults to None.
  • xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
  • **kwargs: Additional keyword arguments.
subtype
b_box
247    @property
248    def b_box(self):
249        """Return the bounding box of the shape.
250
251        Returns:
252            BoundingBox: The bounding box of the shape.
253        """
254        x, y = self.center[:2]
255        x1, y1 = x - self.radius, y - self.radius
256        x2, y2 = x + self.radius, y + self.radius
257        self._b_box = BoundingBox((x1, y1), (x2, y2))
258
259        return self._b_box

Return the bounding box of the shape.

Returns:

BoundingBox: The bounding box of the shape.

closed
261    @property
262    def closed(self):
263        """Return True. Circles are closed.
264
265        Returns:
266            bool: True
267        """
268        return True

Return True. Circles are closed.

Returns:

bool: True

center
274    @property
275    def center(self):
276        """Return the center of the circle.
277
278        Returns:
279            Point: The center of the circle.
280        """
281        return self.vertices[0]

Return the center of the circle.

Returns:

Point: The center of the circle.

radius
292    @property
293    def radius(self):
294        """Return the radius of the circle.
295
296        Returns:
297            float: The radius of the circle.
298        """
299        scale_x = np.linalg.norm(self.xform_matrix[0, :2])  # only x scale is used
300        return self._radius * scale_x

Return the radius of the circle.

Returns:

float: The radius of the circle.

def copy(self):
302    def copy(self):
303        """Return a copy of the circle.
304
305        Returns:
306            Circle: A copy of the circle.
307        """
308        center = self.center
309        radius = self.radius
310        circle = Circle(center=center, radius=radius)
311        style = copy.deepcopy(self.style)
312        circle.style = style
313        circle._set_aliases()
314
315        custom_attribs = custom_attributes(self)
316        custom_attribs.remove("center")
317        custom_attribs.remove("_radius")
318        custom_attribs.remove("radius")
319        for attrib in custom_attribs:
320            setattr(circle, attrib, getattr(self, attrib))
321
322        return circle

Return a copy of the circle.

Returns:

Circle: A copy of the circle.

class Segment(simetri.graphics.shape.Shape):
325class Segment(Shape):
326    """A line segment defined by two points.
327    This is not used in the code-base, but is here for the API.
328    """
329
330    def __init__(self, start: Point, end: Point, **kwargs) -> None:
331        """Initialize a Segment object.
332
333        Args:
334            start (Point): The start point of the segment.
335            end (Point): The end point of the segment.
336            **kwargs: Additional keyword arguments.
337
338        Raises:
339            ValueError: If the start and end points are the same.
340        """
341        dist_tol2 = defaults["dist_tol"] ** 2
342        if close_points2(start, end, dist2=dist_tol2):
343            raise ValueError("Segment: start and end points are the same!")
344        points = [start, end]
345        super().__init__(points, **kwargs)
346        self.subtype = Types.SEGMENT
347
348    @property
349    def start(self):
350        """Return the start point of the segment.
351
352        Returns:
353            Point: The start point of the segment.
354        """
355        return self.vertices[0]
356
357    @property
358    def end(self):
359        """Return the end point of the segment.
360
361        Returns:
362            Point: The end point of the segment.
363        """
364        return self.vertices[1]
365
366    @property
367    def length(self):
368        """Return the length of the segment.
369
370        Returns:
371            float: The length of the segment.
372        """
373        return distance(self.start, self.end)
374
375    def copy(self) -> Shape:
376        """Return a copy of the segment.
377
378        Returns:
379            Shape: A copy of the segment.
380        """
381        return Segment(self.start, self.end, **self.kwargs)
382
383    def __str__(self):
384        """Return a string representation of the segment.
385
386        Returns:
387            str: The string representation of the segment.
388        """
389        return f"Segment({self.start}, {self.end})"
390
391    def __repr__(self):
392        """Return a string representation of the segment.
393
394        Returns:
395            str: The string representation of the segment.
396        """
397        return f"Segment({self.start}, {self.end})"
398
399    def __eq__(self, other):
400        """Check if the segment is equal to another segment.
401
402        Args:
403            other (Segment): The other segment to compare to.
404
405        Returns:
406            bool: True if the segments are equal, False otherwise.
407        """
408        return (
409            other.type == Types.SEGMENT
410            and self.start == other.start
411            and self.end == other.end
412        )

A line segment defined by two points. This is not used in the code-base, but is here for the API.

Segment(start: Sequence[float], end: Sequence[float], **kwargs)
330    def __init__(self, start: Point, end: Point, **kwargs) -> None:
331        """Initialize a Segment object.
332
333        Args:
334            start (Point): The start point of the segment.
335            end (Point): The end point of the segment.
336            **kwargs: Additional keyword arguments.
337
338        Raises:
339            ValueError: If the start and end points are the same.
340        """
341        dist_tol2 = defaults["dist_tol"] ** 2
342        if close_points2(start, end, dist2=dist_tol2):
343            raise ValueError("Segment: start and end points are the same!")
344        points = [start, end]
345        super().__init__(points, **kwargs)
346        self.subtype = Types.SEGMENT

Initialize a Segment object.

Arguments:
  • start (Point): The start point of the segment.
  • end (Point): The end point of the segment.
  • **kwargs: Additional keyword arguments.
Raises:
  • ValueError: If the start and end points are the same.
subtype
start
348    @property
349    def start(self):
350        """Return the start point of the segment.
351
352        Returns:
353            Point: The start point of the segment.
354        """
355        return self.vertices[0]

Return the start point of the segment.

Returns:

Point: The start point of the segment.

end
357    @property
358    def end(self):
359        """Return the end point of the segment.
360
361        Returns:
362            Point: The end point of the segment.
363        """
364        return self.vertices[1]

Return the end point of the segment.

Returns:

Point: The end point of the segment.

length
366    @property
367    def length(self):
368        """Return the length of the segment.
369
370        Returns:
371            float: The length of the segment.
372        """
373        return distance(self.start, self.end)

Return the length of the segment.

Returns:

float: The length of the segment.

def copy(self) -> simetri.graphics.shape.Shape:
375    def copy(self) -> Shape:
376        """Return a copy of the segment.
377
378        Returns:
379            Shape: A copy of the segment.
380        """
381        return Segment(self.start, self.end, **self.kwargs)

Return a copy of the segment.

Returns:

Shape: A copy of the segment.

class Mask(simetri.graphics.shape.Shape):
415class Mask(Shape):
416    """A mask is a closed shape that is used to clip other shapes.
417    All it has is points and a transformation matrix.
418    """
419
420    def __init__(self, points, reverse=False, xform_matrix=None):
421        """Initialize a Mask object.
422
423        Args:
424            points (Sequence[Point]): The points that make up the mask.
425            reverse (bool, optional): Whether to reverse the mask. Defaults to False.
426            xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
427        """
428        super().__init__(points, xform_matrix, subtype=Types.MASK, closed=True)
429        self.reverse: bool = reverse
430        # mask should be between \begin{scope} and \end{scope}
431        # canvas, batch, and shapes can have scope

A mask is a closed shape that is used to clip other shapes. All it has is points and a transformation matrix.

Mask(points, reverse=False, xform_matrix=None)
420    def __init__(self, points, reverse=False, xform_matrix=None):
421        """Initialize a Mask object.
422
423        Args:
424            points (Sequence[Point]): The points that make up the mask.
425            reverse (bool, optional): Whether to reverse the mask. Defaults to False.
426            xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
427        """
428        super().__init__(points, xform_matrix, subtype=Types.MASK, closed=True)
429        self.reverse: bool = reverse
430        # mask should be between \begin{scope} and \end{scope}
431        # canvas, batch, and shapes can have scope

Initialize a Mask object.

Arguments:
  • points (Sequence[Point]): The points that make up the mask.
  • reverse (bool, optional): Whether to reverse the mask. Defaults to False.
  • xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
def reverse(self) -> typing_extensions.Self:
797    def reverse(self) -> Self:
798        """Reverse the order of the vertices.
799
800        Returns:
801            None
802        """
803        self.primary_points.reverse()
804
805        return self

Reverse the order of the vertices.

Returns:

None

def circle_points( center: Sequence[float], radius: float, n: int = 30) -> list[typing.Sequence[float]]:
434def circle_points(center: Point, radius: float, n: int = 30) -> list[Point]:
435    """Return a list of points that form a circle with the given parameters.
436
437    Args:
438        center (Point): The center point of the circle.
439        radius (float): The radius of the circle.
440        n (int, optional): The number of points in the circle. Defaults to 30.
441
442    Returns:
443        list[Point]: A list of points that form a circle.
444    """
445    return arc_points(center, radius, 0, 2 * pi, n=n)

Return a list of points that form a circle with the given parameters.

Arguments:
  • center (Point): The center point of the circle.
  • radius (float): The radius of the circle.
  • n (int, optional): The number of points in the circle. Defaults to 30.
Returns:

list[Point]: A list of points that form a circle.

def arc_points( center: Sequence[float], radius: float, start_angle: float, end_angle: float, clockwise: bool = False, n: int = 20) -> list[typing.Sequence[float]]:
448def arc_points(
449    center: Point,
450    radius: float,
451    start_angle: float,
452    end_angle: float,
453    clockwise: bool = False,
454    n: int = 20,
455) -> list[Point]:
456    """Return a list of points that form a circular arc with the given parameters.
457
458    Args:
459        center (Point): The center point of the arc.
460        radius (float): The radius of the arc.
461        start_angle (float): The starting angle of the arc.
462        end_angle (float): The ending angle of the arc.
463        clockwise (bool, optional): Whether the arc is drawn clockwise. Defaults to False.
464        n (int, optional): The number of points in the arc. Defaults to 20.
465
466    Returns:
467        list[Point]: A list of points that form a circular arc.
468    """
469    x, y = center[:2]
470    points = []
471    if clockwise:
472        start_angle, end_angle = end_angle, start_angle
473    step = (end_angle - start_angle) / n
474    for i in np.arange(start_angle, end_angle + 1, step):
475        points.append([x + radius * cos(i), y + radius * sin(i)])
476    return points

Return a list of points that form a circular arc with the given parameters.

Arguments:
  • center (Point): The center point of the arc.
  • radius (float): The radius of the arc.
  • start_angle (float): The starting angle of the arc.
  • end_angle (float): The ending angle of the arc.
  • clockwise (bool, optional): Whether the arc is drawn clockwise. Defaults to False.
  • n (int, optional): The number of points in the arc. Defaults to 20.
Returns:

list[Point]: A list of points that form a circular arc.

def hex_points(side_length: float) -> List[List[float]]:
479def hex_points(side_length: float) -> List[List[float]]:
480    """Return a list of points that define a hexagon with a given side length.
481
482    Args:
483        side_length (float): The length of each side of the hexagon.
484
485    Returns:
486        list[list[float]]: A list of points that define the hexagon.
487    """
488    points = []
489    for i in range(6):
490        x = side_length * cos(i * 2 * pi / 6)
491        y = side_length * sin(i * 2 * pi / 6)
492        points.append((x, y))
493    return points

Return a list of points that define a hexagon with a given side length.

Arguments:
  • side_length (float): The length of each side of the hexagon.
Returns:

list[list[float]]: A list of points that define the hexagon.

def rectangle_points( x: float, y: float, width: float, height: float, angle: float = 0) -> Sequence[Sequence[float]]:
496def rectangle_points(
497    x: float, y: float, width: float, height: float, angle: float = 0
498) -> Sequence[Point]:
499    """Return a list of points that form a rectangle with the given parameters.
500
501    Args:
502        x (float): The x-coordinate of the center of the rectangle.
503        y (float): The y-coordinate of the center of the rectangle.
504        width (float): The width of the rectangle.
505        height (float): The height of the rectangle.
506        angle (float, optional): The rotation angle of the rectangle. Defaults to 0.
507
508    Returns:
509        Sequence[Point]: A list of points that form the rectangle.
510    """
511    from affine import rotate
512
513    points = []
514    points.append([x - width / 2, y - height / 2])
515    points.append([x + width / 2, y - height / 2])
516    points.append([x + width / 2, y + height / 2])
517    points.append([x - width / 2, y + height / 2])
518    if angle != 0:
519        points = rotate(points, angle, (x, y))
520    return points

Return a list of points that form a rectangle with the given parameters.

Arguments:
  • x (float): The x-coordinate of the center of the rectangle.
  • y (float): The y-coordinate of the center of the rectangle.
  • width (float): The width of the rectangle.
  • height (float): The height of the rectangle.
  • angle (float, optional): The rotation angle of the rectangle. Defaults to 0.
Returns:

Sequence[Point]: A list of points that form the rectangle.

def reg_poly_points_side_length( pos: Sequence[float], n: int, side_len: float) -> Sequence[Sequence[float]]:
523def reg_poly_points_side_length(pos: Point, n: int, side_len: float) -> Sequence[Point]:
524    """Return a regular polygon points list with n sides and side_len length.
525
526    Args:
527        pos (Point): The position of the center of the polygon.
528        n (int): The number of sides of the polygon.
529        side_len (float): The length of each side of the polygon.
530
531    Returns:
532        Sequence[Point]: A list of points that form the polygon.
533    """
534    rad = side_len_to_radius(n, side_len)
535    angle = 2 * pi / n
536    x, y = pos[:2]
537    points = [[cos(angle * i) * rad + x, sin(angle * i) * rad + y] for i in range(n)]
538    points.append(points[0])
539    return points

Return a regular polygon points list with n sides and side_len length.

Arguments:
  • pos (Point): The position of the center of the polygon.
  • n (int): The number of sides of the polygon.
  • side_len (float): The length of each side of the polygon.
Returns:

Sequence[Point]: A list of points that form the polygon.

def reg_poly_points(pos: Sequence[float], n: int, r: float) -> Sequence[Sequence[float]]:
542def reg_poly_points(pos: Point, n: int, r: float) -> Sequence[Point]:
543    """Return a regular polygon points list with n sides and radius r.
544
545    Args:
546        pos (Point): The position of the center of the polygon.
547        n (int): The number of sides of the polygon.
548        r (float): The radius of the polygon.
549
550    Returns:
551        Sequence[Point]: A list of points that form the polygon.
552    """
553    angle = 2 * pi / n
554    x, y = pos[:2]
555    points = [[cos(angle * i) * r + x, sin(angle * i) * r + y] for i in range(n)]
556    points.append(points[0])
557    return points

Return a regular polygon points list with n sides and radius r.

Arguments:
  • pos (Point): The position of the center of the polygon.
  • n (int): The number of sides of the polygon.
  • r (float): The radius of the polygon.
Returns:

Sequence[Point]: A list of points that form the polygon.

def di_star( points: Sequence[Sequence[float]], n: int) -> simetri.graphics.batch.Batch:
560def di_star(points: Sequence[Point], n: int) -> Batch:
561    """Return a dihedral star with n petals.
562
563    Args:
564        points (Sequence[Point]): List of [x, y] points.
565        n (int): Number of petals.
566
567    Returns:
568        Batch: A Batch instance (dihedral star with n petals).
569    """
570    batch = Batch(Shape(points))
571    return batch.mirror(axis_x, reps=1).rotate(2 * pi / n, reps=n - 1)

Return a dihedral star with n petals.

Arguments:
  • points (Sequence[Point]): List of [x, y] points.
  • n (int): Number of petals.
Returns:

Batch: A Batch instance (dihedral star with n petals).

def hex_grid_centers(x, y, side_length, n_rows, n_cols):
574def hex_grid_centers(x, y, side_length, n_rows, n_cols):
575    """Return a list of points that define the centers of hexagons in a grid.
576
577    Args:
578        x (float): The x-coordinate of the starting point.
579        y (float): The y-coordinate of the starting point.
580        side_length (float): The length of each side of the hexagons.
581        n_rows (int): The number of rows in the grid.
582        n_cols (int): The number of columns in the grid.
583
584    Returns:
585        list[Point]: A list of points that define the centers of the hexagons.
586    """
587    centers = []
588    for row in range(n_rows):
589        for col in range(n_cols):
590            x_ = col * 3 * side_length + x
591            y_ = row * 2 * side_length + y
592            if col % 2:
593                y_ += side_length
594            centers.append((x_, y_))
595
596    centers = []
597    # first row
598    origin = Point(x, y)
599    grid = Batch(Point)
600    grid.transform()
601    return centers

Return a list of points that define the centers of hexagons in a grid.

Arguments:
  • x (float): The x-coordinate of the starting point.
  • y (float): The y-coordinate of the starting point.
  • side_length (float): The length of each side of the hexagons.
  • n_rows (int): The number of rows in the grid.
  • n_cols (int): The number of columns in the grid.
Returns:

list[Point]: A list of points that define the centers of the hexagons.

def rect_grid(x, y, cell_width, cell_height, n_rows, n_cols, pattern):
604def rect_grid(x, y, cell_width, cell_height, n_rows, n_cols, pattern):
605    """Return a grid of rectangles with the given parameters.
606
607    Args:
608        x (float): The x-coordinate of the starting point.
609        y (float): The y-coordinate of the starting point.
610        cell_width (float): The width of each cell in the grid.
611        cell_height (float): The height of each cell in the grid.
612        n_rows (int): The number of rows in the grid.
613        n_cols (int): The number of columns in the grid.
614        pattern (list[list[bool]]): A pattern to fill the grid.
615
616    Returns:
617        Batch: A Batch object representing the grid.
618    """
619    width = cell_width * n_cols
620    height = cell_height * n_rows
621    horiz_line = line_shape((x, y), (x + width, y))
622    horiz_lines = Batch(horiz_line)
623    horiz_lines.translate(0, cell_height, reps=n_rows)
624    vert_line = line_shape((x, y), (x, y + height))
625    vert_lines = Batch(vert_line)
626    vert_lines.translate(cell_width, 0, reps=n_cols)
627    grid = Batch(horiz_lines, *vert_lines)
628    for row in range(n_rows):
629        for col in range(n_cols):
630            if pattern[row][col]:
631                x_, y_ = (col * cell_width + x, (n_rows - row - 1) * cell_height + y)
632                points = [
633                    (x_, y_),
634                    (x_ + cell_width, y_),
635                    (x_ + cell_width, y_ + cell_height),
636                    (x_, y_ + cell_height),
637                ]
638                cell = Shape(points, closed=True, fill_color=colors.gray)
639                grid.append(cell)
640    return grid

Return a grid of rectangles with the given parameters.

Arguments:
  • x (float): The x-coordinate of the starting point.
  • y (float): The y-coordinate of the starting point.
  • cell_width (float): The width of each cell in the grid.
  • cell_height (float): The height of each cell in the grid.
  • n_rows (int): The number of rows in the grid.
  • n_cols (int): The number of columns in the grid.
  • pattern (list[list[bool]]): A pattern to fill the grid.
Returns:

Batch: A Batch object representing the grid.

def regular_star_polygon(n, step, rad):
643def regular_star_polygon(n, step, rad):
644    """
645    Return a regular star polygon with the given parameters.
646
647    :param n: The number of vertices of the star polygon.
648    :type n: int
649    :param step: The step size for connecting vertices.
650    :type step: int
651    :param rad: The radius of the star polygon.
652    :type rad: float
653    :return: A Batch object representing the star polygon.
654    :rtype: Batch
655    """
656    angle = 2 * pi / n
657    points = [(cos(angle * i) * rad, sin(angle * i) * rad) for i in range(n)]
658    if n % step:
659        indices = [i % n for i in list(range(0, (n + 1) * step, step))]
660    else:
661        indices = [i % n for i in list(range(0, ((n // step) + 1) * step, step))]
662    vertices = [points[ind] for ind in indices]
663    return Batch(Shape(vertices)).rotate(angle, reps=gcd(n, step) - 1)

Return a regular star polygon with the given parameters.

Parameters
  • n: The number of vertices of the star polygon.
  • step: The step size for connecting vertices.
  • rad: The radius of the star polygon.
Returns

A Batch object representing the star polygon.

def star_shape(points, reps=5, scale=1):
666def star_shape(points, reps=5, scale=1):
667    """Return a dihedral star from a list of points.
668
669    Args:
670        points (list[Point]): The list of points that form the star.
671        reps (int, optional): The number of repetitions. Defaults to 5.
672        scale (float, optional): The scale factor. Defaults to 1.
673
674    Returns:
675        Batch: A Batch object representing the star.
676    """
677    shape = Shape(points, subtype=Types.STAR)
678    batch = Batch(shape)
679    batch.mirror(axis_x, reps=1)
680    batch.rotate(2 * pi / (reps), reps=reps - 1)
681    batch.scale(scale)
682    return batch

Return a dihedral star from a list of points.

Arguments:
  • points (list[Point]): The list of points that form the star.
  • reps (int, optional): The number of repetitions. Defaults to 5.
  • scale (float, optional): The scale factor. Defaults to 1.
Returns:

Batch: A Batch object representing the star.

def dot_shape(x, y, radius=1, fill_color=None, line_color=None, line_width=None):
685def dot_shape(
686    x,
687    y,
688    radius=1,
689    fill_color=None,
690    line_color=None,
691    line_width=None,
692):
693    """Return a Shape object with a single point.
694
695    Args:
696        x (float): The x-coordinate of the point.
697        y (float): The y-coordinate of the point.
698        radius (float, optional): The radius of the point. Defaults to 1.
699        fill_color (Color, optional): The fill color of the point. Defaults to None.
700        line_color (Color, optional): The line color of the point. Defaults to None.
701        line_width (float, optional): The line width of the point. Defaults to None.
702
703    Returns:
704        Shape: A Shape object with a single point.
705    """
706    fill_color, line_color, line_width = get_defaults(
707        ["fill_color", "line_color", "line_width"], [fill_color, line_color, line_width]
708    )
709    dot_shape = Shape(
710        [(x, y)],
711        closed=True,
712        fill_color=fill_color,
713        line_color=line_color,
714        line_width=line_width,
715        subtype=Types.D_o_t,
716    )
717    dot_shape.marker = radius
718    return dot_shape

Return a Shape object with a single point.

Arguments:
  • x (float): The x-coordinate of the point.
  • y (float): The y-coordinate of the point.
  • radius (float, optional): The radius of the point. Defaults to 1.
  • fill_color (Color, optional): The fill color of the point. Defaults to None.
  • line_color (Color, optional): The line color of the point. Defaults to None.
  • line_width (float, optional): The line width of the point. Defaults to None.
Returns:

Shape: A Shape object with a single point.

def rect_shape( x: float, y: float, width: float, height: float, fill_color: Color = Color(1.0, 1.0, 1.0), line_color: Color = Color(0.0, 0.0, 0.0), line_width: float = 1, fill: bool = True, marker: 'Marker' = None) -> simetri.graphics.shape.Shape:
721def rect_shape(
722    x: float,
723    y: float,
724    width: float,
725    height: float,
726    fill_color: Color = colors.white,
727    line_color: Color = defaults["line_color"],
728    line_width: float = defaults["line_width"],
729    fill: bool = True,
730    marker: "Marker" = None,
731) -> Shape:
732    """Given lower left corner position, width, and height,
733    return a Shape object with points that form a rectangle.
734
735    Args:
736        x (float): The x-coordinate of the lower left corner.
737        y (float): The y-coordinate of the lower left corner.
738        width (float): The width of the rectangle.
739        height (float): The height of the rectangle.
740        fill_color (Color, optional): The fill color of the rectangle. Defaults to colors.white.
741        line_color (Color, optional): The line color of the rectangle. Defaults to defaults["line_color"].
742        line_width (float, optional): The line width of the rectangle. Defaults to defaults["line_width"].
743        fill (bool, optional): Whether to fill the rectangle. Defaults to True.
744        marker (Marker, optional): The marker for the rectangle. Defaults to None.
745
746    Returns:
747        Shape: A Shape object with points that form a rectangle.
748    """
749    return Shape(
750        [(x, y), (x + width, y), (x + width, y + height), (x, y + height)],
751        closed=True,
752        fill_color=fill_color,
753        line_color=line_color,
754        fill=fill,
755        line_width=line_width,
756        marker=marker,
757        subtype=Types.RECTANGLE,
758    )

Given lower left corner position, width, and height, return a Shape object with points that form a rectangle.

Arguments:
  • x (float): The x-coordinate of the lower left corner.
  • y (float): The y-coordinate of the lower left corner.
  • width (float): The width of the rectangle.
  • height (float): The height of the rectangle.
  • fill_color (Color, optional): The fill color of the rectangle. Defaults to colors.white.
  • line_color (Color, optional): The line color of the rectangle. Defaults to defaults["line_color"].
  • line_width (float, optional): The line width of the rectangle. Defaults to defaults["line_width"].
  • fill (bool, optional): Whether to fill the rectangle. Defaults to True.
  • marker (Marker, optional): The marker for the rectangle. Defaults to None.
Returns:

Shape: A Shape object with points that form a rectangle.

def arc_shape(x, y, radius, start_angle, end_angle, clockwise=False, n=20):
761def arc_shape(x, y, radius, start_angle, end_angle, clockwise=False, n=20):
762    """Return a Shape object with points that form a circular arc with the given parameters.
763
764    Args:
765        x (float): The x-coordinate of the center of the arc.
766        y (float): The y-coordinate of the center of the arc.
767        radius (float): The radius of the arc.
768        start_angle (float): The starting angle of the arc.
769        end_angle (float): The ending angle of the arc.
770        clockwise (bool, optional): Whether the arc is drawn clockwise. Defaults to False.
771        n (int, optional): The number of points to use for the arc. Defaults to 20.
772
773    Returns:
774        Shape: A Shape object with points that form a circular arc.
775    """
776    points = arc_points(x, y, radius, start_angle, end_angle, clockwise=clockwise, n=n)
777    return Shape(points, closed=False, subtype=Types.ARC)

Return a Shape object with points that form a circular arc with the given parameters.

Arguments:
  • x (float): The x-coordinate of the center of the arc.
  • y (float): The y-coordinate of the center of the arc.
  • radius (float): The radius of the arc.
  • start_angle (float): The starting angle of the arc.
  • end_angle (float): The ending angle of the arc.
  • clockwise (bool, optional): Whether the arc is drawn clockwise. Defaults to False.
  • n (int, optional): The number of points to use for the arc. Defaults to 20.
Returns:

Shape: A Shape object with points that form a circular arc.

def circle_shape(x, y, radius, n=30):
780def circle_shape(x, y, radius, n=30):
781    """Return a Shape object with points that form a circle with the given parameters.
782
783    Args:
784        x (float): The x-coordinate of the center of the circle.
785        y (float): The y-coordinate of the center of the circle.
786        radius (float): The radius of the circle.
787        n (int, optional): The number of points to use for the circle. Defaults to 30.
788
789    Returns:
790        Shape: A Shape object with points that form a circle.
791    """
792    circ = arc_shape(x, y, radius, 0, 2 * pi, n=n)
793    circ.subtype = Types.CIRCLE
794    return circ

Return a Shape object with points that form a circle with the given parameters.

Arguments:
  • x (float): The x-coordinate of the center of the circle.
  • y (float): The y-coordinate of the center of the circle.
  • radius (float): The radius of the circle.
  • n (int, optional): The number of points to use for the circle. Defaults to 30.
Returns:

Shape: A Shape object with points that form a circle.

def reg_poly_shape(pos, n, r=100, **kwargs):
797def reg_poly_shape(pos, n, r=100, **kwargs):
798    """Return a regular polygon.
799
800    Args:
801        pos (Point): The position of the center of the polygon.
802        n (int): The number of sides of the polygon.
803        r (float, optional): The radius of the polygon. Defaults to 100.
804        kwargs (dict): Additional keyword arguments.
805
806    Returns:
807        Shape: A Shape object with points that form a regular polygon.
808    """
809    x, y = pos[:2]
810    points = reg_poly_points((x, y), n=n, r=r)
811    return Shape(points, closed=True, **kwargs)

Return a regular polygon.

Arguments:
  • pos (Point): The position of the center of the polygon.
  • n (int): The number of sides of the polygon.
  • r (float, optional): The radius of the polygon. Defaults to 100.
  • kwargs (dict): Additional keyword arguments.
Returns:

Shape: A Shape object with points that form a regular polygon.

def ellipse_shape(x, y, width, height, angle, n_points=None):
814def ellipse_shape(x, y, width, height, angle, n_points=None):
815    """Return a Shape object with points that form an ellipse with the given parameters.
816
817    Args:
818        x (float): The x-coordinate of the center of the ellipse.
819        y (float): The y-coordinate of the center of the ellipse.
820        width (float): The width of the ellipse.
821        height (float): The height of the ellipse.
822        n (int, optional): The number of points to use for the ellipse. Defaults to 30.
823
824    Returns:
825        Shape: A Shape object with points that form an ellipse.
826    """
827    if n_points is None:
828        n_points = defaults["n_ellipse_points"]
829
830    points = ellipse_points((x, y), width, height, n_points=n)
831    return Shape(points, subtype=Types.ELLIPSE)

Return a Shape object with points that form an ellipse with the given parameters.

Arguments:
  • x (float): The x-coordinate of the center of the ellipse.
  • y (float): The y-coordinate of the center of the ellipse.
  • width (float): The width of the ellipse.
  • height (float): The height of the ellipse.
  • n (int, optional): The number of points to use for the ellipse. Defaults to 30.
Returns:

Shape: A Shape object with points that form an ellipse.

def line_shape(p1, p2, line_width=1, line_color=Color(0.0, 0.0, 0.0), **kwargs):
834def line_shape(p1, p2, line_width=1, line_color=colors.black, **kwargs):
835    """Return a Shape object with two points p1 and p2.
836
837    Args:
838        p1 (Point): The first point of the line.
839        p2 (Point): The second point of the line.
840        line_width (float, optional): The width of the line. Defaults to 1.
841        line_color (Color, optional): The color of the line. Defaults to colors.black.
842        kwargs (dict): Additional keyword arguments.
843
844    Returns:
845        Shape: A Shape object with two points that form a line.
846    """
847    x1, y1 = p1
848    x2, y2 = p2
849    return Shape(
850        [(x1, y1), (x2, y2)],
851        closed=False,
852        line_color=line_color,
853        line_width=line_width,
854        subtype=Types.L_i_n_e,
855        **kwargs,
856    )

Return a Shape object with two points p1 and p2.

Arguments:
  • p1 (Point): The first point of the line.
  • p2 (Point): The second point of the line.
  • line_width (float, optional): The width of the line. Defaults to 1.
  • line_color (Color, optional): The color of the line. Defaults to colors.black.
  • kwargs (dict): Additional keyword arguments.
Returns:

Shape: A Shape object with two points that form a line.

def offset_polygon_shape( polygon_shape, offset: float = 1, dist_tol: float = 0.05) -> list[typing.Sequence[float]]:
859def offset_polygon_shape(
860    polygon_shape, offset: float = 1, dist_tol: float = defaults["dist_tol"]
861) -> list[Point]:
862    """Return a copy of a polygon with offset edges.
863
864    Args:
865        polygon_shape (Shape): The original polygon shape.
866        offset (float, optional): The offset distance. Defaults to 1.
867        dist_tol (float, optional): The distance tolerance. Defaults to defaults["dist_tol"].
868
869    Returns:
870        list[Point]: A list of points that form the offset polygon.
871    """
872    vertices = offset_polygon_points(polygon_shape.vertices, offset, dist_tol)
873
874    return Shape(vertices)

Return a copy of a polygon with offset edges.

Arguments:
  • polygon_shape (Shape): The original polygon shape.
  • offset (float, optional): The offset distance. Defaults to 1.
  • dist_tol (float, optional): The distance tolerance. Defaults to defaults["dist_tol"].
Returns:

list[Point]: A list of points that form the offset polygon.