=== 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)
187 lines
6.4 KiB
Python
187 lines
6.4 KiB
Python
"""
|
|
Track 06: NATURE'S CALL - 3D Fractal Tree (L-System)
|
|
"""
|
|
|
|
import bpy
|
|
import math
|
|
import random
|
|
import sys
|
|
import os
|
|
from mathutils import Vector, Matrix
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from utils import *
|
|
|
|
|
|
def create_branch(start, end, thickness=0.05, name="Branch"):
|
|
"""Create a cylindrical branch between two points."""
|
|
direction = Vector(end) - Vector(start)
|
|
length = direction.length
|
|
|
|
if length < 0.01:
|
|
return None
|
|
|
|
# Create cylinder
|
|
bpy.ops.mesh.primitive_cylinder_add(
|
|
radius=thickness,
|
|
depth=length,
|
|
location=(0, 0, 0)
|
|
)
|
|
branch = bpy.context.active_object
|
|
branch.name = name
|
|
|
|
# Position and orient
|
|
mid = [(start[i] + end[i]) / 2 for i in range(3)]
|
|
branch.location = mid
|
|
|
|
# Rotate to align with direction
|
|
up = Vector((0, 0, 1))
|
|
direction.normalize()
|
|
|
|
rotation_axis = up.cross(direction)
|
|
if rotation_axis.length > 0.0001:
|
|
rotation_axis.normalize()
|
|
angle = math.acos(max(-1, min(1, up.dot(direction))))
|
|
branch.rotation_mode = 'AXIS_ANGLE'
|
|
branch.rotation_axis_angle = (angle, rotation_axis.x, rotation_axis.y, rotation_axis.z)
|
|
|
|
return branch
|
|
|
|
|
|
def generate_tree_recursive(start, direction, length, depth, max_depth, branches, leaves):
|
|
"""Recursively generate tree branches."""
|
|
if depth > max_depth or length < 0.1:
|
|
# Add leaf
|
|
leaves.append(start)
|
|
return
|
|
|
|
end = [start[i] + direction[i] * length for i in range(3)]
|
|
branches.append((start, end, depth))
|
|
|
|
# Generate child branches
|
|
angles = [math.pi/5, -math.pi/5, math.pi/6, -math.pi/6]
|
|
|
|
for angle in angles:
|
|
if random.random() > 0.35:
|
|
# Rotate direction around random axis
|
|
rot_axis = (random.uniform(-1, 1), random.uniform(-1, 1), 0)
|
|
norm = math.sqrt(sum(r*r for r in rot_axis))
|
|
if norm > 0.01:
|
|
rot_axis = tuple(r/norm for r in rot_axis)
|
|
else:
|
|
rot_axis = (1, 0, 0)
|
|
|
|
# Simple rotation (approximate)
|
|
new_dir = [
|
|
direction[0] * math.cos(angle) + rot_axis[0] * (1 - math.cos(angle)),
|
|
direction[1] * math.cos(angle) + rot_axis[1] * (1 - math.cos(angle)),
|
|
direction[2] * math.cos(angle) + math.sin(angle) * 0.3 + 0.3
|
|
]
|
|
|
|
# Normalize
|
|
norm = math.sqrt(sum(d*d for d in new_dir))
|
|
new_dir = [d/norm for d in new_dir]
|
|
|
|
generate_tree_recursive(end, new_dir, length * 0.7, depth + 1, max_depth, branches, leaves)
|
|
|
|
|
|
def create_natures_call_animation():
|
|
"""Create the full Track 06 animation."""
|
|
clear_scene()
|
|
setup_scene(background_color=(0.02, 0.11, 0.09, 1.0)) # Forest dark green
|
|
|
|
# Create camera
|
|
camera = create_camera(location=(0, -15, 5), rotation=(1.2, 0, 0))
|
|
animate_camera_orbit(camera, center=(0, 0, 2), radius=15, height=5,
|
|
start_frame=1, end_frame=TOTAL_FRAMES, revolutions=0.3)
|
|
|
|
# Generate tree structure
|
|
branches = []
|
|
leaves = []
|
|
|
|
start = (0, 0, -4)
|
|
direction = (0, 0, 1)
|
|
generate_tree_recursive(start, direction, 2.0, 0, 5, branches, leaves)
|
|
|
|
# Create branch objects with progressive animation
|
|
branch_mat = create_emission_material("BranchMat", COLORS["natures_call"], strength=1.5)
|
|
branch_objects = []
|
|
|
|
total_branches = len(branches)
|
|
frames_per_branch = max(1, 400 // total_branches)
|
|
|
|
for i, (start_pos, end_pos, depth) in enumerate(branches):
|
|
thickness = max(0.02, 0.1 - depth * 0.015)
|
|
|
|
# Create curve instead of cylinder for smoother look
|
|
curve_data = bpy.data.curves.new(name=f"Branch_{i}", type='CURVE')
|
|
curve_data.dimensions = '3D'
|
|
curve_data.bevel_depth = thickness
|
|
curve_data.bevel_resolution = 4
|
|
|
|
spline = curve_data.splines.new('BEZIER')
|
|
spline.bezier_points.add(1)
|
|
spline.bezier_points[0].co = start_pos
|
|
spline.bezier_points[0].handle_right = [start_pos[j] + (end_pos[j] - start_pos[j]) * 0.3 for j in range(3)]
|
|
spline.bezier_points[0].handle_left = start_pos
|
|
spline.bezier_points[1].co = end_pos
|
|
spline.bezier_points[1].handle_left = [end_pos[j] - (end_pos[j] - start_pos[j]) * 0.3 for j in range(3)]
|
|
spline.bezier_points[1].handle_right = end_pos
|
|
|
|
branch_obj = bpy.data.objects.new(f"Branch_{i}", curve_data)
|
|
bpy.context.collection.objects.link(branch_obj)
|
|
branch_obj.data.materials.append(branch_mat)
|
|
|
|
# Animate growth (bevel depth)
|
|
appear_frame = 30 + i * frames_per_branch
|
|
|
|
curve_data.bevel_depth = 0.0
|
|
curve_data.keyframe_insert(data_path="bevel_depth", frame=1)
|
|
curve_data.keyframe_insert(data_path="bevel_depth", frame=appear_frame)
|
|
|
|
curve_data.bevel_depth = thickness
|
|
curve_data.keyframe_insert(data_path="bevel_depth", frame=appear_frame + 20)
|
|
|
|
branch_objects.append(branch_obj)
|
|
|
|
# Create leaves
|
|
leaf_mat = create_emission_material("LeafMat", (0.133, 0.773, 0.333, 1.0), strength=2.0)
|
|
leaf_objects = []
|
|
|
|
leaves_appear_frame = 450
|
|
|
|
for i, pos in enumerate(leaves[:80]): # Limit leaves
|
|
bpy.ops.mesh.primitive_ico_sphere_add(radius=0.12, subdivisions=1, location=pos)
|
|
leaf = bpy.context.active_object
|
|
leaf.name = f"Leaf_{i:03d}"
|
|
leaf.data.materials.append(leaf_mat)
|
|
|
|
# Animate leaf appearance
|
|
keyframe_scale(leaf, 1, 0.01)
|
|
keyframe_scale(leaf, leaves_appear_frame + i * 2, 0.01)
|
|
keyframe_scale(leaf, leaves_appear_frame + i * 2 + 30, 1.0)
|
|
|
|
leaf_objects.append(leaf)
|
|
|
|
return branch_objects, leaf_objects
|
|
|
|
|
|
if __name__ == "__main__":
|
|
create_natures_call_animation()
|
|
|
|
output_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(output_dir, "exports", "track06_natures_call.blend"))
|
|
|
|
bpy.ops.export_scene.gltf(
|
|
filepath=os.path.join(output_dir, "exports", "track06_natures_call.gltf"),
|
|
export_animations=True,
|
|
export_format='GLTF_SEPARATE'
|
|
)
|
|
|
|
bpy.ops.wm.alembic_export(
|
|
filepath=os.path.join(output_dir, "exports", "track06_natures_call.abc"),
|
|
start=1, end=TOTAL_FRAMES
|
|
)
|
|
|
|
print("Track 06 - Nature's Call: Export complete!")
|