hero: fix 'у' overflow, add tagline integration, remove separate banner
This commit is contained in:
1 parent
02a98b7949
commit
5396801baa
3 files changed
+1005
-11
No files matched your search
@@ -0,0 +1,460 @@
|
||||
"""
|
||||
ROI24 — Matrix 3D Tunnel Hero Animation
|
||||
========================================
|
||||
Blender 4.x Geometry Nodes script.
|
||||
Creates a Matrix-style infinite tunnel with binary digits
|
||||
and floating KPI cards.
|
||||
|
||||
Usage:
|
||||
1. Open Blender 4.x
|
||||
2. Scripting workspace → Text Editor → New → "matrix_tunnel.py"
|
||||
3. Paste this entire file
|
||||
4. Alt+P (Run Script)
|
||||
5. Press ~ in 3D viewport → shade smooth
|
||||
6. Numpad 0 for camera view
|
||||
7. Ctrl+F12 to render animation
|
||||
|
||||
Output: 1920x1080, 60fps, 180 frames (3s loop)
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import random
|
||||
import os
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
RENDER_WIDTH = 1920
|
||||
RENDER_HEIGHT = 1080
|
||||
FRAMES = 180
|
||||
FPS = 60
|
||||
TUNNEL_RADIUS = 8.0
|
||||
TUNNEL_LENGTH = 80.0
|
||||
RINGS = 32
|
||||
CHARS_PER_RING = 48
|
||||
FLOW_SPEED = 3.0 # tunnel speed
|
||||
|
||||
# ROI24 brand colors
|
||||
GREEN = (0.133, 0.773, 0.369) # #22c55e
|
||||
GREEN_BRIGHT = (0.286, 0.886, 0.502) # #4ade80
|
||||
DARK = (0.039, 0.039, 0.039) # #0a0a0a
|
||||
|
||||
# ============================================================
|
||||
# CLEANUP
|
||||
# ============================================================
|
||||
print("Cleaning scene...")
|
||||
for obj in bpy.data.objects:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
for mat in bpy.data.materials:
|
||||
bpy.data.materials.remove(mat, do_unlink=True)
|
||||
for tex in bpy.data.textures:
|
||||
bpy.data.textures.remove(tex, do_unlink=True)
|
||||
for img in bpy.data.images:
|
||||
bpy.data.images.remove(img, do_unlink=True)
|
||||
for gn in bpy.data.node_groups:
|
||||
bpy.data.node_groups.remove(gn)
|
||||
|
||||
# ============================================================
|
||||
# MATERIALS
|
||||
# ============================================================
|
||||
print("Creating materials...")
|
||||
|
||||
def create_green_emit_mat(name):
|
||||
"""Create a green emission material."""
|
||||
mat = bpy.data.materials.new(name)
|
||||
mat.use_nodes = True
|
||||
nodes = mat.node_tree.nodes
|
||||
links = mat.node_tree.links
|
||||
nodes.clear()
|
||||
|
||||
emit = nodes.new('ShaderNodeEmission')
|
||||
emit.inputs['Color'].default_value = (*GREEN, 1.0)
|
||||
emit.inputs['Strength'].default_value = 1.5
|
||||
output = nodes.new('ShaderNodeOutputMaterial')
|
||||
links.new(emit.outputs['Emission'], output.inputs['Surface'])
|
||||
return mat
|
||||
|
||||
def create_glow_mat(name, color=GREEN, strength=3.0):
|
||||
"""Create a bright glow material."""
|
||||
mat = bpy.data.materials.new(name)
|
||||
mat.use_nodes = True
|
||||
nodes = mat.node_tree.nodes
|
||||
links = mat.node_tree.links
|
||||
nodes.clear()
|
||||
|
||||
emit = nodes.new('ShaderNodeEmission')
|
||||
emit.inputs['Color'].default_value = (*color, 1.0)
|
||||
emit.inputs['Strength'].default_value = strength
|
||||
output = nodes.new('ShaderNodeOutputMaterial')
|
||||
links.new(emit.outputs['Emission'], output.inputs['Surface'])
|
||||
return mat
|
||||
|
||||
char_mat = create_green_emit_mat('CharMat')
|
||||
glow_mat = create_glow_mat('GlowMat', GREEN_BRIGHT, 4.0)
|
||||
card_mat = create_green_emit_mat('CardMat')
|
||||
line_mat = create_green_emit_mat('LineMat')
|
||||
|
||||
# ============================================================
|
||||
# CAMERA
|
||||
# ============================================================
|
||||
print("Setting up camera...")
|
||||
cam_data = bpy.data.cameras.new('Camera')
|
||||
cam_data.type = 'PERSP'
|
||||
cam_data.lens = 35
|
||||
cam_data.clip_start = 0.1
|
||||
cam_data.clip_end = 200
|
||||
|
||||
cam = bpy.data.objects.new('Camera', cam_data)
|
||||
cam.location = (0, 0, 0)
|
||||
bpy.context.scene.collection.objects.link(cam)
|
||||
bpy.context.scene.camera = cam
|
||||
|
||||
# ============================================================
|
||||
# SCENE SETUP
|
||||
# ============================================================
|
||||
print("Setting up scene...")
|
||||
scene = bpy.context.scene
|
||||
scene.render.engine = 'CYCLES'
|
||||
scene.cycles.samples = 128
|
||||
scene.cycles.preview_samples = 32
|
||||
scene.cycles.device = 'GPU'
|
||||
scene.render.fps = FPS
|
||||
scene.frame_start = 0
|
||||
scene.frame_end = FRAMES - 1
|
||||
|
||||
# Render resolution
|
||||
scene.render.resolution_x = RENDER_WIDTH
|
||||
scene.render.resolution_y = RENDER_HEIGHT
|
||||
scene.render.resolution_percentage = 100
|
||||
|
||||
# Output path (set before file_format)
|
||||
output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'renders')
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
scene.render.filepath = os.path.join(output_dir, 'matrix_tunnel')
|
||||
|
||||
# FFMPEG settings (Blender 4.x API)
|
||||
scene.ffmpeg.format = 'MPEG_4'
|
||||
scene.ffmpeg.codec = 'H264'
|
||||
scene.ffmpeg.video_bitrate = 50000
|
||||
|
||||
# World background
|
||||
world = bpy.data.worlds.new('World')
|
||||
world.use_nodes = True
|
||||
nodes = world.node_tree.nodes
|
||||
links = world.node_tree.links
|
||||
nodes.clear()
|
||||
|
||||
bg = nodes.new('ShaderNodeBackground')
|
||||
bg.inputs['Color'].default_value = (*DARK, 1.0)
|
||||
bg.inputs['Strength'].default_value = 0.3
|
||||
output = nodes['World Output']
|
||||
links.new(bg.outputs['Background'], output.inputs['Surface'])
|
||||
|
||||
# Fog
|
||||
fog = nodes.new('ShaderNodeFog')
|
||||
fog.fog_type = 'EXP'
|
||||
fog.density = 0.025
|
||||
fog.inputs['Color'].default_value = (*GREEN, 0.15)
|
||||
links.new(fog, output.inputs['Fog'])
|
||||
scene.world = world
|
||||
|
||||
# ============================================================
|
||||
# GEOMETRY NODES TUNNEL
|
||||
# ============================================================
|
||||
print("Creating tunnel with Geometry Nodes...")
|
||||
|
||||
# Create instancing system for binary characters
|
||||
tunnel_gn = bpy.data.node_groups.new('MatrixTunnel', 'GeometryNodeTree')
|
||||
tunnel_gn.interface.new_socket('Geometry', socket_type='NodeSocketGeometry')
|
||||
tunnel_gn.interface.new_socket('Progress', socket_type='NodeSocketFloat')
|
||||
|
||||
nodes = tunnel_gn.nodes
|
||||
links = tunnel_gn.links
|
||||
|
||||
# Input geometry
|
||||
input_geom = nodes.new('NodeGroupInput')
|
||||
|
||||
# Separate by Z
|
||||
separate_z = nodes.new('GeometryNodeSeparateGeometry')
|
||||
separate_z.location = (200, 0)
|
||||
|
||||
# Instance on points
|
||||
instance_on_points = nodes.new('GeometryNodeInstanceOnPoints')
|
||||
instance_on_points.location = (500, 0)
|
||||
|
||||
# Transform geometry (rotate instances to face camera)
|
||||
transform = nodes.new('GeometryNodeTransform')
|
||||
transform.location = (750, 0)
|
||||
|
||||
# Output
|
||||
output_gn = nodes.new('NodeGroupOutput')
|
||||
|
||||
# Connect
|
||||
links.new(input_geom.outputs[0], separate_z.inputs[0])
|
||||
links.new(separate_z.outputs[0], instance_on_points.inputs[0])
|
||||
links.new(instance_on_points.outputs[0], transform.inputs[0])
|
||||
links.new(transform.outputs[0], output_gn.inputs[0])
|
||||
|
||||
# ============================================================
|
||||
# CREATE BINARY CHARACTER MESH (simple diamond shape)
|
||||
# ============================================================
|
||||
print("Creating character mesh...")
|
||||
|
||||
# Diamond shape for '0'
|
||||
char_mesh_0 = bpy.data.meshes.new('Char0')
|
||||
verts_0 = [
|
||||
(0, 0.3, 0), (0.2, 0, 0), (0, -0.3, 0), (-0.2, 0, 0)
|
||||
]
|
||||
faces_0 = [[0, 1, 2, 3]]
|
||||
char_mesh_0.from_pydata(verts_0, [], faces_0)
|
||||
char_mesh_0.validate()
|
||||
|
||||
# Line shape for '1'
|
||||
char_mesh_1 = bpy.data.meshes.new('Char1')
|
||||
verts_1 = [
|
||||
(-0.08, 0.25, 0), (0.08, 0.25, 0),
|
||||
(0.08, -0.25, 0), (-0.08, -0.25, 0),
|
||||
(-0.08, -0.15, 0), (0.08, -0.15, 0)
|
||||
]
|
||||
faces_1 = [[0, 1, 2, 3], [2, 5, 4, 3]]
|
||||
char_mesh_1.from_pydata(verts_1, [], faces_1)
|
||||
char_mesh_1.validate()
|
||||
|
||||
# ============================================================
|
||||
# INSTANCED CHARACTER OBJECTS
|
||||
# ============================================================
|
||||
print("Instancing characters...")
|
||||
|
||||
def create_instanced_chars():
|
||||
"""Create instanced binary characters around tunnel."""
|
||||
angle_step = (2 * math.pi) / CHARS_PER_RING
|
||||
|
||||
# Create collection for tunnel objects
|
||||
tunnel_col = bpy.data.collections.new('MatrixTunnel')
|
||||
bpy.context.scene.collection.children.link(tunnel_col)
|
||||
|
||||
# Create a single "instancer" object with geometry nodes
|
||||
instancer = bpy.data.objects.new('TunnelInstancer', None)
|
||||
instancer.location = (0, 0, 0)
|
||||
tunnel_col.objects.link(instancer)
|
||||
|
||||
# Geometry nodes modifier
|
||||
gn = instancer.modifiers.new('TunnelGenerator', 'NODES')
|
||||
gn.node_group = tunnel_gn
|
||||
|
||||
# Now create actual character instances
|
||||
ring_spacing = TUNNEL_LENGTH / RINGS
|
||||
|
||||
for ring_idx in range(RINGS):
|
||||
z = -(ring_idx * ring_spacing)
|
||||
|
||||
for char_idx in range(CHARS_PER_RING):
|
||||
angle = char_idx * angle_step
|
||||
x = math.cos(angle) * TUNNEL_RADIUS
|
||||
y = math.sin(angle) * TUNNEL_RADIUS
|
||||
|
||||
# Create character mesh object
|
||||
use_mesh = char_mesh_0 if random.random() > 0.5 else char_mesh_1
|
||||
|
||||
# Use object instancing for performance
|
||||
char_obj = bpy.data.objects.new(
|
||||
f'Char_R{ring_idx:02d}_C{char_idx:02d}',
|
||||
use_mesh
|
||||
)
|
||||
char_obj.location = (x, y, z)
|
||||
char_obj.rotation_euler = (0, 0, angle + math.radians(90))
|
||||
|
||||
# Scale based on position (closer = bigger)
|
||||
scale = 0.6 + (ring_idx / RINGS) * 0.3
|
||||
char_obj.scale = (scale, scale, scale)
|
||||
|
||||
# Material
|
||||
char_obj.data.materials.append(char_mat)
|
||||
|
||||
# Set material color based on depth
|
||||
depth_ratio = ring_idx / RINGS
|
||||
r = GREEN[0] * (0.6 + depth_ratio * 0.4)
|
||||
g = GREEN[1] * (0.8 + depth_ratio * 0.2)
|
||||
b = GREEN[2] * (0.7 + depth_ratio * 0.3)
|
||||
char_mat.nodes['Emission'].inputs['Color'].default_value = (r, g, b, 1.0)
|
||||
|
||||
# Store animation data
|
||||
char_obj['ring_idx'] = ring_idx
|
||||
char_obj['char_idx'] = char_idx
|
||||
char_obj['base_x'] = x
|
||||
char_obj['base_y'] = y
|
||||
char_obj['base_z'] = z
|
||||
char_obj['base_scale'] = scale
|
||||
char_obj['angle'] = angle
|
||||
|
||||
tunnel_col.objects.link(char_obj)
|
||||
|
||||
return tunnel_col
|
||||
|
||||
tunnel_col = create_instanced_chars()
|
||||
|
||||
# ============================================================
|
||||
# KPI CARDS — 3D panels
|
||||
# ============================================================
|
||||
print("Creating KPI cards...")
|
||||
|
||||
KPI_DATA = [
|
||||
{'label': 'CPA', 'value': '890₽', 'pos': (-10, 5, -12), 'rot': (0, 0.1, 0), 'size': (3, 2)},
|
||||
{'label': 'ROI', 'value': '91.8%', 'pos': (10, 4, -18), 'rot': (0, -0.1, 0), 'size': (3, 2)},
|
||||
{'label': 'LTV', 'value': '12.4K', 'pos': (-9, -2, -24), 'rot': (0, 0.15, 0), 'size': (3, 2)},
|
||||
{'label': 'CONV', 'value': '2.8%', 'pos': (9, -3, -30), 'rot': (0, -0.15, 0), 'size': (3, 2)},
|
||||
{'label': 'LEADS', 'value': '44.5K', 'pos': (-8, 1, -36), 'rot': (0, 0.1, 0), 'size': (3, 2)},
|
||||
{'label': 'REVENUE', 'value': '2.05M', 'pos': (8, 0, -42), 'rot': (0, -0.1, 0), 'size': (3, 2)},
|
||||
]
|
||||
|
||||
for i, kpi in enumerate(KPI_DATA):
|
||||
w, h = kpi['size']
|
||||
|
||||
# Card mesh
|
||||
card_mesh = bpy.data.meshes.new(f'KPI_Card_{i}')
|
||||
verts = [
|
||||
(-w/2, -h/2, 0), (w/2, -h/2, 0),
|
||||
(w/2, h/2, 0), (-w/2, h/2, 0),
|
||||
(-w/2, -h/2, -0.05), (w/2, -h/2, -0.05),
|
||||
(w/2, h/2, -0.05), (-w/2, h/2, -0.05),
|
||||
]
|
||||
faces = [
|
||||
[0, 4, 5, 1], # bottom
|
||||
[4, 7, 6, 5], # back
|
||||
[7, 3, 2, 6], # top
|
||||
[3, 0, 1, 2], # front
|
||||
[4, 0, 3, 7], # left
|
||||
[1, 5, 6, 2], # right
|
||||
]
|
||||
card_mesh.from_pydata(verts, [], faces)
|
||||
card_mesh.validate()
|
||||
|
||||
card_obj = bpy.data.objects.new(f'KPI_Card_{i}', card_mesh)
|
||||
card_obj.location = kpi['pos']
|
||||
card_obj.rotation_euler = kpi['rot']
|
||||
card_obj.scale = (1, 1, 1)
|
||||
|
||||
# Material
|
||||
card_obj.data.materials.append(card_mat)
|
||||
|
||||
# Store data
|
||||
card_obj['kpi_index'] = i
|
||||
card_obj['base_x'] = kpi['pos'][0]
|
||||
card_obj['base_y'] = kpi['pos'][1]
|
||||
card_obj['base_z'] = kpi['pos'][2]
|
||||
|
||||
tunnel_col.objects.link(card_obj)
|
||||
|
||||
# ============================================================
|
||||
# SPEED LINES
|
||||
# ============================================================
|
||||
print("Creating speed lines...")
|
||||
|
||||
for i in range(48):
|
||||
angle = (i / 48) * 2 * math.pi
|
||||
x1 = math.cos(angle) * 0.5
|
||||
y1 = math.sin(angle) * 0.5
|
||||
x2 = math.cos(angle) * TUNNEL_RADIUS * 2
|
||||
y2 = math.sin(angle) * TUNNEL_RADIUS * 2
|
||||
z_start = -10
|
||||
z_end = -TUNNEL_LENGTH
|
||||
|
||||
# Curve
|
||||
curve = bpy.data.curves.new(f'SpeedLine_{i}', 'CURVE')
|
||||
curve.dimensions = '3D'
|
||||
curve.bevel_depth = 0.03
|
||||
spline = curve.splines.new('POLY')
|
||||
spline.points.add(1)
|
||||
spline.points[0].co = (x1, y1, z_start, 1)
|
||||
spline.points[1].co = (x2, y2, z_end, 1)
|
||||
|
||||
line_obj = bpy.data.objects.new(f'SpeedLine_{i}', curve)
|
||||
line_obj.data.materials.append(line_mat)
|
||||
tunnel_col.objects.link(line_obj)
|
||||
|
||||
# ============================================================
|
||||
# ANIMATION FUNCTIONS (uncomment to enable)
|
||||
# ============================================================
|
||||
|
||||
def animate_characters():
|
||||
"""Animate binary characters flowing toward camera."""
|
||||
for obj in tunnel_col.objects:
|
||||
ring_idx = obj.get('ring_idx', None)
|
||||
if ring_idx is None:
|
||||
continue
|
||||
|
||||
base_z = obj.get('base_z', 0)
|
||||
base_x = obj.get('base_x', 0)
|
||||
base_y = obj.get('base_y', 0)
|
||||
base_scale = obj.get('base_scale', 0.6)
|
||||
angle = obj.get('angle', 0)
|
||||
|
||||
for frame in range(FRAMES):
|
||||
# Flow forward
|
||||
z = base_z + ((frame / FPS) * FLOW_SPEED) % TUNNEL_LENGTH
|
||||
if z > 0:
|
||||
z -= TUNNEL_LENGTH
|
||||
|
||||
# Scale by depth
|
||||
depth_ratio = max(0, min(1, (TUNNEL_LENGTH + z) / TUNNEL_LENGTH))
|
||||
scale = base_scale * (0.4 + depth_ratio * 0.7)
|
||||
|
||||
# Set keyframe
|
||||
obj.location = (base_x, base_y, z)
|
||||
obj.scale = (scale / base_scale, scale / base_scale, 1)
|
||||
obj.keyframe_insert(data_path='location', frame=frame)
|
||||
obj.keyframe_insert(data_path='scale', frame=frame)
|
||||
|
||||
|
||||
def animate_kpi_cards():
|
||||
"""Animate KPI cards floating through tunnel."""
|
||||
for obj in tunnel_col.objects:
|
||||
kpi_idx = obj.get('kpi_index', None)
|
||||
if kpi_idx is None:
|
||||
continue
|
||||
|
||||
base_x = obj.get('base_x', 0)
|
||||
base_y = obj.get('base_y', 0)
|
||||
base_z = obj.get('base_z', 0)
|
||||
|
||||
for frame in range(FRAMES):
|
||||
# Move toward camera
|
||||
z = base_z + ((frame / FPS) * FLOW_SPEED * 0.7)
|
||||
|
||||
# Float motion
|
||||
y = base_y + math.sin((frame / FPS) * 2 + kpi_idx * 1.5) * 1.5
|
||||
|
||||
# Scale
|
||||
depth_ratio = max(0, min(1, (TUNNEL_LENGTH + z) / TUNNEL_LENGTH))
|
||||
scale = 0.5 + depth_ratio * 0.7
|
||||
|
||||
obj.location = (base_x, y, z)
|
||||
obj.scale = (scale, scale, scale)
|
||||
obj.keyframe_insert(data_path='location', frame=frame)
|
||||
obj.keyframe_insert(data_path='scale', frame=frame)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SUMMARY
|
||||
# ============================================================
|
||||
print("\n" + "=" * 60)
|
||||
print("ROI24 Matrix 3D Tunnel — Setup Complete")
|
||||
print("=" * 60)
|
||||
print(f"Resolution: {RENDER_WIDTH}x{RENDER_HEIGHT}")
|
||||
print(f"Duration: {FRAMES / FPS}s @ {FPS}fps")
|
||||
print(f"Engine: {scene.render.engine} (Cycles)")
|
||||
print(f"Tunnel: {RINGS} rings x {CHARS_PER_RING} chars = {RINGS * CHARS_PER_RING}")
|
||||
print(f"KPI cards: {len(KPI_DATA)}")
|
||||
print(f"Speed lines: 48")
|
||||
print()
|
||||
print("NEXT STEPS:")
|
||||
print("1. In 3D Viewport: Press ~ → Shade Smooth")
|
||||
print("2. Numpad 0 — Camera view")
|
||||
print("3. F12 — Preview render")
|
||||
print("4. Ctrl+F12 — Render animation (MP4)")
|
||||
print()
|
||||
print("TO ANIMATE:")
|
||||
print("Uncomment animate_characters() and animate_kpi_cards()")
|
||||
print("then re-run this script")
|
||||
print("=" * 60)
|
||||
Reference in new issue
Block a user