blenderpython/suw_auto_client.py

523 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SUW 自动客户端模块
用于在 Blender 插件启动时自动启动 SUW 客户端
"""
import sys
import os
import time
import threading
import datetime
import traceback
import socket
from typing import Dict, Any, Optional, List
import logging
# 配置日志
logger = logging.getLogger(__name__)
# 尝试导入 Blender 模块
try:
import bpy
BLENDER_AVAILABLE = True
except ImportError:
BLENDER_AVAILABLE = False
logger.warning("⚠️ Blender模块不可用")
# 尝试导入 SUWood 模块
try:
from . import suw_core
from . import suw_client
SUWOOD_AVAILABLE = True
logger.info("✅ SUWood模块导入成功")
except ImportError as e:
SUWOOD_AVAILABLE = False
logger.error(f"❌ SUWood模块导入失败: {e}")
class SUWAutoClient:
"""SUW 自动客户端 - 集成到 Blender 插件中"""
def __init__(self):
"""初始化 SUW 自动客户端"""
self.client = None
self.is_running = False
self.command_count = 0
self.success_count = 0
self.fail_count = 0
self.last_check_time = None
self.start_time = None
self.command_dispatcher = None
self.client_thread = None
self.auto_start_enabled = True
def initialize_system(self):
"""初始化 SUW 系统"""
try:
logger.info("🔧 初始化 SUW 自动客户端系统...")
if not SUWOOD_AVAILABLE:
logger.error("❌ SUWood模块不可用无法初始化客户端")
return False
# 导入客户端模块
logger.info("📡 导入客户端模块...")
from .suw_client import SUWClient
# 创建客户端实例
logger.info("🔗 创建客户端连接...")
self.client = SUWClient()
# 检查连接状态
if self.client.sock is None:
logger.error("❌ 客户端连接失败")
return False
logger.info("✅ 客户端连接成功")
# 测试连接
logger.info("🔗 测试服务器连接...")
test_result = self._test_connection()
if test_result:
logger.info("✅ 服务器连接正常")
# 初始化命令分发器
logger.info("🔧 初始化命令分发器...")
if self._init_command_dispatcher():
logger.info("✅ 命令分发器初始化完成")
return True
else:
logger.error("❌ 命令分发器初始化失败")
return False
else:
logger.error("❌ 服务器连接测试失败")
return False
except Exception as e:
logger.error(f"❌ SUW 自动客户端初始化失败: {e}")
traceback.print_exc()
return False
def _init_command_dispatcher(self):
"""初始化命令分发器"""
try:
logger.info("📦 导入管理器模块...")
# 导入各个管理器
from .suw_core.data_manager import get_data_manager
from .suw_core.material_manager import MaterialManager
from .suw_core.part_creator import PartCreator
from .suw_core.machining_manager import MachiningManager
from .suw_core.selection_manager import SelectionManager
from .suw_core.deletion_manager import DeletionManager
from .suw_core.hardware_manager import HardwareManager
from .suw_core.door_drawer_manager import get_door_drawer_manager
from .suw_core.dimension_manager import get_dimension_manager
from .suw_core.command_dispatcher import get_command_dispatcher
logger.info("✅ 所有管理器模块导入完成")
# 获取管理器实例
logger.info("🔧 获取管理器实例...")
data_manager = get_data_manager()
material_manager = MaterialManager()
part_creator = PartCreator()
machining_manager = MachiningManager()
selection_manager = SelectionManager()
deletion_manager = DeletionManager()
hardware_manager = HardwareManager()
door_drawer_manager = get_door_drawer_manager()
dimension_manager = get_dimension_manager()
logger.info("✅ 管理器实例获取完成")
# 获取命令分发器
self.command_dispatcher = get_command_dispatcher()
logger.info(f"✅ 命令分发器获取完成: {type(self.command_dispatcher)}")
# 测试命令分发器
if self.command_dispatcher:
logger.info("✅ 命令分发器测试: 已初始化")
# 测试一个简单的命令
try:
test_result = self.command_dispatcher.dispatch_command(
"test", {})
logger.info(f"🔧 命令分发器测试结果: {test_result}")
except Exception as e:
logger.info(f"🔧 命令分发器测试异常(正常): {e}")
else:
logger.error("❌ 命令分发器获取失败")
return False
return True
except Exception as e:
logger.error(f"❌ 命令分发器初始化失败: {e}")
logger.error(f"❌ 异常详情: {traceback.format_exc()}")
return False
def _test_connection(self):
"""测试连接"""
try:
if not self.client or not self.client.sock:
return False
# 发送一个简单的测试消息
test_msg = '{"cmd": "test", "params": {"from": "blender_plugin"}}'
if self.client.send_msg(0x01, test_msg):
logger.info("✅ 测试消息发送成功")
return True
else:
logger.error("❌ 测试消息发送失败")
return False
except Exception as e:
logger.error(f"❌ 连接测试失败: {e}")
return False
def start_client(self):
"""启动客户端"""
try:
logger.info("🌐 启动 SUW 自动客户端...")
if not self.client:
logger.error("❌ 客户端未初始化")
return False
self.is_running = True
self.start_time = datetime.datetime.now()
self.last_check_time = self.start_time
# 启动后台线程
logger.info("🧵 启动客户端后台线程...")
self.client_thread = threading.Thread(
target=self._client_loop, daemon=True)
self.client_thread.start()
logger.info("✅ SUW 自动客户端启动成功!")
return True
except Exception as e:
logger.error(f"❌ 客户端启动失败: {e}")
traceback.print_exc()
return False
def _client_loop(self):
"""客户端主循环"""
logger.info("🔄 进入客户端监听循环...")
consecutive_errors = 0
max_consecutive_errors = 10
try:
if not self.client or not self.client.sock:
logger.error("❌ 无法连接到SUWood服务器")
return
logger.info("✅ 已连接到SUWood服务器")
logger.info("🎯 开始监听命令...")
while self.is_running:
try:
# 获取命令
from .suw_client import get_cmds
commands = get_cmds()
if commands and len(commands) > 0:
logger.info(f"\n📨 收到 {len(commands)} 个命令")
consecutive_errors = 0 # 重置错误计数
# 处理每个命令
for cmd in commands:
if not self.is_running:
break
self._process_command(cmd)
# 短暂休眠避免过度占用CPU
time.sleep(0.1)
except KeyboardInterrupt:
logger.info("🛑 收到中断信号,退出客户端循环")
break
except Exception as e:
consecutive_errors += 1
logger.error(
f"❌ 客户端循环异常 ({consecutive_errors}/{max_consecutive_errors}): {e}")
if consecutive_errors >= max_consecutive_errors:
logger.error("💀 连续错误过多,退出客户端循环")
break
# 错误后稍长休眠
time.sleep(1)
except Exception as e:
logger.error(f"❌ 客户端线程异常: {e}")
logger.info("🔄 客户端循环结束")
def check_commands(self):
"""手动检查命令"""
try:
if not self.is_running or not self.client:
return # 静默返回,不输出日志
# 使用get_cmds函数检查命令添加超时保护
from .suw_client import get_cmds
try:
# 设置socket超时避免阻塞
if self.client and self.client.sock:
self.client.sock.settimeout(0.3) # 300ms超时
cmds = get_cmds()
# 检查返回值是否为None或空列表
if cmds is None:
cmds = []
elif not isinstance(cmds, list):
cmds = []
# 恢复socket超时设置
if self.client and self.client.sock:
self.client.sock.settimeout(None)
if cmds and len(cmds) > 0:
# 只有在有命令时才输出日志
logger.info(
f"\n 手动检查命令... (上次检查: {self.last_check_time.strftime('%H:%M:%S') if self.last_check_time else '从未'})")
logger.info(f" 收到 {len(cmds)} 个命令")
logger.info(
f" 命令分发器状态: {'✅ 已初始化' if self.command_dispatcher else '❌ 未初始化'}")
# 参考blender_suw_core_independent.py的处理方式
for i, cmd in enumerate(cmds):
logger.info(f"🔍 处理第 {i+1}/{len(cmds)} 个命令")
self._process_command(cmd)
except socket.timeout:
# 超时是正常的,静默处理
pass
except Exception as e:
logger.error(f"❌ 获取命令失败: {e}")
self.last_check_time = datetime.datetime.now()
except Exception as e:
logger.error(f"❌ 检查命令失败: {e}")
def _process_command(self, cmd_data):
"""处理命令"""
from datetime import datetime
try:
self.command_count += 1
logger.info(
f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}")
logger.info(f"🎯 处理命令 #{self.command_count}: {cmd_data}")
# 解析命令数据
command_type = None
command_data = {}
# 处理不同的命令格式
if isinstance(cmd_data, dict):
if 'cmd' in cmd_data and 'data' in cmd_data:
# 格式: {'cmd': 'set_cmd', 'data': {'cmd': 'c04', ...}}
command_type = cmd_data['data'].get('cmd')
command_data = cmd_data['data']
elif 'cmd' in cmd_data:
# 格式: {'cmd': 'c04', ...}
command_type = cmd_data['cmd']
command_data = cmd_data
else:
logger.warning(f"⚠️ 无法解析命令格式: {cmd_data}")
return
if command_type:
logger.info(f"🔧 执行命令: {command_type}")
# 使用命令分发器执行命令 - 简化处理
if self.command_dispatcher:
try:
result = self.command_dispatcher.dispatch_command(
command_type, command_data)
if result:
logger.info(f"✅ 命令 {command_type} 执行成功")
self.success_count += 1
else:
logger.error(f"❌ 命令 {command_type} 执行失败")
self.fail_count += 1
except Exception as e:
logger.error(f"❌ 命令 {command_type} 执行异常: {e}")
self.fail_count += 1
else:
logger.warning("⚠️ 命令分发器未初始化,只记录命令")
self.success_count += 1
logger.info("") # 空行分隔
else:
logger.warning(f"⚠️ 无法识别命令类型: {cmd_data}")
self.fail_count += 1
logger.info("") # 空行分隔
except Exception as e:
logger.error(f"❌ 命令处理失败: {e}")
self.fail_count += 1
logger.info("") # 空行分隔
traceback.print_exc()
def print_status(self):
"""打印状态"""
if not self.is_running:
logger.info("❌ 客户端未运行")
return
runtime = datetime.datetime.now(
) - self.start_time if self.start_time else datetime.timedelta(0)
success_rate = (self.success_count / self.command_count *
100) if self.command_count > 0 else 0
thread_alive = self.client_thread.is_alive() if self.client_thread else False
logger.info("📊 SUW 自动客户端状态:")
logger.info(f"🔄 运行状态: {'✅ 运行中' if self.is_running else '❌ 已停止'}")
logger.info(f"🧵 线程状态: {'✅ 活跃' if thread_alive else '❌ 停止'}")
logger.info(f"⏱️ 运行时间: {runtime}")
logger.info(
f"📈 命令统计: 总计: {self.command_count}, 成功: {self.success_count}, 失败: {self.fail_count}, 成功率: {success_rate:.1f}%")
logger.info(
f"🔍 最后检查: {self.last_check_time.strftime('%H:%M:%S') if self.last_check_time else '从未'}")
logger.info(
f"🎯 命令分发器: {'✅ 已初始化' if self.command_dispatcher else '❌ 未初始化'}")
def stop_client(self):
"""停止客户端"""
try:
logger.info("🛑 停止 SUW 自动客户端...")
self.is_running = False
if self.client_thread and self.client_thread.is_alive():
self.client_thread.join(timeout=2)
if self.client and self.client.sock:
try:
self.client.sock.close()
except:
pass
logger.info("✅ 客户端已停止")
except Exception as e:
logger.error(f"❌ 停止客户端失败: {e}")
traceback.print_exc()
# ==================== 全局客户端实例 ====================
suw_auto_client = SUWAutoClient()
# ==================== 便捷函数 ====================
def start_suw_auto_client():
"""启动 SUW 自动客户端"""
logger.info("🚀 启动 SUW 自动客户端...")
if suw_auto_client.initialize_system():
if suw_auto_client.start_client():
logger.info("🎉 SUW 自动客户端启动成功!")
return True
else:
logger.error("❌ 客户端启动失败")
return False
else:
logger.error("❌ 系统初始化失败")
return False
def stop_suw_auto_client():
"""停止 SUW 自动客户端"""
suw_auto_client.stop_client()
def check_suw_commands():
"""检查 SUW 命令"""
suw_auto_client.check_commands()
def print_suw_status():
"""打印 SUW 状态"""
suw_auto_client.print_status()
# ==================== Blender 集成函数 ====================
def register_suw_auto_client():
"""注册 SUW 自动客户端到 Blender"""
try:
if not BLENDER_AVAILABLE:
logger.error("❌ Blender环境不可用无法注册SUW自动客户端")
return False
if not SUWOOD_AVAILABLE:
logger.error("❌ SUWood模块不可用无法注册SUW自动客户端")
return False
# 启动 SUW 自动客户端
if start_suw_auto_client():
logger.info("✅ SUW 自动客户端注册成功")
return True
else:
logger.error("❌ SUW 自动客户端注册失败")
return False
except Exception as e:
logger.error(f"❌ SUW 自动客户端注册失败: {e}")
return False
def unregister_suw_auto_client():
"""注销 SUW 自动客户端"""
try:
stop_suw_auto_client()
logger.info("✅ SUW 自动客户端注销成功")
except Exception as e:
logger.error(f"❌ SUW 自动客户端注销失败: {e}")
# ==================== 定时器回调函数 ====================
def suw_client_timer():
"""SUW 客户端定时器回调函数"""
# 暂时禁用定时器避免阻塞Blender主线程
return None # 返回None停止定时器
def start_suw_client_timer():
"""启动 SUW 客户端定时器"""
try:
if BLENDER_AVAILABLE:
# 注册定时器
bpy.app.timers.register(suw_client_timer)
logger.info("✅ SUW 客户端定时器启动成功")
else:
logger.warning("⚠️ Blender环境不可用无法启动定时器")
except Exception as e:
logger.error(f"❌ 启动SUW客户端定时器失败: {e}")
def stop_suw_client_timer():
"""停止 SUW 客户端定时器"""
try:
if BLENDER_AVAILABLE:
# 注销定时器
bpy.app.timers.unregister(suw_client_timer)
logger.info("✅ SUW 客户端定时器停止成功")
else:
logger.warning("⚠️ Blender环境不可用无法停止定时器")
except Exception as e:
logger.error(f"❌ 停止SUW客户端定时器失败: {e}")