simetri.helpers.illustration
This module contains functions and classes for creating annotations, arrows, dimensions, etc.
1"""This module contains functions and classes for creating annotations, 2arrows, dimensions, etc.""" 3 4from dataclasses import dataclass 5from math import pi, atan2 6from PIL import ImageFont 7 8import fitz 9import numpy as np 10 11# from reportlab.pdfbase import pdfmetrics # to do: remove this 12 13from ..graphics.core import Base 14from ..graphics.bbox import bounding_box 15from ..graphics.points import Points 16from ..graphics.batch import Batch 17from ..graphics.shape import Shape 18 19from ..graphics.shapes import reg_poly_points_side_length 20from ..graphics.common import get_defaults, common_properties, Point, _set_Nones 21from ..graphics.all_enums import ( 22 Types, 23 LineJoin, 24 Anchor, 25 FrameShape, 26 HeadPos, 27 ArrowLine, 28 Placement, 29 FontSize, 30) 31from ..canvas.style_map import shape_style_map, tag_style_map, TagStyle 32from ..graphics.affine import identity_matrix 33from ..geometry.ellipse import Arc 34from ..geometry.geometry import ( 35 distance, 36 line_angle, 37 extended_line, 38 line_by_point_angle_length, 39 mid_point, 40) 41from .utilities import get_transform, detokenize 42from ..colors.swatches import swatches_255 43from ..settings.settings import defaults 44from ..colors import colors 45from .validation import validate_args 46 47Color = colors.Color 48array = np.array 49 50 51def logo(scale=1): 52 """Returns the Simetri logo. 53 54 Args: 55 scale (int, optional): Scale factor for the logo. Defaults to 1. 56 57 Returns: 58 Batch: A Batch object containing the logo shapes. 59 """ 60 w = 10 * scale 61 points = [ 62 (0, 0), 63 (-4, 0), 64 (-4, 6), 65 (1, 6), 66 (1, 2), 67 (-2, 2), 68 (-2, 4), 69 (-1, 4), 70 (-1, 3), 71 (0, 3), 72 (0, 5), 73 (-3, 5), 74 (-3, 1), 75 (5, 1), 76 (5, -10), 77 (0, -10), 78 (0, -6), 79 (3, -6), 80 (3, -8), 81 (2, -8), 82 (2, -7), 83 (1, -7), 84 (1, -9), 85 (4, -9), 86 (4, -5), 87 (-4, -5), 88 (-4, -1), 89 (-1, -1), 90 (-1, -3), 91 (-2, -3), 92 (-2, -2), 93 (-3, -2), 94 (-3, -4), 95 (0, -4), 96 ] 97 98 points2 = [ 99 (1, 0), 100 (1, -4), 101 (4, -4), 102 (4, -3), 103 (2, -3), 104 (2, -1), 105 (3, -1), 106 (3, -2), 107 (4, -2), 108 (4, 0), 109 ] 110 111 points = [(x * w, y * w) for x, y in points] 112 points2 = [(x * w, y * w) for x, y in points2] 113 kernel1 = Shape(points, closed=True) 114 kernel2 = Shape(points2, closed=True) 115 rad = 1 116 line_width = 2 117 kernel1.fillet_radius = rad 118 kernel2.fillet_radius = rad 119 kernel1.line_width = line_width 120 kernel2.line_width = line_width 121 fill_color = Color(*swatches_255[62][8]) 122 kernel1.fill_color = fill_color 123 kernel2.fill_color = colors.white 124 125 return Batch([kernel1, kernel2]) 126 127 128def convert_latex_font_size(latex_font_size: FontSize): 129 """Converts LaTeX font size to a numerical value. 130 131 Args: 132 latex_font_size (FontSize): The LaTeX font size. 133 134 Returns: 135 int: The corresponding numerical font size. 136 """ 137 d_font_size = { 138 FontSize.TINY: 5, 139 FontSize.SMALL: 7, 140 FontSize.NORMAL: 10, 141 FontSize.LARGE: 12, 142 FontSize.LARGE2: 14, 143 FontSize.LARGE3: 17, 144 FontSize.HUGE: 20, 145 FontSize.HUGE2: 25, 146 } 147 148 return d_font_size[latex_font_size] 149 150 151def letter_F_points(): 152 """Returns the points of the capital letter F. 153 154 Returns: 155 list: A list of points representing the letter F. 156 """ 157 return [ 158 (0.0, 0.0), 159 (20.0, 0.0), 160 (20.0, 40.0), 161 (40.0, 40.0), 162 (40.0, 60.0), 163 (20.0, 60.0), 164 (20.0, 80.0), 165 (50.0, 80.0), 166 (50.0, 100.0), 167 (0.0, 100.0), 168 (0.0, 0.0), 169 ] 170 171 172def letter_F(scale=1, **kwargs): 173 """Returns a Shape object representing the capital letter F. 174 175 Args: 176 scale (int, optional): Scale factor for the letter. Defaults to 1. 177 **kwargs: Additional keyword arguments for shape styling. 178 179 Returns: 180 Shape: A Shape object representing the letter F. 181 """ 182 F = Shape(letter_F_points(), closed=True) 183 if scale != 1: 184 F.scale(scale) 185 for k, v in kwargs.items(): 186 if k in shape_style_map: 187 setattr(F, k, v) 188 else: 189 raise AttributeError(f"{k}. Invalid attribute!") 190 return F 191 192 193def cube(size: float = 100): 194 """Returns a Batch object representing a cube. 195 196 Args: 197 size (float, optional): The size of the cube. Defaults to 100. 198 199 Returns: 200 Batch: A Batch object representing the cube. 201 """ 202 points = reg_poly_points_side_length((0, 0), 6, size) 203 center = (0, 0) 204 face1 = Shape([points[0], center] + points[4:], closed=True) 205 cube_ = face1.rotate(-2 * pi / 3, (0, 0), reps=2) 206 cube_[0].fill_color = Color(0.3, 0.3, 0.3) 207 cube_[1].fill_color = Color(0.4, 0.4, 0.4) 208 cube_[2].fill_color = Color(0.6, 0.6, 0.6) 209 210 return cube_ 211 212 213def pdf_to_svg(pdf_path, svg_path): 214 """Converts a single-page PDF file to SVG. 215 216 Args: 217 pdf_path (str): The path to the PDF file. 218 svg_path (str): The path to save the SVG file. 219 """ 220 doc = fitz.open(pdf_path) 221 page = doc.load_page(0) 222 svg = page.get_svg_image() 223 with open(svg_path, "w", encoding="utf-8") as f: 224 f.write(svg) 225 226 227# To do: use a different name for the Annotation class 228# annotation is a label with an arrow 229class Annotation(Batch): 230 """An Annotation object is a label with an arrow pointing to a specific location. 231 232 Args: 233 text (str): The annotation text. 234 pos (tuple): The position of the annotation. 235 frame (FrameShape): The frame shape of the annotation. 236 root_pos (tuple): The root position of the arrow. 237 arrow_line (ArrowLine, optional): The type of arrow line. Defaults to ArrowLine.STRAIGHT_END. 238 **kwargs: Additional keyword arguments for annotation styling. 239 """ 240 241 def __init__( 242 self, text, pos, frame, root_pos, arrow_line=ArrowLine.STRAIGHT_END, **kwargs 243 ): 244 self.text = text 245 self.pos = pos 246 self.frame = frame 247 self.root_pos = root_pos 248 self.arrow_line = arrow_line 249 self.kwargs = kwargs 250 251 super().__init__(subtype=Types.ANNOTATION, **kwargs) 252 253 254@dataclass 255class TagFrame: 256 """Frame objects are used with Tag objects to create boxes. 257 258 Args: 259 frame_shape (FrameShape, optional): The shape of the frame. Defaults to "rectangle". 260 line_width (float, optional): The width of the frame line. Defaults to 1. 261 line_dash_array (list, optional): The dash pattern for the frame line. Defaults to None. 262 line_join (LineJoin, optional): The line join style. Defaults to "miter". 263 line_color (Color, optional): The color of the frame line. Defaults to colors.black. 264 back_color (Color, optional): The background color of the frame. Defaults to colors.white. 265 fill (bool, optional): Whether to fill the frame. Defaults to False. 266 stroke (bool, optional): Whether to stroke the frame. Defaults to True. 267 double (bool, optional): Whether to use a double line. Defaults to False. 268 double_distance (float, optional): The distance between double lines. Defaults to 2. 269 inner_sep (float, optional): The inner separation. Defaults to 10. 270 outer_sep (float, optional): The outer separation. Defaults to 10. 271 smooth (bool, optional): Whether to smooth the frame. Defaults to False. 272 rounded_corners (bool, optional): Whether to use rounded corners. Defaults to False. 273 fillet_radius (float, optional): The radius of the fillet. Defaults to 10. 274 draw_fillets (bool, optional): Whether to draw fillets. Defaults to False. 275 blend_mode (str, optional): The blend mode. Defaults to None. 276 gradient (str, optional): The gradient. Defaults to None. 277 pattern (str, optional): The pattern. Defaults to None. 278 min_width (float, optional): The minimum width. Defaults to None. 279 min_height (float, optional): The minimum height. Defaults to None. 280 min_size (float, optional): The minimum size. Defaults to None. 281 """ 282 283 frame_shape: FrameShape = "rectangle" 284 line_width: float = 1 285 line_dash_array: list = None 286 line_join: LineJoin = "miter" 287 line_color: Color = colors.black 288 back_color: Color = colors.white 289 fill: bool = False 290 stroke: bool = True 291 double: bool = False 292 double_distance: float = 2 293 inner_sep: float = 10 294 outer_sep: float = 10 295 smooth: bool = False 296 rounded_corners: bool = False 297 fillet_radius: float = 10 298 draw_fillets: bool = False 299 blend_mode: str = None 300 gradient: str = None 301 pattern: str = None 302 min_width: float = None 303 min_height: float = None 304 min_size: float = None 305 306 def __post_init__(self): 307 self.type = Types.FRAME 308 self.subtype = Types.FRAME 309 common_properties(self, id_only=True) 310 311 312class Tag(Base): 313 """A Tag object is very similar to TikZ library's nodes. It is a text with a frame. 314 315 Args: 316 text (str): The text of the tag. 317 pos (Point): The position of the tag. 318 font_family (str, optional): The font family. Defaults to None. 319 font_size (int, optional): The font size. Defaults to None. 320 font_color (Color, optional): The font color. Defaults to None. 321 anchor (Anchor, optional): The anchor point. Defaults to Anchor.CENTER. 322 bold (bool, optional): Whether the text is bold. Defaults to False. 323 italic (bool, optional): Whether the text is italic. Defaults to False. 324 text_width (float, optional): The width of the text. Defaults to None. 325 placement (Placement, optional): The placement of the tag. Defaults to None. 326 minimum_size (float, optional): The minimum size of the tag. Defaults to None. 327 minimum_width (float, optional): The minimum width of the tag. Defaults to None. 328 minimum_height (float, optional): The minimum height of the tag. Defaults to None. 329 frame (TagFrame, optional): The frame of the tag. Defaults to None. 330 xform_matrix (array, optional): The transformation matrix. Defaults to None. 331 **kwargs: Additional keyword arguments for tag styling. 332 """ 333 334 def __init__( 335 self, 336 text: str, 337 pos: Point, 338 font_family: str = None, 339 font_size: int = None, 340 font_color: Color = None, 341 anchor: Anchor = Anchor.CENTER, 342 bold: bool = False, 343 italic: bool = False, 344 text_width: float = None, 345 placement: Placement = None, 346 minimum_size: float = None, 347 minimum_width: float = None, 348 minimum_height: float = None, 349 frame=None, 350 xform_matrix=None, 351 **kwargs, 352 ): 353 self.__dict__["style"] = TagStyle() 354 self.__dict__["_style_map"] = tag_style_map 355 self._set_aliases() 356 tag_attribs = list(tag_style_map.keys()) 357 tag_attribs.append("subtype") 358 _set_Nones( 359 self, 360 ["font_family", "font_size", "font_color"], 361 [font_family, font_size, font_color], 362 ) 363 validate_args(kwargs, tag_attribs) 364 x, y = pos[:2] 365 self._init_pos = array([x, y, 1.0]) 366 367 self.text = detokenize(text) 368 if frame is None: 369 self.frame = TagFrame() 370 self.type = Types.TAG 371 self.subtype = Types.TAG 372 self.style = TagStyle() 373 self.style.draw_frame = True 374 if font_family: 375 self.font_family = font_family 376 if font_size: 377 self.font_size = font_size 378 else: 379 self.font_size = defaults["font_size"] 380 if xform_matrix is None: 381 self.xform_matrix = identity_matrix() 382 else: 383 self.xform_matrix = get_transform(xform_matrix) 384 385 self.anchor = anchor 386 self.bold = bold 387 self.italic = italic 388 self.text_width = text_width 389 self.placement = placement 390 self.minimum_size = minimum_size 391 self.minimum_width = minimum_width 392 self.minimum_height = minimum_height 393 for k, v in kwargs.items(): 394 setattr(self, k, v) 395 396 x1, y1, x2, y2 = self.text_bounds() 397 w = x2 - x1 398 h = y2 - y1 399 self.points = Points([(0, 0, 1), (w, 0, 1), (w, h, 1), (0, h, 1)]) 400 common_properties(self) 401 402 def __setattr__(self, name, value): 403 obj, attrib = self.__dict__["_aliasses"].get(name, (None, None)) 404 if obj: 405 setattr(obj, attrib, value) 406 else: 407 self.__dict__[name] = value 408 409 def __getattr__(self, name): 410 obj, attrib = self.__dict__["_aliasses"].get(name, (None, None)) 411 if obj: 412 res = getattr(obj, attrib) 413 else: 414 try: 415 res = super().__getattr__(name) 416 except AttributeError: 417 res = self.__dict__[name] 418 419 return res 420 421 def _set_aliases(self): 422 _aliasses = {} 423 424 for alias, path_attrib in self._style_map.items(): 425 style_path, attrib = path_attrib 426 obj = self 427 for attrib_name in style_path.split("."): 428 obj = obj.__dict__[attrib_name] 429 430 if obj is not self: 431 _aliasses[alias] = (obj, attrib) 432 self.__dict__["_aliasses"] = _aliasses 433 434 def _update(self, xform_matrix, reps: int = 0): 435 if reps == 0: 436 self.xform_matrix = self.xform_matrix @ xform_matrix 437 res = self 438 else: 439 tags = [self] 440 tag = self 441 for _ in range(reps): 442 tag = tag.copy() 443 tag._update(xform_matrix) 444 tags.append(tag) 445 res = Batch(tags) 446 447 return res 448 449 @property 450 def pos(self) -> Point: 451 """Returns the position of the text. 452 453 Returns: 454 Point: The position of the text. 455 """ 456 return (self._init_pos @ self.xform_matrix)[:2].tolist() 457 458 def copy(self) -> "Tag": 459 """Returns a copy of the Tag object. 460 461 Returns: 462 Tag: A copy of the Tag object. 463 """ 464 tag = Tag(self.text, self.pos, xform_matrix=self.xform_matrix) 465 tag._init_pos = self._init_pos 466 tag.font_family = self.font_family 467 tag.font_size = self.font_size 468 tag.font_color = self.font_color 469 tag.anchor = self.anchor 470 tag.bold = self.bold 471 tag.italic = self.italic 472 tag.text_width = self.text_width 473 tag.placement = self.placement 474 tag.minimum_size = self.minimum_size 475 tag.minimum_width = self.minimum_width 476 477 return tag 478 479 def text_bounds(self) -> tuple[float, float, float, float]: 480 """Returns the bounds of the text. 481 482 Returns: 483 tuple: The bounds of the text (xmin, ymin, xmax, ymax). 484 """ 485 mult = 1 # font size multiplier 486 if self.font_size is None: 487 font_size = defaults["font_size"] 488 elif type(self.font_size) in [int, float]: 489 font_size = self.font_size 490 elif self.font_size in FontSize: 491 font_size = convert_latex_font_size(self.font_size) 492 else: 493 raise ValueError("Invalid font size.") 494 try: 495 font = ImageFont.truetype(f"{self.font_family}.ttf", font_size) 496 except OSError: 497 font = ImageFont.load_default() 498 mult = self.font_size / 10 499 xmin, ymin, xmax, ymax = font.getbbox(self.text) 500 width = xmax - xmin 501 height = ymax - ymin 502 dx = (width * mult) / 2 503 dy = (height * mult) / 2 504 xmin -= dx 505 xmax += dx 506 ymin -= dy 507 ymax += dy 508 509 return xmin, ymin, xmax, ymax 510 511 @property 512 def final_coords(self): 513 """Returns the final coordinates of the text. 514 515 Returns: 516 array: The final coordinates of the text. 517 """ 518 return self.points.homogen_coords @ self.xform_matrix 519 520 @property 521 def b_box(self): 522 """Returns the bounding box of the text. 523 524 Returns: 525 tuple: The bounding box of the text. 526 """ 527 xmin, ymin, xmax, ymax = self.text_bounds() 528 w2 = (xmax - xmin) / 2 529 h2 = (ymax - ymin) / 2 530 x, y = self.pos 531 inner_sep = self.frame.inner_sep 532 xmin = x - w2 - inner_sep 533 xmax = x + w2 + inner_sep 534 ymin = y - h2 - inner_sep 535 ymax = y + h2 + inner_sep 536 points = [ 537 (xmin, ymin), 538 (xmax, ymin), 539 (xmax, ymax), 540 (xmin, ymax), 541 ] 542 return bounding_box(points) 543 def __str__(self) -> str: 544 return f"Tag({self.text})" 545 546 def __repr__(self) -> str: 547 return f"Tag({self.text})" 548 549 550class ArrowHead(Shape): 551 """An ArrowHead object is a shape that represents the head of an arrow. 552 553 Args: 554 length (float, optional): The length of the arrow head. Defaults to None. 555 width_ (float, optional): The width of the arrow head. Defaults to None. 556 points (list, optional): The points defining the arrow head. Defaults to None. 557 **kwargs: Additional keyword arguments for arrow head styling. 558 """ 559 560 def __init__( 561 self, length: float = None, width_: float = None, points: list = None, **kwargs 562 ): 563 length, width_ = get_defaults( 564 ["arrow_head_length", "arrow_head_width"], [length, width_] 565 ) 566 if points is None: 567 w2 = width_ / 2 568 points = [(0, 0), (0, -w2), (length, 0), (0, w2)] 569 super().__init__(points, closed=True, subtype=Types.ARROW_HEAD, **kwargs) 570 self.head_length = length 571 self.head_width = width_ 572 573 self.kwargs = kwargs 574 575 576def draw_cs_tiny(canvas, pos=(0, 0), width=25, height=25, neg_width=5, neg_height=5): 577 """Draws a tiny coordinate system. 578 579 Args: 580 canvas: The canvas to draw on. 581 pos (tuple, optional): The position of the coordinate system. Defaults to (0, 0). 582 width (int, optional): The length of the x-axis. Defaults to 25. 583 height (int, optional): The length of the y-axis. Defaults to 25. 584 neg_width (int, optional): The negative length of the x-axis. Defaults to 5. 585 neg_height (int, optional): The negative length of the y-axis. Defaults to 5. 586 """ 587 x, y = pos[:2] 588 canvas.circle((x, y), 2, fill=False, line_color=colors.gray) 589 canvas.draw(Shape([(x - neg_width, y), (x + width, y)]), line_color=colors.gray) 590 canvas.draw(Shape([(x, y - neg_height), (x, y + height)]), line_color=colors.gray) 591 592 593def draw_cs_small(canvas, pos=(0, 0), width=80, height=100, neg_width=5, neg_height=5): 594 """Draws a small coordinate system. 595 596 Args: 597 canvas: The canvas to draw on. 598 pos (tuple, optional): The position of the coordinate system. Defaults to (0, 0). 599 width (int, optional): The length of the x-axis. Defaults to 80. 600 height (int, optional): The length of the y-axis. Defaults to 100. 601 neg_width (int, optional): The negative length of the x-axis. Defaults to 5. 602 neg_height (int, optional): The negative length of the y-axis. Defaults to 5. 603 """ 604 x, y = pos[:2] 605 x_axis = arrow( 606 (-neg_width + x, y), (width + 10 + x, y), head_length=8, head_width=2 607 ) 608 y_axis = arrow( 609 (x, -neg_height + y), (x, height + 10 + y), head_length=8, head_width=2 610 ) 611 canvas.draw(x_axis, line_width=1) 612 canvas.draw(y_axis, line_width=1) 613 614 615def arrow( 616 p1, 617 p2, 618 head_length=10, 619 head_width=4, 620 line_width=1, 621 line_color=colors.black, 622 fill_color=colors.black, 623 centered=False, 624): 625 """Return an arrow from p1 to p2. 626 627 Args: 628 p1 (tuple): The starting point of the arrow. 629 p2 (tuple): The ending point of the arrow. 630 head_length (int, optional): The length of the arrow head. Defaults to 10. 631 head_width (int, optional): The width of the arrow head. Defaults to 4. 632 line_width (int, optional): The width of the arrow line. Defaults to 1. 633 line_color (Color, optional): The color of the arrow line. Defaults to colors.black. 634 fill_color (Color, optional): The fill color of the arrow head. Defaults to colors.black. 635 centered (bool, optional): Whether the arrow is centered. Defaults to False. 636 637 Returns: 638 Batch: A Batch object containing the arrow shapes. 639 """ 640 x1, y1 = p1[:2] 641 x2, y2 = p2[:2] 642 dx = x2 - x1 643 dy = y2 - y1 644 angle = atan2(dy, dx) 645 body = Shape( 646 [(x1, y1), (x2, y2)], 647 closed=False, 648 line_color=line_color, 649 fill_color=fill_color, 650 line_width=line_width, 651 ) 652 w2 = head_width / 2 653 head = Shape( 654 [(-head_length, w2), (0, 0), (-head_length, -w2)], 655 closed=True, 656 line_color=line_color, 657 fill_color=fill_color, 658 line_width=line_width, 659 ) 660 head.rotate(angle) 661 if centered: 662 head.translate(*mid_point((x1, y1), (x2, y2))) 663 else: 664 head.translate(x2, y2) 665 return Batch([body, head]) 666 667 668class ArcArrow(Batch): 669 """An ArcArrow object is an arrow with an arc. 670 671 Args: 672 center (Point): The center of the arc. 673 radius (float): The radius of the arc. 674 start_angle (float): The starting angle of the arc. 675 end_angle (float): The ending angle of the arc. 676 xform_matrix (array, optional): The transformation matrix. Defaults to None. 677 **kwargs: Additional keyword arguments for arc arrow styling. 678 """ 679 680 def __init__( 681 self, 682 center: Point, 683 radius: float, 684 start_angle: float, 685 end_angle: float, 686 xform_matrix: array = None, 687 **kwargs, 688 ): 689 self.center = center 690 self.radius = radius 691 self.start_angle = start_angle 692 self.end_angle = end_angle 693 # create the arc 694 self.arc = Arc(center, radius, start_angle, end_angle) 695 self.arc.fill = False 696 # create arrow_head1 697 self.arrow_head1 = ArrowHead() 698 # create arrow_head2 699 self.arrow_head2 = ArrowHead() 700 start = self.arc.start_point 701 end = self.arc.end_point 702 self.points = [center, start, end] 703 704 self.arrow_head1.translate(-1 * self.arrow_head1.head_length, 0) 705 self.arrow_head1.rotate(start_angle - pi / 2) 706 self.arrow_head1.translate(*start) 707 self.arrow_head2.translate(-1 * self.arrow_head2.head_length, 0) 708 self.arrow_head2.rotate(end_angle + pi / 2) 709 self.arrow_head2.translate(*end) 710 items = [self.arc, self.arrow_head1, self.arrow_head2] 711 super().__init__(items, subtype=Types.ARC_ARROW, **kwargs) 712 for k, v in kwargs.items(): 713 if k in shape_style_map: 714 setattr(self, k, v) # we should check for valid values here 715 else: 716 raise AttributeError(f"{k}. Invalid attribute!") 717 self.xform_matrix = get_transform(xform_matrix) 718 719 720class Arrow(Batch): 721 """An Arrow object is a line with an arrow head. 722 723 Args: 724 p1 (Point): The starting point of the arrow. 725 p2 (Point): The ending point of the arrow. 726 head_pos (HeadPos, optional): The position of the arrow head. Defaults to HeadPos.END. 727 head (Shape, optional): The shape of the arrow head. Defaults to None. 728 **kwargs: Additional keyword arguments for arrow styling. 729 """ 730 731 def __init__( 732 self, 733 p1: Point, 734 p2: Point, 735 head_pos: HeadPos = HeadPos.END, 736 head: Shape = None, 737 **kwargs, 738 ): 739 self.p1 = p1 740 self.p2 = p2 741 self.head_pos = head_pos 742 self.head = head 743 self.kwargs = kwargs 744 length = distance(p1, p2) 745 angle = line_angle(p1, p2) 746 self.line = Shape([(0, 0), (length, 0)]) 747 if head is None: 748 self.head = ArrowHead() 749 else: 750 self.head = head 751 if self.head_pos == HeadPos.END: 752 x = length 753 self.head.translate(x - self.head.head_length, 0) 754 self.head.rotate(angle) 755 self.line.rotate(angle) 756 self.line.translate(*p1) 757 self.head.translate(*p1) 758 self.heads = [self.head] 759 elif self.head_pos == HeadPos.START: 760 self.head = [None] 761 elif self.head_pos == HeadPos.BOTH: 762 self.head2 = ArrowHead() 763 self.head2.rotate(pi) 764 self.head2.translate(self.head2.head_length, 0) 765 self.head2.rotate(angle) 766 self.head2.translate(*p1) 767 x = length 768 self.head.translate(x - self.head.head_length, 0) 769 self.head.rotate(angle) 770 self.line.rotate(angle) 771 self.line.translate(*p1) 772 self.head.translate(*p1) 773 self.heads = [self.head, self.head2] 774 elif self.head_pos == HeadPos.NONE: 775 self.heads = [None] 776 777 items = [self.line] + self.heads 778 super().__init__(items, subtype=Types.ARROW, **kwargs) 779 780 781class AngularDimension(Batch): 782 """An AngularDimension object is a dimension that represents an angle. 783 784 Args: 785 center (Point): The center of the angle. 786 radius (float): The radius of the angle. 787 start_angle (float): The starting angle. 788 end_angle (float): The ending angle. 789 ext_angle (float): The extension angle. 790 gap_angle (float): The gap angle. 791 text_offset (float, optional): The text offset. Defaults to None. 792 gap (float, optional): The gap. Defaults to None. 793 **kwargs: Additional keyword arguments for angular dimension styling. 794 """ 795 796 def __init__( 797 self, 798 center: Point, 799 radius: float, 800 start_angle: float, 801 end_angle: float, 802 ext_angle: float, 803 gap_angle: float, 804 text_offset: float = None, 805 gap: float = None, 806 **kwargs, 807 ): 808 text_offset, gap = get_defaults(["text_offset", "gap"], [text_offset, gap]) 809 self.center = center 810 self.radius = radius 811 self.start_angle = start_angle 812 self.end_angle = end_angle 813 self.ext_angle = ext_angle 814 self.gap_angle = gap_angle 815 self.text_offset = text_offset 816 self.gap = gap 817 super().__init__(subtype=Types.ANGULAR_DIMENSION, **kwargs) 818 819 820class Dimension(Batch): 821 """A Dimension object is a line with arrows and a text. 822 823 Args: 824 text (str): The text of the dimension. 825 p1 (Point): The starting point of the dimension. 826 p2 (Point): The ending point of the dimension. 827 ext_length (float): The length of the extension lines. 828 ext_length2 (float, optional): The length of the second extension line. Defaults to None. 829 orientation (Anchor, optional): The orientation of the dimension. Defaults to None. 830 text_pos (Anchor, optional): The position of the text. Defaults to Anchor.CENTER. 831 text_offset (float, optional): The offset of the text. Defaults to 0. 832 gap (float, optional): The gap. Defaults to None. 833 reverse_arrows (bool, optional): Whether to reverse the arrows. Defaults to False. 834 reverse_arrow_length (float, optional): The length of the reversed arrows. Defaults to None. 835 parallel (bool, optional): Whether the dimension is parallel. Defaults to False. 836 ext1pnt (Point, optional): The first extension point. Defaults to None. 837 ext2pnt (Point, optional): The second extension point. Defaults to None. 838 scale (float, optional): The scale factor. Defaults to 1. 839 font_size (int, optional): The font size. Defaults to 12. 840 **kwargs: Additional keyword arguments for dimension styling. 841 """ 842 843 # To do: This is too long and convoluted. Refactor it. 844 def __init__( 845 self, 846 text: str, 847 p1: Point, 848 p2: Point, 849 ext_length: float, 850 ext_length2: float = None, 851 orientation: Anchor = None, 852 text_pos: Anchor = Anchor.CENTER, 853 text_offset: float = 0, 854 gap: float = None, 855 reverse_arrows: bool = False, 856 reverse_arrow_length: float = None, 857 parallel: bool = False, 858 ext1pnt: Point = None, 859 ext2pnt: Point = None, 860 scale: float = 1, 861 font_size: int = 12, 862 **kwargs, 863 ): 864 ext_length2, gap, reverse_arrow_length = get_defaults( 865 ["ext_length2", "gap", "rev_arrow_length"], 866 [ext_length2, gap, reverse_arrow_length], 867 ) 868 if text == "": 869 self.text = str(distance(p1, p2) / scale) 870 else: 871 self.text = text 872 self.p1 = p1 873 self.p2 = p2 874 self.ext_length = ext_length 875 self.ext_length2 = ext_length2 876 self.orientation = orientation 877 self.text_pos = text_pos 878 self.text_offset = text_offset 879 self.gap = gap 880 self.reverse_arrows = reverse_arrows 881 self.reverse_arrow_length = reverse_arrow_length 882 self.kwargs = kwargs 883 self.ext1 = None 884 self.ext2 = None 885 self.ext3 = None 886 self.arrow1 = None 887 self.arrow2 = None 888 self.dim_line = None 889 self.mid_line = None 890 self.ext1pnt = ext1pnt 891 self.ext2pnt = ext2pnt 892 x1, y1 = p1[:2] 893 x2, y2 = p2[:2] 894 895 # px1_1 : extension1 point 1 896 # px1_2 : extension1 point 2 897 # px2_1 : extension2 point 1 898 # px2_2 : extension2 point 2 899 # px3_1 : extension3 point 1 900 # px3_2 : extension3 point 2 901 # pa1 : arrow point 1 902 # pa2 : arrow point 2 903 # ptext : text point 904 super().__init__(subtype=Types.DIMENSION, **kwargs) 905 dist_tol = defaults["dist_tol"] 906 if font_size is not None: 907 self.font_size = font_size 908 if parallel: 909 if orientation is None: 910 orientation = Anchor.NORTHEAST 911 912 if orientation == Anchor.NORTHEAST: 913 angle = line_angle(p1, p2) + pi / 2 914 elif orientation == Anchor.NORTHWEST: 915 angle = line_angle(p1, p2) + pi / 2 916 elif orientation == Anchor.SOUTHEAST: 917 angle = line_angle(p1, p2) - pi / 2 918 elif orientation == Anchor.SOUTHWEST: 919 angle = line_angle(p1, p2) + pi / 2 920 if self.ext1pnt is None: 921 px1_1 = line_by_point_angle_length(p1, angle, self.gap)[1] 922 else: 923 px1_1 = self.ext1pnt 924 px1_2 = line_by_point_angle_length(p1, angle, self.gap + self.ext_length)[1] 925 if self.ext2pnt is None: 926 px2_1 = line_by_point_angle_length(p2, angle, self.gap)[1] 927 else: 928 px2_1 = self.ext2pnt 929 px2_2 = line_by_point_angle_length(p2, angle, self.gap + self.ext_length)[1] 930 931 pa1 = line_by_point_angle_length(px1_2, angle, self.gap * -1.5)[1] 932 pa2 = line_by_point_angle_length(px2_2, angle, self.gap * -1.5)[1] 933 934 self.text_pos = mid_point(pa1, pa2) 935 self.dim_line = Arrow(pa1, pa2, head_pos=HeadPos.BOTH) 936 self.ext1 = Shape([px1_1, px1_2]) 937 self.ext2 = Shape([px2_1, px2_2]) 938 self.append(self.dim_line) 939 self.append(self.ext1) 940 self.append(self.ext2) 941 942 else: 943 if abs(x1 - x2) < dist_tol: 944 # vertical line 945 if self.orientation is None: 946 orientation = Anchor.EAST 947 948 if orientation in [Anchor.WEST, Anchor.SOUTHWEST, Anchor.NORTHWEST]: 949 x = x1 - self.gap 950 px1_1 = (x, y1) 951 px1_2 = (x - ext_length, y1) 952 px2_1 = (x, y2) 953 px2_2 = (x - ext_length, y2) 954 x = px1_2[0] + self.gap * 1.5 955 pa1 = (x, y1) 956 pa2 = (x, y2) 957 elif orientation in [Anchor.EAST, Anchor.SOUTHEAST, Anchor.NORTHEAST]: 958 x = x1 + self.gap 959 px1_1 = (x, y1) 960 px1_2 = (x + ext_length, y1) 961 px2_1 = (x, y2) 962 px2_2 = (x + ext_length, y2) 963 x = px1_2[0] - self.gap * 1.5 964 pa1 = (x, y1) 965 pa2 = (x, y2) 966 elif orientation == Anchor.CENTER: 967 pa1 = (x1, y1) 968 pa2 = (x1, y2) 969 x = pa1[0] 970 if orientation in [Anchor.SOUTHWEST, Anchor.SOUTHEAST]: 971 px3_1 = pa2 972 y = y2 - self.ext_length2 973 px3_2 = (x, y) 974 self.ext3 = Shape([px3_1, px3_2]) 975 self.text_pos = (x, y - self.text_offset) 976 elif orientation in [Anchor.NORTHWEST, Anchor.NORTHEAST]: 977 px3_1 = pa1 978 y = y1 + self.ext_length2 979 px3_2 = (x, y) 980 self.ext3 = Shape([px3_1, px3_2]) 981 self.text_pos = (x, y + self.text_offset) 982 elif orientation == Anchor.SOUTH: 983 px3_1 = pa2 984 y = y2 - self.ext_length2 985 px3_2 = (x, y) 986 self.ext3 = Shape([px3_1, px3_2]) 987 self.text_pos = (x, y - self.text_offset) 988 elif orientation == Anchor.NORTH: 989 px3_2 = pa1 990 y = y2 + self.ext_length2 991 px3_1 = (x, y) 992 self.ext3 = Shape([px3_1, px3_2]) 993 self.text_pos = (x, y + self.text_offset) 994 else: 995 self.text_pos = (x, y1 - (y1 - y2) / 2) 996 if orientation not in [Anchor.CENTER, Anchor.NORTH, Anchor.SOUTH]: 997 if self.ext1pnt is None: 998 self.ext1 = Shape([px1_1, px1_2]) 999 else: 1000 self.ext1 = Shape([ext1pnt, px1_2]) 1001 if self.ext2pnt is None: 1002 self.ext2 = Shape([px2_1, px2_2]) 1003 else: 1004 self.ext2 = Shape([ext2pnt, px2_2]) 1005 elif abs(y1 - y2) < dist_tol: 1006 # horizontal line 1007 if self.orientation is None: 1008 orientation = Anchor.SOUTH 1009 1010 if orientation in [Anchor.SOUTH, Anchor.SOUTHWEST, Anchor.SOUTHEAST]: 1011 y = y1 - self.gap 1012 px1_1 = (x1, y) 1013 px1_2 = (x1, y - ext_length) 1014 px2_1 = (x2, y) 1015 px2_2 = (x2, y - ext_length) 1016 y = px1_2[1] + self.gap * 1.5 1017 pa1 = (x1, y) 1018 pa2 = (x2, y) 1019 elif orientation in [Anchor.NORTH, Anchor.NORTHWEST, Anchor.NORTHEAST]: 1020 y = y1 + self.gap 1021 px1_1 = (x1, y) 1022 px1_2 = (x1, y + ext_length) 1023 px2_1 = (x2, y) 1024 px2_2 = (x2, y + ext_length) 1025 y = px1_2[1] - self.gap * 1.5 1026 pa1 = (x1, y) 1027 pa2 = (x2, y) 1028 elif orientation in [Anchor.WEST, Anchor.EAST]: 1029 pa1 = (x1, y1) 1030 pa2 = (x2, y2) 1031 if orientation == Anchor.WEST: 1032 px3_1 = (pa1[0] - self.ext_length2, pa1[1]) 1033 px3_2 = pa1 1034 self.text_pos = (px3_1[0] - self.text_offset, pa1[1]) 1035 else: 1036 px3_1 = pa2 1037 px3_2 = (pa2[0] + self.ext_length2, pa1[1]) 1038 self.text_pos = (px3_1[0] + self.text_offset, pa1[1]) 1039 self.ext3 = Shape([px3_1, px3_2]) 1040 elif orientation == Anchor.CENTER: 1041 pa1 = (x1, y1) 1042 pa2 = (x2, y2) 1043 1044 y = pa1[1] 1045 if orientation in [Anchor.SOUTHWEST, Anchor.NORTHWEST]: 1046 px3_1 = pa1 1047 x = x1 - self.ext_length2 1048 px3_2 = (x, y) 1049 self.ext3 = Shape([px3_1, px3_2]) 1050 self.text_pos = (x - self.text_offset, y) 1051 elif orientation in [Anchor.NORTHEAST, Anchor.SOUTHEAST]: 1052 px3_1 = pa2 1053 x = x2 + self.ext_length2 1054 px3_2 = (x, y) 1055 self.ext3 = Shape([px3_1, px3_2]) 1056 self.text_pos = (x + self.text_offset, y) 1057 elif orientation in [Anchor.CENTER, Anchor.NORTH, Anchor.SOUTH]: 1058 self.text_pos = (x1 + (x2 - x1) / 2, y) 1059 1060 if orientation not in [Anchor.CENTER, Anchor.WEST, Anchor.EAST]: 1061 if self.ext1pnt is None: 1062 self.ext1 = Shape([px1_1, px1_2]) 1063 else: 1064 self.ext1Shape([ext1pnt, px1_2]) 1065 if self.ext2pnt is None: 1066 self.ext2 = Shape([px2_1, px2_2]) 1067 else: 1068 self.ext2 = Shape([ext2pnt, px2_2]) 1069 1070 if self.reverse_arrows: 1071 dist = self.reverse_arrow_length 1072 p2 = extended_line(dist, [pa1, pa2])[1] 1073 self.arrow1 = Arrow(p2, pa2) 1074 p2 = extended_line(dist, [pa2, pa1])[1] 1075 self.arrow2 = Arrow(p2, pa1) 1076 self.append(self.arrow1) 1077 self.append(self.arrow2) 1078 self.mid_line = Shape([pa1, pa2]) 1079 self.append(self.mid_line) 1080 dist = self.text_offset + self.reverse_arrow_length 1081 if orientation in [Anchor.EAST, Anchor.NORTHEAST, Anchor.NORTH]: 1082 1083 self.text_pos = extended_line(dist, [pa1, pa2])[1] 1084 else: 1085 self.text_pos = extended_line(dist, [pa2, pa1])[1] 1086 else: 1087 self.dim_line = Arrow(pa1, pa2, head_pos=HeadPos.BOTH) 1088 self.append(self.dim_line) 1089 if self.ext1 is not None: 1090 self.append(self.ext1) 1091 1092 if self.ext2 is not None: 1093 self.append(self.ext2) 1094 1095 if self.ext3 is not None: 1096 self.append(self.ext3)
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
array(object, dtype=None, *, copy=True, order='K', subok=False, ndmin=0, like=None)
Create an array.
Parameters
object : array_like
An array, any object exposing the array interface, an object whose
__array__
method returns an array, or any (nested) sequence.
If object is a scalar, a 0-dimensional array containing object is
returned.
dtype : data-type, optional
The desired data-type for the array. If not given, NumPy will try to use
a default dtype
that can represent the values (by applying promotion
rules when necessary.)
copy : bool, optional
If True
(default), then the array data is copied. If None
,
a copy will only be made if __array__
returns a copy, if obj is
a nested sequence, or if a copy is needed to satisfy any of the other
requirements (dtype
, order
, etc.). Note that any copy of
the data is shallow, i.e., for arrays with object dtype, the new
array will point to the same objects. See Examples for ndarray.copy
.
For False
it raises a ValueError
if a copy cannot be avoided.
Default: True
.
order : {'K', 'A', 'C', 'F'}, optional
Specify the memory layout of the array. If object is not an array, the
newly created array will be in C order (row major) unless 'F' is
specified, in which case it will be in Fortran order (column major).
If object is an array the following holds.
===== ========= ===================================================
order no copy copy=True
===== ========= ===================================================
'K' unchanged F & C order preserved, otherwise most similar order
'A' unchanged F order if input is F and not C, otherwise C order
'C' C order C order
'F' F order F order
===== ========= ===================================================
When ``copy=None`` and a copy is made for other reasons, the result is
the same as if ``copy=True``, with some exceptions for 'A', see the
Notes section. The default order is 'K'.
subok : bool, optional
If True, then sub-classes will be passed-through, otherwise
the returned array will be forced to be a base-class array (default).
ndmin : int, optional
Specifies the minimum number of dimensions that the resulting
array should have. Ones will be prepended to the shape as
needed to meet this requirement.
like : array_like, optional
Reference object to allow the creation of arrays which are not
NumPy arrays. If an array-like passed in as like
supports
the __array_function__
protocol, the result will be defined
by it. In this case, it ensures the creation of an array object
compatible with that passed in via this argument.
*New in version 1.20.0.*
Returns
out : ndarray An array object satisfying the specified requirements.
See Also
empty_like : Return an empty array with shape and type of input. ones_like : Return an array of ones with shape and type of input. zeros_like : Return an array of zeros with shape and type of input. full_like : Return a new array with shape of input filled with value. empty : Return a new uninitialized array. ones : Return a new array setting values to one. zeros : Return a new array setting values to zero. full : Return a new array of given shape filled with value. copy: Return an array copy of the given object.
Notes
When order is 'A' and object
is an array in neither 'C' nor 'F' order,
and a copy is forced by a change in dtype, then the order of the result is
not necessarily 'C' as expected. This is likely a bug.
Examples
>>> np.array([1, 2, 3])
array([1, 2, 3])
Upcasting:
>>> np.array([1, 2, 3.0])
array([ 1., 2., 3.])
More than one dimension:
>>> np.array([[1, 2], [3, 4]])
array([[1, 2],
[3, 4]])
Minimum dimensions 2:
>>> np.array([1, 2, 3], ndmin=2)
array([[1, 2, 3]])
Type provided:
>>> np.array([1, 2, 3], dtype=complex)
array([ 1.+0.j, 2.+0.j, 3.+0.j])
Data-type consisting of more than one element:
>>> x = np.array([(1,2),(3,4)],dtype=[('a','<i4'),('b','<i4')])
>>> x['a']
array([1, 3])
Creating an array from sub-classes:
>>> np.array(np.asmatrix('1 2; 3 4'))
array([[1, 2],
[3, 4]])
>>> np.array(np.asmatrix('1 2; 3 4'), subok=True)
matrix([[1, 2],
[3, 4]])
52def logo(scale=1): 53 """Returns the Simetri logo. 54 55 Args: 56 scale (int, optional): Scale factor for the logo. Defaults to 1. 57 58 Returns: 59 Batch: A Batch object containing the logo shapes. 60 """ 61 w = 10 * scale 62 points = [ 63 (0, 0), 64 (-4, 0), 65 (-4, 6), 66 (1, 6), 67 (1, 2), 68 (-2, 2), 69 (-2, 4), 70 (-1, 4), 71 (-1, 3), 72 (0, 3), 73 (0, 5), 74 (-3, 5), 75 (-3, 1), 76 (5, 1), 77 (5, -10), 78 (0, -10), 79 (0, -6), 80 (3, -6), 81 (3, -8), 82 (2, -8), 83 (2, -7), 84 (1, -7), 85 (1, -9), 86 (4, -9), 87 (4, -5), 88 (-4, -5), 89 (-4, -1), 90 (-1, -1), 91 (-1, -3), 92 (-2, -3), 93 (-2, -2), 94 (-3, -2), 95 (-3, -4), 96 (0, -4), 97 ] 98 99 points2 = [ 100 (1, 0), 101 (1, -4), 102 (4, -4), 103 (4, -3), 104 (2, -3), 105 (2, -1), 106 (3, -1), 107 (3, -2), 108 (4, -2), 109 (4, 0), 110 ] 111 112 points = [(x * w, y * w) for x, y in points] 113 points2 = [(x * w, y * w) for x, y in points2] 114 kernel1 = Shape(points, closed=True) 115 kernel2 = Shape(points2, closed=True) 116 rad = 1 117 line_width = 2 118 kernel1.fillet_radius = rad 119 kernel2.fillet_radius = rad 120 kernel1.line_width = line_width 121 kernel2.line_width = line_width 122 fill_color = Color(*swatches_255[62][8]) 123 kernel1.fill_color = fill_color 124 kernel2.fill_color = colors.white 125 126 return Batch([kernel1, kernel2])
Returns the Simetri logo.
Arguments:
- scale (int, optional): Scale factor for the logo. Defaults to 1.
Returns:
Batch: A Batch object containing the logo shapes.
129def convert_latex_font_size(latex_font_size: FontSize): 130 """Converts LaTeX font size to a numerical value. 131 132 Args: 133 latex_font_size (FontSize): The LaTeX font size. 134 135 Returns: 136 int: The corresponding numerical font size. 137 """ 138 d_font_size = { 139 FontSize.TINY: 5, 140 FontSize.SMALL: 7, 141 FontSize.NORMAL: 10, 142 FontSize.LARGE: 12, 143 FontSize.LARGE2: 14, 144 FontSize.LARGE3: 17, 145 FontSize.HUGE: 20, 146 FontSize.HUGE2: 25, 147 } 148 149 return d_font_size[latex_font_size]
Converts LaTeX font size to a numerical value.
Arguments:
- latex_font_size (FontSize): The LaTeX font size.
Returns:
int: The corresponding numerical font size.
152def letter_F_points(): 153 """Returns the points of the capital letter F. 154 155 Returns: 156 list: A list of points representing the letter F. 157 """ 158 return [ 159 (0.0, 0.0), 160 (20.0, 0.0), 161 (20.0, 40.0), 162 (40.0, 40.0), 163 (40.0, 60.0), 164 (20.0, 60.0), 165 (20.0, 80.0), 166 (50.0, 80.0), 167 (50.0, 100.0), 168 (0.0, 100.0), 169 (0.0, 0.0), 170 ]
Returns the points of the capital letter F.
Returns:
list: A list of points representing the letter F.
173def letter_F(scale=1, **kwargs): 174 """Returns a Shape object representing the capital letter F. 175 176 Args: 177 scale (int, optional): Scale factor for the letter. Defaults to 1. 178 **kwargs: Additional keyword arguments for shape styling. 179 180 Returns: 181 Shape: A Shape object representing the letter F. 182 """ 183 F = Shape(letter_F_points(), closed=True) 184 if scale != 1: 185 F.scale(scale) 186 for k, v in kwargs.items(): 187 if k in shape_style_map: 188 setattr(F, k, v) 189 else: 190 raise AttributeError(f"{k}. Invalid attribute!") 191 return F
Returns a Shape object representing the capital letter F.
Arguments:
- scale (int, optional): Scale factor for the letter. Defaults to 1.
- **kwargs: Additional keyword arguments for shape styling.
Returns:
Shape: A Shape object representing the letter F.
194def cube(size: float = 100): 195 """Returns a Batch object representing a cube. 196 197 Args: 198 size (float, optional): The size of the cube. Defaults to 100. 199 200 Returns: 201 Batch: A Batch object representing the cube. 202 """ 203 points = reg_poly_points_side_length((0, 0), 6, size) 204 center = (0, 0) 205 face1 = Shape([points[0], center] + points[4:], closed=True) 206 cube_ = face1.rotate(-2 * pi / 3, (0, 0), reps=2) 207 cube_[0].fill_color = Color(0.3, 0.3, 0.3) 208 cube_[1].fill_color = Color(0.4, 0.4, 0.4) 209 cube_[2].fill_color = Color(0.6, 0.6, 0.6) 210 211 return cube_
Returns a Batch object representing a cube.
Arguments:
- size (float, optional): The size of the cube. Defaults to 100.
Returns:
Batch: A Batch object representing the cube.
214def pdf_to_svg(pdf_path, svg_path): 215 """Converts a single-page PDF file to SVG. 216 217 Args: 218 pdf_path (str): The path to the PDF file. 219 svg_path (str): The path to save the SVG file. 220 """ 221 doc = fitz.open(pdf_path) 222 page = doc.load_page(0) 223 svg = page.get_svg_image() 224 with open(svg_path, "w", encoding="utf-8") as f: 225 f.write(svg)
Converts a single-page PDF file to SVG.
Arguments:
- pdf_path (str): The path to the PDF file.
- svg_path (str): The path to save the SVG file.
230class Annotation(Batch): 231 """An Annotation object is a label with an arrow pointing to a specific location. 232 233 Args: 234 text (str): The annotation text. 235 pos (tuple): The position of the annotation. 236 frame (FrameShape): The frame shape of the annotation. 237 root_pos (tuple): The root position of the arrow. 238 arrow_line (ArrowLine, optional): The type of arrow line. Defaults to ArrowLine.STRAIGHT_END. 239 **kwargs: Additional keyword arguments for annotation styling. 240 """ 241 242 def __init__( 243 self, text, pos, frame, root_pos, arrow_line=ArrowLine.STRAIGHT_END, **kwargs 244 ): 245 self.text = text 246 self.pos = pos 247 self.frame = frame 248 self.root_pos = root_pos 249 self.arrow_line = arrow_line 250 self.kwargs = kwargs 251 252 super().__init__(subtype=Types.ANNOTATION, **kwargs)
An Annotation object is a label with an arrow pointing to a specific location.
Arguments:
- text (str): The annotation text.
- pos (tuple): The position of the annotation.
- frame (FrameShape): The frame shape of the annotation.
- root_pos (tuple): The root position of the arrow.
- arrow_line (ArrowLine, optional): The type of arrow line. Defaults to ArrowLine.STRAIGHT_END.
- **kwargs: Additional keyword arguments for annotation styling.
242 def __init__( 243 self, text, pos, frame, root_pos, arrow_line=ArrowLine.STRAIGHT_END, **kwargs 244 ): 245 self.text = text 246 self.pos = pos 247 self.frame = frame 248 self.root_pos = root_pos 249 self.arrow_line = arrow_line 250 self.kwargs = kwargs 251 252 super().__init__(subtype=Types.ANNOTATION, **kwargs)
Initialize a Batch object.
Arguments:
- elements (Sequence[Any], optional): The elements to include in the batch.
- modifiers (Sequence[Modifier], optional): The modifiers to apply to the batch.
- subtype (Types, optional): The subtype of the batch.
- kwargs (dict): Additional keyword arguments.
Inherited Members
- simetri.graphics.batch.Batch
- type
- subtype
- modifiers
- blend_mode
- alpha
- line_alpha
- fill_alpha
- text_alpha
- clip
- mask
- even_odd_rule
- blend_group
- transparency_group
- set_attribs
- set_batch_attr
- proximity
- append
- reverse
- insert
- remove
- pop
- clear
- extend
- iter_elements
- all_elements
- all_shapes
- all_vertices
- all_segments
- as_graph
- graph_summary
- merge_shapes
- all_polygons
- copy
- b_box
255@dataclass 256class TagFrame: 257 """Frame objects are used with Tag objects to create boxes. 258 259 Args: 260 frame_shape (FrameShape, optional): The shape of the frame. Defaults to "rectangle". 261 line_width (float, optional): The width of the frame line. Defaults to 1. 262 line_dash_array (list, optional): The dash pattern for the frame line. Defaults to None. 263 line_join (LineJoin, optional): The line join style. Defaults to "miter". 264 line_color (Color, optional): The color of the frame line. Defaults to colors.black. 265 back_color (Color, optional): The background color of the frame. Defaults to colors.white. 266 fill (bool, optional): Whether to fill the frame. Defaults to False. 267 stroke (bool, optional): Whether to stroke the frame. Defaults to True. 268 double (bool, optional): Whether to use a double line. Defaults to False. 269 double_distance (float, optional): The distance between double lines. Defaults to 2. 270 inner_sep (float, optional): The inner separation. Defaults to 10. 271 outer_sep (float, optional): The outer separation. Defaults to 10. 272 smooth (bool, optional): Whether to smooth the frame. Defaults to False. 273 rounded_corners (bool, optional): Whether to use rounded corners. Defaults to False. 274 fillet_radius (float, optional): The radius of the fillet. Defaults to 10. 275 draw_fillets (bool, optional): Whether to draw fillets. Defaults to False. 276 blend_mode (str, optional): The blend mode. Defaults to None. 277 gradient (str, optional): The gradient. Defaults to None. 278 pattern (str, optional): The pattern. Defaults to None. 279 min_width (float, optional): The minimum width. Defaults to None. 280 min_height (float, optional): The minimum height. Defaults to None. 281 min_size (float, optional): The minimum size. Defaults to None. 282 """ 283 284 frame_shape: FrameShape = "rectangle" 285 line_width: float = 1 286 line_dash_array: list = None 287 line_join: LineJoin = "miter" 288 line_color: Color = colors.black 289 back_color: Color = colors.white 290 fill: bool = False 291 stroke: bool = True 292 double: bool = False 293 double_distance: float = 2 294 inner_sep: float = 10 295 outer_sep: float = 10 296 smooth: bool = False 297 rounded_corners: bool = False 298 fillet_radius: float = 10 299 draw_fillets: bool = False 300 blend_mode: str = None 301 gradient: str = None 302 pattern: str = None 303 min_width: float = None 304 min_height: float = None 305 min_size: float = None 306 307 def __post_init__(self): 308 self.type = Types.FRAME 309 self.subtype = Types.FRAME 310 common_properties(self, id_only=True)
Frame objects are used with Tag objects to create boxes.
Arguments:
- frame_shape (FrameShape, optional): The shape of the frame. Defaults to "rectangle".
- line_width (float, optional): The width of the frame line. Defaults to 1.
- line_dash_array (list, optional): The dash pattern for the frame line. Defaults to None.
- line_join (LineJoin, optional): The line join style. Defaults to "miter".
- line_color (Color, optional): The color of the frame line. Defaults to colors.black.
- back_color (Color, optional): The background color of the frame. Defaults to colors.white.
- fill (bool, optional): Whether to fill the frame. Defaults to False.
- stroke (bool, optional): Whether to stroke the frame. Defaults to True.
- double (bool, optional): Whether to use a double line. Defaults to False.
- double_distance (float, optional): The distance between double lines. Defaults to 2.
- inner_sep (float, optional): The inner separation. Defaults to 10.
- outer_sep (float, optional): The outer separation. Defaults to 10.
- smooth (bool, optional): Whether to smooth the frame. Defaults to False.
- rounded_corners (bool, optional): Whether to use rounded corners. Defaults to False.
- fillet_radius (float, optional): The radius of the fillet. Defaults to 10.
- draw_fillets (bool, optional): Whether to draw fillets. Defaults to False.
- blend_mode (str, optional): The blend mode. Defaults to None.
- gradient (str, optional): The gradient. Defaults to None.
- pattern (str, optional): The pattern. Defaults to None.
- min_width (float, optional): The minimum width. Defaults to None.
- min_height (float, optional): The minimum height. Defaults to None.
- min_size (float, optional): The minimum size. Defaults to None.
313class Tag(Base): 314 """A Tag object is very similar to TikZ library's nodes. It is a text with a frame. 315 316 Args: 317 text (str): The text of the tag. 318 pos (Point): The position of the tag. 319 font_family (str, optional): The font family. Defaults to None. 320 font_size (int, optional): The font size. Defaults to None. 321 font_color (Color, optional): The font color. Defaults to None. 322 anchor (Anchor, optional): The anchor point. Defaults to Anchor.CENTER. 323 bold (bool, optional): Whether the text is bold. Defaults to False. 324 italic (bool, optional): Whether the text is italic. Defaults to False. 325 text_width (float, optional): The width of the text. Defaults to None. 326 placement (Placement, optional): The placement of the tag. Defaults to None. 327 minimum_size (float, optional): The minimum size of the tag. Defaults to None. 328 minimum_width (float, optional): The minimum width of the tag. Defaults to None. 329 minimum_height (float, optional): The minimum height of the tag. Defaults to None. 330 frame (TagFrame, optional): The frame of the tag. Defaults to None. 331 xform_matrix (array, optional): The transformation matrix. Defaults to None. 332 **kwargs: Additional keyword arguments for tag styling. 333 """ 334 335 def __init__( 336 self, 337 text: str, 338 pos: Point, 339 font_family: str = None, 340 font_size: int = None, 341 font_color: Color = None, 342 anchor: Anchor = Anchor.CENTER, 343 bold: bool = False, 344 italic: bool = False, 345 text_width: float = None, 346 placement: Placement = None, 347 minimum_size: float = None, 348 minimum_width: float = None, 349 minimum_height: float = None, 350 frame=None, 351 xform_matrix=None, 352 **kwargs, 353 ): 354 self.__dict__["style"] = TagStyle() 355 self.__dict__["_style_map"] = tag_style_map 356 self._set_aliases() 357 tag_attribs = list(tag_style_map.keys()) 358 tag_attribs.append("subtype") 359 _set_Nones( 360 self, 361 ["font_family", "font_size", "font_color"], 362 [font_family, font_size, font_color], 363 ) 364 validate_args(kwargs, tag_attribs) 365 x, y = pos[:2] 366 self._init_pos = array([x, y, 1.0]) 367 368 self.text = detokenize(text) 369 if frame is None: 370 self.frame = TagFrame() 371 self.type = Types.TAG 372 self.subtype = Types.TAG 373 self.style = TagStyle() 374 self.style.draw_frame = True 375 if font_family: 376 self.font_family = font_family 377 if font_size: 378 self.font_size = font_size 379 else: 380 self.font_size = defaults["font_size"] 381 if xform_matrix is None: 382 self.xform_matrix = identity_matrix() 383 else: 384 self.xform_matrix = get_transform(xform_matrix) 385 386 self.anchor = anchor 387 self.bold = bold 388 self.italic = italic 389 self.text_width = text_width 390 self.placement = placement 391 self.minimum_size = minimum_size 392 self.minimum_width = minimum_width 393 self.minimum_height = minimum_height 394 for k, v in kwargs.items(): 395 setattr(self, k, v) 396 397 x1, y1, x2, y2 = self.text_bounds() 398 w = x2 - x1 399 h = y2 - y1 400 self.points = Points([(0, 0, 1), (w, 0, 1), (w, h, 1), (0, h, 1)]) 401 common_properties(self) 402 403 def __setattr__(self, name, value): 404 obj, attrib = self.__dict__["_aliasses"].get(name, (None, None)) 405 if obj: 406 setattr(obj, attrib, value) 407 else: 408 self.__dict__[name] = value 409 410 def __getattr__(self, name): 411 obj, attrib = self.__dict__["_aliasses"].get(name, (None, None)) 412 if obj: 413 res = getattr(obj, attrib) 414 else: 415 try: 416 res = super().__getattr__(name) 417 except AttributeError: 418 res = self.__dict__[name] 419 420 return res 421 422 def _set_aliases(self): 423 _aliasses = {} 424 425 for alias, path_attrib in self._style_map.items(): 426 style_path, attrib = path_attrib 427 obj = self 428 for attrib_name in style_path.split("."): 429 obj = obj.__dict__[attrib_name] 430 431 if obj is not self: 432 _aliasses[alias] = (obj, attrib) 433 self.__dict__["_aliasses"] = _aliasses 434 435 def _update(self, xform_matrix, reps: int = 0): 436 if reps == 0: 437 self.xform_matrix = self.xform_matrix @ xform_matrix 438 res = self 439 else: 440 tags = [self] 441 tag = self 442 for _ in range(reps): 443 tag = tag.copy() 444 tag._update(xform_matrix) 445 tags.append(tag) 446 res = Batch(tags) 447 448 return res 449 450 @property 451 def pos(self) -> Point: 452 """Returns the position of the text. 453 454 Returns: 455 Point: The position of the text. 456 """ 457 return (self._init_pos @ self.xform_matrix)[:2].tolist() 458 459 def copy(self) -> "Tag": 460 """Returns a copy of the Tag object. 461 462 Returns: 463 Tag: A copy of the Tag object. 464 """ 465 tag = Tag(self.text, self.pos, xform_matrix=self.xform_matrix) 466 tag._init_pos = self._init_pos 467 tag.font_family = self.font_family 468 tag.font_size = self.font_size 469 tag.font_color = self.font_color 470 tag.anchor = self.anchor 471 tag.bold = self.bold 472 tag.italic = self.italic 473 tag.text_width = self.text_width 474 tag.placement = self.placement 475 tag.minimum_size = self.minimum_size 476 tag.minimum_width = self.minimum_width 477 478 return tag 479 480 def text_bounds(self) -> tuple[float, float, float, float]: 481 """Returns the bounds of the text. 482 483 Returns: 484 tuple: The bounds of the text (xmin, ymin, xmax, ymax). 485 """ 486 mult = 1 # font size multiplier 487 if self.font_size is None: 488 font_size = defaults["font_size"] 489 elif type(self.font_size) in [int, float]: 490 font_size = self.font_size 491 elif self.font_size in FontSize: 492 font_size = convert_latex_font_size(self.font_size) 493 else: 494 raise ValueError("Invalid font size.") 495 try: 496 font = ImageFont.truetype(f"{self.font_family}.ttf", font_size) 497 except OSError: 498 font = ImageFont.load_default() 499 mult = self.font_size / 10 500 xmin, ymin, xmax, ymax = font.getbbox(self.text) 501 width = xmax - xmin 502 height = ymax - ymin 503 dx = (width * mult) / 2 504 dy = (height * mult) / 2 505 xmin -= dx 506 xmax += dx 507 ymin -= dy 508 ymax += dy 509 510 return xmin, ymin, xmax, ymax 511 512 @property 513 def final_coords(self): 514 """Returns the final coordinates of the text. 515 516 Returns: 517 array: The final coordinates of the text. 518 """ 519 return self.points.homogen_coords @ self.xform_matrix 520 521 @property 522 def b_box(self): 523 """Returns the bounding box of the text. 524 525 Returns: 526 tuple: The bounding box of the text. 527 """ 528 xmin, ymin, xmax, ymax = self.text_bounds() 529 w2 = (xmax - xmin) / 2 530 h2 = (ymax - ymin) / 2 531 x, y = self.pos 532 inner_sep = self.frame.inner_sep 533 xmin = x - w2 - inner_sep 534 xmax = x + w2 + inner_sep 535 ymin = y - h2 - inner_sep 536 ymax = y + h2 + inner_sep 537 points = [ 538 (xmin, ymin), 539 (xmax, ymin), 540 (xmax, ymax), 541 (xmin, ymax), 542 ] 543 return bounding_box(points) 544 def __str__(self) -> str: 545 return f"Tag({self.text})" 546 547 def __repr__(self) -> str: 548 return f"Tag({self.text})"
A Tag object is very similar to TikZ library's nodes. It is a text with a frame.
Arguments:
- text (str): The text of the tag.
- pos (Point): The position of the tag.
- font_family (str, optional): The font family. Defaults to None.
- font_size (int, optional): The font size. Defaults to None.
- font_color (Color, optional): The font color. Defaults to None.
- anchor (Anchor, optional): The anchor point. Defaults to Anchor.CENTER.
- bold (bool, optional): Whether the text is bold. Defaults to False.
- italic (bool, optional): Whether the text is italic. Defaults to False.
- text_width (float, optional): The width of the text. Defaults to None.
- placement (Placement, optional): The placement of the tag. Defaults to None.
- minimum_size (float, optional): The minimum size of the tag. Defaults to None.
- minimum_width (float, optional): The minimum width of the tag. Defaults to None.
- minimum_height (float, optional): The minimum height of the tag. Defaults to None.
- frame (TagFrame, optional): The frame of the tag. Defaults to None.
- xform_matrix (array, optional): The transformation matrix. Defaults to None.
- **kwargs: Additional keyword arguments for tag styling.
335 def __init__( 336 self, 337 text: str, 338 pos: Point, 339 font_family: str = None, 340 font_size: int = None, 341 font_color: Color = None, 342 anchor: Anchor = Anchor.CENTER, 343 bold: bool = False, 344 italic: bool = False, 345 text_width: float = None, 346 placement: Placement = None, 347 minimum_size: float = None, 348 minimum_width: float = None, 349 minimum_height: float = None, 350 frame=None, 351 xform_matrix=None, 352 **kwargs, 353 ): 354 self.__dict__["style"] = TagStyle() 355 self.__dict__["_style_map"] = tag_style_map 356 self._set_aliases() 357 tag_attribs = list(tag_style_map.keys()) 358 tag_attribs.append("subtype") 359 _set_Nones( 360 self, 361 ["font_family", "font_size", "font_color"], 362 [font_family, font_size, font_color], 363 ) 364 validate_args(kwargs, tag_attribs) 365 x, y = pos[:2] 366 self._init_pos = array([x, y, 1.0]) 367 368 self.text = detokenize(text) 369 if frame is None: 370 self.frame = TagFrame() 371 self.type = Types.TAG 372 self.subtype = Types.TAG 373 self.style = TagStyle() 374 self.style.draw_frame = True 375 if font_family: 376 self.font_family = font_family 377 if font_size: 378 self.font_size = font_size 379 else: 380 self.font_size = defaults["font_size"] 381 if xform_matrix is None: 382 self.xform_matrix = identity_matrix() 383 else: 384 self.xform_matrix = get_transform(xform_matrix) 385 386 self.anchor = anchor 387 self.bold = bold 388 self.italic = italic 389 self.text_width = text_width 390 self.placement = placement 391 self.minimum_size = minimum_size 392 self.minimum_width = minimum_width 393 self.minimum_height = minimum_height 394 for k, v in kwargs.items(): 395 setattr(self, k, v) 396 397 x1, y1, x2, y2 = self.text_bounds() 398 w = x2 - x1 399 h = y2 - y1 400 self.points = Points([(0, 0, 1), (w, 0, 1), (w, h, 1), (0, h, 1)]) 401 common_properties(self)
450 @property 451 def pos(self) -> Point: 452 """Returns the position of the text. 453 454 Returns: 455 Point: The position of the text. 456 """ 457 return (self._init_pos @ self.xform_matrix)[:2].tolist()
Returns the position of the text.
Returns:
Point: The position of the text.
459 def copy(self) -> "Tag": 460 """Returns a copy of the Tag object. 461 462 Returns: 463 Tag: A copy of the Tag object. 464 """ 465 tag = Tag(self.text, self.pos, xform_matrix=self.xform_matrix) 466 tag._init_pos = self._init_pos 467 tag.font_family = self.font_family 468 tag.font_size = self.font_size 469 tag.font_color = self.font_color 470 tag.anchor = self.anchor 471 tag.bold = self.bold 472 tag.italic = self.italic 473 tag.text_width = self.text_width 474 tag.placement = self.placement 475 tag.minimum_size = self.minimum_size 476 tag.minimum_width = self.minimum_width 477 478 return tag
Returns a copy of the Tag object.
Returns:
Tag: A copy of the Tag object.
480 def text_bounds(self) -> tuple[float, float, float, float]: 481 """Returns the bounds of the text. 482 483 Returns: 484 tuple: The bounds of the text (xmin, ymin, xmax, ymax). 485 """ 486 mult = 1 # font size multiplier 487 if self.font_size is None: 488 font_size = defaults["font_size"] 489 elif type(self.font_size) in [int, float]: 490 font_size = self.font_size 491 elif self.font_size in FontSize: 492 font_size = convert_latex_font_size(self.font_size) 493 else: 494 raise ValueError("Invalid font size.") 495 try: 496 font = ImageFont.truetype(f"{self.font_family}.ttf", font_size) 497 except OSError: 498 font = ImageFont.load_default() 499 mult = self.font_size / 10 500 xmin, ymin, xmax, ymax = font.getbbox(self.text) 501 width = xmax - xmin 502 height = ymax - ymin 503 dx = (width * mult) / 2 504 dy = (height * mult) / 2 505 xmin -= dx 506 xmax += dx 507 ymin -= dy 508 ymax += dy 509 510 return xmin, ymin, xmax, ymax
Returns the bounds of the text.
Returns:
tuple: The bounds of the text (xmin, ymin, xmax, ymax).
512 @property 513 def final_coords(self): 514 """Returns the final coordinates of the text. 515 516 Returns: 517 array: The final coordinates of the text. 518 """ 519 return self.points.homogen_coords @ self.xform_matrix
Returns the final coordinates of the text.
Returns:
array: The final coordinates of the text.
521 @property 522 def b_box(self): 523 """Returns the bounding box of the text. 524 525 Returns: 526 tuple: The bounding box of the text. 527 """ 528 xmin, ymin, xmax, ymax = self.text_bounds() 529 w2 = (xmax - xmin) / 2 530 h2 = (ymax - ymin) / 2 531 x, y = self.pos 532 inner_sep = self.frame.inner_sep 533 xmin = x - w2 - inner_sep 534 xmax = x + w2 + inner_sep 535 ymin = y - h2 - inner_sep 536 ymax = y + h2 + inner_sep 537 points = [ 538 (xmin, ymin), 539 (xmax, ymin), 540 (xmax, ymax), 541 (xmin, ymax), 542 ] 543 return bounding_box(points)
Returns the bounding box of the text.
Returns:
tuple: The bounding box of the text.
551class ArrowHead(Shape): 552 """An ArrowHead object is a shape that represents the head of an arrow. 553 554 Args: 555 length (float, optional): The length of the arrow head. Defaults to None. 556 width_ (float, optional): The width of the arrow head. Defaults to None. 557 points (list, optional): The points defining the arrow head. Defaults to None. 558 **kwargs: Additional keyword arguments for arrow head styling. 559 """ 560 561 def __init__( 562 self, length: float = None, width_: float = None, points: list = None, **kwargs 563 ): 564 length, width_ = get_defaults( 565 ["arrow_head_length", "arrow_head_width"], [length, width_] 566 ) 567 if points is None: 568 w2 = width_ / 2 569 points = [(0, 0), (0, -w2), (length, 0), (0, w2)] 570 super().__init__(points, closed=True, subtype=Types.ARROW_HEAD, **kwargs) 571 self.head_length = length 572 self.head_width = width_ 573 574 self.kwargs = kwargs
An ArrowHead object is a shape that represents the head of an arrow.
Arguments:
- length (float, optional): The length of the arrow head. Defaults to None.
- width_ (float, optional): The width of the arrow head. Defaults to None.
- points (list, optional): The points defining the arrow head. Defaults to None.
- **kwargs: Additional keyword arguments for arrow head styling.
561 def __init__( 562 self, length: float = None, width_: float = None, points: list = None, **kwargs 563 ): 564 length, width_ = get_defaults( 565 ["arrow_head_length", "arrow_head_width"], [length, width_] 566 ) 567 if points is None: 568 w2 = width_ / 2 569 points = [(0, 0), (0, -w2), (length, 0), (0, w2)] 570 super().__init__(points, closed=True, subtype=Types.ARROW_HEAD, **kwargs) 571 self.head_length = length 572 self.head_width = width_ 573 574 self.kwargs = kwargs
Initialize a Shape object.
Arguments:
- points (Sequence[Point], optional): The points that make up the shape.
- closed (bool, optional): Whether the shape is closed. Defaults to False.
- xform_matrix (np.array, optional): The transformation matrix. Defaults to None.
- **kwargs (dict): Additional attributes for the shape.
Raises:
- ValueError: If the provided subtype is not valid.
Inherited Members
577def draw_cs_tiny(canvas, pos=(0, 0), width=25, height=25, neg_width=5, neg_height=5): 578 """Draws a tiny coordinate system. 579 580 Args: 581 canvas: The canvas to draw on. 582 pos (tuple, optional): The position of the coordinate system. Defaults to (0, 0). 583 width (int, optional): The length of the x-axis. Defaults to 25. 584 height (int, optional): The length of the y-axis. Defaults to 25. 585 neg_width (int, optional): The negative length of the x-axis. Defaults to 5. 586 neg_height (int, optional): The negative length of the y-axis. Defaults to 5. 587 """ 588 x, y = pos[:2] 589 canvas.circle((x, y), 2, fill=False, line_color=colors.gray) 590 canvas.draw(Shape([(x - neg_width, y), (x + width, y)]), line_color=colors.gray) 591 canvas.draw(Shape([(x, y - neg_height), (x, y + height)]), line_color=colors.gray)
Draws a tiny coordinate system.
Arguments:
- canvas: The canvas to draw on.
- pos (tuple, optional): The position of the coordinate system. Defaults to (0, 0).
- width (int, optional): The length of the x-axis. Defaults to 25.
- height (int, optional): The length of the y-axis. Defaults to 25.
- neg_width (int, optional): The negative length of the x-axis. Defaults to 5.
- neg_height (int, optional): The negative length of the y-axis. Defaults to 5.
594def draw_cs_small(canvas, pos=(0, 0), width=80, height=100, neg_width=5, neg_height=5): 595 """Draws a small coordinate system. 596 597 Args: 598 canvas: The canvas to draw on. 599 pos (tuple, optional): The position of the coordinate system. Defaults to (0, 0). 600 width (int, optional): The length of the x-axis. Defaults to 80. 601 height (int, optional): The length of the y-axis. Defaults to 100. 602 neg_width (int, optional): The negative length of the x-axis. Defaults to 5. 603 neg_height (int, optional): The negative length of the y-axis. Defaults to 5. 604 """ 605 x, y = pos[:2] 606 x_axis = arrow( 607 (-neg_width + x, y), (width + 10 + x, y), head_length=8, head_width=2 608 ) 609 y_axis = arrow( 610 (x, -neg_height + y), (x, height + 10 + y), head_length=8, head_width=2 611 ) 612 canvas.draw(x_axis, line_width=1) 613 canvas.draw(y_axis, line_width=1)
Draws a small coordinate system.
Arguments:
- canvas: The canvas to draw on.
- pos (tuple, optional): The position of the coordinate system. Defaults to (0, 0).
- width (int, optional): The length of the x-axis. Defaults to 80.
- height (int, optional): The length of the y-axis. Defaults to 100.
- neg_width (int, optional): The negative length of the x-axis. Defaults to 5.
- neg_height (int, optional): The negative length of the y-axis. Defaults to 5.
616def arrow( 617 p1, 618 p2, 619 head_length=10, 620 head_width=4, 621 line_width=1, 622 line_color=colors.black, 623 fill_color=colors.black, 624 centered=False, 625): 626 """Return an arrow from p1 to p2. 627 628 Args: 629 p1 (tuple): The starting point of the arrow. 630 p2 (tuple): The ending point of the arrow. 631 head_length (int, optional): The length of the arrow head. Defaults to 10. 632 head_width (int, optional): The width of the arrow head. Defaults to 4. 633 line_width (int, optional): The width of the arrow line. Defaults to 1. 634 line_color (Color, optional): The color of the arrow line. Defaults to colors.black. 635 fill_color (Color, optional): The fill color of the arrow head. Defaults to colors.black. 636 centered (bool, optional): Whether the arrow is centered. Defaults to False. 637 638 Returns: 639 Batch: A Batch object containing the arrow shapes. 640 """ 641 x1, y1 = p1[:2] 642 x2, y2 = p2[:2] 643 dx = x2 - x1 644 dy = y2 - y1 645 angle = atan2(dy, dx) 646 body = Shape( 647 [(x1, y1), (x2, y2)], 648 closed=False, 649 line_color=line_color, 650 fill_color=fill_color, 651 line_width=line_width, 652 ) 653 w2 = head_width / 2 654 head = Shape( 655 [(-head_length, w2), (0, 0), (-head_length, -w2)], 656 closed=True, 657 line_color=line_color, 658 fill_color=fill_color, 659 line_width=line_width, 660 ) 661 head.rotate(angle) 662 if centered: 663 head.translate(*mid_point((x1, y1), (x2, y2))) 664 else: 665 head.translate(x2, y2) 666 return Batch([body, head])
Return an arrow from p1 to p2.
Arguments:
- p1 (tuple): The starting point of the arrow.
- p2 (tuple): The ending point of the arrow.
- head_length (int, optional): The length of the arrow head. Defaults to 10.
- head_width (int, optional): The width of the arrow head. Defaults to 4.
- line_width (int, optional): The width of the arrow line. Defaults to 1.
- line_color (Color, optional): The color of the arrow line. Defaults to colors.black.
- fill_color (Color, optional): The fill color of the arrow head. Defaults to colors.black.
- centered (bool, optional): Whether the arrow is centered. Defaults to False.
Returns:
Batch: A Batch object containing the arrow shapes.
669class ArcArrow(Batch): 670 """An ArcArrow object is an arrow with an arc. 671 672 Args: 673 center (Point): The center of the arc. 674 radius (float): The radius of the arc. 675 start_angle (float): The starting angle of the arc. 676 end_angle (float): The ending angle of the arc. 677 xform_matrix (array, optional): The transformation matrix. Defaults to None. 678 **kwargs: Additional keyword arguments for arc arrow styling. 679 """ 680 681 def __init__( 682 self, 683 center: Point, 684 radius: float, 685 start_angle: float, 686 end_angle: float, 687 xform_matrix: array = None, 688 **kwargs, 689 ): 690 self.center = center 691 self.radius = radius 692 self.start_angle = start_angle 693 self.end_angle = end_angle 694 # create the arc 695 self.arc = Arc(center, radius, start_angle, end_angle) 696 self.arc.fill = False 697 # create arrow_head1 698 self.arrow_head1 = ArrowHead() 699 # create arrow_head2 700 self.arrow_head2 = ArrowHead() 701 start = self.arc.start_point 702 end = self.arc.end_point 703 self.points = [center, start, end] 704 705 self.arrow_head1.translate(-1 * self.arrow_head1.head_length, 0) 706 self.arrow_head1.rotate(start_angle - pi / 2) 707 self.arrow_head1.translate(*start) 708 self.arrow_head2.translate(-1 * self.arrow_head2.head_length, 0) 709 self.arrow_head2.rotate(end_angle + pi / 2) 710 self.arrow_head2.translate(*end) 711 items = [self.arc, self.arrow_head1, self.arrow_head2] 712 super().__init__(items, subtype=Types.ARC_ARROW, **kwargs) 713 for k, v in kwargs.items(): 714 if k in shape_style_map: 715 setattr(self, k, v) # we should check for valid values here 716 else: 717 raise AttributeError(f"{k}. Invalid attribute!") 718 self.xform_matrix = get_transform(xform_matrix)
An ArcArrow object is an arrow with an arc.
Arguments:
- center (Point): 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.
- xform_matrix (array, optional): The transformation matrix. Defaults to None.
- **kwargs: Additional keyword arguments for arc arrow styling.
681 def __init__( 682 self, 683 center: Point, 684 radius: float, 685 start_angle: float, 686 end_angle: float, 687 xform_matrix: array = None, 688 **kwargs, 689 ): 690 self.center = center 691 self.radius = radius 692 self.start_angle = start_angle 693 self.end_angle = end_angle 694 # create the arc 695 self.arc = Arc(center, radius, start_angle, end_angle) 696 self.arc.fill = False 697 # create arrow_head1 698 self.arrow_head1 = ArrowHead() 699 # create arrow_head2 700 self.arrow_head2 = ArrowHead() 701 start = self.arc.start_point 702 end = self.arc.end_point 703 self.points = [center, start, end] 704 705 self.arrow_head1.translate(-1 * self.arrow_head1.head_length, 0) 706 self.arrow_head1.rotate(start_angle - pi / 2) 707 self.arrow_head1.translate(*start) 708 self.arrow_head2.translate(-1 * self.arrow_head2.head_length, 0) 709 self.arrow_head2.rotate(end_angle + pi / 2) 710 self.arrow_head2.translate(*end) 711 items = [self.arc, self.arrow_head1, self.arrow_head2] 712 super().__init__(items, subtype=Types.ARC_ARROW, **kwargs) 713 for k, v in kwargs.items(): 714 if k in shape_style_map: 715 setattr(self, k, v) # we should check for valid values here 716 else: 717 raise AttributeError(f"{k}. Invalid attribute!") 718 self.xform_matrix = get_transform(xform_matrix)
Initialize a Batch object.
Arguments:
- elements (Sequence[Any], optional): The elements to include in the batch.
- modifiers (Sequence[Modifier], optional): The modifiers to apply to the batch.
- subtype (Types, optional): The subtype of the batch.
- kwargs (dict): Additional keyword arguments.
Inherited Members
- simetri.graphics.batch.Batch
- type
- subtype
- modifiers
- blend_mode
- alpha
- line_alpha
- fill_alpha
- text_alpha
- clip
- mask
- even_odd_rule
- blend_group
- transparency_group
- set_attribs
- set_batch_attr
- proximity
- append
- reverse
- insert
- remove
- pop
- clear
- extend
- iter_elements
- all_elements
- all_shapes
- all_vertices
- all_segments
- as_graph
- graph_summary
- merge_shapes
- all_polygons
- copy
- b_box
721class Arrow(Batch): 722 """An Arrow object is a line with an arrow head. 723 724 Args: 725 p1 (Point): The starting point of the arrow. 726 p2 (Point): The ending point of the arrow. 727 head_pos (HeadPos, optional): The position of the arrow head. Defaults to HeadPos.END. 728 head (Shape, optional): The shape of the arrow head. Defaults to None. 729 **kwargs: Additional keyword arguments for arrow styling. 730 """ 731 732 def __init__( 733 self, 734 p1: Point, 735 p2: Point, 736 head_pos: HeadPos = HeadPos.END, 737 head: Shape = None, 738 **kwargs, 739 ): 740 self.p1 = p1 741 self.p2 = p2 742 self.head_pos = head_pos 743 self.head = head 744 self.kwargs = kwargs 745 length = distance(p1, p2) 746 angle = line_angle(p1, p2) 747 self.line = Shape([(0, 0), (length, 0)]) 748 if head is None: 749 self.head = ArrowHead() 750 else: 751 self.head = head 752 if self.head_pos == HeadPos.END: 753 x = length 754 self.head.translate(x - self.head.head_length, 0) 755 self.head.rotate(angle) 756 self.line.rotate(angle) 757 self.line.translate(*p1) 758 self.head.translate(*p1) 759 self.heads = [self.head] 760 elif self.head_pos == HeadPos.START: 761 self.head = [None] 762 elif self.head_pos == HeadPos.BOTH: 763 self.head2 = ArrowHead() 764 self.head2.rotate(pi) 765 self.head2.translate(self.head2.head_length, 0) 766 self.head2.rotate(angle) 767 self.head2.translate(*p1) 768 x = length 769 self.head.translate(x - self.head.head_length, 0) 770 self.head.rotate(angle) 771 self.line.rotate(angle) 772 self.line.translate(*p1) 773 self.head.translate(*p1) 774 self.heads = [self.head, self.head2] 775 elif self.head_pos == HeadPos.NONE: 776 self.heads = [None] 777 778 items = [self.line] + self.heads 779 super().__init__(items, subtype=Types.ARROW, **kwargs)
An Arrow object is a line with an arrow head.
Arguments:
- p1 (Point): The starting point of the arrow.
- p2 (Point): The ending point of the arrow.
- head_pos (HeadPos, optional): The position of the arrow head. Defaults to HeadPos.END.
- head (Shape, optional): The shape of the arrow head. Defaults to None.
- **kwargs: Additional keyword arguments for arrow styling.
732 def __init__( 733 self, 734 p1: Point, 735 p2: Point, 736 head_pos: HeadPos = HeadPos.END, 737 head: Shape = None, 738 **kwargs, 739 ): 740 self.p1 = p1 741 self.p2 = p2 742 self.head_pos = head_pos 743 self.head = head 744 self.kwargs = kwargs 745 length = distance(p1, p2) 746 angle = line_angle(p1, p2) 747 self.line = Shape([(0, 0), (length, 0)]) 748 if head is None: 749 self.head = ArrowHead() 750 else: 751 self.head = head 752 if self.head_pos == HeadPos.END: 753 x = length 754 self.head.translate(x - self.head.head_length, 0) 755 self.head.rotate(angle) 756 self.line.rotate(angle) 757 self.line.translate(*p1) 758 self.head.translate(*p1) 759 self.heads = [self.head] 760 elif self.head_pos == HeadPos.START: 761 self.head = [None] 762 elif self.head_pos == HeadPos.BOTH: 763 self.head2 = ArrowHead() 764 self.head2.rotate(pi) 765 self.head2.translate(self.head2.head_length, 0) 766 self.head2.rotate(angle) 767 self.head2.translate(*p1) 768 x = length 769 self.head.translate(x - self.head.head_length, 0) 770 self.head.rotate(angle) 771 self.line.rotate(angle) 772 self.line.translate(*p1) 773 self.head.translate(*p1) 774 self.heads = [self.head, self.head2] 775 elif self.head_pos == HeadPos.NONE: 776 self.heads = [None] 777 778 items = [self.line] + self.heads 779 super().__init__(items, subtype=Types.ARROW, **kwargs)
Initialize a Batch object.
Arguments:
- elements (Sequence[Any], optional): The elements to include in the batch.
- modifiers (Sequence[Modifier], optional): The modifiers to apply to the batch.
- subtype (Types, optional): The subtype of the batch.
- kwargs (dict): Additional keyword arguments.
Inherited Members
- simetri.graphics.batch.Batch
- type
- subtype
- modifiers
- blend_mode
- alpha
- line_alpha
- fill_alpha
- text_alpha
- clip
- mask
- even_odd_rule
- blend_group
- transparency_group
- set_attribs
- set_batch_attr
- proximity
- append
- reverse
- insert
- remove
- pop
- clear
- extend
- iter_elements
- all_elements
- all_shapes
- all_vertices
- all_segments
- as_graph
- graph_summary
- merge_shapes
- all_polygons
- copy
- b_box
782class AngularDimension(Batch): 783 """An AngularDimension object is a dimension that represents an angle. 784 785 Args: 786 center (Point): The center of the angle. 787 radius (float): The radius of the angle. 788 start_angle (float): The starting angle. 789 end_angle (float): The ending angle. 790 ext_angle (float): The extension angle. 791 gap_angle (float): The gap angle. 792 text_offset (float, optional): The text offset. Defaults to None. 793 gap (float, optional): The gap. Defaults to None. 794 **kwargs: Additional keyword arguments for angular dimension styling. 795 """ 796 797 def __init__( 798 self, 799 center: Point, 800 radius: float, 801 start_angle: float, 802 end_angle: float, 803 ext_angle: float, 804 gap_angle: float, 805 text_offset: float = None, 806 gap: float = None, 807 **kwargs, 808 ): 809 text_offset, gap = get_defaults(["text_offset", "gap"], [text_offset, gap]) 810 self.center = center 811 self.radius = radius 812 self.start_angle = start_angle 813 self.end_angle = end_angle 814 self.ext_angle = ext_angle 815 self.gap_angle = gap_angle 816 self.text_offset = text_offset 817 self.gap = gap 818 super().__init__(subtype=Types.ANGULAR_DIMENSION, **kwargs)
An AngularDimension object is a dimension that represents an angle.
Arguments:
- center (Point): The center of the angle.
- radius (float): The radius of the angle.
- start_angle (float): The starting angle.
- end_angle (float): The ending angle.
- ext_angle (float): The extension angle.
- gap_angle (float): The gap angle.
- text_offset (float, optional): The text offset. Defaults to None.
- gap (float, optional): The gap. Defaults to None.
- **kwargs: Additional keyword arguments for angular dimension styling.
797 def __init__( 798 self, 799 center: Point, 800 radius: float, 801 start_angle: float, 802 end_angle: float, 803 ext_angle: float, 804 gap_angle: float, 805 text_offset: float = None, 806 gap: float = None, 807 **kwargs, 808 ): 809 text_offset, gap = get_defaults(["text_offset", "gap"], [text_offset, gap]) 810 self.center = center 811 self.radius = radius 812 self.start_angle = start_angle 813 self.end_angle = end_angle 814 self.ext_angle = ext_angle 815 self.gap_angle = gap_angle 816 self.text_offset = text_offset 817 self.gap = gap 818 super().__init__(subtype=Types.ANGULAR_DIMENSION, **kwargs)
Initialize a Batch object.
Arguments:
- elements (Sequence[Any], optional): The elements to include in the batch.
- modifiers (Sequence[Modifier], optional): The modifiers to apply to the batch.
- subtype (Types, optional): The subtype of the batch.
- kwargs (dict): Additional keyword arguments.
Inherited Members
- simetri.graphics.batch.Batch
- type
- subtype
- modifiers
- blend_mode
- alpha
- line_alpha
- fill_alpha
- text_alpha
- clip
- mask
- even_odd_rule
- blend_group
- transparency_group
- set_attribs
- set_batch_attr
- proximity
- append
- reverse
- insert
- remove
- pop
- clear
- extend
- iter_elements
- all_elements
- all_shapes
- all_vertices
- all_segments
- as_graph
- graph_summary
- merge_shapes
- all_polygons
- copy
- b_box
821class Dimension(Batch): 822 """A Dimension object is a line with arrows and a text. 823 824 Args: 825 text (str): The text of the dimension. 826 p1 (Point): The starting point of the dimension. 827 p2 (Point): The ending point of the dimension. 828 ext_length (float): The length of the extension lines. 829 ext_length2 (float, optional): The length of the second extension line. Defaults to None. 830 orientation (Anchor, optional): The orientation of the dimension. Defaults to None. 831 text_pos (Anchor, optional): The position of the text. Defaults to Anchor.CENTER. 832 text_offset (float, optional): The offset of the text. Defaults to 0. 833 gap (float, optional): The gap. Defaults to None. 834 reverse_arrows (bool, optional): Whether to reverse the arrows. Defaults to False. 835 reverse_arrow_length (float, optional): The length of the reversed arrows. Defaults to None. 836 parallel (bool, optional): Whether the dimension is parallel. Defaults to False. 837 ext1pnt (Point, optional): The first extension point. Defaults to None. 838 ext2pnt (Point, optional): The second extension point. Defaults to None. 839 scale (float, optional): The scale factor. Defaults to 1. 840 font_size (int, optional): The font size. Defaults to 12. 841 **kwargs: Additional keyword arguments for dimension styling. 842 """ 843 844 # To do: This is too long and convoluted. Refactor it. 845 def __init__( 846 self, 847 text: str, 848 p1: Point, 849 p2: Point, 850 ext_length: float, 851 ext_length2: float = None, 852 orientation: Anchor = None, 853 text_pos: Anchor = Anchor.CENTER, 854 text_offset: float = 0, 855 gap: float = None, 856 reverse_arrows: bool = False, 857 reverse_arrow_length: float = None, 858 parallel: bool = False, 859 ext1pnt: Point = None, 860 ext2pnt: Point = None, 861 scale: float = 1, 862 font_size: int = 12, 863 **kwargs, 864 ): 865 ext_length2, gap, reverse_arrow_length = get_defaults( 866 ["ext_length2", "gap", "rev_arrow_length"], 867 [ext_length2, gap, reverse_arrow_length], 868 ) 869 if text == "": 870 self.text = str(distance(p1, p2) / scale) 871 else: 872 self.text = text 873 self.p1 = p1 874 self.p2 = p2 875 self.ext_length = ext_length 876 self.ext_length2 = ext_length2 877 self.orientation = orientation 878 self.text_pos = text_pos 879 self.text_offset = text_offset 880 self.gap = gap 881 self.reverse_arrows = reverse_arrows 882 self.reverse_arrow_length = reverse_arrow_length 883 self.kwargs = kwargs 884 self.ext1 = None 885 self.ext2 = None 886 self.ext3 = None 887 self.arrow1 = None 888 self.arrow2 = None 889 self.dim_line = None 890 self.mid_line = None 891 self.ext1pnt = ext1pnt 892 self.ext2pnt = ext2pnt 893 x1, y1 = p1[:2] 894 x2, y2 = p2[:2] 895 896 # px1_1 : extension1 point 1 897 # px1_2 : extension1 point 2 898 # px2_1 : extension2 point 1 899 # px2_2 : extension2 point 2 900 # px3_1 : extension3 point 1 901 # px3_2 : extension3 point 2 902 # pa1 : arrow point 1 903 # pa2 : arrow point 2 904 # ptext : text point 905 super().__init__(subtype=Types.DIMENSION, **kwargs) 906 dist_tol = defaults["dist_tol"] 907 if font_size is not None: 908 self.font_size = font_size 909 if parallel: 910 if orientation is None: 911 orientation = Anchor.NORTHEAST 912 913 if orientation == Anchor.NORTHEAST: 914 angle = line_angle(p1, p2) + pi / 2 915 elif orientation == Anchor.NORTHWEST: 916 angle = line_angle(p1, p2) + pi / 2 917 elif orientation == Anchor.SOUTHEAST: 918 angle = line_angle(p1, p2) - pi / 2 919 elif orientation == Anchor.SOUTHWEST: 920 angle = line_angle(p1, p2) + pi / 2 921 if self.ext1pnt is None: 922 px1_1 = line_by_point_angle_length(p1, angle, self.gap)[1] 923 else: 924 px1_1 = self.ext1pnt 925 px1_2 = line_by_point_angle_length(p1, angle, self.gap + self.ext_length)[1] 926 if self.ext2pnt is None: 927 px2_1 = line_by_point_angle_length(p2, angle, self.gap)[1] 928 else: 929 px2_1 = self.ext2pnt 930 px2_2 = line_by_point_angle_length(p2, angle, self.gap + self.ext_length)[1] 931 932 pa1 = line_by_point_angle_length(px1_2, angle, self.gap * -1.5)[1] 933 pa2 = line_by_point_angle_length(px2_2, angle, self.gap * -1.5)[1] 934 935 self.text_pos = mid_point(pa1, pa2) 936 self.dim_line = Arrow(pa1, pa2, head_pos=HeadPos.BOTH) 937 self.ext1 = Shape([px1_1, px1_2]) 938 self.ext2 = Shape([px2_1, px2_2]) 939 self.append(self.dim_line) 940 self.append(self.ext1) 941 self.append(self.ext2) 942 943 else: 944 if abs(x1 - x2) < dist_tol: 945 # vertical line 946 if self.orientation is None: 947 orientation = Anchor.EAST 948 949 if orientation in [Anchor.WEST, Anchor.SOUTHWEST, Anchor.NORTHWEST]: 950 x = x1 - self.gap 951 px1_1 = (x, y1) 952 px1_2 = (x - ext_length, y1) 953 px2_1 = (x, y2) 954 px2_2 = (x - ext_length, y2) 955 x = px1_2[0] + self.gap * 1.5 956 pa1 = (x, y1) 957 pa2 = (x, y2) 958 elif orientation in [Anchor.EAST, Anchor.SOUTHEAST, Anchor.NORTHEAST]: 959 x = x1 + self.gap 960 px1_1 = (x, y1) 961 px1_2 = (x + ext_length, y1) 962 px2_1 = (x, y2) 963 px2_2 = (x + ext_length, y2) 964 x = px1_2[0] - self.gap * 1.5 965 pa1 = (x, y1) 966 pa2 = (x, y2) 967 elif orientation == Anchor.CENTER: 968 pa1 = (x1, y1) 969 pa2 = (x1, y2) 970 x = pa1[0] 971 if orientation in [Anchor.SOUTHWEST, Anchor.SOUTHEAST]: 972 px3_1 = pa2 973 y = y2 - self.ext_length2 974 px3_2 = (x, y) 975 self.ext3 = Shape([px3_1, px3_2]) 976 self.text_pos = (x, y - self.text_offset) 977 elif orientation in [Anchor.NORTHWEST, Anchor.NORTHEAST]: 978 px3_1 = pa1 979 y = y1 + self.ext_length2 980 px3_2 = (x, y) 981 self.ext3 = Shape([px3_1, px3_2]) 982 self.text_pos = (x, y + self.text_offset) 983 elif orientation == Anchor.SOUTH: 984 px3_1 = pa2 985 y = y2 - self.ext_length2 986 px3_2 = (x, y) 987 self.ext3 = Shape([px3_1, px3_2]) 988 self.text_pos = (x, y - self.text_offset) 989 elif orientation == Anchor.NORTH: 990 px3_2 = pa1 991 y = y2 + self.ext_length2 992 px3_1 = (x, y) 993 self.ext3 = Shape([px3_1, px3_2]) 994 self.text_pos = (x, y + self.text_offset) 995 else: 996 self.text_pos = (x, y1 - (y1 - y2) / 2) 997 if orientation not in [Anchor.CENTER, Anchor.NORTH, Anchor.SOUTH]: 998 if self.ext1pnt is None: 999 self.ext1 = Shape([px1_1, px1_2]) 1000 else: 1001 self.ext1 = Shape([ext1pnt, px1_2]) 1002 if self.ext2pnt is None: 1003 self.ext2 = Shape([px2_1, px2_2]) 1004 else: 1005 self.ext2 = Shape([ext2pnt, px2_2]) 1006 elif abs(y1 - y2) < dist_tol: 1007 # horizontal line 1008 if self.orientation is None: 1009 orientation = Anchor.SOUTH 1010 1011 if orientation in [Anchor.SOUTH, Anchor.SOUTHWEST, Anchor.SOUTHEAST]: 1012 y = y1 - self.gap 1013 px1_1 = (x1, y) 1014 px1_2 = (x1, y - ext_length) 1015 px2_1 = (x2, y) 1016 px2_2 = (x2, y - ext_length) 1017 y = px1_2[1] + self.gap * 1.5 1018 pa1 = (x1, y) 1019 pa2 = (x2, y) 1020 elif orientation in [Anchor.NORTH, Anchor.NORTHWEST, Anchor.NORTHEAST]: 1021 y = y1 + self.gap 1022 px1_1 = (x1, y) 1023 px1_2 = (x1, y + ext_length) 1024 px2_1 = (x2, y) 1025 px2_2 = (x2, y + ext_length) 1026 y = px1_2[1] - self.gap * 1.5 1027 pa1 = (x1, y) 1028 pa2 = (x2, y) 1029 elif orientation in [Anchor.WEST, Anchor.EAST]: 1030 pa1 = (x1, y1) 1031 pa2 = (x2, y2) 1032 if orientation == Anchor.WEST: 1033 px3_1 = (pa1[0] - self.ext_length2, pa1[1]) 1034 px3_2 = pa1 1035 self.text_pos = (px3_1[0] - self.text_offset, pa1[1]) 1036 else: 1037 px3_1 = pa2 1038 px3_2 = (pa2[0] + self.ext_length2, pa1[1]) 1039 self.text_pos = (px3_1[0] + self.text_offset, pa1[1]) 1040 self.ext3 = Shape([px3_1, px3_2]) 1041 elif orientation == Anchor.CENTER: 1042 pa1 = (x1, y1) 1043 pa2 = (x2, y2) 1044 1045 y = pa1[1] 1046 if orientation in [Anchor.SOUTHWEST, Anchor.NORTHWEST]: 1047 px3_1 = pa1 1048 x = x1 - self.ext_length2 1049 px3_2 = (x, y) 1050 self.ext3 = Shape([px3_1, px3_2]) 1051 self.text_pos = (x - self.text_offset, y) 1052 elif orientation in [Anchor.NORTHEAST, Anchor.SOUTHEAST]: 1053 px3_1 = pa2 1054 x = x2 + self.ext_length2 1055 px3_2 = (x, y) 1056 self.ext3 = Shape([px3_1, px3_2]) 1057 self.text_pos = (x + self.text_offset, y) 1058 elif orientation in [Anchor.CENTER, Anchor.NORTH, Anchor.SOUTH]: 1059 self.text_pos = (x1 + (x2 - x1) / 2, y) 1060 1061 if orientation not in [Anchor.CENTER, Anchor.WEST, Anchor.EAST]: 1062 if self.ext1pnt is None: 1063 self.ext1 = Shape([px1_1, px1_2]) 1064 else: 1065 self.ext1Shape([ext1pnt, px1_2]) 1066 if self.ext2pnt is None: 1067 self.ext2 = Shape([px2_1, px2_2]) 1068 else: 1069 self.ext2 = Shape([ext2pnt, px2_2]) 1070 1071 if self.reverse_arrows: 1072 dist = self.reverse_arrow_length 1073 p2 = extended_line(dist, [pa1, pa2])[1] 1074 self.arrow1 = Arrow(p2, pa2) 1075 p2 = extended_line(dist, [pa2, pa1])[1] 1076 self.arrow2 = Arrow(p2, pa1) 1077 self.append(self.arrow1) 1078 self.append(self.arrow2) 1079 self.mid_line = Shape([pa1, pa2]) 1080 self.append(self.mid_line) 1081 dist = self.text_offset + self.reverse_arrow_length 1082 if orientation in [Anchor.EAST, Anchor.NORTHEAST, Anchor.NORTH]: 1083 1084 self.text_pos = extended_line(dist, [pa1, pa2])[1] 1085 else: 1086 self.text_pos = extended_line(dist, [pa2, pa1])[1] 1087 else: 1088 self.dim_line = Arrow(pa1, pa2, head_pos=HeadPos.BOTH) 1089 self.append(self.dim_line) 1090 if self.ext1 is not None: 1091 self.append(self.ext1) 1092 1093 if self.ext2 is not None: 1094 self.append(self.ext2) 1095 1096 if self.ext3 is not None: 1097 self.append(self.ext3)
A Dimension object is a line with arrows and a text.
Arguments:
- text (str): The text of the dimension.
- p1 (Point): The starting point of the dimension.
- p2 (Point): The ending point of the dimension.
- ext_length (float): The length of the extension lines.
- ext_length2 (float, optional): The length of the second extension line. Defaults to None.
- orientation (Anchor, optional): The orientation of the dimension. Defaults to None.
- text_pos (Anchor, optional): The position of the text. Defaults to Anchor.CENTER.
- text_offset (float, optional): The offset of the text. Defaults to 0.
- gap (float, optional): The gap. Defaults to None.
- reverse_arrows (bool, optional): Whether to reverse the arrows. Defaults to False.
- reverse_arrow_length (float, optional): The length of the reversed arrows. Defaults to None.
- parallel (bool, optional): Whether the dimension is parallel. Defaults to False.
- ext1pnt (Point, optional): The first extension point. Defaults to None.
- ext2pnt (Point, optional): The second extension point. Defaults to None.
- scale (float, optional): The scale factor. Defaults to 1.
- font_size (int, optional): The font size. Defaults to 12.
- **kwargs: Additional keyword arguments for dimension styling.
845 def __init__( 846 self, 847 text: str, 848 p1: Point, 849 p2: Point, 850 ext_length: float, 851 ext_length2: float = None, 852 orientation: Anchor = None, 853 text_pos: Anchor = Anchor.CENTER, 854 text_offset: float = 0, 855 gap: float = None, 856 reverse_arrows: bool = False, 857 reverse_arrow_length: float = None, 858 parallel: bool = False, 859 ext1pnt: Point = None, 860 ext2pnt: Point = None, 861 scale: float = 1, 862 font_size: int = 12, 863 **kwargs, 864 ): 865 ext_length2, gap, reverse_arrow_length = get_defaults( 866 ["ext_length2", "gap", "rev_arrow_length"], 867 [ext_length2, gap, reverse_arrow_length], 868 ) 869 if text == "": 870 self.text = str(distance(p1, p2) / scale) 871 else: 872 self.text = text 873 self.p1 = p1 874 self.p2 = p2 875 self.ext_length = ext_length 876 self.ext_length2 = ext_length2 877 self.orientation = orientation 878 self.text_pos = text_pos 879 self.text_offset = text_offset 880 self.gap = gap 881 self.reverse_arrows = reverse_arrows 882 self.reverse_arrow_length = reverse_arrow_length 883 self.kwargs = kwargs 884 self.ext1 = None 885 self.ext2 = None 886 self.ext3 = None 887 self.arrow1 = None 888 self.arrow2 = None 889 self.dim_line = None 890 self.mid_line = None 891 self.ext1pnt = ext1pnt 892 self.ext2pnt = ext2pnt 893 x1, y1 = p1[:2] 894 x2, y2 = p2[:2] 895 896 # px1_1 : extension1 point 1 897 # px1_2 : extension1 point 2 898 # px2_1 : extension2 point 1 899 # px2_2 : extension2 point 2 900 # px3_1 : extension3 point 1 901 # px3_2 : extension3 point 2 902 # pa1 : arrow point 1 903 # pa2 : arrow point 2 904 # ptext : text point 905 super().__init__(subtype=Types.DIMENSION, **kwargs) 906 dist_tol = defaults["dist_tol"] 907 if font_size is not None: 908 self.font_size = font_size 909 if parallel: 910 if orientation is None: 911 orientation = Anchor.NORTHEAST 912 913 if orientation == Anchor.NORTHEAST: 914 angle = line_angle(p1, p2) + pi / 2 915 elif orientation == Anchor.NORTHWEST: 916 angle = line_angle(p1, p2) + pi / 2 917 elif orientation == Anchor.SOUTHEAST: 918 angle = line_angle(p1, p2) - pi / 2 919 elif orientation == Anchor.SOUTHWEST: 920 angle = line_angle(p1, p2) + pi / 2 921 if self.ext1pnt is None: 922 px1_1 = line_by_point_angle_length(p1, angle, self.gap)[1] 923 else: 924 px1_1 = self.ext1pnt 925 px1_2 = line_by_point_angle_length(p1, angle, self.gap + self.ext_length)[1] 926 if self.ext2pnt is None: 927 px2_1 = line_by_point_angle_length(p2, angle, self.gap)[1] 928 else: 929 px2_1 = self.ext2pnt 930 px2_2 = line_by_point_angle_length(p2, angle, self.gap + self.ext_length)[1] 931 932 pa1 = line_by_point_angle_length(px1_2, angle, self.gap * -1.5)[1] 933 pa2 = line_by_point_angle_length(px2_2, angle, self.gap * -1.5)[1] 934 935 self.text_pos = mid_point(pa1, pa2) 936 self.dim_line = Arrow(pa1, pa2, head_pos=HeadPos.BOTH) 937 self.ext1 = Shape([px1_1, px1_2]) 938 self.ext2 = Shape([px2_1, px2_2]) 939 self.append(self.dim_line) 940 self.append(self.ext1) 941 self.append(self.ext2) 942 943 else: 944 if abs(x1 - x2) < dist_tol: 945 # vertical line 946 if self.orientation is None: 947 orientation = Anchor.EAST 948 949 if orientation in [Anchor.WEST, Anchor.SOUTHWEST, Anchor.NORTHWEST]: 950 x = x1 - self.gap 951 px1_1 = (x, y1) 952 px1_2 = (x - ext_length, y1) 953 px2_1 = (x, y2) 954 px2_2 = (x - ext_length, y2) 955 x = px1_2[0] + self.gap * 1.5 956 pa1 = (x, y1) 957 pa2 = (x, y2) 958 elif orientation in [Anchor.EAST, Anchor.SOUTHEAST, Anchor.NORTHEAST]: 959 x = x1 + self.gap 960 px1_1 = (x, y1) 961 px1_2 = (x + ext_length, y1) 962 px2_1 = (x, y2) 963 px2_2 = (x + ext_length, y2) 964 x = px1_2[0] - self.gap * 1.5 965 pa1 = (x, y1) 966 pa2 = (x, y2) 967 elif orientation == Anchor.CENTER: 968 pa1 = (x1, y1) 969 pa2 = (x1, y2) 970 x = pa1[0] 971 if orientation in [Anchor.SOUTHWEST, Anchor.SOUTHEAST]: 972 px3_1 = pa2 973 y = y2 - self.ext_length2 974 px3_2 = (x, y) 975 self.ext3 = Shape([px3_1, px3_2]) 976 self.text_pos = (x, y - self.text_offset) 977 elif orientation in [Anchor.NORTHWEST, Anchor.NORTHEAST]: 978 px3_1 = pa1 979 y = y1 + self.ext_length2 980 px3_2 = (x, y) 981 self.ext3 = Shape([px3_1, px3_2]) 982 self.text_pos = (x, y + self.text_offset) 983 elif orientation == Anchor.SOUTH: 984 px3_1 = pa2 985 y = y2 - self.ext_length2 986 px3_2 = (x, y) 987 self.ext3 = Shape([px3_1, px3_2]) 988 self.text_pos = (x, y - self.text_offset) 989 elif orientation == Anchor.NORTH: 990 px3_2 = pa1 991 y = y2 + self.ext_length2 992 px3_1 = (x, y) 993 self.ext3 = Shape([px3_1, px3_2]) 994 self.text_pos = (x, y + self.text_offset) 995 else: 996 self.text_pos = (x, y1 - (y1 - y2) / 2) 997 if orientation not in [Anchor.CENTER, Anchor.NORTH, Anchor.SOUTH]: 998 if self.ext1pnt is None: 999 self.ext1 = Shape([px1_1, px1_2]) 1000 else: 1001 self.ext1 = Shape([ext1pnt, px1_2]) 1002 if self.ext2pnt is None: 1003 self.ext2 = Shape([px2_1, px2_2]) 1004 else: 1005 self.ext2 = Shape([ext2pnt, px2_2]) 1006 elif abs(y1 - y2) < dist_tol: 1007 # horizontal line 1008 if self.orientation is None: 1009 orientation = Anchor.SOUTH 1010 1011 if orientation in [Anchor.SOUTH, Anchor.SOUTHWEST, Anchor.SOUTHEAST]: 1012 y = y1 - self.gap 1013 px1_1 = (x1, y) 1014 px1_2 = (x1, y - ext_length) 1015 px2_1 = (x2, y) 1016 px2_2 = (x2, y - ext_length) 1017 y = px1_2[1] + self.gap * 1.5 1018 pa1 = (x1, y) 1019 pa2 = (x2, y) 1020 elif orientation in [Anchor.NORTH, Anchor.NORTHWEST, Anchor.NORTHEAST]: 1021 y = y1 + self.gap 1022 px1_1 = (x1, y) 1023 px1_2 = (x1, y + ext_length) 1024 px2_1 = (x2, y) 1025 px2_2 = (x2, y + ext_length) 1026 y = px1_2[1] - self.gap * 1.5 1027 pa1 = (x1, y) 1028 pa2 = (x2, y) 1029 elif orientation in [Anchor.WEST, Anchor.EAST]: 1030 pa1 = (x1, y1) 1031 pa2 = (x2, y2) 1032 if orientation == Anchor.WEST: 1033 px3_1 = (pa1[0] - self.ext_length2, pa1[1]) 1034 px3_2 = pa1 1035 self.text_pos = (px3_1[0] - self.text_offset, pa1[1]) 1036 else: 1037 px3_1 = pa2 1038 px3_2 = (pa2[0] + self.ext_length2, pa1[1]) 1039 self.text_pos = (px3_1[0] + self.text_offset, pa1[1]) 1040 self.ext3 = Shape([px3_1, px3_2]) 1041 elif orientation == Anchor.CENTER: 1042 pa1 = (x1, y1) 1043 pa2 = (x2, y2) 1044 1045 y = pa1[1] 1046 if orientation in [Anchor.SOUTHWEST, Anchor.NORTHWEST]: 1047 px3_1 = pa1 1048 x = x1 - self.ext_length2 1049 px3_2 = (x, y) 1050 self.ext3 = Shape([px3_1, px3_2]) 1051 self.text_pos = (x - self.text_offset, y) 1052 elif orientation in [Anchor.NORTHEAST, Anchor.SOUTHEAST]: 1053 px3_1 = pa2 1054 x = x2 + self.ext_length2 1055 px3_2 = (x, y) 1056 self.ext3 = Shape([px3_1, px3_2]) 1057 self.text_pos = (x + self.text_offset, y) 1058 elif orientation in [Anchor.CENTER, Anchor.NORTH, Anchor.SOUTH]: 1059 self.text_pos = (x1 + (x2 - x1) / 2, y) 1060 1061 if orientation not in [Anchor.CENTER, Anchor.WEST, Anchor.EAST]: 1062 if self.ext1pnt is None: 1063 self.ext1 = Shape([px1_1, px1_2]) 1064 else: 1065 self.ext1Shape([ext1pnt, px1_2]) 1066 if self.ext2pnt is None: 1067 self.ext2 = Shape([px2_1, px2_2]) 1068 else: 1069 self.ext2 = Shape([ext2pnt, px2_2]) 1070 1071 if self.reverse_arrows: 1072 dist = self.reverse_arrow_length 1073 p2 = extended_line(dist, [pa1, pa2])[1] 1074 self.arrow1 = Arrow(p2, pa2) 1075 p2 = extended_line(dist, [pa2, pa1])[1] 1076 self.arrow2 = Arrow(p2, pa1) 1077 self.append(self.arrow1) 1078 self.append(self.arrow2) 1079 self.mid_line = Shape([pa1, pa2]) 1080 self.append(self.mid_line) 1081 dist = self.text_offset + self.reverse_arrow_length 1082 if orientation in [Anchor.EAST, Anchor.NORTHEAST, Anchor.NORTH]: 1083 1084 self.text_pos = extended_line(dist, [pa1, pa2])[1] 1085 else: 1086 self.text_pos = extended_line(dist, [pa2, pa1])[1] 1087 else: 1088 self.dim_line = Arrow(pa1, pa2, head_pos=HeadPos.BOTH) 1089 self.append(self.dim_line) 1090 if self.ext1 is not None: 1091 self.append(self.ext1) 1092 1093 if self.ext2 is not None: 1094 self.append(self.ext2) 1095 1096 if self.ext3 is not None: 1097 self.append(self.ext3)
Initialize a Batch object.
Arguments:
- elements (Sequence[Any], optional): The elements to include in the batch.
- modifiers (Sequence[Modifier], optional): The modifiers to apply to the batch.
- subtype (Types, optional): The subtype of the batch.
- kwargs (dict): Additional keyword arguments.
Inherited Members
- simetri.graphics.batch.Batch
- type
- subtype
- modifiers
- blend_mode
- alpha
- line_alpha
- fill_alpha
- text_alpha
- clip
- mask
- even_odd_rule
- blend_group
- transparency_group
- set_attribs
- set_batch_attr
- proximity
- append
- reverse
- insert
- remove
- pop
- clear
- extend
- iter_elements
- all_elements
- all_shapes
- all_vertices
- all_segments
- as_graph
- graph_summary
- merge_shapes
- all_polygons
- copy
- b_box