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(): """ """ 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()
-
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.
-