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