don't keep track of y-mirroring separately from x
This commit is contained in:
		
							parent
							
								
									9bc8d29b85
								
							
						
					
					
						commit
						91465b7175
					
				@ -17,7 +17,8 @@ def main():
 | 
				
			|||||||
    cell_name = 'ellip_grating'
 | 
					    cell_name = 'ellip_grating'
 | 
				
			||||||
    pat = masque.Pattern()
 | 
					    pat = masque.Pattern()
 | 
				
			||||||
    for rmin in numpy.arange(10, 15, 0.5):
 | 
					    for rmin in numpy.arange(10, 15, 0.5):
 | 
				
			||||||
        pat.shapes.append(Arc(
 | 
					        layer = (0, 0)
 | 
				
			||||||
 | 
					        pat.shapes[layer].append(Arc(
 | 
				
			||||||
            radii=(rmin, rmin),
 | 
					            radii=(rmin, rmin),
 | 
				
			||||||
            width=0.1,
 | 
					            width=0.1,
 | 
				
			||||||
            angles=(0 * -pi/4, pi/4),
 | 
					            angles=(0 * -pi/4, pi/4),
 | 
				
			||||||
@ -35,27 +36,27 @@ def main():
 | 
				
			|||||||
    print(f'\nAdded a copy of {cell_name} as {new_name}')
 | 
					    print(f'\nAdded a copy of {cell_name} as {new_name}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pat3 = Pattern()
 | 
					    pat3 = Pattern()
 | 
				
			||||||
    pat3.refs = [
 | 
					    pat3.refs[cell_name] = [
 | 
				
			||||||
        Ref(cell_name, offset=(1e5, 3e5), annotations={'4': ['Hello I am the base Ref']}),
 | 
					        Ref(offset=(1e5, 3e5), annotations={'4': ['Hello I am the base Ref']}),
 | 
				
			||||||
        Ref(cell_name, offset=(2e5, 3e5), rotation=pi/3),
 | 
					        Ref(offset=(2e5, 3e5), rotation=pi/3),
 | 
				
			||||||
        Ref(cell_name, offset=(3e5, 3e5), rotation=pi/2),
 | 
					        Ref(offset=(3e5, 3e5), rotation=pi/2),
 | 
				
			||||||
        Ref(cell_name, offset=(4e5, 3e5), rotation=pi),
 | 
					        Ref(offset=(4e5, 3e5), rotation=pi),
 | 
				
			||||||
        Ref(cell_name, offset=(5e5, 3e5), rotation=3*pi/2),
 | 
					        Ref(offset=(5e5, 3e5), rotation=3*pi/2),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, False), offset=(1e5, 4e5)),
 | 
					        Ref(mirrored=True, offset=(1e5, 4e5)),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, False), offset=(2e5, 4e5), rotation=pi/3),
 | 
					        Ref(mirrored=True, offset=(2e5, 4e5), rotation=pi/3),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, False), offset=(3e5, 4e5), rotation=pi/2),
 | 
					        Ref(mirrored=True, offset=(3e5, 4e5), rotation=pi/2),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, False), offset=(4e5, 4e5), rotation=pi),
 | 
					        Ref(mirrored=True, offset=(4e5, 4e5), rotation=pi),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, False), offset=(5e5, 4e5), rotation=3*pi/2),
 | 
					        Ref(mirrored=True, offset=(5e5, 4e5), rotation=3*pi/2),
 | 
				
			||||||
        Ref(cell_name, mirrored=(False, True), offset=(1e5, 5e5)),
 | 
					        Ref(offset=(1e5, 5e5)).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, mirrored=(False, True), offset=(2e5, 5e5), rotation=pi/3),
 | 
					        Ref(offset=(2e5, 5e5), rotation=pi/3).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, mirrored=(False, True), offset=(3e5, 5e5), rotation=pi/2),
 | 
					        Ref(offset=(3e5, 5e5), rotation=pi/2).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, mirrored=(False, True), offset=(4e5, 5e5), rotation=pi),
 | 
					        Ref(offset=(4e5, 5e5), rotation=pi).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, mirrored=(False, True), offset=(5e5, 5e5), rotation=3*pi/2),
 | 
					        Ref(offset=(5e5, 5e5), rotation=3*pi/2).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, True), offset=(1e5, 6e5)),
 | 
					        Ref(offset=(1e5, 6e5)).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, True), offset=(2e5, 6e5), rotation=pi/3),
 | 
					        Ref(offset=(2e5, 6e5), rotation=pi/3).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, True), offset=(3e5, 6e5), rotation=pi/2),
 | 
					        Ref(offset=(3e5, 6e5), rotation=pi/2).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, True), offset=(4e5, 6e5), rotation=pi),
 | 
					        Ref(offset=(4e5, 6e5), rotation=pi).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, mirrored=(True, True), offset=(5e5, 6e5), rotation=3*pi/2),
 | 
					        Ref(offset=(5e5, 6e5), rotation=3*pi/2).mirror2d_target(True, True),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lib['sref_test'] = pat3
 | 
					    lib['sref_test'] = pat3
 | 
				
			||||||
@ -70,27 +71,27 @@ def main():
 | 
				
			|||||||
        b_count=2,
 | 
					        b_count=2,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    pat4 = Pattern()
 | 
					    pat4 = Pattern()
 | 
				
			||||||
    pat4.refs = [
 | 
					    pat4.refs[cell_name] = [
 | 
				
			||||||
        Ref(cell_name, repetition=rep, offset=(1e5, 3e5)),
 | 
					        Ref(repetition=rep, offset=(1e5, 3e5)),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, offset=(2e5, 3e5), rotation=pi/3),
 | 
					        Ref(repetition=rep, offset=(2e5, 3e5), rotation=pi/3),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, offset=(3e5, 3e5), rotation=pi/2),
 | 
					        Ref(repetition=rep, offset=(3e5, 3e5), rotation=pi/2),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, offset=(4e5, 3e5), rotation=pi),
 | 
					        Ref(repetition=rep, offset=(4e5, 3e5), rotation=pi),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, offset=(5e5, 3e5), rotation=3*pi/2),
 | 
					        Ref(repetition=rep, offset=(5e5, 3e5), rotation=3*pi/2),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(1e5, 4e5)),
 | 
					        Ref(repetition=rep, mirrored=True, offset=(1e5, 4e5)),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(2e5, 4e5), rotation=pi/3),
 | 
					        Ref(repetition=rep, mirrored=True, offset=(2e5, 4e5), rotation=pi/3),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(3e5, 4e5), rotation=pi/2),
 | 
					        Ref(repetition=rep, mirrored=True, offset=(3e5, 4e5), rotation=pi/2),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(4e5, 4e5), rotation=pi),
 | 
					        Ref(repetition=rep, mirrored=True, offset=(4e5, 4e5), rotation=pi),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, False), offset=(5e5, 4e5), rotation=3*pi/2),
 | 
					        Ref(repetition=rep, mirrored=True, offset=(5e5, 4e5), rotation=3*pi/2),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(1e5, 5e5)),
 | 
					        Ref(repetition=rep, offset=(1e5, 5e5)).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(2e5, 5e5), rotation=pi/3),
 | 
					        Ref(repetition=rep, offset=(2e5, 5e5), rotation=pi/3).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(3e5, 5e5), rotation=pi/2),
 | 
					        Ref(repetition=rep, offset=(3e5, 5e5), rotation=pi/2).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(4e5, 5e5), rotation=pi),
 | 
					        Ref(repetition=rep, offset=(4e5, 5e5), rotation=pi).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(False, True), offset=(5e5, 5e5), rotation=3*pi/2),
 | 
					        Ref(repetition=rep, offset=(5e5, 5e5), rotation=3*pi/2).mirror_target(1),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(1e5, 6e5)),
 | 
					        Ref(repetition=rep, offset=(1e5, 6e5)).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(2e5, 6e5), rotation=pi/3),
 | 
					        Ref(repetition=rep, offset=(2e5, 6e5), rotation=pi/3).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(3e5, 6e5), rotation=pi/2),
 | 
					        Ref(repetition=rep, offset=(3e5, 6e5), rotation=pi/2).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(4e5, 6e5), rotation=pi),
 | 
					        Ref(repetition=rep, offset=(4e5, 6e5), rotation=pi).mirror2d_target(True, True),
 | 
				
			||||||
        Ref(cell_name, repetition=rep, mirrored=(True, True), offset=(5e5, 6e5), rotation=3*pi/2),
 | 
					        Ref(repetition=rep, offset=(5e5, 6e5), rotation=3*pi/2).mirror2d_target(True, True),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    lib['aref_test'] = pat4
 | 
					    lib['aref_test'] = pat4
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ from numpy.typing import ArrayLike
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from .ref import Ref
 | 
					from .ref import Ref
 | 
				
			||||||
from .ports import PortList, Port
 | 
					from .ports import PortList, Port
 | 
				
			||||||
from .utils import rotation_matrix_2d, normalize_mirror
 | 
					from .utils import rotation_matrix_2d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if TYPE_CHECKING:
 | 
					#if TYPE_CHECKING:
 | 
				
			||||||
#    from .builder import Builder, Tool
 | 
					#    from .builder import Builder, Tool
 | 
				
			||||||
@ -143,7 +143,7 @@ class Abstract(PortList):
 | 
				
			|||||||
            port.rotate(rotation)
 | 
					            port.rotate(rotation)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror_port_offsets(self, across_axis: int) -> Self:
 | 
					    def mirror_port_offsets(self, across_axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the offsets of all shapes, labels, and refs across an axis
 | 
					        Mirror the offsets of all shapes, labels, and refs across an axis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -158,7 +158,7 @@ class Abstract(PortList):
 | 
				
			|||||||
            port.offset[across_axis - 1] *= -1
 | 
					            port.offset[across_axis - 1] *= -1
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror_ports(self, across_axis: int) -> Self:
 | 
					    def mirror_ports(self, across_axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror each port's rotation across an axis, relative to its
 | 
					        Mirror each port's rotation across an axis, relative to its
 | 
				
			||||||
          offset
 | 
					          offset
 | 
				
			||||||
@ -174,7 +174,7 @@ class Abstract(PortList):
 | 
				
			|||||||
            port.mirror(across_axis)
 | 
					            port.mirror(across_axis)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, across_axis: int) -> Self:
 | 
					    def mirror(self, across_axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the Pattern across an axis
 | 
					        Mirror the Pattern across an axis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -200,11 +200,10 @@ class Abstract(PortList):
 | 
				
			|||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            self
 | 
					            self
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        mirrored_across_x, angle = normalize_mirror(ref.mirrored)
 | 
					        if ref.mirrored:
 | 
				
			||||||
        if mirrored_across_x:
 | 
					            self.mirror()
 | 
				
			||||||
            self.mirror(across_axis=0)
 | 
					        self.rotate_ports(ref.rotation)
 | 
				
			||||||
        self.rotate_ports(angle + ref.rotation)
 | 
					        self.rotate_port_offsets(ref.rotation)
 | 
				
			||||||
        self.rotate_port_offsets(angle + ref.rotation)
 | 
					 | 
				
			||||||
        self.translate_ports(ref.offset)
 | 
					        self.translate_ports(ref.offset)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -221,10 +220,9 @@ class Abstract(PortList):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # TODO test undo_ref_transform
 | 
					        # TODO test undo_ref_transform
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        mirrored_across_x, angle = normalize_mirror(ref.mirrored)
 | 
					 | 
				
			||||||
        self.translate_ports(-ref.offset)
 | 
					        self.translate_ports(-ref.offset)
 | 
				
			||||||
        self.rotate_port_offsets(-angle - ref.rotation)
 | 
					        self.rotate_port_offsets(-ref.rotation)
 | 
				
			||||||
        self.rotate_ports(-angle - ref.rotation)
 | 
					        self.rotate_ports(-ref.rotation)
 | 
				
			||||||
        if mirrored_across_x:
 | 
					        if ref.mirrored:
 | 
				
			||||||
            self.mirror(across_axis=0)
 | 
					            self.mirror(0)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
				
			|||||||
@ -230,7 +230,7 @@ class Builder(PortList):
 | 
				
			|||||||
#            map_in: dict[str, str],
 | 
					#            map_in: dict[str, str],
 | 
				
			||||||
#            map_out: dict[str, str | None] | None,
 | 
					#            map_out: dict[str, str | None] | None,
 | 
				
			||||||
#            *,
 | 
					#            *,
 | 
				
			||||||
#            mirrored: tuple[bool, bool],
 | 
					#            mirrored: bool = False,
 | 
				
			||||||
#            inherit_name: bool,
 | 
					#            inherit_name: bool,
 | 
				
			||||||
#            set_rotation: bool | None,
 | 
					#            set_rotation: bool | None,
 | 
				
			||||||
#            append: bool,
 | 
					#            append: bool,
 | 
				
			||||||
@ -244,7 +244,7 @@ class Builder(PortList):
 | 
				
			|||||||
#            map_in: dict[str, str],
 | 
					#            map_in: dict[str, str],
 | 
				
			||||||
#            map_out: dict[str, str | None] | None = None,
 | 
					#            map_out: dict[str, str | None] | None = None,
 | 
				
			||||||
#            *,
 | 
					#            *,
 | 
				
			||||||
#            mirrored: tuple[bool, bool] = (False, False),
 | 
					#            mirrored: bool = False,
 | 
				
			||||||
#            inherit_name: bool = True,
 | 
					#            inherit_name: bool = True,
 | 
				
			||||||
#            set_rotation: bool | None = None,
 | 
					#            set_rotation: bool | None = None,
 | 
				
			||||||
#            append: bool = False,
 | 
					#            append: bool = False,
 | 
				
			||||||
@ -257,7 +257,7 @@ class Builder(PortList):
 | 
				
			|||||||
            map_in: dict[str, str],
 | 
					            map_in: dict[str, str],
 | 
				
			||||||
            map_out: dict[str, str | None] | None = None,
 | 
					            map_out: dict[str, str | None] | None = None,
 | 
				
			||||||
            *,
 | 
					            *,
 | 
				
			||||||
            mirrored: tuple[bool, bool] = (False, False),
 | 
					            mirrored: bool = False,
 | 
				
			||||||
            inherit_name: bool = True,
 | 
					            inherit_name: bool = True,
 | 
				
			||||||
            set_rotation: bool | None = None,
 | 
					            set_rotation: bool | None = None,
 | 
				
			||||||
            append: bool = False,
 | 
					            append: bool = False,
 | 
				
			||||||
@ -351,9 +351,7 @@ class Builder(PortList):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if isinstance(other, Pattern):
 | 
					        if isinstance(other, Pattern):
 | 
				
			||||||
            assert append
 | 
					            assert append
 | 
				
			||||||
            self.place(other, offset=translation, rotation=rotation, pivot=pivot,
 | 
					
 | 
				
			||||||
                       mirrored=mirrored, port_map=map_out, skip_port_check=True, append=append)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
        self.place(other, offset=translation, rotation=rotation, pivot=pivot,
 | 
					        self.place(other, offset=translation, rotation=rotation, pivot=pivot,
 | 
				
			||||||
                   mirrored=mirrored, port_map=map_out, skip_port_check=True, append=append)
 | 
					                   mirrored=mirrored, port_map=map_out, skip_port_check=True, append=append)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
@ -366,7 +364,7 @@ class Builder(PortList):
 | 
				
			|||||||
#            offset: ArrayLike,
 | 
					#            offset: ArrayLike,
 | 
				
			||||||
#            rotation: float,
 | 
					#            rotation: float,
 | 
				
			||||||
#            pivot: ArrayLike,
 | 
					#            pivot: ArrayLike,
 | 
				
			||||||
#            mirrored: tuple[bool, bool],
 | 
					#            mirrored: bool = False,
 | 
				
			||||||
#            port_map: dict[str, str | None] | None,
 | 
					#            port_map: dict[str, str | None] | None,
 | 
				
			||||||
#            skip_port_check: bool,
 | 
					#            skip_port_check: bool,
 | 
				
			||||||
#            append: bool,
 | 
					#            append: bool,
 | 
				
			||||||
@ -381,7 +379,7 @@ class Builder(PortList):
 | 
				
			|||||||
#            offset: ArrayLike,
 | 
					#            offset: ArrayLike,
 | 
				
			||||||
#            rotation: float,
 | 
					#            rotation: float,
 | 
				
			||||||
#            pivot: ArrayLike,
 | 
					#            pivot: ArrayLike,
 | 
				
			||||||
#            mirrored: tuple[bool, bool],
 | 
					#            mirrored: bool = False,
 | 
				
			||||||
#            port_map: dict[str, str | None] | None,
 | 
					#            port_map: dict[str, str | None] | None,
 | 
				
			||||||
#            skip_port_check: bool,
 | 
					#            skip_port_check: bool,
 | 
				
			||||||
#            append: Literal[True],
 | 
					#            append: Literal[True],
 | 
				
			||||||
@ -395,7 +393,7 @@ class Builder(PortList):
 | 
				
			|||||||
            offset: ArrayLike = (0, 0),
 | 
					            offset: ArrayLike = (0, 0),
 | 
				
			||||||
            rotation: float = 0,
 | 
					            rotation: float = 0,
 | 
				
			||||||
            pivot: ArrayLike = (0, 0),
 | 
					            pivot: ArrayLike = (0, 0),
 | 
				
			||||||
            mirrored: tuple[bool, bool] = (False, False),
 | 
					            mirrored: bool = False,
 | 
				
			||||||
            port_map: dict[str, str | None] | None = None,
 | 
					            port_map: dict[str, str | None] | None = None,
 | 
				
			||||||
            skip_port_check: bool = False,
 | 
					            skip_port_check: bool = False,
 | 
				
			||||||
            append: bool = False,
 | 
					            append: bool = False,
 | 
				
			||||||
@ -461,7 +459,8 @@ class Builder(PortList):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        for name, port in ports.items():
 | 
					        for name, port in ports.items():
 | 
				
			||||||
            p = port.deepcopy()
 | 
					            p = port.deepcopy()
 | 
				
			||||||
            p.mirror2d(mirrored)
 | 
					            if mirrored:
 | 
				
			||||||
 | 
					                p.mirror()
 | 
				
			||||||
            p.rotate_around(pivot, rotation)
 | 
					            p.rotate_around(pivot, rotation)
 | 
				
			||||||
            p.translate(offset)
 | 
					            p.translate(offset)
 | 
				
			||||||
            self.ports[name] = p
 | 
					            self.ports[name] = p
 | 
				
			||||||
@ -476,7 +475,8 @@ class Builder(PortList):
 | 
				
			|||||||
                other_pat = self.library[name]
 | 
					                other_pat = self.library[name]
 | 
				
			||||||
            other_copy = other_pat.deepcopy()
 | 
					            other_copy = other_pat.deepcopy()
 | 
				
			||||||
            other_copy.ports.clear()
 | 
					            other_copy.ports.clear()
 | 
				
			||||||
            other_copy.mirror2d(mirrored)
 | 
					            if mirrored:
 | 
				
			||||||
 | 
					                other_copy.mirror()
 | 
				
			||||||
            other_copy.rotate_around(pivot, rotation)
 | 
					            other_copy.rotate_around(pivot, rotation)
 | 
				
			||||||
            other_copy.translate_elements(offset)
 | 
					            other_copy.translate_elements(offset)
 | 
				
			||||||
            self.pattern.append(other_copy)
 | 
					            self.pattern.append(other_copy)
 | 
				
			||||||
@ -517,7 +517,7 @@ class Builder(PortList):
 | 
				
			|||||||
            port.rotate_around(pivot, angle)
 | 
					            port.rotate_around(pivot, angle)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> Self:
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the pattern and all ports across the specified axis.
 | 
					        Mirror the pattern and all ports across the specified axis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -528,8 +528,6 @@ class Builder(PortList):
 | 
				
			|||||||
            self
 | 
					            self
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.pattern.mirror(axis)
 | 
					        self.pattern.mirror(axis)
 | 
				
			||||||
        for p in self.ports.values():
 | 
					 | 
				
			||||||
            p.mirror(axis)
 | 
					 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_dead(self) -> Self:
 | 
					    def set_dead(self) -> Self:
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@ from ..library import ILibrary
 | 
				
			|||||||
from ..error import PortError, BuildError
 | 
					from ..error import PortError, BuildError
 | 
				
			||||||
from ..ports import PortList, Port
 | 
					from ..ports import PortList, Port
 | 
				
			||||||
from ..abstract import Abstract
 | 
					from ..abstract import Abstract
 | 
				
			||||||
from ..utils import rotation_matrix_2d
 | 
					 | 
				
			||||||
from ..utils import SupportsBool
 | 
					from ..utils import SupportsBool
 | 
				
			||||||
from .tools import Tool, RenderStep
 | 
					from .tools import Tool, RenderStep
 | 
				
			||||||
from .utils import ell
 | 
					from .utils import ell
 | 
				
			||||||
@ -94,7 +93,6 @@ class RenderPather(PortList):
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.tools = dict(tools)
 | 
					            self.tools = dict(tools)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def interface(
 | 
					    def interface(
 | 
				
			||||||
            cls,
 | 
					            cls,
 | 
				
			||||||
@ -200,7 +198,7 @@ class RenderPather(PortList):
 | 
				
			|||||||
            map_in: dict[str, str],
 | 
					            map_in: dict[str, str],
 | 
				
			||||||
            map_out: dict[str, str | None] | None = None,
 | 
					            map_out: dict[str, str | None] | None = None,
 | 
				
			||||||
            *,
 | 
					            *,
 | 
				
			||||||
            mirrored: tuple[bool, bool] = (False, False),
 | 
					            mirrored: bool = False,
 | 
				
			||||||
            inherit_name: bool = True,
 | 
					            inherit_name: bool = True,
 | 
				
			||||||
            set_rotation: bool | None = None,
 | 
					            set_rotation: bool | None = None,
 | 
				
			||||||
            ) -> Self:
 | 
					            ) -> Self:
 | 
				
			||||||
@ -250,7 +248,7 @@ class RenderPather(PortList):
 | 
				
			|||||||
            offset: ArrayLike = (0, 0),
 | 
					            offset: ArrayLike = (0, 0),
 | 
				
			||||||
            rotation: float = 0,
 | 
					            rotation: float = 0,
 | 
				
			||||||
            pivot: ArrayLike = (0, 0),
 | 
					            pivot: ArrayLike = (0, 0),
 | 
				
			||||||
            mirrored: tuple[bool, bool] = (False, False),
 | 
					            mirrored: bool = False,
 | 
				
			||||||
            port_map: dict[str, str | None] | None = None,
 | 
					            port_map: dict[str, str | None] | None = None,
 | 
				
			||||||
            skip_port_check: bool = False,
 | 
					            skip_port_check: bool = False,
 | 
				
			||||||
            ) -> Self:
 | 
					            ) -> Self:
 | 
				
			||||||
@ -280,7 +278,8 @@ class RenderPather(PortList):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        for name, port in ports.items():
 | 
					        for name, port in ports.items():
 | 
				
			||||||
            p = port.deepcopy()
 | 
					            p = port.deepcopy()
 | 
				
			||||||
            p.mirror2d(mirrored)
 | 
					            if mirrored:
 | 
				
			||||||
 | 
					                p.mirror()
 | 
				
			||||||
            p.rotate_around(pivot, rotation)
 | 
					            p.rotate_around(pivot, rotation)
 | 
				
			||||||
            p.translate(offset)
 | 
					            p.translate(offset)
 | 
				
			||||||
            self.ports[name] = p
 | 
					            self.ports[name] = p
 | 
				
			||||||
 | 
				
			|||||||
@ -125,7 +125,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
 | 
				
			|||||||
            bb.plug(straight, {port_names[1]: sport_in})
 | 
					            bb.plug(straight, {port_names[1]: sport_in})
 | 
				
			||||||
        if data.ccw is not None:
 | 
					        if data.ccw is not None:
 | 
				
			||||||
            bend, bport_in, bport_out = self.bend
 | 
					            bend, bport_in, bport_out = self.bend
 | 
				
			||||||
            bb.plug(bend, {port_names[1]: bport_in}, mirrored=(False, bool(ccw)))
 | 
					            bb.plug(bend, {port_names[1]: bport_in}, mirrored=bool(ccw))
 | 
				
			||||||
        if data.out_transition:
 | 
					        if data.out_transition:
 | 
				
			||||||
            opat, oport_theirs, oport_ours = data.out_transition
 | 
					            opat, oport_theirs, oport_ours = data.out_transition
 | 
				
			||||||
            bb.plug(opat, {port_names[1]: oport_ours})
 | 
					            bb.plug(opat, {port_names[1]: oport_ours})
 | 
				
			||||||
@ -239,7 +239,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
 | 
				
			|||||||
                        bb.plug(straight, {port_names[1]: sport_in}, append=True)
 | 
					                        bb.plug(straight, {port_names[1]: sport_in}, append=True)
 | 
				
			||||||
                if ccw is not None:
 | 
					                if ccw is not None:
 | 
				
			||||||
                    bend, bport_in, bport_out = self.bend
 | 
					                    bend, bport_in, bport_out = self.bend
 | 
				
			||||||
                    bb.plug(bend, {port_names[1]: bport_in}, mirrored=(False, bool(ccw)))
 | 
					                    bb.plug(bend, {port_names[1]: bport_in}, mirrored=bool(ccw))
 | 
				
			||||||
                if out_transition:
 | 
					                if out_transition:
 | 
				
			||||||
                    opat, oport_theirs, oport_ours = out_transition
 | 
					                    opat, oport_theirs, oport_ours = out_transition
 | 
				
			||||||
                    bb.plug(opat, {port_names[1]: oport_ours})
 | 
					                    bb.plug(opat, {port_names[1]: oport_ours})
 | 
				
			||||||
@ -256,7 +256,6 @@ class PathTool(Tool, metaclass=ABCMeta):
 | 
				
			|||||||
    #class LData:
 | 
					    #class LData:
 | 
				
			||||||
    #    dxy: NDArray[numpy.float64]
 | 
					    #    dxy: NDArray[numpy.float64]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    #def __init__(self, layer: layer_t, width: float, ptype: str = 'unk') -> None:
 | 
					    #def __init__(self, layer: layer_t, width: float, ptype: str = 'unk') -> None:
 | 
				
			||||||
    #    Tool.__init__(self)
 | 
					    #    Tool.__init__(self)
 | 
				
			||||||
    #    self.layer = layer
 | 
					    #    self.layer = layer
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ from .. import Pattern, Ref, PatternError, Label
 | 
				
			|||||||
from ..library import ILibraryView, LibraryView, Library
 | 
					from ..library import ILibraryView, LibraryView, Library
 | 
				
			||||||
from ..shapes import Shape, Polygon, Path
 | 
					from ..shapes import Shape, Polygon, Path
 | 
				
			||||||
from ..repetition import Grid
 | 
					from ..repetition import Grid
 | 
				
			||||||
from ..utils import rotation_matrix_2d, layer_t
 | 
					from ..utils import rotation_matrix_2d, layer_t, normalize_mirror
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
@ -258,8 +258,8 @@ def _read_block(block) -> tuple[str, Pattern]:
 | 
				
			|||||||
            if abs(xscale) != abs(yscale):
 | 
					            if abs(xscale) != abs(yscale):
 | 
				
			||||||
                logger.warning('Masque does not support per-axis scaling; using x-scaling only!')
 | 
					                logger.warning('Masque does not support per-axis scaling; using x-scaling only!')
 | 
				
			||||||
            scale = abs(xscale)
 | 
					            scale = abs(xscale)
 | 
				
			||||||
            mirrored = (yscale < 0, xscale < 0)
 | 
					            mirrored, extra_angle = normalize_mirror((yscale < 0, xscale < 0))
 | 
				
			||||||
            rotation = numpy.deg2rad(attr.get('rotation', 0))
 | 
					            rotation = numpy.deg2rad(attr.get('rotation', 0)) + extra_angle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            offset = numpy.array(attr.get('insert', (0, 0, 0)))[:2]
 | 
					            offset = numpy.array(attr.get('insert', (0, 0, 0)))[:2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -291,8 +291,8 @@ def _mrefs_to_drefs(
 | 
				
			|||||||
    def mk_blockref(encoded_name: str, ref: Ref) -> None:
 | 
					    def mk_blockref(encoded_name: str, ref: Ref) -> None:
 | 
				
			||||||
        rotation = numpy.rad2deg(ref.rotation) % 360
 | 
					        rotation = numpy.rad2deg(ref.rotation) % 360
 | 
				
			||||||
        attribs = dict(
 | 
					        attribs = dict(
 | 
				
			||||||
            xscale=ref.scale * (-1 if ref.mirrored[1] else 1),
 | 
					            xscale=ref.scale,
 | 
				
			||||||
            yscale=ref.scale * (-1 if ref.mirrored[0] else 1),
 | 
					            yscale=ref.scale * (-1 if ref.mirrored else 1),
 | 
				
			||||||
            rotation=rotation,
 | 
					            rotation=rotation,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -37,7 +37,7 @@ from .utils import is_gzipped, tmpfile
 | 
				
			|||||||
from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
 | 
					from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
 | 
				
			||||||
from ..shapes import Polygon, Path
 | 
					from ..shapes import Polygon, Path
 | 
				
			||||||
from ..repetition import Grid
 | 
					from ..repetition import Grid
 | 
				
			||||||
from ..utils import layer_t, normalize_mirror, annotations_t
 | 
					from ..utils import layer_t, annotations_t
 | 
				
			||||||
from ..library import LazyLibrary, Library, ILibrary, ILibraryView
 | 
					from ..library import LazyLibrary, Library, ILibrary, ILibraryView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -306,7 +306,7 @@ def _gref_to_mref(ref: klamath.library.Reference) -> tuple[str, Ref]:
 | 
				
			|||||||
        offset=offset,
 | 
					        offset=offset,
 | 
				
			||||||
        rotation=numpy.deg2rad(ref.angle_deg),
 | 
					        rotation=numpy.deg2rad(ref.angle_deg),
 | 
				
			||||||
        scale=ref.mag,
 | 
					        scale=ref.mag,
 | 
				
			||||||
        mirrored=(ref.invert_y, False),
 | 
					        mirrored=ref.invert_y,
 | 
				
			||||||
        annotations=_properties_to_annotations(ref.properties),
 | 
					        annotations=_properties_to_annotations(ref.properties),
 | 
				
			||||||
        repetition=repetition,
 | 
					        repetition=repetition,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
@ -348,10 +348,9 @@ def _mrefs_to_grefs(refs: dict[str | None, list[Ref]]) -> list[klamath.library.R
 | 
				
			|||||||
            continue
 | 
					            continue
 | 
				
			||||||
        encoded_name = target.encode('ASCII')
 | 
					        encoded_name = target.encode('ASCII')
 | 
				
			||||||
        for ref in rseq:
 | 
					        for ref in rseq:
 | 
				
			||||||
            # Note: GDS mirrors first and rotates second
 | 
					            # Note: GDS also mirrors first and rotates second
 | 
				
			||||||
            mirror_across_x, extra_angle = normalize_mirror(ref.mirrored)
 | 
					 | 
				
			||||||
            rep = ref.repetition
 | 
					            rep = ref.repetition
 | 
				
			||||||
            angle_deg = numpy.rad2deg(ref.rotation + extra_angle) % 360
 | 
					            angle_deg = numpy.rad2deg(ref.rotation) % 360
 | 
				
			||||||
            properties = _annotations_to_properties(ref.annotations, 512)
 | 
					            properties = _annotations_to_properties(ref.annotations, 512)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if isinstance(rep, Grid):
 | 
					            if isinstance(rep, Grid):
 | 
				
			||||||
@ -367,7 +366,7 @@ def _mrefs_to_grefs(refs: dict[str | None, list[Ref]]) -> list[klamath.library.R
 | 
				
			|||||||
                    xy=rint_cast(xy),
 | 
					                    xy=rint_cast(xy),
 | 
				
			||||||
                    colrow=(numpy.rint(rep.a_count), numpy.rint(rep.b_count)),
 | 
					                    colrow=(numpy.rint(rep.a_count), numpy.rint(rep.b_count)),
 | 
				
			||||||
                    angle_deg=angle_deg,
 | 
					                    angle_deg=angle_deg,
 | 
				
			||||||
                    invert_y=mirror_across_x,
 | 
					                    invert_y=ref.mirrored,
 | 
				
			||||||
                    mag=ref.scale,
 | 
					                    mag=ref.scale,
 | 
				
			||||||
                    properties=properties,
 | 
					                    properties=properties,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
@ -378,7 +377,7 @@ def _mrefs_to_grefs(refs: dict[str | None, list[Ref]]) -> list[klamath.library.R
 | 
				
			|||||||
                    xy=rint_cast([ref.offset]),
 | 
					                    xy=rint_cast([ref.offset]),
 | 
				
			||||||
                    colrow=None,
 | 
					                    colrow=None,
 | 
				
			||||||
                    angle_deg=angle_deg,
 | 
					                    angle_deg=angle_deg,
 | 
				
			||||||
                    invert_y=mirror_across_x,
 | 
					                    invert_y=ref.mirrored,
 | 
				
			||||||
                    mag=ref.scale,
 | 
					                    mag=ref.scale,
 | 
				
			||||||
                    properties=properties,
 | 
					                    properties=properties,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
@ -390,7 +389,7 @@ def _mrefs_to_grefs(refs: dict[str | None, list[Ref]]) -> list[klamath.library.R
 | 
				
			|||||||
                        xy=rint_cast([ref.offset + dd]),
 | 
					                        xy=rint_cast([ref.offset + dd]),
 | 
				
			||||||
                        colrow=None,
 | 
					                        colrow=None,
 | 
				
			||||||
                        angle_deg=angle_deg,
 | 
					                        angle_deg=angle_deg,
 | 
				
			||||||
                        invert_y=mirror_across_x,
 | 
					                        invert_y=ref.mirrored,
 | 
				
			||||||
                        mag=ref.scale,
 | 
					                        mag=ref.scale,
 | 
				
			||||||
                        properties=properties,
 | 
					                        properties=properties,
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ from .. import Pattern, Ref, PatternError, LibraryError, Label, Shape
 | 
				
			|||||||
from ..library import Library, ILibrary
 | 
					from ..library import Library, ILibrary
 | 
				
			||||||
from ..shapes import Path, Circle
 | 
					from ..shapes import Path, Circle
 | 
				
			||||||
from ..repetition import Grid, Arbitrary, Repetition
 | 
					from ..repetition import Grid, Arbitrary, Repetition
 | 
				
			||||||
from ..utils import layer_t, normalize_mirror, annotations_t
 | 
					from ..utils import layer_t, annotations_t
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
@ -494,7 +494,7 @@ def _placement_to_ref(placement: fatrec.Placement, lib: fatamorgana.OasisLayout)
 | 
				
			|||||||
        rotation = numpy.deg2rad(float(placement.angle))
 | 
					        rotation = numpy.deg2rad(float(placement.angle))
 | 
				
			||||||
    ref = Ref(
 | 
					    ref = Ref(
 | 
				
			||||||
        offset=xy,
 | 
					        offset=xy,
 | 
				
			||||||
        mirrored=(placement.flip, False),
 | 
					        mirrored=placement.flip,
 | 
				
			||||||
        rotation=rotation,
 | 
					        rotation=rotation,
 | 
				
			||||||
        scale=float(mag),
 | 
					        scale=float(mag),
 | 
				
			||||||
        repetition=repetition_fata2masq(placement.repetition),
 | 
					        repetition=repetition_fata2masq(placement.repetition),
 | 
				
			||||||
@ -511,15 +511,14 @@ def _refs_to_placements(
 | 
				
			|||||||
        if target is None:
 | 
					        if target is None:
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        for ref in rseq:
 | 
					        for ref in rseq:
 | 
				
			||||||
            # Note: OASIS mirrors first and rotates second
 | 
					            # Note: OASIS also mirrors first and rotates second
 | 
				
			||||||
            mirror_across_x, extra_angle = normalize_mirror(ref.mirrored)
 | 
					 | 
				
			||||||
            frep, rep_offset = repetition_masq2fata(ref.repetition)
 | 
					            frep, rep_offset = repetition_masq2fata(ref.repetition)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            offset = rint_cast(ref.offset + rep_offset)
 | 
					            offset = rint_cast(ref.offset + rep_offset)
 | 
				
			||||||
            angle = numpy.rad2deg(ref.rotation + extra_angle) % 360
 | 
					            angle = numpy.rad2deg(ref.rotation) % 360
 | 
				
			||||||
            placement = fatrec.Placement(
 | 
					            placement = fatrec.Placement(
 | 
				
			||||||
                name=target,
 | 
					                name=target,
 | 
				
			||||||
                flip=mirror_across_x,
 | 
					                flip=ref.mirrored,
 | 
				
			||||||
                angle=angle,
 | 
					                angle=angle,
 | 
				
			||||||
                magnification=ref.scale,
 | 
					                magnification=ref.scale,
 | 
				
			||||||
                properties=annotations_to_properties(ref.annotations),
 | 
					                properties=annotations_to_properties(ref.annotations),
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ import numpy
 | 
				
			|||||||
from numpy.typing import ArrayLike
 | 
					from numpy.typing import ArrayLike
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .error import LibraryError, PatternError
 | 
					from .error import LibraryError, PatternError
 | 
				
			||||||
from .utils import rotation_matrix_2d, normalize_mirror
 | 
					from .utils import rotation_matrix_2d
 | 
				
			||||||
from .shapes import Shape, Polygon
 | 
					from .shapes import Shape, Polygon
 | 
				
			||||||
from .label import Label
 | 
					from .label import Label
 | 
				
			||||||
from .abstract import Abstract
 | 
					from .abstract import Abstract
 | 
				
			||||||
@ -410,9 +410,7 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta):
 | 
				
			|||||||
                    if transform[3]:
 | 
					                    if transform[3]:
 | 
				
			||||||
                        sign[1] = -1
 | 
					                        sign[1] = -1
 | 
				
			||||||
                    xy = numpy.dot(rotation_matrix_2d(transform[2]), ref.offset * sign)
 | 
					                    xy = numpy.dot(rotation_matrix_2d(transform[2]), ref.offset * sign)
 | 
				
			||||||
                    mirror_x, angle = normalize_mirror(ref.mirrored)
 | 
					                    ref_transform = transform + (xy[0], xy[1], ref.rotation, ref.mirrored)
 | 
				
			||||||
                    angle += ref.rotation
 | 
					 | 
				
			||||||
                    ref_transform = transform + (xy[0], xy[1], angle, mirror_x)
 | 
					 | 
				
			||||||
                    ref_transform[3] %= 2
 | 
					                    ref_transform[3] %= 2
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    ref_transform = False
 | 
					                    ref_transform = False
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ from numpy.typing import NDArray, ArrayLike
 | 
				
			|||||||
from .ref import Ref
 | 
					from .ref import Ref
 | 
				
			||||||
from .shapes import Shape, Polygon, Path, DEFAULT_POLY_NUM_VERTICES
 | 
					from .shapes import Shape, Polygon, Path, DEFAULT_POLY_NUM_VERTICES
 | 
				
			||||||
from .label import Label
 | 
					from .label import Label
 | 
				
			||||||
from .utils import rotation_matrix_2d, annotations_t, layer_t, normalize_mirror
 | 
					from .utils import rotation_matrix_2d, annotations_t, layer_t
 | 
				
			||||||
from .error import PatternError
 | 
					from .error import PatternError
 | 
				
			||||||
from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable, Bounded
 | 
					from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable, Bounded
 | 
				
			||||||
from .ports import Port, PortList
 | 
					from .ports import Port, PortList
 | 
				
			||||||
@ -370,12 +370,10 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
 | 
				
			|||||||
                            bounds = None
 | 
					                            bounds = None
 | 
				
			||||||
                        else:
 | 
					                        else:
 | 
				
			||||||
                            ubounds = unrot_bounds.copy()
 | 
					                            ubounds = unrot_bounds.copy()
 | 
				
			||||||
                            mirr_x, rot2 = normalize_mirror(ref.mirrored)
 | 
					                            if ref.mirrored:
 | 
				
			||||||
                            if mirr_x:
 | 
					 | 
				
			||||||
                                ubounds[:, 1] *= -1
 | 
					                                ubounds[:, 1] *= -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            # note: rounding fixes up sin/cos inaccuracy, probably unnecessary
 | 
					                            corners = (rotation_matrix_2d(ref.rotation) @ ubounds.T).T
 | 
				
			||||||
                            corners = (numpy.round(rotation_matrix_2d(ref.rotation + rot2)) @ ubounds.T).T
 | 
					 | 
				
			||||||
                            bounds = numpy.vstack((numpy.min(corners, axis=0),
 | 
					                            bounds = numpy.vstack((numpy.min(corners, axis=0),
 | 
				
			||||||
                                                   numpy.max(corners, axis=0))) * ref.scale + [ref.offset]
 | 
					                                                   numpy.max(corners, axis=0))) * ref.scale + [ref.offset]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -518,7 +516,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
 | 
				
			|||||||
            cast(Rotatable, entry).rotate(rotation)
 | 
					            cast(Rotatable, entry).rotate(rotation)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror_element_centers(self, across_axis: int) -> Self:
 | 
					    def mirror_element_centers(self, across_axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the offsets of all shapes, labels, and refs across an axis
 | 
					        Mirror the offsets of all shapes, labels, and refs across an axis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -533,7 +531,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
 | 
				
			|||||||
            cast(Positionable, entry).offset[across_axis - 1] *= -1
 | 
					            cast(Positionable, entry).offset[across_axis - 1] *= -1
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror_elements(self, across_axis: int) -> Self:
 | 
					    def mirror_elements(self, across_axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror each shape, ref, and pattern across an axis, relative
 | 
					        Mirror each shape, ref, and pattern across an axis, relative
 | 
				
			||||||
          to its offset
 | 
					          to its offset
 | 
				
			||||||
@ -549,7 +547,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
 | 
				
			|||||||
            cast(Mirrorable, entry).mirror(across_axis)
 | 
					            cast(Mirrorable, entry).mirror(across_axis)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, across_axis: int) -> Self:
 | 
					    def mirror(self, across_axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the Pattern across an axis
 | 
					        Mirror the Pattern across an axis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -76,7 +76,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable):
 | 
				
			|||||||
        self.ptype = ptype
 | 
					        self.ptype = ptype
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> Self:
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        self.offset[1 - axis] *= -1
 | 
					        self.offset[1 - axis] *= -1
 | 
				
			||||||
        if self.rotation is not None:
 | 
					        if self.rotation is not None:
 | 
				
			||||||
            self.rotation *= -1
 | 
					            self.rotation *= -1
 | 
				
			||||||
@ -275,7 +275,7 @@ class PortList(metaclass=ABCMeta):
 | 
				
			|||||||
            other: 'PortList',
 | 
					            other: 'PortList',
 | 
				
			||||||
            map_in: dict[str, str],
 | 
					            map_in: dict[str, str],
 | 
				
			||||||
            *,
 | 
					            *,
 | 
				
			||||||
            mirrored: tuple[bool, bool] = (False, False),
 | 
					            mirrored: bool = False,
 | 
				
			||||||
            set_rotation: bool | None = None,
 | 
					            set_rotation: bool | None = None,
 | 
				
			||||||
            ) -> tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]:
 | 
					            ) -> tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -286,7 +286,7 @@ class PortList(metaclass=ABCMeta):
 | 
				
			|||||||
            other: a device
 | 
					            other: a device
 | 
				
			||||||
            map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
 | 
					            map_in: dict of `{'self_port': 'other_port'}` mappings, specifying
 | 
				
			||||||
                port connections between the two devices.
 | 
					                port connections between the two devices.
 | 
				
			||||||
            mirrored: Mirrors `other` across the x or y axes prior to
 | 
					            mirrored: Mirrors `other` across the x axis prior to
 | 
				
			||||||
                connecting any ports.
 | 
					                connecting any ports.
 | 
				
			||||||
            set_rotation: If the necessary rotation cannot be determined from
 | 
					            set_rotation: If the necessary rotation cannot be determined from
 | 
				
			||||||
                the ports being connected (i.e. all pairs have at least one
 | 
					                the ports being connected (i.e. all pairs have at least one
 | 
				
			||||||
@ -317,7 +317,7 @@ class PortList(metaclass=ABCMeta):
 | 
				
			|||||||
            o_ports: Mapping[str, Port],
 | 
					            o_ports: Mapping[str, Port],
 | 
				
			||||||
            map_in: dict[str, str],
 | 
					            map_in: dict[str, str],
 | 
				
			||||||
            *,
 | 
					            *,
 | 
				
			||||||
            mirrored: tuple[bool, bool] = (False, False),
 | 
					            mirrored: bool = False,
 | 
				
			||||||
            set_rotation: bool | None = None,
 | 
					            set_rotation: bool | None = None,
 | 
				
			||||||
            ) -> tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]:
 | 
					            ) -> tuple[NDArray[numpy.float64], float, NDArray[numpy.float64]]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -330,7 +330,7 @@ class PortList(metaclass=ABCMeta):
 | 
				
			|||||||
            o_ports: A list of ports which are to be moved/mirrored.
 | 
					            o_ports: A list of ports which are to be moved/mirrored.
 | 
				
			||||||
            map_in: dict of `{'s_port': 'o_port'}` mappings, specifying
 | 
					            map_in: dict of `{'s_port': 'o_port'}` mappings, specifying
 | 
				
			||||||
                port connections.
 | 
					                port connections.
 | 
				
			||||||
            mirrored: Mirrors `o_ports` across the x or y axes prior to
 | 
					            mirrored: Mirrors `o_ports` across the x axis prior to
 | 
				
			||||||
                connecting any ports.
 | 
					                connecting any ports.
 | 
				
			||||||
            set_rotation: If the necessary rotation cannot be determined from
 | 
					            set_rotation: If the necessary rotation cannot be determined from
 | 
				
			||||||
                the ports being connected (i.e. all pairs have at least one
 | 
					                the ports being connected (i.e. all pairs have at least one
 | 
				
			||||||
@ -356,13 +356,9 @@ class PortList(metaclass=ABCMeta):
 | 
				
			|||||||
        o_has_rot = numpy.array([p.rotation is not None for p in o_ports.values()], dtype=bool)
 | 
					        o_has_rot = numpy.array([p.rotation is not None for p in o_ports.values()], dtype=bool)
 | 
				
			||||||
        has_rot = s_has_rot & o_has_rot
 | 
					        has_rot = s_has_rot & o_has_rot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if mirrored[0]:
 | 
					        if mirrored:
 | 
				
			||||||
            o_offsets[:, 1] *= -1
 | 
					            o_offsets[:, 1] *= -1
 | 
				
			||||||
            o_rotations *= -1
 | 
					            o_rotations *= -1
 | 
				
			||||||
        if mirrored[1]:
 | 
					 | 
				
			||||||
            o_offsets[:, 0] *= -1
 | 
					 | 
				
			||||||
            o_rotations *= -1
 | 
					 | 
				
			||||||
            o_rotations += pi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        type_conflicts = numpy.array([st != ot and st != 'unk' and ot != 'unk'
 | 
					        type_conflicts = numpy.array([st != ot and st != 'unk' and ot != 'unk'
 | 
				
			||||||
                                      for st, ot in zip(s_types, o_types)])
 | 
					                                      for st, ot in zip(s_types, o_types)])
 | 
				
			||||||
 | 
				
			|||||||
@ -4,15 +4,14 @@
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
#TODO more top-level documentation
 | 
					#TODO more top-level documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Sequence, Mapping, TYPE_CHECKING, Any, Self
 | 
					from typing import Mapping, TYPE_CHECKING, Self
 | 
				
			||||||
import copy
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import numpy
 | 
					import numpy
 | 
				
			||||||
from numpy import pi
 | 
					from numpy import pi
 | 
				
			||||||
from numpy.typing import NDArray, ArrayLike
 | 
					from numpy.typing import NDArray, ArrayLike
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .error import PatternError
 | 
					from .utils import annotations_t
 | 
				
			||||||
from .utils import is_scalar, annotations_t
 | 
					 | 
				
			||||||
from .repetition import Repetition
 | 
					from .repetition import Repetition
 | 
				
			||||||
from .traits import (
 | 
					from .traits import (
 | 
				
			||||||
    PositionableImpl, RotatableImpl, ScalableImpl,
 | 
					    PositionableImpl, RotatableImpl, ScalableImpl,
 | 
				
			||||||
@ -31,6 +30,8 @@ class Ref(
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
    `Ref` provides basic support for nesting Pattern objects within each other, by adding
 | 
					    `Ref` provides basic support for nesting Pattern objects within each other, by adding
 | 
				
			||||||
     offset, rotation, scaling, and associated methods.
 | 
					     offset, rotation, scaling, and associated methods.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Note: Order is (mirror, rotate, scale, translate, repeat)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    __slots__ = (
 | 
					    __slots__ = (
 | 
				
			||||||
        '_mirrored',
 | 
					        '_mirrored',
 | 
				
			||||||
@ -38,32 +39,41 @@ class Ref(
 | 
				
			|||||||
        '_offset', '_rotation', 'scale', '_repetition', '_annotations',
 | 
					        '_offset', '_rotation', 'scale', '_repetition', '_annotations',
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _mirrored: NDArray[numpy.bool_]
 | 
					    _mirrored: bool
 | 
				
			||||||
    """ Whether to mirror the instance across the x and/or y axes. """
 | 
					    """ Whether to mirror the instance across the x axis (new_y = -old_y)ubefore rotating. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Mirrored property
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def mirrored(self) -> bool:     # mypy#3004, setter should be SupportsBool
 | 
				
			||||||
 | 
					        return self._mirrored
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @mirrored.setter
 | 
				
			||||||
 | 
					    def mirrored(self, val: bool) -> None:
 | 
				
			||||||
 | 
					        self._mirrored = bool(val)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            *,
 | 
					            *,
 | 
				
			||||||
            offset: ArrayLike = (0.0, 0.0),
 | 
					            offset: ArrayLike = (0.0, 0.0),
 | 
				
			||||||
            rotation: float = 0.0,
 | 
					            rotation: float = 0.0,
 | 
				
			||||||
            mirrored: Sequence[bool] | None = None,
 | 
					            mirrored: bool = False,
 | 
				
			||||||
            scale: float = 1.0,
 | 
					            scale: float = 1.0,
 | 
				
			||||||
            repetition: Repetition | None = None,
 | 
					            repetition: Repetition | None = None,
 | 
				
			||||||
            annotations: annotations_t | None = None,
 | 
					            annotations: annotations_t | None = None,
 | 
				
			||||||
            ) -> None:
 | 
					            ) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					        Note: Order is (mirror, rotate, scale, translate, repeat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Args:
 | 
					        Args:
 | 
				
			||||||
            offset: (x, y) offset applied to the referenced pattern. Not affected by rotation etc.
 | 
					            offset: (x, y) offset applied to the referenced pattern. Not affected by rotation etc.
 | 
				
			||||||
            rotation: Rotation (radians, counterclockwise) relative to the referenced pattern's (0, 0).
 | 
					            rotation: Rotation (radians, counterclockwise) relative to the referenced pattern's (0, 0).
 | 
				
			||||||
            mirrored: Whether to mirror the referenced pattern across its x and y axes.
 | 
					            mirrored: Whether to mirror the referenced pattern across its x axis before rotating.
 | 
				
			||||||
            scale: Scaling factor applied to the pattern's geometry.
 | 
					            scale: Scaling factor applied to the pattern's geometry.
 | 
				
			||||||
            repetition: `Repetition` object, default `None`
 | 
					            repetition: `Repetition` object, default `None`
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.offset = offset
 | 
					        self.offset = offset
 | 
				
			||||||
        self.rotation = rotation
 | 
					        self.rotation = rotation
 | 
				
			||||||
        self.scale = scale
 | 
					        self.scale = scale
 | 
				
			||||||
        if mirrored is None:
 | 
					 | 
				
			||||||
            mirrored = (False, False)
 | 
					 | 
				
			||||||
        self.mirrored = mirrored
 | 
					        self.mirrored = mirrored
 | 
				
			||||||
        self.repetition = repetition
 | 
					        self.repetition = repetition
 | 
				
			||||||
        self.annotations = annotations if annotations is not None else {}
 | 
					        self.annotations = annotations if annotations is not None else {}
 | 
				
			||||||
@ -73,7 +83,7 @@ class Ref(
 | 
				
			|||||||
            offset=self.offset.copy(),
 | 
					            offset=self.offset.copy(),
 | 
				
			||||||
            rotation=self.rotation,
 | 
					            rotation=self.rotation,
 | 
				
			||||||
            scale=self.scale,
 | 
					            scale=self.scale,
 | 
				
			||||||
            mirrored=self.mirrored.copy(),
 | 
					            mirrored=self.mirrored,
 | 
				
			||||||
            repetition=copy.deepcopy(self.repetition),
 | 
					            repetition=copy.deepcopy(self.repetition),
 | 
				
			||||||
            annotations=copy.deepcopy(self.annotations),
 | 
					            annotations=copy.deepcopy(self.annotations),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@ -86,17 +96,6 @@ class Ref(
 | 
				
			|||||||
        new.annotations = copy.deepcopy(self.annotations, memo)
 | 
					        new.annotations = copy.deepcopy(self.annotations, memo)
 | 
				
			||||||
        return new
 | 
					        return new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Mirrored property
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def mirrored(self) -> Any:   # TODO mypy#3004  NDArray[numpy.bool_]:
 | 
					 | 
				
			||||||
        return self._mirrored
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @mirrored.setter
 | 
					 | 
				
			||||||
    def mirrored(self, val: ArrayLike) -> None:
 | 
					 | 
				
			||||||
        if is_scalar(val):
 | 
					 | 
				
			||||||
            raise PatternError('Mirrored must be a 2-element list of booleans')
 | 
					 | 
				
			||||||
        self._mirrored = numpy.array(val, dtype=bool, copy=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def as_pattern(
 | 
					    def as_pattern(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            pattern: 'Pattern',
 | 
					            pattern: 'Pattern',
 | 
				
			||||||
@ -113,8 +112,8 @@ class Ref(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if self.scale != 1:
 | 
					        if self.scale != 1:
 | 
				
			||||||
            pattern.scale_by(self.scale)
 | 
					            pattern.scale_by(self.scale)
 | 
				
			||||||
        if numpy.any(self.mirrored):
 | 
					        if self.mirrored:
 | 
				
			||||||
            pattern.mirror2d(self.mirrored)
 | 
					            pattern.mirror()
 | 
				
			||||||
        if self.rotation % (2 * pi) != 0:
 | 
					        if self.rotation % (2 * pi) != 0:
 | 
				
			||||||
            pattern.rotate_around((0.0, 0.0), self.rotation)
 | 
					            pattern.rotate_around((0.0, 0.0), self.rotation)
 | 
				
			||||||
        if numpy.any(self.offset):
 | 
					        if numpy.any(self.offset):
 | 
				
			||||||
@ -137,13 +136,24 @@ class Ref(
 | 
				
			|||||||
            self.repetition.rotate(rotation)
 | 
					            self.repetition.rotate(rotation)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> Self:
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        self.mirrored[axis] = not self.mirrored[axis]
 | 
					        self.mirror_target(axis)
 | 
				
			||||||
        self.rotation *= -1
 | 
					        self.rotation *= -1
 | 
				
			||||||
        if self.repetition is not None:
 | 
					        if self.repetition is not None:
 | 
				
			||||||
            self.repetition.mirror(axis)
 | 
					            self.repetition.mirror(axis)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def mirror_target(self, axis: int = 0) -> Self:
 | 
				
			||||||
 | 
					        self.mirrored = not self.mirrored
 | 
				
			||||||
 | 
					        self.rotation += axis * pi
 | 
				
			||||||
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def mirror2d_target(self, across_x: bool = False, across_y: bool = False) -> Self:
 | 
				
			||||||
 | 
					        self.mirrored = bool((self.mirrored + across_x + across_y) % 2)
 | 
				
			||||||
 | 
					        if across_y:
 | 
				
			||||||
 | 
					            self.rotation += pi
 | 
				
			||||||
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_bounds_single(
 | 
					    def get_bounds_single(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
            pattern: 'Pattern',
 | 
					            pattern: 'Pattern',
 | 
				
			||||||
@ -169,5 +179,5 @@ class Ref(
 | 
				
			|||||||
    def __repr__(self) -> str:
 | 
					    def __repr__(self) -> str:
 | 
				
			||||||
        rotation = f' r{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
 | 
					        rotation = f' r{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
 | 
				
			||||||
        scale = f' d{self.scale:g}' if self.scale != 1 else ''
 | 
					        scale = f' d{self.scale:g}' if self.scale != 1 else ''
 | 
				
			||||||
        mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
 | 
					        mirrored = ' m' if self.mirrored else ''
 | 
				
			||||||
        return f'<Ref {self.offset}{rotation}{scale}{mirrored}>'
 | 
					        return f'<Ref {self.offset}{rotation}{scale}{mirrored}>'
 | 
				
			||||||
 | 
				
			|||||||
@ -2,8 +2,7 @@
 | 
				
			|||||||
    Repetitions provide support for efficiently representing multiple identical
 | 
					    Repetitions provide support for efficiently representing multiple identical
 | 
				
			||||||
     instances of an object .
 | 
					     instances of an object .
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					from typing import Any, Type, Self, TypeVar
 | 
				
			||||||
from typing import Any, Type
 | 
					 | 
				
			||||||
import copy
 | 
					import copy
 | 
				
			||||||
from abc import ABCMeta, abstractmethod
 | 
					from abc import ABCMeta, abstractmethod
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -15,6 +14,9 @@ from .error import PatternError
 | 
				
			|||||||
from .utils import rotation_matrix_2d
 | 
					from .utils import rotation_matrix_2d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GG = TypeVar('GG', bound='Grid')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Repetition(Copyable, Rotatable, Mirrorable, Scalable, Bounded, metaclass=ABCMeta):
 | 
					class Repetition(Copyable, Rotatable, Mirrorable, Scalable, Bounded, metaclass=ABCMeta):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Interface common to all objects which specify repetitions
 | 
					    Interface common to all objects which specify repetitions
 | 
				
			||||||
@ -104,12 +106,12 @@ class Grid(Repetition):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def aligned(
 | 
					    def aligned(
 | 
				
			||||||
            cls: Type,
 | 
					            cls: Type[GG],
 | 
				
			||||||
            x: float,
 | 
					            x: float,
 | 
				
			||||||
            y: float,
 | 
					            y: float,
 | 
				
			||||||
            x_count: int,
 | 
					            x_count: int,
 | 
				
			||||||
            y_count: int,
 | 
					            y_count: int,
 | 
				
			||||||
            ) -> 'Grid':
 | 
					            ) -> GG:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Simple constructor for an axis-aligned 2D grid
 | 
					        Simple constructor for an axis-aligned 2D grid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -133,7 +135,7 @@ class Grid(Repetition):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        return new
 | 
					        return new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __deepcopy__(self, memo: dict | None = None) -> 'Grid':
 | 
					    def __deepcopy__(self, memo: dict | None = None) -> Self:
 | 
				
			||||||
        memo = {} if memo is None else memo
 | 
					        memo = {} if memo is None else memo
 | 
				
			||||||
        new = copy.copy(self)
 | 
					        new = copy.copy(self)
 | 
				
			||||||
        return new
 | 
					        return new
 | 
				
			||||||
@ -197,7 +199,7 @@ class Grid(Repetition):
 | 
				
			|||||||
        return (aa.flatten()[:, None] * self.a_vector[None, :]
 | 
					        return (aa.flatten()[:, None] * self.a_vector[None, :]
 | 
				
			||||||
              + bb.flatten()[:, None] * self.b_vector[None, :])             # noqa
 | 
					              + bb.flatten()[:, None] * self.b_vector[None, :])             # noqa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rotate(self, rotation: float) -> 'Grid':
 | 
					    def rotate(self, rotation: float) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Rotate lattice vectors (around (0, 0))
 | 
					        Rotate lattice vectors (around (0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -212,7 +214,7 @@ class Grid(Repetition):
 | 
				
			|||||||
            self.b_vector = numpy.dot(rotation_matrix_2d(rotation), self.b_vector)
 | 
					            self.b_vector = numpy.dot(rotation_matrix_2d(rotation), self.b_vector)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Grid':
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the Grid across an axis.
 | 
					        Mirror the Grid across an axis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -248,7 +250,7 @@ class Grid(Repetition):
 | 
				
			|||||||
        xy_max = numpy.max(corners, axis=0)
 | 
					        xy_max = numpy.max(corners, axis=0)
 | 
				
			||||||
        return numpy.array((xy_min, xy_max))
 | 
					        return numpy.array((xy_min, xy_max))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def scale_by(self, c: float) -> 'Grid':
 | 
					    def scale_by(self, c: float) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Scale the Grid by a factor
 | 
					        Scale the Grid by a factor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -327,7 +329,7 @@ class Arbitrary(Repetition):
 | 
				
			|||||||
            return False
 | 
					            return False
 | 
				
			||||||
        return numpy.array_equal(self.displacements, other.displacements)
 | 
					        return numpy.array_equal(self.displacements, other.displacements)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rotate(self, rotation: float) -> 'Arbitrary':
 | 
					    def rotate(self, rotation: float) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Rotate dispacements (around (0, 0))
 | 
					        Rotate dispacements (around (0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -340,7 +342,7 @@ class Arbitrary(Repetition):
 | 
				
			|||||||
        self.displacements = numpy.dot(rotation_matrix_2d(rotation), self.displacements.T).T
 | 
					        self.displacements = numpy.dot(rotation_matrix_2d(rotation), self.displacements.T).T
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Arbitrary':
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the displacements across an axis.
 | 
					        Mirror the displacements across an axis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -366,7 +368,7 @@ class Arbitrary(Repetition):
 | 
				
			|||||||
        xy_max = numpy.max(self.displacements, axis=0)
 | 
					        xy_max = numpy.max(self.displacements, axis=0)
 | 
				
			||||||
        return numpy.array((xy_min, xy_max))
 | 
					        return numpy.array((xy_min, xy_max))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def scale_by(self, c: float) -> 'Arbitrary':
 | 
					    def scale_by(self, c: float) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Scale the displacements by a factor
 | 
					        Scale the displacements by a factor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Sequence, Any
 | 
					from typing import Any
 | 
				
			||||||
import copy
 | 
					import copy
 | 
				
			||||||
import math
 | 
					import math
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -155,7 +155,6 @@ class Arc(Shape):
 | 
				
			|||||||
            *,
 | 
					            *,
 | 
				
			||||||
            offset: ArrayLike = (0.0, 0.0),
 | 
					            offset: ArrayLike = (0.0, 0.0),
 | 
				
			||||||
            rotation: float = 0,
 | 
					            rotation: float = 0,
 | 
				
			||||||
            mirrored: Sequence[bool] = (False, False),
 | 
					 | 
				
			||||||
            repetition: Repetition | None = None,
 | 
					            repetition: Repetition | None = None,
 | 
				
			||||||
            annotations: annotations_t | None = None,
 | 
					            annotations: annotations_t | None = None,
 | 
				
			||||||
            raw: bool = False,
 | 
					            raw: bool = False,
 | 
				
			||||||
@ -179,7 +178,6 @@ class Arc(Shape):
 | 
				
			|||||||
            self.rotation = rotation
 | 
					            self.rotation = rotation
 | 
				
			||||||
            self.repetition = repetition
 | 
					            self.repetition = repetition
 | 
				
			||||||
            self.annotations = annotations if annotations is not None else {}
 | 
					            self.annotations = annotations if annotations is not None else {}
 | 
				
			||||||
        [self.mirror(a) for a, do in enumerate(mirrored) if do]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __deepcopy__(self, memo: dict | None = None) -> 'Arc':
 | 
					    def __deepcopy__(self, memo: dict | None = None) -> 'Arc':
 | 
				
			||||||
        memo = {} if memo is None else memo
 | 
					        memo = {} if memo is None else memo
 | 
				
			||||||
@ -315,7 +313,7 @@ class Arc(Shape):
 | 
				
			|||||||
        self.rotation += theta
 | 
					        self.rotation += theta
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Arc':
 | 
					    def mirror(self, axis: int = 0) -> 'Arc':
 | 
				
			||||||
        self.offset[axis - 1] *= -1
 | 
					        self.offset[axis - 1] *= -1
 | 
				
			||||||
        self.rotation *= -1
 | 
					        self.rotation *= -1
 | 
				
			||||||
        self.rotation += axis * pi
 | 
					        self.rotation += axis * pi
 | 
				
			||||||
 | 
				
			|||||||
@ -96,7 +96,7 @@ class Circle(Shape):
 | 
				
			|||||||
    def rotate(self, theta: float) -> 'Circle':
 | 
					    def rotate(self, theta: float) -> 'Circle':
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Circle':
 | 
					    def mirror(self, axis: int = 0) -> 'Circle':
 | 
				
			||||||
        self.offset *= -1
 | 
					        self.offset *= -1
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Sequence, Any
 | 
					from typing import Any, Self
 | 
				
			||||||
import copy
 | 
					import copy
 | 
				
			||||||
import math
 | 
					import math
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -90,7 +90,6 @@ class Ellipse(Shape):
 | 
				
			|||||||
            *,
 | 
					            *,
 | 
				
			||||||
            offset: ArrayLike = (0.0, 0.0),
 | 
					            offset: ArrayLike = (0.0, 0.0),
 | 
				
			||||||
            rotation: float = 0,
 | 
					            rotation: float = 0,
 | 
				
			||||||
            mirrored: Sequence[bool] = (False, False),
 | 
					 | 
				
			||||||
            repetition: Repetition | None = None,
 | 
					            repetition: Repetition | None = None,
 | 
				
			||||||
            annotations: annotations_t | None = None,
 | 
					            annotations: annotations_t | None = None,
 | 
				
			||||||
            raw: bool = False,
 | 
					            raw: bool = False,
 | 
				
			||||||
@ -109,9 +108,8 @@ class Ellipse(Shape):
 | 
				
			|||||||
            self.rotation = rotation
 | 
					            self.rotation = rotation
 | 
				
			||||||
            self.repetition = repetition
 | 
					            self.repetition = repetition
 | 
				
			||||||
            self.annotations = annotations if annotations is not None else {}
 | 
					            self.annotations = annotations if annotations is not None else {}
 | 
				
			||||||
        [self.mirror(a) for a, do in enumerate(mirrored) if do]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __deepcopy__(self, memo: dict | None = None) -> 'Ellipse':
 | 
					    def __deepcopy__(self, memo: dict | None = None) -> Self:
 | 
				
			||||||
        memo = {} if memo is None else memo
 | 
					        memo = {} if memo is None else memo
 | 
				
			||||||
        new = copy.copy(self)
 | 
					        new = copy.copy(self)
 | 
				
			||||||
        new._offset = self._offset.copy()
 | 
					        new._offset = self._offset.copy()
 | 
				
			||||||
@ -157,17 +155,17 @@ class Ellipse(Shape):
 | 
				
			|||||||
        return numpy.vstack((self.offset - rot_radii[0],
 | 
					        return numpy.vstack((self.offset - rot_radii[0],
 | 
				
			||||||
                             self.offset + rot_radii[1]))
 | 
					                             self.offset + rot_radii[1]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rotate(self, theta: float) -> 'Ellipse':
 | 
					    def rotate(self, theta: float) -> Self:
 | 
				
			||||||
        self.rotation += theta
 | 
					        self.rotation += theta
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Ellipse':
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        self.offset[axis - 1] *= -1
 | 
					        self.offset[axis - 1] *= -1
 | 
				
			||||||
        self.rotation *= -1
 | 
					        self.rotation *= -1
 | 
				
			||||||
        self.rotation += axis * pi
 | 
					        self.rotation += axis * pi
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def scale_by(self, c: float) -> 'Ellipse':
 | 
					    def scale_by(self, c: float) -> Self:
 | 
				
			||||||
        self.radii *= c
 | 
					        self.radii *= c
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -153,7 +153,6 @@ class Path(Shape):
 | 
				
			|||||||
            cap_extensions: ArrayLike | None = None,
 | 
					            cap_extensions: ArrayLike | None = None,
 | 
				
			||||||
            offset: ArrayLike = (0.0, 0.0),
 | 
					            offset: ArrayLike = (0.0, 0.0),
 | 
				
			||||||
            rotation: float = 0,
 | 
					            rotation: float = 0,
 | 
				
			||||||
            mirrored: Sequence[bool] = (False, False),
 | 
					 | 
				
			||||||
            repetition: Repetition | None = None,
 | 
					            repetition: Repetition | None = None,
 | 
				
			||||||
            annotations: annotations_t | None = None,
 | 
					            annotations: annotations_t | None = None,
 | 
				
			||||||
            raw: bool = False,
 | 
					            raw: bool = False,
 | 
				
			||||||
@ -180,7 +179,6 @@ class Path(Shape):
 | 
				
			|||||||
            self.cap = cap
 | 
					            self.cap = cap
 | 
				
			||||||
            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]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __deepcopy__(self, memo: dict | None = None) -> 'Path':
 | 
					    def __deepcopy__(self, memo: dict | None = None) -> 'Path':
 | 
				
			||||||
        memo = {} if memo is None else memo
 | 
					        memo = {} if memo is None else memo
 | 
				
			||||||
@ -200,7 +198,6 @@ class Path(Shape):
 | 
				
			|||||||
            cap_extensions: tuple[float, float] | None = None,
 | 
					            cap_extensions: tuple[float, float] | None = None,
 | 
				
			||||||
            offset: ArrayLike = (0.0, 0.0),
 | 
					            offset: ArrayLike = (0.0, 0.0),
 | 
				
			||||||
            rotation: float = 0,
 | 
					            rotation: float = 0,
 | 
				
			||||||
            mirrored: Sequence[bool] = (False, False),
 | 
					 | 
				
			||||||
            ) -> 'Path':
 | 
					            ) -> 'Path':
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Build a path by specifying the turn angles and travel distances
 | 
					        Build a path by specifying the turn angles and travel distances
 | 
				
			||||||
@ -217,9 +214,6 @@ class Path(Shape):
 | 
				
			|||||||
                Default `(0, 0)` or `None`, depending on cap type
 | 
					                Default `(0, 0)` or `None`, depending on cap type
 | 
				
			||||||
            offset: Offset, default `(0, 0)`
 | 
					            offset: Offset, default `(0, 0)`
 | 
				
			||||||
            rotation: Rotation counterclockwise, in radians. Default `0`
 | 
					            rotation: Rotation counterclockwise, in radians. Default `0`
 | 
				
			||||||
            mirrored: Whether to mirror across the x or y axes. For example,
 | 
					 | 
				
			||||||
                `mirrored=(True, False)` results in a reflection across the x-axis,
 | 
					 | 
				
			||||||
                multiplying the path's y-coordinates by -1. Default `(False, False)`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            The resulting Path object
 | 
					            The resulting Path object
 | 
				
			||||||
@ -233,7 +227,7 @@ class Path(Shape):
 | 
				
			|||||||
            verts.append(verts[-1] + direction * distance)
 | 
					            verts.append(verts[-1] + direction * distance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Path(vertices=verts, width=width, cap=cap, cap_extensions=cap_extensions,
 | 
					        return Path(vertices=verts, width=width, cap=cap, cap_extensions=cap_extensions,
 | 
				
			||||||
                    offset=offset, rotation=rotation, mirrored=mirrored)
 | 
					                    offset=offset, rotation=rotation)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def to_polygons(
 | 
					    def to_polygons(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
@ -334,7 +328,7 @@ class Path(Shape):
 | 
				
			|||||||
            self.vertices = numpy.dot(rotation_matrix_2d(theta), self.vertices.T).T
 | 
					            self.vertices = numpy.dot(rotation_matrix_2d(theta), self.vertices.T).T
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Path':
 | 
					    def mirror(self, axis: int = 0) -> 'Path':
 | 
				
			||||||
        self.vertices[:, axis - 1] *= -1
 | 
					        self.vertices[:, axis - 1] *= -1
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -81,7 +81,6 @@ class Polygon(Shape):
 | 
				
			|||||||
            *,
 | 
					            *,
 | 
				
			||||||
            offset: ArrayLike = (0.0, 0.0),
 | 
					            offset: ArrayLike = (0.0, 0.0),
 | 
				
			||||||
            rotation: float = 0.0,
 | 
					            rotation: float = 0.0,
 | 
				
			||||||
            mirrored: Sequence[bool] = (False, False),
 | 
					 | 
				
			||||||
            repetition: Repetition | None = None,
 | 
					            repetition: Repetition | None = None,
 | 
				
			||||||
            annotations: annotations_t | None = None,
 | 
					            annotations: annotations_t | None = None,
 | 
				
			||||||
            raw: bool = False,
 | 
					            raw: bool = False,
 | 
				
			||||||
@ -99,7 +98,6 @@ class Polygon(Shape):
 | 
				
			|||||||
            self.repetition = repetition
 | 
					            self.repetition = repetition
 | 
				
			||||||
            self.annotations = annotations if annotations is not None else {}
 | 
					            self.annotations = annotations if annotations is not None else {}
 | 
				
			||||||
        self.rotate(rotation)
 | 
					        self.rotate(rotation)
 | 
				
			||||||
        [self.mirror(a) for a, do in enumerate(mirrored) if do]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __deepcopy__(self, memo: dict | None = None) -> 'Polygon':
 | 
					    def __deepcopy__(self, memo: dict | None = None) -> 'Polygon':
 | 
				
			||||||
        memo = {} if memo is None else memo
 | 
					        memo = {} if memo is None else memo
 | 
				
			||||||
@ -336,7 +334,7 @@ class Polygon(Shape):
 | 
				
			|||||||
            self.vertices = numpy.dot(rotation_matrix_2d(theta), self.vertices.T).T
 | 
					            self.vertices = numpy.dot(rotation_matrix_2d(theta), self.vertices.T).T
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Polygon':
 | 
					    def mirror(self, axis: int = 0) -> 'Polygon':
 | 
				
			||||||
        self.vertices[:, axis - 1] *= -1
 | 
					        self.vertices[:, axis - 1] *= -1
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
from typing import Sequence, Any
 | 
					from typing import Self
 | 
				
			||||||
import copy
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import numpy
 | 
					import numpy
 | 
				
			||||||
@ -9,8 +9,7 @@ from . import Shape, Polygon, normalized_shape_tuple
 | 
				
			|||||||
from ..error import PatternError
 | 
					from ..error import PatternError
 | 
				
			||||||
from ..repetition import Repetition
 | 
					from ..repetition import Repetition
 | 
				
			||||||
from ..traits import RotatableImpl
 | 
					from ..traits import RotatableImpl
 | 
				
			||||||
from ..utils import is_scalar, get_bit, normalize_mirror
 | 
					from ..utils import is_scalar, get_bit, annotations_t
 | 
				
			||||||
from ..utils import annotations_t
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Loaded on use:
 | 
					# Loaded on use:
 | 
				
			||||||
# from freetype import Face
 | 
					# from freetype import Face
 | 
				
			||||||
@ -30,7 +29,7 @@ class Text(RotatableImpl, Shape):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    _string: str
 | 
					    _string: str
 | 
				
			||||||
    _height: float
 | 
					    _height: float
 | 
				
			||||||
    _mirrored: NDArray[numpy.bool_]
 | 
					    _mirrored: bool
 | 
				
			||||||
    font_path: str
 | 
					    font_path: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # vertices property
 | 
					    # vertices property
 | 
				
			||||||
@ -53,16 +52,13 @@ class Text(RotatableImpl, Shape):
 | 
				
			|||||||
            raise PatternError('Height must be a scalar')
 | 
					            raise PatternError('Height must be a scalar')
 | 
				
			||||||
        self._height = val
 | 
					        self._height = val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Mirrored property
 | 
					 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def mirrored(self) -> Any:   # TODO mypy#3004  NDArray[numpy.bool_]:
 | 
					    def mirrored(self) -> bool:     # mypy#3004, should be bool
 | 
				
			||||||
        return self._mirrored
 | 
					        return self._mirrored
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @mirrored.setter
 | 
					    @mirrored.setter
 | 
				
			||||||
    def mirrored(self, val: Sequence[bool]) -> None:
 | 
					    def mirrored(self, val: bool) -> None:
 | 
				
			||||||
        if is_scalar(val):
 | 
					        self._mirrored = bool(val)
 | 
				
			||||||
            raise PatternError('Mirrored must be a 2-element list of booleans')
 | 
					 | 
				
			||||||
        self._mirrored = numpy.array(val, dtype=bool, copy=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(
 | 
				
			||||||
            self,
 | 
					            self,
 | 
				
			||||||
@ -72,19 +68,16 @@ class Text(RotatableImpl, Shape):
 | 
				
			|||||||
            *,
 | 
					            *,
 | 
				
			||||||
            offset: ArrayLike = (0.0, 0.0),
 | 
					            offset: ArrayLike = (0.0, 0.0),
 | 
				
			||||||
            rotation: float = 0.0,
 | 
					            rotation: float = 0.0,
 | 
				
			||||||
            mirrored: ArrayLike = (False, False),
 | 
					 | 
				
			||||||
            repetition: Repetition | None = None,
 | 
					            repetition: Repetition | None = None,
 | 
				
			||||||
            annotations: annotations_t | None = None,
 | 
					            annotations: annotations_t | None = None,
 | 
				
			||||||
            raw: bool = False,
 | 
					            raw: bool = False,
 | 
				
			||||||
            ) -> None:
 | 
					            ) -> None:
 | 
				
			||||||
        if raw:
 | 
					        if raw:
 | 
				
			||||||
            assert isinstance(offset, numpy.ndarray)
 | 
					            assert isinstance(offset, numpy.ndarray)
 | 
				
			||||||
            assert isinstance(mirrored, numpy.ndarray)
 | 
					 | 
				
			||||||
            self._offset = offset
 | 
					            self._offset = offset
 | 
				
			||||||
            self._string = string
 | 
					            self._string = string
 | 
				
			||||||
            self._height = height
 | 
					            self._height = height
 | 
				
			||||||
            self._rotation = rotation
 | 
					            self._rotation = rotation
 | 
				
			||||||
            self._mirrored = mirrored
 | 
					 | 
				
			||||||
            self._repetition = repetition
 | 
					            self._repetition = repetition
 | 
				
			||||||
            self._annotations = annotations if annotations is not None else {}
 | 
					            self._annotations = annotations if annotations is not None else {}
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
@ -92,16 +85,14 @@ class Text(RotatableImpl, Shape):
 | 
				
			|||||||
            self.string = string
 | 
					            self.string = string
 | 
				
			||||||
            self.height = height
 | 
					            self.height = height
 | 
				
			||||||
            self.rotation = rotation
 | 
					            self.rotation = rotation
 | 
				
			||||||
            self.mirrored = mirrored
 | 
					 | 
				
			||||||
            self.repetition = repetition
 | 
					            self.repetition = repetition
 | 
				
			||||||
            self.annotations = annotations if annotations is not None else {}
 | 
					            self.annotations = annotations if annotations is not None else {}
 | 
				
			||||||
        self.font_path = font_path
 | 
					        self.font_path = font_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __deepcopy__(self, memo: dict | None = None) -> 'Text':
 | 
					    def __deepcopy__(self, memo: dict | None = None) -> Self:
 | 
				
			||||||
        memo = {} if memo is None else memo
 | 
					        memo = {} if memo is None else memo
 | 
				
			||||||
        new = copy.copy(self)
 | 
					        new = copy.copy(self)
 | 
				
			||||||
        new._offset = self._offset.copy()
 | 
					        new._offset = self._offset.copy()
 | 
				
			||||||
        new._mirrored = copy.deepcopy(self._mirrored, memo)
 | 
					 | 
				
			||||||
        new._annotations = copy.deepcopy(self._annotations)
 | 
					        new._annotations = copy.deepcopy(self._annotations)
 | 
				
			||||||
        return new
 | 
					        return new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -118,7 +109,8 @@ class Text(RotatableImpl, Shape):
 | 
				
			|||||||
            # Move these polygons to the right of the previous letter
 | 
					            # Move these polygons to the right of the previous letter
 | 
				
			||||||
            for xys in raw_polys:
 | 
					            for xys in raw_polys:
 | 
				
			||||||
                poly = Polygon(xys)
 | 
					                poly = Polygon(xys)
 | 
				
			||||||
                poly.mirror2d(self.mirrored)
 | 
					                if self.mirrored:
 | 
				
			||||||
 | 
					                    poly.mirror()
 | 
				
			||||||
                poly.scale_by(self.height)
 | 
					                poly.scale_by(self.height)
 | 
				
			||||||
                poly.offset = self.offset + [total_advance, 0]
 | 
					                poly.offset = self.offset + [total_advance, 0]
 | 
				
			||||||
                poly.rotate_around(self.offset, self.rotation)
 | 
					                poly.rotate_around(self.offset, self.rotation)
 | 
				
			||||||
@ -129,27 +121,27 @@ class Text(RotatableImpl, Shape):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return all_polygons
 | 
					        return all_polygons
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror(self, axis: int) -> 'Text':
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        self.mirrored[axis] = not self.mirrored[axis]
 | 
					        self.mirrored = not self.mirrored
 | 
				
			||||||
 | 
					        if axis == 1:
 | 
				
			||||||
 | 
					            self.rotation += pi
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def scale_by(self, c: float) -> 'Text':
 | 
					    def scale_by(self, c: float) -> Self:
 | 
				
			||||||
        self.height *= c
 | 
					        self.height *= c
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def normalized_form(self, norm_value: float) -> normalized_shape_tuple:
 | 
					    def normalized_form(self, norm_value: float) -> normalized_shape_tuple:
 | 
				
			||||||
        mirror_x, rotation = normalize_mirror(self.mirrored)
 | 
					        rotation = self.rotation % (2 * pi)
 | 
				
			||||||
        rotation += self.rotation
 | 
					 | 
				
			||||||
        rotation %= 2 * pi
 | 
					 | 
				
			||||||
        return ((type(self), self.string, self.font_path),
 | 
					        return ((type(self), self.string, self.font_path),
 | 
				
			||||||
                (self.offset, self.height / norm_value, rotation, mirror_x),
 | 
					                (self.offset, self.height / norm_value, rotation, bool(self.mirrored)),
 | 
				
			||||||
                lambda: Text(
 | 
					                lambda: Text(
 | 
				
			||||||
                    string=self.string,
 | 
					                    string=self.string,
 | 
				
			||||||
                    height=self.height * norm_value,
 | 
					                    height=self.height * norm_value,
 | 
				
			||||||
                    font_path=self.font_path,
 | 
					                    font_path=self.font_path,
 | 
				
			||||||
                    rotation=rotation,
 | 
					                    rotation=rotation,
 | 
				
			||||||
                    mirrored=(mirror_x, False),
 | 
					                    ).mirror2d(across_x=self.mirrored),
 | 
				
			||||||
                    ))
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_bounds_single(self) -> NDArray[numpy.float64]:
 | 
					    def get_bounds_single(self) -> NDArray[numpy.float64]:
 | 
				
			||||||
        # rotation makes this a huge pain when using slot.advance and glyph.bbox(), so
 | 
					        # rotation makes this a huge pain when using slot.advance and glyph.bbox(), so
 | 
				
			||||||
@ -258,5 +250,5 @@ def get_char_as_polygons(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def __repr__(self) -> str:
 | 
					    def __repr__(self) -> str:
 | 
				
			||||||
        rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
 | 
					        rotation = f' r°{numpy.rad2deg(self.rotation):g}' if self.rotation != 0 else ''
 | 
				
			||||||
        mirrored = ' m{:d}{:d}'.format(*self.mirrored) if self.mirrored.any() else ''
 | 
					        mirrored = ' m{:d}' if self.mirrored else ''
 | 
				
			||||||
        return f'<TextShape "{self.string}" o{self.offset} h{self.height:g}{rotation}{mirrored}>'
 | 
					        return f'<TextShape "{self.string}" o{self.offset} h{self.height:g}{rotation}{mirrored}>'
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ class Mirrorable(metaclass=ABCMeta):
 | 
				
			|||||||
    __slots__ = ()
 | 
					    __slots__ = ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @abstractmethod
 | 
					    @abstractmethod
 | 
				
			||||||
    def mirror(self, axis: int) -> Self:
 | 
					    def mirror(self, axis: int = 0) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mirror the entity across an axis.
 | 
					        Mirror the entity across an axis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -21,7 +21,7 @@ class Mirrorable(metaclass=ABCMeta):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def mirror2d(self, axes: tuple[bool, bool]) -> Self:
 | 
					    def mirror2d(self, across_x: bool = False, across_y: bool = False) -> Self:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Optionally mirror the entity across both axes
 | 
					        Optionally mirror the entity across both axes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,9 +31,9 @@ class Mirrorable(metaclass=ABCMeta):
 | 
				
			|||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            self
 | 
					            self
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if axes[0]:
 | 
					        if across_x:
 | 
				
			||||||
            self.mirror(0)
 | 
					            self.mirror(0)
 | 
				
			||||||
        if axes[1]:
 | 
					        if across_y:
 | 
				
			||||||
            self.mirror(1)
 | 
					            self.mirror(1)
 | 
				
			||||||
        return self
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user