SOLVED Actively Link Hair Guides to a Spline or Alembic?

Hi,

By default, when you create a guide, you can't modify it outside the hair object.
Works for static meshes but not really for animated/dynamics objects.

I wanted to actively link the hair guides to separate object (i.e. spline and alembic) like the hair system of other DCCs. So that I can animate/do dynamics on the spline and perform much more feasible art direction.

Not possible natively but was wondering if this is possible in Python?

Hello @bentraje,

Thank you for reaching out to us. Your question is a bit ambiguous, but I largely understand it so that you want to control hair guide vertices programmatically, e.g., bind the vertex of a hair guide to a null object, or even more complex cases as "splines and alembic". The short answer is, no, that is not possible from Python, but it would be from C++.

The longer answer is that while there is c4d.modules.hair in Python, you are effectively restricted to read access. You can write hair guide vertex values, and they will be reflected in a scene in a certain sense, but you lack the means (MSG_HAIR_GET_OBJECT_TYPE) to properly update the hair object using these guides. Which will then result in unrooted hair guides, duplicated guides, and finally crashes. Find an example below.

I will update the Python docs with a warning for others not to run into the same trap I did. What you could however do, is using the Hair Object > Guides > Editing > Points Link parameter to drive the guides. This could be with one of the many deformation tools of Cinema 4D, please reach out to user support for any help with this, or using Python, find a simple example below.

Cheers,
Ferdinand

Hair Module in Python

You should not be doing what I am doing here, as this will lead to crashes.

Scene File: python_hair_crash.c4d

Spline Workaround

Scene File: python_hair_spline_rig.c4d

Result:

Code:

"""Demonstrates how to drive hair guides with a null object.

This could all be done much more elaborately, I implemented here the most simple version possible.
"""

import c4d
import typing

doc: c4d.documents.BaseDocument
op: c4d.BaseTag
bt: typing.Optional[c4d.threading.BaseThread]

ID_SPLINE_OBJECT: int = (c4d.ID_USERDATA, 1)
ID_SPLINE_SEGMENT: int = (c4d.ID_USERDATA, 2)

def GetSegmentVertexIndexRange(spline: c4d.SplineObject, index: int) -> tuple[int, int, int]:
    """Returns the vertex index range for a given segment in a spline.

    Args:
        spline: The spline to evaluate.
        index: The segment index to evaluate.

    Returns:
        The number of vertices in #index,
        The first vertex index in #index,
        The last vertex index in #index
    """
    # Validate the inputs.
    if not isinstance(spline, c4d.SplineObject):
        raise TypeError(f"{spline = }")

    count: int = spline.GetSegmentCount()
    if index > (count - 1):
        raise IndexError(f"Segment index {index} is out of bounds for {spline}.")

    # Get the start and end index by summing up the number of points in all segments up to #index.
    a, b = 0, 0
    for i in range(count):
        if i < index:
            a += spline.GetSegment(i)["cnt"]
            continue
        b = a + spline.GetSegment(i)["cnt"] - 1
        break

    return b - a, a, b

def main() -> None:
    """
    """
    # Get the inputs.
    target: c4d.BaseObject = op.GetMain()
    if not isinstance(target, c4d.BaseObject):
        raise RuntimeError("Tag is not attached to an object.")

    spline: c4d.SplineObject = op[ID_SPLINE_OBJECT]
    if not isinstance(spline, c4d.SplineObject):
        raise TypeError(f"{spline = }")

    # Get the vertex index range for the given segment index, e.g., (0, 8) for the first segment or
    # (9, 17) for the second segment, for a spline with two segments, each holding eight points.
    index: int = op[ID_SPLINE_SEGMENT]
    count, a, b = GetSegmentVertexIndexRange(spline, index)

    # The position of the null object in the coordinate system of the spline object and all its points.
    p: c4d.Vector = ~spline.GetMg() * target.GetMg().off
    points: list[c4d.Vector] = spline.GetAllPoints()

    # The last point q in the segment and the delta vector from q to our target point p.
    q: c4d.Vector = points[b]
    delta: c4d.Vector = p - q

    # Iterate over all points in the segment and adjust their position so that points[b] == p and
    # points[a] == points[a], and all other points cubically interpolated in between.
    for segmentIndex, pointIndex in enumerate(range(a, b + 1)):
        weight: float = (float(segmentIndex) / float(count)) ** 3
        print (segmentIndex, pointIndex, weight)
        points[pointIndex] += (delta * weight)

    spline.SetAllPoints(points)
    spline.Message(c4d.MSG_UPDATE)

Hi @ferdinand

Thanks for the response and heads up on the crashes.

RE: you want to control hair guide vertices programmatically
Yep yep you are right on this part. Basically, have a geometric hair animated and simulated for preview. But rendered on the actual hair object.

This is the workflow for other DCC, and the more logical one. This way you separate the hair source and hair generation. It's easier to debug.

Anyhow, for looking at your python example, this should get me by on my current use case.
Thanks for your illustration as always!

Will close thread now.