If you work with fluid simulations, 3D scans, or stop-motion animation, you’ve likely run into the dreaded OBJ Sequence. Unlike Alembic files, Cinema 4D doesn’t have a native “Import Sequence” button for OBJs.
For years, artists have relied on simple Python tags to toggle visibility frame-by-frame. But there’s a problem: Cinema 4D 2025 and 2026 work differently.
Old scripts force the viewport to recalculate (refresh the “Dirty State”) on every single frame, even if nothing changes. This causes massive lag, freezing your viewport even with simple geometry.
Today, we are sharing an updated, optimized toolset that solves this. It includes an Auto-Importer to load your files and a Smart Controller Tag with speed control and zero viewport lag.

Why Use This Script?
- C4D 2025/2026 Optimized: It uses a “Check-Before-Write” method. It only talks to Cinema 4D when visibility actually changes, keeping your playback smooth.
- Speed Control: Unlike older scripts, you aren’t locked to 1 frame = 1 object. You can slow it down (0.5x) or speed it up (2.0x) using a slider.
- Full Control: Includes Reverse, Hold First/Last Frame, and Manual scrubbing.
Part 1: The Importer
Get your files into Cinema 4D instantly.
Dragging 500 OBJ files into the viewport is a nightmare. This script lets you pick a folder, and it automatically imports, centers, and sorts every file into a container Null.
How to use:
- Go to Extensions > Script Manager.
- Paste the code below and hit Execute.
- Select the folder containing your .obj sequence.
# OBJ Sequence Importer for Cinema 4D
# Sorts files naturally and places them under a container Null
import c4d
import os
def natural_sort_key(s):
import re
return [int(text) if text.isdigit() else text.lower()
for text in re.split('([0-9]+)', s)]
def main():
folder_path = c4d.storage.LoadDialog(type=c4d.FILESELECTTYPE_ANYTHING, title="Select Sequence Folder", flags=c4d.FILESELECT_DIRECTORY)
if not folder_path: return
files = [f for f in os.listdir(folder_path) if f.lower().endswith(".obj")]
if not files: return
files.sort(key=natural_sort_key)
doc = c4d.documents.GetActiveDocument()
doc.StartUndo()
container = c4d.BaseObject(c4d.Onull)
container.SetName(f"SEQ_{os.path.basename(os.path.normpath(folder_path))}")
doc.InsertObject(container)
doc.AddUndo(c4d.UNDOTYPE_NEW, container)
c4d.StopAllThreads()
c4d.StatusSetBar(0)
for i, filename in enumerate(files):
c4d.documents.MergeDocument(doc, os.path.join(folder_path, filename), c4d.SCENEFILTER_OBJECTS)
imported_obj = doc.GetActiveObject()
if imported_obj:
imported_obj.SetName(filename)
imported_obj.Remove()
imported_obj.InsertUnderLast(container)
imported_obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 1 # Start hidden
imported_obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = 1
c4d.StatusSetBar(int(100.0 * (i / float(len(files)))))
c4d.StatusClear()
doc.EndUndo()
c4d.EventAdd()
if __name__=='__main__':
main()Part 2: The Player Tag
The engine that drives the animation.
Once your objects are in a Null, add a Python Tag to that Null.
Step 1: Setup User Data
For the script to work, you need to create the interface. Click on your Python Tag, go to User Data > Manage User Data, and add the following exactly:
| Name | ID | Data Type | Interface |
| Reverse | 2 | Bool | Bool |
| Start Frame | 3 | Integer | Integer |
| Hold First | 4 | Bool | Bool |
| Hold Last | 5 | Bool | Bool |
| Speed | 6 | Float | Float Slider (Min 0, Max 10, Step 0.1) |
| Manual | 7 | Bool | Bool |
| Frame | 8 | Integer | Integer |
> Note: The “ID” is crucial. If the IDs don’t match the numbers above, the script won’t find your controls.
Step 2: The Code
Paste this into the Python Tag code editor.
# Optimized OBJ Sequence Player for C4D 2025/2026
# Based on original code by Arttu Rautio (aturtur)
import c4d
def SetVisibility(obj, value):
# Only write to object if value is different
# This saves performance in C4D 2025+
if obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] != value:
obj[c4d.ID_BASEOBJECT_VISIBILITY_RENDER] = value
if obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] != value:
obj[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = value
def main():
obj = op.GetObject()
if not obj: return
fps = doc.GetFps()
frame = doc.GetTime().GetFrame(fps)
children = obj.GetChildren()
if not children: return
try:
# Read User Data
rev = op[c4d.ID_USERDATA, 2]
start = op[c4d.ID_USERDATA, 3]
holdf = op[c4d.ID_USERDATA, 4]
holdl = op[c4d.ID_USERDATA, 5]
# Speed safe-check
try: speed = op[c4d.ID_USERDATA, 6]
except: speed = 1.0
if speed is None: speed = 1.0
manon = op[c4d.ID_USERDATA, 7]
manfr = op[c4d.ID_USERDATA, 8]
except:
return
child_count = len(children)
# Logic
if manon:
# Manual Mode
for i, child in enumerate(children):
if i == int(manfr): SetVisibility(child, 2)
else: SetVisibility(child, 1)
else:
# Auto Mode with Speed Calculation
offset = frame - start
calc_index = int(offset * speed)
target_index = (child_count - 1) - calc_index if rev else calc_index
is_running = 0 <= target_index < child_count
for i, child in enumerate(children):
should_show = False
if is_running:
if i == target_index: should_show = True
elif holdf: # Hold Start
check = children[-1] if rev else children[0]
if (rev and calc_index < 0) or (not rev and calc_index < 0):
if child == check: should_show = True
elif holdl: # Hold End
check = children[0] if rev else children[-1]
if (rev and target_index < 0) or (not rev and target_index >= child_count):
if child == check: should_show = True
SetVisibility(child, 2 if should_show else 1)This setup provides a robust, production-ready way to handle OBJ sequences in the latest versions of Cinema 4D. By optimizing the code to respect the evaluation cache, you can now scrub through high-poly sequences without the viewport stalling.