Hello, I have got quite far with the methods from the method reporter, but I am hitting a wall.
I have a path segment that has an intersection point with another path segment. Essentially, I would like to insert this intersection point on my first segment and thus create a new, shorter segment.
This technically works, but it doesn’t change the position of the other offcurve nodes of the segment.
So, I would need: the path time of the point closest to my given intersection point, or a method that directly inserts the point into the segment, but also shortens the offcurve nodes accordingly (so that the segment doesn’t visually change), or a method that lets me imitate “make corner” using two segments.
Okay, Claude answered my question pretty well (some manual edits to make it work with the Python API had to be done). To find the path time of the highlighted extra node:
import math
from typing import Tuple
def path_time_for_point(segment, position: Tuple[float, float]) -> float:
"""
Find the path time parameter t where the given position lies on the cubic Bézier segment.
Assumes the point is definitely on the segment.
Args:
segment: GSPathSegment with four control points
position: Target coordinate (x, y) that lies on the segment
Returns:
float: Parameter t in [0, 1] where the curve passes through the position
"""
if segment.count() != 4:
raise ValueError("Segment must have exactly four control points")
points = []
# Extract control points
for index in range(4):
points.append(segment.objectAtIndexedSubscript_(index))
p0, p1, p2, p3 = points
target_x, target_y = position
# Quick check for endpoints
if (target_x, target_y) == (p0.x, p0.y):
return 0.0
if (target_x, target_y) == (p3.x, p3.y):
return 1.0
def bezier_point(t: float) -> Tuple[float, float]:
"""Calculate point on Bézier curve at parameter t"""
u = 1 - t
x = u ** 3 * p0.x + 3 * u ** 2 * t * p1.x + 3 * u * t ** 2 * p2.x + t ** 3 * p3.x
y = u ** 3 * p0.y + 3 * u ** 2 * t * p1.y + 3 * u * t ** 2 * p2.y + t ** 3 * p3.y
return x, y
# Use Newton-Raphson to find t
# Start with a good initial guess (midpoint)
t = 0.5
h = 1e-8 # Step for numerical derivatives
for _ in range(20): # Usually converges in just a few iterations
# Current point
x, y = bezier_point(t)
# Check if we're close enough
if abs(x - target_x) < 1e-10 and abs(y - target_y) < 1e-10:
return t
# Calculate numerical derivatives
if t + h <= 1:
x_plus, y_plus = bezier_point(t + h)
dx_dt = (x_plus - x) / h
dy_dt = (y_plus - y) / h
else:
x_minus, y_minus = bezier_point(t - h)
dx_dt = (x - x_minus) / h
dy_dt = (y - y_minus) / h
# Newton-Raphson step for the x-coordinate equation
# We solve x(t) - target_x = 0
if abs(dx_dt) > 1e-12:
t_new = t - (x - target_x) / dx_dt
# Clamp to valid range
t_new = max(0, min(1, t_new))
t = t_new
else:
# If dx_dt is too small, try using y-coordinate
if abs(dy_dt) > 1e-12:
t_new = t - (y - target_y) / dy_dt
t_new = max(0, min(1, t_new))
t = t_new
else:
break
return t
n = Layer.selection[0]
p = n.parent
s = p.segmentAtIndex_(n.index)
s2 = p.segmentAtIndex_(n.index + 2)
intersection = s.intersectionPoints_(s2)[0]
print(path_time_for_point(s, intersection))