diff --git a/masque/builder/pather.py b/masque/builder/pather.py index 49ad3be..e8804d1 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -228,17 +228,7 @@ class Pather(PortList): @logged_op(lambda args: list(args['mapping'].keys())) def rename_ports(self, mapping: dict[str, str | None], overwrite: bool = False) -> Self: self.pattern.rename_ports(mapping, overwrite) - renamed: dict[str, list[RenderStep]] = {} - for kk, vv in mapping.items(): - if kk not in self.paths: - continue - steps = self.paths.pop(kk) - # Preserve deferred geometry even if the live port is deleted. - # `render()` can still materialize the saved steps using their stored start/end ports. - # Current semantics intentionally keep deleted ports' queued steps under the old key, - # so if a new live port later reuses that name it does not retarget the old geometry; - # the old and new routes merely share a render bucket until `render()` consumes them. - renamed[kk if vv is None else vv] = steps + renamed: dict[str, list[RenderStep]] = {vv: self.paths.pop(kk) for kk, vv in mapping.items() if kk in self.paths and vv is not None} self.paths.update(renamed) return self @@ -799,9 +789,6 @@ class PortPather: # # Delegate to port # - # These mutate only the selected live port state. They do not rewrite already planned - # RenderSteps, so deferred geometry remains as previously planned and only future routing - # starts from the updated port. def set_ptype(self, ptype: str) -> Self: for port in self.ports: self.pather.pattern[port].set_ptype(ptype) @@ -878,7 +865,8 @@ class PortPather: def drop(self) -> Self: """ Remove selected ports from the pattern and the PortPather. """ - self.pather.rename_ports({pp: None for pp in self.ports}) + for pp in self.ports: + del self.pather.pattern.ports[pp] self.ports = [] return self @@ -892,7 +880,7 @@ class PortPather: if name is None: self.drop() return None - self.pather.rename_ports({name: None}) + del self.pather.pattern.ports[name] self.ports = [pp for pp in self.ports if pp != name] return self diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 5b1a0a9..f8779bd 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -85,8 +85,8 @@ class RenderStep: new_start = self.start_port.copy() new_end = self.end_port.copy() - new_start.flip_across(axis=axis) - new_end.flip_across(axis=axis) + new_start.mirror(axis) + new_end.mirror(axis) return RenderStep( opcode = self.opcode, @@ -240,7 +240,7 @@ class Tool: BuildError if an impossible or unsupported geometry is requested. """ # Fallback implementation using traceL - port_names = kwargs.pop('port_names', ('A', 'B')) + port_names = kwargs.get('port_names', ('A', 'B')) tree = self.traceL( ccw, length, @@ -288,7 +288,7 @@ class Tool: BuildError if an impossible or unsupported geometry is requested. """ # Fallback implementation using traceS - port_names = kwargs.pop('port_names', ('A', 'B')) + port_names = kwargs.get('port_names', ('A', 'B')) tree = self.traceS( length, jog, diff --git a/masque/file/dxf.py b/masque/file/dxf.py index 301910d..0c19b5a 100644 --- a/masque/file/dxf.py +++ b/masque/file/dxf.py @@ -300,57 +300,12 @@ def _read_block(block: ezdxf.layouts.BlockLayout | ezdxf.layouts.Modelspace) -> ) if 'column_count' in attr: - col_spacing = attr['column_spacing'] - row_spacing = attr['row_spacing'] - col_count = attr['column_count'] - row_count = attr['row_count'] - local_x = numpy.array((col_spacing, 0.0)) - local_y = numpy.array((0.0, row_spacing)) - inv_rot = rotation_matrix_2d(-rotation) - - candidates = ( - (inv_rot @ local_x, inv_rot @ local_y, col_count, row_count), - (inv_rot @ local_y, inv_rot @ local_x, row_count, col_count), + args['repetition'] = Grid( + a_vector=(attr['column_spacing'], 0), + b_vector=(0, attr['row_spacing']), + a_count=attr['column_count'], + b_count=attr['row_count'], ) - repetition = None - for a_vector, b_vector, a_count, b_count in candidates: - rotated_a = rotation_matrix_2d(rotation) @ a_vector - rotated_b = rotation_matrix_2d(rotation) @ b_vector - if (numpy.isclose(rotated_a[1], 0, atol=1e-8) - and numpy.isclose(rotated_b[0], 0, atol=1e-8) - and numpy.isclose(rotated_a[0], col_spacing, atol=1e-8) - and numpy.isclose(rotated_b[1], row_spacing, atol=1e-8) - and a_count == col_count - and b_count == row_count): - repetition = Grid( - a_vector=a_vector, - b_vector=b_vector, - a_count=a_count, - b_count=b_count, - ) - break - if (numpy.isclose(rotated_a[0], 0, atol=1e-8) - and numpy.isclose(rotated_b[1], 0, atol=1e-8) - and numpy.isclose(rotated_b[0], col_spacing, atol=1e-8) - and numpy.isclose(rotated_a[1], row_spacing, atol=1e-8) - and b_count == col_count - and a_count == row_count): - repetition = Grid( - a_vector=a_vector, - b_vector=b_vector, - a_count=a_count, - b_count=b_count, - ) - break - - if repetition is None: - repetition = Grid( - a_vector=inv_rot @ local_x, - b_vector=inv_rot @ local_y, - a_count=col_count, - b_count=row_count, - ) - args['repetition'] = repetition pat.ref(**args) else: logger.warning(f'Ignoring DXF element {element.dxftype()} (not implemented).') diff --git a/masque/file/oasis.py b/masque/file/oasis.py index 95a2039..5e343ea 100644 --- a/masque/file/oasis.py +++ b/masque/file/oasis.py @@ -714,7 +714,7 @@ def properties_to_annotations( string = repr(value) logger.warning(f'Converting property value for key ({key}) to string ({string})') values.append(string) - annotations.setdefault(key, []).extend(values) + annotations[key] = values return annotations diff --git a/masque/label.py b/masque/label.py index 3dbbc08..b662035 100644 --- a/masque/label.py +++ b/masque/label.py @@ -78,8 +78,6 @@ class Label(PositionableImpl, RepeatableImpl, AnnotatableImpl, Bounded, Pivotabl return annotations_lt(self.annotations, other.annotations) def __eq__(self, other: Any) -> bool: - if type(self) is not type(other): - return False return ( self.string == other.string and numpy.array_equal(self.offset, other.offset) diff --git a/masque/library.py b/masque/library.py index 9d1f1b7..bb2e3d2 100644 --- a/masque/library.py +++ b/masque/library.py @@ -1037,18 +1037,6 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): def label2name(label: tuple) -> str: # noqa: ARG001 return self.get_name(SINGLE_USE_PREFIX + 'shape') - used_names = set(self.keys()) - - def reserve_target_name(label: tuple) -> str: - base_name = label2name(label) - name = base_name - ii = sum(1 for nn in used_names if nn.startswith(base_name)) if base_name in used_names else 0 - while name in used_names or name == '': - name = base_name + b64suffix(ii) - ii += 1 - used_names.add(name) - return name - shape_counts: MutableMapping[tuple, int] = defaultdict(int) shape_funcs = {} @@ -1065,7 +1053,6 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): shape_counts[label] += 1 shape_pats = {} - target_names = {} for label, count in shape_counts.items(): if count < threshold: continue @@ -1074,7 +1061,6 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): shape_pat = Pattern() shape_pat.shapes[label[-1]] += [shape_func()] shape_pats[label] = shape_pat - target_names[label] = reserve_target_name(label) # ## Second pass ## for pat in tuple(self.values()): @@ -1099,14 +1085,14 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): # For repeated shapes, create a `Pattern` holding a normalized shape object, # and add `pat.refs` entries for each occurrence in pat. Also, note down that # we should delete the `pat.shapes` entries for which we made `Ref`s. + shapes_to_remove = [] for label, shape_entries in shape_table.items(): layer = label[-1] - target = target_names[label] - shapes_to_remove = [] + target = label2name(label) for ii, values in shape_entries: offset, scale, rotation, mirror_x = values pat.ref(target=target, offset=offset, scale=scale, - rotation=rotation, mirrored=mirror_x) + rotation=rotation, mirrored=(mirror_x, False)) shapes_to_remove.append(ii) # Remove any shapes for which we have created refs. @@ -1114,7 +1100,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta): del pat.shapes[layer][ii] for ll, pp in shape_pats.items(): - self[target_names[ll]] = pp + self[label2name(ll)] = pp return self diff --git a/masque/ref.py b/masque/ref.py index b012365..a40776a 100644 --- a/masque/ref.py +++ b/masque/ref.py @@ -122,8 +122,6 @@ class Ref( return annotations_lt(self.annotations, other.annotations) def __eq__(self, other: Any) -> bool: - if type(self) is not type(other): - return False return ( numpy.array_equal(self.offset, other.offset) and self.mirrored == other.mirrored diff --git a/masque/repetition.py b/masque/repetition.py index 99e1082..a8de94c 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -34,7 +34,7 @@ class Repetition(Copyable, Rotatable, Mirrorable, Scalable, Bounded, metaclass=A pass @abstractmethod - def __lt__(self, other: 'Repetition') -> bool: + def __le__(self, other: 'Repetition') -> bool: pass @abstractmethod @@ -288,7 +288,7 @@ class Grid(Repetition): return False return True - def __lt__(self, other: Repetition) -> bool: + def __le__(self, other: Repetition) -> bool: if type(self) is not type(other): return repr(type(self)) < repr(type(other)) other = cast('Grid', other) @@ -347,7 +347,7 @@ class Arbitrary(Repetition): return False return numpy.array_equal(self.displacements, other.displacements) - def __lt__(self, other: Repetition) -> bool: + def __le__(self, other: Repetition) -> bool: if type(self) is not type(other): return repr(type(self)) < repr(type(other)) other = cast('Arbitrary', other) @@ -415,3 +415,4 @@ class Arbitrary(Repetition): """ self.displacements = self.displacements * c return self + diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index 4befb6d..6f948cb 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -422,7 +422,7 @@ class Arc(PositionableImpl, Shape): rotation %= 2 * pi width = self.width - return ((type(self), tuple(radii.tolist()), norm_angles, width / norm_value), + return ((type(self), radii, norm_angles, width / norm_value), (self.offset, scale / norm_value, rotation, False), lambda: Arc( radii=radii * norm_value, diff --git a/masque/shapes/ellipse.py b/masque/shapes/ellipse.py index 985d252..8e3fd49 100644 --- a/masque/shapes/ellipse.py +++ b/masque/shapes/ellipse.py @@ -206,7 +206,7 @@ class Ellipse(PositionableImpl, Shape): radii = self.radii[::-1] / self.radius_y scale = self.radius_y angle = (self.rotation + pi / 2) % pi - return ((type(self), tuple(radii.tolist())), + return ((type(self), radii), (self.offset, scale / norm_value, angle, False), lambda: Ellipse(radii=radii * norm_value)) diff --git a/masque/shapes/text.py b/masque/shapes/text.py index 93f2024..dec4c33 100644 --- a/masque/shapes/text.py +++ b/masque/shapes/text.py @@ -108,7 +108,6 @@ class Text(PositionableImpl, RotatableImpl, Shape): and self.string == other.string and self.height == other.height and self.font_path == other.font_path - and self.mirrored == other.mirrored and self.rotation == other.rotation and self.repetition == other.repetition and annotations_eq(self.annotations, other.annotations) @@ -128,8 +127,6 @@ class Text(PositionableImpl, RotatableImpl, Shape): return self.font_path < other.font_path if not numpy.array_equal(self.offset, other.offset): return tuple(self.offset) < tuple(other.offset) - if self.mirrored != other.mirrored: - return self.mirrored < other.mirrored if self.rotation != other.rotation: return self.rotation < other.rotation if self.repetition != other.repetition: @@ -177,25 +174,22 @@ class Text(PositionableImpl, RotatableImpl, Shape): (self.offset, self.height / norm_value, rotation, bool(self.mirrored)), lambda: Text( string=self.string, - height=norm_value, + height=self.height * norm_value, font_path=self.font_path, rotation=rotation, ).mirror2d(across_x=self.mirrored), ) - def get_bounds_single(self) -> NDArray[numpy.float64] | None: + def get_bounds_single(self) -> NDArray[numpy.float64]: # rotation makes this a huge pain when using slot.advance and glyph.bbox(), so # just convert to polygons instead polys = self.to_polygons() - if not polys: - return None - pbounds = numpy.full((len(polys), 2, 2), nan) for pp, poly in enumerate(polys): pbounds[pp] = poly.get_bounds_nonempty() bounds = numpy.vstack(( - numpy.min(pbounds[:, 0, :], axis=0), - numpy.max(pbounds[:, 1, :], axis=0), + numpy.min(pbounds[: 0, :], axis=0), + numpy.max(pbounds[: 1, :], axis=0), )) return bounds diff --git a/masque/test/test_dxf.py b/masque/test/test_dxf.py index 5b038c6..0c0a1a3 100644 --- a/masque/test/test_dxf.py +++ b/masque/test/test_dxf.py @@ -112,38 +112,6 @@ def test_dxf_manhattan_precision(tmp_path: Path): assert isinstance(ref.repetition, Grid), "Grid should be preserved for 90-degree rotation" -def test_dxf_rotated_grid_roundtrip_preserves_basis_and_counts(tmp_path: Path): - lib = Library() - sub = Pattern() - sub.polygon("1", vertices=[[0, 0], [1, 0], [1, 1]]) - lib["sub"] = sub - - top = Pattern() - top.ref( - "sub", - offset=(0, 0), - rotation=numpy.pi / 2, - repetition=Grid(a_vector=(10, 0), a_count=3, b_vector=(0, 20), b_count=2), - ) - lib["top"] = top - - dxf_file = tmp_path / "rotated_grid.dxf" - dxf.writefile(lib, "top", dxf_file) - - read_lib, _ = dxf.readfile(dxf_file) - read_top = read_lib.get("Model") or read_lib.get("top") or list(read_lib.values())[0] - - target_name = next(k for k in read_top.refs if k.upper() == "SUB") - ref = read_top.refs[target_name][0] - assert isinstance(ref.repetition, Grid) - actual = ref.repetition.displacements - expected = Grid(a_vector=(10, 0), a_count=3, b_vector=(0, 20), b_count=2).displacements - assert_allclose( - actual[numpy.lexsort((actual[:, 1], actual[:, 0]))], - expected[numpy.lexsort((expected[:, 1], expected[:, 0]))], - ) - - def test_dxf_read_legacy_polyline() -> None: doc = ezdxf.new() msp = doc.modelspace() diff --git a/masque/test/test_label.py b/masque/test/test_label.py index f4f364b..ad8c08b 100644 --- a/masque/test/test_label.py +++ b/masque/test/test_label.py @@ -46,9 +46,3 @@ def test_label_copy() -> None: assert l1 is not l2 l2.offset[0] = 100 assert l1.offset[0] == 1 - - -def test_label_eq_unrelated_objects_is_false() -> None: - lbl = Label("test") - assert not (lbl == None) - assert not (lbl == object()) diff --git a/masque/test/test_library.py b/masque/test/test_library.py index 6ac8536..e58bd10 100644 --- a/masque/test/test_library.py +++ b/masque/test/test_library.py @@ -6,7 +6,7 @@ from ..pattern import Pattern from ..error import LibraryError, PatternError from ..ports import Port from ..repetition import Grid -from ..shapes import Arc, Ellipse, Path, Text +from ..shapes import Path from ..file.utils import preflight if TYPE_CHECKING: @@ -259,88 +259,3 @@ def test_library_dedup_shapes_does_not_merge_custom_capped_paths() -> None: assert not lib["top"].refs assert len(lib["top"].shapes[(1, 0)]) == 2 - - -def test_library_dedup_text_preserves_scale_and_mirror_flag() -> None: - lib = Library() - pat = Pattern() - pat.shapes[(1, 0)] += [ - Text("A", 10, "dummy.ttf", offset=(0, 0)), - Text("A", 10, "dummy.ttf", offset=(100, 0)), - ] - lib["top"] = pat - - lib.dedup(exclude_types=(), norm_value=5, threshold=2) - - target_name = next(iter(lib["top"].refs)) - refs = lib["top"].refs[target_name] - assert [ref.mirrored for ref in refs] == [False, False] - assert [ref.scale for ref in refs] == [2.0, 2.0] - assert cast("Text", lib[target_name].shapes[(1, 0)][0]).height == 5 - - flat = lib.flatten("top")["top"] - assert [cast("Text", shape).height for shape in flat.shapes[(1, 0)]] == [10, 10] - - -def test_library_dedup_handles_arc_and_ellipse_labels() -> None: - lib = Library() - pat = Pattern() - pat.shapes[(1, 0)] += [ - Arc(radii=(10, 20), angles=(0, 1), width=2, offset=(0, 0)), - Arc(radii=(10, 20), angles=(0, 1), width=2, offset=(50, 0)), - ] - pat.shapes[(2, 0)] += [ - Ellipse(radii=(10, 20), offset=(0, 0)), - Ellipse(radii=(10, 20), offset=(50, 0)), - ] - lib["top"] = pat - - lib.dedup(exclude_types=(), norm_value=1, threshold=2) - - assert len(lib["top"].refs) == 2 - assert lib["top"].shapes[(1, 0)] == [] - assert lib["top"].shapes[(2, 0)] == [] - - flat = lib.flatten("top")["top"] - assert sum(isinstance(shape, Arc) for shape in flat.shapes[(1, 0)]) == 2 - assert sum(isinstance(shape, Ellipse) for shape in flat.shapes[(2, 0)]) == 2 - - -def test_library_dedup_handles_multiple_duplicate_groups() -> None: - from ..shapes import Circle - - lib = Library() - pat = Pattern() - pat.shapes[(1, 0)] += [Circle(radius=1, offset=(0, 0)), Circle(radius=1, offset=(10, 0))] - pat.shapes[(2, 0)] += [Path(vertices=[[0, 0], [5, 0]], width=2), Path(vertices=[[10, 0], [15, 0]], width=2)] - lib["top"] = pat - - lib.dedup(exclude_types=(), norm_value=1, threshold=2) - - assert len(lib["top"].refs) == 2 - assert all(len(refs) == 2 for refs in lib["top"].refs.values()) - assert len(lib["top"].shapes[(1, 0)]) == 0 - assert len(lib["top"].shapes[(2, 0)]) == 0 - - -def test_library_dedup_uses_stable_target_names_per_label() -> None: - from ..shapes import Circle - - lib = Library() - - p1 = Pattern() - p1.shapes[(1, 0)] += [Circle(radius=1, offset=(0, 0)), Circle(radius=1, offset=(10, 0))] - lib["p1"] = p1 - - p2 = Pattern() - p2.shapes[(2, 0)] += [Path(vertices=[[0, 0], [5, 0]], width=2), Path(vertices=[[10, 0], [15, 0]], width=2)] - lib["p2"] = p2 - - lib.dedup(exclude_types=(), norm_value=1, threshold=2) - - circle_target = next(iter(lib["p1"].refs)) - path_target = next(iter(lib["p2"].refs)) - - assert circle_target != path_target - assert all(isinstance(shape, Circle) for shapes in lib[circle_target].shapes.values() for shape in shapes) - assert all(isinstance(shape, Path) for shapes in lib[path_target].shapes.values() for shape in shapes) diff --git a/masque/test/test_oasis.py b/masque/test/test_oasis.py index ad9bbf9..b1129f4 100644 --- a/masque/test/test_oasis.py +++ b/masque/test/test_oasis.py @@ -4,8 +4,6 @@ from numpy.testing import assert_equal from ..pattern import Pattern from ..library import Library - - def test_oasis_roundtrip(tmp_path: Path) -> None: # Skip if fatamorgana is not installed pytest.importorskip("fatamorgana") @@ -25,20 +23,3 @@ def test_oasis_roundtrip(tmp_path: Path) -> None: # Check bounds assert_equal(read_lib["cell1"].get_bounds(), [[0, 0], [10, 10]]) - - -def test_oasis_properties_to_annotations_merges_repeated_keys() -> None: - pytest.importorskip("fatamorgana") - import fatamorgana.records as fatrec - from ..file.oasis import properties_to_annotations - - annotations = properties_to_annotations( - [ - fatrec.Property("k", [1], is_standard=False), - fatrec.Property("k", [2, 3], is_standard=False), - ], - {}, - {}, - ) - - assert annotations == {"k": [1, 2, 3]} diff --git a/masque/test/test_pather_api.py b/masque/test/test_pather_api.py index 495d305..c837280 100644 --- a/masque/test/test_pather_api.py +++ b/masque/test/test_pather_api.py @@ -2,7 +2,7 @@ import pytest import numpy from numpy import pi from masque import Pather, RenderPather, Library, Pattern, Port -from masque.builder.tools import PathTool, Tool +from masque.builder.tools import PathTool from masque.error import BuildError def test_pather_trace_basic() -> None: @@ -258,36 +258,6 @@ def test_pather_jog_failed_fallback_is_atomic() -> None: assert len(p.paths['A']) == 0 -def test_tool_planL_fallback_accepts_custom_port_names() -> None: - class DummyTool(Tool): - def traceL(self, ccw, length, *, in_ptype=None, out_ptype=None, port_names=('A', 'B'), **kwargs) -> Library: - lib = Library() - pat = Pattern() - pat.ports[port_names[0]] = Port((0, 0), 0, ptype='wire') - pat.ports[port_names[1]] = Port((length, 0), pi, ptype='wire') - lib['top'] = pat - return lib - - out_port, _ = DummyTool().planL(None, 5, port_names=('X', 'Y')) - assert numpy.allclose(out_port.offset, (5, 0)) - assert numpy.isclose(out_port.rotation, pi) - - -def test_tool_planS_fallback_accepts_custom_port_names() -> None: - class DummyTool(Tool): - def traceS(self, length, jog, *, in_ptype=None, out_ptype=None, port_names=('A', 'B'), **kwargs) -> Library: - lib = Library() - pat = Pattern() - pat.ports[port_names[0]] = Port((0, 0), 0, ptype='wire') - pat.ports[port_names[1]] = Port((length, jog), pi, ptype='wire') - lib['top'] = pat - return lib - - out_port, _ = DummyTool().planS(5, 2, port_names=('X', 'Y')) - assert numpy.allclose(out_port.offset, (5, 2)) - assert numpy.isclose(out_port.rotation, pi) - - def test_pather_uturn_failed_fallback_is_atomic() -> None: lib = Library() tool = PathTool(layer='M1', width=2, ptype='wire') @@ -300,20 +270,3 @@ def test_pather_uturn_failed_fallback_is_atomic() -> None: 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_renderpather_rename_to_none_keeps_pending_geometry_without_port() -> None: - lib = Library() - tool = PathTool(layer='M1', width=1000) - rp = RenderPather(lib, tools=tool) - rp.pattern.ports['A'] = Port((0, 0), rotation=0) - - rp.at('A').straight(5000) - rp.rename_ports({'A': None}) - - assert 'A' not in rp.pattern.ports - assert len(rp.paths['A']) == 1 - - rp.render() - assert rp.pattern.has_shapes() - assert 'A' not in rp.pattern.ports diff --git a/masque/test/test_ref.py b/masque/test/test_ref.py index d3e9778..c1dbf26 100644 --- a/masque/test/test_ref.py +++ b/masque/test/test_ref.py @@ -87,9 +87,3 @@ def test_ref_scale_by_rejects_nonpositive_scale() -> None: with pytest.raises(MasqueError, match='Scale must be positive'): ref.scale_by(-1) - - -def test_ref_eq_unrelated_objects_is_false() -> None: - ref = Ref() - assert not (ref == None) - assert not (ref == object()) diff --git a/masque/test/test_renderpather.py b/masque/test/test_renderpather.py index 0da9588..3ad0d95 100644 --- a/masque/test/test_renderpather.py +++ b/masque/test/test_renderpather.py @@ -65,17 +65,6 @@ def test_renderpather_bend(rpather_setup: tuple[RenderPather, PathTool, Library] assert_allclose(path_shape.vertices, [[0, 0], [0, -10], [0, -20], [-1, -20]], atol=1e-10) -def test_renderpather_mirror_preserves_planned_bend_geometry(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None: - rp, tool, lib = rpather_setup - rp.at("start").straight(10).cw(10) - - rp.mirror(0) - rp.render() - - path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0]) - assert_allclose(path_shape.vertices, [[0, 0], [0, 10], [0, 20], [-1, 20]], atol=1e-10) - - def test_renderpather_retool(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None: rp, tool1, lib = rpather_setup tool2 = PathTool(layer=(2, 0), width=4, ptype="wire") @@ -90,22 +79,6 @@ def test_renderpather_retool(rpather_setup: tuple[RenderPather, PathTool, Librar assert len(rp.pattern.shapes[(2, 0)]) == 1 -def test_portpather_translate_only_affects_future_steps(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None: - rp, tool, lib = rpather_setup - pp = rp.at("start") - pp.straight(10) - pp.translate((5, 0)) - pp.straight(10) - - rp.render() - - shapes = rp.pattern.shapes[(1, 0)] - assert len(shapes) == 2 - assert_allclose(cast("Path", shapes[0]).vertices, [[0, 0], [0, -10]], atol=1e-10) - assert_allclose(cast("Path", shapes[1]).vertices, [[5, -10], [5, -20]], atol=1e-10) - assert_allclose(rp.ports["start"].offset, [5, -20], atol=1e-10) - - def test_renderpather_dead_ports() -> None: lib = Library() tool = PathTool(layer=(1, 0), width=1) @@ -148,20 +121,6 @@ def test_renderpather_rename_port(rpather_setup: tuple[RenderPather, PathTool, L assert_allclose(rp.ports["new_start"].offset, [0, -20], atol=1e-10) -def test_renderpather_drop_keeps_pending_geometry_without_port(rpather_setup: tuple[RenderPather, PathTool, Library]) -> None: - rp, tool, lib = rpather_setup - rp.at("start").straight(10).drop() - - assert "start" not in rp.ports - assert len(rp.paths["start"]) == 1 - - rp.render() - assert rp.pattern.has_shapes() - assert "start" not in rp.ports - path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0]) - assert_allclose(path_shape.vertices, [[0, 0], [0, -10]], atol=1e-10) - - def test_pathtool_traceL_bend_geometry_matches_ports() -> None: tool = PathTool(layer=(1, 0), width=2, ptype="wire") diff --git a/masque/test/test_repetition.py b/masque/test/test_repetition.py index f423ab2..5ef2fa9 100644 --- a/masque/test/test_repetition.py +++ b/masque/test/test_repetition.py @@ -49,17 +49,3 @@ def test_arbitrary_transform() -> None: # self.displacements[:, 1 - axis] *= -1 # if axis=0, 1-axis=1, so y *= -1 assert_allclose(arb.displacements, [[0, -10]], atol=1e-10) - - -def test_repetition_less_equal_includes_equality() -> None: - grid_a = Grid(a_vector=(10, 0), a_count=2) - grid_b = Grid(a_vector=(10, 0), a_count=2) - assert grid_a == grid_b - assert grid_a <= grid_b - assert grid_a >= grid_b - - arb_a = Arbitrary([[0, 0], [1, 0]]) - arb_b = Arbitrary([[0, 0], [1, 0]]) - assert arb_a == arb_b - assert arb_a <= arb_b - assert arb_a >= arb_b diff --git a/masque/test/test_shape_advanced.py b/masque/test/test_shape_advanced.py index 046df1a..4e38e55 100644 --- a/masque/test/test_shape_advanced.py +++ b/masque/test/test_shape_advanced.py @@ -27,33 +27,6 @@ def test_text_to_polygons() -> None: assert len(set(char_x_means)) >= 2 -def test_text_bounds_and_normalized_form() -> None: - pytest.importorskip("freetype") - font_path = "/usr/share/fonts/truetype/dejavu/DejaVuMathTeXGyre.ttf" - if not Path(font_path).exists(): - pytest.skip("Font file not found") - - text = Text("Hi", height=10, font_path=font_path) - _intrinsic, extrinsic, ctor = text.normalized_form(5) - normalized = ctor() - - assert extrinsic[1] == 2 - assert normalized.height == 5 - - bounds = text.get_bounds_single() - assert bounds is not None - assert numpy.isfinite(bounds).all() - assert numpy.all(bounds[1] > bounds[0]) - - -def test_text_mirroring_affects_comparison() -> None: - text = Text("A", height=10, font_path="dummy.ttf") - mirrored = Text("A", height=10, font_path="dummy.ttf", mirrored=True) - - assert text != mirrored - assert (text < mirrored) != (mirrored < text) - - # 2. Manhattanization tests def test_manhattanize() -> None: pytest.importorskip("float_raster")