import bpy
import threading
import http.server
import socketserver
import webbrowser
import os
import tempfile
import json
import time
from pathlib import Path
class BlenderWebServer:
def __init__(self, port=8000):
self.port = port
self.server = None
self.thread = None
self.html_file = None
def create_html_content(self):
"""创建简化的HTML页面内容"""
return """
Blender 简单控制面板
就绪 - 等待操作
"""
def start_server(self):
"""启动HTTP服务器"""
try:
# 创建HTML文件
temp_dir = tempfile.gettempdir()
self.html_file = os.path.join(temp_dir, "blender_web_panel.html")
with open(self.html_file, 'w', encoding='utf-8') as f:
f.write(self.create_html_content())
# 创建自定义HTTP处理器
class BlenderHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/':
# 修复:直接使用服务器的html_file属性
self.path = self.server.html_file
elif self.path.startswith('/api/'):
self.handle_api_request()
return
return http.server.SimpleHTTPRequestHandler.do_GET(self)
def do_POST(self):
if self.path.startswith('/api/'):
self.handle_api_request()
return
return http.server.SimpleHTTPRequestHandler.do_POST(self)
def handle_api_request(self):
"""处理API请求"""
try:
if self.path == '/api/scene_info':
self.send_scene_info()
elif self.path == '/api/add_cube':
self.handle_add_cube()
else:
self.send_error(404, "API not found")
except Exception as e:
self.send_error(500, str(e))
def send_json_response(self, data):
"""发送JSON响应"""
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header(
'Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header(
'Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
self.wfile.write(json.dumps(
data, ensure_ascii=False).encode('utf-8'))
def send_scene_info(self):
"""发送场景信息"""
try:
scene = bpy.context.scene
data = {
'scene_name': scene.name,
'object_count': len(bpy.data.objects),
'material_count': len(bpy.data.materials),
'mesh_count': len(bpy.data.meshes),
'blender_version': bpy.app.version_string
}
self.send_json_response(data)
except Exception as e:
self.send_json_response({'error': str(e)})
def handle_add_cube(self):
"""处理添加立方体请求"""
try:
bpy.ops.mesh.primitive_cube_add()
self.send_json_response({'message': '立方体已添加'})
except Exception as e:
self.send_json_response({'error': str(e)})
# 创建自定义服务器类
class BlenderTCPServer(socketserver.TCPServer):
def __init__(self, server_address, RequestHandlerClass, html_file):
self.html_file = html_file
super().__init__(server_address, RequestHandlerClass)
# 启动服务器
with BlenderTCPServer(("", self.port), BlenderHTTPRequestHandler, self.html_file) as httpd:
self.server = httpd
print(f"🎨 Blender Web服务器启动在端口 {self.port}")
print(f"🌐 访问地址: http://localhost:{self.port}")
httpd.serve_forever()
except Exception as e:
print(f"❌ 服务器启动失败: {e}")
def start(self):
"""在后台线程中启动服务器"""
self.thread = threading.Thread(target=self.start_server, daemon=True)
self.thread.start()
def stop(self):
"""停止服务器"""
if self.server:
self.server.shutdown()
self.server.server_close()
# 全局服务器实例
web_server = None
def start_web_server(port=8000):
"""启动Web服务器"""
global web_server
if web_server is None:
web_server = BlenderWebServer(port)
web_server.start()
print("✅ Web服务器已启动")
return web_server
def open_web_panel():
"""在Blender中打开Web面板"""
# 启动服务器
server = start_web_server()
# 检查Blender版本是否支持WEB_BROWSER
try:
# 尝试在Blender中打开Web浏览器面板
bpy.ops.screen.area_split(direction='VERTICAL', factor=0.5)
# 检查可用的区域类型
available_areas = ('EMPTY', 'VIEW_3D', 'IMAGE_EDITOR', 'NODE_EDITOR',
'SEQUENCE_EDITOR', 'CLIP_EDITOR', 'DOPESHEET_EDITOR',
'GRAPH_EDITOR', 'NLA_EDITOR', 'TEXT_EDITOR', 'CONSOLE',
'INFO', 'TOPBAR', 'STATUSBAR', 'OUTLINER', 'PROPERTIES',
'FILE_BROWSER', 'SPREADSHEET', 'PREFERENCES')
# 尝试使用TEXT_EDITOR作为替代
for area in bpy.context.screen.areas:
if area.type == 'INFO':
area.type = 'TEXT_EDITOR'
# 创建一个简单的HTML显示
text_editor = area.spaces[0]
text_editor.text = bpy.data.texts.new("Web Panel")
text_editor.text.write(f"""
Blender 简单控制面板
服务器已启动在端口 {server.port}
访问地址: http://localhost:{server.port}
功能:
- 添加立方体
- 查看场景信息
请在浏览器中打开上述地址使用Web界面。
""")
break
print(f"🌐 Web面板信息已在文本编辑器中显示")
except Exception as e:
print(f"❌ 在Blender中打开Web面板失败: {e}")
# 如果失败,尝试在外部浏览器中打开
try:
webbrowser.open(f"http://localhost:{server.port}")
print(f" 已在外部浏览器中打开Web面板")
except Exception as e2:
print(f"❌ 打开外部浏览器失败: {e2}")
# Blender操作符类
class StartWebServerOperator(bpy.types.Operator):
bl_idname = "wm.start_web_server"
bl_label = "启动Web服务器"
bl_description = "启动Blender Web控制服务器"
def execute(self, context):
start_web_server()
self.report({'INFO'}, f"Web服务器已启动在端口8000")
return {'FINISHED'}
class OpenWebPanelOperator(bpy.types.Operator):
bl_idname = "wm.open_web_panel"
bl_label = "打开Web面板"
bl_description = "在Blender中打开Web控制面板"
def execute(self, context):
open_web_panel()
return {'FINISHED'}
class StopWebServerOperator(bpy.types.Operator):
bl_idname = "wm.stop_web_server"
bl_label = "停止Web服务器"
bl_description = "停止Blender Web控制服务器"
def execute(self, context):
global web_server
if web_server:
web_server.stop()
web_server = None
self.report({'INFO'}, "Web服务器已停止")
else:
self.report({'WARNING'}, "Web服务器未运行")
return {'FINISHED'}
# 菜单类
class WebPanelMenu(bpy.types.Menu):
bl_idname = "VIEW3D_MT_web_panel"
bl_label = "Web控制面板"
def draw(self, context):
layout = self.layout
layout.operator("wm.start_web_server")
layout.operator("wm.open_web_panel")
layout.operator("wm.stop_web_server")
# 面板类
class WebPanelPanel(bpy.types.Panel):
bl_label = "Web控制面板"
bl_idname = "VIEW3D_PT_web_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Web控制'
def draw(self, context):
layout = self.layout
# 服务器状态
global web_server
if web_server and web_server.server:
layout.label(text="✅ 服务器运行中", icon='CHECKMARK')
layout.label(text=f"端口: {web_server.port}")
else:
layout.label(text="❌ 服务器未运行", icon='ERROR')
# 控制按钮
col = layout.column(align=True)
col.operator("wm.start_web_server", text="启动服务器", icon='PLAY')
col.operator("wm.open_web_panel", text="打开面板", icon='URL')
col.operator("wm.stop_web_server", text="停止服务器", icon='X')
# 注册函数
def register():
bpy.utils.register_class(StartWebServerOperator)
bpy.utils.register_class(OpenWebPanelOperator)
bpy.utils.register_class(StopWebServerOperator)
bpy.utils.register_class(WebPanelMenu)
bpy.utils.register_class(WebPanelPanel)
# 添加到视图菜单
def draw_menu(self, context):
layout = self.layout
layout.menu("VIEW3D_MT_web_panel")
bpy.types.VIEW3D_MT_view.append(draw_menu)
def unregister():
bpy.utils.unregister_class(StartWebServerOperator)
bpy.utils.unregister_class(OpenWebPanelOperator)
bpy.utils.unregister_class(StopWebServerOperator)
bpy.utils.unregister_class(WebPanelMenu)
bpy.utils.unregister_class(WebPanelPanel)
# 从视图菜单移除
bpy.types.VIEW3D_MT_view.remove(draw_menu)
# 主执行函数
def main():
"""主函数 - 一键启动Web服务器和面板"""
print("🚀 启动Blender 简单Web控制面板...")
# 启动服务器
server = start_web_server()
# 等待服务器启动
time.sleep(1)
# 打开Web面板
open_web_panel()
print("✅ Blender 简单Web控制面板启动完成!")
print(f"🌐 访问地址: http://localhost:{server.port}")
print(" 提示: 可以在3D视图的侧边栏找到'Web控制'面板")
# 注册所有类
register()
# 如果直接运行脚本,自动启动
if __name__ == "__main__":
main()