@zipit said in Set Spline Tangents with Python Generator:

```
> """Example for constructing smooth tangents in a bezier spline.
>
> Run this with a spline object selected that has at least three vertices. It
> will set the tangents of the second vertex of the spline in respect to its
> neighbors.
> """
>
> import c4d
>
>
> def project_on_line(p, a, b):
> """Projects the point p onto the line which contains the line segment ab.
>
> Args:
> p (c4d.Vector): The point to project.
> a (c4d.Vector): The first point of the line segment.
> b (c4d.Vector): The second point of the line segment.
>
> Returns:
> c4d.Vector: The projection of p onto the line described by the line
> segment ab.
> """
> # Not much to explain here, check a trigonometry textbook on point line
> # projections / the dot product if you are lost.
> ab = b - a
> t = ((p - a) * ab) / ab.GetLengthSquared()
> return a + ab * t
>
>
> def construct_vertex_frame(p, q, r):
> """Constructs a frame for a spline vertex and its two normalized tangent
> lengths.
>
> Args:
> p (c4d.Vector): The point of the vertex for which to construct the
> frame.
> q (c4d.Vector): The point of the vertex which is the vertex before p.
> r (c4d.Vector): The point of the vertex which is the vertex after p.
>
> Returns:
> float, float, c4d.Vector: The normalized length of the left and
> right tangent and the frame.
> """
>
> # The Vectors pointing from p to q and from p to r.
> a, b = q - p, r - p
>
> # Now we construct our frame axis, I did use axis labels instead of the
> # terms normal, binormal and tangent to keep things unambiguous.
>
> # The x-axis or tangent of the frame, i.e. the axis along which we will
> # place out tangent control points.
> x = (r - q).GetNormalized()
> # The z-axis or normal of the frame, which is the normal to the plane
> # of the three points. We just take the cross product of two vectors that
> # lie in that plane.
> z = (x % a).GetNormalized()
> # The y axis or binormal of the frame, we just build it with the two
> # other axis.
> y = (x % z).GetNormalized()
> # The finished matrix/frame. It is important to set the offset to the
> # zero vector and not p, since each tangent pair lives in its own tangent
> # space relative to their vertex.
> frame = c4d.Matrix(c4d.Vector(), x, y, z)
>
> # We are technically finished here, but we could do some more, as we
> # will have to determine the length of each tangent. We could just set
> # these to a fraction of the vectors a, b, but this will give us odd
> # results when theta, the angle between a and b is getting smaller and
> # smaller. Instead we will project both a and b onto x and return the
> # lengths of those two projections as the normalized length for the
> # respective tangent.
> origin = c4d.Vector()
> tlength_left = project_on_line(a, origin, x).GetLength()
> tlength_right = project_on_line(b, origin, x).GetLength()
>
> # Return the length of the left and right tangent and the frame.
> return tlength_left, tlength_right, frame
>
>
> def align_tangents(node, i, rel_scale_left=.5, rel_scale_right=.5):
> """Aligns the tangents of a spline at a given index.
>
> Calculates the frame for the given vertex and sets its tangents for a
> smooth interpolation in respect to its two neighbors. Will also force
> the spline into bezier mode.
>
> Note:
> This currently cannot handle the first and last vertex. It is trivial
> to implement this, but things would have gotten more bloated, so I
> kept short and (slightly) flawed.
>
> Args:
> node (c4d.SplineObject): The spline.
> i (int): The index of the vertex to align the tangents for. Has to
> satisfy "0 < i < vertex_count - 1".
> rel_scale_left (float, optional): The relative scale of the left
> tangent. Defaults to 50%.
> rel_scale_right (float, optional): The relative scale of the right
> tangent. Defaults to 50%.
>
> Raises:
> TypeError: When 'node' is not a SplineObject.
> ValueError: When 'i' is out of bounds.
> """
> if not isinstance(node, c4d.SplineObject):
> raise TypeError("Expected SplineObject as input.")
>
> points = node.GetAllPoints()
> # Out of bounds condition for the spline vertex indices.
> if i < 1 or len(points) < i + 1:
> raise ValueError("Index out of bounds.")
>
> # The three consecutive spline vertices that form the plane. p is
> # the mid vertex for which we want to set the tangents and q and r
> # are the vertices previous and next to it.
> q, p, r = points[i-1:i+2]
> # The normalized length of the tangents and the frame.
> tlength_left, tlength_right, frame = construct_vertex_frame(p, q, r)
>
> # Constructing the tangents:
> tangent_left = c4d.Vector(-tlength_left * rel_scale_left, 0, 0) * frame
> tangent_right = c4d.Vector(tlength_right * rel_scale_right, 0, 0) * frame
>
> # Update the spline.
> if node[c4d.SPLINEOBJECT_TYPE] is not c4d.SPLINETYPE_BEZIER:
> node[c4d.SPLINEOBJECT_TYPE] = c4d.SPLINETYPE_BEZIER
> node.SetTangent(i, tangent_left, tangent_right)
> node.Message(c4d.MSG_UPDATE)
>
> # For visualization purposes we insert the frame of our vertex as a
> # null object. SHOULD BE COMMENTED OUT.
> null = c4d.BaseList2D(c4d.Onull)
> null.SetMg(frame)
> null.SetName("frame_for_vertex_" + str(i))
> doc.InsertObject(null)
>
> # Push the changes to Cinema.
> c4d.EventAdd()
>
>
> def main():
> """Entry point.
> """
> # ALign the tangents of the second vertex of the selected spline object.
> if op:
> align_tangents(op, 1)
>
>
> if __name__ == "__main__":
> main()
```

This was extremely helpful, thank you very much. My 3d geometry / Calc III is pretty rusty and seeing this really helped.