Path Planning Tutorial¶
This tutorial covers InterpolatePy's geometric path primitives and Frenet frame computation for spatial trajectory planning.
Geometric Path Primitives¶
InterpolatePy provides two basic geometric paths parameterized by arc length.
Linear Path¶
A straight line between two 3D points:
from interpolatepy import LinearPath
import numpy as np
# Define endpoints
pi = np.array([0, 0, 0]) # Start
pf = np.array([5, 3, 2]) # End
path = LinearPath(pi, pf)
# Path properties
print(f"Path length: {path.length:.2f}")
print(f"Tangent (constant): {path.tangent}")
# Evaluate by arc length parameter s
s = 2.0 # 2 units along the path
position = path.position(s)
velocity = path.velocity(s) # Constant tangent vector
acceleration = path.acceleration(s) # Zero for straight line
print(f"Position at s={s}: {position}")
Circular Path¶
A circular arc in 3D space defined by an axis of rotation, a point on the axis, and a starting point:
from interpolatepy import CircularPath
import numpy as np
# Define circular arc
r = np.array([0, 0, 1]) # Rotation axis (Z-axis)
d = np.array([0, 0, 0]) # Point on axis (origin)
pi = np.array([1, 0, 0]) # Starting point on circle
path = CircularPath(r, d, pi)
# The radius is computed from pi and the axis
print(f"Radius: {path.radius:.2f}")
# Evaluate at arc length
s = np.pi # Half circle
position = path.position(s)
velocity = path.velocity(s) # Tangent to circle
acceleration = path.acceleration(s) # Centripetal
print(f"Position at s=pi: {position}")
print(f"Acceleration (centripetal): {acceleration}")
Combining Paths with Motion Laws¶
Geometric paths define where to move. Motion laws define how fast. Combine them for complete trajectories:
from interpolatepy import LinearPath, PolynomialTrajectory, BoundaryCondition, TimeInterval
import numpy as np
import matplotlib.pyplot as plt
# 1. Define the geometric path
pi = np.array([0, 0, 0])
pf = np.array([10, 5, 3])
path = LinearPath(pi, pf)
# 2. Define the motion law (how arc length varies with time)
# Start and end at rest, traverse the full path length
initial = BoundaryCondition(position=0.0, velocity=0.0)
final = BoundaryCondition(position=path.length, velocity=0.0)
interval = TimeInterval(start=0.0, end=5.0)
motion_law = PolynomialTrajectory.order_5_trajectory(initial, final, interval)
# 3. Combine: evaluate motion law to get arc length, then path to get position
t_eval = np.linspace(0, 5, 200)
positions = []
velocities = []
for t in t_eval:
s, ds_dt, _, _ = motion_law(t) # Arc length and its derivatives
pos = path.position(s) # 3D position
vel = path.velocity(s) * ds_dt # Chain rule: dp/dt = dp/ds * ds/dt
positions.append(pos)
velocities.append(np.linalg.norm(vel))
positions = np.array(positions)
# Plot
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 3D path
ax3d = fig.add_subplot(121, projection="3d")
ax3d.plot(positions[:, 0], positions[:, 1], positions[:, 2], "b-", linewidth=2)
ax3d.scatter(*pi, color="green", s=100, label="Start")
ax3d.scatter(*pf, color="red", s=100, label="End")
ax3d.set_xlabel("X")
ax3d.set_ylabel("Y")
ax3d.set_zlabel("Z")
ax3d.set_title("Linear Path with 5th-Order Motion Law")
ax3d.legend()
# Speed profile
axes[1].plot(t_eval, velocities, "r-", linewidth=2)
axes[1].set_xlabel("Time (s)")
axes[1].set_ylabel("Speed")
axes[1].set_title("Speed Profile (smooth start/stop)")
axes[1].grid(True)
plt.tight_layout()
plt.show()
Frenet Frame Computation¶
The Frenet-Serret frame provides a local coordinate system (tangent, normal, binormal) at each point along a curve. This is essential for tool orientation in CNC machining and robotic path following.
Basic Usage¶
from interpolatepy import compute_trajectory_frames
from interpolatepy import helicoidal_trajectory_with_derivatives
import numpy as np
# Define a helicoidal (helical) trajectory
r = 2.0 # Radius
d = 0.5 # Pitch per radian
def helix_func(u):
return helicoidal_trajectory_with_derivatives(u, r, d)
# Compute frames at sampled points
u_values = np.linspace(0, 4 * np.pi, 100)
points, frames = compute_trajectory_frames(helix_func, u_values)
# points: (100, 3) array of 3D positions
# frames: (100, 3, 3) array where frames[i] = [tangent, normal, binormal]
print(f"Tangent at u=0: {frames[0, 0]}")
print(f"Normal at u=0: {frames[0, 1]}")
print(f"Binormal at u=0: {frames[0, 2]}")
Visualizing Frames¶
from interpolatepy import compute_trajectory_frames, plot_frames
from interpolatepy import helicoidal_trajectory_with_derivatives
import numpy as np
import matplotlib.pyplot as plt
r, d = 2.0, 0.5
u_values = np.linspace(0, 4 * np.pi, 100)
def helix_func(u):
return helicoidal_trajectory_with_derivatives(u, r, d)
points, frames = compute_trajectory_frames(helix_func, u_values)
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection="3d")
plot_frames(ax, points, frames, scale=0.5, skip=10)
ax.set_title("Helicoidal Trajectory with Frenet Frames")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
plt.tight_layout()
plt.show()
Tool Orientation¶
For CNC machining or robotic applications, you can specify an additional tool orientation relative to the Frenet frame:
from interpolatepy import compute_trajectory_frames
from interpolatepy import circular_trajectory_with_derivatives
import numpy as np
def circle_func(u):
return circular_trajectory_with_derivatives(u, radius=3.0)
u_values = np.linspace(0, 2 * np.pi, 50)
# Add tool tilt: (roll, pitch, yaw) relative to Frenet frame
points, frames = compute_trajectory_frames(
circle_func,
u_values,
tool_orientation=(0.1, -0.2, 0.0) # Small roll and pitch tilt
)
Custom Path Functions¶
Any function returning (position, first_derivative, second_derivative) works:
from interpolatepy import compute_trajectory_frames
import numpy as np
def lissajous_3d(u):
"""3D Lissajous curve with derivatives."""
position = np.array([
np.sin(2 * u),
np.sin(3 * u),
np.sin(5 * u) * 0.5
])
first_derivative = np.array([
2 * np.cos(2 * u),
3 * np.cos(3 * u),
5 * np.cos(5 * u) * 0.5
])
second_derivative = np.array([
-4 * np.sin(2 * u),
-9 * np.sin(3 * u),
-25 * np.sin(5 * u) * 0.5
])
return position, first_derivative, second_derivative
u_values = np.linspace(0, 2 * np.pi, 200)
points, frames = compute_trajectory_frames(lissajous_3d, u_values)
Built-in Trajectory Functions¶
InterpolatePy provides ready-made trajectory functions for common curves:
| Function | Description |
|---|---|
helicoidal_trajectory_with_derivatives(u, r, d) |
Helical curve with radius r and pitch d |
circular_trajectory_with_derivatives(u, radius) |
Circle in the XY plane |
Next Steps¶
- API Reference for complete method documentation
- Algorithms Guide for Frenet-Serret mathematical foundations
- Example scripts:
examples/simple_paths_ex.py,examples/frenet_frame_ex.py