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