amd-gaia 0.15.0__py3-none-any.whl → 0.15.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
- amd_gaia-0.15.2.dist-info/RECORD +182 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2132 -2177
- gaia/agents/base/api_agent.py +119 -120
- gaia/agents/base/console.py +1967 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +88 -83
- gaia/agents/blender/__init__.py +7 -0
- gaia/agents/blender/agent.py +553 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +809 -835
- gaia/agents/chat/app.py +1065 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1744 -1729
- gaia/agents/chat/tools/shell_tools.py +437 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2034 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +643 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1504 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1972 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +184 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +428 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5659 -5632
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/installer/__init__.py +23 -0
- gaia/installer/init_command.py +1275 -0
- gaia/installer/lemonade_installer.py +619 -0
- gaia/llm/__init__.py +10 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3421 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +118 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +183 -163
- gaia/talk/app.py +287 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.15.0.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -723
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
|
@@ -1,316 +1,316 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
import math
|
|
5
|
-
from typing import Dict, Tuple
|
|
6
|
-
|
|
7
|
-
from gaia.mcp.blender_mcp_client import MCPClient
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ObjectManager:
|
|
11
|
-
"""Manages Blender object operations."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, mcp: MCPClient):
|
|
14
|
-
self.mcp = mcp
|
|
15
|
-
|
|
16
|
-
def create_base_sphere_from_cube(self, radius: float = 6000) -> Dict:
|
|
17
|
-
"""Create a highly detailed sphere from a subdivided cube for planet Earth.
|
|
18
|
-
The default radius is 6000 meters, which corresponds to Earth's approximate radius of 6000 km
|
|
19
|
-
(at 1:10,000 scale, where 1 meter in Blender = 1 km in real life)."""
|
|
20
|
-
|
|
21
|
-
def generate_code():
|
|
22
|
-
return f"""
|
|
23
|
-
import bpy
|
|
24
|
-
import time
|
|
25
|
-
|
|
26
|
-
# Start with a clean slate but keep the default cube
|
|
27
|
-
for obj in bpy.data.objects:
|
|
28
|
-
if obj.name != "Cube":
|
|
29
|
-
obj.select_set(True)
|
|
30
|
-
else:
|
|
31
|
-
obj.select_set(False)
|
|
32
|
-
bpy.ops.object.delete()
|
|
33
|
-
|
|
34
|
-
# Use the default cube or create one if missing
|
|
35
|
-
bpy.ops.object.select_all(action='DESELECT')
|
|
36
|
-
cube = bpy.data.objects.get("Cube")
|
|
37
|
-
if not cube:
|
|
38
|
-
bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=False, align='WORLD')
|
|
39
|
-
cube = bpy.context.active_object
|
|
40
|
-
else:
|
|
41
|
-
cube.select_set(True)
|
|
42
|
-
bpy.context.view_layer.objects.active = cube
|
|
43
|
-
|
|
44
|
-
# Rename to Earth
|
|
45
|
-
cube.name = "Earth"
|
|
46
|
-
print("Working with cube named: " + cube.name)
|
|
47
|
-
|
|
48
|
-
# Enter edit mode
|
|
49
|
-
bpy.ops.object.mode_set(mode='EDIT')
|
|
50
|
-
print("Entered Edit mode")
|
|
51
|
-
|
|
52
|
-
# Select all vertices (matching step 4 in the documentation)
|
|
53
|
-
bpy.ops.mesh.select_all(action='SELECT')
|
|
54
|
-
print("Selected all vertices")
|
|
55
|
-
|
|
56
|
-
# Subdivide multiple times using Blender's subdivide operator
|
|
57
|
-
# Note: This matches the tutorial which uses W key + Shift+R for repeated subdivisions
|
|
58
|
-
# Using fewer subdivisions for better performance
|
|
59
|
-
for i in range(4): # Reduced subdivisions temporarily
|
|
60
|
-
bpy.ops.mesh.subdivide()
|
|
61
|
-
print("Completed subdivision " + str(i+1) + "/4")
|
|
62
|
-
time.sleep(0.1) # Small delay to ensure operation completes
|
|
63
|
-
|
|
64
|
-
# Spherify the cube (equivalent to Shift+Alt+S in the tutorial)
|
|
65
|
-
bpy.ops.transform.tosphere(value=1.0)
|
|
66
|
-
print("Applied spherify transform")
|
|
67
|
-
|
|
68
|
-
# Exit edit mode
|
|
69
|
-
bpy.ops.object.mode_set(mode='OBJECT')
|
|
70
|
-
print("Exited Edit mode")
|
|
71
|
-
|
|
72
|
-
# Smooth shading
|
|
73
|
-
bpy.ops.object.shade_smooth()
|
|
74
|
-
print("Applied smooth shading")
|
|
75
|
-
|
|
76
|
-
# Get the active object (should be our sphere)
|
|
77
|
-
obj = bpy.context.active_object
|
|
78
|
-
if not obj:
|
|
79
|
-
print("ERROR: No active object found!")
|
|
80
|
-
else:
|
|
81
|
-
print("Working with object: " + obj.name)
|
|
82
|
-
|
|
83
|
-
# Set real-world scale - start very small for testing
|
|
84
|
-
# First ensure dimensions are reset
|
|
85
|
-
obj.dimensions = (2, 2, 2)
|
|
86
|
-
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
87
|
-
print("Reset dimensions to 2x2x2, actual dimensions: " + str(obj.dimensions))
|
|
88
|
-
|
|
89
|
-
# Then scale up - using small test value first
|
|
90
|
-
obj.scale = ({radius}, {radius}, {radius})
|
|
91
|
-
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
92
|
-
print("Applied scale of {radius}, actual dimensions: " + str(obj.dimensions))
|
|
93
|
-
|
|
94
|
-
# Center the object (critical for visibility)
|
|
95
|
-
obj.location = (0, 0, 0)
|
|
96
|
-
print("Centered object at origin, location: " + str(obj.location))
|
|
97
|
-
|
|
98
|
-
# Return info for test verification
|
|
99
|
-
sphere = bpy.context.active_object
|
|
100
|
-
vertex_count = len(sphere.data.vertices) if sphere else 0
|
|
101
|
-
face_count = len(sphere.data.polygons) if sphere else 0
|
|
102
|
-
is_sphere = True if sphere and vertex_count > 100 else False
|
|
103
|
-
|
|
104
|
-
# Set result variable that will be returned by the MCP addon
|
|
105
|
-
result = {{
|
|
106
|
-
"found": sphere is not None,
|
|
107
|
-
"vertex_count": vertex_count,
|
|
108
|
-
"face_count": face_count,
|
|
109
|
-
"is_sphere": is_sphere,
|
|
110
|
-
"dimensions": [float(d) for d in sphere.dimensions] if sphere else [],
|
|
111
|
-
"location": [float(l) for l in sphere.location] if sphere else []
|
|
112
|
-
}}
|
|
113
|
-
|
|
114
|
-
print("Base Earth sphere created with " + str(vertex_count) + " vertices and " + str(face_count) + " faces")
|
|
115
|
-
"""
|
|
116
|
-
|
|
117
|
-
code = generate_code()
|
|
118
|
-
response = self.mcp.execute_code(code)
|
|
119
|
-
|
|
120
|
-
# Extract the returned result from the MCP if available
|
|
121
|
-
if response.get("result") and isinstance(response["result"], dict):
|
|
122
|
-
return {"status": "success", **response["result"]}
|
|
123
|
-
# Add stdout to the response for debugging
|
|
124
|
-
if "stdout" in response:
|
|
125
|
-
return {"status": "success", "message": response.get("stdout", "")}
|
|
126
|
-
# Fallback
|
|
127
|
-
return {"status": "success"}
|
|
128
|
-
|
|
129
|
-
def add_sunlight(
|
|
130
|
-
self, energy: float = 5.0, angle_degrees: Tuple[float, float] = (60, 45)
|
|
131
|
-
) -> Dict:
|
|
132
|
-
"""Add a sun light to illuminate the planet."""
|
|
133
|
-
|
|
134
|
-
def generate_code():
|
|
135
|
-
angle_x = math.radians(angle_degrees[0])
|
|
136
|
-
angle_z = math.radians(angle_degrees[1])
|
|
137
|
-
return f"""
|
|
138
|
-
import bpy
|
|
139
|
-
import math
|
|
140
|
-
|
|
141
|
-
bpy.ops.object.light_add(type='SUN', radius=1, align='WORLD')
|
|
142
|
-
sun = bpy.context.active_object
|
|
143
|
-
sun.name = "Sun"
|
|
144
|
-
sun.rotation_euler = ({angle_x}, 0, {angle_z})
|
|
145
|
-
sun.data.energy = {energy}
|
|
146
|
-
|
|
147
|
-
# Return info for test verification
|
|
148
|
-
result = {{
|
|
149
|
-
"found": sun is not None,
|
|
150
|
-
"is_sun": True if sun and sun.data.type == 'SUN' else False,
|
|
151
|
-
"name": sun.name if sun else None
|
|
152
|
-
}}
|
|
153
|
-
|
|
154
|
-
print("Sunlight added")
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
response = self.mcp.execute_code(generate_code())
|
|
158
|
-
# Extract the returned result from the MCP if available
|
|
159
|
-
if response.get("result") and isinstance(response["result"], dict):
|
|
160
|
-
return {"status": "success", **response["result"]}
|
|
161
|
-
# Add stdout to the response for debugging
|
|
162
|
-
if "stdout" in response:
|
|
163
|
-
return {"status": "success", "message": response.get("stdout", "")}
|
|
164
|
-
# Fallback
|
|
165
|
-
return {"status": "success"}
|
|
166
|
-
|
|
167
|
-
def load_earth_texture(
|
|
168
|
-
self, texture_name: str, texture_path: str, is_noncolor: bool = False
|
|
169
|
-
) -> Dict:
|
|
170
|
-
"""Load a texture image for the Earth."""
|
|
171
|
-
|
|
172
|
-
def generate_code():
|
|
173
|
-
# Convert backslashes to forward slashes to avoid unicode escape issues
|
|
174
|
-
safe_path = texture_path.replace("\\", "/")
|
|
175
|
-
|
|
176
|
-
noncolor_code = """
|
|
177
|
-
if img:
|
|
178
|
-
img.colorspace_settings.name = 'Non-Color'
|
|
179
|
-
"""
|
|
180
|
-
return f"""
|
|
181
|
-
import bpy
|
|
182
|
-
import os
|
|
183
|
-
|
|
184
|
-
# Load image
|
|
185
|
-
img = bpy.data.images.load(r"{safe_path}")
|
|
186
|
-
if img:
|
|
187
|
-
img.name = "{texture_name}"
|
|
188
|
-
{"" if not is_noncolor else noncolor_code}
|
|
189
|
-
|
|
190
|
-
# Return info for test verification
|
|
191
|
-
result = {{
|
|
192
|
-
"found": img is not None,
|
|
193
|
-
"name": img.name if img else None,
|
|
194
|
-
"filepath": img.filepath if img else None
|
|
195
|
-
}}
|
|
196
|
-
|
|
197
|
-
print(f"Texture '{texture_name}' loaded")
|
|
198
|
-
else:
|
|
199
|
-
result = {{"found": False}}
|
|
200
|
-
print(f"Failed to load texture from {safe_path}")
|
|
201
|
-
"""
|
|
202
|
-
|
|
203
|
-
response = self.mcp.execute_code(generate_code())
|
|
204
|
-
# Extract the returned result from the MCP if available
|
|
205
|
-
if (
|
|
206
|
-
response.get("status") == "success"
|
|
207
|
-
and isinstance(response["result"], dict)
|
|
208
|
-
and "found" in response["result"]
|
|
209
|
-
):
|
|
210
|
-
return {"status": "success", **response["result"]}
|
|
211
|
-
|
|
212
|
-
# Add stdout to the response for debugging
|
|
213
|
-
if response.get("status") == "success" and "stdout" in response.get(
|
|
214
|
-
"result", {}
|
|
215
|
-
):
|
|
216
|
-
return {
|
|
217
|
-
"status": "success",
|
|
218
|
-
"message": response["result"].get("stdout", ""),
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
# Fallback for error
|
|
222
|
-
if response.get("status") == "error":
|
|
223
|
-
return {
|
|
224
|
-
"status": "error",
|
|
225
|
-
"message": response.get(
|
|
226
|
-
"message", "Unknown error in load_earth_texture"
|
|
227
|
-
),
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
# General fallback
|
|
231
|
-
return {"status": "success", "found": False}
|
|
232
|
-
|
|
233
|
-
def create_atmosphere_object(self) -> Dict:
|
|
234
|
-
"""Create the atmosphere object."""
|
|
235
|
-
|
|
236
|
-
def generate_code():
|
|
237
|
-
return """
|
|
238
|
-
import bpy
|
|
239
|
-
|
|
240
|
-
# Duplicate Earth for atmosphere
|
|
241
|
-
bpy.ops.object.select_all(action='DESELECT')
|
|
242
|
-
earth = bpy.data.objects.get("Earth")
|
|
243
|
-
if not earth:
|
|
244
|
-
print("Error: Earth object not found")
|
|
245
|
-
result = {"found": False, "error": "Earth object not found"}
|
|
246
|
-
exit()
|
|
247
|
-
|
|
248
|
-
earth.select_set(True)
|
|
249
|
-
bpy.context.view_layer.objects.active = earth
|
|
250
|
-
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False})
|
|
251
|
-
atm = bpy.context.active_object
|
|
252
|
-
atm.name = "Atmosphere"
|
|
253
|
-
|
|
254
|
-
# Return info for test verification
|
|
255
|
-
result = {
|
|
256
|
-
"found": atm is not None,
|
|
257
|
-
"name": atm.name if atm else None,
|
|
258
|
-
"is_duplicate": True if atm and atm.data.users > 1 else False
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
print("Atmosphere object created")
|
|
262
|
-
"""
|
|
263
|
-
|
|
264
|
-
response = self.mcp.execute_code(generate_code())
|
|
265
|
-
# Extract the returned result from the MCP if available
|
|
266
|
-
if response.get("result") and isinstance(response["result"], dict):
|
|
267
|
-
return {"status": "success", **response["result"]}
|
|
268
|
-
# Add stdout to the response for debugging
|
|
269
|
-
if "stdout" in response:
|
|
270
|
-
return {"status": "success", "message": response.get("stdout", "")}
|
|
271
|
-
# Fallback
|
|
272
|
-
return {"status": "success"}
|
|
273
|
-
|
|
274
|
-
def create_clouds_object(self) -> Dict:
|
|
275
|
-
"""Create the clouds object."""
|
|
276
|
-
|
|
277
|
-
def generate_code():
|
|
278
|
-
return """
|
|
279
|
-
import bpy
|
|
280
|
-
|
|
281
|
-
# Duplicate Earth for clouds
|
|
282
|
-
bpy.ops.object.select_all(action='DESELECT')
|
|
283
|
-
atm = bpy.data.objects.get("Atmosphere")
|
|
284
|
-
if not atm:
|
|
285
|
-
print("Error: Atmosphere object not found")
|
|
286
|
-
result = {"found": False, "error": "Atmosphere object not found"}
|
|
287
|
-
exit()
|
|
288
|
-
|
|
289
|
-
atm.select_set(True)
|
|
290
|
-
bpy.context.view_layer.objects.active = atm
|
|
291
|
-
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False})
|
|
292
|
-
clouds = bpy.context.active_object
|
|
293
|
-
clouds.name = "Clouds"
|
|
294
|
-
|
|
295
|
-
# Scale up slightly to place above the Earth - exactly as in tutorial
|
|
296
|
-
clouds.scale = (1.001, 1.001, 1.001) # Tutorial uses 1.001 scale
|
|
297
|
-
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
298
|
-
|
|
299
|
-
# Return info for test verification
|
|
300
|
-
result = {
|
|
301
|
-
"found": clouds is not None,
|
|
302
|
-
"name": clouds.name if clouds else None
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
print("Clouds object created")
|
|
306
|
-
"""
|
|
307
|
-
|
|
308
|
-
response = self.mcp.execute_code(generate_code())
|
|
309
|
-
# Extract the returned result from the MCP if available
|
|
310
|
-
if response.get("result") and isinstance(response["result"], dict):
|
|
311
|
-
return {"status": "success", **response["result"]}
|
|
312
|
-
# Add stdout to the response for debugging
|
|
313
|
-
if "stdout" in response:
|
|
314
|
-
return {"status": "success", "message": response.get("stdout", "")}
|
|
315
|
-
# Fallback
|
|
316
|
-
return {"status": "success"}
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
from typing import Dict, Tuple
|
|
6
|
+
|
|
7
|
+
from gaia.mcp.blender_mcp_client import MCPClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ObjectManager:
|
|
11
|
+
"""Manages Blender object operations."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, mcp: MCPClient):
|
|
14
|
+
self.mcp = mcp
|
|
15
|
+
|
|
16
|
+
def create_base_sphere_from_cube(self, radius: float = 6000) -> Dict:
|
|
17
|
+
"""Create a highly detailed sphere from a subdivided cube for planet Earth.
|
|
18
|
+
The default radius is 6000 meters, which corresponds to Earth's approximate radius of 6000 km
|
|
19
|
+
(at 1:10,000 scale, where 1 meter in Blender = 1 km in real life)."""
|
|
20
|
+
|
|
21
|
+
def generate_code():
|
|
22
|
+
return f"""
|
|
23
|
+
import bpy
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
# Start with a clean slate but keep the default cube
|
|
27
|
+
for obj in bpy.data.objects:
|
|
28
|
+
if obj.name != "Cube":
|
|
29
|
+
obj.select_set(True)
|
|
30
|
+
else:
|
|
31
|
+
obj.select_set(False)
|
|
32
|
+
bpy.ops.object.delete()
|
|
33
|
+
|
|
34
|
+
# Use the default cube or create one if missing
|
|
35
|
+
bpy.ops.object.select_all(action='DESELECT')
|
|
36
|
+
cube = bpy.data.objects.get("Cube")
|
|
37
|
+
if not cube:
|
|
38
|
+
bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=False, align='WORLD')
|
|
39
|
+
cube = bpy.context.active_object
|
|
40
|
+
else:
|
|
41
|
+
cube.select_set(True)
|
|
42
|
+
bpy.context.view_layer.objects.active = cube
|
|
43
|
+
|
|
44
|
+
# Rename to Earth
|
|
45
|
+
cube.name = "Earth"
|
|
46
|
+
print("Working with cube named: " + cube.name)
|
|
47
|
+
|
|
48
|
+
# Enter edit mode
|
|
49
|
+
bpy.ops.object.mode_set(mode='EDIT')
|
|
50
|
+
print("Entered Edit mode")
|
|
51
|
+
|
|
52
|
+
# Select all vertices (matching step 4 in the documentation)
|
|
53
|
+
bpy.ops.mesh.select_all(action='SELECT')
|
|
54
|
+
print("Selected all vertices")
|
|
55
|
+
|
|
56
|
+
# Subdivide multiple times using Blender's subdivide operator
|
|
57
|
+
# Note: This matches the tutorial which uses W key + Shift+R for repeated subdivisions
|
|
58
|
+
# Using fewer subdivisions for better performance
|
|
59
|
+
for i in range(4): # Reduced subdivisions temporarily
|
|
60
|
+
bpy.ops.mesh.subdivide()
|
|
61
|
+
print("Completed subdivision " + str(i+1) + "/4")
|
|
62
|
+
time.sleep(0.1) # Small delay to ensure operation completes
|
|
63
|
+
|
|
64
|
+
# Spherify the cube (equivalent to Shift+Alt+S in the tutorial)
|
|
65
|
+
bpy.ops.transform.tosphere(value=1.0)
|
|
66
|
+
print("Applied spherify transform")
|
|
67
|
+
|
|
68
|
+
# Exit edit mode
|
|
69
|
+
bpy.ops.object.mode_set(mode='OBJECT')
|
|
70
|
+
print("Exited Edit mode")
|
|
71
|
+
|
|
72
|
+
# Smooth shading
|
|
73
|
+
bpy.ops.object.shade_smooth()
|
|
74
|
+
print("Applied smooth shading")
|
|
75
|
+
|
|
76
|
+
# Get the active object (should be our sphere)
|
|
77
|
+
obj = bpy.context.active_object
|
|
78
|
+
if not obj:
|
|
79
|
+
print("ERROR: No active object found!")
|
|
80
|
+
else:
|
|
81
|
+
print("Working with object: " + obj.name)
|
|
82
|
+
|
|
83
|
+
# Set real-world scale - start very small for testing
|
|
84
|
+
# First ensure dimensions are reset
|
|
85
|
+
obj.dimensions = (2, 2, 2)
|
|
86
|
+
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
87
|
+
print("Reset dimensions to 2x2x2, actual dimensions: " + str(obj.dimensions))
|
|
88
|
+
|
|
89
|
+
# Then scale up - using small test value first
|
|
90
|
+
obj.scale = ({radius}, {radius}, {radius})
|
|
91
|
+
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
92
|
+
print("Applied scale of {radius}, actual dimensions: " + str(obj.dimensions))
|
|
93
|
+
|
|
94
|
+
# Center the object (critical for visibility)
|
|
95
|
+
obj.location = (0, 0, 0)
|
|
96
|
+
print("Centered object at origin, location: " + str(obj.location))
|
|
97
|
+
|
|
98
|
+
# Return info for test verification
|
|
99
|
+
sphere = bpy.context.active_object
|
|
100
|
+
vertex_count = len(sphere.data.vertices) if sphere else 0
|
|
101
|
+
face_count = len(sphere.data.polygons) if sphere else 0
|
|
102
|
+
is_sphere = True if sphere and vertex_count > 100 else False
|
|
103
|
+
|
|
104
|
+
# Set result variable that will be returned by the MCP addon
|
|
105
|
+
result = {{
|
|
106
|
+
"found": sphere is not None,
|
|
107
|
+
"vertex_count": vertex_count,
|
|
108
|
+
"face_count": face_count,
|
|
109
|
+
"is_sphere": is_sphere,
|
|
110
|
+
"dimensions": [float(d) for d in sphere.dimensions] if sphere else [],
|
|
111
|
+
"location": [float(l) for l in sphere.location] if sphere else []
|
|
112
|
+
}}
|
|
113
|
+
|
|
114
|
+
print("Base Earth sphere created with " + str(vertex_count) + " vertices and " + str(face_count) + " faces")
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
code = generate_code()
|
|
118
|
+
response = self.mcp.execute_code(code)
|
|
119
|
+
|
|
120
|
+
# Extract the returned result from the MCP if available
|
|
121
|
+
if response.get("result") and isinstance(response["result"], dict):
|
|
122
|
+
return {"status": "success", **response["result"]}
|
|
123
|
+
# Add stdout to the response for debugging
|
|
124
|
+
if "stdout" in response:
|
|
125
|
+
return {"status": "success", "message": response.get("stdout", "")}
|
|
126
|
+
# Fallback
|
|
127
|
+
return {"status": "success"}
|
|
128
|
+
|
|
129
|
+
def add_sunlight(
|
|
130
|
+
self, energy: float = 5.0, angle_degrees: Tuple[float, float] = (60, 45)
|
|
131
|
+
) -> Dict:
|
|
132
|
+
"""Add a sun light to illuminate the planet."""
|
|
133
|
+
|
|
134
|
+
def generate_code():
|
|
135
|
+
angle_x = math.radians(angle_degrees[0])
|
|
136
|
+
angle_z = math.radians(angle_degrees[1])
|
|
137
|
+
return f"""
|
|
138
|
+
import bpy
|
|
139
|
+
import math
|
|
140
|
+
|
|
141
|
+
bpy.ops.object.light_add(type='SUN', radius=1, align='WORLD')
|
|
142
|
+
sun = bpy.context.active_object
|
|
143
|
+
sun.name = "Sun"
|
|
144
|
+
sun.rotation_euler = ({angle_x}, 0, {angle_z})
|
|
145
|
+
sun.data.energy = {energy}
|
|
146
|
+
|
|
147
|
+
# Return info for test verification
|
|
148
|
+
result = {{
|
|
149
|
+
"found": sun is not None,
|
|
150
|
+
"is_sun": True if sun and sun.data.type == 'SUN' else False,
|
|
151
|
+
"name": sun.name if sun else None
|
|
152
|
+
}}
|
|
153
|
+
|
|
154
|
+
print("Sunlight added")
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
response = self.mcp.execute_code(generate_code())
|
|
158
|
+
# Extract the returned result from the MCP if available
|
|
159
|
+
if response.get("result") and isinstance(response["result"], dict):
|
|
160
|
+
return {"status": "success", **response["result"]}
|
|
161
|
+
# Add stdout to the response for debugging
|
|
162
|
+
if "stdout" in response:
|
|
163
|
+
return {"status": "success", "message": response.get("stdout", "")}
|
|
164
|
+
# Fallback
|
|
165
|
+
return {"status": "success"}
|
|
166
|
+
|
|
167
|
+
def load_earth_texture(
|
|
168
|
+
self, texture_name: str, texture_path: str, is_noncolor: bool = False
|
|
169
|
+
) -> Dict:
|
|
170
|
+
"""Load a texture image for the Earth."""
|
|
171
|
+
|
|
172
|
+
def generate_code():
|
|
173
|
+
# Convert backslashes to forward slashes to avoid unicode escape issues
|
|
174
|
+
safe_path = texture_path.replace("\\", "/")
|
|
175
|
+
|
|
176
|
+
noncolor_code = """
|
|
177
|
+
if img:
|
|
178
|
+
img.colorspace_settings.name = 'Non-Color'
|
|
179
|
+
"""
|
|
180
|
+
return f"""
|
|
181
|
+
import bpy
|
|
182
|
+
import os
|
|
183
|
+
|
|
184
|
+
# Load image
|
|
185
|
+
img = bpy.data.images.load(r"{safe_path}")
|
|
186
|
+
if img:
|
|
187
|
+
img.name = "{texture_name}"
|
|
188
|
+
{"" if not is_noncolor else noncolor_code}
|
|
189
|
+
|
|
190
|
+
# Return info for test verification
|
|
191
|
+
result = {{
|
|
192
|
+
"found": img is not None,
|
|
193
|
+
"name": img.name if img else None,
|
|
194
|
+
"filepath": img.filepath if img else None
|
|
195
|
+
}}
|
|
196
|
+
|
|
197
|
+
print(f"Texture '{texture_name}' loaded")
|
|
198
|
+
else:
|
|
199
|
+
result = {{"found": False}}
|
|
200
|
+
print(f"Failed to load texture from {safe_path}")
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
response = self.mcp.execute_code(generate_code())
|
|
204
|
+
# Extract the returned result from the MCP if available
|
|
205
|
+
if (
|
|
206
|
+
response.get("status") == "success"
|
|
207
|
+
and isinstance(response["result"], dict)
|
|
208
|
+
and "found" in response["result"]
|
|
209
|
+
):
|
|
210
|
+
return {"status": "success", **response["result"]}
|
|
211
|
+
|
|
212
|
+
# Add stdout to the response for debugging
|
|
213
|
+
if response.get("status") == "success" and "stdout" in response.get(
|
|
214
|
+
"result", {}
|
|
215
|
+
):
|
|
216
|
+
return {
|
|
217
|
+
"status": "success",
|
|
218
|
+
"message": response["result"].get("stdout", ""),
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
# Fallback for error
|
|
222
|
+
if response.get("status") == "error":
|
|
223
|
+
return {
|
|
224
|
+
"status": "error",
|
|
225
|
+
"message": response.get(
|
|
226
|
+
"message", "Unknown error in load_earth_texture"
|
|
227
|
+
),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# General fallback
|
|
231
|
+
return {"status": "success", "found": False}
|
|
232
|
+
|
|
233
|
+
def create_atmosphere_object(self) -> Dict:
|
|
234
|
+
"""Create the atmosphere object."""
|
|
235
|
+
|
|
236
|
+
def generate_code():
|
|
237
|
+
return """
|
|
238
|
+
import bpy
|
|
239
|
+
|
|
240
|
+
# Duplicate Earth for atmosphere
|
|
241
|
+
bpy.ops.object.select_all(action='DESELECT')
|
|
242
|
+
earth = bpy.data.objects.get("Earth")
|
|
243
|
+
if not earth:
|
|
244
|
+
print("Error: Earth object not found")
|
|
245
|
+
result = {"found": False, "error": "Earth object not found"}
|
|
246
|
+
exit()
|
|
247
|
+
|
|
248
|
+
earth.select_set(True)
|
|
249
|
+
bpy.context.view_layer.objects.active = earth
|
|
250
|
+
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False})
|
|
251
|
+
atm = bpy.context.active_object
|
|
252
|
+
atm.name = "Atmosphere"
|
|
253
|
+
|
|
254
|
+
# Return info for test verification
|
|
255
|
+
result = {
|
|
256
|
+
"found": atm is not None,
|
|
257
|
+
"name": atm.name if atm else None,
|
|
258
|
+
"is_duplicate": True if atm and atm.data.users > 1 else False
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
print("Atmosphere object created")
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
response = self.mcp.execute_code(generate_code())
|
|
265
|
+
# Extract the returned result from the MCP if available
|
|
266
|
+
if response.get("result") and isinstance(response["result"], dict):
|
|
267
|
+
return {"status": "success", **response["result"]}
|
|
268
|
+
# Add stdout to the response for debugging
|
|
269
|
+
if "stdout" in response:
|
|
270
|
+
return {"status": "success", "message": response.get("stdout", "")}
|
|
271
|
+
# Fallback
|
|
272
|
+
return {"status": "success"}
|
|
273
|
+
|
|
274
|
+
def create_clouds_object(self) -> Dict:
|
|
275
|
+
"""Create the clouds object."""
|
|
276
|
+
|
|
277
|
+
def generate_code():
|
|
278
|
+
return """
|
|
279
|
+
import bpy
|
|
280
|
+
|
|
281
|
+
# Duplicate Earth for clouds
|
|
282
|
+
bpy.ops.object.select_all(action='DESELECT')
|
|
283
|
+
atm = bpy.data.objects.get("Atmosphere")
|
|
284
|
+
if not atm:
|
|
285
|
+
print("Error: Atmosphere object not found")
|
|
286
|
+
result = {"found": False, "error": "Atmosphere object not found"}
|
|
287
|
+
exit()
|
|
288
|
+
|
|
289
|
+
atm.select_set(True)
|
|
290
|
+
bpy.context.view_layer.objects.active = atm
|
|
291
|
+
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False})
|
|
292
|
+
clouds = bpy.context.active_object
|
|
293
|
+
clouds.name = "Clouds"
|
|
294
|
+
|
|
295
|
+
# Scale up slightly to place above the Earth - exactly as in tutorial
|
|
296
|
+
clouds.scale = (1.001, 1.001, 1.001) # Tutorial uses 1.001 scale
|
|
297
|
+
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
|
298
|
+
|
|
299
|
+
# Return info for test verification
|
|
300
|
+
result = {
|
|
301
|
+
"found": clouds is not None,
|
|
302
|
+
"name": clouds.name if clouds else None
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
print("Clouds object created")
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
response = self.mcp.execute_code(generate_code())
|
|
309
|
+
# Extract the returned result from the MCP if available
|
|
310
|
+
if response.get("result") and isinstance(response["result"], dict):
|
|
311
|
+
return {"status": "success", **response["result"]}
|
|
312
|
+
# Add stdout to the response for debugging
|
|
313
|
+
if "stdout" in response:
|
|
314
|
+
return {"status": "success", "message": response.get("stdout", "")}
|
|
315
|
+
# Fallback
|
|
316
|
+
return {"status": "success"}
|