diff --git a/masque/builder/pather.py b/masque/builder/pather.py index e8804d1..3478c32 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -415,17 +415,8 @@ class Pather(PortList): self._apply_dead_fallback(portspec, length, jog, None, in_ptype, plug_into, out_rot=pi) return self - try: - out_port0, data0 = tool.planL(ccw0, L1, in_ptype=in_ptype, **(kwargs | {'out_ptype': None})) - out_port1, data1 = tool.planL(not ccw0, L2, in_ptype=out_port0.ptype, **kwargs) - except (BuildError, NotImplementedError): - if not self._dead: - raise - self._apply_dead_fallback(portspec, length, jog, None, in_ptype, plug_into, out_rot=pi) - return self - - self._apply_step('L', portspec, out_port0, data0, tool) - self._apply_step('L', portspec, out_port1, data1, tool, plug_into) + self._traceL(portspec, ccw0, L1, **(kwargs | {'out_ptype': None})) + self._traceL(portspec, not ccw0, L2, **(kwargs | {'plug_into': plug_into})) return self if out_port is not None: self._apply_step('S', portspec, out_port, data, tool, plug_into) @@ -445,16 +436,14 @@ class Pather(PortList): try: R = self._get_tool_R(tool, ccw, in_ptype, **kwargs) L1, L2 = length + R, abs(jog) - R - out_port0, data0 = tool.planL(ccw, L1, in_ptype=in_ptype, **(kwargs | {'out_ptype': None})) - out_port1, data1 = tool.planL(ccw, L2, in_ptype=out_port0.ptype, **kwargs) + self._traceL(portspec, ccw, L1, **(kwargs | {'out_ptype': None})) + self._traceL(portspec, ccw, L2, **(kwargs | {'plug_into': plug_into})) except (BuildError, NotImplementedError): if not self._dead: raise self._apply_dead_fallback(portspec, length, jog, None, in_ptype, plug_into, out_rot=0) return self else: - self._apply_step('L', portspec, out_port0, data0, tool) - self._apply_step('L', portspec, out_port1, data1, tool, plug_into) return self if out_port is not None: self._apply_step('U', portspec, out_port, data, tool, plug_into) diff --git a/masque/builder/tools.py b/masque/builder/tools.py index f8779bd..4a82b34 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -1031,7 +1031,7 @@ class AutoTool(Tool, metaclass=ABCMeta): jog_remaining = jog - itrans_dxy[1] - otrans_dxy[1] if sbend.jog_range[0] <= jog_remaining < sbend.jog_range[1]: sbend_dxy = self._sbend2dxy(sbend, jog_remaining) - success = numpy.isclose(length, sbend_dxy[0] + itrans_dxy[0] + otrans_dxy[0]) + success = numpy.isclose(length, sbend_dxy[0] + itrans_dxy[1] + otrans_dxy[1]) if success: b_transition = None straight_length = 0 @@ -1244,7 +1244,7 @@ class PathTool(Tool, metaclass=ABCMeta): port_names: tuple[str, str] = ('A', 'B'), **kwargs, # noqa: ARG002 (unused) ) -> Library: - out_port, _data = self.planL( + out_port, dxy = self.planL( ccw, length, in_ptype=in_ptype, @@ -1252,12 +1252,7 @@ class PathTool(Tool, metaclass=ABCMeta): ) tree, pat = Library.mktree(SINGLE_USE_PREFIX + 'traceL') - vertices: list[tuple[float, float]] - if ccw is None: - vertices = [(0.0, 0.0), (length, 0.0)] - else: - vertices = [(0.0, 0.0), (length, 0.0), tuple(out_port.offset)] - pat.path(layer=self.layer, width=self.width, vertices=vertices) + pat.path(layer=self.layer, width=self.width, vertices=[(0, 0), (length, 0)]) if ccw is None: out_rot = pi @@ -1268,7 +1263,7 @@ class PathTool(Tool, metaclass=ABCMeta): pat.ports = { port_names[0]: Port((0, 0), rotation=0, ptype=self.ptype), - port_names[1]: Port(out_port.offset, rotation=out_rot, ptype=self.ptype), + port_names[1]: Port(dxy, rotation=out_rot, ptype=self.ptype), } return tree diff --git a/masque/shapes/path.py b/masque/shapes/path.py index 3aa6f07..3df55f4 100644 --- a/masque/shapes/path.py +++ b/masque/shapes/path.py @@ -453,8 +453,6 @@ class Path(Shape): def scale_by(self, c: float) -> 'Path': self.vertices *= c self.width *= c - if self.cap_extensions is not None: - self.cap_extensions *= c return self def normalized_form(self, norm_value: float) -> normalized_shape_tuple: @@ -478,15 +476,13 @@ class Path(Shape): reordered_vertices = rotated_vertices width0 = self.width / norm_value - cap_extensions0 = None if self.cap_extensions is None else tuple(float(v) / norm_value for v in self.cap_extensions) - return ((type(self), reordered_vertices.data.tobytes(), width0, self.cap, cap_extensions0), + return ((type(self), reordered_vertices.data.tobytes(), width0, self.cap), (offset, scale / norm_value, rotation, False), lambda: Path( reordered_vertices * norm_value, - width=width0 * norm_value, + width=self.width * norm_value, cap=self.cap, - cap_extensions=None if cap_extensions0 is None else tuple(v * norm_value for v in cap_extensions0), )) def clean_vertices(self) -> 'Path': diff --git a/masque/test/test_autotool_refactor.py b/masque/test/test_autotool_refactor.py index d93f935..677ddd6 100644 --- a/masque/test/test_autotool_refactor.py +++ b/masque/test/test_autotool_refactor.py @@ -108,62 +108,6 @@ def test_autotool_planS_double_L(multi_bend_tool) -> None: assert data.ldata1.straight_length == 0 assert data.l2_length == 6 - -def test_autotool_planS_pure_sbend_with_transition_dx() -> None: - lib = Library() - - def make_straight(length: float) -> Pattern: - pat = Pattern() - pat.ports["A"] = Port((0, 0), 0, ptype="core") - pat.ports["B"] = Port((length, 0), pi, ptype="core") - return pat - - def make_sbend(jog: float) -> Pattern: - pat = Pattern() - pat.ports["A"] = Port((0, 0), 0, ptype="core") - pat.ports["B"] = Port((10, jog), pi, ptype="core") - return pat - - trans_pat = Pattern() - trans_pat.ports["EXT"] = Port((0, 0), 0, ptype="ext") - trans_pat.ports["CORE"] = Port((5, 0), pi, ptype="core") - lib["xin"] = trans_pat - - tool = AutoTool( - straights=[ - AutoTool.Straight( - ptype="core", - fn=make_straight, - in_port_name="A", - out_port_name="B", - length_range=(1, 1e8), - ) - ], - bends=[], - sbends=[ - AutoTool.SBend( - ptype="core", - fn=make_sbend, - in_port_name="A", - out_port_name="B", - jog_range=(0, 1e8), - ) - ], - transitions={ - ("ext", "core"): AutoTool.Transition(lib.abstract("xin"), "EXT", "CORE"), - }, - default_out_ptype="core", - ) - - p, data = tool.planS(15, 4, in_ptype="ext") - - assert_allclose(p.offset, [15, 4]) - assert_allclose(p.rotation, pi) - assert data.straight_length == 0 - assert data.jog_remaining == 4 - assert data.in_transition is not None - - def test_renderpather_autotool_double_L(multi_bend_tool) -> None: tool, lib = multi_bend_tool rp = RenderPather(lib, tools=tool) diff --git a/masque/test/test_library.py b/masque/test/test_library.py index e58bd10..0a04d98 100644 --- a/masque/test/test_library.py +++ b/masque/test/test_library.py @@ -6,7 +6,6 @@ from ..pattern import Pattern from ..error import LibraryError, PatternError from ..ports import Port from ..repetition import Grid -from ..shapes import Path from ..file.utils import preflight if TYPE_CHECKING: @@ -244,18 +243,3 @@ def test_library_get_name() -> None: name2 = lib.get_name("other") assert name2 == "other" - - -def test_library_dedup_shapes_does_not_merge_custom_capped_paths() -> None: - lib = Library() - pat = Pattern() - pat.shapes[(1, 0)] += [ - Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(1, 2)), - Path(vertices=[[20, 0], [30, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(3, 4)), - ] - lib["top"] = pat - - lib.dedup(norm_value=1, threshold=2) - - assert not lib["top"].refs - assert len(lib["top"].shapes[(1, 0)]) == 2 diff --git a/masque/test/test_pack2d.py b/masque/test/test_pack2d.py index 914c23e..5390a4c 100644 --- a/masque/test/test_pack2d.py +++ b/masque/test/test_pack2d.py @@ -1,4 +1,4 @@ -from ..utils.pack2d import maxrects_bssf, guillotine_bssf_sas, pack_patterns +from ..utils.pack2d import maxrects_bssf, pack_patterns from ..library import Library from ..pattern import Pattern @@ -25,34 +25,6 @@ def test_maxrects_bssf_reject() -> None: assert 0 not in rejects -def test_maxrects_bssf_exact_fill_rejects_remaining() -> None: - rects = [[20, 20], [1, 1]] - containers = [[0, 0, 20, 20]] - - locs, rejects = maxrects_bssf(rects, containers, presort=False, allow_rejects=True) - - assert tuple(locs[0]) == (0.0, 0.0) - assert rejects == {1} - - -def test_maxrects_bssf_presort_reject_mapping() -> None: - rects = [[10, 12], [19, 14], [13, 11]] - containers = [[0, 0, 20, 20]] - - _locs, rejects = maxrects_bssf(rects, containers, presort=True, allow_rejects=True) - - assert rejects == {0, 2} - - -def test_guillotine_bssf_sas_presort_reject_mapping() -> None: - rects = [[2, 1], [17, 15], [16, 11]] - containers = [[0, 0, 20, 20]] - - _locs, rejects = guillotine_bssf_sas(rects, containers, presort=True, allow_rejects=True) - - assert rejects == {2} - - def test_pack_patterns() -> None: lib = Library() p1 = Pattern() @@ -77,20 +49,3 @@ def test_pack_patterns() -> None: # p1 size 10x10, effectively 12x12 # p2 size 5x5, effectively 7x7 # Both should fit in 20x20 - - -def test_pack_patterns_reject_names_match_original_patterns() -> None: - lib = Library() - for name, (lx, ly) in { - "p0": (10, 12), - "p1": (19, 14), - "p2": (13, 11), - }.items(): - pat = Pattern() - pat.rect((1, 0), xmin=0, xmax=lx, ymin=0, ymax=ly) - lib[name] = pat - - pat, rejects = pack_patterns(lib, ["p0", "p1", "p2"], [[0, 0, 20, 20]], spacing=(0, 0)) - - assert set(rejects) == {"p0", "p2"} - assert set(pat.refs) == {"p1"} diff --git a/masque/test/test_path.py b/masque/test/test_path.py index 1cdd872..766798f 100644 --- a/masque/test/test_path.py +++ b/masque/test/test_path.py @@ -1,4 +1,4 @@ -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_equal from ..shapes import Path @@ -79,33 +79,3 @@ def test_path_scale() -> None: p.scale_by(2) assert_equal(p.vertices, [[0, 0], [20, 0]]) assert p.width == 4 - - -def test_path_scale_custom_cap_extensions() -> None: - p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(1, 2)) - p.scale_by(3) - - assert_equal(p.vertices, [[0, 0], [30, 0]]) - assert p.width == 6 - assert p.cap_extensions is not None - assert_allclose(p.cap_extensions, [3, 6]) - assert_equal(p.to_polygons()[0].get_bounds_single(), [[-3, -3], [36, 3]]) - - -def test_path_normalized_form_preserves_width_and_custom_cap_extensions() -> None: - p = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(1, 2)) - - intrinsic, _extrinsic, ctor = p.normalized_form(5) - q = ctor() - - assert intrinsic[-1] == (0.2, 0.4) - assert q.width == 2 - assert q.cap_extensions is not None - assert_allclose(q.cap_extensions, [1, 2]) - - -def test_path_normalized_form_distinguishes_custom_caps() -> None: - p1 = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(1, 2)) - p2 = Path(vertices=[[0, 0], [10, 0]], width=2, cap=Path.Cap.SquareCustom, cap_extensions=(3, 4)) - - assert p1.normalized_form(1)[0] != p2.normalized_form(1)[0] diff --git a/masque/test/test_pather_api.py b/masque/test/test_pather_api.py index c837280..9ac1b78 100644 --- a/masque/test/test_pather_api.py +++ b/masque/test/test_pather_api.py @@ -1,9 +1,7 @@ -import pytest import numpy from numpy import pi from masque import Pather, RenderPather, Library, Pattern, Port from masque.builder.tools import PathTool -from masque.error import BuildError def test_pather_trace_basic() -> None: lib = Library() @@ -242,31 +240,3 @@ def test_pather_trace_into() -> None: assert numpy.allclose(p.pattern.ports['G'].offset, (-10000, 2000)) assert p.pattern.ports['G'].rotation is not None assert numpy.isclose(p.pattern.ports['G'].rotation, pi) - - -def test_pather_jog_failed_fallback_is_atomic() -> None: - lib = Library() - tool = PathTool(layer='M1', width=2, ptype='wire') - p = Pather(lib, tools=tool) - p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire') - - with pytest.raises(BuildError, match='shorter than required bend'): - p.jog('A', 1.5, length=5) - - assert numpy.allclose(p.pattern.ports['A'].offset, (0, 0)) - assert p.pattern.ports['A'].rotation == 0 - assert len(p.paths['A']) == 0 - - -def test_pather_uturn_failed_fallback_is_atomic() -> None: - lib = Library() - tool = PathTool(layer='M1', width=2, ptype='wire') - p = Pather(lib, tools=tool) - p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire') - - with pytest.raises(BuildError, match='shorter than required bend'): - p.uturn('A', 1.5, length=0) - - assert numpy.allclose(p.pattern.ports['A'].offset, (0, 0)) - assert p.pattern.ports['A'].rotation == 0 - assert len(p.paths['A']) == 0 diff --git a/masque/test/test_renderpather.py b/masque/test/test_renderpather.py index 3ad0d95..ee04671 100644 --- a/masque/test/test_renderpather.py +++ b/masque/test/test_renderpather.py @@ -119,14 +119,3 @@ def test_renderpather_rename_port(rpather_setup: tuple[RenderPather, PathTool, L assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20]], atol=1e-10) assert "new_start" in rp.ports assert_allclose(rp.ports["new_start"].offset, [0, -20], atol=1e-10) - - -def test_pathtool_traceL_bend_geometry_matches_ports() -> None: - tool = PathTool(layer=(1, 0), width=2, ptype="wire") - - tree = tool.traceL(True, 10) - pat = tree.top_pattern() - path_shape = cast("Path", pat.shapes[(1, 0)][0]) - - assert_allclose(path_shape.vertices, [[0, 0], [10, 0], [10, 1]], atol=1e-10) - assert_allclose(pat.ports["B"].offset, [10, 1], atol=1e-10) diff --git a/masque/utils/pack2d.py b/masque/utils/pack2d.py index 248f408..a99b01e 100644 --- a/masque/utils/pack2d.py +++ b/masque/utils/pack2d.py @@ -60,12 +60,6 @@ def maxrects_bssf( degenerate = (min_more & max_less).any(axis=0) regions = regions[~degenerate] - if regions.shape[0] == 0: - if allow_rejects: - rejected_inds.add(rect_ind) - continue - raise MasqueError(f'Failed to find a suitable location for rectangle {rect_ind}') - ''' Place the rect ''' # Best short-side fit (bssf) to pick a region region_sizes = regions[:, 2:] - regions[:, :2] @@ -108,7 +102,7 @@ def maxrects_bssf( if presort: unsort_order = rect_order.argsort() rect_locs = rect_locs[unsort_order] - rejected_inds = {int(rect_order[ii]) for ii in rejected_inds} + rejected_inds = set(unsort_order[list(rejected_inds)]) return rect_locs, rejected_inds @@ -193,7 +187,7 @@ def guillotine_bssf_sas( if presort: unsort_order = rect_order.argsort() rect_locs = rect_locs[unsort_order] - rejected_inds = {int(rect_order[ii]) for ii in rejected_inds} + rejected_inds = set(unsort_order[list(rejected_inds)]) return rect_locs, rejected_inds