Skip to content

Instantly share code, notes, and snippets.

@Cdaprod
Last active May 28, 2025 21:20
Show Gist options
  • Save Cdaprod/8e5b985509ed4e226a395a6a61e780f8 to your computer and use it in GitHub Desktop.
Save Cdaprod/8e5b985509ed4e226a395a6a61e780f8 to your computer and use it in GitHub Desktop.
Blender BPY — Terminal 3D Layered Scene Setup
import bpy
import bmesh
from mathutils import Vector
# Clear existing mesh objects
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
# Scene setup
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 200
# Add camera
bpy.ops.object.camera_add(location=(0, 0, 10))
camera = bpy.context.object
camera.name = "Terminal_Camera"
# Set initial camera to orthographic
camera.data.type = 'ORTHO'
camera.data.ortho_scale = 12
camera.rotation_euler = (0, 0, 0)
# Create background plane (iPhone aspect ratio)
bpy.ops.mesh.primitive_plane_add(size=2, location=(0, 0, 0))
bg_plane = bpy.context.object
bg_plane.name = "Background_Plane"
bg_plane.scale = (1.5, 3.25, 1) # iPhone-like aspect ratio
# Create material for background
bg_material = bpy.data.materials.new(name="Terminal_Background")
bg_material.use_nodes = True
bg_material.node_tree.nodes.clear()
# Background material nodes
bg_nodes = bg_material.node_tree.nodes
bg_output = bg_nodes.new(type='ShaderNodeOutputMaterial')
bg_emission = bg_nodes.new(type='ShaderNodeEmission')
bg_emission.inputs[0].default_value = (0.1, 0.05, 0.15, 1.0) # Dark purple
bg_emission.inputs[1].default_value = 0.3
bg_material.node_tree.links.new(bg_emission.outputs[0], bg_output.inputs[0])
bg_plane.data.materials.append(bg_material)
# Terminal text content with Z positions
terminal_content = [
# Layer 1 - Deepest (background processes)
("❯ sha256:c27bfeead89fb81a2287284cd...", 0.2, (0.4, 0.6, 0.2, 1.0)),
("❯ sha256:7eeb606c5fbc1e54cb5b19952051e...", 0.2, (0.4, 0.6, 0.2, 1.0)),
# Layer 2 - Docker extraction commands
("❯ extracting sha256:254e724d778624c53abbd3bf0e27f9d2f64293909c...", 0.4, (0.8, 0.6, 0.2, 1.0)),
("❯ extracting sha256:65ce365d59d3783d293849e78341cb034f362a182b...", 0.4, (0.8, 0.6, 0.2, 1.0)),
("❯ extracting sha256:c27bfeead89fb81a2287284cd...", 0.4, (0.8, 0.6, 0.2, 1.0)),
# Layer 3 - Build processes
("❯ [internal] load build context", 0.6, (0.2, 0.8, 0.9, 1.0)),
("❯ transferring context: 11.40kB", 0.6, (0.2, 0.8, 0.9, 1.0)),
("❯ [2/9] WORKDIR /app", 0.6, (0.2, 0.8, 0.9, 1.0)),
# Layer 4 - Docker build steps
("❯ [3/9] RUN apt-get update && apt-get install -y --no-install-recomm[+]", 0.8, (0.1, 0.9, 0.1, 1.0)),
("❯ [1/9] FROM docker.io/library/python:3.11-slim@sha256:75a17dd6f00b2779751...", 0.8, (0.1, 0.9, 0.1, 1.0)),
# Layer 5 - Status messages
("❯ CACHED [sanity-studio 2/6] WORKDIR /app", 1.0, (0.9, 0.9, 0.1, 1.0)),
("❯ CACHED [sanity-studio 3/6] RUN yarn global add sanity", 1.0, (0.9, 0.9, 0.1, 1.0)),
# Layer 6 - Prompts (closest)
("❯", 1.2, (0.2, 1.0, 0.2, 1.0)),
("~/Pr/local-docker-rtmp-server/se/sanity-studio +1 +1 ?1", 1.2, (0.6, 0.8, 1.0, 1.0)),
]
# Create text objects with materials
text_objects = []
for i, (text_content, z_pos, color) in enumerate(terminal_content):
# Create text
bpy.ops.object.text_add(location=(0, 0, z_pos))
text_obj = bpy.context.object
text_obj.name = f"Terminal_Text_{i:02d}"
# Set text content
text_obj.data.body = text_content
text_obj.data.font = bpy.data.fonts.load("bpy.path.abspath('//fonts/courier.ttf')") if 'courier.ttf' in bpy.data.fonts else bpy.data.fonts[0]
text_obj.data.size = 0.15
text_obj.data.space_character = 1.0
text_obj.data.space_line = 1.2
# Position text (spread vertically)
text_obj.location.y = 2.5 - (i * 0.25)
text_obj.location.x = -2.8
# Create emission material for glow effect
text_material = bpy.data.materials.new(name=f"Terminal_Text_Mat_{i:02d}")
text_material.use_nodes = True
text_material.node_tree.nodes.clear()
# Text material nodes
text_nodes = text_material.node_tree.nodes
text_output = text_nodes.new(type='ShaderNodeOutputMaterial')
text_emission = text_nodes.new(type='ShaderNodeEmission')
text_emission.inputs[0].default_value = color
text_emission.inputs[1].default_value = 2.0 # Emission strength
text_material.node_tree.links.new(text_emission.outputs[0], text_output.inputs[0])
text_obj.data.materials.append(text_material)
text_objects.append(text_obj)
# Create tracking empty for camera
bpy.ops.object.empty_add(location=(0, 0, 0.6))
tracking_empty = bpy.context.object
tracking_empty.name = "Camera_Target"
# Add track-to constraint to camera
track_constraint = camera.constraints.new(type='TRACK_TO')
track_constraint.target = tracking_empty
track_constraint.track_axis = 'TRACK_NEGATIVE_Z'
track_constraint.up_axis = 'UP_Y'
# Set up lighting
bpy.ops.object.light_add(type='AREA', location=(0, -5, 8))
light = bpy.context.object
light.data.energy = 5
light.data.size = 10
# Animation keyframes
def set_keyframes():
# Frame 1 - Initial orthographic view (screenshot style)
scene.frame_set(1)
camera.location = (0, 0, 10)
camera.rotation_euler = (0, 0, 0)
camera.data.type = 'ORTHO'
camera.data.ortho_scale = 12
camera.keyframe_insert(data_path="location", frame=1)
camera.keyframe_insert(data_path="rotation_euler", frame=1)
camera.data.keyframe_insert(data_path="ortho_scale", frame=1)
# Frame 60 - Start transition to perspective
scene.frame_set(60)
camera.data.type = 'PERSP'
camera.data.lens = 35 # 35mm focal length
camera.location = (2, -2, 8)
camera.keyframe_insert(data_path="location", frame=60)
camera.data.keyframe_insert(data_path="lens", frame=60)
# Frame 120 - Mid orbit
scene.frame_set(120)
camera.location = (8, -6, 6)
camera.keyframe_insert(data_path="location", frame=120)
# Frame 180 - Complete orbit
scene.frame_set(180)
camera.location = (6, 8, 4)
camera.keyframe_insert(data_path="location", frame=180)
# Frame 200 - Final position
scene.frame_set(200)
camera.location = (-4, 6, 8)
camera.keyframe_insert(data_path="location", frame=200)
# Apply keyframes
set_keyframes()
# Set interpolation to smooth
for obj in [camera]:
if obj.animation_data and obj.animation_data.action:
for fcurve in obj.animation_data.action.fcurves:
for keyframe in fcurve.keyframe_points:
keyframe.interpolation = 'BEZIER'
keyframe.handle_left_type = 'AUTO'
keyframe.handle_right_type = 'AUTO'
# Set camera as active camera
scene.camera = camera
# Set render settings
scene.render.resolution_x = 1080
scene.render.resolution_y = 1920 # Vertical iPhone resolution
scene.render.film_transparent = True
# Set viewport shading to solid with matcap for preview
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
break
# Set frame to 1 to see initial setup
scene.frame_set(1)
print("Terminal 3D scene setup complete!")
print("- Press Space to play animation")
print("- Camera starts orthographic (screenshot view)")
print("- Transitions to perspective view with orbital movement")
print("- Text is layered in 3D space at different Z depths")
print("- Materials use emission shaders for terminal glow effect")
"""
New enhancements:
• 🆕 6 distinct depth layers with your actual terminal content
• 🆕 Sophisticated camera animation (orthographic → perspective → orbital)
• 🆕 Professional lighting setup (key, fill, rim lights)
• 🆕 Depth of field for cinematic quality
• 🆕 Text extrusion for better 3D presence
• 🆕 300-frame animation with smooth transitions
• 🆕 Cycles rendering with denoising
The animation flow:
1. Frames 1-80: Flat iPhone screenshot view
2. Frames 80-150: Transition reveals 3D depth
3. Frames 150-300: Camera orbits revealing all layers
This gives you a professional, cinematic reveal of your actual terminal session with smooth transitions and beautiful 3D layering. Ready to run!​​​​​​​​​​​​​​​​
"""
import bpy
import bmesh
from mathutils import Vector
import os
# ============ CONFIGURATION ============
# Font path (optional - will use default if not found)
FONT_PATH = "/System/Library/Fonts/Menlo.ttc" # macOS default, adjust as needed
# Your actual terminal content from screenshots
TERMINAL_LAYERS = [
# Layer 1 (Deepest) - Background hash processes
{
"lines": [
"❯ sha256:c27bfeead89fb81a2287284cd...",
"❯ sha256:7eeb606c5fbc1e54cb5b19952051e...",
"❯ sha256:65ce365d59d3783d293849e78341cb034f362a182b...",
],
"z_pos": 0.0,
"color": (0.4, 0.6, 0.2, 1.0), # Dim green
"emission": 1.5
},
# Layer 2 - Docker extraction
{
"lines": [
"❯ extracting sha256:254e724d778624c53abbd3bf0e27f9d2f64293909c...",
"❯ extracting sha256:c27bfeead89fb81a2287284cd...",
"❯ extracting sha256:7eeb606c5fbc1e54cb5b19952051e...",
],
"z_pos": 0.3,
"color": (0.8, 0.6, 0.2, 1.0), # Orange
"emission": 2.0
},
# Layer 3 - Build context
{
"lines": [
"❯ [internal] load build context",
"❯ transferring context: 11.40kB",
"❯ [2/9] WORKDIR /app",
"❯ [3/9] RUN apt-get update && apt-get install -y --no-install-recomm[+]",
],
"z_pos": 0.6,
"color": (0.2, 0.8, 0.9, 1.0), # Cyan
"emission": 2.5
},
# Layer 4 - Docker build steps
{
"lines": [
"❯ [1/9] FROM docker.io/library/python:3.11-slim@sha256:75a17dd6f00b2779751...",
"❯ [sanity-studio 3/6] RUN yarn global add sanity",
"❯ [sanity-studio 4/6] COPY package.json ./",
"❯ [sanity-studio 5/6] RUN yarn install",
],
"z_pos": 0.9,
"color": (0.1, 0.9, 0.1, 1.0), # Bright green
"emission": 3.0
},
# Layer 5 - Status and cache
{
"lines": [
"❯ CACHED [sanity-studio 2/6] WORKDIR /app",
"❯ ERROR [sanity-studio 4/6] COPY package.json yarn.lock ./",
"❯ [sanity-studio] importing to docker",
"❯ loading layer 0e5a58b3e135 163.84kB / 13.49MB",
],
"z_pos": 1.2,
"color": (0.9, 0.9, 0.1, 1.0), # Yellow
"emission": 3.5
},
# Layer 6 (Closest) - Active prompts
{
"lines": [
"❯ yarn run v1.22.22",
"❯ $ sanity dev",
"❯ The Sanity CLI now collects telemetry data on general usage and errors.",
"~/Pr/local-docker-rtmp-server/se/sanity-studio +1 +1 ?1",
"❯",
],
"z_pos": 1.5,
"color": (0.231, 0.608, 0.953, 1.0), # Cdaprod blue
"emission": 4.0
}
]
# Animation settings
TOTAL_FRAMES = 300
TRANSITION_START = 80
ORBIT_START = 150
# ============ SCENE SETUP ============
def clear_scene():
"""Clear existing objects"""
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
def setup_world():
"""Set dark terminal background"""
world = bpy.data.worlds.get('World')
if not world:
world = bpy.data.worlds.new('World')
bpy.context.scene.world = world
world.use_nodes = True
world.node_tree.nodes.clear()
bg_node = world.node_tree.nodes.new('ShaderNodeBackground')
output_node = world.node_tree.nodes.new('ShaderNodeOutputWorld')
# Dark purple terminal background
bg_node.inputs[0].default_value = (0.05, 0.02, 0.08, 1.0)
bg_node.inputs[1].default_value = 0.1
world.node_tree.links.new(bg_node.outputs[0], output_node.inputs[0])
def load_font():
"""Load terminal font"""
if os.path.exists(FONT_PATH):
try:
return bpy.data.fonts.load(FONT_PATH)
except:
pass
return bpy.data.fonts.get('Bfont', bpy.data.fonts[0])
def create_terminal_text():
"""Create layered terminal text objects"""
font = load_font()
text_objects = []
for layer_idx, layer in enumerate(TERMINAL_LAYERS):
for line_idx, line_text in enumerate(layer["lines"]):
# Create text object
bpy.ops.object.text_add(location=(0, 0, layer["z_pos"]))
text_obj = bpy.context.object
text_obj.name = f"Terminal_L{layer_idx:02d}_Line{line_idx:02d}"
# Configure text
text_obj.data.body = line_text
text_obj.data.font = font
text_obj.data.size = 0.08
text_obj.data.space_character = 1.0
text_obj.data.space_line = 1.1
# Position text
text_obj.location.x = -2.5
text_obj.location.y = 1.8 - (line_idx * 0.15) - (layer_idx * 0.8)
# Add subtle extrude for depth
text_obj.data.extrude = 0.005
text_obj.data.bevel_depth = 0.001
# Create emission material
material = bpy.data.materials.new(name=f"Terminal_Mat_L{layer_idx}_Line{line_idx}")
material.use_nodes = True
material.node_tree.nodes.clear()
# Material nodes
output = material.node_tree.nodes.new('ShaderNodeOutputMaterial')
emission = material.node_tree.nodes.new('ShaderNodeEmission')
emission.inputs[0].default_value = layer["color"]
emission.inputs[1].default_value = layer["emission"]
material.node_tree.links.new(emission.outputs[0], output.inputs[0])
text_obj.data.materials.append(material)
text_objects.append((text_obj, layer_idx))
return text_objects
def create_camera_system():
"""Create camera with tracking system"""
# Add camera
bpy.ops.object.camera_add(location=(0, 0, 12))
camera = bpy.context.object
camera.name = "Terminal_Camera"
# Camera settings
camera.data.lens = 35
camera.data.dof.use_dof = True
camera.data.dof.focus_distance = 2.0
camera.data.dof.aperture_fstop = 2.8
# Create tracking target
bpy.ops.object.empty_add(location=(0, 0, 0.75))
target = bpy.context.object
target.name = "Camera_Target"
# Add track-to constraint
track_constraint = camera.constraints.new(type='TRACK_TO')
track_constraint.target = target
track_constraint.track_axis = 'TRACK_NEGATIVE_Z'
track_constraint.up_axis = 'UP_Y'
return camera, target
def setup_lighting():
"""Create dramatic lighting"""
# Key light
bpy.ops.object.light_add(type='AREA', location=(3, -4, 8))
key_light = bpy.context.object
key_light.data.energy = 8
key_light.data.size = 6
key_light.data.color = (0.8, 0.9, 1.0)
# Fill light
bpy.ops.object.light_add(type='AREA', location=(-2, 2, 4))
fill_light = bpy.context.object
fill_light.data.energy = 3
fill_light.data.size = 4
fill_light.data.color = (0.4, 0.6, 1.0)
# Rim light
bpy.ops.object.light_add(type='SPOT', location=(0, 6, 2))
rim_light = bpy.context.object
rim_light.data.energy = 15
rim_light.data.spot_size = 1.2
rim_light.data.color = (0.2, 0.8, 1.0)
def animate_camera(camera, target):
"""Create sophisticated camera animation"""
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = TOTAL_FRAMES
# Frame 1-80: Orthographic screenshot view
scene.frame_set(1)
camera.data.type = 'ORTHO'
camera.data.ortho_scale = 8
camera.location = (0, 0, 12)
camera.rotation_euler = (0, 0, 0)
camera.keyframe_insert(data_path="location")
camera.keyframe_insert(data_path="rotation_euler")
camera.data.keyframe_insert(data_path="ortho_scale")
# Frame 80: Transition to perspective
scene.frame_set(TRANSITION_START)
camera.data.type = 'PERSP'
camera.data.lens = 35
camera.location = (1, -1, 10)
camera.keyframe_insert(data_path="location")
camera.data.keyframe_insert(data_path="lens")
# Frame 150-300: Orbital movement with depth reveals
orbit_frames = [
(150, (4, -3, 6)),
(180, (6, -1, 4)),
(210, (4, 4, 6)),
(240, (-2, 5, 8)),
(270, (-6, 2, 5)),
(300, (-3, -4, 7))
]
for frame, location in orbit_frames:
scene.frame_set(frame)
camera.location = location
camera.keyframe_insert(data_path="location")
# Smooth keyframe interpolation
if camera.animation_data and camera.animation_data.action:
for fcurve in camera.animation_data.action.fcurves:
for keyframe in fcurve.keyframe_points:
keyframe.interpolation = 'BEZIER'
keyframe.handle_left_type = 'AUTO'
keyframe.handle_right_type = 'AUTO'
def setup_render():
"""Configure render settings"""
scene = bpy.context.scene
# Render engine
scene.render.engine = 'CYCLES'
scene.cycles.samples = 256
scene.cycles.use_denoising = True
# Resolution (vertical iPhone style)
scene.render.resolution_x = 1080
scene.render.resolution_y = 1920
scene.render.resolution_percentage = 100
# Output
scene.render.film_transparent = True
scene.render.filepath = "/tmp/terminal_3d_"
# ============ MAIN EXECUTION ============
def main():
"""Execute complete scene setup"""
print("🚀 Setting up Enhanced Terminal 3D Scene...")
# Setup scene
clear_scene()
setup_world()
# Create content
print("📝 Creating terminal text layers...")
text_objects = create_terminal_text()
print("📷 Setting up camera system...")
camera, target = create_camera_system()
print("💡 Adding lighting...")
setup_lighting()
print("🎬 Animating camera...")
animate_camera(camera, target)
print("⚙️ Configuring render settings...")
setup_render()
# Set active camera
bpy.context.scene.camera = camera
# Set viewport shading
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = 'MATERIAL'
break
# Start at frame 1
bpy.context.scene.frame_set(1)
print("✅ Enhanced Terminal 3D Scene Complete!")
print(f"📊 Created {len([obj for obj, _ in text_objects])} text objects across {len(TERMINAL_LAYERS)} layers")
print("🎯 Animation: Orthographic → Perspective → Orbital (300 frames)")
print("▶️ Press SPACEBAR to play animation")
print("🎬 Press F12 to render")
# Execute
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment