Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Python Generator associated with User Data?

    Cinema 4D SDK
    python
    3
    4
    1.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • M
      Matt_B
      last edited by Matt_B

      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?

      1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand
        last edited by ferdinand

        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():
                """
                """
                if not c4d.threading.GeIsMainThread():
                    return
                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()
        

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 1
        • r_giganteR
          r_gigante
          last edited by

          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

          1 Reply Last reply Reply Quote 1
          • M
            Matt_B
            last edited by

            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.

            1 Reply Last reply Reply Quote 0
            • ferdinandF ferdinand referenced this topic on
            • First post
              Last post