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()