blender 开放exec接口的插件 连接ide vscode

要pylance的话有个 fake-bpy-module-latest库

插件:

https://wwamf.lanzouu.com/iOGce3f47nra

https://www.bilibili.com/video/BV1ZwiwBxELf/

有了这个插件,就可以在ide里面为所欲为地调用blender方法了

python 复制代码
正在连接到Blender服务器...

--- 使用 exec 端点 ---
执行成功!
返回结果: obj: Cube
obj: Light
obj: Camera


--- 删除所有物体 ---
(E:\code\my_python_server\micromambavenv) PS E:\code\my_python_server> ^C
(E:\code\my_python_server\micromambavenv) PS E:\code\my_python_server>
(E:\code\my_python_server\micromambavenv) PS E:\code\my_python_server>  e:; cd 'e:\code\my_python_server'; & 'e:\code\my_python_server\micromambavenv\python.exe' 'c:\Users\njsgcs\.vscode\extensions\ms-python.debugpy-2025.18.0-win32-x64\bundled\libs\debugpy\launcher' '59039' '--' 'E:\code\my_python_server\blender_cline\demo.py'
正在连接到Blender服务器...

--- 使用 exec 端点 ---

--- 删除所有物体 ---
删除所有物体成功!
返回结果: 信息: 已删除 3 个物体   


--- 创建立方体 ---
(E:\code\my_python_server\micromambavenv) PS E:\code\my_python_server> ^C
(E:\code\my_python_server\micromambavenv) PS E:\code\my_python_server>
(E:\code\my_python_server\micromambavenv) PS E:\code\my_python_server>  e:; cd 'e:\code\my_python_server'; & 'e:\code\my_python_server\micromambavenv\python.exe' 'c:\Users\njsgcs\.vscode\extensions\ms-python.debugpy-2025.18.0-win32-x64\bundled\libs\debugpy\launcher' '50734' '--' 'E:\code\my_python_server\blender_cline\demo.py'
正在连接到Blender服务器...

--- 使用 exec 端点 ---

--- 删除所有物体 ---

--- 创建立方体 ---
立方体创建成功!
返回结果: Code executed successfully
python 复制代码
import requests
import json

def call_blender_api(endpoint, code):
    """
    调用Blender API执行代码
    """
    url = f"http://localhost:8080{endpoint}"
    
    payload = {
        "code": code
    }
    
    try:
        response = requests.post(url, json=payload)
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"请求错误: {e}")
        return None

def get_all_objects():
    """
    获取当前场景中的所有物体并打印名称
    """
    # 您提供的代码
    code = '''
import bpy

# 获取当前场景中的所有物体
all_objects = bpy.context.scene.objects

# 打印每个物体的名称
result = []
for obj in all_objects:
    obj_info = f"obj: {obj.name}"
    result.append(obj_info)
    print(obj_info)

result
'''
    
    # 使用 /api/exec 端点执行代码块
    response = call_blender_api('/api/exec', code)
    
    if response:
        if response['status'] == 'success':
            print("执行成功!")
            print("返回结果:", response['result'])
        else:
            print("执行失败:", response['message'])
    else:
        print("无法连接到Blender服务器")

def delete_all_objects():
    """
    删除当前场景中的所有物体
    """
    code = '''
import bpy


# 选择所有对象
bpy.ops.object.select_all(action='SELECT')

# 删除选中的对象
bpy.ops.object.delete()
"所有物体已删除"
'''
    
    # 使用 /api/exec 端点执行代码块
    response = call_blender_api('/api/exec', code)
    
    if response:
        if response['status'] == 'success':
            print("删除所有物体成功!")
            print("返回结果:", response['result'])
        else:
            print("删除失败:", response['message'])
    else:
        print("无法连接到Blender服务器")

def create_cube():
    """
    在Blender场景中创建一个立方体
    """
    code = '''
import bpy

# 添加一个立方体
bpy.ops.mesh.primitive_cube_add(
    location=(0, 0, 0)  # 设置立方体的位置
)

# 获取新创建的立方体对象
cube = bpy.context.active_object
cube.name = "MyCube"  # 重命名立方体

f"立方体已创建,名称: {cube.name}"
'''
    
    # 使用 /api/exec 端点执行代码块
    response = call_blender_api('/api/exec', code)
    
    if response:
        if response['status'] == 'success':
            print("立方体创建成功!")
            print("返回结果:", response['result'])
        else:
            print("创建失败:", response['message'])
    else:
        print("无法连接到Blender服务器")


if __name__ == "__main__":
    # 确保Blender服务器正在运行
    print("正在连接到Blender服务器...")
    
    # 方法1: 使用exec端点执行完整代码块(推荐)
    print("\n--- 使用 exec 端点 ---")
    #get_all_objects()
    
    # 示例:删除所有物体
    print("\n--- 删除所有物体 ---")
    #delete_all_objects()

    print("\n--- 创建立方体 ---")
    create_cube()
python 复制代码
# __init__.py
"""
Blender API Server Plugin
Blender API服务器插件,允许通过HTTP API控制Blender
"""

import bpy
import sys
import os
from bpy.props import IntProperty, StringProperty, BoolProperty
from bpy.types import AddonPreferences, Operator, Panel

# 将当前目录添加到Python路径
blender_api_dir = os.path.dirname(__file__)
if blender_api_dir not in sys.path:
    sys.path.insert(0, blender_api_dir)

# 导入API模块
from .api import init_blender_server, stop_blender_server_timer, is_server_running_timer

bl_info = {
    "name": "exec Server",
    "author": "Your Name",
    "version": (1, 0, 0),
    "blender": (2, 80, 0),
    "location": "View3D > Sidebar > API Server",
    "description": "提供HTTP API接口来控制Blender",
    "warning": "",
    "doc_url": "",
    "category": "Development",
}

class BLENDER_API_PT_server_panel(Panel):
    """API服务器控制面板"""
    bl_label = "API Server"
    bl_idname = "BLENDER_API_PT_server_panel"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'API Server'

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        
        # 服务器状态
        col = layout.column()
        if is_server_running_timer():
            col.label(text="Server Status: Running", icon='CHECKMARK')
        else:
            col.label(text="Server Status: Stopped", icon='X')
        
        # 服务器控制按钮
        col.separator()
        row = col.row()
        if is_server_running_timer():
            row.operator("blender_api.stop_server", text="Stop Server", icon='X')
        else:
            row.operator("blender_api.start_server", text="Start Server", icon='PLAY')
        
        # 服务器信息
        col.separator()
        col.label(text="Server Info:")
        col.label(text="Host: localhost")
        col.label(text="Port: 8080")
        col.label(text="Endpoints:")
        col.label(text="  - POST /api/eval")
        col.label(text="  - POST /api/exec") 
        col.label(text="  - POST /api/clear_scene")

class BLENDER_API_OT_start_server(Operator):
    """启动API服务器"""
    bl_idname = "blender_api.start_server"
    bl_label = "Start API Server"
    bl_description = "启动HTTP API服务器"
    
    def execute(self, context):
        success = init_blender_server()
        if success:
            self.report({'INFO'}, "API Server started successfully")
        else:
            self.report({'ERROR'}, "Failed to start API Server")
        return {'FINISHED'}

class BLENDER_API_OT_stop_server(Operator):
    """停止API服务器"""
    bl_idname = "blender_api.stop_server"
    bl_label = "Stop API Server"
    bl_description = "停止HTTP API服务器"
    
    def execute(self, context):
        from .api import stop_blender_server_timer
        stop_blender_server_timer()
        self.report({'INFO'}, "API Server stopped")
        return {'FINISHED'}

classes = (
    BLENDER_API_PT_server_panel,
    BLENDER_API_OT_start_server,
    BLENDER_API_OT_stop_server,
)

def register():
    """注册插件"""
    from .api import register as api_register
    api_register()  # 调用API模块的注册函数
    
    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    """注销插件"""
    from .api import unregister as api_unregister
    api_unregister()  # 调用API模块的注销函数
    
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

if __name__ == "__main__":
    register()
python 复制代码
import bpy
import sys
import json
import ast
from http.server import BaseHTTPRequestHandler
import threading
import socket
from io import StringIO
import select
import errno
from urllib.parse import urlparse, parse_qs

class BlenderAPIHandler:
    def __init__(self, request_data, client_socket, address):
        self.request_data = request_data
        self.client_socket = client_socket
        self.address = address
        
        # 解析请求
        self.parse_request()
    
    def parse_request(self):
        """解析HTTP请求"""
        request_str = self.request_data.decode('utf-8')
        lines = request_str.split('\r\n')
        
        # 解析请求行
        request_line = lines[0]
        parts = request_line.split(' ')
        self.method = parts[0]
        self.path = parts[1]
        
        # 解析头部
        self.headers = {}
        i = 1
        while i < len(lines) and lines[i] != '':
            header_line = lines[i]
            if ':' in header_line:
                key, value = header_line.split(':', 1)
                self.headers[key.strip().lower()] = value.strip()
            i += 1
        
        # 提取请求体
        body_start = request_str.find('\r\n\r\n') + 4
        self.body = request_str[body_start:] if body_start > 3 else ''
    
    def handle_request(self):
        """处理请求"""
        if self.method == 'POST':
            if self.path == '/api/eval':
                self.handle_eval()
            elif self.path == '/api/exec':
                self.handle_exec()
   
            else:
                self.send_response(404, {"status": "error", "message": "Endpoint not found"})
        else:
            self.send_response(405, {"status": "error", "message": "Method not allowed"})
    
    def handle_eval(self):
        """处理eval请求"""
        try:
            request_data = json.loads(self.body)
            code_string = request_data.get('code', '')
            
            if not code_string:
                response = {"status": "error", "message": "No code provided"}
            else:
                # 执行代码并捕获结果
                result = self.execute_blender_code(code_string)
                response = {"status": "success", "result": result}
                
        except json.JSONDecodeError:
            response = {"status": "error", "message": "Invalid JSON format"}
        except Exception as e:
            response = {"status": "error", "message": str(e)}
        
        self.send_response(200, response)
    
    def handle_exec(self):
        """处理exec请求"""
        try:
            request_data = json.loads(self.body)
            code_string = request_data.get('code', '')
            
            if not code_string:
                response = {"status": "error", "message": "No code provided"}
            else:
                # 使用exec执行代码块
                result = self.execute_blender_code_exec(code_string)
                response = {"status": "success", "result": result}
                
        except json.JSONDecodeError:
            response = {"status": "error", "message": "Invalid JSON format"}
        except Exception as e:
            response = {"status": "error", "message": str(e)}
        
        self.send_response(200, response)
    

    
    def execute_blender_code(self, code_string):
        """
        执行Blender代码字符串(用于单个表达式)
        """
        try:
            # 验证语法
            try:
                parsed = ast.parse(code_string, mode='eval')
            except SyntaxError as e:
                raise Exception(f"Syntax error in code: {str(e)}")
            
            # 完全开放的执行环境
            result = eval(code_string)
            return result
            
        except Exception as e:
            raise Exception(f"Error executing code: {str(e)}")
    
    def execute_blender_code_exec(self, code_string):
        """
        使用exec执行Blender代码块
        """
        try:
            # 验证语法
            try:
                parsed = ast.parse(code_string)
            except SyntaxError as e:
                raise Exception(f"Syntax error in code: {str(e)}")
            
            # 捕获print输出
            captured_output = StringIO()
            old_stdout = sys.stdout
            sys.stdout = captured_output
            
            try:
                # 完全开放的执行环境
                exec(code_string)
            finally:
                # 恢复标准输出
                sys.stdout = old_stdout
            
            # 返回捕获的输出
            output = captured_output.getvalue()
            return output if output else "Code executed successfully"
            
        except Exception as e:
            raise Exception(f"Error executing code: {str(e)}")
    
    def send_response(self, status_code, data):
        """发送HTTP响应"""
        try:
            response_body = json.dumps(data, default=str)
            response_headers = [
                f"HTTP/1.1 {status_code} OK",
                "Content-Type: application/json",
                f"Content-Length: {len(response_body)}",
                "Connection: close",
                "\r\n"
            ]
            response_str = "\r\n".join(response_headers) + response_body
            
            self.client_socket.send(response_str.encode())
        except Exception as e:
            print(f"Error sending response: {e}")

class BlenderHTTPServer:
    """
    纯socket实现的HTTP服务器
    """
    def __init__(self, host='localhost', port=8080):
        self.host = host
        self.port = port
        self.socket = None
        self.running = False
        
    def start(self):
        """启动服务器"""
        if self.running:
            print("Server already running")
            return False
            
        try:
            # 创建非阻塞socket
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind((self.host, self.port))
            self.socket.listen(5)
            self.socket.setblocking(False)  # 设置为非阻塞模式
            
            self.running = True
            print(f"Blender API server running on {self.host}:{self.port}")
            
            # 注册定时器函数
            bpy.app.timers.register(self._handle_requests, persistent=True)
            
            return True
        except Exception as e:
            print(f"Failed to start server: {e}")
            return False
    
    def _handle_requests(self):
        """处理HTTP请求的定时器函数"""
        if not self.running:
            return None  # 停止定时器
            
        try:
            # 检查是否有新的连接
            ready, _, _ = select.select([self.socket], [], [], 0)  # 非阻塞检查
            
            if ready:
                client_socket, address = self.socket.accept()
                
                # 设置客户端socket为非阻塞模式
                client_socket.setblocking(False)
                
                # 读取请求数据
                request_data = self._read_request(client_socket)
                
                if request_data:
                    # 创建处理线程
                    thread = threading.Thread(
                        target=self._process_request_data,
                        args=(request_data, client_socket, address)
                    )
                    thread.daemon = True
                    thread.start()
                
        except socket.error as e:
            if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
                print(f"Socket error: {e}")
        except Exception as e:
            print(f"Error handling requests: {e}")
        
        # 每0.1秒调用一次
        return 0.1
    
    def _read_request(self, client_socket):
        """读取HTTP请求数据"""
        try:
            # 首先尝试接收数据
            data = b""
            while True:
                try:
                    chunk = client_socket.recv(1024)
                    if not chunk:
                        break
                    data += chunk
                    
                    # 检查是否接收到了完整的HTTP头部
                    if b'\r\n\r\n' in data:
                        # 解析Content-Length头部
                        header_end = data.find(b'\r\n\r\n')
                        headers = data[:header_end].decode('utf-8')
                        
                        content_length = 0
                        for line in headers.split('\r\n'):
                            if line.lower().startswith('content-length:'):
                                content_length = int(line.split(':')[1].strip())
                                break
                        
                        # 检查是否接收了完整的请求体
                        body_start = header_end + 4
                        if len(data) >= body_start + content_length:
                            break
                except socket.error as e:
                    if e.errno == errno.EWOULDBLOCK or e.errno == errno.EAGAIN:
                        # 非阻塞模式下暂时没有数据
                        break
                    else:
                        raise e
            
            return data
        except Exception as e:
            print(f"Error reading request: {e}")
            return None
    
    def _process_request_data(self, request_data, client_socket, address):  
        """处理请求数据"""  
        try:  
            # 创建处理器  
            handler = BlenderAPIHandler(request_data, client_socket, address)  
            
            # 在主线程上调度执行  
            def execute_on_main_thread():  
                handler.handle_request()  
                return None  # 停止定时器  
            
            bpy.app.timers.register(execute_on_main_thread, first_interval=0.0)  
            
        except Exception as e:  
            print(f"Error processing request: {e}")
    
    def stop(self):
        """停止服务器"""
        self.running = False
        if self.socket:
            self.socket.close()
            self.socket = None
        print("Server stopped")

# 全局服务器实例
blender_timer_server = None

def start_blender_server_with_timer(port=8080):
    """启动使用Blender定时器的服务器"""
    global blender_timer_server
    if blender_timer_server is None:
        blender_timer_server = BlenderHTTPServer(port=port)
    return blender_timer_server.start()

def stop_blender_server_timer():
    """停止使用定时器的服务器"""
    global blender_timer_server
    if blender_timer_server:
        blender_timer_server.stop()
        blender_timer_server = None

def is_server_running_timer():
    """检查使用定时器的服务器是否正在运行"""
    global blender_timer_server
    return blender_timer_server is not None and blender_timer_server.running

def init_blender_server():
    """在Blender中初始化服务器(使用定时器方式)"""
    success = start_blender_server_with_timer(8080)
    if success:
        print("Blender server initialized with timer and running in background")
        return True
    else:
        print("Failed to initialize Blender server")
        return False

# Blender插件兼容代码
def register():
    """注册到Blender(用于插件)"""
    init_blender_server()

def unregister():
    """从Blender取消注册(用于插件)"""
    stop_blender_server_timer()

if __name__ == "__main__":
    # 直接运行时,模拟Blender环境
    print("Starting server in timer mode...")
    start_blender_server_with_timer(8080)
    
    # 保持运行
    try:
        import time
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Shutting down...")
        stop_blender_server_timer()
相关推荐
CG_MAGIC2 天前
多软件协同工作流:ZBrush+SP+Blender角色资产全流程解析
3d·blender·zbrush·建模教程·渲云渲染·渲云
成都渲染101云渲染66662 天前
5090 显卡云端上线!Blender / Maya / UE5 渲染速度再提升,云渲染成主流选择
ue5·blender·maya
哎呦哥哥和巨炮叔叔8 天前
2026 年 Blender 与 Maya 对比分析:动画与渲染制作该如何选择?
blender·云渲染·maya·三维动画·blender vs maya·动画制作渲染·cg 动画
成都渲染101云渲染666610 天前
Blender 在国内到底应用怎么样?
blender
成都渲染101云渲染666610 天前
Maya 正在被 Blender 取代吗?从实际项目说点不太好听的实话
3d·blender·maya
i学长的猫12 天前
blender
blender
CG_MAGIC12 天前
Blender制作水世界烟雾休息
blender·贴图·建模教程·渲云渲染
j_jiajia18 天前
Blender安装教程
blender
XR101yqm122120 天前
从 DCC 工具生态视角,解析 Blender 在国内企业的应用困局
blender
王伯爵21 天前
blender常用快捷键大全
blender