simetri.graphics.pattern
1from math import prod 2from itertools import product 3from dataclasses import dataclass 4from hashlib import md5 5 6import numpy as np 7from typing_extensions import Union, Self 8 9from .shape import Shape 10from .batch import Batch 11from .affine import * 12from .common import Point, Line, common_properties 13from .all_enums import Types, get_enum_value, Anchor 14from .core import StyleMixin 15from .bbox import bounding_box, BoundingBox 16from ..canvas.style_map import ShapeStyle, shape_style_map, shape_args 17from ..helpers.validation import validate_args 18from ..geometry.geometry import homogenize 19 20@dataclass 21class Transform: 22 """ 23 A class representing a single transformation. 24 Used in the Transformation class to represent a transformation matrix and its repetitions. 25 26 Attributes: 27 xform_matrix (ndarray): The transformation matrix. 28 reps (int): The number of repetitions of the transformation. 29 """ 30 31 xform_matrix: 'ndarray' 32 _reps: int = 0 33 34 def __post_init__(self): 35 self.type = Types.TRANSFORM 36 self.subtype = Types.TRANSFORM 37 common_properties(self, graphics_object=False, id_only=True) 38 self.__dict__['_xform_matrix'] = self.xform_matrix 39 # self.__dict__['_reps'] = self.reps 40 self._update() 41 42 def _update(self): 43 self.hash = md5(self.xform_matrix.tobytes()).hexdigest() 44 self._set_partitions() 45 self._composite = np.concatenate(self.partitions, axis=1) 46 47 @property 48 def reps(self) -> int: 49 return self._reps 50 51 @reps.setter 52 def reps(self, value: int): 53 if value < 0: 54 raise ValueError("x cannot be negative") 55 self._reps = value 56 57 58 def _changed(self): 59 """ 60 Checks if the transformation matrix has changed. 61 62 Returns: 63 bool: True if the transformation matrix has changed, False otherwise. 64 """ 65 return not((self.hash == md5(self.xform_matrix.tobytes()).hexdigest()) and 66 (self.reps == self._reps)) 67 68 def _set_partitions(self): 69 if self.reps == 0: 70 partition_list = [identity_matrix()] 71 elif self.reps == 1: 72 partition_list = [identity_matrix(), self.xform_matrix] 73 else: 74 xform_mat = self.xform_matrix 75 partition_list = [identity_matrix(), xform_mat] 76 last = xform_mat 77 for _ in range(self._reps-1): 78 last = xform_mat @ last 79 partition_list.append(last) 80 81 self._partitions = partition_list 82 83 def update(self): 84 self._set_partitions() 85 self._composite = np.concatenate(self.partitions, axis=1) 86 self.hash = md5(self._composition.tobytes()).hexdigest() 87 88 @property 89 def xform_matrix(self) -> 'ndarray': 90 """ 91 Returns the transformation matrix. 92 93 Returns: 94 ndarray: The transformation matrix. 95 """ 96 97 return self._xform_matrix 98 99 @xform_matrix.setter 100 def xform_matrix(self, value: 'ndarray'): 101 if not isinstance(value, np.ndarray): 102 raise ValueError("xform_matrix must be a numpy array") 103 self._xform_matrix = value 104 self._update() 105 106 @property 107 def partitions(self) -> list: 108 """ 109 Returns the submatrices in the transformation. 110 111 Returns: 112 list: A list of submatrices. 113 """ 114 if self._changed(): 115 self.update() 116 117 return self._partitions 118 119 @partitions.setter 120 def partitions(self, value: list): 121 raise AttributeError(("Cannot set partitions directly. " 122 "Use the update method to update the partitions.")) 123 124 @property 125 def composite(self) -> 'ndarray': 126 """ 127 Returns the compound transformation matrix. 128 129 Returns: 130 ndarray: The compound transformation matrix. 131 """ 132 if self._changed(): 133 self.update() 134 135 return self._composite 136 137 @composite.setter 138 def composite(self, value: 'ndarray'): 139 raise AttributeError(("Cannot set composition directly. " 140 "Use the update method to update the composition.")) 141 142 def copy(self) -> 'Tranform': 143 """ 144 Creates a copy of the Transform instance. 145 146 Returns: 147 Transform: A new Transform instance with the same attributes. 148 """ 149 return Transform(self.xform_matrix.copy(), self.reps) 150 151@dataclass 152class Transformation: 153 """ 154 A class representing a transformation that can be composite or not. 155 156 Attributes: 157 transforms (list): A list of Transform instances representing the transformations. 158 """ 159 160 components: list=None 161 162 def __post_init__(self): 163 self.type = Types.TRANSFORMATION 164 self.subtype = Types.TRANSFORMATION 165 if self.components is None: 166 self.components = [] 167 common_properties(self, graphics_object=False, id_only=True) 168 169 @property 170 def partitions(self) -> list: 171 """ 172 Returns the submatrices in the transformation. 173 174 Returns: 175 list of ndarrays. 176 """ 177 if len(self.components) == 0: 178 return [identity_matrix()] 179 elif len(self.components) == 1: 180 partitions = [identity_matrix(), self.components[0].xform_matrix] 181 else: 182 partitions = [] 183 for component in self.components: 184 partitions.extend(component.partitions) 185 186 return partitions 187 188 @property 189 def composite(self) -> 'ndarray': 190 """ 191 Returns the compound transformation matrix. 192 193 Returns: 194 ndarray: The compound transformation matrix. 195 """ 196 if len(self.components) == 0: 197 return identity_matrix() 198 matrices = [] 199 for component in self.components: 200 matrices.append(component.partitions) 201 res = [] 202 if len(matrices) == 1: 203 if len(matrices[0]) == 1: 204 return matrices[0][0] 205 else: 206 return np.concatenate(matrices[0], axis=1) 207 else: 208 for mats in product(*matrices): 209 res.append(np.linalg.multi_dot(mats)) 210 211 return np.concatenate(res, axis=1) 212 213 @composite.setter 214 def composite(self, value: 'ndarray'): 215 raise AttributeError(("Cannot set composition directly. " 216 "Use the update method to update the composition.")) 217 218 219 def copy(self) -> 'Transformation': 220 """ 221 Creates a copy of the Transform instance. 222 223 Returns: 224 Transform: A new Transform instance with the same components. 225 """ 226 return Transformation([component.copy() for component in self.components]) 227 228class Pattern(Batch, StyleMixin): 229 """ 230 A class representing a pattern of a shape or batch object. 231 232 Attributes: 233 kernel (Shape/Batch): The repeated form. 234 transformation: A Transformation object. 235 """ 236 237 def __init__(self, kernel: Union[Shape, Batch]=None, transformation:Transformation=None, **kwargs): 238 """ 239 Initializes the Pattern instance with a pattern and its count. 240 241 Args: 242 kernel (Shape/Batch): The repeated form of the pattern. 243 transformation (Transformation): The transformation applied to the pattern. 244 **kwargs: Additional keyword arguments. 245 """ 246 self.__dict__["style"] = ShapeStyle() 247 self.__dict__["_style_map"] = shape_style_map 248 self._set_aliases() 249 self.kernel = kernel 250 if transformation is None: 251 transformation = Transformation() 252 253 self.transformation = transformation 254 super().__init__(**kwargs) 255 self.subtype = Types.PATTERN 256 common_properties(self) 257 258 valid_args = shape_args 259 validate_args(kwargs, valid_args) 260 261 @property 262 def closed(self) -> bool: 263 """ 264 Returns True if the pattern is closed. 265 266 Returns: 267 bool: True if the pattern is closed, False otherwise. 268 """ 269 return self.kernel.closed 270 271 @closed.setter 272 def closed(self, value: bool): 273 """ 274 Sets the closed property of the pattern. 275 276 Args: 277 value (bool): True to set the pattern as closed, False otherwise. 278 """ 279 self.kernel.closed = value 280 281 @property 282 def composite(self) -> 'ndarray': 283 return self.transformation.composite 284 285 def __bool__(self): 286 return bool(self.kernel) 287 288 def get_all_vertices(self) -> 'ndarray': 289 290 return self.kernel.final_coords @ self.composite 291 292 @property 293 def b_box(self) -> BoundingBox: 294 """ 295 Returns the bounding box of the pattern. 296 297 Returns: 298 BoundingBox: The bounding box of the pattern. 299 """ 300 vertices = self.get_all_vertices() 301 verts=np.hsplit(vertices, self.count) 302 res = [] 303 for x in verts: 304 pass # comeback here later!!!!!! 305 306 return bounding_box(vertices) 307 308 def get_vertices_list(self) -> list: 309 """ 310 Returns the submatrices of the transformation. 311 312 Returns: 313 list: A list of submatrices. 314 """ 315 return np.hsplit(self.get_all_vertices(), self.count) 316 317 def get_shapes(self) -> Batch: 318 """ 319 Expands the pattern into a batch of shapes. 320 321 Returns: 322 Batch: A new Batch instance with the expanded shapes. 323 """ 324 vertices_list = self.get_vertices_list() 325 res = Batch() 326 for vertices in vertices_list: 327 res.append(Shape(vertices)) 328 329 return res 330 331 @property 332 def count(self): 333 """ 334 Returns the number of occurrences of the pattern. 335 336 Returns: 337 int: The total number of forms in the pattern. 338 """ 339 340 return prod([comp.reps+1 for comp in self.transformation.components]) 341 342 def copy(self) -> 'Pattern': 343 """ 344 Creates a copy of the Pattern instance. 345 346 Returns: 347 Pattern: A new Pattern instance with the same attributes. 348 """ 349 kernel = None 350 if self.kernel is not None: 351 kernel = self.kernel.copy() 352 353 transformation = None 354 if self.transformation is not None: 355 transformation = self.transformation.copy() 356 357 pattern = Pattern(kernel, transformation) 358 for attrib in shape_style_map: 359 setattr(pattern, attrib, getattr(self, attrib)) 360 return pattern 361 362 def translate(self, dx: float = 0, dy: float = 0, reps: int = 0) -> Self: 363 """ 364 Translates the object by dx and dy. 365 366 Args: 367 dx (float): The translation distance along the x-axis. 368 dy (float): The translation distance along the y-axis. 369 reps (int, optional): The number of repetitions. Defaults to 0. 370 371 Returns: 372 Self: The transformed object. 373 """ 374 375 component = Transform(translation_matrix(dx, dy), reps) 376 self.transformation.components.append(component) 377 378 return self 379 380 def rotate(self, angle: float, about: Point = (0, 0), reps: int = 0) -> Self: 381 """ 382 Rotates the object by the given angle (in radians) about the given point. 383 384 Args: 385 angle (float): The rotation angle in radians. 386 about (Point, optional): The point to rotate about. Defaults to (0, 0). 387 reps (int, optional): The number of repetitions. Defaults to 0. 388 389 Returns: 390 Self: The rotated object. 391 """ 392 component = Transform(rotation_matrix(angle, about), reps) 393 self.transformation.components.append(component) 394 395 return self 396 397 def mirror(self, about: Union[Line, Point], reps: int = 0) -> Self: 398 """ 399 Mirrors the object about the given line or point. 400 401 Args: 402 about (Union[Line, Point]): The line or point to mirror about. 403 reps (int, optional): The number of repetitions. Defaults to 0. 404 405 Returns: 406 Self: The mirrored object. 407 """ 408 component = Transform(mirror_matrix(about), reps) 409 self.transformation.components.append(component) 410 411 return self 412 413 def glide(self, glide_line: Line, glide_dist: float, reps: int = 0) -> Self: 414 """ 415 Glides (first mirror then translate) the object along the given line 416 by the given glide_dist. 417 418 Args: 419 glide_line (Line): The line to glide along. 420 glide_dist (float): The distance to glide. 421 reps (int, optional): The number of repetitions. Defaults to 0. 422 423 Returns: 424 Self: The glided object. 425 """ 426 component = Transform(glide_matrix(glide_line, glide_dist), reps) 427 self.transformation.components.append(component) 428 429 return self 430 431 def scale( 432 self, 433 scale_x: float, 434 scale_y: Union[float, None] = None, 435 about: Point = (0, 0), 436 reps: int = 0, 437 ) -> Self: 438 """ 439 Scales the object by the given scale factors about the given point. 440 441 Args: 442 scale_x (float): The scale factor in the x direction. 443 scale_y (float, optional): The scale factor in the y direction. Defaults to None. 444 about (Point, optional): The point to scale about. Defaults to (0, 0). 445 reps (int, optional): The number of repetitions. Defaults to 0. 446 447 Returns: 448 Self: The scaled object. 449 """ 450 if scale_y is None: 451 scale_y = scale_x 452 component = Transform(scale_in_place_matrix(scale_x, scale_y, about), reps) 453 self.transformation.components.append(component) 454 455 return self 456 457 def shear(self, theta_x: float, theta_y: float, reps: int = 0) -> Self: 458 """ 459 Shears the object by the given angles. 460 461 Args: 462 theta_x (float): The shear angle in the x direction. 463 theta_y (float): The shear angle in the y direction. 464 reps (int, optional): The number of repetitions. Defaults to 0. 465 466 Returns: 467 Self: The sheared object. 468 """ 469 component = Transform(shear_matrix(theta_x, theta_y), reps) 470 self.transformation.components.append(component) 471 472 return self 473 474 def transform(self, transform_matrix: 'ndarray', reps: int = 0) -> Self: 475 """ 476 Transforms the pattern by the given transformation matrix. 477 478 Args: 479 transform_matrix (ndarray): The transformation matrix. 480 reps (int, optional): The number of repetitions. Defaults to 0. 481 482 Returns: 483 Self: The transformed pattern. 484 """ 485 return self._update(transform_matrix, reps=reps) 486 487 def move_to(self, pos: Point, anchor: Anchor = Anchor.CENTER) -> Self: 488 """ 489 Moves the object to the given position by using its center point. 490 491 Args: 492 pos (Point): The position to move to. 493 anchor (Anchor, optional): The anchor point. Defaults to Anchor.CENTER. 494 495 Returns: 496 Self: The moved object. 497 """ 498 x, y = pos[:2] 499 anchor = get_enum_value(Anchor, anchor) 500 x1, y1 = getattr(self.b_box, anchor) 501 component = Transform(translation_matrix(x - x1, y - y1), reps=0) 502 self.transformation.components.append(component) 503 504 return self
21@dataclass 22class Transform: 23 """ 24 A class representing a single transformation. 25 Used in the Transformation class to represent a transformation matrix and its repetitions. 26 27 Attributes: 28 xform_matrix (ndarray): The transformation matrix. 29 reps (int): The number of repetitions of the transformation. 30 """ 31 32 xform_matrix: 'ndarray' 33 _reps: int = 0 34 35 def __post_init__(self): 36 self.type = Types.TRANSFORM 37 self.subtype = Types.TRANSFORM 38 common_properties(self, graphics_object=False, id_only=True) 39 self.__dict__['_xform_matrix'] = self.xform_matrix 40 # self.__dict__['_reps'] = self.reps 41 self._update() 42 43 def _update(self): 44 self.hash = md5(self.xform_matrix.tobytes()).hexdigest() 45 self._set_partitions() 46 self._composite = np.concatenate(self.partitions, axis=1) 47 48 @property 49 def reps(self) -> int: 50 return self._reps 51 52 @reps.setter 53 def reps(self, value: int): 54 if value < 0: 55 raise ValueError("x cannot be negative") 56 self._reps = value 57 58 59 def _changed(self): 60 """ 61 Checks if the transformation matrix has changed. 62 63 Returns: 64 bool: True if the transformation matrix has changed, False otherwise. 65 """ 66 return not((self.hash == md5(self.xform_matrix.tobytes()).hexdigest()) and 67 (self.reps == self._reps)) 68 69 def _set_partitions(self): 70 if self.reps == 0: 71 partition_list = [identity_matrix()] 72 elif self.reps == 1: 73 partition_list = [identity_matrix(), self.xform_matrix] 74 else: 75 xform_mat = self.xform_matrix 76 partition_list = [identity_matrix(), xform_mat] 77 last = xform_mat 78 for _ in range(self._reps-1): 79 last = xform_mat @ last 80 partition_list.append(last) 81 82 self._partitions = partition_list 83 84 def update(self): 85 self._set_partitions() 86 self._composite = np.concatenate(self.partitions, axis=1) 87 self.hash = md5(self._composition.tobytes()).hexdigest() 88 89 @property 90 def xform_matrix(self) -> 'ndarray': 91 """ 92 Returns the transformation matrix. 93 94 Returns: 95 ndarray: The transformation matrix. 96 """ 97 98 return self._xform_matrix 99 100 @xform_matrix.setter 101 def xform_matrix(self, value: 'ndarray'): 102 if not isinstance(value, np.ndarray): 103 raise ValueError("xform_matrix must be a numpy array") 104 self._xform_matrix = value 105 self._update() 106 107 @property 108 def partitions(self) -> list: 109 """ 110 Returns the submatrices in the transformation. 111 112 Returns: 113 list: A list of submatrices. 114 """ 115 if self._changed(): 116 self.update() 117 118 return self._partitions 119 120 @partitions.setter 121 def partitions(self, value: list): 122 raise AttributeError(("Cannot set partitions directly. " 123 "Use the update method to update the partitions.")) 124 125 @property 126 def composite(self) -> 'ndarray': 127 """ 128 Returns the compound transformation matrix. 129 130 Returns: 131 ndarray: The compound transformation matrix. 132 """ 133 if self._changed(): 134 self.update() 135 136 return self._composite 137 138 @composite.setter 139 def composite(self, value: 'ndarray'): 140 raise AttributeError(("Cannot set composition directly. " 141 "Use the update method to update the composition.")) 142 143 def copy(self) -> 'Tranform': 144 """ 145 Creates a copy of the Transform instance. 146 147 Returns: 148 Transform: A new Transform instance with the same attributes. 149 """ 150 return Transform(self.xform_matrix.copy(), self.reps)
A class representing a single transformation. Used in the Transformation class to represent a transformation matrix and its repetitions.
Attributes:
- xform_matrix (ndarray): The transformation matrix.
- reps (int): The number of repetitions of the transformation.
89 @property 90 def xform_matrix(self) -> 'ndarray': 91 """ 92 Returns the transformation matrix. 93 94 Returns: 95 ndarray: The transformation matrix. 96 """ 97 98 return self._xform_matrix
Returns the transformation matrix.
Returns:
ndarray: The transformation matrix.
107 @property 108 def partitions(self) -> list: 109 """ 110 Returns the submatrices in the transformation. 111 112 Returns: 113 list: A list of submatrices. 114 """ 115 if self._changed(): 116 self.update() 117 118 return self._partitions
Returns the submatrices in the transformation.
Returns:
list: A list of submatrices.
125 @property 126 def composite(self) -> 'ndarray': 127 """ 128 Returns the compound transformation matrix. 129 130 Returns: 131 ndarray: The compound transformation matrix. 132 """ 133 if self._changed(): 134 self.update() 135 136 return self._composite
Returns the compound transformation matrix.
Returns:
ndarray: The compound transformation matrix.
143 def copy(self) -> 'Tranform': 144 """ 145 Creates a copy of the Transform instance. 146 147 Returns: 148 Transform: A new Transform instance with the same attributes. 149 """ 150 return Transform(self.xform_matrix.copy(), self.reps)
Creates a copy of the Transform instance.
Returns:
Transform: A new Transform instance with the same attributes.
152@dataclass 153class Transformation: 154 """ 155 A class representing a transformation that can be composite or not. 156 157 Attributes: 158 transforms (list): A list of Transform instances representing the transformations. 159 """ 160 161 components: list=None 162 163 def __post_init__(self): 164 self.type = Types.TRANSFORMATION 165 self.subtype = Types.TRANSFORMATION 166 if self.components is None: 167 self.components = [] 168 common_properties(self, graphics_object=False, id_only=True) 169 170 @property 171 def partitions(self) -> list: 172 """ 173 Returns the submatrices in the transformation. 174 175 Returns: 176 list of ndarrays. 177 """ 178 if len(self.components) == 0: 179 return [identity_matrix()] 180 elif len(self.components) == 1: 181 partitions = [identity_matrix(), self.components[0].xform_matrix] 182 else: 183 partitions = [] 184 for component in self.components: 185 partitions.extend(component.partitions) 186 187 return partitions 188 189 @property 190 def composite(self) -> 'ndarray': 191 """ 192 Returns the compound transformation matrix. 193 194 Returns: 195 ndarray: The compound transformation matrix. 196 """ 197 if len(self.components) == 0: 198 return identity_matrix() 199 matrices = [] 200 for component in self.components: 201 matrices.append(component.partitions) 202 res = [] 203 if len(matrices) == 1: 204 if len(matrices[0]) == 1: 205 return matrices[0][0] 206 else: 207 return np.concatenate(matrices[0], axis=1) 208 else: 209 for mats in product(*matrices): 210 res.append(np.linalg.multi_dot(mats)) 211 212 return np.concatenate(res, axis=1) 213 214 @composite.setter 215 def composite(self, value: 'ndarray'): 216 raise AttributeError(("Cannot set composition directly. " 217 "Use the update method to update the composition.")) 218 219 220 def copy(self) -> 'Transformation': 221 """ 222 Creates a copy of the Transform instance. 223 224 Returns: 225 Transform: A new Transform instance with the same components. 226 """ 227 return Transformation([component.copy() for component in self.components])
A class representing a transformation that can be composite or not.
Attributes:
- transforms (list): A list of Transform instances representing the transformations.
170 @property 171 def partitions(self) -> list: 172 """ 173 Returns the submatrices in the transformation. 174 175 Returns: 176 list of ndarrays. 177 """ 178 if len(self.components) == 0: 179 return [identity_matrix()] 180 elif len(self.components) == 1: 181 partitions = [identity_matrix(), self.components[0].xform_matrix] 182 else: 183 partitions = [] 184 for component in self.components: 185 partitions.extend(component.partitions) 186 187 return partitions
Returns the submatrices in the transformation.
Returns:
list of ndarrays.
189 @property 190 def composite(self) -> 'ndarray': 191 """ 192 Returns the compound transformation matrix. 193 194 Returns: 195 ndarray: The compound transformation matrix. 196 """ 197 if len(self.components) == 0: 198 return identity_matrix() 199 matrices = [] 200 for component in self.components: 201 matrices.append(component.partitions) 202 res = [] 203 if len(matrices) == 1: 204 if len(matrices[0]) == 1: 205 return matrices[0][0] 206 else: 207 return np.concatenate(matrices[0], axis=1) 208 else: 209 for mats in product(*matrices): 210 res.append(np.linalg.multi_dot(mats)) 211 212 return np.concatenate(res, axis=1)
Returns the compound transformation matrix.
Returns:
ndarray: The compound transformation matrix.
220 def copy(self) -> 'Transformation': 221 """ 222 Creates a copy of the Transform instance. 223 224 Returns: 225 Transform: A new Transform instance with the same components. 226 """ 227 return Transformation([component.copy() for component in self.components])
Creates a copy of the Transform instance.
Returns:
Transform: A new Transform instance with the same components.
229class Pattern(Batch, StyleMixin): 230 """ 231 A class representing a pattern of a shape or batch object. 232 233 Attributes: 234 kernel (Shape/Batch): The repeated form. 235 transformation: A Transformation object. 236 """ 237 238 def __init__(self, kernel: Union[Shape, Batch]=None, transformation:Transformation=None, **kwargs): 239 """ 240 Initializes the Pattern instance with a pattern and its count. 241 242 Args: 243 kernel (Shape/Batch): The repeated form of the pattern. 244 transformation (Transformation): The transformation applied to the pattern. 245 **kwargs: Additional keyword arguments. 246 """ 247 self.__dict__["style"] = ShapeStyle() 248 self.__dict__["_style_map"] = shape_style_map 249 self._set_aliases() 250 self.kernel = kernel 251 if transformation is None: 252 transformation = Transformation() 253 254 self.transformation = transformation 255 super().__init__(**kwargs) 256 self.subtype = Types.PATTERN 257 common_properties(self) 258 259 valid_args = shape_args 260 validate_args(kwargs, valid_args) 261 262 @property 263 def closed(self) -> bool: 264 """ 265 Returns True if the pattern is closed. 266 267 Returns: 268 bool: True if the pattern is closed, False otherwise. 269 """ 270 return self.kernel.closed 271 272 @closed.setter 273 def closed(self, value: bool): 274 """ 275 Sets the closed property of the pattern. 276 277 Args: 278 value (bool): True to set the pattern as closed, False otherwise. 279 """ 280 self.kernel.closed = value 281 282 @property 283 def composite(self) -> 'ndarray': 284 return self.transformation.composite 285 286 def __bool__(self): 287 return bool(self.kernel) 288 289 def get_all_vertices(self) -> 'ndarray': 290 291 return self.kernel.final_coords @ self.composite 292 293 @property 294 def b_box(self) -> BoundingBox: 295 """ 296 Returns the bounding box of the pattern. 297 298 Returns: 299 BoundingBox: The bounding box of the pattern. 300 """ 301 vertices = self.get_all_vertices() 302 verts=np.hsplit(vertices, self.count) 303 res = [] 304 for x in verts: 305 pass # comeback here later!!!!!! 306 307 return bounding_box(vertices) 308 309 def get_vertices_list(self) -> list: 310 """ 311 Returns the submatrices of the transformation. 312 313 Returns: 314 list: A list of submatrices. 315 """ 316 return np.hsplit(self.get_all_vertices(), self.count) 317 318 def get_shapes(self) -> Batch: 319 """ 320 Expands the pattern into a batch of shapes. 321 322 Returns: 323 Batch: A new Batch instance with the expanded shapes. 324 """ 325 vertices_list = self.get_vertices_list() 326 res = Batch() 327 for vertices in vertices_list: 328 res.append(Shape(vertices)) 329 330 return res 331 332 @property 333 def count(self): 334 """ 335 Returns the number of occurrences of the pattern. 336 337 Returns: 338 int: The total number of forms in the pattern. 339 """ 340 341 return prod([comp.reps+1 for comp in self.transformation.components]) 342 343 def copy(self) -> 'Pattern': 344 """ 345 Creates a copy of the Pattern instance. 346 347 Returns: 348 Pattern: A new Pattern instance with the same attributes. 349 """ 350 kernel = None 351 if self.kernel is not None: 352 kernel = self.kernel.copy() 353 354 transformation = None 355 if self.transformation is not None: 356 transformation = self.transformation.copy() 357 358 pattern = Pattern(kernel, transformation) 359 for attrib in shape_style_map: 360 setattr(pattern, attrib, getattr(self, attrib)) 361 return pattern 362 363 def translate(self, dx: float = 0, dy: float = 0, reps: int = 0) -> Self: 364 """ 365 Translates the object by dx and dy. 366 367 Args: 368 dx (float): The translation distance along the x-axis. 369 dy (float): The translation distance along the y-axis. 370 reps (int, optional): The number of repetitions. Defaults to 0. 371 372 Returns: 373 Self: The transformed object. 374 """ 375 376 component = Transform(translation_matrix(dx, dy), reps) 377 self.transformation.components.append(component) 378 379 return self 380 381 def rotate(self, angle: float, about: Point = (0, 0), reps: int = 0) -> Self: 382 """ 383 Rotates the object by the given angle (in radians) about the given point. 384 385 Args: 386 angle (float): The rotation angle in radians. 387 about (Point, optional): The point to rotate about. Defaults to (0, 0). 388 reps (int, optional): The number of repetitions. Defaults to 0. 389 390 Returns: 391 Self: The rotated object. 392 """ 393 component = Transform(rotation_matrix(angle, about), reps) 394 self.transformation.components.append(component) 395 396 return self 397 398 def mirror(self, about: Union[Line, Point], reps: int = 0) -> Self: 399 """ 400 Mirrors the object about the given line or point. 401 402 Args: 403 about (Union[Line, Point]): The line or point to mirror about. 404 reps (int, optional): The number of repetitions. Defaults to 0. 405 406 Returns: 407 Self: The mirrored object. 408 """ 409 component = Transform(mirror_matrix(about), reps) 410 self.transformation.components.append(component) 411 412 return self 413 414 def glide(self, glide_line: Line, glide_dist: float, reps: int = 0) -> Self: 415 """ 416 Glides (first mirror then translate) the object along the given line 417 by the given glide_dist. 418 419 Args: 420 glide_line (Line): The line to glide along. 421 glide_dist (float): The distance to glide. 422 reps (int, optional): The number of repetitions. Defaults to 0. 423 424 Returns: 425 Self: The glided object. 426 """ 427 component = Transform(glide_matrix(glide_line, glide_dist), reps) 428 self.transformation.components.append(component) 429 430 return self 431 432 def scale( 433 self, 434 scale_x: float, 435 scale_y: Union[float, None] = None, 436 about: Point = (0, 0), 437 reps: int = 0, 438 ) -> Self: 439 """ 440 Scales the object by the given scale factors about the given point. 441 442 Args: 443 scale_x (float): The scale factor in the x direction. 444 scale_y (float, optional): The scale factor in the y direction. Defaults to None. 445 about (Point, optional): The point to scale about. Defaults to (0, 0). 446 reps (int, optional): The number of repetitions. Defaults to 0. 447 448 Returns: 449 Self: The scaled object. 450 """ 451 if scale_y is None: 452 scale_y = scale_x 453 component = Transform(scale_in_place_matrix(scale_x, scale_y, about), reps) 454 self.transformation.components.append(component) 455 456 return self 457 458 def shear(self, theta_x: float, theta_y: float, reps: int = 0) -> Self: 459 """ 460 Shears the object by the given angles. 461 462 Args: 463 theta_x (float): The shear angle in the x direction. 464 theta_y (float): The shear angle in the y direction. 465 reps (int, optional): The number of repetitions. Defaults to 0. 466 467 Returns: 468 Self: The sheared object. 469 """ 470 component = Transform(shear_matrix(theta_x, theta_y), reps) 471 self.transformation.components.append(component) 472 473 return self 474 475 def transform(self, transform_matrix: 'ndarray', reps: int = 0) -> Self: 476 """ 477 Transforms the pattern by the given transformation matrix. 478 479 Args: 480 transform_matrix (ndarray): The transformation matrix. 481 reps (int, optional): The number of repetitions. Defaults to 0. 482 483 Returns: 484 Self: The transformed pattern. 485 """ 486 return self._update(transform_matrix, reps=reps) 487 488 def move_to(self, pos: Point, anchor: Anchor = Anchor.CENTER) -> Self: 489 """ 490 Moves the object to the given position by using its center point. 491 492 Args: 493 pos (Point): The position to move to. 494 anchor (Anchor, optional): The anchor point. Defaults to Anchor.CENTER. 495 496 Returns: 497 Self: The moved object. 498 """ 499 x, y = pos[:2] 500 anchor = get_enum_value(Anchor, anchor) 501 x1, y1 = getattr(self.b_box, anchor) 502 component = Transform(translation_matrix(x - x1, y - y1), reps=0) 503 self.transformation.components.append(component) 504 505 return self
A class representing a pattern of a shape or batch object.
Attributes:
- kernel (Shape/Batch): The repeated form.
- transformation: A Transformation object.
238 def __init__(self, kernel: Union[Shape, Batch]=None, transformation:Transformation=None, **kwargs): 239 """ 240 Initializes the Pattern instance with a pattern and its count. 241 242 Args: 243 kernel (Shape/Batch): The repeated form of the pattern. 244 transformation (Transformation): The transformation applied to the pattern. 245 **kwargs: Additional keyword arguments. 246 """ 247 self.__dict__["style"] = ShapeStyle() 248 self.__dict__["_style_map"] = shape_style_map 249 self._set_aliases() 250 self.kernel = kernel 251 if transformation is None: 252 transformation = Transformation() 253 254 self.transformation = transformation 255 super().__init__(**kwargs) 256 self.subtype = Types.PATTERN 257 common_properties(self) 258 259 valid_args = shape_args 260 validate_args(kwargs, valid_args)
Initializes the Pattern instance with a pattern and its count.
Arguments:
- kernel (Shape/Batch): The repeated form of the pattern.
- transformation (Transformation): The transformation applied to the pattern.
- **kwargs: Additional keyword arguments.
262 @property 263 def closed(self) -> bool: 264 """ 265 Returns True if the pattern is closed. 266 267 Returns: 268 bool: True if the pattern is closed, False otherwise. 269 """ 270 return self.kernel.closed
Returns True if the pattern is closed.
Returns:
bool: True if the pattern is closed, False otherwise.
293 @property 294 def b_box(self) -> BoundingBox: 295 """ 296 Returns the bounding box of the pattern. 297 298 Returns: 299 BoundingBox: The bounding box of the pattern. 300 """ 301 vertices = self.get_all_vertices() 302 verts=np.hsplit(vertices, self.count) 303 res = [] 304 for x in verts: 305 pass # comeback here later!!!!!! 306 307 return bounding_box(vertices)
Returns the bounding box of the pattern.
Returns:
BoundingBox: The bounding box of the pattern.
309 def get_vertices_list(self) -> list: 310 """ 311 Returns the submatrices of the transformation. 312 313 Returns: 314 list: A list of submatrices. 315 """ 316 return np.hsplit(self.get_all_vertices(), self.count)
Returns the submatrices of the transformation.
Returns:
list: A list of submatrices.
318 def get_shapes(self) -> Batch: 319 """ 320 Expands the pattern into a batch of shapes. 321 322 Returns: 323 Batch: A new Batch instance with the expanded shapes. 324 """ 325 vertices_list = self.get_vertices_list() 326 res = Batch() 327 for vertices in vertices_list: 328 res.append(Shape(vertices)) 329 330 return res
Expands the pattern into a batch of shapes.
Returns:
Batch: A new Batch instance with the expanded shapes.
332 @property 333 def count(self): 334 """ 335 Returns the number of occurrences of the pattern. 336 337 Returns: 338 int: The total number of forms in the pattern. 339 """ 340 341 return prod([comp.reps+1 for comp in self.transformation.components])
Returns the number of occurrences of the pattern.
Returns:
int: The total number of forms in the pattern.
343 def copy(self) -> 'Pattern': 344 """ 345 Creates a copy of the Pattern instance. 346 347 Returns: 348 Pattern: A new Pattern instance with the same attributes. 349 """ 350 kernel = None 351 if self.kernel is not None: 352 kernel = self.kernel.copy() 353 354 transformation = None 355 if self.transformation is not None: 356 transformation = self.transformation.copy() 357 358 pattern = Pattern(kernel, transformation) 359 for attrib in shape_style_map: 360 setattr(pattern, attrib, getattr(self, attrib)) 361 return pattern
Creates a copy of the Pattern instance.
Returns:
Pattern: A new Pattern instance with the same attributes.
363 def translate(self, dx: float = 0, dy: float = 0, reps: int = 0) -> Self: 364 """ 365 Translates the object by dx and dy. 366 367 Args: 368 dx (float): The translation distance along the x-axis. 369 dy (float): The translation distance along the y-axis. 370 reps (int, optional): The number of repetitions. Defaults to 0. 371 372 Returns: 373 Self: The transformed object. 374 """ 375 376 component = Transform(translation_matrix(dx, dy), reps) 377 self.transformation.components.append(component) 378 379 return self
Translates the object by dx and dy.
Arguments:
- dx (float): The translation distance along the x-axis.
- dy (float): The translation distance along the y-axis.
- reps (int, optional): The number of repetitions. Defaults to 0.
Returns:
Self: The transformed object.
381 def rotate(self, angle: float, about: Point = (0, 0), reps: int = 0) -> Self: 382 """ 383 Rotates the object by the given angle (in radians) about the given point. 384 385 Args: 386 angle (float): The rotation angle in radians. 387 about (Point, optional): The point to rotate about. Defaults to (0, 0). 388 reps (int, optional): The number of repetitions. Defaults to 0. 389 390 Returns: 391 Self: The rotated object. 392 """ 393 component = Transform(rotation_matrix(angle, about), reps) 394 self.transformation.components.append(component) 395 396 return self
Rotates the object by the given angle (in radians) about the given point.
Arguments:
- angle (float): The rotation angle in radians.
- about (Point, optional): The point to rotate about. Defaults to (0, 0).
- reps (int, optional): The number of repetitions. Defaults to 0.
Returns:
Self: The rotated object.
398 def mirror(self, about: Union[Line, Point], reps: int = 0) -> Self: 399 """ 400 Mirrors the object about the given line or point. 401 402 Args: 403 about (Union[Line, Point]): The line or point to mirror about. 404 reps (int, optional): The number of repetitions. Defaults to 0. 405 406 Returns: 407 Self: The mirrored object. 408 """ 409 component = Transform(mirror_matrix(about), reps) 410 self.transformation.components.append(component) 411 412 return self
Mirrors the object about the given line or point.
Arguments:
- about (Union[Line, Point]): The line or point to mirror about.
- reps (int, optional): The number of repetitions. Defaults to 0.
Returns:
Self: The mirrored object.
414 def glide(self, glide_line: Line, glide_dist: float, reps: int = 0) -> Self: 415 """ 416 Glides (first mirror then translate) the object along the given line 417 by the given glide_dist. 418 419 Args: 420 glide_line (Line): The line to glide along. 421 glide_dist (float): The distance to glide. 422 reps (int, optional): The number of repetitions. Defaults to 0. 423 424 Returns: 425 Self: The glided object. 426 """ 427 component = Transform(glide_matrix(glide_line, glide_dist), reps) 428 self.transformation.components.append(component) 429 430 return self
Glides (first mirror then translate) the object along the given line by the given glide_dist.
Arguments:
- glide_line (Line): The line to glide along.
- glide_dist (float): The distance to glide.
- reps (int, optional): The number of repetitions. Defaults to 0.
Returns:
Self: The glided object.
432 def scale( 433 self, 434 scale_x: float, 435 scale_y: Union[float, None] = None, 436 about: Point = (0, 0), 437 reps: int = 0, 438 ) -> Self: 439 """ 440 Scales the object by the given scale factors about the given point. 441 442 Args: 443 scale_x (float): The scale factor in the x direction. 444 scale_y (float, optional): The scale factor in the y direction. Defaults to None. 445 about (Point, optional): The point to scale about. Defaults to (0, 0). 446 reps (int, optional): The number of repetitions. Defaults to 0. 447 448 Returns: 449 Self: The scaled object. 450 """ 451 if scale_y is None: 452 scale_y = scale_x 453 component = Transform(scale_in_place_matrix(scale_x, scale_y, about), reps) 454 self.transformation.components.append(component) 455 456 return self
Scales the object by the given scale factors about the given point.
Arguments:
- scale_x (float): The scale factor in the x direction.
- scale_y (float, optional): The scale factor in the y direction. Defaults to None.
- about (Point, optional): The point to scale about. Defaults to (0, 0).
- reps (int, optional): The number of repetitions. Defaults to 0.
Returns:
Self: The scaled object.
458 def shear(self, theta_x: float, theta_y: float, reps: int = 0) -> Self: 459 """ 460 Shears the object by the given angles. 461 462 Args: 463 theta_x (float): The shear angle in the x direction. 464 theta_y (float): The shear angle in the y direction. 465 reps (int, optional): The number of repetitions. Defaults to 0. 466 467 Returns: 468 Self: The sheared object. 469 """ 470 component = Transform(shear_matrix(theta_x, theta_y), reps) 471 self.transformation.components.append(component) 472 473 return self
Shears the object by the given angles.
Arguments:
- theta_x (float): The shear angle in the x direction.
- theta_y (float): The shear angle in the y direction.
- reps (int, optional): The number of repetitions. Defaults to 0.
Returns:
Self: The sheared object.
475 def transform(self, transform_matrix: 'ndarray', reps: int = 0) -> Self: 476 """ 477 Transforms the pattern by the given transformation matrix. 478 479 Args: 480 transform_matrix (ndarray): The transformation matrix. 481 reps (int, optional): The number of repetitions. Defaults to 0. 482 483 Returns: 484 Self: The transformed pattern. 485 """ 486 return self._update(transform_matrix, reps=reps)
Transforms the pattern by the given transformation matrix.
Arguments:
- transform_matrix (ndarray): The transformation matrix.
- reps (int, optional): The number of repetitions. Defaults to 0.
Returns:
Self: The transformed pattern.
488 def move_to(self, pos: Point, anchor: Anchor = Anchor.CENTER) -> Self: 489 """ 490 Moves the object to the given position by using its center point. 491 492 Args: 493 pos (Point): The position to move to. 494 anchor (Anchor, optional): The anchor point. Defaults to Anchor.CENTER. 495 496 Returns: 497 Self: The moved object. 498 """ 499 x, y = pos[:2] 500 anchor = get_enum_value(Anchor, anchor) 501 x1, y1 = getattr(self.b_box, anchor) 502 component = Transform(translation_matrix(x - x1, y - y1), reps=0) 503 self.transformation.components.append(component) 504 505 return self
Moves the object to the given position by using its center point.
Arguments:
- pos (Point): The position to move to.
- anchor (Anchor, optional): The anchor point. Defaults to Anchor.CENTER.
Returns:
Self: The moved object.
Inherited Members
- simetri.graphics.batch.Batch
- type
- 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