[svg] avoid mutating the original library

This commit is contained in:
Jan Petykiewicz 2026-04-20 20:50:45 -07:00
commit 27e1c23c33

View file

@ -45,6 +45,10 @@ def _make_svg_ids(names: Mapping[str, Pattern]) -> dict[str, str]:
return svg_ids
def _detached_library(library: Mapping[str, Pattern]) -> dict[str, Pattern]:
return {name: pat.deepcopy() for name, pat in library.items()}
def writefile(
library: Mapping[str, Pattern],
top: str,
@ -53,13 +57,12 @@ def writefile(
annotate_ports: bool = False,
) -> None:
"""
Write a Pattern to an SVG file, by first calling .polygonize() on it
Write a Pattern to an SVG file, by first calling .polygonize() on a detached
materialized copy
to change the shapes into polygons, and then writing patterns as SVG
groups (<g>, inside <defs>), polygons as paths (<path>), and refs
as <use> elements.
Note that this function modifies the Pattern.
If `custom_attributes` is `True`, a non-standard `pattern_layer` attribute
is written to the relevant elements.
@ -71,19 +74,21 @@ def writefile(
prior to calling this function.
Args:
pattern: Pattern to write to file. Modified by this function.
library: Mapping of pattern names to patterns.
top: Name of the top-level pattern to render.
filename: Filename to write to.
custom_attributes: Whether to write non-standard `pattern_layer` attribute to the
SVG elements.
annotate_ports: If True, draw an arrow for each port (similar to
`Pattern.visualize(..., ports=True)`).
"""
pattern = library[top]
detached = _detached_library(library)
pattern = detached[top]
# Polygonize pattern
pattern.polygonize()
bounds = pattern.get_bounds(library=library)
bounds = pattern.get_bounds(library=detached)
if bounds is None:
bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]])
logger.warning('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1)
@ -96,10 +101,10 @@ def writefile(
# Create file
svg = svgwrite.Drawing(filename, profile='full', viewBox=viewbox_string,
debug=(not custom_attributes))
svg_ids = _make_svg_ids(library)
svg_ids = _make_svg_ids(detached)
# Now create a group for each pattern and add in any Boundary and Use elements
for name, pat in library.items():
for name, pat in detached.items():
svg_group = svg.g(id=svg_ids[name], fill='blue', stroke='red')
for layer, shapes in pat.shapes.items():
@ -158,21 +163,21 @@ def writefile_inverted(
box and drawing the polygons with reverse vertex order inside it, all within
one `<path>` element.
Note that this function modifies the Pattern.
If you want pattern polygonized with non-default arguments, just call `pattern.polygonize()`
prior to calling this function.
Args:
pattern: Pattern to write to file. Modified by this function.
library: Mapping of pattern names to patterns.
top: Name of the top-level pattern to render.
filename: Filename to write to.
"""
pattern = library[top]
detached = _detached_library(library)
pattern = detached[top]
# Polygonize and flatten pattern
pattern.polygonize().flatten(library)
pattern.polygonize().flatten(detached)
bounds = pattern.get_bounds(library=library)
bounds = pattern.get_bounds(library=detached)
if bounds is None:
bounds_min, bounds_max = numpy.array([[-1, -1], [1, 1]])
logger.warning('Pattern had no bounds (empty?); setting arbitrary viewbox', stacklevel=1)