Last active
May 28, 2025 21:20
-
-
Save Cdaprod/8e5b985509ed4e226a395a6a61e780f8 to your computer and use it in GitHub Desktop.
Blender BPY — Terminal 3D Layered Scene Setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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