[Library] fix dedup()

- use consistent deduplicated target name
- remove shape indices per dedup
This commit is contained in:
Jan Petykiewicz 2026-03-31 18:58:37 -07:00
commit 462a05a665
2 changed files with 57 additions and 3 deletions

View file

@ -1037,6 +1037,18 @@ 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 = {}
@ -1053,6 +1065,7 @@ 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
@ -1061,6 +1074,7 @@ 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()):
@ -1085,10 +1099,10 @@ 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 = label2name(label)
target = target_names[label]
shapes_to_remove = []
for ii, values in shape_entries:
offset, scale, rotation, mirror_x = values
pat.ref(target=target, offset=offset, scale=scale,
@ -1100,7 +1114,7 @@ class ILibrary(ILibraryView, MutableMapping[str, 'Pattern'], metaclass=ABCMeta):
del pat.shapes[layer][ii]
for ll, pp in shape_pats.items():
self[label2name(ll)] = pp
self[target_names[ll]] = pp
return self

View file

@ -259,3 +259,43 @@ 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_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)