From b843ffb4d33ed57fe83f7d4d6696b712da03d024 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 30 Mar 2026 21:11:00 -0700 Subject: [PATCH] [ILibraryView / Pattern] flatten() shouldn't drop ports-only patterns if flatten_ports=True --- masque/library.py | 3 ++- masque/pattern.py | 3 ++- masque/test/test_library.py | 20 +++++++++++++++++++- masque/test/test_pattern.py | 15 +++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/masque/library.py b/masque/library.py index 8dc63a4..5b32260 100644 --- a/masque/library.py +++ b/masque/library.py @@ -303,7 +303,8 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): target_pat = flattened[target] if target_pat is None: raise PatternError(f'Circular reference in {name} to {target}') - if target_pat.is_empty(): # avoid some extra allocations + ports_only = flatten_ports and bool(target_pat.ports) + if target_pat.is_empty() and not ports_only: # avoid some extra allocations continue for ref in pat.refs[target]: diff --git a/masque/pattern.py b/masque/pattern.py index 46b564a..9014298 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -1077,7 +1077,8 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): if target_pat is None: raise PatternError(f'Circular reference in {name} to {target}') - if target_pat.is_empty(): # avoid some extra allocations + ports_only = flatten_ports and bool(target_pat.ports) + if target_pat.is_empty() and not ports_only: # avoid some extra allocations continue for ref in refs: diff --git a/masque/test/test_library.py b/masque/test/test_library.py index 22ad42a..5b143fa 100644 --- a/masque/test/test_library.py +++ b/masque/test/test_library.py @@ -2,7 +2,9 @@ import pytest from typing import cast, TYPE_CHECKING from ..library import Library, LazyLibrary from ..pattern import Pattern -from ..error import LibraryError +from ..error import LibraryError, PatternError +from ..ports import Port +from ..repetition import Grid if TYPE_CHECKING: from ..shapes import Polygon @@ -59,6 +61,22 @@ def test_library_flatten() -> None: assert tuple(assert_vertices[0]) == (10.0, 10.0) +def test_library_flatten_preserves_ports_only_child() -> None: + lib = Library() + child = Pattern(ports={"P1": Port((1, 2), 0)}) + lib["child"] = child + + parent = Pattern() + parent.ref("child", offset=(10, 10)) + lib["parent"] = parent + + flat_parent = lib.flatten("parent", flatten_ports=True)["parent"] + + assert set(flat_parent.ports) == {"P1"} + assert cast("Port", flat_parent.ports["P1"]).rotation == 0 + assert tuple(flat_parent.ports["P1"].offset) == (11.0, 12.0) + + def test_lazy_library() -> None: lib = LazyLibrary() called = 0 diff --git a/masque/test/test_pattern.py b/masque/test/test_pattern.py index f5da195..338bd39 100644 --- a/masque/test/test_pattern.py +++ b/masque/test/test_pattern.py @@ -1,12 +1,15 @@ +import pytest from typing import cast from numpy.testing import assert_equal, assert_allclose from numpy import pi +from ..error import PatternError from ..pattern import Pattern from ..shapes import Polygon from ..ref import Ref from ..ports import Port from ..label import Label +from ..repetition import Grid def test_pattern_init() -> None: @@ -99,6 +102,18 @@ def test_pattern_get_bounds() -> None: assert_equal(bounds, [[-5, -5], [10, 10]]) +def test_pattern_flatten_preserves_ports_only_child() -> None: + child = Pattern(ports={"P1": Port((1, 2), 0)}) + + parent = Pattern() + parent.ref("child", offset=(10, 10)) + + parent.flatten({"child": child}, flatten_ports=True) + + assert set(parent.ports) == {"P1"} + assert parent.ports["P1"].rotation == 0 + assert tuple(parent.ports["P1"].offset) == (11.0, 12.0) + def test_pattern_interface() -> None: source = Pattern() source.ports["A"] = Port((10, 20), 0, ptype="test")