enable per-shape repetitions

This commit is contained in:
Jan Petykiewicz 2020-07-22 21:50:39 -07:00
parent ad6fa88e53
commit 629a6a9ba2
9 changed files with 75 additions and 5 deletions

View File

@ -1,14 +1,16 @@
from typing import List, Tuple, Dict from typing import List, Tuple, Dict, Optional
import copy import copy
import numpy import numpy
from numpy import pi from numpy import pi
from .repetition import Repetition
from .error import PatternError, PatternLockedError from .error import PatternError, PatternLockedError
from .utils import is_scalar, vector2, rotation_matrix_2d, layer_t, AutoSlots from .utils import is_scalar, vector2, rotation_matrix_2d, layer_t, AutoSlots
from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, LockableImpl from .traits import PositionableImpl, LayerableImpl, Copyable, Pivotable, LockableImpl, RepeatableImpl
class Label(PositionableImpl, LayerableImpl, LockableImpl, Pivotable, Copyable, metaclass=AutoSlots): class Label(PositionableImpl, LayerableImpl, LockableImpl, RepeatableImpl,
Pivotable, Copyable, metaclass=AutoSlots):
""" """
A text annotation with a position and layer (but no size; it is not drawn) A text annotation with a position and layer (but no size; it is not drawn)
""" """
@ -39,18 +41,21 @@ class Label(PositionableImpl, LayerableImpl, LockableImpl, Pivotable, Copyable,
string: str, string: str,
offset: vector2 = (0.0, 0.0), offset: vector2 = (0.0, 0.0),
layer: layer_t = 0, layer: layer_t = 0,
repetition: Optional[Repetition] = None,
locked: bool = False): locked: bool = False):
object.__setattr__(self, 'locked', False) object.__setattr__(self, 'locked', False)
self.identifier = () self.identifier = ()
self.string = string self.string = string
self.offset = numpy.array(offset, dtype=float, copy=True) self.offset = numpy.array(offset, dtype=float, copy=True)
self.layer = layer self.layer = layer
self.repetition = repetition
self.locked = locked self.locked = locked
def __copy__(self) -> 'Label': def __copy__(self) -> 'Label':
return Label(string=self.string, return Label(string=self.string,
offset=self.offset.copy(), offset=self.offset.copy(),
layer=self.layer, layer=self.layer,
repetition=self.repetition,
locked=self.locked) locked=self.locked)
def __deepcopy__(self, memo: Dict = None) -> 'Label': def __deepcopy__(self, memo: Dict = None) -> 'Label':

View File

@ -575,6 +575,52 @@ class Pattern:
self.append(p) self.append(p)
return self return self
def wrap_repeated_shapes(self,
name_func: Callable[['Pattern', Union[Shape, Label]], str] = lambda p, s: '_repetition',
recursive: bool = True,
) -> 'Pattern':
"""
Wraps all shapes and labels with a non-`None` `repetition` attribute
into a `SubPattern`/`Pattern` combination, and applies the `repetition`
to each `SubPattern` instead of its contained shape.
Args:
name_func: Function f(this_pattern, shape) which generates a name for the
wrapping pattern. Default always returns '_repetition'.
recursive: If `True`, this function is also applied to all referenced patterns
recursively. Default `True`.
Returns:
self
"""
def do_wrap(pat: Optional[Pattern]) -> Optional[Pattern]:
if pat is None:
return pat
new_subpatterns = []
for shape in pat.shapes:
if shape.repetition is None:
continue
new_subpatterns.append(SubPattern(Pattern(name_func(pat, shape), shapes=[shape])))
shape.repetition = None
for label in self.labels:
if label.repetition is None:
continue
new_subpatterns.append(SubPattern(Pattern(name_func(pat, shape), labels=[label])))
label.repetition = None
pat.subpatterns += new_subpatterns
return pat
if recursive:
self.apply(do_wrap)
else:
do_wrap(self)
return self
def translate_elements(self, offset: vector2) -> 'Pattern': def translate_elements(self, offset: vector2) -> 'Pattern':
""" """
Translates all shapes, label, and subpatterns by the given offset. Translates all shapes, label, and subpatterns by the given offset.

View File

@ -6,6 +6,7 @@ from numpy import pi
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
from .. import PatternError from .. import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, vector2, layer_t, AutoSlots from ..utils import is_scalar, vector2, layer_t, AutoSlots
@ -158,6 +159,7 @@ class Arc(Shape, metaclass=AutoSlots):
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
dose: float = 1.0, dose: float = 1.0,
repetition: Optional[Repetition] = None,
locked: bool = False): locked: bool = False):
object.__setattr__(self, 'locked', False) object.__setattr__(self, 'locked', False)
self.identifier = () self.identifier = ()
@ -171,6 +173,7 @@ class Arc(Shape, metaclass=AutoSlots):
self.dose = dose self.dose = dose
self.poly_num_points = poly_num_points self.poly_num_points = poly_num_points
self.poly_max_arclen = poly_max_arclen self.poly_max_arclen = poly_max_arclen
self.repetition = repetition
self.locked = locked self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Arc': def __deepcopy__(self, memo: Dict = None) -> 'Arc':

View File

@ -5,6 +5,7 @@ from numpy import pi
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
from .. import PatternError from .. import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, vector2, layer_t, AutoSlots from ..utils import is_scalar, vector2, layer_t, AutoSlots
@ -46,6 +47,7 @@ class Circle(Shape, metaclass=AutoSlots):
offset: vector2 = (0.0, 0.0), offset: vector2 = (0.0, 0.0),
layer: layer_t = 0, layer: layer_t = 0,
dose: float = 1.0, dose: float = 1.0,
repetition: Optional[Repetition] = None,
locked: bool = False): locked: bool = False):
object.__setattr__(self, 'locked', False) object.__setattr__(self, 'locked', False)
self.identifier = () self.identifier = ()
@ -55,6 +57,7 @@ class Circle(Shape, metaclass=AutoSlots):
self.radius = radius self.radius = radius
self.poly_num_points = poly_num_points self.poly_num_points = poly_num_points
self.poly_max_arclen = poly_max_arclen self.poly_max_arclen = poly_max_arclen
self.repetition = repetition
self.locked = locked self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Circle': def __deepcopy__(self, memo: Dict = None) -> 'Circle':

View File

@ -6,6 +6,7 @@ from numpy import pi
from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS from . import Shape, Polygon, normalized_shape_tuple, DEFAULT_POLY_NUM_POINTS
from .. import PatternError from .. import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
@ -93,6 +94,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
dose: float = 1.0, dose: float = 1.0,
repetition: Optional[Repetition] = None,
locked: bool = False): locked: bool = False):
object.__setattr__(self, 'locked', False) object.__setattr__(self, 'locked', False)
self.identifier = () self.identifier = ()
@ -104,6 +106,7 @@ class Ellipse(Shape, metaclass=AutoSlots):
self.dose = dose self.dose = dose
self.poly_num_points = poly_num_points self.poly_num_points = poly_num_points
self.poly_max_arclen = poly_max_arclen self.poly_max_arclen = poly_max_arclen
self.repetition = repetition
self.locked = locked self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Ellipse': def __deepcopy__(self, memo: Dict = None) -> 'Ellipse':

View File

@ -6,6 +6,7 @@ from numpy import pi, inf
from . import Shape, normalized_shape_tuple, Polygon, Circle from . import Shape, normalized_shape_tuple, Polygon, Circle
from .. import PatternError from .. import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
from ..utils import remove_colinear_vertices, remove_duplicate_vertices from ..utils import remove_colinear_vertices, remove_duplicate_vertices
@ -147,6 +148,7 @@ class Path(Shape, metaclass=AutoSlots):
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
dose: float = 1.0, dose: float = 1.0,
repetition: Optional[Repetition] = None,
locked: bool = False, locked: bool = False,
): ):
object.__setattr__(self, 'locked', False) object.__setattr__(self, 'locked', False)
@ -163,6 +165,7 @@ class Path(Shape, metaclass=AutoSlots):
self.cap_extensions = cap_extensions self.cap_extensions = cap_extensions
self.rotate(rotation) self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do] [self.mirror(a) for a, do in enumerate(mirrored) if do]
self.repetition = repetition
self.locked = locked self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Path': def __deepcopy__(self, memo: Dict = None) -> 'Path':

View File

@ -5,6 +5,7 @@ from numpy import pi
from . import Shape, normalized_shape_tuple from . import Shape, normalized_shape_tuple
from .. import PatternError from .. import PatternError
from ..repetition import Repetition
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t, AutoSlots
from ..utils import remove_colinear_vertices, remove_duplicate_vertices from ..utils import remove_colinear_vertices, remove_duplicate_vertices
@ -75,6 +76,7 @@ class Polygon(Shape, metaclass=AutoSlots):
mirrored: Sequence[bool] = (False, False), mirrored: Sequence[bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
dose: float = 1.0, dose: float = 1.0,
repetition: Optional[Repetition] = None,
locked: bool = False, locked: bool = False,
): ):
object.__setattr__(self, 'locked', False) object.__setattr__(self, 'locked', False)
@ -85,6 +87,7 @@ class Polygon(Shape, metaclass=AutoSlots):
self.offset = offset self.offset = offset
self.rotate(rotation) self.rotate(rotation)
[self.mirror(a) for a, do in enumerate(mirrored) if do] [self.mirror(a) for a, do in enumerate(mirrored) if do]
self.repetition = repetition
self.locked = locked self.locked = locked
def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon': def __deepcopy__(self, memo: Optional[Dict] = None) -> 'Polygon':

View File

@ -7,7 +7,7 @@ from ..error import PatternError, PatternLockedError
from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t from ..utils import is_scalar, rotation_matrix_2d, vector2, layer_t
from ..traits import (PositionableImpl, LayerableImpl, DoseableImpl, from ..traits import (PositionableImpl, LayerableImpl, DoseableImpl,
Rotatable, Mirrorable, Copyable, Scalable, Rotatable, Mirrorable, Copyable, Scalable,
PivotableImpl, LockableImpl) PivotableImpl, LockableImpl, RepeatableImpl)
if TYPE_CHECKING: if TYPE_CHECKING:
from . import Polygon from . import Polygon
@ -26,7 +26,8 @@ DEFAULT_POLY_NUM_POINTS = 24
T = TypeVar('T', bound='Shape') T = TypeVar('T', bound='Shape')
class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable, Copyable, Scalable, PivotableImpl, LockableImpl, metaclass=ABCMeta): class Shape(PositionableImpl, LayerableImpl, DoseableImpl, Rotatable, Mirrorable, Copyable, Scalable,
PivotableImpl, RepeatableImpl, LockableImpl, metaclass=ABCMeta):
""" """
Abstract class specifying functions common to all shapes. Abstract class specifying functions common to all shapes.
""" """

View File

@ -5,6 +5,7 @@ from numpy import pi, inf
from . import Shape, Polygon, normalized_shape_tuple from . import Shape, Polygon, normalized_shape_tuple
from .. import PatternError from .. import PatternError
from ..repetition import Repetition
from ..traits import RotatableImpl from ..traits import RotatableImpl
from ..utils import is_scalar, vector2, get_bit, normalize_mirror, layer_t, AutoSlots from ..utils import is_scalar, vector2, get_bit, normalize_mirror, layer_t, AutoSlots
@ -65,6 +66,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
mirrored: Tuple[bool, bool] = (False, False), mirrored: Tuple[bool, bool] = (False, False),
layer: layer_t = 0, layer: layer_t = 0,
dose: float = 1.0, dose: float = 1.0,
repetition: Optional[Repetition] = None,
locked: bool = False, locked: bool = False,
): ):
object.__setattr__(self, 'locked', False) object.__setattr__(self, 'locked', False)
@ -77,6 +79,7 @@ class Text(RotatableImpl, Shape, metaclass=AutoSlots):
self.rotation = rotation self.rotation = rotation
self.font_path = font_path self.font_path = font_path
self.mirrored = mirrored self.mirrored = mirrored
self.repetition = repetition
self.locked = locked self.locked = locked
def __deepcopy__(self, memo: Dict = None) -> 'Text': def __deepcopy__(self, memo: Dict = None) -> 'Text':