---
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.