# Offset points in space with python

I have a path-curve that uses a profile with two circles (reference and profile on the image below). I'd like to create a second curve at the same distance as the distance between the profile circles (red arrow between the profile circles) — a black curve in the expected result. If I simply offset each point of a curve by that distance, my curve is 'parallel' to the original (my result), how do I calculate those vectors that are used for offset?

• blender.stackexchange.com/questions/95289/… – Duarte Farrajota Ramos Mar 16 '20 at 12:11
• @DuarteFarrajotaRamos thank you for your input, forgot to mention that I use polygon splines here, not bezier curves: all the roundness comes from a bunch of points, so mathematically this should be possible. I know how to do that in 2d, but not in 3d :/ – Sergey Kritskiy Mar 16 '20 at 13:04
• blendermarket.com/products/curve-offset-in-3d-space – Duarte Farrajota Ramos Mar 16 '20 at 13:22
• @DuarteFarrajotaRamos thank you for all the links but I'm looking for a python solution for polygon splines: curves offset is a paid addon that works only on bezier curves – Sergey Kritskiy Mar 16 '20 at 13:41
• I am not a Python expert, but it would seem that you would have to iterate through point by point and get the vector between points to go the direction you want. I hope to see the solution! – Coby Randal Mar 17 '20 at 4:17

Not perfect (but imperfections are like the one we have bevelling or insetting, etc. too far), and that can also be the case with a curve and bevel object.

The principle:

• Start with the first two vertices of the curve, a segment S0 = (V0, V1)
• Get the location of the starting position of the // curve P0
• Calculate the displacement D0 = (V0, P0)
• Calculate the normal N0 = D0 cross (V0, V1)

Now iterate for 1 to n:

• Get the new normal by D(i-1) cross S(i)
• Get the new D(i) = S(i) cross N(i)
• The location is Pi = Vi + Di

Result with imperfection (the active curve with 4 empties as starting points):

Main part of the code is the following (complete code is in the blend file below, so this snippet won't work alone).

To use it from the blend file: the curve is the active object, starting points are selected object.

def create_parallel(curve_object, from_object):
#Get the poly spline
poly = curve_object.data.splines[0]

#Create the target poly spline
target_object = create_curve(curve_object, bpy.context.scene.collection)
target_poly = target_object.data.splines[0]

#Get the starting location in curve coordinates
starting_location = curve_object.matrix_world.inverted() @ from_object.location

#Delta from the first point
delta = starting_location - poly.points[0].co.xyz
#Its length
distance = delta.length
#Keep it normalized
delta.normalize()
#Orthogonal direction
ortho = delta.cross( poly.points[1].co.xyz - poly.points[0].co.xyz ).normalized()

#First vertex
target_poly.points[0].co = (*starting_location, poly.points[0].co.w)

for i in range(len(poly.points)-1):
prev = poly.points[i].co
co = poly.points[i+1].co

axis = (co.xyz - prev.xyz)

#Orthogonal direction is recalculated to be orthogonal to this delta and the next curve segment
ortho = delta.cross(axis).normalized()

#New delta is calculated as being orthogonal to the curve orientation and the current orthogonal direction
delta = (axis).cross( ortho ).normalized()

#Calculate the location of the current vertex
location = co.xyz + delta * distance
#Assign it to the curve
target_poly.points[i+1].co = (*location, co.w)


Improving it, should need to relax (or remove) vertices comparing the curvatures of the curves... another story...

• This is amazing, thank you! I went with a different approach (more bruteforce and stupid) but this helped me to understand better the math behind the problem – Sergey Kritskiy Mar 18 '20 at 22:17