=== WHAT'S BEEN DONE (Recent) === MCP Pipeline Factory: - 38 MCP servers tracked across 7 pipeline stages - 31 servers at Stage 16 (Website Built) — ready to deploy - All 30 production servers patched to 100/100 protocol compliance - Built complete testing infra: mcp-jest, mcp-validator, mcp-add, MCP Inspector - 702 auto-generated test cases ready for live API testing - Autonomous pipeline operator system w/ 7 Discord channels + cron jobs - Dashboard live at 192.168.0.25:8888 (drag-drop kanban) CloseBot MCP: - 119 tools, 4,656 lines TypeScript, compiles clean - 14 modules (8 tool groups + 6 UI apps) GHL MCP: - Stage 11 (Edge Case Testing) — 42 failing tests identified Sub-agent _meta Labels: - All 643 tools across 5 MCPs tagged (GHL, Google Ads, Meta Ads, Google Console, Twilio) OpenClaw Upwork Launch: - 15 graphics, 6 mockups, 2 PDFs, 90-sec Remotion video - 3-tier pricing: $2,499 / $7,499 / $24,999 - First $20k deal closed + $2k/mo retainer (hospice) Other: - Surya Blender animation scripts (7 tracks) - Clawdbot architecture deep dive doc - Pipeline state.json updates === TO-DO (Open Items) === BLOCKERS: - [ ] GHL MCP: Fix 42 failing edge case tests (Stage 11) - [ ] Expired Anthropic API key in localbosses-app .env.local - [ ] Testing strategy decision: structural vs live API vs hybrid NEEDS API KEYS (can't progress without): - [ ] Meta Ads MCP — needs META_ADS_API_KEY for Stage 8→9 - [ ] Twilio MCP — needs TWILIO_API_KEY for Stage 8→9 - [ ] CloseBot MCP — needs CLOSEBOT_API_KEY for live testing - [ ] 702 test cases across all servers need live API credentials PIPELINE ADVANCEMENT: - [ ] Stage 7→8: CloseBot + Google Console need design approval - [ ] Stage 6→7: 22 servers need UI apps built - [ ] Stage 5→6: 5 servers need core tools built (FreshBooks, Gusto, Jobber, Keap, Lightspeed) - [ ] Stage 1→5: 3 new MCPs need scaffolding (Compliance GRC, HR People Ops, Product Analytics) PENDING REVIEW: - [ ] Jake review OpenClaw video + gallery → finalize Upwork listing - [ ] LocalBosses UI redesign (Steve Jobs critique delivered, recs available) QUEUED PROJECTS: - [ ] SongSense AI music analysis product (architecture done, build not started) - [ ] 8-Week Agent Study Plan execution (curriculum posted, Week 1 not started)
239 lines
7.2 KiB
Python
239 lines
7.2 KiB
Python
"""
|
|
SURYA Blender Utilities
|
|
Shared functions for all track animations
|
|
"""
|
|
|
|
import bpy
|
|
import math
|
|
from mathutils import Vector, Matrix, Euler
|
|
import random
|
|
|
|
# Color palette (RGB normalized 0-1)
|
|
COLORS = {
|
|
"skin": (0.957, 0.447, 0.714, 1.0), # #f472b6 Pink
|
|
"u_saved_me": (0.133, 0.827, 0.933, 1.0), # #22d3ee Cyan
|
|
"nothing": (0.4, 0.4, 0.4, 1.0), # #666666 Grey
|
|
"natures_call": (0.369, 0.918, 0.831, 1.0), # #5eead4 Teal
|
|
"idk": (0.957, 0.447, 0.714, 1.0), # #f472b6 Pink
|
|
"with_u": (0.984, 0.749, 0.141, 1.0), # #fbbf24 Gold
|
|
"hollow": (0.984, 0.749, 0.141, 1.0), # #fbbf24 Gold
|
|
"intro": (0.102, 0.039, 0.180, 1.0), # #1a0a2e Deep purple
|
|
"white": (1.0, 1.0, 1.0, 1.0),
|
|
"black": (0.0, 0.0, 0.0, 1.0),
|
|
}
|
|
|
|
# Animation settings
|
|
FPS = 30
|
|
DURATION_SECONDS = 25
|
|
TOTAL_FRAMES = FPS * DURATION_SECONDS # 750 frames
|
|
|
|
|
|
def clear_scene():
|
|
"""Remove all objects from the current scene."""
|
|
bpy.ops.object.select_all(action='SELECT')
|
|
bpy.ops.object.delete(use_global=False)
|
|
|
|
# Clear orphan data
|
|
for block in bpy.data.meshes:
|
|
if block.users == 0:
|
|
bpy.data.meshes.remove(block)
|
|
for block in bpy.data.materials:
|
|
if block.users == 0:
|
|
bpy.data.materials.remove(block)
|
|
for block in bpy.data.curves:
|
|
if block.users == 0:
|
|
bpy.data.curves.remove(block)
|
|
|
|
|
|
def create_scene(name):
|
|
"""Create a new scene with the given name."""
|
|
scene = bpy.data.scenes.new(name)
|
|
bpy.context.window.scene = scene
|
|
scene.frame_start = 1
|
|
scene.frame_end = TOTAL_FRAMES
|
|
scene.render.fps = FPS
|
|
return scene
|
|
|
|
|
|
def setup_scene(background_color=(0.1, 0.04, 0.18, 1.0)):
|
|
"""Setup scene with camera and world settings."""
|
|
# Set world background
|
|
world = bpy.context.scene.world
|
|
if world is None:
|
|
world = bpy.data.worlds.new("World")
|
|
bpy.context.scene.world = world
|
|
|
|
world.use_nodes = True
|
|
bg_node = world.node_tree.nodes.get("Background")
|
|
if bg_node:
|
|
bg_node.inputs[0].default_value = background_color
|
|
bg_node.inputs[1].default_value = 1.0 # Strength
|
|
|
|
# Set render settings
|
|
bpy.context.scene.render.resolution_x = 1920
|
|
bpy.context.scene.render.resolution_y = 1080
|
|
|
|
|
|
def create_camera(location=(0, -10, 5), rotation=(1.1, 0, 0)):
|
|
"""Create and setup camera."""
|
|
bpy.ops.object.camera_add(location=location)
|
|
camera = bpy.context.active_object
|
|
camera.rotation_euler = Euler(rotation, 'XYZ')
|
|
bpy.context.scene.camera = camera
|
|
return camera
|
|
|
|
|
|
def create_emission_material(name, color, strength=2.0):
|
|
"""Create an emission material for glowing objects."""
|
|
mat = bpy.data.materials.new(name=name)
|
|
mat.use_nodes = True
|
|
nodes = mat.node_tree.nodes
|
|
links = mat.node_tree.links
|
|
|
|
# Clear default nodes
|
|
nodes.clear()
|
|
|
|
# Create emission shader
|
|
emission = nodes.new('ShaderNodeEmission')
|
|
emission.inputs[0].default_value = color
|
|
emission.inputs[1].default_value = strength
|
|
|
|
# Output
|
|
output = nodes.new('ShaderNodeOutputMaterial')
|
|
links.new(emission.outputs[0], output.inputs[0])
|
|
|
|
return mat
|
|
|
|
|
|
def create_basic_material(name, color, metallic=0.0, roughness=0.5):
|
|
"""Create a basic PBR material."""
|
|
mat = bpy.data.materials.new(name=name)
|
|
mat.use_nodes = True
|
|
bsdf = mat.node_tree.nodes.get("Principled BSDF")
|
|
if bsdf:
|
|
bsdf.inputs["Base Color"].default_value = color
|
|
bsdf.inputs["Metallic"].default_value = metallic
|
|
bsdf.inputs["Roughness"].default_value = roughness
|
|
return mat
|
|
|
|
|
|
def create_sphere(location=(0, 0, 0), radius=1.0, segments=32, rings=16, name="Sphere"):
|
|
"""Create a UV sphere."""
|
|
bpy.ops.mesh.primitive_uv_sphere_add(
|
|
radius=radius,
|
|
segments=segments,
|
|
ring_count=rings,
|
|
location=location
|
|
)
|
|
obj = bpy.context.active_object
|
|
obj.name = name
|
|
return obj
|
|
|
|
|
|
def create_icosahedron(location=(0, 0, 0), radius=1.0, name="Icosahedron"):
|
|
"""Create an icosahedron."""
|
|
bpy.ops.mesh.primitive_ico_sphere_add(
|
|
radius=radius,
|
|
subdivisions=1,
|
|
location=location
|
|
)
|
|
obj = bpy.context.active_object
|
|
obj.name = name
|
|
return obj
|
|
|
|
|
|
def create_curve_from_points(points, name="Curve", bevel_depth=0.02):
|
|
"""Create a curve from a list of 3D points."""
|
|
curve_data = bpy.data.curves.new(name=name, type='CURVE')
|
|
curve_data.dimensions = '3D'
|
|
curve_data.bevel_depth = bevel_depth
|
|
curve_data.bevel_resolution = 4
|
|
|
|
spline = curve_data.splines.new('NURBS')
|
|
spline.points.add(len(points) - 1)
|
|
|
|
for i, point in enumerate(points):
|
|
spline.points[i].co = (point[0], point[1], point[2], 1)
|
|
|
|
spline.use_endpoint_u = True
|
|
|
|
curve_obj = bpy.data.objects.new(name, curve_data)
|
|
bpy.context.collection.objects.link(curve_obj)
|
|
|
|
return curve_obj
|
|
|
|
|
|
def keyframe_location(obj, frame, location):
|
|
"""Set a location keyframe."""
|
|
obj.location = location
|
|
obj.keyframe_insert(data_path="location", frame=frame)
|
|
|
|
|
|
def keyframe_scale(obj, frame, scale):
|
|
"""Set a scale keyframe."""
|
|
if isinstance(scale, (int, float)):
|
|
scale = (scale, scale, scale)
|
|
obj.scale = scale
|
|
obj.keyframe_insert(data_path="scale", frame=frame)
|
|
|
|
|
|
def keyframe_rotation(obj, frame, rotation):
|
|
"""Set a rotation keyframe (Euler)."""
|
|
obj.rotation_euler = rotation
|
|
obj.keyframe_insert(data_path="rotation_euler", frame=frame)
|
|
|
|
|
|
def animate_camera_orbit(camera, center=(0, 0, 0), radius=10, height=5,
|
|
start_frame=1, end_frame=750, revolutions=1):
|
|
"""Animate camera orbiting around a center point."""
|
|
for frame in range(start_frame, end_frame + 1):
|
|
t = (frame - start_frame) / (end_frame - start_frame)
|
|
angle = t * 2 * math.pi * revolutions
|
|
|
|
x = center[0] + radius * math.cos(angle)
|
|
y = center[1] + radius * math.sin(angle)
|
|
z = center[2] + height
|
|
|
|
camera.location = (x, y, z)
|
|
camera.keyframe_insert(data_path="location", frame=frame)
|
|
|
|
# Point at center
|
|
direction = Vector(center) - Vector((x, y, z))
|
|
rot_quat = direction.to_track_quat('-Z', 'Y')
|
|
camera.rotation_euler = rot_quat.to_euler()
|
|
camera.keyframe_insert(data_path="rotation_euler", frame=frame)
|
|
|
|
|
|
def create_star_field(count=200, radius=20, min_size=0.02, max_size=0.08):
|
|
"""Create a field of star particles."""
|
|
stars = []
|
|
for i in range(count):
|
|
x = random.uniform(-radius, radius)
|
|
y = random.uniform(-radius, radius)
|
|
z = random.uniform(-radius/2, radius)
|
|
size = random.uniform(min_size, max_size)
|
|
|
|
bpy.ops.mesh.primitive_ico_sphere_add(radius=size, subdivisions=1, location=(x, y, z))
|
|
star = bpy.context.active_object
|
|
star.name = f"Star_{i:03d}"
|
|
|
|
# Add emission material
|
|
mat = create_emission_material(f"StarMat_{i:03d}", COLORS["white"], strength=5.0)
|
|
star.data.materials.append(mat)
|
|
stars.append(star)
|
|
|
|
return stars
|
|
|
|
|
|
def smooth_interpolation(t):
|
|
"""Smooth step interpolation (ease in-out)."""
|
|
return t * t * (3 - 2 * t)
|
|
|
|
|
|
def ease_in_out(t):
|
|
"""Ease in-out cubic."""
|
|
if t < 0.5:
|
|
return 4 * t * t * t
|
|
else:
|
|
return 1 - pow(-2 * t + 2, 3) / 2
|