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
@dataclass
class Transform:
 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.
Transform(xform_matrix: 'ndarray' = <property object>, _reps: int = 0)
xform_matrix: 'ndarray'
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.

reps: int
48    @property
49    def reps(self) -> int:
50        return self._reps
def update(self):
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()
partitions: list
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.

composite: 'ndarray'
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.

def copy(self) -> 'Tranform':
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.

@dataclass
class Transformation:
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.
Transformation(components: list = None)
components: list = None
partitions: list
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.

composite: 'ndarray'
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.

def copy(self) -> Transformation:
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.
Pattern( kernel: Union[simetri.graphics.shape.Shape, simetri.graphics.batch.Batch] = None, transformation: Transformation = None, **kwargs)
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.
kernel
transformation
subtype
closed: bool
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.

composite: 'ndarray'
282    @property
283    def composite(self) -> 'ndarray':
284        return self.transformation.composite
def get_all_vertices(self) -> 'ndarray':
289    def get_all_vertices(self) -> 'ndarray':
290
291        return self.kernel.final_coords @ self.composite
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.

def get_vertices_list(self) -> list:
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.

def get_shapes(self) -> simetri.graphics.batch.Batch:
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.

count
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.

def copy(self) -> 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.

def translate( self, dx: float = 0, dy: float = 0, reps: int = 0) -> typing_extensions.Self:
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.

def rotate( self, angle: float, about: Sequence[float] = (0, 0), reps: int = 0) -> typing_extensions.Self:
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.

def mirror( self, about: Union[Sequence[Sequence], Sequence[float]], reps: int = 0) -> typing_extensions.Self:
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.

def glide( self, glide_line: Sequence[Sequence], glide_dist: float, reps: int = 0) -> typing_extensions.Self:
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.

def scale( self, scale_x: float, scale_y: Optional[float] = None, about: Sequence[float] = (0, 0), reps: int = 0) -> typing_extensions.Self:
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.

def shear( self, theta_x: float, theta_y: float, reps: int = 0) -> typing_extensions.Self:
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.

def transform( self, transform_matrix: 'ndarray', reps: int = 0) -> typing_extensions.Self:
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.

def move_to( self, pos: Sequence[float], anchor: simetri.graphics.all_enums.Anchor = <Anchor.CENTER: 'center'>) -> typing_extensions.Self:
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.