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…
Reference in New Issue
Block a user