The Inkscape SVG editor has some neat path manipulation tools built in. One I'm particularly interested in accessing programmatically is the offset function, which (attempts to) create a path a fixed distance from an existing path, as depicted here (the black lines are offsets of the red line):

I'd like to be able to perform this operation from a Python program.
Inkscape has rudimentary scripting support, but it basically only consists of calling non-interactive menu commands - For example, you can create a path that's inset or outset from an existing path, but only by exactly 1px or 10px, not by a user-specified amount. So that doesn't seem useful here.
Is there a library or other tool from which I can do these sorts of path transformations (ideally to an SVG file) in Python?
There's a problem with this. You can create a visual approximation (or a path approximating) the offset path, but the offset curve of a Bezier curve or elliptic arc will not in general be a Bezier curve or elliptic arc.
That said, there are explicit instructions how to create a piecewise-linear approximation of such an offset curve in the README of the svgpathtools python module (just follow the link and scroll down - it's the last example, "An Advanced Application: Offsetting Paths").
Here's the code:
from svgpathtools import parse_path, Line, Path, wsvg
def offset_curve(path, offset_distance, steps=1000):
    """Takes in a Path object, `path`, and a distance,
    `offset_distance`, and outputs an piecewise-linear approximation 
    of the 'parallel' offset curve."""
    nls = []
    for seg in path:
        ct = 1
        for k in range(steps):
            t = k / steps
            offset_vector = offset_distance * seg.normal(t)
            nl = Line(seg.point(t), seg.point(t) + offset_vector)
            nls.append(nl)
    connect_the_dots = [Line(nls[k].end, nls[k+1].end) for k in range(len(nls)-1)]
    if path.isclosed():
        connect_the_dots.append(Line(nls[-1].end, nls[0].end))
    offset_path = Path(*connect_the_dots)
    return offset_path
# Examples:
path1 = parse_path("m 288,600 c -52,-28 -42,-61 0,-97 ")
path2 = parse_path("M 151,395 C 407,485 726.17662,160 634,339").translated(300)
path3 = parse_path("m 117,695 c 237,-7 -103,-146 457,0").translated(500+400j)
paths = [path1, path2, path3]
offset_distances = [10*k for k in range(1,51)]
offset_paths = []
for path in paths:
    for distances in offset_distances:
        offset_paths.append(offset_curve(path, distances))
# Note: This will take a few moments
wsvg(paths + offset_paths, 'g'*len(paths) + 'r'*len(offset_paths), filename='offsetcurves.svg')
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With