Skip to main content
Glama

MCP-Blender

by shdann
blender_addon_enhanced.py13.3 kB
""" Blender Addon Enhanced for VeFrank Integration Installs as a Blender addon to handle VeFrank component generation """ bl_info = { "name": "VeFrank MCP Enhanced", "blender": (3, 0, 0), "category": "Import-Export", "version": (2, 0, 0), "author": "VeFrank Team", "description": "Enhanced MCP server integration for VeFrank automotive components", } import bpy import json import socket import threading import math from pathlib import Path from mathutils import Vector class VeFrankServer: """Socket server for receiving commands from VeFrank.""" def __init__(self, host="localhost", port=9876): self.host = host self.port = port self.server_socket = None self.running = False self.thread = None def start(self): """Start the server in a separate thread.""" if not self.running: self.running = True self.thread = threading.Thread(target=self.run) self.thread.daemon = True self.thread.start() return True return False def stop(self): """Stop the server.""" self.running = False if self.server_socket: self.server_socket.close() def run(self): """Main server loop.""" self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind((self.host, self.port)) self.server_socket.listen(5) self.server_socket.settimeout(1.0) print(f"VeFrank server listening on {self.host}:{self.port}") while self.running: try: client_socket, address = self.server_socket.accept() data = client_socket.recv(8192).decode('utf-8') if data: response = self.handle_command(json.loads(data)) client_socket.send(json.dumps(response).encode('utf-8')) client_socket.close() except socket.timeout: continue except Exception as e: print(f"Server error: {e}") def handle_command(self, command): """Handle incoming commands.""" cmd_type = command.get("type") params = command.get("params", {}) handlers = { "import_obj": self.import_obj, "apply_material": self.apply_material, "add_connectors": self.add_connectors, "capture_viewport": self.capture_viewport, "get_scene_info": self.get_scene_info, "execute_code": self.execute_code, } handler = handlers.get(cmd_type) if handler: try: result = handler(params) return {"status": "success", "result": result} except Exception as e: return {"status": "error", "message": str(e)} else: return {"status": "error", "message": f"Unknown command: {cmd_type}"} def import_obj(self, params): """Import OBJ file into Blender.""" filepath = params.get("filepath") name = params.get("name", "Component") # Import OBJ bpy.ops.import_scene.obj(filepath=filepath) # Rename imported object if bpy.context.selected_objects: obj = bpy.context.selected_objects[0] obj.name = name # Center and scale bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS') obj.location = (0, 0, 0) # Ensure reasonable scale max_dim = max(obj.dimensions) if max_dim > 10: scale_factor = 5 / max_dim obj.scale = (scale_factor, scale_factor, scale_factor) return {"object_name": obj.name} return {"error": "No object imported"} def apply_material(self, params): """Apply material to object.""" object_name = params.get("object_name") material_props = params.get("material", {}) obj = bpy.data.objects.get(object_name) if not obj: return {"error": f"Object {object_name} not found"} # Create material mat_name = f"{object_name}_Material" mat = bpy.data.materials.get(mat_name) or bpy.data.materials.new(mat_name) mat.use_nodes = True # Configure material nodes nodes = mat.node_tree.nodes nodes.clear() # Add Principled BSDF bsdf = nodes.new(type='ShaderNodeBsdfPrincipled') bsdf.inputs['Base Color'].default_value = material_props.get('base_color', [0.5, 0.5, 0.5, 1]) bsdf.inputs['Metallic'].default_value = material_props.get('metallic', 0.5) bsdf.inputs['Roughness'].default_value = material_props.get('roughness', 0.5) # Add output node output = nodes.new(type='ShaderNodeOutputMaterial') mat.node_tree.links.new(bsdf.outputs['BSDF'], output.inputs['Surface']) # Apply to object if obj.data.materials: obj.data.materials[0] = mat else: obj.data.materials.append(mat) return {"material_applied": mat_name} def add_connectors(self, params): """Add connector geometry to component.""" object_name = params.get("object_name") connectors = params.get("connectors", []) obj = bpy.data.objects.get(object_name) if not obj: return {"error": f"Object {object_name} not found"} created_connectors = [] for conn in connectors: # Create connector mesh conn_type = conn.get("type", "generic") pin_count = conn.get("pins", 4) position = conn.get("position", {"x": 0, "y": 0, "z": 0}) # Create connector geometry based on type if conn_type in ["weatherpack", "deutsch"]: # Rectangular connector bpy.ops.mesh.primitive_cube_add( size=0.5, location=(position["x"], position["y"], position["z"]) ) connector = bpy.context.active_object connector.scale = (0.3 * (pin_count / 4), 0.2, 0.15) else: # Cylindrical connector bpy.ops.mesh.primitive_cylinder_add( radius=0.1 * math.sqrt(pin_count / 4), depth=0.2, location=(position["x"], position["y"], position["z"]) ) connector = bpy.context.active_object connector.name = f"{object_name}_conn_{len(created_connectors)}" # Parent to main object connector.parent = obj # Apply dark material for connectors mat = bpy.data.materials.new(f"{connector.name}_Mat") mat.use_nodes = True mat.node_tree.nodes["Principled BSDF"].inputs[0].default_value = (0.1, 0.1, 0.1, 1) connector.data.materials.append(mat) created_connectors.append(connector.name) return {"connectors_added": created_connectors} def capture_viewport(self, params): """Capture viewport screenshot.""" output_path = params.get("output_path", "screenshot.png") # Ensure output directory exists Path(output_path).parent.mkdir(parents=True, exist_ok=True) # Set render settings bpy.context.scene.render.filepath = output_path bpy.context.scene.render.resolution_x = 1920 bpy.context.scene.render.resolution_y = 1080 # Render viewport bpy.ops.render.opengl(write_still=True, view_context=True) return {"path": output_path} def get_scene_info(self, params): """Get information about current scene.""" objects = [] for obj in bpy.data.objects: if obj.type == 'MESH': objects.append({ "name": obj.name, "vertices": len(obj.data.vertices), "faces": len(obj.data.polygons), "location": list(obj.location), "dimensions": list(obj.dimensions) }) return { "objects": objects, "total_objects": len(objects) } def execute_code(self, params): """Execute Python code in Blender context.""" code = params.get("code", "") # Execute in Blender's Python context try: exec(code) return {"executed": True} except Exception as e: return {"error": str(e)} # Global server instance vefrank_server = None class VEFRANK_PT_panel(bpy.types.Panel): """Creates a Panel in the 3D viewport N panel.""" bl_label = "VeFrank MCP Enhanced" bl_idname = "VEFRANK_PT_panel" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "VeFrank" def draw(self, context): layout = self.layout row = layout.row() if context.scene.vefrank_server_running: row.operator("vefrank.stop_server", text="Stop Server", icon='PAUSE') row = layout.row() row.label(text="Server running on localhost:9876", icon='CHECKMARK') else: row.operator("vefrank.start_server", text="Start Server", icon='PLAY') row = layout.row() row.label(text="Server stopped", icon='X') layout.separator() # Quick import section col = layout.column() col.label(text="Quick Import:") col.operator("vefrank.import_component", text="Import Component") # Info section layout.separator() col = layout.column() col.label(text="Components in Scene:") for obj in bpy.data.objects: if obj.type == 'MESH' and obj.name.startswith(('ecu', 'pcm', 'bcm', 'alternator')): col.label(text=f" • {obj.name}", icon='MESH_DATA') class VEFRANK_OT_start_server(bpy.types.Operator): """Start VeFrank MCP server.""" bl_idname = "vefrank.start_server" bl_label = "Start VeFrank Server" def execute(self, context): global vefrank_server if not vefrank_server: vefrank_server = VeFrankServer() if vefrank_server.start(): context.scene.vefrank_server_running = True self.report({'INFO'}, "VeFrank server started") else: self.report({'WARNING'}, "Server already running") return {'FINISHED'} class VEFRANK_OT_stop_server(bpy.types.Operator): """Stop VeFrank MCP server.""" bl_idname = "vefrank.stop_server" bl_label = "Stop VeFrank Server" def execute(self, context): global vefrank_server if vefrank_server: vefrank_server.stop() context.scene.vefrank_server_running = False self.report({'INFO'}, "VeFrank server stopped") return {'FINISHED'} class VEFRANK_OT_import_component(bpy.types.Operator): """Import VeFrank component.""" bl_idname = "vefrank.import_component" bl_label = "Import VeFrank Component" component_type: bpy.props.EnumProperty( name="Component Type", items=[ ('alternator', 'Alternator', 'Automotive alternator'), ('ecu', 'ECU', 'Engine Control Unit'), ('pcm', 'PCM', 'Powertrain Control Module'), ('bcm', 'BCM', 'Body Control Module'), ('sensor', 'Sensor', 'Generic sensor'), ] ) def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def execute(self, context): # Look for OBJ file obj_path = Path(__file__).parent.parent / "assets" / "3d_models" / "automotive" / f"{self.component_type}.obj" if obj_path.exists(): bpy.ops.import_scene.obj(filepath=str(obj_path)) self.report({'INFO'}, f"Imported {self.component_type}") else: self.report({'ERROR'}, f"Component file not found: {obj_path}") return {'FINISHED'} classes = [ VEFRANK_PT_panel, VEFRANK_OT_start_server, VEFRANK_OT_stop_server, VEFRANK_OT_import_component, ] def register(): for cls in classes: bpy.utils.register_class(cls) # Add property to track server state bpy.types.Scene.vefrank_server_running = bpy.props.BoolProperty( name="VeFrank Server Running", default=False ) def unregister(): global vefrank_server if vefrank_server: vefrank_server.stop() for cls in classes: bpy.utils.unregister_class(cls) del bpy.types.Scene.vefrank_server_running if __name__ == "__main__": register()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shdann/mcp-blend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server