要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()