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)
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
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.
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.
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.
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.
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.
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.
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.
Inherited Members
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.
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.
Inherited Members
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.
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.
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.
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
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.
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.
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.
Inherited Members
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.
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.
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.
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.
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.
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.
Inherited Members
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.
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.
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
Inherited Members
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.