Cinema 4D’s Field system is a powerhouse for procedural animation, but when paired with Python, it becomes a playground for precision and control. In this post, we’ll explore a custom Python Field script that enables directional sweeps across your geometry—perfect for animating growth, transitions, or revealing effects with finesse.
🎯 What This Script Does
This Python Field script creates a directional sweep plane that moves across your geometry in world space. As the plane progresses, it evaluates each point’s position and assigns a falloff value based on its distance to the plane. The result? A smooth, controllable gradient of influence that can drive MoGraph effects like scale, color, or visibility.
🧩 User Controls
The script is designed to be artist-friendly, with three intuitive user data sliders:
- Direction (
ID_DIRECTION): Choose from six sweep directions (+X, -X, +Y, -Y, +Z, -Z). - Progress (
ID_PROGRESS): Controls the position of the sweep plane (0–100%). - Falloff Width (
ID_FALLOFF): Defines the thickness of the transition zone (0–100%).
These parameters let you animate the field’s influence over time or trigger effects based on spatial thresholds.
🧠 How It Works
Here’s a breakdown of the core logic:
- World-Space Bounding Box: The script calculates the min/max coordinates of all sample points to define the sweep range.
- Sweep Plane Position: Based on the selected direction and progress, it computes the current position of the plane.
- Falloff Evaluation: Each point is evaluated against the plane using a smoothstep function, creating a soft gradient from full influence (1.0) to none (0.0).
This approach ensures consistent behavior regardless of object scale or orientation.
🧪 Practical Use Cases
Motion designers can use this script to:
- Reveal geometry along a specific axis (e.g., growing buildings from the ground up).
- Trigger effects like color changes or deformation as the sweep passes.
- Control MoGraph clones with directional logic, enabling cascading animations.
It’s especially powerful when combined with Fields like Delay, Shader, or Random for layered effects.
🛠️ Integration Tips
To use this script:
- Create a Python Field object.
- Add three User Data entries:
- Integer Cycle for Direction (values 0–5).
- Float Slider for Progress (0–100).
- Float Slider for Falloff Width (0–100).
- Paste the script into the Python Field.
- Animate the Progress slider to drive the sweep.
Make sure your geometry or MoGraph setup is using the Field as a modifier or effector input.
🧵 Final Thoughts
This script exemplifies how Python can extend Cinema 4D’s Field system into a programmable canvas. By abstracting directional logic and falloff control, it empowers motion designers to build dynamic, responsive setups with minimal effort.
Want to go further? Try chaining this with other Python Fields, or use it to drive vertex maps for material blending. The possibilities are endless.
import c4d
from c4d.modules import mograph as mo
# User Data IDs
ID_DIRECTION = 1 # Integer Cycle: +X, -X, +Y, -Y, +Z, -Z
ID_PROGRESS = 2 # Float Slider: 0–100% (or 0..1)
ID_FALLOFF = 3 # Float Slider: 0–100% (or 0..1)
def Sample(op: c4d.modules.mograph.FieldObject, inputs: c4d.modules.mograph.FieldInput,
outputs: c4d.modules.mograph.FieldOutputBlock, info: c4d.modules.mograph.FieldInfo) -> bool:
# Read user data
direction = op[c4d.ID_USERDATA, ID_DIRECTION]
progress = op[c4d.ID_USERDATA, ID_PROGRESS]
falloff_width = op[c4d.ID_USERDATA, ID_FALLOFF]
# Accept both 0..1 and 0..100 user values
if progress is None:
progress = 0.0
progress = float(progress)
if progress > 1.0:
progress = max(0.0, min(1.0, progress / 100.0))
else:
progress = max(0.0, min(1.0, progress))
if falloff_width is None:
falloff_width = 0.2
falloff_width = float(falloff_width)
if falloff_width > 1.0:
falloff_width = max(0.001, min(1.0, falloff_width / 100.0))
else:
falloff_width = max(0.001, min(1.0, falloff_width))
if not inputs._position:
return False
mgInput = inputs._transform
world_points = [mgInput * p for p in inputs._position]
# Compute world-space bounding box
min_pos = c4d.Vector(
min(p.x for p in world_points),
min(p.y for p in world_points),
min(p.z for p in world_points)
)
max_pos = c4d.Vector(
max(p.x for p in world_points),
max(p.y for p in world_points),
max(p.z for p in world_points)
)
# Direction entries: (axis_letter, sweep_start, sweep_end)
directions = [
("x", min_pos.x, max_pos.x), # +X
("x", max_pos.x, min_pos.x), # -X
("y", min_pos.y, max_pos.y), # +Y
("y", max_pos.y, min_pos.y), # -Y
("z", min_pos.z, max_pos.z), # +Z
("z", max_pos.z, min_pos.z), # -Z
]
if direction is None or direction < 0 or direction >= len(directions):
direction = 0
axis_letter, sweep_start, sweep_end = directions[direction]
# Sweep length and falloff in world units
total_sweep = abs(sweep_end - sweep_start)
# If sweep is zero-sized (degenerate), avoid zero division
if total_sweep <= 1e-6:
total_sweep = 1e-6
falloff_distance = total_sweep * falloff_width
# Make plane range symmetrical around the sweep so falloff behaves the same in all directions
plane_start = sweep_start - (falloff_distance * 0.5)
plane_end = sweep_end + (falloff_distance * 0.5)
# plane moves from plane_start -> plane_end as progress goes 0..1
plane_pos_1d = plane_start + (plane_end - plane_start) * progress
# Build a vector plane position (only the chosen axis matters)
plane_pos = c4d.Vector()
if axis_letter == "x":
plane_pos.x = plane_pos_1d
elif axis_letter == "y":
plane_pos.y = plane_pos_1d
else:
plane_pos.z = plane_pos_1d
half_f = falloff_distance * 0.5
# Evaluate each sample point using the scalar coordinate on chosen axis.
values = []
for wp in world_points:
coord = getattr(wp, axis_letter)
# Distance relative to plane (positive = point is ahead in axis direction)
dist = coord - plane_pos_1d
# Regions:
# dist <= -half_f -> fully affected (1.0)
# dist >= +half_f -> not affected (0.0)
# in-between -> smoothstep from 1 -> 0
if dist <= -half_f:
val = 1.0
elif dist >= half_f:
val = 0.0
else:
# Map dist from [-half_f, +half_f] to t in [0,1]
t = (dist / falloff_distance) + 0.5
t = max(0.0, min(1.0, t))
# Smoothstep and invert (we want 1 at t=0, 0 at t=1)
smooth = t * t * (3.0 - 2.0 * t)
val = 1.0 - smooth
values.append(val)
outputs._value = values
outputs.ClearDeactivated(False)
return True