diff --git a/masque/file/svg.py b/masque/file/svg.py index 621bcdb..772aa39 100644 --- a/masque/file/svg.py +++ b/masque/file/svg.py @@ -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 (, inside ), polygons as paths (), and refs as 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 `` 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)