Seamless looping 360-degree turntable. First and last frames match perfectly for infinite loop playback.
Scanned 9/22/2026
npx -y skills add MAX-786/claude-3d-harness --skill perfect-loop --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Perfect Loop?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/max-786-perfect-loop)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: perfect-loop
description: Seamless looping 360-degree turntable. First and last frames match perfectly for infinite loop playback.
---
## Perfect Loop Camera Animation
A seamless 360-degree turntable where the last frame connects perfectly to the first frame, creating an infinite loop. Ideal for website backgrounds, social media, and product page embeds.
## When to Use
- User wants a "looping" or "seamless" animation
- User says "perfect loop", "infinite loop", "GIF", "looping turntable"
- Website product embed, social media loop
## Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `duration` | `5` | Video length in seconds |
| `fps` | `24` | Frames per second |
| `camera_distance` | `4.0` | Distance from object |
| `camera_height` | `2.0` | Camera height |
| `camera_lens` | `50` | Focal length |
## Flow
### Step 1: Set Up Camera Rig
```python
import bpy, math
bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0, 0, 0))
tgt = bpy.context.active_object
tgt.name = 'PerfectLoopTarget'
bpy.ops.object.camera_add(location=(0, -CAMERA_DISTANCE, CAMERA_HEIGHT))
cam = bpy.context.active_object
cam.name = 'PerfectLoopCam'
cam.data.lens = CAMERA_LENS
trk = cam.constraints.new(type='TRACK_TO')
trk.target = tgt
trk.track_axis = 'TRACK_NEGATIVE_Z'
trk.up_axis = 'UP_Y'
bpy.ops.object.select_all(action='DESELECT')
cam.select_set(True)
tgt.select_set(True)
bpy.context.view_layer.objects.active = tgt
bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
bpy.context.scene.camera = cam
```
### Step 2: Animate Seamless Loop
**Critical**: Keyframe 360 degrees at frame N+1 (one past the last rendered frame). This way frame N and frame 1 are adjacent in the loop, not duplicated.
```python
TOTAL_FRAMES = DURATION * FPS # 120
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = TOTAL_FRAMES # render 1-120
scene.render.fps = FPS
tgt.rotation_euler = (0, 0, 0)
tgt.keyframe_insert(data_path='rotation_euler', frame=1)
# 360 at frame 121 — frame 121 is NOT rendered, but ensures
# frame 120 is one step before frame 1, creating seamless loop
tgt.rotation_euler = (0, 0, math.radians(360))
tgt.keyframe_insert(data_path='rotation_euler', frame=TOTAL_FRAMES + 1)
# LINEAR interpolation is CRITICAL for perfect loop
action = tgt.animation_data.action
strip = action.layers[0].strips[0]
for cb in strip.channelbags:
for fc in cb.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = 'LINEAR'
```
### Step 3: Render + Encode
```bash
ffmpeg -y -framerate 24 \
-i "~/Desktop/Blender Videos/perfect_loop_frames/%04d.png" \
-c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le \
"~/Desktop/Blender Videos/perfect_loop.mov"
```
## Critical Notes
- **LINEAR interpolation only** — BEZIER will cause the loop to stutter at the seam
- **Keyframe at N+1**: Place the 360-degree keyframe one frame PAST the render range
- **No camera movement**: Camera distance, height, and lens must be static (no keyframes on these)
- **Verify loop**: Play the video on repeat to confirm seamless transition
- Good for encoding to looping formats: GIF, WebM, or MP4 with `-loop 0`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!