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