How do I get a coordinates list from an svgpathtools bezier curve?
Question:
I have python code to create a bezier curve, from which I create a bezier path.
Here are my imports:
import from svgpathtools import Path, Line, CubicBezier
Here is my code:
bezier_curve = CubicBezier(start_coordinate, control_point_1, control_point_2, end_coordinate)
bezier_path = Path(bezier_curve)
I would like to create a list of coordinates that make up this curve, but none of the documentation I am reading gives a straightforward way to do that. bezier_curve and bezier_path only have parameters for the start point, end point, and control point.
Answers:
Seems like a pretty reasonable question. Surprised there’s no answer. I had to do this myself recently, and the secret is point()
.
Here’s how I got it done, using your boilerplate as a starting point:
from svgpathtools import Path, Line, CubicBezier
bezier_curve = CubicBezier(start=(300+100j), control1=(100+100j), control2=(200+200j), end=(200+300j))
bezier_path = Path(bezier_curve)
NUM_SAMPLES = 10
myPath = []
for i in range(NUM_SAMPLES):
myPath.append(bezier_path.point(i/(NUM_SAMPLES-1)))
print(myPath)
Output:
[(300+100j), (243.8957475994513+103.56652949245542j), (206.72153635116598+113.71742112482853j), (185.1851851851852+129.62962962962962j), (175.99451303155004+150.480109739369j), (175.85733882030178+175.44581618655695j), (181.4814814814815+203.7037037037037j), (189.57475994513032+234.43072702331963j), (196.84499314128942+266.8038408779149j), (200+300j)]
The answer given above worked very well for me. I only had to make a tiny modification to the code:
from svgpathtools import Path, Line, CubicBezier
bezier_curve = CubicBezier(start=(300+100j), control1=(100+100j), control2=(200+200j), end=(200+300j))
bezier_path = Path(bezier_curve)
NUM_SAMPLES = 10
myPath = []
for i in range(NUM_SAMPLES):
myPath.append(bezier_path.point(i/(**float(NUM_SAMPLES)**-1)))
print(myPath)
Changing i/(NUM_SAMPLES -1) by i/(float(NUM_SAMPLES) -1)
assures a correct behavior when the curve is parametrized from 0 to 1. Otherwise only an integer division is produced.
#to demonstrate lines and cubics, improving readibility
from svgpathtools import Path, Line, CubicBezier
cubic = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j) # A cubic beginning at (300, 100) and ending at (200, 300)
line = Line(200+300j, 250+350j) # A line beginning at (200, 300) and ending at (250, 350)
number_of_points = 10
cubic_points = []
for i in range(number_of_points):
cubic_points.append(cubic.point(i/(NUM_SAMPLES-1)))
print('cubic points', path_points)
line_points = []
for i in range(number_of_points):
line_points.append(line.point(i/(NUM_SAMPLES-1)))
print('line points', path_points)
I needed a more general form to extract multiple paths with different features. Its a recursive solution to work with path, list of path, list of segments and single segments simultaneously. And you can specify the sample_points
per segment for curves, but a line stays 2 points to not add extra points without a need:
import svgpathtools.path
def svgpathtools_unpacker(obj, sample_points=10):
path = []
if isinstance(obj, (svgpathtools.path.Path, list)):
for i in obj:
path.extend(svgpathtools_unpacker(i, sample_points=sample_points))
elif isinstance(obj, svgpathtools.path.Line):
path.extend(obj.bpoints())
elif isinstance(obj, (svgpathtools.path.CubicBezier, svgpathtools.path.QuadraticBezier)):
path.extend(obj.points(np.linspace(0,1,sample_points)))
else:
print(type(obj))
return np.array(path)
and how you can use it:
import matplotlib.pyplot as plt
bezier_curve = svgpathtools.path.CubicBezier(start=(300+100j), control1=(100+100j), control2=(200+200j), end=(200+300j))
bezier_quad = svgpathtools.path.QuadraticBezier(bezier_curve.end, control=(200+200j), end=(300+150j))
line = svgpathtools.path.Line(start=bezier_quad.end, end=bezier_curve.start)
bezier_path = svgpathtools.path.Path(bezier_curve, bezier_quad, line)
plt.figure()
for i in [10, 100]:
xy = svgpathtools_unpacker(bezier_path, sample_points=i)
plt.plot(xy.real, xy.imag, label=f'sample_points={i}')
plt.legend()
I have python code to create a bezier curve, from which I create a bezier path.
Here are my imports:
import from svgpathtools import Path, Line, CubicBezier
Here is my code:
bezier_curve = CubicBezier(start_coordinate, control_point_1, control_point_2, end_coordinate)
bezier_path = Path(bezier_curve)
I would like to create a list of coordinates that make up this curve, but none of the documentation I am reading gives a straightforward way to do that. bezier_curve and bezier_path only have parameters for the start point, end point, and control point.
Seems like a pretty reasonable question. Surprised there’s no answer. I had to do this myself recently, and the secret is point()
.
Here’s how I got it done, using your boilerplate as a starting point:
from svgpathtools import Path, Line, CubicBezier
bezier_curve = CubicBezier(start=(300+100j), control1=(100+100j), control2=(200+200j), end=(200+300j))
bezier_path = Path(bezier_curve)
NUM_SAMPLES = 10
myPath = []
for i in range(NUM_SAMPLES):
myPath.append(bezier_path.point(i/(NUM_SAMPLES-1)))
print(myPath)
Output:
[(300+100j), (243.8957475994513+103.56652949245542j), (206.72153635116598+113.71742112482853j), (185.1851851851852+129.62962962962962j), (175.99451303155004+150.480109739369j), (175.85733882030178+175.44581618655695j), (181.4814814814815+203.7037037037037j), (189.57475994513032+234.43072702331963j), (196.84499314128942+266.8038408779149j), (200+300j)]
The answer given above worked very well for me. I only had to make a tiny modification to the code:
from svgpathtools import Path, Line, CubicBezier
bezier_curve = CubicBezier(start=(300+100j), control1=(100+100j), control2=(200+200j), end=(200+300j))
bezier_path = Path(bezier_curve)
NUM_SAMPLES = 10
myPath = []
for i in range(NUM_SAMPLES):
myPath.append(bezier_path.point(i/(**float(NUM_SAMPLES)**-1)))
print(myPath)
Changing i/(NUM_SAMPLES -1) by i/(float(NUM_SAMPLES) -1)
assures a correct behavior when the curve is parametrized from 0 to 1. Otherwise only an integer division is produced.
#to demonstrate lines and cubics, improving readibility
from svgpathtools import Path, Line, CubicBezier
cubic = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j) # A cubic beginning at (300, 100) and ending at (200, 300)
line = Line(200+300j, 250+350j) # A line beginning at (200, 300) and ending at (250, 350)
number_of_points = 10
cubic_points = []
for i in range(number_of_points):
cubic_points.append(cubic.point(i/(NUM_SAMPLES-1)))
print('cubic points', path_points)
line_points = []
for i in range(number_of_points):
line_points.append(line.point(i/(NUM_SAMPLES-1)))
print('line points', path_points)
I needed a more general form to extract multiple paths with different features. Its a recursive solution to work with path, list of path, list of segments and single segments simultaneously. And you can specify the sample_points
per segment for curves, but a line stays 2 points to not add extra points without a need:
import svgpathtools.path
def svgpathtools_unpacker(obj, sample_points=10):
path = []
if isinstance(obj, (svgpathtools.path.Path, list)):
for i in obj:
path.extend(svgpathtools_unpacker(i, sample_points=sample_points))
elif isinstance(obj, svgpathtools.path.Line):
path.extend(obj.bpoints())
elif isinstance(obj, (svgpathtools.path.CubicBezier, svgpathtools.path.QuadraticBezier)):
path.extend(obj.points(np.linspace(0,1,sample_points)))
else:
print(type(obj))
return np.array(path)
and how you can use it:
import matplotlib.pyplot as plt
bezier_curve = svgpathtools.path.CubicBezier(start=(300+100j), control1=(100+100j), control2=(200+200j), end=(200+300j))
bezier_quad = svgpathtools.path.QuadraticBezier(bezier_curve.end, control=(200+200j), end=(300+150j))
line = svgpathtools.path.Line(start=bezier_quad.end, end=bezier_curve.start)
bezier_path = svgpathtools.path.Path(bezier_curve, bezier_quad, line)
plt.figure()
for i in [10, 100]:
xy = svgpathtools_unpacker(bezier_path, sample_points=i)
plt.plot(xy.real, xy.imag, label=f'sample_points={i}')
plt.legend()