Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SVG path manipulation

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):

path offset

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?

like image 210
Josh Avatar asked Feb 10 '23 20:02

Josh


1 Answers

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')
like image 97
mathandy Avatar answered Feb 13 '23 11:02

mathandy