Python Generator associated with User Data?



  • I've written a Python Generator which uses 14 User Data items (located on the the generator object).

    Is there a way to associate the User Data definition with my Python Generator script, such that whenever I create a new instance of this PyGen object, the associated User Data is automatically added to it?



  • Hi,

    there is no official way, since there are no dedicated messages being sent and functions being called for this task. However you can just piggyback some of the other messages that are being sent. Be aware that not all Python scripting nodes (e.g.: tag, object, xpresso, ...) do receive the same messages and therefor you will have to adapt your approach for each of them. Here is an example for a python generator object that builds its interface on its own. (the relevant part is the message() function).

    import c4d
    import random
    
    from c4d.utils import SendModelingCommand
    
    
    def get_greeble(op, subdivisions, probability, probability_decay, time, seed):
        """ Computes a greeble object for a polygon object input. Calls itself
         recursively for multiple subdivisions.
    
        The subdivision scheme is slight variation on the one midpoint subdivision
         scheme (used for example in Catmull-Clark SDS). The differences are that
         along the v axis two center points are being generated and the surrounding
         edge points are not centered on their edges. This will result in a
         segmented (not connected) mesh. The scheme in detail is as follows:
    
        a, b, c, d   - The points of the polygon (the vertex points)
        du, dv, dw   - The random offsets (.25 to .75) for the intermediate points
        p, q, r, s   - The intermediate points (the edge points)
        g, h         - The two center points
        A, B, C, D   - The resulting four new polygons
    
                       du
                   ┌───────┐
                   ↓       ↓
    
                  [d]───── r ───────[c]
                   │   D   ┆         │
           ┌───>   s┄┄┄┄┄┄┄g    C    │
           │       │       ┆         │
        dv │       │   A   h┄┄┄┄┄┄┄┄┄q   <───┐
           │       │       ┆    B    │       │ dw
           └───>  [a]───── p ───────[b]  <───┘
    
        Args:
            op (c4d.BaseObject): The input object to greeble.
            subdivisions (int): The number of subdivisions per polygon.
            probability (float): The chance that a subdivision occurs on a polygon. 
            probability_decay (float): The decay of probability per recursion.
            time (float): The time for computing the random offsets.
            seed (int): The seed for computing the random offsets.
    
        Returns:
            c4d.PolygonObject or None: The greeble or None if op was not a valid
             input object.
        """
    
        # Get the caches
        if isinstance(op, c4d.BaseObject):
            deform_cache = op.GetDeformCache()
            cache = op.GetCache() if deform_cache is None else deform_cache
            op = cache if isinstance(cache, c4d.PolygonObject) else op
    
        if not isinstance(op, c4d.PolygonObject):
            return None
    
        # Data IO
        points = op.GetAllPoints()
        polygons = op.GetAllPolygons()
        new_points = []
        new_polygons = []
    
        def lerp(a, b, t): return a + (b-a) * t
        def noise(p): return c4d.utils.noise.Noise(p + time)
        seed = 1./seed
    
        # For every polygon in the input object
        for cpoly in polygons:
    
            # The points of the polygon
            a, b = points[cpoly.a], points[cpoly.b]
            c, d = points[cpoly.c], points[cpoly.d]
            # The mean of the points as the identity for the noise seed
            ip = (a + b + c + d) * .25
    
            # Skip a subdivision step and just copy the old polygon
            if random.random() > probability:
                new_points += [a, b, c, d]
                bid = len(new_points) - 1
                A = c4d.CPolygon(bid - 3, bid - 2, bid - 1, bid - 0)
                new_polygons.append(A)
                continue
    
            # The random offsets
            du = noise(ip + c4d.Vector(seed, 0., 0.))
            dv = noise(ip + c4d.Vector(0., seed, 0.))
            dw = noise(ip + c4d.Vector(0., 0., seed))
            # The edge points
            p, r = lerp(a, b, du), lerp(c, d, 1. - du)
            q, s = lerp(b, c, dw), lerp(a, d, dv)
            # The center points
            g, h = lerp(p, r, dv), lerp(p, r, dw)
    
            # append the generated points to our output point list.
            # id offsets:  9, 8, 7, 6, 5, 4, 3, 2, 1, 0
            new_points += [a, b, c, d, p, q, r, s, g, h]
            # the base id for the point indices of the current polygon group
            bid = len(new_points) - 1
            """ [d]───── r ───────[c]
                 │   D   ┆         │
                 s┄┄┄┄┄┄┄g   C     │
                 │       ┆         │
                 │   A   h┄┄┄┄┄┄┄┄┄q
                 │       ┆   B     │
                [a]───── p ───────[b] """
            #                      a,       p,       g,       s
            A = c4d.CPolygon(bid - 9, bid - 5, bid - 1, bid - 2)
            #                      p,       b,       q,       h
            B = c4d.CPolygon(bid - 5, bid - 8, bid - 4, bid - 0)
            #                      h,       q,       c,       r
            C = c4d.CPolygon(bid - 0, bid - 4, bid - 7, bid - 3)
            #                      s,       g,       r,       d
            D = c4d.CPolygon(bid - 2, bid - 1, bid - 3, bid - 6)
    
            new_polygons += [A, B, C, D]
    
        # Generate the output object
        res = c4d.PolygonObject(len(new_points), len(new_polygons))
        res.SetAllPoints(new_points)
        for index, cpoly in enumerate(new_polygons):
            res.SetPolygon(index, cpoly)
    
        # subdivide the result further
        if subdivisions > 1:
            res = get_greeble(op=res,
                              subdivisions=subdivisions - 1,
                              probability=probability * probability_decay,
                              probability_decay=probability_decay,
                              time=time,
                              seed=seed)
        return res
    
    def get_output(op):
        """
        """
        if op is None:
            return c4d.BaseObject(c4d.Onull)
    
    
        for i in range(1):
            poly_ids = list(range(op.GetPolygonCount()))
            random.shuffle(poly_ids)
    
            selection = c4d.BaseSelect()
            for j in range(10):
                selection.Select(poly_ids.pop(0))
    
            op.GetPolygonS().DeselectAll()
            selection.CopyTo(op.GetPolygonS())
    
            res = SendModelingCommand(c4d.MCOMMAND_GENERATESELECTION, [op],
                                      c4d.MODELINGCOMMANDMODE_POLYGONSELECTION)
            tag = op.GetLastTag()
            tag.SetName(i)
        return op
    
    def main():
        """
        """
        res = None
    
        if op.GetUserDataContainer():
            # Get the user data
            obj = op[c4d.ID_USERDATA, 1]
            subdivisions = op[c4d.ID_USERDATA, 2]
            probability = op[c4d.ID_USERDATA, 3]
            probability_decay = op[c4d.ID_USERDATA, 4]
            time = op[c4d.ID_USERDATA, 5]
            seed = op[c4d.ID_USERDATA, 6]
    
            random.seed(seed)
    
            # Compute the greeble object
            res = get_greeble(op=obj,
                              subdivisions=subdivisions,
                              probability=probability,
                              probability_decay=probability_decay,
                              time=time,
                              seed=seed)
    
        # Return a null object if get_greeble failed, else return the greeble
        return get_output(res)
    
    def message(mid, data):
        """
        """
        if mid == c4d.MSG_GETREALCAMERADATA and not op.GetUserDataContainer():
            """
            """
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_BASELISTLINK)
            bc[c4d.DESC_NAME] = "Object"
            eid = op.AddUserData(bc)
    
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG)
            bc[c4d.DESC_NAME] = "Subdivisions"
            bc[c4d.DESC_MIN] = 1
            bc[c4d.DESC_MAX] = 16
            bc[c4d.DESC_MINSLIDER] = 1
            bc[c4d.DESC_MAXSLIDER] = 8
            bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_LONGSLIDER
            eid = op.AddUserData(bc)
            op[eid] = 3
    
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL)
            bc[c4d.DESC_NAME] = "Subdivision Probability"
            bc[c4d.DESC_MIN] = 0.
            bc[c4d.DESC_MAX] = 1.
            bc[c4d.DESC_MINSLIDER] = 0.
            bc[c4d.DESC_MAXSLIDER] = 1.
            bc[c4d.DESC_STEP] = .005
            bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT
            bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_REALSLIDER
            eid = op.AddUserData(bc)
            op[eid] = 1.
    
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL)
            bc[c4d.DESC_NAME] = "Subdivision Probability Decay"
            bc[c4d.DESC_MIN] = 0.
            bc[c4d.DESC_MAX] = 1.
            bc[c4d.DESC_MINSLIDER] = 0.
            bc[c4d.DESC_MAXSLIDER] = 1.
            bc[c4d.DESC_STEP] = .005
            bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT
            bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_REALSLIDER
            eid = op.AddUserData(bc)
            op[eid] = .75
    
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL)
            bc[c4d.DESC_NAME] = "Animation"
            bc[c4d.DESC_MIN] = 0.
            bc[c4d.DESC_STEP] = .01
            bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT
            eid = op.AddUserData(bc)
            op[eid] = .0
    
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG)
            bc[c4d.DESC_NAME] = "Seed"
            bc[c4d.DESC_MIN] = 1
            eid = op.AddUserData(bc)
            op[eid] = 1
            c4d.EventAdd()
    


  • Hi Matt_B, thanks for reaching out us.

    Aside from the solution offered by @zipit, a less elegant one could be to save your python generator with its userdata in a "template" c4d scene, load the scene in a script, copy the python generator from the template doc into the new doc and everything will be right on place.

    Best, R



  • Thanks for the great suggestions! ❤

    I just discovered I can also use "File | Save Object Preset" and "File | Load Object Preset" in the Attribute Manager.


Log in to reply