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?

enter image description here

  • $\begingroup$ blender.stackexchange.com/questions/95289/… $\endgroup$ Mar 16, 2020 at 12:11
  • $\begingroup$ @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 :/ $\endgroup$ Mar 16, 2020 at 13:04
  • $\begingroup$ blendermarket.com/products/curve-offset-in-3d-space $\endgroup$ Mar 16, 2020 at 13:22
  • $\begingroup$ @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 $\endgroup$ Mar 16, 2020 at 13:41
  • $\begingroup$ 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! $\endgroup$ Mar 17, 2020 at 4:17

1 Answer 1


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

enter image description here

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
    #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...

  • $\begingroup$ 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 $\endgroup$ Mar 18, 2020 at 22:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .