--- jupyter: description: 'SVG files with planex2d: px.WriteSVG writes faces filled and edges on top at true scale, with names as layers and colors, and px.FromSVG reads the paths of a drawing as exact lines, arcs and splines, in meters.' --- # SVG files SVG is the vector format of drawing programs, laser cutters, vinyl cutters and web pages. `px.WriteSVG` writes planex2d geometry as an SVG file at true scale, and `px.FromSVG` reads the paths of a drawing back as exact geometry. Writing needs no extra package. Reading needs `svgelements` (`pip install svgelements`), which parses the file and applies its transforms. The picture that a notebook shows, `ToSVG`, is something else. It is sized in pixels for the screen and filled in the preview colors. See [SVG](../plotting/svg.md) in the plotting section for details. ```python import math from pathlib import Path import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## Writing The example is the front panel of an instrument case, 120 × 70, with a window slot of width 24, an opening of diameter 22 for a rotary switch and four mounting holes of diameter 4.5. It is drawn counting in millimeters and scaled to meters, the unit that the files' sizes refer to (see [Units](../basics/units.md)). The panel has a color, and the mounting holes have a color of their own. ```python sketch = px.QuickSketch().RectangleC(120, 70, name="outline") sketch.Move((-40, 0)).Slot(45, 24, name="window") sketch.Circle((38, 0), 22 / 2, name="switch") for x in (-52, 52): for y in (-27, 27): sketch.Circle((x, y), 4.5 / 2, name="mounting") panel = sketch.Face().Scaled((0, 0), px.mm) panel.col = (0.75, 0.8, 0.86) panel.edges(name="mounting").col = (0.85, 0.2, 0.15) ``` `WriteSVG` fills each face in its color, its holes left open, and draws every edge on top, once, in its color. The edges go into one group per name, which drawing programs such as Inkscape show as layers. The width and height of the file are in millimeters, so the panel opens at its real size, and `unit` sets the unit that the numbers inside the file count in: ```python panel.WriteSVG("panel.svg", unit=px.mm) print(Path("panel.svg").read_text().splitlines()[1]) ``` A laser cutter cuts along every path, so a file for cutting holds the outlines only. Wires are never filled, so writing the face's wires instead of the face gives just the outlines. `WriteSVG` also takes a list of objects for one file. ```python px.WriteSVG(panel.wires, "panel-cut.svg", unit=px.mm) ``` Lines, arcs, circles, ellipses and splines up to degree 3 are written exactly, as SVG lines, arcs and Bezier curves. A rational spline that is no part of an ellipse, or a spline of a higher degree, has no exact SVG form. It is written as its arc approximation, the geometry that Boolean operations use, with a warning that says how many curves went out that way. ## Reading back `FromSVG` returns the wires of the drawing, in meters, as `FromDXF` does. The names of the groups become the edge names, and `px.Face` builds the face. The panel read from the outlines' file has the area it was written with (Figure 1): ```python back = px.Face(px.FromSVG("panel-cut.svg")) print("area difference", back.mass - panel.mass) print(sorted({e.name for e in back.edges})) ``` ```python nbsphinx="hide-input" Row((panel, "Written, in its color", {"colors": True}), (back, "Read back, the mounting holes in cyan", {"highlight": list(back.edges(name="mounting"))}), caption="A front panel written to SVG and read back") ``` ## A drawing from another program The example is a bracket of 100 × 50 mm as a drawing program saves it, with one corner rounded to radius 20, a hole of diameter 16 and an elliptical opening of 24 × 12. Each part of the drawing is on a layer of its own (a group with a label), the holes are moved into place by a transform, and a layer of center lines belongs to the drawing, not to the part. ```python Path("bracket.svg").write_text(""" """); ``` `layers` reads only the outline and the holes. Lines and circular arcs stay lines and arcs, and the ellipse is read as exact rational splines. Each edge takes its stroke color, and the center line, which is left out, is shown dashed (Figure 2). ```python bracket = px.Face(px.FromSVG("bracket.svg", layers=["outline", "holes"])) print(sorted({e.name for e in bracket.edges}), sorted({type(e.edge).__name__ for e in bracket.edges})) ``` ```python nbsphinx="hide-input" center_line = px.FromSVG("bracket.svg", layers=["center lines"]) Row((bracket, "Outline and holes, in their stroke colors", {"colors": True, "dashed": center_line}), caption="A bracket read from two of its three layers") ``` SVG counts $y$ downward, and `FromSVG` turns it upward, as in planex2d, so the rounded corner lies at the bottom right. The area is that of the 100 × 50 outline with its corner rounded to a radius of 20, less the hole of radius 8 and the ellipse with half axes of 12 and 6, $100 \cdot 50 - (1 - \pi/4)\,20^2 - \pi \cdot 8^2 - \pi \cdot 12 \cdot 6$ in mm²: ```python exact = 100 * 50 - (1 - math.pi / 4) * 20**2 - math.pi * 8**2 - math.pi * 12 * 6 print("area", bracket.mass / px.mm**2, "exact", exact) ``` ## Units `FromSVG` takes the scale from the file's width and height, which name their unit, such as mm, cm, in, pt or pc. A file whose width names no unit, as many drawings for the screen do, is read as CSS pixels of 1/96 inch, with a warning. `unit` sets the length of one unit of the drawing instead. The example is a rectangle of 80 × 30 in a file without units, read as millimeters: ```python Path("plain.svg").write_text('' '') print(px.Compound(px.FromSVG("plain.svg", unit=px.mm)).bounding_box) ``` `tol` joins ends that are closer than the given length, as in `FromDXF`, and `plane` puts the wires on a plane other than the xy plane. See [Files](../files.md) for the other formats.