"""
Scene and Object Information Handlers
Business logic for scene inspection and viewport operations that run inside Blender.
These functions use bpy and are called by the addon.py socket server.
"""
import bpy
import mathutils
import traceback
import io
from contextlib import redirect_stdout
def get_scene_info():
"""Get information about the current Blender scene"""
try:
print("Getting scene info...")
# Simplify the scene info to reduce data size
scene_info = {
"name": bpy.context.scene.name,
"object_count": len(bpy.context.scene.objects),
"objects": [],
"materials_count": len(bpy.data.materials),
}
# Collect minimal object information (limit to first 10 objects)
for i, obj in enumerate(bpy.context.scene.objects):
if i >= 10: # Reduced from 20 to 10
break
obj_info = {
"name": obj.name,
"type": obj.type,
# Only include basic location data
"location": [round(float(obj.location.x), 2),
round(float(obj.location.y), 2),
round(float(obj.location.z), 2)],
}
scene_info["objects"].append(obj_info)
print(f"Scene info collected: {len(scene_info['objects'])} objects")
return scene_info
except Exception as e:
print(f"Error in get_scene_info: {str(e)}")
traceback.print_exc()
return {"error": str(e)}
def _get_aabb(obj):
"""Returns the world-space axis-aligned bounding box (AABB) of an object."""
if obj.type != 'MESH':
raise TypeError("Object must be a mesh")
# Get the bounding box corners in local space
local_bbox_corners = [mathutils.Vector(corner) for corner in obj.bound_box]
# Convert to world coordinates
world_bbox_corners = [obj.matrix_world @ corner for corner in local_bbox_corners]
# Compute axis-aligned min/max coordinates
min_corner = mathutils.Vector(map(min, zip(*world_bbox_corners)))
max_corner = mathutils.Vector(map(max, zip(*world_bbox_corners)))
return [
[*min_corner], [*max_corner]
]
def get_object_info(name):
"""Get detailed information about a specific object"""
obj = bpy.data.objects.get(name)
if not obj:
raise ValueError(f"Object not found: {name}")
# Basic object info
obj_info = {
"name": obj.name,
"type": obj.type,
"location": [obj.location.x, obj.location.y, obj.location.z],
"rotation": [obj.rotation_euler.x, obj.rotation_euler.y, obj.rotation_euler.z],
"scale": [obj.scale.x, obj.scale.y, obj.scale.z],
"visible": obj.visible_get(),
"materials": [],
}
if obj.type == "MESH":
bounding_box = _get_aabb(obj)
obj_info["world_bounding_box"] = bounding_box
# Add material slots
for slot in obj.material_slots:
if slot.material:
obj_info["materials"].append(slot.material.name)
# Add mesh data if applicable
if obj.type == 'MESH' and obj.data:
mesh = obj.data
obj_info["mesh"] = {
"vertices": len(mesh.vertices),
"edges": len(mesh.edges),
"polygons": len(mesh.polygons),
}
return obj_info
def get_viewport_screenshot(max_size=800, filepath=None, format="png"):
"""
Capture a screenshot of the current 3D viewport and save it to the specified path.
Parameters:
- max_size: Maximum size in pixels for the largest dimension of the image
- filepath: Path where to save the screenshot file
- format: Image format (png, jpg, etc.)
Returns success/error status
"""
try:
if not filepath:
return {"error": "No filepath provided"}
# Find the active 3D viewport
area = None
for a in bpy.context.screen.areas:
if a.type == 'VIEW_3D':
area = a
break
if not area:
return {"error": "No 3D viewport found"}
# Take screenshot with proper context override
with bpy.context.temp_override(area=area):
bpy.ops.screen.screenshot_area(filepath=filepath)
# Load and resize if needed
img = bpy.data.images.load(filepath)
width, height = img.size
if max(width, height) > max_size:
scale = max_size / max(width, height)
new_width = int(width * scale)
new_height = int(height * scale)
img.scale(new_width, new_height)
# Set format and save
img.file_format = format.upper()
img.save()
width, height = new_width, new_height
# Cleanup Blender image data
bpy.data.images.remove(img)
return {
"success": True,
"width": width,
"height": height,
"filepath": filepath
}
except Exception as e:
return {"error": str(e)}
def execute_code(code):
"""Execute arbitrary Blender Python code"""
# This is powerful but potentially dangerous - use with caution
try:
# Create a local namespace for execution
namespace = {"bpy": bpy}
# Capture stdout during execution, and return it as result
capture_buffer = io.StringIO()
with redirect_stdout(capture_buffer):
exec(code, namespace)
captured_output = capture_buffer.getvalue()
return {"executed": True, "result": captured_output}
except Exception as e:
raise Exception(f"Code execution error: {str(e)}")