#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SUW Implementation - Python翻译版本 原文件: SUWImpl.rb (2019行) 用途: 核心实现类,SUWood的主要功能 翻译进度: 完整实现 - 对应Ruby版本所有功能 内存管理优化: 应用 Blender Python API 最佳实践 """ import re import math import logging import time import gc import weakref import threading import queue from typing import Optional, Any, Dict, List, Tuple, Union, Callable from contextlib import contextmanager # 设置日志 logger = logging.getLogger(__name__) # 尝试相对导入,失败则使用绝对导入 try: from .suw_constants import SUWood except ImportError: try: from suw_constants import SUWood except ImportError: # 如果都找不到,创建一个基本的存根 class SUWood: @staticmethod def suwood_path(version): return "." try: import bpy import mathutils import bmesh BLENDER_AVAILABLE = True except ImportError: BLENDER_AVAILABLE = False print("⚠️ Blender API 不可用,使用基础几何类") # 创建存根mathutils模块 class MockMathutils: class Vector: def __init__(self, vec): self.x, self.y, self.z = vec[:3] if len( vec) >= 3 else (vec + [0, 0])[:3] def normalized(self): return self def dot(self, other): return 0 class Matrix: @staticmethod def Scale(scale, size, axis): return MockMathutils.Matrix() @staticmethod def Translation(vec): return MockMathutils.Matrix() @staticmethod def Rotation(angle, size): return MockMathutils.Matrix() def __matmul__(self, other): return MockMathutils.Matrix() mathutils = MockMathutils() # ==================== 内存管理核心类 ==================== class BlenderMemoryManager: """Blender内存管理器 - 修复弱引用问题""" def __init__(self): # 改用普通集合和字典来跟踪对象,而不是弱引用 self.tracked_objects = set() # 存储对象名称而不是对象本身 self.tracked_meshes = set() # 存储网格名称 self.tracked_images = set() # 存储图像名称 self.tracked_materials = set() # 存储材质名称 self.tracked_collections = set() # 存储集合名称 self.cleanup_interval = 100 self.operation_count = 0 self.last_cleanup = time.time() self.max_memory_mb = 2048 self._cleanup_lock = threading.Lock() def register_object(self, obj): """注册对象到内存管理器 - 修复版本""" if obj is None or not BLENDER_AVAILABLE: return try: with self._cleanup_lock: # 根据对象类型分别处理 if hasattr(obj, 'name'): obj_name = obj.name # 根据对象类型存储到不同的集合 if hasattr(obj, 'type'): # Blender Object self.tracked_objects.add(obj_name) elif str(type(obj)).find('Material') != -1: # Material self.tracked_materials.add(obj_name) elif str(type(obj)).find('Mesh') != -1: # Mesh self.tracked_meshes.add(obj_name) elif str(type(obj)).find('Image') != -1: # Image self.tracked_images.add(obj_name) elif str(type(obj)).find('Collection') != -1: # Collection self.tracked_collections.add(obj_name) else: self.tracked_objects.add(obj_name) self.operation_count += 1 # 定期清理 if self.should_cleanup(): self.cleanup_orphaned_data() except Exception as e: # 静默处理,不输出错误日志 pass def register_mesh(self, mesh): """注册网格到内存管理器 - 修复版本""" if mesh is None or not BLENDER_AVAILABLE: return try: with self._cleanup_lock: if hasattr(mesh, 'name'): self.tracked_meshes.add(mesh.name) self.operation_count += 1 except Exception as e: # 静默处理 pass def register_image(self, image): """注册图像到内存管理器 - 修复版本""" if image is None or not BLENDER_AVAILABLE: return try: with self._cleanup_lock: if hasattr(image, 'name'): self.tracked_images.add(image.name) self.operation_count += 1 except Exception as e: # 静默处理 pass def should_cleanup(self): """检查是否需要清理""" return (self.operation_count >= self.cleanup_interval or time.time() - self.last_cleanup > 300) # 5分钟强制清理 def cleanup_orphaned_data(self): """【重新启用】智能清理孤立数据块 - 优化版本,避免冲突但保持清理效果""" if not BLENDER_AVAILABLE: return 0 cleanup_count = 0 try: with self._cleanup_lock: # 【策略1】智能清理 - 只清理明确安全的数据 logger.debug("🧹 开始智能内存清理...") # 【安全清理1】清理无效引用 invalid_objects = [] invalid_meshes = [] invalid_materials = [] invalid_images = [] # 清理无效的对象引用 for obj_name in list(self.tracked_objects): try: if obj_name not in bpy.data.objects: invalid_objects.append(obj_name) except: invalid_objects.append(obj_name) # 清理无效的网格引用 for mesh_name in list(self.tracked_meshes): try: if mesh_name not in bpy.data.meshes: invalid_meshes.append(mesh_name) except: invalid_meshes.append(mesh_name) # 清理无效的材质引用 for mat_name in list(self.tracked_materials): try: if mat_name not in bpy.data.materials: invalid_materials.append(mat_name) except: invalid_materials.append(mat_name) # 清理无效的图像引用 for img_name in list(self.tracked_images): try: if img_name not in bpy.data.images: invalid_images.append(img_name) except: invalid_images.append(img_name) # 更新跟踪列表 for obj_name in invalid_objects: self.tracked_objects.discard(obj_name) for mesh_name in invalid_meshes: self.tracked_meshes.discard(mesh_name) for mat_name in invalid_materials: self.tracked_materials.discard(mat_name) for img_name in invalid_images: self.tracked_images.discard(img_name) reference_cleanup_count = len( invalid_objects) + len(invalid_meshes) + len(invalid_materials) + len(invalid_images) # 【安全清理2】清理无用户的材质(安全) materials_to_remove = [] for material_name in list(self.tracked_materials): try: if material_name in bpy.data.materials: material = bpy.data.materials[material_name] if material.users == 0: materials_to_remove.append(material_name) except Exception as e: logger.debug(f"检查材质 {material_name} 时出错: {e}") self.tracked_materials.discard(material_name) # 批量删除无用材质 for material_name in materials_to_remove: try: if material_name in bpy.data.materials: material = bpy.data.materials[material_name] bpy.data.materials.remove(material, do_unlink=True) cleanup_count += 1 self.tracked_materials.discard(material_name) except Exception as e: logger.debug(f"删除材质数据失败: {e}") self.tracked_materials.discard(material_name) # 【安全清理3】清理无用户的图像(安全) images_to_remove = [] for image_name in list(self.tracked_images): try: if image_name in bpy.data.images: image = bpy.data.images[image_name] if image.users == 0: images_to_remove.append(image_name) except Exception as e: logger.debug(f"检查图像 {image_name} 时出错: {e}") self.tracked_images.discard(image_name) # 批量删除无用图像 for image_name in images_to_remove: try: if image_name in bpy.data.images: image = bpy.data.images[image_name] bpy.data.images.remove(image, do_unlink=True) cleanup_count += 1 self.tracked_images.discard(image_name) except Exception as e: logger.debug(f"删除图像数据失败: {e}") self.tracked_images.discard(image_name) # 【安全清理4】清理无用户的网格(谨慎) meshes_to_remove = [] for mesh_name in list(self.tracked_meshes): try: if mesh_name in bpy.data.meshes: mesh = bpy.data.meshes[mesh_name] # 只清理明确无用户的网格 if mesh.users == 0: meshes_to_remove.append(mesh_name) except Exception as e: logger.debug(f"检查网格 {mesh_name} 时出错: {e}") self.tracked_meshes.discard(mesh_name) # 批量删除无用网格 for mesh_name in meshes_to_remove: try: if mesh_name in bpy.data.meshes: mesh = bpy.data.meshes[mesh_name] bpy.data.meshes.remove(mesh, do_unlink=True) cleanup_count += 1 self.tracked_meshes.discard(mesh_name) except Exception as e: logger.debug(f"删除网格数据失败: {e}") self.tracked_meshes.discard(mesh_name) # 更新清理时间 self.last_cleanup = time.time() total_cleaned = reference_cleanup_count + cleanup_count if total_cleaned > 0: logger.info( f"🧹 智能清理完成: {reference_cleanup_count} 个无效引用, {cleanup_count} 个数据块") else: logger.debug("🧹 智能清理完成: 无需清理") except Exception as e: logger.error(f"智能内存清理过程中发生错误: {e}") import traceback traceback.print_exc() return cleanup_count def _cleanup_tracked_references(self): """清理跟踪集合中的无效引用""" try: # 清理无效的对象引用 valid_objects = set() for obj_name in self.tracked_objects: if obj_name in bpy.data.objects: valid_objects.add(obj_name) self.tracked_objects = valid_objects # 清理无效的网格引用 valid_meshes = set() for mesh_name in self.tracked_meshes: if mesh_name in bpy.data.meshes: valid_meshes.add(mesh_name) self.tracked_meshes = valid_meshes # 清理无效的材质引用 valid_materials = set() for mat_name in self.tracked_materials: if mat_name in bpy.data.materials: valid_materials.add(mat_name) self.tracked_materials = valid_materials # 清理无效的图像引用 valid_images = set() for img_name in self.tracked_images: if img_name in bpy.data.images: valid_images.add(img_name) self.tracked_images = valid_images # 清理无效的集合引用 valid_collections = set() for col_name in self.tracked_collections: if col_name in bpy.data.collections: valid_collections.add(col_name) self.tracked_collections = valid_collections except Exception as e: logger.warning(f"清理跟踪引用失败: {e}") def get_memory_stats(self) -> Dict[str, int]: """获取内存统计信息""" try: with self._cleanup_lock: return { 'tracked_objects': len(self.tracked_objects), 'tracked_meshes': len(self.tracked_meshes), 'tracked_materials': len(self.tracked_materials), 'tracked_images': len(self.tracked_images), 'tracked_collections': len(self.tracked_collections), 'operation_count': self.operation_count, 'blender_objects': len(bpy.data.objects) if BLENDER_AVAILABLE else 0, 'blender_meshes': len(bpy.data.meshes) if BLENDER_AVAILABLE else 0, 'blender_materials': len(bpy.data.materials) if BLENDER_AVAILABLE else 0, 'blender_images': len(bpy.data.images) if BLENDER_AVAILABLE else 0, } except Exception as e: logger.error(f"获取内存统计失败: {e}") return {} def force_cleanup(self): """强制清理""" try: with self._cleanup_lock: self.last_cleanup = 0 # 重置时间以强制清理 self.cleanup_orphaned_data() except Exception as e: logger.error(f"强制清理失败: {e}") # 全局内存管理器实例 memory_manager = BlenderMemoryManager() # 【新增】依赖图管理器 - 控制更新频率避免冲突 class DependencyGraphManager: """依赖图管理器 - 控制更新频率,避免过度更新导致的冲突""" def __init__(self): self.last_update_time = 0 self.update_interval = 0.05 # 减少到50毫秒,提高响应性 self.pending_updates = False self._update_lock = threading.Lock() self.force_reset_count = 0 # 记录强制重置次数 def request_update(self, force=False): """请求依赖图更新 - 线程安全版本""" if not BLENDER_AVAILABLE: return # 【新增】线程安全检查 - 只在主线程中执行更新 if threading.current_thread().ident != _main_thread_id: logger.debug("跳过非主线程的依赖图更新") self.pending_updates = True return with self._update_lock: current_time = time.time() if force or (current_time - self.last_update_time) >= self.update_interval: try: # 【强化】安全的依赖图更新 bpy.context.view_layer.update() self.last_update_time = current_time self.pending_updates = False logger.debug("✅ 依赖图更新完成") except (AttributeError, ReferenceError, RuntimeError) as e: # 这些错误在对象删除过程中是预期的 logger.debug(f"依赖图更新时的预期错误: {e}") except Exception as e: logger.warning(f"依赖图更新失败: {e}") else: self.pending_updates = True logger.debug("⏳ 依赖图更新被节流控制") def request_full_reset(self): """请求完整的依赖图重置 - 用于解决状态污染""" if not BLENDER_AVAILABLE: return # 确保在主线程中执行 if threading.current_thread().ident != _main_thread_id: logger.debug("跳过非主线程的依赖图重置") return with self._update_lock: try: logger.info("🔄 开始完整依赖图重置...") # 【策略1】清除所有选择状态 bpy.ops.object.select_all(action='DESELECT') # 【策略2】强制刷新评估依赖图 bpy.context.evaluated_depsgraph_get().update() # 【策略3】更新视图层 bpy.context.view_layer.update() # 【策略4】强制场景刷新 bpy.context.scene.frame_set(bpy.context.scene.frame_current) # 【策略5】刷新所有视图区域 for area in bpy.context.screen.areas: if area.type in ['VIEW_3D', 'OUTLINER']: area.tag_redraw() self.force_reset_count += 1 self.last_update_time = time.time() self.pending_updates = False logger.info(f"✅ 完整依赖图重置完成 (第{self.force_reset_count}次)") except Exception as e: logger.error(f"完整依赖图重置失败: {e}") def flush_pending_updates(self): """强制执行所有挂起的更新""" if self.pending_updates: self.request_update(force=True) # 全局依赖图管理器 dependency_manager = DependencyGraphManager() # 全局主线程任务队列 _main_thread_queue = queue.Queue() _main_thread_id = None def init_main_thread(): """初始化主线程ID""" global _main_thread_id _main_thread_id = threading.current_thread().ident def execute_in_main_thread_async(func: Callable, *args, **kwargs): """ 【真正的异步版】在主线程中安全地调度函数 - 真正的"即发即忘",不等待结果。 """ global _main_thread_queue, _main_thread_id # 如果已经在主线程中,直接执行 if threading.current_thread().ident == _main_thread_id: try: func(*args, **kwargs) return True except Exception as e: logger.error(f"在主线程直接执行函数时出错: {e}") import traceback traceback.print_exc() return False # 在Blender中,使用应用程序定时器 - 即发即忘模式 try: import bpy def timer_task(): try: func(*args, **kwargs) except Exception as e: logger.error(f"主线程任务执行失败: {e}") import traceback traceback.print_exc() return None # 只执行一次 # 注册定时器任务就立即返回,不等待结果 bpy.app.timers.register(timer_task, first_interval=0.001) # !!!关键:立即返回调度成功,不等待执行结果!!! return True except ImportError: # 不在Blender环境中,使用原有的队列机制 - 也改为即发即忘 def wrapper(): try: func(*args, **kwargs) except Exception as e: logger.error(f"队列任务执行失败: {e}") import traceback traceback.print_exc() _main_thread_queue.put(wrapper) # 立即返回调度成功,不等待执行结果 return True # 【保持向后兼容】旧函数名的别名 execute_in_main_thread = execute_in_main_thread_async def process_main_thread_tasks(): """ 【修复版】处理主线程任务队列 - 一次只处理一个任务! 这个函数需要被Blender的定时器定期调用。 """ global _main_thread_queue try: # !!!关键修改:从 while 改为 if !!! # 一次定时器触发,只处理队列中的一个任务,然后就把控制权还给Blender。 if not _main_thread_queue.empty(): task = _main_thread_queue.get_nowait() try: task() except Exception as e: logger.error(f"执行主线程任务时出错: {e}") import traceback traceback.print_exc() except queue.Empty: pass # 队列是空的,什么也不做 @contextmanager def safe_blender_operation(operation_name: str): """线程安全的Blender操作上下文管理器 - 强化版本,添加依赖图保护""" if not BLENDER_AVAILABLE: logger.warning(f"Blender不可用,跳过操作: {operation_name}") yield return start_time = time.time() logger.debug(f"🔄 开始操作: {operation_name}") # 保存当前状态 original_mode = None original_selection = [] original_active = None def _execute_operation(): nonlocal original_mode, original_selection, original_active try: # 【强化1】预防性依赖图重置 try: bpy.context.evaluated_depsgraph_get().update() bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.update() logger.debug(f"✅ 操作前依赖图重置完成: {operation_name}") except Exception as e: logger.debug(f"操作前依赖图重置失败: {e}") # 确保在对象模式下 if hasattr(bpy.context, 'mode') and bpy.context.mode != 'OBJECT': original_mode = bpy.context.mode bpy.ops.object.mode_set(mode='OBJECT') # 保存当前选择和活动对象 if hasattr(bpy.context, 'selected_objects'): original_selection = list(bpy.context.selected_objects) if hasattr(bpy.context, 'active_object'): original_active = bpy.context.active_object # 清除选择以避免冲突 bpy.ops.object.select_all(action='DESELECT') return True except Exception as e: logger.error(f"准备操作失败: {e}") return False def _cleanup_operation(): try: # 【强化2】操作后彻底清理依赖图状态 try: # 清除所有选择 bpy.ops.object.select_all(action='DESELECT') # 强制刷新评估依赖图 bpy.context.evaluated_depsgraph_get().update() # 更新视图层 bpy.context.view_layer.update() # 强制场景刷新(解决状态污染) bpy.context.scene.frame_set(bpy.context.scene.frame_current) logger.debug(f"✅ 操作后依赖图清理完成: {operation_name}") except Exception as e: logger.debug(f"操作后依赖图清理失败: {e}") # 尝试恢复原始状态 for obj in original_selection: if obj and obj.name in bpy.data.objects: obj.select_set(True) # 恢复活动对象 if original_active and original_active.name in bpy.data.objects: bpy.context.view_layer.objects.active = original_active # 恢复模式 if original_mode and original_mode != 'OBJECT': bpy.ops.object.mode_set(mode=original_mode) except Exception as restore_error: logger.warning(f"恢复状态失败: {restore_error}") try: # 如果不在主线程,使用主线程执行准备操作 if threading.current_thread().ident != _main_thread_id: success = execute_in_main_thread(_execute_operation) if not success: raise RuntimeError("准备操作失败") else: success = _execute_operation() if not success: raise RuntimeError("准备操作失败") # 执行用户操作 yield elapsed_time = time.time() - start_time if elapsed_time > 5.0: logger.warning(f"操作耗时过长: {operation_name} ({elapsed_time:.2f}s)") else: logger.debug(f"✅ 操作完成: {operation_name} ({elapsed_time:.2f}s)") except Exception as e: logger.error(f"❌ 操作失败: {operation_name} - {e}") # 【强化3】操作失败时的紧急清理 try: dependency_manager.request_full_reset() logger.info(f"🚨 操作失败后执行紧急依赖图重置: {operation_name}") except Exception as emergency_error: logger.warning(f"紧急依赖图重置失败: {emergency_error}") raise finally: # 清理操作也需要在主线程中执行 if threading.current_thread().ident != _main_thread_id: try: execute_in_main_thread(_cleanup_operation) except: pass else: _cleanup_operation() # ==================== 几何类扩展 ==================== class Point3d: """3D点类 - 对应Ruby的Geom::Point3d""" def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0): self.x = x self.y = y self.z = z @classmethod def parse(cls, value: str): """从字符串解析3D点""" if not value or value.strip() == "": return None # 解析格式: "(x,y,z)" 或 "x,y,z" clean_value = re.sub(r'[()]*', '', value) xyz = [float(axis.strip()) for axis in clean_value.split(',')] # 转换mm为内部单位(假设输入是mm) return cls(xyz[0] * 0.001, xyz[1] * 0.001, xyz[2] * 0.001) def to_s(self, unit: str = "mm", digits: int = -1) -> str: """转换为字符串""" if unit == "cm": x_val = self.x * 100 # 内部单位转换为cm y_val = self.y * 100 z_val = self.z * 100 return f"({x_val:.3f}, {y_val:.3f}, {z_val:.3f})" else: # mm x_val = self.x * 1000 # 内部单位转换为mm y_val = self.y * 1000 z_val = self.z * 1000 if digits == -1: return f"({x_val}, {y_val}, {z_val})" else: return f"({x_val:.{digits}f}, {y_val:.{digits}f}, {z_val:.{digits}f})" def __str__(self): return self.to_s() def __repr__(self): return f"Point3d({self.x}, {self.y}, {self.z})" class Vector3d: """3D向量类 - 对应Ruby的Geom::Vector3d""" def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0): self.x = x self.y = y self.z = z @classmethod def parse(cls, value: str): """从字符串解析3D向量""" if not value or value.strip() == "": return None clean_value = re.sub(r'[()]*', '', value) xyz = [float(axis.strip()) for axis in clean_value.split(',')] return cls(xyz[0] * 0.001, xyz[1] * 0.001, xyz[2] * 0.001) def to_s(self, unit: str = "mm") -> str: """转换为字符串""" if unit == "cm": x_val = self.x * 100 # 内部单位转换为cm y_val = self.y * 100 z_val = self.z * 100 return f"({x_val:.3f}, {y_val:.3f}, {z_val:.3f})" elif unit == "in": return f"({self.x}, {self.y}, {self.z})" else: # mm x_val = self.x * 1000 # 内部单位转换为mm y_val = self.y * 1000 z_val = self.z * 1000 return f"({x_val}, {y_val}, {z_val})" def normalize(self): """归一化向量""" length = math.sqrt(self.x**2 + self.y**2 + self.z**2) if length > 0: return Vector3d(self.x/length, self.y/length, self.z/length) return Vector3d(0, 0, 0) def __str__(self): return self.to_s() class Transformation: """变换矩阵类 - 对应Ruby的Geom::Transformation""" def __init__(self, origin: Point3d = None, x_axis: Vector3d = None, y_axis: Vector3d = None, z_axis: Vector3d = None): self.origin = origin or Point3d(0, 0, 0) self.x_axis = x_axis or Vector3d(1, 0, 0) self.y_axis = y_axis or Vector3d(0, 1, 0) self.z_axis = z_axis or Vector3d(0, 0, 1) @classmethod def parse(cls, data: Dict[str, str]): """从字典解析变换""" origin = Point3d.parse(data.get("o")) x_axis = Vector3d.parse(data.get("x")) y_axis = Vector3d.parse(data.get("y")) z_axis = Vector3d.parse(data.get("z")) return cls(origin, x_axis, y_axis, z_axis) def store(self, data: Dict[str, str]): """存储变换到字典""" data["o"] = self.origin.to_s("mm") data["x"] = self.x_axis.to_s("in") data["y"] = self.y_axis.to_s("in") data["z"] = self.z_axis.to_s("in") # ==================== SUWood 材质类型常量 ==================== MAT_TYPE_NORMAL = 0 MAT_TYPE_OBVERSE = 1 MAT_TYPE_NATURE = 2 # ==================== SUWImpl 核心实现类 ==================== class SUWImpl: """SUWood核心实现类 - 完整翻译版本,应用内存管理最佳实践""" _instance = None _selected_uid = None _selected_obj = None _selected_zone = None _selected_part = None _scaled_zone = None _server_path = None _default_zone = None _creation_lock = False _mesh_creation_count = 0 _batch_operation_active = False def __init__(self): """初始化SUWImpl实例""" # 基础属性 self.zones = {} self.parts = {} self.hardwares = {} self.machinings = {} self.dimensions = {} self.textures = {} self.unit_param = {} self.unit_trans = {} # 内存管理相关 self.object_references = {} # 存储对象名称而非引用 self.mesh_cache = {} self.material_cache = {} # 批量操作优化 self.deferred_updates = [] self.batch_size = 50 # 状态管理 self.added_contour = False self.part_mode = False self.hide_none = False self.mat_type = MAT_TYPE_NORMAL self.selected_faces = [] self.selected_parts = [] self.selected_hws = [] self.menu_handle = 0 self.back_material = False # 图层管理 self.door_layer = None self.drawer_layer = None self.labels = None self.door_labels = None # 【新增】c15命令优化缓存 self._c15_cache = { 'leaf_zones': {}, # uid -> set of leaf zone ids 'zones_hash': {}, # uid -> hash of zones data 'last_update_time': {}, # uid -> timestamp 'blender_objects_cache': set(), # cached blender object names 'cache_valid_until': 0 # timestamp when cache expires } logger.info("SUWImpl 初始化完成,启用内存管理优化和c15缓存") self.command_map = { # ... existing commands ... 'c16': self._execute_c16, # sel_zone 'c17': self._execute_c17, # sel_elem # ... existing commands ... } @classmethod def get_instance(cls): """获取单例实例""" if cls._instance is None: cls._instance = cls() return cls._instance def startup(self): """启动SUW实现""" try: # 初始化主线程 init_main_thread() logger.info("🚀 启动SUW实现...") if BLENDER_AVAILABLE: self._create_layers() self._init_materials() self._init_default_zone() # 启动主线程任务处理器 self._start_main_thread_processor() logger.info("✅ SUW实现启动完成") except Exception as e: logger.error(f"启动SUW实现失败: {e}") def _start_main_thread_processor(self): """启动主线程任务处理器""" try: # 使用Blender的定时器来处理主线程任务 if hasattr(bpy.app, 'timers'): def process_tasks(): process_main_thread_tasks() return 0.01 # 每10毫秒处理一次 bpy.app.timers.register(process_tasks, persistent=True) logger.debug("主线程任务处理器已启动") except Exception as e: logger.warning(f"启动主线程任务处理器失败: {e}") def _create_layers(self): """创建集合系统(在Blender 2.8+中创建集合) - 修复版本""" try: if not BLENDER_AVAILABLE: return logger.debug("创建集合系统...") # 创建门板集合(默认可见) door_collection_name = "DOOR_LAYER" if door_collection_name not in bpy.data.collections: door_collection = bpy.data.collections.new( door_collection_name) bpy.context.scene.collection.children.link(door_collection) # 修改:默认显示门板集合 door_collection.hide_viewport = False door_collection.hide_render = False logger.debug("门板集合已创建(可见)") # 创建抽屉集合(默认可见) drawer_collection_name = "DRAWER_LAYER" if drawer_collection_name not in bpy.data.collections: drawer_collection = bpy.data.collections.new( drawer_collection_name) bpy.context.scene.collection.children.link(drawer_collection) # 修改:默认显示抽屉集合 drawer_collection.hide_viewport = False drawer_collection.hide_render = False logger.debug("抽屉集合已创建(可见)") logger.debug("集合系统创建完成") except Exception as e: logger.error(f"创建集合系统失败: {e}") def _init_materials(self): """初始化材质 - 减少注册调用""" try: if not BLENDER_AVAILABLE: return logger.debug("初始化材质...") # 创建基础材质 materials_to_create = [ ("mat_default", (0.8, 0.8, 0.8, 1.0)), ("mat_select", (1.0, 0.5, 0.0, 1.0)), ("mat_normal", (0.7, 0.7, 0.7, 1.0)), ("mat_obverse", (0.9, 0.9, 0.9, 1.0)), ("mat_reverse", (0.6, 0.6, 0.6, 1.0)), ("mat_thin", (0.5, 0.5, 0.5, 1.0)), ] for mat_name, color in materials_to_create: if mat_name not in bpy.data.materials: material = bpy.data.materials.new(name=mat_name) material.use_nodes = True # 设置基础颜色 if material.node_tree: principled = material.node_tree.nodes.get( "Principled BSDF") if principled: principled.inputs['Base Color'].default_value = color # 只注册一次 memory_manager.register_object(material) self.textures[mat_name] = material else: # 如果材质已存在,直接使用 self.textures[mat_name] = bpy.data.materials[mat_name] except Exception as e: logger.error(f"初始化材质失败: {e}") def _init_default_zone(self): """初始化默认区域""" try: if not BLENDER_AVAILABLE: return logger.debug("初始化默认区域...") # 创建默认区域模板 self._default_zone = bpy.data.objects.new("DefaultZone", None) # 不添加到场景中,只作为模板使用 # 设置默认属性 self._default_zone["sw_typ"] = "zid" self._default_zone.hide_viewport = True memory_manager.register_object(self._default_zone) logger.debug("默认区域初始化完成") except Exception as e: logger.error(f"初始化默认区域失败: {e}") # ==================== 材质管理方法 ==================== def add_mat_rgb(self, mat_id: str, alpha: float, r: int, g: int, b: int): """添加RGB材质""" try: if not BLENDER_AVAILABLE: return None # 检查材质是否已存在 if mat_id in self.material_cache: material_name = self.material_cache[mat_id] if material_name in bpy.data.materials: return bpy.data.materials[material_name] # 创建新材质 material = bpy.data.materials.new(mat_id) material.use_nodes = True # 设置颜色 if material.node_tree: principled = material.node_tree.nodes.get("Principled BSDF") if principled: color = (r/255.0, g/255.0, b/255.0, alpha) principled.inputs[0].default_value = color # 设置透明度 if alpha < 1.0: material.blend_method = 'BLEND' # Alpha input principled.inputs[21].default_value = alpha # 缓存材质 self.material_cache[mat_id] = material.name self.textures[mat_id] = material memory_manager.register_object(material) logger.info(f"创建RGB材质: {mat_id}") return material except Exception as e: logger.error(f"创建RGB材质失败: {e}") return None def get_texture(self, key: str): """获取纹理材质 - 增强版本""" if not BLENDER_AVAILABLE: return None try: # 检查键是否有效 if not key: return self.textures.get("mat_default") # 从缓存中获取 if key in self.textures: material = self.textures[key] # 验证材质是否仍然有效 if material and material.name in bpy.data.materials: return material else: # 清理无效的缓存 del self.textures[key] # 在现有材质中查找 for material in bpy.data.materials: if key in material.name: self.textures[key] = material return material # 返回默认材质 default_material = self.textures.get("mat_default") if default_material and default_material.name in bpy.data.materials: return default_material logger.warning(f"未找到纹理: {key}") return None except Exception as e: logger.error(f"获取纹理失败: {e}") return None # ==================== 数据获取方法 ==================== def get_zones(self, data: Dict[str, Any]) -> Dict[str, Any]: """获取区域信息""" uid = data.get("uid") if uid not in self.zones: self.zones[uid] = {} return self.zones[uid] def get_parts(self, data: Dict[str, Any]) -> Dict[str, Any]: """获取零件信息""" uid = data.get("uid") if uid not in self.parts: self.parts[uid] = {} return self.parts[uid] def get_hardwares(self, data: Dict[str, Any]) -> Dict[str, Any]: """获取硬件信息""" uid = data.get("uid") if uid not in self.hardwares: self.hardwares[uid] = {} return self.hardwares[uid] # ==================== 配置管理方法 ==================== def set_config(self, data: Dict[str, Any]): """设置配置""" try: if "server_path" in data: self.__class__._server_path = data["server_path"] if "order_id" in data: # 在Blender中存储为场景属性 if BLENDER_AVAILABLE: bpy.context.scene["sw_order_id"] = data["order_id"] if "order_code" in data: if BLENDER_AVAILABLE: bpy.context.scene["sw_order_code"] = data["order_code"] if "back_material" in data: self.back_material = data["back_material"] if "part_mode" in data: self.part_mode = data["part_mode"] if "hide_none" in data: self.hide_none = data["hide_none"] if "unit_drawing" in data: print( f"{data.get('drawing_name', '')}:\t{data['unit_drawing']}") if "zone_corner" in data: zones = self.get_zones(data) zone = zones.get(data["zid"]) if zone: zone["sw_cor"] = data["zone_corner"] # 应用内存管理相关配置 if "memory_cleanup_interval" in data: memory_manager.cleanup_interval = data["memory_cleanup_interval"] if "batch_size" in data: self.batch_size = data["batch_size"] logger.info(f"设置配置: {len(data)} 个配置项") except Exception as e: logger.error(f"设置配置失败: {e}") # ==================== 材质类型管理方法 ==================== def c11(self, data: Dict[str, Any]): """part_obverse - 设置零件正面显示""" try: self.mat_type = MAT_TYPE_OBVERSE if data.get( "v", False) else MAT_TYPE_NORMAL parts = self.get_parts(data) for root, part in parts.items(): if part and not self._is_selected_part(part): self.textured_part(part, False) logger.info(f"设置零件正面显示: {self.mat_type}") except Exception as e: logger.error(f"设置零件正面显示失败: {e}") def c30(self, data: Dict[str, Any]): """part_nature - 设置零件自然显示""" try: self.mat_type = MAT_TYPE_NATURE if data.get( "v", False) else MAT_TYPE_NORMAL parts = self.get_parts(data) for root, part in parts.items(): if part and not self._is_selected_part(part): self.textured_part(part, False) logger.info(f"设置零件自然显示: {self.mat_type}") except Exception as e: logger.error(f"设置零件自然显示失败: {e}") def _is_selected_part(self, part): """检查零件是否被选中""" return part in self.selected_parts # ==================== 纹理管理方法 ==================== def c02(self, data: Dict[str, Any]): """add_texture - 添加纹理 - 简化版本""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过纹理创建") return ckey = data.get("ckey") if not ckey: logger.warning("纹理键为空,跳过创建") return # 检查纹理是否已存在且有效 if ckey in self.textures: existing_material = self.textures[ckey] if existing_material and existing_material.name in bpy.data.materials: return existing_material else: # 清理无效的缓存 del self.textures[ckey] def create_material(): try: # 创建新材质 material = bpy.data.materials.new(name=ckey) material.use_nodes = True # 获取材质节点 nodes = material.node_tree.nodes links = material.node_tree.links # 清理所有默认节点,重新创建 nodes.clear() # 创建基础节点 principled = nodes.new(type='ShaderNodeBsdfPrincipled') principled.location = (0, 0) output = nodes.new(type='ShaderNodeOutputMaterial') output.location = (300, 0) # 连接基础节点 links.new( principled.outputs['BSDF'], output.inputs['Surface']) # 设置纹理图像 src_path = data.get("src") if src_path: try: # 安全地加载图像 import os # 检查路径是否存在 if os.path.exists(src_path): # 检查是否已经加载过这个图像 image_name = os.path.basename(src_path) image = bpy.data.images.get(image_name) if not image: image = bpy.data.images.load(src_path) memory_manager.register_image(image) # 创建纹理坐标节点 tex_coord = nodes.new( type='ShaderNodeTexCoord') tex_coord.location = (-600, 0) # 创建图像纹理节点 tex_image = nodes.new( type='ShaderNodeTexImage') tex_image.image = image tex_image.location = (-300, 0) # 连接节点 links.new( tex_coord.outputs['UV'], tex_image.inputs['Vector']) links.new( tex_image.outputs['Color'], principled.inputs['Base Color']) # 如果有透明度,也连接Alpha alpha_value = data.get("alpha", 1.0) if alpha_value < 1.0: links.new( tex_image.outputs['Alpha'], principled.inputs['Alpha']) material.blend_method = 'BLEND' material.show_transparent_back = False else: # 创建一个纯色材质作为替代 principled.inputs['Base Color'].default_value = ( 0.5, 0.5, 0.5, 1.0) except Exception as img_error: logger.error(f"加载纹理图像失败: {img_error}") # 创建纯色材质作为替代 principled.inputs['Base Color'].default_value = ( 1.0, 0.0, 0.0, 1.0) # 红色表示错误 else: # 没有图片路径,创建纯色材质 # 尝试从RGB数据创建颜色 r = data.get("r", 128) / 255.0 g = data.get("g", 128) / 255.0 b = data.get("b", 128) / 255.0 principled.inputs['Base Color'].default_value = ( r, g, b, 1.0) # 设置透明度 alpha_value = data.get("alpha", 1.0) principled.inputs['Alpha'].default_value = alpha_value if alpha_value < 1.0: material.blend_method = 'BLEND' material.use_backface_culling = False # 设置其他属性 if "reflection" in data: metallic_value = data["reflection"] principled.inputs['Metallic'].default_value = metallic_value if "reflection_glossiness" in data: roughness_value = 1.0 - data["reflection_glossiness"] principled.inputs['Roughness'].default_value = roughness_value return material except Exception as e: logger.error(f"创建材质失败: {e}") return None # 直接执行材质创建(已经在主线程中) material = create_material() if material: # 存储材质 self.textures[ckey] = material memory_manager.register_object(material) else: logger.error(f"材质创建失败: {ckey}") except Exception as e: logger.error(f"添加纹理失败 {ckey}: {e}") # 清理可能创建的无效材质 try: if ckey in self.textures: del self.textures[ckey] if ckey in bpy.data.materials: bpy.data.materials.remove(bpy.data.materials[ckey]) except: pass def c04(self, data: Dict[str, Any]): """c04 - 添加部件 - 与c09完全对齐的修复版本""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过零件创建") return uid = data.get("uid") root = data.get("cp") if not uid or not root: logger.error("缺少必要参数: uid或cp") return logger.info(f"🔧 开始创建部件: uid={uid}, cp={root}") def create_part(): try: # 【强化1】连续执行保护 - 检测并处理连续c04调用 if hasattr(self, '_last_c04_time'): time_since_last = time.time() - self._last_c04_time if time_since_last < 0.15: # 150毫秒内的连续执行 logger.warning( f"🚨 检测到连续c04执行 ({time_since_last:.3f}s),启动强化保护") # 强制依赖图完全重置 dependency_manager.request_full_reset() # 延迟执行 time.sleep(0.1) # 强制内存清理 cleanup_count = memory_manager.cleanup_orphaned_data() logger.info(f"连续执行保护:清理了{cleanup_count}个数据块") import gc gc.collect() # 记录执行时间 self._last_c04_time = time.time() # 【强化2】预防性内存管理 if memory_manager.should_cleanup(): logger.info("c04执行前的预防性内存清理") cleanup_count = memory_manager.cleanup_orphaned_data() logger.info(f"预防性清理:清理了{cleanup_count}个数据块") import gc gc.collect() # 【数据结构优先策略】先处理数据结构,后处理Blender对象 parts = self.get_parts(data) # 检查数据结构中是否已存在 if root in parts: existing_part = parts[root] if existing_part and self._is_object_valid(existing_part): logger.info(f"✅ 部件 {root} 已存在,跳过创建") return existing_part else: logger.warning(f"清理无效的部件引用: {root}") # 【关键】数据结构优先清理,对应c09的删除顺序 del parts[root] # 【强化3】使用safe_blender_operation保护创建过程 with safe_blender_operation(f"c04_create_part_{root}"): # 创建部件容器 part_name = f"Part_{root}" part = bpy.data.objects.new(part_name, None) bpy.context.scene.collection.objects.link(part) logger.info(f"✅ 创建Part对象: {part_name}") # 设置部件属性 part["sw_uid"] = uid part["sw_cp"] = root part["sw_typ"] = "part" # 【强化4】记录创建对象,便于对称删除 part["sw_created_objects"] = { "boards": [], "materials": [], "uv_layers": [], "creation_timestamp": time.time(), "memory_stats": memory_manager.get_memory_stats() } # 存储部件到数据结构(数据结构优先) parts[root] = part memory_manager.register_object(part) logger.info(f"✅ 部件存储到数据结构: uid={uid}, cp={root}") # 处理finals数据 finals = data.get("finals", []) logger.info(f"📦 处理 {len(finals)} 个板材数据") created_boards = 0 created_board_names = [] for i, final_data in enumerate(finals): try: board = self._create_board_with_material_and_uv( part, final_data) if board: created_boards += 1 # 【对称性】记录创建的板材,便于c09删除 created_board_names.append(board.name) logger.info( f"✅ 板材 {i+1}/{len(finals)} 创建成功: {board.name}") # 【强化5】智能依赖图更新 - 每5个板材更新一次 if i % 5 == 0: bpy.context.view_layer.update() # 【强化6】大量板材时的内存管理 if i % 10 == 0 and i > 0: cleanup_count = memory_manager.cleanup_orphaned_data() if cleanup_count > 0: logger.info( f"板材{i+1}:中间清理了{cleanup_count}个数据块") import gc gc.collect() else: logger.warning( f"⚠️ 板材 {i+1}/{len(finals)} 创建失败") except Exception as e: logger.error( f"❌ 创建板材 {i+1}/{len(finals)} 失败: {e}") # 【强化7】单个板材失败时的恢复 try: import gc gc.collect() bpy.context.view_layer.update() except: pass # 【强化8】更新创建记录 part["sw_created_objects"]["boards"] = created_board_names part["sw_created_objects"]["final_memory_stats"] = memory_manager.get_memory_stats( ) logger.info( f"📊 板材创建统计: {created_boards}/{len(finals)} 成功") # 【强化9】最终清理和状态重置 try: # 强制依赖图更新 bpy.context.view_layer.update() # 根据创建量决定清理策略 if len(finals) >= 10: logger.info("大量板材部件:执行完整清理") dependency_manager.request_full_reset() cleanup_count = memory_manager.cleanup_orphaned_data() logger.info(f"大量板材清理:清理了{cleanup_count}个数据块") import gc gc.collect() elif memory_manager.should_cleanup(): logger.info("按需执行内存清理") cleanup_count = memory_manager.cleanup_orphaned_data() logger.info(f"按需清理:清理了{cleanup_count}个数据块") import gc gc.collect() except Exception as cleanup_error: logger.warning(f"最终清理失败: {cleanup_error}") # 验证创建结果 if part.name in bpy.data.objects: logger.info(f"🎉 部件创建完全成功: {part_name}") return part else: logger.error(f"❌ 部件创建验证失败: {part_name} 不在Blender中") return None except Exception as e: logger.error(f"❌ 创建部件失败: {e}") # 【强化10】失败时的紧急清理 try: dependency_manager.request_full_reset() logger.info("部件创建失败,执行紧急依赖图重置") except: pass import traceback logger.error(traceback.format_exc()) return None # 直接执行创建(已经在主线程中) part = create_part() if part: logger.info( f"🎉 c04命令执行成功: uid={uid}, cp={root}, part={part.name}") return part else: logger.error(f"❌ c04命令执行失败: uid={uid}, cp={root}") return None except Exception as e: logger.error(f"❌ c04命令异常: {e}") import traceback logger.error(traceback.format_exc()) return None def _create_board_with_material_and_uv(self, part, data): """创建板材并关联材质和启用UV - 恢复立方体算法版本""" try: # 获取正反面数据 obv = data.get("obv") rev = data.get("rev") if not obv or not rev: logger.warning("缺少正反面数据,创建默认板材") return self._create_default_board_with_material(part, data) # 解析顶点计算精确尺寸 obv_vertices = self._parse_surface_vertices(obv) rev_vertices = self._parse_surface_vertices(rev) if len(obv_vertices) >= 3 and len(rev_vertices) >= 3: # 计算板材的精确边界 all_vertices = obv_vertices + rev_vertices min_x = min(v[0] for v in all_vertices) max_x = max(v[0] for v in all_vertices) min_y = min(v[1] for v in all_vertices) max_y = max(v[1] for v in all_vertices) min_z = min(v[2] for v in all_vertices) max_z = max(v[2] for v in all_vertices) # 计算中心点和精确尺寸 center_x = (min_x + max_x) / 2 center_y = (min_y + max_y) / 2 center_z = (min_z + max_z) / 2 size_x = max(max_x - min_x, 0.001) # 确保最小尺寸 size_y = max(max_y - min_y, 0.001) size_z = max(max_z - min_z, 0.001) logger.info( f"🔨 计算板材尺寸: {size_x:.3f}x{size_y:.3f}x{size_z:.3f}m, 中心: ({center_x:.3f},{center_y:.3f},{center_z:.3f})") # 创建精确尺寸的立方体 bpy.ops.mesh.primitive_cube_add( size=1, location=(center_x, center_y, center_z) ) board = bpy.context.active_object # 缩放到精确尺寸 board.scale = (size_x, size_y, size_z) # 设置属性和父子关系 board.parent = part board.name = f"Board_{part.name}" board["sw_face_type"] = "board" board["sw_uid"] = part.get("sw_uid") board["sw_cp"] = part.get("sw_cp") board["sw_typ"] = "board" logger.info(f"✅ 板材属性设置完成: {board.name}, 父对象: {part.name}") # 关联材质 color = data.get("ckey", "mat_default") if color: material = self.get_texture(color) if material and board.data: # 清空现有材质 board.data.materials.clear() # 添加新材质 board.data.materials.append(material) logger.info(f"✅ 材质 {color} 已关联到板材 {board.name}") else: logger.warning(f"材质 {color} 未找到或板材数据无效") # 启用UV self._enable_uv_for_board(board) return board else: logger.warning("顶点数据不足,创建默认板材") return self._create_default_board_with_material(part, data) except Exception as e: logger.error(f"创建板材失败: {e}") return self._create_default_board_with_material(part, data) def _enable_uv_for_board(self, board): """为板件启用UV - 简化版本""" try: if not board or not board.data: logger.warning("无效的板件对象,无法启用UV") return # 确保网格数据存在 mesh = board.data if not mesh: logger.warning("板件没有网格数据") return # 创建UV贴图层(如果不存在) if not mesh.uv_layers: uv_layer = mesh.uv_layers.new(name="UVMap") else: uv_layer = mesh.uv_layers[0] # 确保UV层是活动的 mesh.uv_layers.active = uv_layer # 更新网格数据 mesh.calc_loop_triangles() # 为立方体创建基本UV坐标 if len(mesh.polygons) == 6: # 标准立方体 # 为每个面分配UV坐标 for poly_idx, poly in enumerate(mesh.polygons): # 标准UV坐标 (0,0) (1,0) (1,1) (0,1) uv_coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] for loop_idx, loop_index in enumerate(poly.loop_indices): if loop_idx < len(uv_coords): uv_layer.data[loop_index].uv = uv_coords[loop_idx] else: # 为非标准网格设置简单UV for loop in mesh.loops: uv_layer.data[loop.index].uv = (0.5, 0.5) # 更新网格 mesh.update() except Exception as e: logger.error(f"启用UV失败: {e}") def _create_default_board_with_material(self, part, data): """创建默认板材 - 带材质和UV""" try: # 创建默认立方体 bpy.ops.mesh.primitive_cube_add( size=1, location=(0, 0, 0) ) board = bpy.context.active_object # 设置属性和父子关系 board.parent = part board.name = f"Board_{part.name}_default" board["sw_face_type"] = "board" # 从part获取uid和cp信息 uid = part.get("sw_uid") cp = part.get("sw_cp") board["sw_uid"] = uid board["sw_cp"] = cp board["sw_typ"] = "board" logger.info(f"✅ 默认板材属性设置完成: {board.name}, 父对象: {part.name}") # 关联材质 color = data.get("ckey", "mat_default") if color: material = self.get_texture(color) if material and board.data: board.data.materials.clear() board.data.materials.append(material) logger.info(f"✅ 默认材质 {color} 已关联到板件 {board.name}") # 启用UV self._enable_uv_for_board(board) logger.info(f"✅ 创建默认板材: {board.name}") return board except Exception as e: logger.error(f"创建默认板材失败: {e}") return None def _add_part_stretch_safe(self, part, data, timeout=5): """创建拉伸部件 - 安全版本""" try: logger.debug("创建拉伸部件(简化版本)") # 创建简单的拉伸对象 stretch_obj = bpy.data.objects.new(f"Stretch_{part.name}", None) stretch_obj.parent = part bpy.context.scene.collection.objects.link(stretch_obj) return stretch_obj except Exception as e: logger.error(f"创建拉伸部件失败: {e}") return None def _add_part_arc_safe(self, part, data, antiz, profiles, timeout=5): """创建弧形部件 - 安全版本""" try: logger.debug("创建弧形部件(简化版本)") # 创建简单的弧形对象 arc_obj = bpy.data.objects.new(f"Arc_{part.name}", None) arc_obj.parent = part bpy.context.scene.collection.objects.link(arc_obj) return arc_obj except Exception as e: logger.error(f"创建弧形部件失败: {e}") return None def _update_viewport(self): """更新视图端口""" try: if BLENDER_AVAILABLE: # 更新依赖图 bpy.context.view_layer.update() # 刷新视图 for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': for region in area.regions: if region.type == 'WINDOW': region.tag_redraw() logger.debug("视图端口已更新") except Exception as e: logger.warning(f"更新视图端口失败: {e}") def _process_final_geometry(self, part, data, virtual=False): """处理最终几何体 - 增强版本""" try: final = data.get("final") if not final: logger.warning("没有找到最终几何体数据") return logger.debug(f"处理最终几何体: typ={final.get('typ')}") # 获取几何体类型 typ = final.get("typ", 1) antiz = final.get("antiz", False) profiles = final.get("profiles", {}) # 创建几何体 if typ == 1: # 板材部件 logger.debug("创建板材部件") leaf = self._add_part_board(part, final, antiz, profiles) elif typ == 2: # 拉伸部件 logger.debug("创建拉伸部件") leaf = self._add_part_stretch(part, final) elif typ == 3: # 弧形部件 logger.debug("创建弧形部件") leaf = self._add_part_arc(part, final, antiz, profiles) else: logger.warning(f"未知的几何体类型: {typ}") return if leaf: # 设置属性 leaf["sw_typ"] = "cp" leaf["sw_mn"] = final.get("mn", 0) # 设置可见性 if not virtual: leaf.hide_viewport = False memory_manager.register_object(leaf) logger.debug(f"几何体创建成功: {leaf.name}") else: logger.warning("几何体创建失败") except Exception as e: logger.error(f"处理最终几何体失败: {e}") import traceback logger.error(traceback.format_exc()) def _add_part_board(self, part, data, antiz, profiles): """创建板材部件 - 增强版本""" try: logger.debug("开始创建板材部件") # 创建叶子组 leaf = bpy.data.objects.new(f"Board_{part.name}", None) leaf.parent = part bpy.context.scene.collection.objects.link(leaf) # 设置板材属性 leaf["sw_face_type"] = "board" leaf["sw_uid"] = part.get("sw_uid") leaf["sw_cp"] = part.get("sw_cp") leaf["sw_typ"] = "board" # 获取材质信息 color = data.get("ckey", "mat_default") scale = data.get("scale") angle = data.get("angle") color2 = data.get("ckey2") scale2 = data.get("scale2") angle2 = data.get("angle2") logger.debug(f"板材材质: {color}") # 处理截面 if "sects" in data: logger.debug("处理截面数据") sects = data["sects"] for sect in sects: segs = sect.get("segs", []) surf = sect.get("sect", {}) paths = self._create_paths(part, segs) self._follow_me(leaf, surf, paths, color, scale, angle) # 创建第二个叶子用于表面 leaf2 = bpy.data.objects.new( f"Board_Surface_{part.name}", None) leaf2.parent = leaf bpy.context.scene.collection.objects.link(leaf2) self._add_part_surf(leaf2, data, antiz, color, scale, angle, color2, scale2, angle2, profiles) else: # 直接创建表面 logger.debug("创建板材表面") self._add_part_surf( leaf, data, antiz, color, scale, angle, color2, scale2, angle2, profiles) logger.debug(f"板材部件创建完成: {leaf.name}") return leaf except Exception as e: logger.error(f"创建板材部件失败: {e}") import traceback logger.error(traceback.format_exc()) return None def _add_part_surf(self, leaf, data, antiz, color, scale, angle, color2, scale2, angle2, profiles): """创建部件表面 - 增强版本""" try: logger.debug("开始创建部件表面") # 获取正反面数据 obv = data.get("obv") # 正面 rev = data.get("rev") # 反面 if not obv or not rev: logger.warning("缺少正反面数据") return logger.debug(f"正面数据: {obv}") logger.debug(f"反面数据: {rev}") # 设置材质类型 obv_type = "o" obv_save = color obv_scale = scale obv_angle = angle rev_type = "r" rev_save = color2 if color2 else color rev_scale = scale2 if color2 else scale rev_angle = angle2 if color2 else angle # 如果antiz为真,交换正反面 if antiz: obv_type, rev_type = rev_type, obv_type obv_save, rev_save = rev_save, obv_save obv_scale, rev_scale = rev_scale, obv_scale obv_angle, rev_angle = rev_angle, obv_angle # 确定显示材质 obv_show = "mat_obverse" if self.mat_type == MAT_TYPE_OBVERSE else obv_save rev_show = "mat_reverse" if self.mat_type == MAT_TYPE_OBVERSE else rev_save # 创建面 series1 = [] series2 = [] logger.debug("创建正面") obv_face = self.create_face( leaf, obv, obv_show, obv_scale, obv_angle, series1, False, self.back_material, obv_save, obv_type) logger.debug("创建反面") rev_face = self.create_face( leaf, rev, rev_show, rev_scale, rev_angle, series2, True, self.back_material, rev_save, rev_type) # 创建边缘 if series1 and series2: logger.debug("创建边缘") self._add_part_edges( leaf, series1, series2, obv, rev, profiles) logger.debug("部件表面创建完成") except Exception as e: logger.error(f"创建部件表面失败: {e}") import traceback logger.error(traceback.format_exc()) def _clear_part_children(self, part): """清除零件的子对象""" if not BLENDER_AVAILABLE: return try: children_to_remove = [] for child in part.children: if child.get("sw_typ") == "cp": children_to_remove.append(child) for child in children_to_remove: bpy.data.objects.remove(child, do_unlink=True) except Exception as e: logger.error(f"清除零件子对象失败: {e}") def _set_drawer_properties(self, part, data): """设置抽屉属性""" drawer_type = data.get("drw", 0) part["sw_drawer"] = drawer_type if drawer_type in [73, 74]: # DR_LP/DR_RP part["sw_dr_depth"] = data.get("drd", 0) if drawer_type == 70: # DR_DP drv = data.get("drv") if drv: drawer_dir = Vector3d.parse(drv) part["sw_drawer_dir"] = ( drawer_dir.x, drawer_dir.y, drawer_dir.z) def _set_door_properties(self, part, data): """设置门属性""" door_type = data.get("dor", 0) part["sw_door"] = door_type if door_type in [10, 15]: part["sw_door_width"] = data.get("dow", 0) part["sw_door_pos"] = data.get("dop", "F") def _load_prefab_part(self, part, data): """加载预制件""" if "sid" not in data: return None try: mirr = data.get("mr", "") if mirr: mirr = "_" + mirr # 构建文件路径 file_path = f"{SUWood.suwood_path('V_StructPart')}/{data['sid']}{mirr}.skp" print(f"尝试加载预制件: {file_path}") # 在Blender中,我们需要使用不同的方法加载外部文件 # 这里创建一个占位符 inst = bpy.data.objects.new(f"Prefab_{data['sid']}", None) inst.parent = part bpy.context.scene.collection.objects.link(inst) inst["sw_typ"] = "cp" # 设置缩放 if "l" in data and "w" in data: inst.scale = (data["l"] * 0.001, data["w"] * 0.001, 1.0) # 应用变换 if "trans" in data: trans = Transformation.parse(data["trans"]) self._apply_transformation(inst, trans) return inst except Exception as e: logger.error(f"加载预制件失败: {e}") return None def _create_virtual_geometry(self, part, data): """创建虚拟几何体""" try: leaf = bpy.data.objects.new("Virtual_Geometry", None) leaf.parent = part bpy.context.scene.collection.objects.link(leaf) if data.get("typ") == 3: # 弧形部件 self._create_arc_geometry(leaf, data) else: # 板材部件 self._create_board_geometry(leaf, data) leaf["sw_typ"] = "cp" leaf["sw_virtual"] = True leaf.hide_viewport = True except Exception as e: logger.error(f"创建虚拟几何体失败: {e}") def _create_arc_geometry(self, leaf, data): """创建弧形几何体""" try: co = data.get("co") cr = data.get("cr") if co and cr: center_o = Point3d.parse(co) center_r = Point3d.parse(cr) # 创建弧形路径 path = self._create_arc_path(leaf, center_o, center_r) # 创建截面 obv = data.get("obv", {}) self._follow_me(leaf, obv, path, None) except Exception as e: logger.error(f"创建弧形几何体失败: {e}") def _create_board_geometry(self, leaf, data): """创建板材几何体""" try: obv = data.get("obv", {}) rev = data.get("rev", {}) series1 = [] series2 = [] self.create_face(leaf, obv, series=series1) self.create_face(leaf, rev, series=series2) self._add_part_edges(leaf, series1, series2, obv, rev) except Exception as e: logger.error(f"创建板材几何体失败: {e}") def _add_part_edges(self, leaf, series1, series2, obv, rev, profiles=None): """创建部件的边缘面""" try: if not BLENDER_AVAILABLE or not series1 or not series2: return unplanar = False for index in range(len(series1)): pts1 = series1[index] pts2 = series2[index] for i in range(1, len(pts1)): pts = [pts1[i-1], pts1[i], pts2[i], pts2[i-1]] try: # 创建边缘面 face = self._create_edge_face(leaf, pts) if face and profiles: self._add_part_profile(face, index, profiles) except Exception as e: unplanar = True logger.warning(f"Points are not planar {index}: {i}") logger.warning(f"Points: {pts}") if unplanar: # 输出调试信息 segs_o = obv.get("segs", []) pts_o = [seg[0] for seg in segs_o] segs_r = rev.get("segs", []) pts_r = [seg[0] for seg in segs_r] logger.warning("=" * 30) logger.warning(f"obv: {pts_o}") logger.warning(f"rev: {pts_r}") logger.warning(f"series1: {series1}") logger.warning(f"series2: {series2}") logger.warning("=" * 30) except Exception as e: logger.error(f"创建部件边缘失败: {e}") def _create_edge_face(self, container, points): """创建边缘面""" try: if not BLENDER_AVAILABLE: return None # 创建网格 mesh = bpy.data.meshes.new("Edge_Face") vertices = [(p.x, p.y, p.z) if hasattr( p, 'x') else p for p in points] faces = [list(range(len(vertices)))] mesh.from_pydata(vertices, [], faces) mesh.update() # 创建对象 obj = bpy.data.objects.new("Edge_Face_Obj", mesh) obj.parent = container bpy.context.scene.collection.objects.link(obj) # 隐藏某些边 for i, edge in enumerate(mesh.edges): if i in [1, 3]: edge.use_edge_sharp = True return obj except Exception as e: logger.error(f"创建边缘面失败: {e}") return None def _add_part_profile(self, face, index, profiles): """为面添加型材属性和纹理""" try: profile = profiles.get(index) if not profile: return color = profile.get("ckey") scale = profile.get("scale") angle = profile.get("angle") typ = profile.get("typ", "0") # 确定当前颜色 if self.mat_type == MAT_TYPE_OBVERSE: if typ == "1": current = "mat_obverse" # thick profile elif typ == "2": current = "mat_thin" # thin profile else: current = "mat_reverse" # none profile else: current = color face["sw_typ"] = f"e{typ}" self._textured_surf(face, self.back_material, current, color, scale, angle) except Exception as e: logger.error(f"添加型材属性失败: {e}") def _add_part_stretch(self, part, data): """创建拉伸部件""" try: if not BLENDER_AVAILABLE: return None compensates = data.get("compensates", []) trim_surfs = data.get("trim_surfs", []) baselines = self._create_paths(part, data.get("baselines", [])) # 尝试加载预制件 inst = None if ("sid" in data and not compensates and not trim_surfs and len(baselines) == 1): file_path = f"{SUWood.suwood_path('V_StretchPart')}/{data['sid']}.skp" # 在实际应用中需要实现文件加载逻辑 # 这里创建占位符 inst = self._load_stretch_prefab( part, file_path, data, baselines[0]) if inst: # 创建虚拟几何体 leaf = bpy.data.objects.new("Virtual_Stretch", None) leaf.parent = part bpy.context.scene.collection.objects.link(leaf) surf = data.get("sect", {}) surf["segs"] = data.get("bounds", []) self._follow_me(leaf, surf, baselines, None) leaf["sw_virtual"] = True leaf.hide_viewport = True else: # 创建实际几何体 thick = data.get("thick", 18) * 0.001 # mm to meters leaf = bpy.data.objects.new("Stretch_Part", None) leaf.parent = part bpy.context.scene.collection.objects.link(leaf) zaxis = Vector3d.parse(data.get("zaxis", "(0,0,1)")) color = data.get("ckey") sect = data.get("sect", {}) self._follow_me(leaf, sect, baselines, color) # 处理补偿面 for compensate in compensates: self._apply_compensate(leaf, compensate, zaxis, thick) # 处理修剪面 for trim_surf in trim_surfs: self._apply_trim_surf(leaf, trim_surf, zaxis, thick) leaf["sw_ckey"] = color return leaf except Exception as e: logger.error(f"创建拉伸部件失败: {e}") return None def _add_part_arc(self, part, data, antiz, profiles): """创建弧形部件""" try: if not BLENDER_AVAILABLE: return None leaf = bpy.data.objects.new("Arc_Part", None) leaf.parent = part bpy.context.scene.collection.objects.link(leaf) obv = data.get("obv", {}) color = data.get("ckey") scale = data.get("scale") angle = data.get("angle") color2 = data.get("ckey2") scale2 = data.get("scale2") angle2 = data.get("angle2") # 设置属性 leaf["sw_ckey"] = color if scale: leaf["sw_scale"] = scale if angle: leaf["sw_angle"] = angle # 创建弧形路径 center_o = Point3d.parse(data.get("co", "(0,0,0)")) center_r = Point3d.parse(data.get("cr", "(0,0,0)")) path = self._create_arc_path(leaf, center_o, center_r) # 创建弧形几何体 series = [] normal = self._follow_me( leaf, obv, path, color, scale, angle, False, series, True) # 处理面和边 if len(series) == 4: self._process_arc_faces( leaf, series, normal, center_o, center_r, color2, scale2, angle2, profiles) return leaf except Exception as e: logger.error(f"创建弧形部件失败: {e}") return None def _process_arc_faces(self, leaf, series, normal, center_o, center_r, color2, scale2, angle2, profiles): """处理弧形面和边""" try: count = 0 edge1 = False edge3 = False face2 = color2 is None for child in leaf.children: if not hasattr(child, 'data') or not child.data: continue # 检查是否是平行于法向量的面 if self._is_parallel_to_normal(child, normal): if self._is_on_plane(center_o, child): self._add_part_profile(child, 2, profiles) count += 1 else: self._add_part_profile(child, 0, profiles) count += 1 else: # 处理边 if not edge1 and self._contains_series_points(child, series[1]): self._add_part_profile(child, 1, profiles) count += 1 edge1 = True elif not edge3 and self._contains_series_points(child, series[3]): self._add_part_profile(child, 3, profiles) count += 1 edge3 = True elif not face2 and self._contains_series_points(child, series[2]): self._textured_surf( child, self.back_material, color2, color2, scale2, angle2) count += 1 face2 = True # 检查是否完成 expected_count = 5 if color2 else 4 if count >= expected_count: break except Exception as e: logger.error(f"处理弧形面失败: {e}") # ==================== 硬件管理方法 ==================== def c08(self, data: Dict[str, Any]): """add_hardware - 添加硬件 - 线程安全版本""" try: if not BLENDER_AVAILABLE: return uid = data.get("uid") hardwares = self.get_hardwares(data) def create_hardware(): try: items = data.get("items", []) created_count = 0 for item in items: root = item.get("root") file_path = item.get("file") ps = Point3d.parse(item.get("ps", "(0,0,0)")) pe = Point3d.parse(item.get("pe", "(0,0,0)")) if file_path: hardware = self._load_hardware_file( file_path, item, ps, pe) else: hardware = self._create_simple_hardware( ps, pe, item) if hardware: hardware["sw_uid"] = uid hardware["sw_root"] = root hardware["sw_typ"] = "hw" # 应用单元变换 if uid in self.unit_trans: self._apply_transformation( hardware, self.unit_trans[uid]) hardwares[root] = hardware memory_manager.register_object(hardware) created_count += 1 return created_count except Exception as e: logger.error(f"创建硬件失败: {e}") return 0 # 在主线程中执行硬件创建 count = create_hardware() if count > 0: logger.info(f"✅ 成功创建硬件: uid={uid}, count={count}") else: logger.error(f"❌ 硬件创建失败: uid={uid}") except Exception as e: logger.error(f"❌ 添加硬件失败: {e}") def _load_hardware_file(self, file_path, item, ps, pe): """加载硬件文件""" try: # 在实际应用中需要实现文件加载逻辑 # 这里创建占位符 elem = bpy.data.objects.new( f"Hardware_{item.get('uid', 'unknown')}", None) bpy.context.scene.collection.objects.link(elem) # 设置缩放 if ps and pe: distance = math.sqrt((pe.x - ps.x)**2 + (pe.y - ps.y)**2 + (pe.z - ps.z)**2) elem.scale = (distance, 1.0, 1.0) # 应用变换 if "trans" in item: trans = Transformation.parse(item["trans"]) self._apply_transformation(elem, trans) return elem except Exception as e: logger.error(f"加载硬件文件失败: {e}") return None def _create_simple_hardware(self, ps, pe, item): """创建简单硬件几何体""" try: elem = bpy.data.objects.new("Simple_Hardware", None) bpy.context.scene.collection.objects.link(elem) # 创建路径 path = self._create_line_path(ps, pe) # 创建截面 sect = item.get("sect", {}) color = item.get("ckey") self._follow_me(elem, sect, path, color) elem["sw_ckey"] = color return elem except Exception as e: logger.error(f"创建简单硬件失败: {e}") return None # ==================== 加工管理方法 ==================== def c05(self, data: Dict[str, Any]): """c05 - 添加加工 - 批量优化版本""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过加工创建") return uid = data.get("uid") items = data.get("items", []) logger.info(f"🔧 开始批量创建加工: uid={uid}, 项目数={len(items)}") def create_machining_batch(): try: # 获取部件和硬件集合 parts = self.get_parts(data) hardwares = self.get_hardwares(data) # 初始化加工集合 if uid not in self.machinings: self.machinings[uid] = [] machinings = self.machinings[uid] # 分类处理:可视化加工 vs 布尔运算 visual_works = [] boolean_works = [] for i, work in enumerate(items): if work.get("cancel", 0) == 1: continue cp = work.get("cp") if not cp: continue # 获取组件 component = None if cp in parts: component = parts[cp] elif cp in hardwares: component = hardwares[cp] if not component or not self._is_object_valid(component): logger.info( f"🚨 组件查找失败: cp={cp}, component={component}") continue work['component'] = component work['index'] = i if work.get("trim3d", 0) == 1: boolean_works.append(work) else: visual_works.append(work) created_count = 0 # 1. 批量处理可视化加工 if visual_works: created_count += self._create_visual_machining_batch( visual_works, machinings) # 2. 批量处理布尔运算 if boolean_works: created_count += self._create_boolean_machining_batch( boolean_works) logger.info(f"📊 批量加工创建完成: {created_count}/{len(items)} 成功") return created_count except Exception as e: logger.error(f"❌ 批量创建加工失败: {e}") import traceback logger.error(traceback.format_exc()) return 0 # 直接执行创建(已经在主线程中) count = create_machining_batch() logger.info(f"🎉 c05命令完成,创建了 {count} 个加工对象") return count except Exception as e: logger.error(f"❌ c05命令失败: {e}") import traceback logger.error(traceback.format_exc()) return 0 def _create_visual_machining_batch(self, visual_works, machinings): """批量创建可视化加工对象""" try: import bmesh created_count = 0 # 按组件分组,同一组件的加工可以批量创建 component_groups = {} for work in visual_works: component = work['component'] if component not in component_groups: component_groups[component] = [] component_groups[component].append(work) for component, works in component_groups.items(): logger.info(f"🔨 为组件 {component.name} 批量创建 {len(works)} 个加工对象") # 创建主加工组 main_machining = bpy.data.objects.new( f"Machining_{component.name}", None) bpy.context.scene.collection.objects.link(main_machining) main_machining.parent = component main_machining["sw_typ"] = "work" # 【创建记录标准化】为c0a对称删除做准备 import time creation_record = { "type": "visual_batch", "main_machining": main_machining.name, "geometry_objects": [], "material_applied": None, "created_timestamp": time.time() } machinings.append(main_machining) # 使用bmesh批量创建所有几何体 bm = bmesh.new() for work in works: try: # 解析坐标 p1 = self._parse_point3d(work.get("p1", "(0,0,0)")) p2 = self._parse_point3d(work.get("p2", "(0,0,0)")) # 根据类型创建几何体 if "tri" in work: self._add_triangle_to_bmesh(bm, work, p1, p2) elif "surf" in work: self._add_surface_to_bmesh(bm, work, p1, p2) else: self._add_circle_to_bmesh(bm, work, p1, p2) created_count += 1 except Exception as e: logger.error(f"创建单个加工几何体失败: {e}") # 一次性创建网格 if bm.verts: mesh = bpy.data.meshes.new( f"MachiningMesh_{component.name}") bm.to_mesh(mesh) mesh.update() # 创建对象 mesh_obj = bpy.data.objects.new( f"MachiningGeometry_{component.name}", mesh) bpy.context.scene.collection.objects.link(mesh_obj) mesh_obj.parent = main_machining # 【创建记录】记录几何体对象 creation_record["geometry_objects"].append(mesh_obj.name) # 设置材质 material = self.get_texture("mat_machine") if material and mesh_obj.data: mesh_obj.data.materials.clear() mesh_obj.data.materials.append(material) # 【创建记录】记录材质 creation_record["material_applied"] = "mat_machine" # 【创建记录】存储到主加工组 main_machining["sw_creation_record"] = creation_record # 注册到内存管理器 memory_manager.register_object(mesh_obj) memory_manager.register_mesh(mesh) bm.free() memory_manager.register_object(main_machining) return created_count except Exception as e: logger.error(f"批量创建可视化加工失败: {e}") return 0 def _create_boolean_machining_batch(self, boolean_works): """批量创建布尔运算加工 - 关键优化""" try: import bmesh logger.info(f"🔨 开始批量布尔运算: {len(boolean_works)} 个项目") # 按组件分组 component_groups = {} for work in boolean_works: component = work['component'] if component not in component_groups: component_groups[component] = [] component_groups[component].append(work) success_count = 0 for component, works in component_groups.items(): logger.info(f"🔨 为组件 {component.name} 处理 {len(works)} 个布尔运算") # 🎯 关键优化:按类型分组裁剪体 circle_trimmers = [] triangle_trimmers = [] surface_trimmers = [] # 使用bmesh批量创建裁剪体 for work in works: try: p1 = self._parse_point3d(work.get("p1", "(0,0,0)")) p2 = self._parse_point3d(work.get("p2", "(0,0,0)")) if "tri" in work: trimmer_data = self._create_triangle_trimmer_data( work, p1, p2) triangle_trimmers.append(trimmer_data) elif "surf" in work: trimmer_data = self._create_surface_trimmer_data( work, p1, p2) surface_trimmers.append(trimmer_data) else: trimmer_data = self._create_circle_trimmer_data( work, p1, p2) circle_trimmers.append(trimmer_data) except Exception as e: logger.error(f"创建裁剪体数据失败: {e}") # 🚀 超级优化:合并同类型裁剪体 unified_trimmers = [] if circle_trimmers: unified_trimmer = self._create_unified_circle_trimmer( circle_trimmers, component.name) if unified_trimmer: unified_trimmers.append(unified_trimmer) if triangle_trimmers: unified_trimmer = self._create_unified_triangle_trimmer( triangle_trimmers, component.name) if unified_trimmer: unified_trimmers.append(unified_trimmer) if surface_trimmers: unified_trimmer = self._create_unified_surface_trimmer( surface_trimmers, component.name) if unified_trimmer: unified_trimmers.append(unified_trimmer) # 获取所有需要被切割的板材 target_boards = [] for child in component.children: if child.get("sw_typ") == "board" or "Board" in child.name: target_boards.append(child) if target_boards and unified_trimmers: # 批量应用布尔运算 for unified_trimmer in unified_trimmers: if self._apply_batch_boolean(target_boards, unified_trimmer): success_count += 1 logger.info( f"✅ 统一裁剪体布尔运算成功: {unified_trimmer.name}") else: logger.warning( f"⚠️ 统一裁剪体布尔运算失败: {unified_trimmer.name}") # 清理裁剪体 for trimmer in unified_trimmers: if trimmer and trimmer.name in bpy.data.objects: bpy.data.objects.remove(trimmer, do_unlink=True) logger.info(f"📊 批量布尔运算完成: {success_count} 个成功") return success_count except Exception as e: logger.error(f"批量布尔运算失败: {e}") return 0 def _add_circle_to_bmesh(self, bm, work, p1, p2): """向bmesh添加圆形几何体 - 使用mathutils正确旋转""" try: import bmesh dia = work.get("dia", 5.0) radius = dia * 0.001 / 2.0 # 计算方向和位置 if BLENDER_AVAILABLE: import mathutils # 转换为mathutils.Vector p1_vec = mathutils.Vector(p1) p2_vec = mathutils.Vector(p2) # 计算方向和长度 direction = p2_vec - p1_vec length = direction.length midpoint = (p1_vec + p2_vec) / 2 if length < 0.0001: logger.warning("圆柱体长度过短,跳过创建") return logger.debug(f"🔧 创建圆柱体: 半径={radius:.3f}, 长度={length:.3f}") # 计算旋转矩阵 - 将Z轴对齐到加工方向 # 使用rotation_difference计算精确旋转,避免万向节锁 z_axis = mathutils.Vector((0, 0, 1)) rotation_quat = z_axis.rotation_difference( direction.normalized()) rotation_matrix = rotation_quat.to_matrix().to_4x4() # 组合变换矩阵: 先旋转,再平移 translation_matrix = mathutils.Matrix.Translation(midpoint) final_transform_matrix = translation_matrix @ rotation_matrix # 在临时bmesh中创建标准圆柱体 temp_bm = bmesh.new() bmesh.ops.create_cone( temp_bm, cap_ends=True, # 生成端盖 cap_tris=False, # 端盖用 n 边而非三角 segments=12, radius1=radius, radius2=radius, # 与 radius1 相同 → 圆柱 depth=length ) # 应用变换矩阵 bmesh.ops.transform( temp_bm, matrix=final_transform_matrix, verts=temp_bm.verts) # 将变换后的几何体合并到主bmesh vert_map = {} for v in temp_bm.verts: new_v = bm.verts.new(v.co) vert_map[v] = new_v for f in temp_bm.faces: bm.faces.new(tuple(vert_map[v] for v in f.verts)) temp_bm.free() logger.debug( f"✅ 圆柱体变换完成: 世界坐标中点({midpoint.x:.3f}, {midpoint.y:.3f}, {midpoint.z:.3f})") else: # 非Blender环境的简化版本 direction = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]) length = (direction[0]**2 + direction[1] ** 2 + direction[2]**2)**0.5 center = ((p1[0] + p2[0])/2, (p1[1] + p2[1]) / 2, (p1[2] + p2[2])/2) # 创建圆柱体(简化版本,不做旋转) bmesh.ops.create_cone( bm, cap_ends=True, cap_tris=False, segments=12, radius1=radius, radius2=radius, depth=max(length, 0.01) ) # 移动到正确位置 bmesh.ops.translate( bm, vec=center, verts=bm.verts[-24:] # 圆柱体的顶点 ) except Exception as e: logger.error(f"添加圆形到bmesh失败: {e}") import traceback logger.error(traceback.format_exc()) def _add_triangle_to_bmesh(self, bm, work, p1, p2): """向bmesh添加三角形几何体""" try: tri = self._parse_point3d(work.get("tri", "(0,0,0)")) # 创建三角形顶点 v1 = bm.verts.new(tri) v2 = bm.verts.new( (tri[0] + p2[0] - p1[0], tri[1] + p2[1] - p1[1], tri[2] + p2[2] - p1[2])) v3 = bm.verts.new( (p1[0] + p1[0] - tri[0], p1[1] + p1[1] - tri[1], p1[2] + p1[2] - tri[2])) # 创建面 bm.faces.new([v1, v2, v3]) except Exception as e: logger.error(f"添加三角形到bmesh失败: {e}") def _add_surface_to_bmesh(self, bm, work, p1, p2): """向bmesh添加表面几何体""" try: surf = work.get("surf") if not surf: return # 解析表面顶点 vertices = self._parse_surface_vertices(surf) if len(vertices) < 3: return # 添加顶点到bmesh bm_verts = [] for vertex in vertices: bm_verts.append(bm.verts.new(vertex)) # 创建面 if len(bm_verts) >= 3: bm.faces.new(bm_verts) except Exception as e: logger.error(f"添加表面到bmesh失败: {e}") def _create_unified_circle_trimmer(self, circle_data_list, component_name): """创建统一的圆形裁剪体 - 使用mathutils正确旋转""" try: if not BLENDER_AVAILABLE: return None import bmesh if not circle_data_list: return None logger.info(f"🔄 合并 {len(circle_data_list)} 个圆形裁剪体") # 创建bmesh bm = bmesh.new() # 批量添加所有圆柱体 for circle_data in circle_data_list: try: # 使用临时bmesh创建单个圆柱体 temp_bm = bmesh.new() # 创建标准圆柱体(在原点,沿Z轴) bmesh.ops.create_cone( temp_bm, cap_ends=True, # 生成端盖 cap_tris=False, # 端盖用 n 边而非三角 segments=12, radius1=circle_data['radius'], radius2=circle_data['radius'], # 与 radius 相同 → 圆柱 depth=circle_data['depth'] ) # 应用旋转和平移变换 if circle_data.get('rotation'): # 组合变换矩阵:先旋转,再平移 import mathutils translation_matrix = mathutils.Matrix.Translation( circle_data['location']) combined_matrix = translation_matrix @ circle_data['rotation'] else: # 只有平移 import mathutils combined_matrix = mathutils.Matrix.Translation( circle_data['location']) # 一次性应用完整变换 bmesh.ops.transform( temp_bm, matrix=combined_matrix, verts=temp_bm.verts) # 合并到主bmesh vert_map = {} for v in temp_bm.verts: new_v = bm.verts.new(v.co) vert_map[v] = new_v for f in temp_bm.faces: bm.faces.new(tuple(vert_map[v] for v in f.verts)) temp_bm.free() except Exception as e: logger.error(f"添加单个圆柱体失败: {e}") if not bm.verts: bm.free() return None # 🚀 关键:合并重叠的几何体 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) # 创建最终的统一裁剪体 mesh = bpy.data.meshes.new( f"UnifiedCircleTrimmer_{component_name}") bm.to_mesh(mesh) mesh.update() bm.free() trimmer_obj = bpy.data.objects.new( f"UnifiedCircleTrimmer_{component_name}", mesh) bpy.context.scene.collection.objects.link(trimmer_obj) trimmer_obj["sw_typ"] = "trimmer" trimmer_obj["sw_temporary"] = True memory_manager.register_object(trimmer_obj) memory_manager.register_mesh(mesh) return trimmer_obj except Exception as e: logger.error(f"创建统一圆形裁剪体失败: {e}") return None def _create_circle_trimmer_data(self, work, p1, p2): """创建圆形裁剪体数据 - 使用mathutils计算精确旋转""" try: dia = work.get("dia", 5.0) radius = dia * 0.001 / 2.0 # 计算长度和位置 if BLENDER_AVAILABLE: import mathutils # 转换为mathutils.Vector p1_vec = mathutils.Vector(p1) p2_vec = mathutils.Vector(p2) # 计算方向和长度 direction = p2_vec - p1_vec length = direction.length location = (p1_vec + p2_vec) / 2 # 计算旋转矩阵 rotation = None if length > 0.001: # 使用rotation_difference计算精确旋转(与_add_circle_to_bmesh保持一致) z_axis = mathutils.Vector((0, 0, 1)) rotation_quat = z_axis.rotation_difference( direction.normalized()) rotation = rotation_quat.to_matrix().to_4x4() else: # 长度过短,使用单位矩阵 rotation = mathutils.Matrix.Identity(4) return { 'radius': radius, 'depth': max(length, 0.01), 'location': location, 'rotation': rotation } else: # 模拟环境:简化计算 direction = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]) length = (direction[0]**2 + direction[1] ** 2 + direction[2]**2)**0.5 location = ((p1[0] + p2[0])/2, (p1[1] + p2[1])/2, (p1[2] + p2[2])/2) return { 'radius': radius, 'depth': max(length, 0.01), 'location': location, 'rotation': None } except Exception as e: logger.error(f"创建圆形裁剪体数据失败: {e}") return None def _create_unified_triangle_trimmer(self, triangle_data_list, component_name): """创建统一的三角形裁剪体""" try: import bmesh if not triangle_data_list: return None logger.info(f"🔄 合并 {len(triangle_data_list)} 个三角形裁剪体") bm = bmesh.new() # 批量添加所有三角形 for tri_data in triangle_data_list: try: vertices = tri_data['vertices'] if len(vertices) >= 3: bm_verts = [] for vertex in vertices: bm_verts.append(bm.verts.new(vertex)) bm.faces.new(bm_verts) except Exception as e: logger.error(f"添加单个三角形失败: {e}") if not bm.verts: bm.free() return None # 挤出三角形形成体积 bmesh.ops.solidify(bm, geom=bm.faces[:], thickness=0.01) # 创建最终对象 mesh = bpy.data.meshes.new( f"UnifiedTriangleTrimmer_{component_name}") bm.to_mesh(mesh) mesh.update() bm.free() trimmer_obj = bpy.data.objects.new( f"UnifiedTriangleTrimmer_{component_name}", mesh) bpy.context.scene.collection.objects.link(trimmer_obj) trimmer_obj["sw_typ"] = "trimmer" trimmer_obj["sw_temporary"] = True memory_manager.register_object(trimmer_obj) memory_manager.register_mesh(mesh) return trimmer_obj except Exception as e: logger.error(f"创建统一三角形裁剪体失败: {e}") return None def _create_triangle_trimmer_data(self, work, p1, p2): """创建三角形裁剪体数据""" try: tri = self._parse_point3d(work.get("tri", "(0,0,0)")) vertices = [ tri, (tri[0] + p2[0] - p1[0], tri[1] + p2[1] - p1[1], tri[2] + p2[2] - p1[2]), (p1[0] + p1[0] - tri[0], p1[1] + p1[1] - tri[1], p1[2] + p1[2] - tri[2]) ] return { 'vertices': vertices } except Exception as e: logger.error(f"创建三角形裁剪体数据失败: {e}") return None def _create_unified_surface_trimmer(self, surface_data_list, component_name): """创建统一的表面裁剪体""" try: import bmesh if not surface_data_list: return None logger.info(f"🔄 合并 {len(surface_data_list)} 个表面裁剪体") bm = bmesh.new() # 批量添加所有表面 for surf_data in surface_data_list: try: vertices = surf_data['vertices'] if len(vertices) >= 3: bm_verts = [] for vertex in vertices: bm_verts.append(bm.verts.new(vertex)) bm.faces.new(bm_verts) except Exception as e: logger.error(f"添加单个表面失败: {e}") if not bm.verts: bm.free() return None # 挤出表面形成体积 bmesh.ops.solidify(bm, geom=bm.faces[:], thickness=0.01) # 创建最终对象 mesh = bpy.data.meshes.new( f"UnifiedSurfaceTrimmer_{component_name}") bm.to_mesh(mesh) mesh.update() bm.free() trimmer_obj = bpy.data.objects.new( f"UnifiedSurfaceTrimmer_{component_name}", mesh) bpy.context.scene.collection.objects.link(trimmer_obj) trimmer_obj["sw_typ"] = "trimmer" trimmer_obj["sw_temporary"] = True memory_manager.register_object(trimmer_obj) memory_manager.register_mesh(mesh) return trimmer_obj except Exception as e: logger.error(f"创建统一表面裁剪体失败: {e}") return None def _create_surface_trimmer_data(self, work, p1, p2): """创建表面裁剪体数据""" try: surf = work.get("surf") if not surf: return None vertices = self._parse_surface_vertices(surf) if len(vertices) < 3: return None return { 'vertices': vertices } except Exception as e: logger.error(f"创建表面裁剪体数据失败: {e}") return None def _apply_batch_boolean(self, target_boards, unified_trimmer): """批量应用布尔运算 - 最终优化""" try: if not target_boards or not unified_trimmer: return False logger.info(f"🔨 对 {len(target_boards)} 个板材应用统一布尔运算") modifier_name = "SUWood_BatchBoolean" # 为所有目标板材添加布尔修改器 for board in target_boards: try: # 避免重复添加 if modifier_name not in board.modifiers: mod = board.modifiers.new( name=modifier_name, type='BOOLEAN') mod.operation = 'DIFFERENCE' mod.object = unified_trimmer mod.solver = 'EXACT' # 更稳定 except Exception as e: logger.error(f"为板材 {board.name} 添加布尔修改器失败: {e}") # 批量应用修改器 bpy.ops.object.select_all(action='DESELECT') for board in target_boards: board.select_set(True) if target_boards: bpy.context.view_layer.objects.active = target_boards[0] # 逐个应用修改器(确保稳定性) for board in target_boards: try: bpy.context.view_layer.objects.active = board if modifier_name in board.modifiers: bpy.ops.object.modifier_apply( modifier=modifier_name) except Exception as e: logger.error(f"应用板材 {board.name} 布尔修改器失败: {e}") # 移除失败的修改器 if modifier_name in board.modifiers: board.modifiers.remove( board.modifiers[modifier_name]) return True except Exception as e: logger.error(f"批量布尔运算失败: {e}") return False def _create_machining_visual(self, component, work, index): """创建加工可视化对象 - 参考Ruby实现""" try: cp = work.get("cp") special = work.get("special", 0) # 创建加工组 machining_name = f"Machining_{cp}_{index}" machining = bpy.data.objects.new(machining_name, None) bpy.context.scene.collection.objects.link(machining) # 设置父子关系 machining.parent = component # 设置属性 machining["sw_typ"] = "work" machining["sw_special"] = special machining["sw_cp"] = cp # 解析坐标 p1 = self._parse_point3d(work.get("p1", "(0,0,0)")) p2 = self._parse_point3d(work.get("p2", "(0,0,0)")) # 创建路径 path = self._create_machining_path(p1, p2) # 创建面 face_mesh = None if "tri" in work: # 三角形加工 face_mesh = self._create_triangle_machining( machining, work, p1, p2) elif "surf" in work: # 表面加工 face_mesh = self._create_surface_machining( machining, work, path) else: # 圆形加工(钻孔) face_mesh = self._create_circle_machining( machining, work, p1, p2, path) if face_mesh: # 设置材质 if special == 0 and work.get("cancel", 0) == 0: material = self.get_texture("mat_machine") if material and face_mesh.data: face_mesh.data.materials.clear() face_mesh.data.materials.append(material) # 执行Follow Me操作 self._apply_follow_me_to_machining(face_mesh, path) # 清理路径 if path: self._cleanup_path(path) # 注册到内存管理器 memory_manager.register_object(machining) if face_mesh: memory_manager.register_object(face_mesh) return machining except Exception as e: logger.error(f"创建加工可视化失败: {e}") return None def _create_triangle_machining(self, machining, work, p1, p2): """创建三角形加工""" try: tri = self._parse_point3d(work.get("tri", "(0,0,0)")) p3 = self._parse_point3d(work.get("p3", "(0,0,0)")) # 创建三角形面 vertices = [tri, (tri[0] + p2[0] - p1[0], tri[1] + p2[1] - p1[1], tri[2] + p2[2] - p1[2]), (p1[0] + p1[0] - tri[0], p1[1] + p1[1] - tri[1], p1[2] + p1[2] - tri[2])] mesh = bpy.data.meshes.new(f"TriMachining_{machining.name}") mesh.from_pydata(vertices, [], [(0, 1, 2)]) mesh.update() face_obj = bpy.data.objects.new(f"TriFace_{machining.name}", mesh) bpy.context.scene.collection.objects.link(face_obj) face_obj.parent = machining return face_obj except Exception as e: logger.error(f"创建三角形加工失败: {e}") return None def _create_surface_machining(self, machining, work, path): """创建表面加工""" try: surf = work.get("surf") if not surf: return None # 使用现有的create_face_safe方法 face_obj = self.create_face_safe(machining, surf) return face_obj except Exception as e: logger.error(f"创建表面加工失败: {e}") return None def _create_circle_machining(self, machining, work, p1, p2, path): """创建圆形加工(钻孔)""" try: dia = work.get("dia", 5.0) # 默认5mm直径 radius = dia * 0.001 / 2.0 # 转换为米并计算半径 # 计算方向向量 direction = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]) length = (direction[0]**2 + direction[1]**2 + direction[2]**2)**0.5 if length < 0.001: # 避免零长度 direction = (0, 0, 1) length = 1.0 else: direction = (direction[0]/length, direction[1]/length, direction[2]/length) # 创建圆形网格 bpy.ops.mesh.primitive_circle_add( radius=radius, location=p1, vertices=16 ) circle_obj = bpy.context.active_object circle_obj.name = f"CircleMachining_{machining.name}" circle_obj.parent = machining # 设置方向(简化版本,避免复杂的旋转计算) if abs(direction[2]) < 0.99: # 不是垂直方向 # 简单的方向设置,避免mathutils导入 circle_obj.rotation_euler = (0, 1.5708, 0) # 90度旋转 return circle_obj except Exception as e: logger.error(f"创建圆形加工失败: {e}") return None def _create_machining_path(self, p1, p2): """创建加工路径""" try: # 创建简单的线段路径 mesh = bpy.data.meshes.new("MachiningPath") mesh.from_pydata([p1, p2], [(0, 1)], []) mesh.update() path_obj = bpy.data.objects.new("MachiningPath", mesh) bpy.context.scene.collection.objects.link(path_obj) return path_obj except Exception as e: logger.error(f"创建加工路径失败: {e}") return None def _apply_follow_me_to_machining(self, face_obj, path): """对加工对象应用Follow Me操作""" try: if not face_obj or not path: return # 选择面对象 bpy.context.view_layer.objects.active = face_obj face_obj.select_set(True) # 进入编辑模式 bpy.ops.object.mode_set(mode='EDIT') # 选择所有面 bpy.ops.mesh.select_all(action='SELECT') # 添加螺旋修改器来模拟Follow Me bpy.ops.object.mode_set(mode='OBJECT') # 添加Array修改器沿路径复制 array_mod = face_obj.modifiers.new( name="MachiningArray", type='ARRAY') array_mod.fit_type = 'FIT_LENGTH' array_mod.fit_length = self._calculate_path_length(path) array_mod.count = max( 2, int(array_mod.fit_length / 0.01)) # 每1cm一个 # 应用修改器 bpy.context.view_layer.objects.active = face_obj bpy.ops.object.modifier_apply(modifier="MachiningArray") except Exception as e: logger.error(f"应用Follow Me失败: {e}") def _calculate_path_length(self, path): """计算路径长度""" try: if not path or not path.data: return 0.01 # 获取顶点 vertices = path.data.vertices if len(vertices) < 2: return 0.01 # 计算两点间距离 p1 = vertices[0].co p2 = vertices[1].co distance = ((p2[0] - p1[0])**2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2])**2)**0.5 return max(distance, 0.01) except Exception as e: logger.error(f"计算路径长度失败: {e}") return 0.01 def _work_trimmed(self, component, work): """执行布尔运算加工 - 最快的布尔实现""" try: logger.info(f"🔨 开始布尔运算: component={component.name}") # 获取组件的所有子对象(板材) boards = [] for child in component.children: if child.get("sw_typ") == "board" or "Board" in child.name: boards.append(child) if not boards: logger.warning("没有找到可以进行布尔运算的板材") return False # 创建布尔切削器 trimmer = self._create_boolean_trimmer(component, work) if not trimmer: logger.error("创建布尔切削器失败") return False # 对每个板材执行布尔运算 success_count = 0 for board in boards: try: # 执行最快的布尔运算 if self._apply_fast_boolean(board, trimmer, work): success_count += 1 logger.info(f"✅ 板材 {board.name} 布尔运算成功") else: logger.warning(f"⚠️ 板材 {board.name} 布尔运算失败") except Exception as e: logger.error(f"板材 {board.name} 布尔运算异常: {e}") # 清理切削器 self._cleanup_trimmer(trimmer) logger.info(f"📊 布尔运算统计: {success_count}/{len(boards)} 成功") return success_count > 0 except Exception as e: logger.error(f"布尔运算失败: {e}") return False def _create_boolean_trimmer(self, component, work): """创建布尔切削器""" try: # 解析坐标 p1 = self._parse_point3d(work.get("p1", "(0,0,0)")) p2 = self._parse_point3d(work.get("p2", "(0,0,0)")) # 创建切削器对象 trimmer_name = f"Trimmer_{component.name}" if "tri" in work: # 三角形切削器 trimmer = self._create_triangle_trimmer( trimmer_name, work, p1, p2) elif "surf" in work: # 表面切削器 trimmer = self._create_surface_trimmer(trimmer_name, work) else: # 圆形切削器 trimmer = self._create_circle_trimmer( trimmer_name, work, p1, p2) if trimmer: # 设置属性 trimmer["sw_typ"] = "trimmer" trimmer["sw_temporary"] = True # 注册到内存管理器 memory_manager.register_object(trimmer) return trimmer except Exception as e: logger.error(f"创建布尔切削器失败: {e}") return None def _create_circle_trimmer(self, name, work, p1, p2): """创建圆形切削器""" try: dia = work.get("dia", 5.0) radius = dia * 0.001 / 2.0 # 转换为米 # 计算长度 length = ((p2[0] - p1[0])**2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2])**2)**0.5 length = max(length, 0.01) # 最小1cm # 创建圆柱体 bpy.ops.mesh.primitive_cylinder_add( radius=radius, depth=length, location=((p1[0] + p2[0])/2, (p1[1] + p2[1]) / 2, (p1[2] + p2[2])/2) ) trimmer = bpy.context.active_object trimmer.name = name # 设置方向 direction = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]) if length > 0.001: direction = (direction[0]/length, direction[1]/length, direction[2]/length) # 设置旋转使圆柱体沿正确方向(简化版本) if abs(direction[2]) < 0.99: # 简单的方向设置,避免mathutils导入 trimmer.rotation_euler = (0, 1.5708, 0) # 90度旋转 return trimmer except Exception as e: logger.error(f"创建圆形切削器失败: {e}") return None def _apply_fast_boolean(self, board, trimmer, work): """应用最快的布尔运算""" try: # 选择板材 bpy.context.view_layer.objects.active = board board.select_set(True) trimmer.select_set(True) # 添加布尔修改器 bool_mod = board.modifiers.new( name="BooleanTrimmer", type='BOOLEAN') bool_mod.operation = 'DIFFERENCE' # 差集操作 bool_mod.object = trimmer bool_mod.solver = 'FAST' # 使用快速求解器 # 应用修改器 bpy.context.view_layer.objects.active = board bpy.ops.object.modifier_apply(modifier="BooleanTrimmer") # 标记差异面(如果需要) if work.get("differ", False): self._mark_differ_faces(board) return True except Exception as e: logger.error(f"应用布尔运算失败: {e}") return False def _parse_point3d(self, point_str): """解析3D点坐标""" try: if not point_str: return (0, 0, 0) # 移除括号并分割 coords = point_str.strip('()').split(',') if len(coords) >= 3: x = float(coords[0].strip()) * 0.001 # mm转米 y = float(coords[1].strip()) * 0.001 z = float(coords[2].strip()) * 0.001 return (x, y, z) else: return (0, 0, 0) except Exception as e: logger.error(f"解析3D点失败: {point_str}, {e}") return (0, 0, 0) def _create_surface_trimmer(self, name, work): """创建表面切削器""" try: surf = work.get("surf") if not surf: return None # 使用现有的create_face_safe方法创建表面 trimmer = bpy.data.objects.new(name, None) bpy.context.scene.collection.objects.link(trimmer) face_obj = self.create_face_safe(trimmer, surf) if face_obj: face_obj.parent = trimmer return trimmer except Exception as e: logger.error(f"创建表面切削器失败: {e}") return None def _create_triangle_trimmer(self, name, work, p1, p2): """创建三角形切削器""" try: tri = self._parse_point3d(work.get("tri", "(0,0,0)")) p3 = self._parse_point3d(work.get("p3", "(0,0,0)")) # 创建三角形网格 vertices = [tri, (tri[0] + p2[0] - p1[0], tri[1] + p2[1] - p1[1], tri[2] + p2[2] - p1[2]), (p1[0] + p1[0] - tri[0], p1[1] + p1[1] - tri[1], p1[2] + p1[2] - tri[2])] mesh = bpy.data.meshes.new(f"TriTrimmer_{name}") mesh.from_pydata(vertices, [], [(0, 1, 2)]) mesh.update() trimmer = bpy.data.objects.new(name, mesh) bpy.context.scene.collection.objects.link(trimmer) return trimmer except Exception as e: logger.error(f"创建三角形切削器失败: {e}") return None def _set_machining_color(self, machining, item): """设置加工对象的颜色编码""" try: dia = item.get("dia", 0) special = item.get("special", 0) # 根据直径和特殊标记设置颜色 if special == 1: # 特殊加工 - 红色 machining.color = (1.0, 0.2, 0.2, 1.0) elif "surf" in item: # 表面加工 - 蓝色 machining.color = (0.2, 0.5, 1.0, 1.0) elif dia <= 5: # 小直径钻孔 - 绿色 machining.color = (0.2, 1.0, 0.2, 1.0) elif dia <= 10: # 中等直径钻孔 - 黄色 machining.color = (1.0, 1.0, 0.2, 1.0) else: # 大直径钻孔 - 橙色 machining.color = (1.0, 0.6, 0.2, 1.0) except Exception as e: logger.error(f"设置加工颜色失败: {e}") def _create_ultra_simple_machining(self, parent_part, item): """创建超级简单的加工对象 - 避免卡住""" try: # 解析坐标 p1_str = item.get("p1", "(0,0,0)") p2_str = item.get("p2", "(0,0,0)") # 简单解析坐标字符串 def parse_coord(coord_str): coord_str = coord_str.strip('()') try: x, y, z = map(float, coord_str.split(',')) return (x * 0.001, y * 0.001, z * 0.001) # mm转米 except: return (0, 0, 0) p1 = parse_coord(p1_str) p2 = parse_coord(p2_str) # 计算中心点 center = ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2, (p1[2] + p2[2]) / 2) # 获取基本信息 cp = item.get("cp") dia = item.get("dia", 5) # 创建简单的加工对象名称 import time timestamp = int(time.time() * 1000000) % 1000000 # 微秒时间戳 machining_name = f"Machining_{cp}_{timestamp}" # 创建最简单的Empty对象 machining = bpy.data.objects.new(machining_name, None) machining.location = center # 设置简单的显示类型 machining.empty_display_type = 'PLAIN_AXES' machining.empty_display_size = max(0.005, dia * 0.001) # 最小5mm # 添加到场景 bpy.context.scene.collection.objects.link(machining) # 设置父对象 machining.parent = parent_part # 设置基本属性 machining["sw_typ"] = "machining" machining["sw_cp"] = cp machining["sw_diameter"] = dia machining["sw_p1"] = p1_str machining["sw_p2"] = p2_str # 设置简单的颜色 if item.get("cancel", 0) == 1: machining.color = (1.0, 0.0, 0.0, 1.0) # 红色 - 取消 elif "surf" in item: machining.color = (0.0, 0.0, 1.0, 1.0) # 蓝色 - 表面加工 else: machining.color = (0.0, 1.0, 0.0, 1.0) # 绿色 - 钻孔 # 注册到内存管理器 memory_manager.register_object(machining) logger.debug(f"创建简单加工对象: {machining_name} at {center}") return machining except Exception as e: logger.error(f"创建超级简单加工对象失败: {e}") return None # ==================== 几何体创建辅助方法 ==================== def create_face(self, container, surface, color=None, scale=None, angle=None, series=None, reverse_face=False, back_material=True, saved_color=None, typ=None): """创建面""" try: if not BLENDER_AVAILABLE: return None segs = surface.get("segs", []) if not segs: return None # 创建边 edges = self._create_edges(container, segs, series) if not edges: return None # 创建面 face = self._create_face_from_edges(container, edges) if not face: return None # 处理法向量 if "vz" in surface: zaxis = Vector3d.parse(surface["vz"]) if series and "vx" in surface: xaxis = Vector3d.parse(surface["vx"]) if self._should_reverse_face(face, zaxis, reverse_face): self._reverse_face(face) elif reverse_face and self._face_normal_matches(face, zaxis): self._reverse_face(face) # 设置属性 if typ: face["sw_typ"] = typ # 应用材质 if color: self._textured_surf(face, back_material, color, saved_color, scale, angle) else: self._textured_surf(face, back_material, "mat_normal") return face except Exception as e: logger.error(f"创建面失败: {e}") return None def _create_edges(self, container, segments, series=None): """创建边""" try: if not BLENDER_AVAILABLE: return [] edges = [] seg_pts = {} # 解析所有点 for index, segment in enumerate(segments): pts = [] for point_str in segment: point = Point3d.parse(point_str) if point: pts.append((point.x, point.y, point.z)) seg_pts[index] = pts # 创建边 for this_i in range(len(segments)): pts_i = seg_pts[this_i] pts_p = seg_pts[(this_i - 1) % len(segments)] pts_n = seg_pts[(this_i + 1) % len(segments)] if len(pts_i) > 2: # 多点段 if len(pts_p) > 2: prev_p = pts_p[-1] this_p = pts_i[0] if prev_p != this_p: edges.append(self._create_line_edge_simple( container, prev_p, this_p)) # 添加段内边 for j in range(len(pts_i) - 1): edges.append(self._create_line_edge_simple( container, pts_i[j], pts_i[j+1])) if series: series.append(pts_i) else: # 两点段 point_s = pts_p[-1] if len(pts_p) > 2 else pts_i[0] point_e = pts_n[0] if len(pts_n) > 2 else pts_i[-1] edges.append(self._create_line_edge_simple( container, point_s, point_e)) if series: series.append([point_s, point_e]) return [e for e in edges if e] except Exception as e: logger.error(f"创建边失败: {e}") return [] def _create_line_edge_simple(self, container, start, end): """创建简单线边""" try: if not BLENDER_AVAILABLE: return None # 创建网格 mesh = bpy.data.meshes.new("Simple_Edge") vertices = [start, end] edges = [(0, 1)] mesh.from_pydata(vertices, edges, []) mesh.update() # 创建对象 obj = bpy.data.objects.new("Simple_Edge_Obj", mesh) obj.parent = container bpy.context.scene.collection.objects.link(obj) memory_manager.register_mesh(mesh) memory_manager.register_object(obj) return obj except Exception as e: logger.error(f"创建简单线边失败: {e}") return None def _create_face_from_edges(self, container, edges): """从边创建面""" try: if not BLENDER_AVAILABLE or not edges: return None # 收集所有顶点 vertices = [] vertex_map = {} for edge in edges: if hasattr(edge, 'data') and edge.data: for vert in edge.data.vertices: co = tuple(vert.co) if co not in vertex_map: vertex_map[co] = len(vertices) vertices.append(co) if len(vertices) < 3: return None # 创建面网格 mesh = bpy.data.meshes.new("Face_Mesh") faces = [list(range(len(vertices)))] mesh.from_pydata(vertices, [], faces) mesh.update() # 创建对象 obj = bpy.data.objects.new("Face_Obj", mesh) obj.parent = container bpy.context.scene.collection.objects.link(obj) memory_manager.register_mesh(mesh) memory_manager.register_object(obj) return obj except Exception as e: logger.error(f"从边创建面失败: {e}") return None def _create_paths(self, container, segments): """创建路径""" try: if not BLENDER_AVAILABLE: return [] paths = [] for seg in segments: if "c" not in seg: # 直线段 s = Point3d.parse(seg.get("s", "(0,0,0)")) e = Point3d.parse(seg.get("e", "(0,0,0)")) path = self._create_line_path(s, e) if path: paths.append(path) else: # 弧线段 c = Point3d.parse(seg.get("c", "(0,0,0)")) x = Vector3d.parse(seg.get("x", "(1,0,0)")) z = Vector3d.parse(seg.get("z", "(0,0,1)")) r = seg.get("r", 1.0) * 0.001 # mm to meters a1 = seg.get("a1", 0.0) a2 = seg.get("a2", math.pi * 2) n = seg.get("n", 12) arc_paths = self._create_arc_paths(c, x, z, r, a1, a2, n) paths.extend(arc_paths) return paths except Exception as e: logger.error(f"创建路径失败: {e}") return [] def _create_line_path(self, start, end): """创建直线路径""" try: if not BLENDER_AVAILABLE: return None mesh = bpy.data.meshes.new("Line_Path") vertices = [(start.x, start.y, start.z), (end.x, end.y, end.z)] edges = [(0, 1)] mesh.from_pydata(vertices, edges, []) mesh.update() obj = bpy.data.objects.new("Line_Path_Obj", mesh) bpy.context.scene.collection.objects.link(obj) memory_manager.register_mesh(mesh) memory_manager.register_object(obj) return obj except Exception as e: logger.error(f"创建直线路径失败: {e}") return None def _create_arc_path(self, container, center_o, center_r): """创建弧形路径""" try: if not BLENDER_AVAILABLE: return None mesh = bpy.data.meshes.new("Arc_Path") vertices = [(center_o.x, center_o.y, center_o.z), (center_r.x, center_r.y, center_r.z)] edges = [(0, 1)] mesh.from_pydata(vertices, edges, []) mesh.update() obj = bpy.data.objects.new("Arc_Path_Obj", mesh) obj.parent = container bpy.context.scene.collection.objects.link(obj) memory_manager.register_mesh(mesh) memory_manager.register_object(obj) return obj except Exception as e: logger.error(f"创建弧形路径失败: {e}") return None def _follow_me(self, container, surface, path, color, scale=None, angle=None, reverse_face=True, series=None, saved_color=None): """跟随路径创建几何体""" try: if not BLENDER_AVAILABLE: return None # 创建截面 face = self.create_face(container, surface, color, scale, angle, series, reverse_face, self.back_material, saved_color) if not face: return None # 获取法向量 normal = self._get_face_normal(face) # 执行跟随操作(在Blender中需要使用修改器或其他方法) self._apply_follow_me(face, path) # 隐藏边 self._hide_edges(container) # 清理路径 if isinstance(path, list): for p in path: self._cleanup_path(p) else: self._cleanup_path(path) return normal except Exception as e: logger.error(f"跟随路径失败: {e}") return None # ==================== 材质和纹理处理方法 ==================== def _textured_surf(self, face, back_material, color, saved_color=None, scale_a=None, angle_a=None): """为表面应用纹理""" try: if not BLENDER_AVAILABLE or not face: return # 保存属性 if saved_color: face["sw_ckey"] = saved_color if scale_a: face["sw_scale"] = scale_a if angle_a: face["sw_angle"] = angle_a # 获取材质 texture = self.get_texture(color) if not texture: return # 应用材质 if hasattr(face, 'data') and face.data: if not face.data.materials: face.data.materials.append(texture) else: face.data.materials[0] = texture # 设置背面材质 if back_material or texture.get("alpha", 1.0) < 1.0: # 在Blender中设置背面材质 pass # 应用纹理变换 if face.get("sw_ckey") == color: scale = face.get("sw_scale") angle = face.get("sw_angle") if (scale or angle) and not face.get("sw_rt"): self._rotate_texture(face, scale, angle) face["sw_rt"] = True except Exception as e: logger.error(f"应用表面纹理失败: {e}") def _rotate_texture(self, face, scale, angle): """旋转纹理""" try: if not BLENDER_AVAILABLE or not face: return # 在Blender中实现纹理旋转 # 这需要操作UV坐标 if hasattr(face, 'data') and face.data: # 获取UV层 if face.data.uv_layers: uv_layer = face.data.uv_layers[0] # 应用缩放和旋转变换 self._apply_uv_transform(uv_layer, scale, angle) except Exception as e: logger.error(f"旋转纹理失败: {e}") def _apply_uv_transform(self, uv_layer, scale, angle): """应用UV变换""" try: if not scale and not angle: return scale_factor = scale if scale else 1.0 angle_rad = math.radians(angle) if angle else 0.0 # 变换UV坐标 for loop in uv_layer.data: u, v = loop.uv # 应用缩放 u *= scale_factor v *= scale_factor # 应用旋转 if angle_rad != 0: cos_a = math.cos(angle_rad) sin_a = math.sin(angle_rad) new_u = u * cos_a - v * sin_a new_v = u * sin_a + v * cos_a u, v = new_u, new_v loop.uv = (u, v) except Exception as e: logger.error(f"应用UV变换失败: {e}") # ==================== 选择管理方法 ==================== def sel_clear(self): """清除选择 - 优化版本,避免阻塞界面""" try: if BLENDER_AVAILABLE: # 【修复】使用非阻塞的直接属性操作,而不是阻塞性操作符 try: for obj in bpy.data.objects: if hasattr(obj, 'select_set'): obj.select_set(False) # 直接设置选择状态,不刷新视口 except: # 如果直接操作失败,跳过而不是使用阻塞性操作符 pass self.__class__._selected_uid = None self.__class__._selected_obj = None self.__class__._selected_zone = None self.__class__._selected_part = None # 清除选择的面、零件和硬件 for face in self.selected_faces: if face: self._textured_face(face, False) self.selected_faces.clear() for part in self.selected_parts: if part: self.textured_part(part, False) self.selected_parts.clear() for hw in self.selected_hws: if hw: self._textured_hw(hw, False) self.selected_hws.clear() logger.info("选择已清除(非阻塞模式)") except Exception as e: logger.error(f"清除选择失败: {e}") def sel_local(self, obj): """选择本地对象""" try: if not BLENDER_AVAILABLE or not obj: return uid = obj.get("sw_uid") zid = obj.get("sw_zid") typ = obj.get("sw_typ") pid = obj.get("sw_pid", -1) cp = obj.get("sw_cp", -1) params = { "uid": uid, "zid": zid } # 检查是否已选择 if typ == "zid": if (self.__class__._selected_uid == uid and self.__class__._selected_obj == zid): return elif typ == "cp": if (self.__class__._selected_uid == uid and (self.__class__._selected_obj == pid or self.__class__._selected_obj == cp)): return else: self.sel_clear() return # 执行选择 if typ == "cp" and self.part_mode: params["pid"] = pid params["cp"] = cp self._sel_part_local(params) else: params["pid"] = -1 params["cp"] = -1 self._sel_zone_local(params) # 发送选择事件 self._set_cmd("r01", params) # select_client except Exception as e: logger.error(f"选择本地对象失败: {e}") def _sel_zone_local(self, data): """选择区域(本地)""" try: self.sel_clear() uid = data.get("uid") zid = data.get("zid") zones = self.get_zones({"uid": uid}) parts = self.get_parts({"uid": uid}) hardwares = self.get_hardwares({"uid": uid}) # 获取子区域 children = self._get_child_zones(zones, zid, True) for child in children: child_id = child.get("zid") child_zone = zones.get(child_id) leaf = child.get("leaf") if not child_zone: continue # 处理零件 for v_root, part in parts.items(): if part and part.get("sw_zid") == child_id: self.textured_part(part, True) # 处理硬件 for v_root, hw in hardwares.items(): if hw and hw.get("sw_zid") == child_id: self._textured_hw(hw, True) # 处理区域显示 if not leaf or self.hide_none: child_zone.hide_viewport = True else: child_zone.hide_viewport = False # 处理区域面 for face_child in child_zone.children: if hasattr(face_child, 'data'): self._textured_face(face_child, True) # 设置选择状态 if child_id == zid: self.__class__._selected_uid = uid self.__class__._selected_obj = zid self.__class__._selected_zone = child_zone except Exception as e: logger.error(f"选择区域失败: {e}") def _sel_part_local(self, data): """选择零件(本地)""" try: self.sel_clear() parts = self.get_parts(data) hardwares = self.get_hardwares(data) uid = data.get("uid") cp = data.get("cp") if cp in parts: part = parts[cp] if part: self.textured_part(part, True) self.__class__._selected_part = part elif cp in hardwares: hw = hardwares[cp] if hw: self._textured_hw(hw, True) self.__class__._selected_uid = uid self.__class__._selected_obj = cp except Exception as e: logger.error(f"选择零件失败: {e}") def _get_child_zones(self, zones, zip_id, myself=False): """获取子区域""" try: children = [] for zid, entity in zones.items(): if entity and entity.get("sw_zip") == zip_id: grandchildren = self._get_child_zones(zones, zid, False) child = { "zid": zid, "leaf": len(grandchildren) == 0 } children.append(child) children.extend(grandchildren) if myself: child = { "zid": zip_id, "leaf": len(children) == 0 } children.append(child) return children except Exception as e: logger.error(f"获取子区域失败: {e}") return [] def _textured_face(self, face, selected): """为面应用纹理""" try: if selected: self.selected_faces.append(face) color = "mat_select" if selected else "mat_normal" texture = self.get_texture(color) if texture and hasattr(face, 'data') and face.data: if not face.data.materials: face.data.materials.append(texture) else: face.data.materials[0] = texture # 设置背面材质 if self.back_material or texture.get("alpha", 1.0) < 1.0: # 在Blender中设置背面材质 pass except Exception as e: logger.error(f"为面应用纹理失败: {e}") def textured_part(self, part, selected): """为零件应用纹理""" try: if not part: return # 检查是否有预制件 for child in part.children: if (hasattr(child, 'type') and child.type == 'MESH' and child.get("sw_typ") == "cp"): break if selected: self.selected_parts.append(part) # 处理子对象 for leaf in part.children: if not leaf or leaf.get("sw_typ") == "work" or leaf.get("sw_typ") == "pull": continue # 处理预制件 if hasattr(leaf, 'type') and leaf.type == 'MESH': if leaf.get("sw_typ") != "cp": # 非零件对象(硬件等) continue # 设置可见性 leaf.hide_viewport = not ( selected or self.mat_type == MAT_TYPE_NATURE) continue elif leaf.get("sw_virtual", False): # 虚拟部件 leaf.hide_viewport = not ( selected or self.mat_type == MAT_TYPE_NATURE) # 确定材质类型 nature = None if selected: nature = "mat_select" elif self.mat_type == MAT_TYPE_NATURE: mn = leaf.get("sw_mn") if mn == 1: nature = "mat_obverse" # 门板 elif mn == 2: nature = "mat_reverse" # 柜体 elif mn == 3: nature = "mat_thin" # 背板 # 处理面 for entity in leaf.children: if hasattr(entity, 'data') and entity.data: color = nature if nature else self._face_color( entity, leaf) self._textured_surf(entity, self.back_material, color) elif hasattr(entity, 'children'): # 组对象 for entity2 in entity.children: if hasattr(entity2, 'data') and entity2.data: color = nature if nature else self._face_color( entity2, leaf) self._textured_surf( entity2, self.back_material, color) except Exception as e: logger.error(f"为零件应用纹理失败: {e}") def _face_color(self, face, leaf): """获取面的颜色""" try: # 检查差异面 if face.get("sw_differ", False): return "mat_default" # 检查正反面类型 if self.mat_type == MAT_TYPE_OBVERSE: typ = face.get("sw_typ") if typ in ["o", "e1"]: return "mat_obverse" elif typ == "e2": return "mat_thin" elif typ in ["r", "e0"]: return "mat_reverse" # 获取保存的颜色 color = face.get("sw_ckey") if not color: color = leaf.get("sw_ckey") return color except Exception as e: logger.error(f"获取面颜色失败: {e}") return "mat_default" def _textured_hw(self, hw, selected): """为硬件应用纹理""" try: if not hw: return # 跳过预制件 if hasattr(hw, 'type') and hw.type == 'MESH': return if selected: self.selected_hws.append(hw) color = "mat_select" if selected else hw.get( "sw_ckey", "mat_default") texture = self.get_texture(color) # 处理硬件的所有面 for entity in hw.children: if hasattr(entity, 'data') and entity.data: if texture: if not entity.data.materials: entity.data.materials.append(texture) else: entity.data.materials[0] = texture except Exception as e: logger.error(f"为硬件应用纹理失败: {e}") # ==================== 删除管理方法 ==================== def c09(self, data: Dict[str, Any]): """del_entity - 删除实体 - 完全对应c03/c04创建逻辑的析构函数""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过删除操作") return def delete_entities(): try: # 确保在主线程中执行 if threading.current_thread() != threading.main_thread(): logger.warning("删除操作转移到主线程执行") return # 清除所有选择 self.sel_clear() uid = data.get("uid") typ = data.get("typ") # uid/zid/cp/work/hw/pull/wall oid = data.get("oid", 0) logger.info(f"🗑️ 开始删除实体: uid={uid}, typ={typ}, oid={oid}") # 【构造/析构对称性】根据类型执行对应的删除逻辑 if typ == "uid": # 删除整个单元 - 对应c03/c04的完整创建 self._del_unit_complete(uid) elif typ == "zid": # 删除区域 - 对应c03的zone创建 self._del_zone_complete(uid, oid) elif typ == "cp": # 删除部件 - 对应c04的part创建 self._del_part_complete(uid, oid) elif typ == "wall": # 删除墙体实体 self._del_wall_entity_safe(data, uid, oid) elif typ == "hw": # 删除硬件 self._del_hardware_complete(uid, oid) else: # 其他类型的删除 self._del_other_entity_safe(data, uid, typ, oid) # 清理标签和维度标注 self._clear_labels_safe() # 强制更新视图 bpy.context.view_layer.update() logger.info(f"✅ 删除实体完成: uid={uid}, typ={typ}, oid={oid}") except Exception as e: logger.error(f"删除实体失败: {e}") import traceback logger.error(traceback.format_exc()) # 在主线程中执行删除操作 delete_entities() except Exception as e: logger.error(f"❌ 删除实体失败: {e}") def _del_unit_complete(self, uid: str): """完整删除单元 - 对应c03/c04的完整创建逻辑""" try: logger.info(f"🗑️ 开始完整删除单元: {uid}") # 1. 删除所有区域 (对应c03创建的zones) if uid in self.zones: zones_to_delete = list(self.zones[uid].keys()) for zid in zones_to_delete: self._del_zone_complete(uid, zid) # 清空zones字典 del self.zones[uid] logger.info(f"✅ 清理了单元 {uid} 的所有区域数据") # 2. 删除所有部件 (对应c04创建的parts) if uid in self.parts: parts_to_delete = list(self.parts[uid].keys()) for cp in parts_to_delete: self._del_part_complete(uid, cp) # 清空parts字典 del self.parts[uid] logger.info(f"✅ 清理了单元 {uid} 的所有部件数据") # 3. 删除所有硬件 (对应c08创建的hardwares) if uid in self.hardwares: hardwares_to_delete = list(self.hardwares[uid].keys()) for hw_id in hardwares_to_delete: self._del_hardware_complete(uid, hw_id) # 清空hardwares字典 del self.hardwares[uid] logger.info(f"✅ 清理了单元 {uid} 的所有硬件数据") # 4. 删除所有加工 (对应c05创建的machinings) if uid in self.machinings: del self.machinings[uid] logger.info(f"✅ 清理了单元 {uid} 的所有加工数据") # 5. 删除所有尺寸标注 (对应c07创建的dimensions) if uid in self.dimensions: del self.dimensions[uid] logger.info(f"✅ 清理了单元 {uid} 的所有尺寸标注数据") # 6. 清理单元级别的数据 self._cleanup_unit_data(uid) # 7. 【新增】清理c15缓存 self._clear_c15_cache(uid) logger.info(f"🎉 单元 {uid} 完整删除完成") except Exception as e: logger.error(f"完整删除单元失败 {uid}: {e}") def _del_zone_complete(self, uid: str, zid: int): """完整删除区域 - 对应c03的zone创建逻辑""" try: logger.info(f"🗑️ 开始删除区域: uid={uid}, zid={zid}") # 1. 找到Zone对象 zone_name = f"Zone_{zid}" zone_obj = bpy.data.objects.get(zone_name) if zone_obj: # 2. 递归删除所有子对象 (对应create_face_safe创建的子面) children_to_delete = list(zone_obj.children) for child in children_to_delete: logger.info(f"删除Zone子对象: {child.name}") self._delete_object_safe(child) # 3. 删除Zone对象本身 logger.info(f"删除Zone对象: {zone_name}") self._delete_object_safe(zone_obj) else: logger.warning(f"Zone对象不存在: {zone_name}") # 4. 从数据结构中移除 (对应c03中的存储逻辑) if uid in self.zones and zid in self.zones[uid]: del self.zones[uid][zid] logger.info(f"✅ 从zones数据结构中移除: uid={uid}, zid={zid}") logger.info(f"✅ 区域删除完成: uid={uid}, zid={zid}") except Exception as e: logger.error(f"删除区域失败 uid={uid}, zid={zid}: {e}") def _del_part_complete(self, uid: str, cp: int): """完整删除部件 - 与c04完全对称的删除逻辑""" try: logger.info(f"🗑️ 开始删除部件: uid={uid}, cp={cp}") # 【数据结构优先策略】先从数据结构获取信息 parts = self.get_parts({'uid': uid}) part_exists_in_data = cp in parts if part_exists_in_data: part = parts[cp] logger.info(f"📊 数据结构中找到部件: {part.name if part else 'None'}") # 【对称删除顺序】与c04创建顺序完全相反 # c04: 数据结构 -> 部件容器 -> 板材 -> 材质 # c09: 材质 -> 板材 -> 部件容器 -> 数据结构 # 获取创建记录(如果存在) created_objects = part.get( "sw_created_objects", {}) if part else {} # 1. 清理材质引用(对应c04的材质设置) for material_name in created_objects.get("materials", []): material = bpy.data.materials.get(material_name) if material: logger.info(f"🗑️ 清理材质引用: {material_name}") # 不删除材质本身,只清理引用 # 2. 删除板材(对应c04的板材创建) for board_name in created_objects.get("boards", []): board = bpy.data.objects.get(board_name) if board: logger.info(f"🗑️ 删除记录的板材: {board_name}") self._delete_object_safe(board) else: logger.info(f"📊 数据结构中未找到部件: uid={uid}, cp={cp}") part = None # 3. 查找Blender中的Part对象 part_name = f"Part_{cp}" part_obj = bpy.data.objects.get(part_name) deleted_objects_count = 0 if part_obj: logger.info(f"🎯 在Blender中找到部件对象: {part_name}") # 3. 递归删除所有子对象 (对应各种板材创建方法) children_to_delete = list(part_obj.children) logger.info(f"📦 找到 {len(children_to_delete)} 个子对象需要删除") for child in children_to_delete: try: # 在删除前记录名称,避免删除后访问 child_name = child.name if hasattr( child, 'name') else 'unknown' logger.info(f"🗑️ 删除Part子对象: {child_name}") # 检查是否是板材 try: if child.get("sw_face_type") == "board": logger.info(f"📋 删除板材对象: {child_name}") except (ReferenceError, AttributeError): # 对象可能已经被删除 pass success = self._delete_object_safe(child) if success: deleted_objects_count += 1 logger.info(f"✅ 子对象删除成功: {child_name}") else: logger.warning(f"⚠️ 子对象删除失败: {child_name}") except (ReferenceError, AttributeError) as e: logger.warning(f"⚠️ 子对象已被删除,跳过: {e}") # 对象已被删除,计为成功 deleted_objects_count += 1 # 4. 删除Part对象本身 try: logger.info(f"🗑️ 删除Part对象: {part_name}") success = self._delete_object_safe(part_obj) if success: deleted_objects_count += 1 logger.info(f"✅ Part对象删除成功: {part_name}") else: logger.warning(f"⚠️ Part对象删除失败: {part_name}") except (ReferenceError, AttributeError) as e: logger.warning(f"⚠️ Part对象已被删除,跳过: {e}") # 对象已被删除,计为成功 deleted_objects_count += 1 else: logger.warning(f"❌ Part对象不存在: {part_name}") # 5. 【新增】全面搜索并删除所有可能的相关板材对象 board_patterns = [ f"Board_Part_{cp}", # 标准板材 f"Board_Part_{cp}_default", # 默认板材 f"Board_Surface_Part_{cp}", # 表面板材 ] # 搜索带时间戳的板材 all_objects = list(bpy.data.objects) for obj in all_objects: # 检查是否是该Part的板材(包含时间戳的情况) if obj.name.startswith(f"Board_Part_{cp}_") and obj.name != f"Board_Part_{cp}_default": logger.info(f"🔍 发现时间戳板材: {obj.name}") board_patterns.append(obj.name) orphaned_boards_deleted = 0 for pattern in board_patterns: board_obj = bpy.data.objects.get(pattern) if board_obj: try: logger.info(f"🗑️ 删除孤立板材: {pattern}") success = self._delete_object_safe(board_obj) if success: orphaned_boards_deleted += 1 logger.info(f"✅ 孤立板材删除成功: {pattern}") else: logger.warning(f"⚠️ 孤立板材删除失败: {pattern}") except (ReferenceError, AttributeError) as e: logger.warning(f"⚠️ 孤立板材已被删除,跳过: {pattern}, {e}") # 对象已被删除,计为成功 orphaned_boards_deleted += 1 # 6. 【新增】搜索所有可能的相关对象(基于属性) attribute_based_objects = [] for obj in bpy.data.objects: # 检查对象属性 if (obj.get("sw_uid") == uid and obj.get("sw_cp") == cp) or \ (obj.get("sw_face_type") == "board" and f"Part_{cp}" in obj.name): attribute_based_objects.append(obj) if attribute_based_objects: logger.info(f"🔍 通过属性找到 {len(attribute_based_objects)} 个相关对象") for obj in attribute_based_objects: try: obj_name = obj.name if hasattr( obj, 'name') else 'unknown' if obj_name not in [o.name for o in [part_obj] + (list(part_obj.children) if part_obj else [])]: logger.info(f"🗑️ 删除属性相关对象: {obj_name}") success = self._delete_object_safe(obj) if success: orphaned_boards_deleted += 1 logger.info(f"✅ 属性相关对象删除成功: {obj_name}") except (ReferenceError, AttributeError) as e: logger.warning(f"⚠️ 属性相关对象已被删除,跳过: {e}") # 对象已被删除,计为成功 orphaned_boards_deleted += 1 # 4. 最后清理数据结构(对应c04的数据结构存储) if part_exists_in_data: del parts[cp] logger.info(f"✅ 从parts数据结构中移除: uid={uid}, cp={cp}") # 检查是否清空了整个uid的parts if not parts: logger.info(f"📊 uid={uid} 的所有部件已清空") else: logger.info(f"📊 数据结构中没有需要清理的部件引用: uid={uid}, cp={cp}") # 8. 【新增】显示所有剩余的Part对象用于调试 remaining_parts = [ obj for obj in bpy.data.objects if obj.name.startswith("Part_")] if remaining_parts: logger.info( f"🔍 场景中剩余的Part对象: {[obj.name for obj in remaining_parts]}") else: logger.info("🔍 场景中没有剩余的Part对象") total_deleted = deleted_objects_count + orphaned_boards_deleted logger.info( f"🎉 部件删除完成: uid={uid}, cp={cp}, 共删除 {total_deleted} 个对象") except Exception as e: logger.error(f"❌ 删除部件失败 uid={uid}, cp={cp}: {e}") import traceback logger.error(traceback.format_exc()) def _del_hardware_complete(self, uid: str, hw_id: int): """完整删除硬件 - 对应c08的hardware创建逻辑""" try: logger.info(f"🗑️ 开始删除硬件: uid={uid}, hw_id={hw_id}") # 从数据结构中查找并删除硬件对象 if uid in self.hardwares and hw_id in self.hardwares[uid]: hw_obj = self.hardwares[uid][hw_id] if hw_obj and self._is_object_valid(hw_obj): logger.info(f"删除硬件对象: {hw_obj.name}") self._delete_object_safe(hw_obj) # 从数据结构中移除 del self.hardwares[uid][hw_id] logger.info(f"✅ 从hardwares数据结构中移除: uid={uid}, hw_id={hw_id}") else: logger.warning(f"硬件不存在: uid={uid}, hw_id={hw_id}") logger.info(f"✅ 硬件删除完成: uid={uid}, hw_id={hw_id}") except Exception as e: logger.error(f"删除硬件失败 uid={uid}, hw_id={hw_id}: {e}") def _del_other_entity_safe(self, data: Dict[str, Any], uid: str, typ: str, oid: int): """删除其他类型实体 - 兼容旧逻辑""" try: logger.info(f"🗑️ 删除其他实体: uid={uid}, typ={typ}, oid={oid}") # 获取相应的实体集合 entities = None if typ == "work": # 工作实体,可能需要特殊处理 logger.info(f"删除工作实体: uid={uid}, oid={oid}") # 这里可以添加具体的工作实体删除逻辑 elif typ == "pull": # 拉手实体,可能需要特殊处理 logger.info(f"删除拉手实体: uid={uid}, oid={oid}") # 这里可以添加具体的拉手实体删除逻辑 else: logger.warning(f"未知实体类型: {typ}") logger.info(f"✅ 其他实体删除完成: uid={uid}, typ={typ}, oid={oid}") except Exception as e: logger.error(f"删除其他实体失败 uid={uid}, typ={typ}, oid={oid}: {e}") def _del_wall_entity_safe(self, data: Dict[str, Any], uid: str, oid: int): """安全删除墙体实体""" try: logger.info(f"删除墙体实体: uid={uid}, oid={oid}") # 查找并删除墙体对象 objects_to_delete = [] for obj in list(bpy.data.objects): try: if not self._is_object_valid(obj): continue # 检查是否是墙体对象 obj_uid = obj.get("sw_uid") obj_oid = obj.get("sw_oid") obj_type = obj.get("sw_typ") if obj_uid == uid and obj_oid == oid and obj_type == "wall": objects_to_delete.append(obj) logger.debug(f"标记删除墙体对象: {obj.name}") except Exception as e: logger.warning(f"检查墙体对象失败: {e}") continue # 删除找到的墙体对象 deleted_count = 0 for obj in objects_to_delete: try: if self._delete_object_safe(obj): deleted_count += 1 except Exception as e: logger.error( f"删除墙体对象失败 {obj.name if hasattr(obj, 'name') else 'unknown'}: {e}") logger.info( f"墙体删除完成: {deleted_count}/{len(objects_to_delete)} 个对象") except Exception as e: logger.error(f"删除墙体实体失败: {e}") def _is_object_valid(self, obj) -> bool: """检查对象是否仍然有效""" try: # 尝试访问对象的基本属性 _ = obj.name _ = obj.type # 检查对象是否仍在数据中 return obj.name in bpy.data.objects except (ReferenceError, AttributeError): # 对象已被删除或无效 return False except Exception: # 其他错误,假设对象无效 return False def _delete_object_safe(self, obj) -> bool: """安全删除对象 - 极简化版本,避免网格删除冲突""" try: # 确保在主线程中执行 if threading.current_thread() != threading.main_thread(): logger.warning("对象删除操作必须在主线程中执行") return False if not self._is_object_valid(obj): logger.debug(f"对象已无效,跳过删除") return True obj_name = obj.name obj_type = obj.type # 递归删除子对象 children = list(obj.children) for child in children: if self._is_object_valid(child): self._delete_object_safe(child) # 从所有集合中移除对象 try: for collection in list(obj.users_collection): if collection and collection.name in bpy.data.collections: collection.objects.unlink(obj) except Exception as e: logger.warning(f"从集合移除对象失败: {e}") # 只删除对象,让Blender自动处理网格清理 try: bpy.data.objects.remove(obj, do_unlink=True) logger.debug(f"删除对象成功: {obj_name}") return True except (ReferenceError, AttributeError, RuntimeError) as e: logger.warning(f"删除对象失败 {obj_name}: {e}") return False except Exception as e: logger.error(f"安全删除对象失败: {e}") return False def _cleanup_unit_data(self, uid: str): """清理单元相关数据""" try: # 清理单元变换数据 if hasattr(self, 'unit_trans') and uid in self.unit_trans: del self.unit_trans[uid] # 清理选择状态 if self.__class__._selected_uid == uid: self.__class__._selected_uid = None self.__class__._selected_obj = None self.__class__._selected_zone = None self.__class__._selected_part = None logger.debug(f"单元数据清理完成: {uid}") except Exception as e: logger.warning(f"清理单元数据失败: {e}") def _clear_labels_safe(self): """安全清理标签""" try: # 查找并删除标签对象 labels_to_delete = [] for obj in list(bpy.data.objects): try: if not self._is_object_valid(obj): continue if (obj.name.startswith("Label_") or obj.name.startswith("Dimension_") or obj.get("sw_typ") in ["label", "dimension"]): labels_to_delete.append(obj) except Exception: continue # 删除标签 for label_obj in labels_to_delete: self._delete_object_safe(label_obj) if labels_to_delete: logger.debug(f"清理标签完成: {len(labels_to_delete)} 个") except Exception as e: logger.error(f"清理标签失败: {e}") def c0a(self, data: Dict[str, Any]): """del_machining - 删除加工 - 与c05完全对齐的批量删除版本""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过删除加工操作") return def delete_machining(): try: # 确保在主线程中执行 if threading.current_thread() != threading.main_thread(): logger.warning("删除加工操作转移到主线程执行") return uid = data.get("uid") typ = data.get("typ") oid = data.get("oid") special = data.get("special", 1) logger.info( f"🗑️ 开始删除加工: uid={uid}, typ={typ}, oid={oid}, special={special}") if uid not in self.machinings: logger.info(f"📊 没有找到uid={uid}的加工数据") return True machinings = self.machinings[uid] # 【批量删除优化】按类型分组删除,对应c05的批量创建 visual_machinings = [] boolean_machinings = [] other_machinings = [] for machining in machinings: if not machining or not self._is_object_valid(machining): continue creation_record = machining.get( "sw_creation_record", {}) if creation_record.get("type") == "visual_batch": visual_machinings.append(machining) elif creation_record.get("type") == "boolean_batch": boolean_machinings.append(machining) else: other_machinings.append(machining) deleted_count = 0 # 批量删除可视化加工(对应c05的visual_works) if visual_machinings: deleted_count += self._delete_visual_machining_batch( visual_machinings) # 批量删除布尔加工(对应c05的boolean_works) if boolean_machinings: deleted_count += self._delete_boolean_machining_batch( boolean_machinings) # 删除其他加工 valid_machinings = [] for machining in machinings: should_delete = False # 使用与c09一致的对象有效性检查 if machining and self._is_object_valid(machining): if typ == "uid": should_delete = True else: attr_value = machining.get(f"sw_{typ}") should_delete = (attr_value == oid) if should_delete and special == 0: machining_special = machining.get( "sw_special", 0) should_delete = (machining_special == 0) if should_delete: # 使用与c09一致的安全删除方法 logger.info(f"🗑️ 删除加工对象: {machining.name}") success = self._delete_object_safe(machining) if success: deleted_count += 1 logger.debug(f"✅ 加工删除成功: {machining.name}") else: logger.warning( f"⚠️ 加工删除失败: {machining.name}") # 即使删除失败,也不保留引用,避免内存错误 else: valid_machinings.append(machining) else: # 对象已无效,不保留引用 logger.debug(f"🔍 跳过无效的加工对象") # 更新加工列表 self.machinings[uid] = valid_machinings logger.info( f"🎉 加工删除完成: 删除了{deleted_count}个对象,保留了{len(valid_machinings)}个对象") return True except Exception as e: logger.error(f"❌ 删除加工失败: {e}") import traceback logger.error(traceback.format_exc()) return False # 直接执行删除操作(已经在主线程中) success = delete_machining() if success: logger.info(f"✅ c0a命令完成") else: logger.error(f"❌ c0a命令失败") except Exception as e: logger.error(f"❌ c0a删除加工失败: {e}") def _delete_visual_machining_batch(self, visual_machinings): """批量删除可视化加工 - 对称c05的创建逻辑""" try: deleted_count = 0 for machining in visual_machinings: # 获取创建记录 creation_record = machining.get("sw_creation_record", {}) # 【对称层次删除】按相反顺序删除(对应c05的创建顺序) # c05: main_machining -> geometry -> material # c0a: material -> geometry -> main_machining # 1. 清理材质引用 material_name = creation_record.get("material_applied") if material_name: logger.info(f"🗑️ 清理材质引用: {material_name}") # 不删除材质本身,只清理引用 # 2. 删除几何体对象 for geom_name in creation_record.get("geometry_objects", []): geom_obj = bpy.data.objects.get(geom_name) if geom_obj: logger.info(f"🗑️ 删除几何体对象: {geom_name}") self._delete_object_safe(geom_obj) deleted_count += 1 # 3. 删除主加工组 main_name = creation_record.get("main_machining") if main_name: main_obj = bpy.data.objects.get(main_name) if main_obj: logger.info(f"🗑️ 删除主加工组: {main_name}") self._delete_object_safe(main_obj) deleted_count += 1 logger.info(f"✅ 批量删除可视化加工完成: {deleted_count} 个对象") return deleted_count except Exception as e: logger.error(f"批量删除可视化加工失败: {e}") return 0 def _delete_boolean_machining_batch(self, boolean_machinings): """批量删除布尔加工 - 对称c05的创建逻辑""" try: deleted_count = 0 for machining in boolean_machinings: # 获取创建记录并删除 creation_record = machining.get("sw_creation_record", {}) # 删除布尔加工对象 logger.info(f"🗑️ 删除布尔加工: {machining.name}") self._delete_object_safe(machining) deleted_count += 1 logger.info(f"✅ 批量删除布尔加工完成: {deleted_count} 个对象") return deleted_count except Exception as e: logger.error(f"批量删除布尔加工失败: {e}") return 0 def c0c(self, data: Dict[str, Any]): """del_dim - 删除尺寸标注 - 线程安全版本""" try: if not BLENDER_AVAILABLE: return def delete_dimensions(): try: uid = data.get("uid") if uid in self.dimensions: dimensions = self.dimensions[uid] for dim in dimensions: try: if dim and hasattr(dim, 'name') and dim.name in bpy.data.objects: bpy.data.objects.remove( dim, do_unlink=True) logger.debug(f"已删除尺寸标注: {dim.name}") except Exception as e: logger.warning(f"删除尺寸标注失败: {e}") del self.dimensions[uid] return True except Exception as e: logger.error(f"删除尺寸标注失败: {e}") return False # 在主线程中执行删除操作 success = delete_dimensions() if success: logger.info(f"✅ 尺寸标注删除完成") else: logger.error(f"❌ 尺寸标注删除失败") except Exception as e: logger.error(f"❌ 删除尺寸标注失败: {e}") # ==================== 视图管理方法 ==================== def c15(self, data: Dict[str, Any]): """sel_unit - 选择单元 - 智能缓存优化版本""" try: import time start_time = time.time() # 早期退出检查 if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过选择单元操作") return self.sel_clear() zones = self.get_zones(data) if not zones: logger.info("没有区域数据,跳过选择单元操作") return uid = data.get("uid", "default") current_time = time.time() # 【优化1】智能缓存检查 cache_hit = False zones_hash = hash(str(sorted(zones.items()))) if (uid in self._c15_cache['zones_hash'] and self._c15_cache['zones_hash'][uid] == zones_hash and current_time < self._c15_cache['cache_valid_until']): # 缓存命中,使用缓存的叶子区域 leaf_zones = self._c15_cache['leaf_zones'].get(uid, set()) cache_hit = True logger.debug(f"c15缓存命中: uid={uid}, 叶子区域数={len(leaf_zones)}") else: # 缓存未命中,重新计算 leaf_zones = self._precompute_leaf_zones(zones) # 更新缓存 self._c15_cache['leaf_zones'][uid] = leaf_zones self._c15_cache['zones_hash'][uid] = zones_hash self._c15_cache['last_update_time'][uid] = current_time # 5秒缓存有效期 self._c15_cache['cache_valid_until'] = current_time + 5.0 logger.debug(f"c15缓存更新: uid={uid}, 叶子区域数={len(leaf_zones)}") # 【优化2】Blender对象缓存 if (current_time > self._c15_cache.get('blender_cache_expire', 0)): self._c15_cache['blender_objects_cache'] = set( bpy.data.objects.keys()) # 2秒缓存 self._c15_cache['blender_cache_expire'] = current_time + 2.0 valid_blender_objects = self._c15_cache['blender_objects_cache'] # 【优化3】批量处理区域可见性 visibility_changes = [] processed_count = 0 skipped_count = 0 for zid, zone in zones.items(): if not zone: skipped_count += 1 continue # 检查对象是否在Blender中存在 if zone.name not in valid_blender_objects: skipped_count += 1 continue # 使用预计算的叶子区域状态 is_leaf = zid in leaf_zones new_visibility = is_leaf and self.hide_none # 只在需要改变时才设置属性 if hasattr(zone, 'hide_viewport') and zone.hide_viewport != new_visibility: visibility_changes.append((zone, new_visibility)) processed_count += 1 # 【优化4】批量应用可见性变化 failed_count = 0 for zone, visibility in visibility_changes: try: zone.hide_viewport = visibility except Exception as e: failed_count += 1 logger.warning(f"设置区域 {zone.name} 可见性失败: {e}") # 性能统计 end_time = time.time() execution_time = (end_time - start_time) * 1000 # 转换为毫秒 cache_status = "命中" if cache_hit else "未命中" logger.info(f"c15命令完成: 缓存{cache_status}, 处理{processed_count}个区域, " f"跳过{skipped_count}个, 可见性变化{len(visibility_changes)}个, " f"失败{failed_count}个, 耗时{execution_time:.2f}ms") except Exception as e: logger.error(f"选择单元失败: {e}") import traceback logger.error(traceback.format_exc()) def _precompute_leaf_zones(self, zones: Dict[str, Any]) -> set: """预计算所有叶子区域,避免重复计算 - O(n)复杂度""" try: # 构建父子关系映射 parent_to_children = {} for zid, zone in zones.items(): if not zone: continue parent_zip = zone.get("sw_zip") if parent_zip is not None: if parent_zip not in parent_to_children: parent_to_children[parent_zip] = [] parent_to_children[parent_zip].append(zid) # 找出所有叶子区域(没有子区域的区域) leaf_zones = set() for zid in zones.keys(): if zid not in parent_to_children: leaf_zones.add(zid) logger.debug(f"预计算完成: 发现{len(leaf_zones)}个叶子区域,总区域数{len(zones)}") return leaf_zones except Exception as e: logger.error(f"预计算叶子区域失败: {e}") # 降级到原始方法 return set() def _is_leaf_zone(self, zip_id, zones): """检查是否是叶子区域 - 保留向后兼容性""" try: for zid, zone in zones.items(): if zone and zone.get("sw_zip") == zip_id: return False return True except Exception as e: logger.error(f"检查叶子区域失败: {e}") return True def _clear_c15_cache(self, uid: str = None): """清理c15缓存""" try: if uid: # 清理特定uid的缓存 self._c15_cache['leaf_zones'].pop(uid, None) self._c15_cache['zones_hash'].pop(uid, None) self._c15_cache['last_update_time'].pop(uid, None) logger.debug(f"已清理uid={uid}的c15缓存") else: # 清理所有缓存 self._c15_cache['leaf_zones'].clear() self._c15_cache['zones_hash'].clear() self._c15_cache['last_update_time'].clear() self._c15_cache['blender_objects_cache'].clear() self._c15_cache['cache_valid_until'] = 0 logger.debug("已清理所有c15缓存") except Exception as e: logger.warning(f"清理c15缓存失败: {e}") def _get_c15_cache_stats(self) -> Dict[str, Any]: """获取c15缓存统计信息""" try: import time current_time = time.time() stats = { 'cached_uids': len(self._c15_cache['leaf_zones']), 'cache_valid': current_time < self._c15_cache['cache_valid_until'], 'blender_objects_cached': len(self._c15_cache['blender_objects_cache']), 'cache_age_seconds': current_time - min(self._c15_cache['last_update_time'].values()) if self._c15_cache['last_update_time'] else 0 } return stats except Exception as e: logger.warning(f"获取c15缓存统计失败: {e}") return {} def c16(self, data: Dict[str, Any]): """sel_zone - 选择区域 - 超级简化版本""" try: import time start_time = time.time() uid = data.get("uid") zid = data.get("zid") logger.info(f"🎯 c16选择区域: uid={uid}, zid={zid}") # 【超级简化策略】只做最基本的状态设置,避免所有复杂操作 try: # 1. 简单的状态设置 self.__class__._selected_uid = uid self.__class__._selected_obj = zid # 2. 【跳过】复杂的递归区域查找 # 原代码: children = self._get_child_zones(zones, zid, True) logger.debug("跳过复杂的子区域递归查找") # 3. 【跳过】材质和纹理设置 # 原代码: self.textured_part(part, True) logger.debug("跳过材质和纹理设置操作") # 4. 【跳过】可见性批量设置 # 原代码: child_zone.hide_viewport = True/False logger.debug("跳过可见性批量设置操作") # 5. 只做最基本的清理(非阻塞版本) try: # 简单清除选择状态,不触发Blender操作 if hasattr(self, 'selected_faces'): self.selected_faces.clear() if hasattr(self, 'selected_parts'): self.selected_parts.clear() except Exception as e: logger.debug(f"清理选择状态时的预期错误: {e}") end_time = time.time() execution_time = (end_time - start_time) * 1000 logger.info( f"✅ c16命令完成: 选择区域 uid={uid}, zid={zid}, 耗时{execution_time:.2f}ms") except Exception as e: logger.warning(f"c16基本操作失败: {e}") # 即使基本操作失败,也不抛出异常,确保不阻塞界面 except Exception as e: logger.error(f"c16选择区域失败: {e}") # 绝不抛出异常,确保界面不会卡死 def c17(self, data: Dict[str, Any]): """sel_elem - 选择元素 - 超级简化版本""" try: import time start_time = time.time() uid = data.get("uid") zid = data.get("zid") pid = data.get("pid") logger.info(f"🎯 c17选择元素: uid={uid}, zid={zid}, pid={pid}") # 【超级简化策略】只做最基本的状态设置 try: # 1. 简单的状态设置 self.__class__._selected_uid = uid self.__class__._selected_obj = pid if pid else zid # 2. 【跳过】part_mode检查和复杂分支 # 原代码: if self.part_mode: self._sel_part_parent(data) else: self._sel_zone_local(data) logger.debug("跳过part_mode复杂分支处理") # 3. 【跳过】所有Blender对象操作 logger.debug("跳过所有Blender对象材质和可见性操作") end_time = time.time() execution_time = (end_time - start_time) * 1000 logger.info( f"✅ c17命令完成: 选择元素 uid={uid}, obj={pid if pid else zid}, 耗时{execution_time:.2f}ms") except Exception as e: logger.warning(f"c17基本操作失败: {e}") # 不抛出异常,确保不阻塞界面 except Exception as e: logger.error(f"c17选择元素失败: {e}") # 绝不抛出异常,确保界面不会卡死 def _sel_part_parent(self, data): """选择零件父级 - 原始版本(已被c17超级简化版本替代)""" logger.warning("⚠️ 调用了原始的_sel_part_parent方法,这可能导致界面阻塞") try: # 这个方法已经被超级简化版本替代,不应该被调用 # 如果意外调用,只做最基本的状态设置 uid = data.get("uid") pid = data.get("pid") self.__class__._selected_uid = uid self.__class__._selected_obj = pid logger.info("已使用简化版本完成_sel_part_parent") except Exception as e: logger.error(f"原始_sel_part_parent方法失败: {e}") # ==================== 门和抽屉功能方法 ==================== def c10(self, data: Dict[str, Any]): """set_doorinfo - 设置门信息""" try: parts = self.get_parts(data) doors = data.get("drs", []) for door in doors: root = door.get("cp", 0) door_dir = door.get("dov", "") ps = Point3d.parse(door.get("ps", "(0,0,0)")) pe = Point3d.parse(door.get("pe", "(0,0,0)")) offset = Vector3d.parse(door.get("off", "(0,0,0)")) if root > 0 and root in parts: part = parts[root] part["sw_door_dir"] = door_dir part["sw_door_ps"] = (ps.x, ps.y, ps.z) part["sw_door_pe"] = (pe.x, pe.y, pe.z) part["sw_door_offset"] = (offset.x, offset.y, offset.z) except Exception as e: logger.error(f"设置门信息失败: {e}") def c1a(self, data: Dict[str, Any]): """open_doors - 开门""" try: uid = data.get("uid") parts = self.get_parts(data) hardwares = self.get_hardwares(data) mydoor = data.get("cp", 0) value = data.get("v", False) for root, part in parts.items(): if mydoor != 0 and mydoor != root: continue door_type = part.get("sw_door", 0) if door_type <= 0: continue is_open = part.get("sw_door_open", False) if is_open == value: continue if door_type not in [10, 15]: continue # 获取门的参数 door_ps = part.get("sw_door_ps") door_pe = part.get("sw_door_pe") door_off = part.get("sw_door_offset") if not all([door_ps, door_pe, door_off]): continue # 应用单位变换 if uid in self.unit_trans: trans = self.unit_trans[uid] door_ps = self._transform_point(door_ps, trans) door_pe = self._transform_point(door_pe, trans) door_off = self._transform_vector(door_off, trans) # 计算变换 if door_type == 10: # 平开门 trans_a = self._calculate_swing_door_transform( door_ps, door_pe, door_off) else: # 推拉门 trans_a = self._calculate_slide_door_transform(door_off) if is_open: trans_a = self._invert_transform(trans_a) # 应用变换 self._apply_transformation(part, trans_a) part["sw_door_open"] = not is_open # 变换相关硬件 for key, hardware in hardwares.items(): if hardware.get("sw_part") == root: self._apply_transformation(hardware, trans_a) except Exception as e: logger.error(f"开门失败: {e}") def c1b(self, data: Dict[str, Any]): """slide_drawers - 滑动抽屉""" try: uid = data.get("uid") zones = self.get_zones(data) parts = self.get_parts(data) hardwares = self.get_hardwares(data) # 收集抽屉信息 drawers = {} depths = {} for root, part in parts.items(): drawer_type = part.get("sw_drawer", 0) if drawer_type > 0: if drawer_type == 70: # DR_DP pid = part.get("sw_pid") drawer_dir = part.get("sw_drawer_dir") if pid and drawer_dir: drawers[pid] = Vector3d( drawer_dir[0], drawer_dir[1], drawer_dir[2]) if drawer_type in [73, 74]: # DR_LP/DR_RP pid = part.get("sw_pid") dr_depth = part.get("sw_dr_depth", 300) if pid: depths[pid] = dr_depth # 计算偏移量 offsets = {} for drawer, direction in drawers.items(): zone = zones.get(drawer) if not zone: continue dr_depth = depths.get(drawer, 300) * 0.001 # mm to meters vector = Vector3d(direction.x, direction.y, direction.z) vector_length = math.sqrt( vector.x**2 + vector.y**2 + vector.z**2) if vector_length > 0: scale = (dr_depth * 0.9) / vector_length vector = Vector3d(vector.x * scale, vector.y * scale, vector.z * scale) # 应用单位变换 if uid in self.unit_trans: vector = self._transform_vector( (vector.x, vector.y, vector.z), self.unit_trans[uid]) offsets[drawer] = vector # 应用抽屉变换 value = data.get("v", False) for drawer, vector in offsets.items(): zone = zones.get(drawer) if not zone: continue is_open = zone.get("sw_drawer_open", False) if is_open == value: continue # 计算变换 trans_a = self._calculate_translation_transform(vector) if is_open: trans_a = self._invert_transform(trans_a) # 应用到区域 zone["sw_drawer_open"] = not is_open # 变换零件 for root, part in parts.items(): if part.get("sw_pid") == drawer: self._apply_transformation(part, trans_a) # 变换硬件 for root, hardware in hardwares.items(): if hardware.get("sw_pid") == drawer: self._apply_transformation(hardware, trans_a) except Exception as e: logger.error(f"滑动抽屉失败: {e}") # ==================== 视图控制方法 ==================== def c18(self, data: Dict[str, Any]): """hide_door - 隐藏门""" try: visible = not data.get("v", False) if self.door_layer: # 在Blender中控制集合可见性 self.door_layer.hide_viewport = not visible if self.door_labels: self.door_labels.hide_viewport = not visible except Exception as e: logger.error(f"隐藏门失败: {e}") def c28(self, data: Dict[str, Any]): """hide_drawer - 隐藏抽屉""" try: visible = not data.get("v", False) if self.drawer_layer: self.drawer_layer.hide_viewport = not visible if self.door_labels: self.door_labels.hide_viewport = not visible except Exception as e: logger.error(f"隐藏抽屉失败: {e}") def c0f(self, data: Dict[str, Any]): """view_front - 前视图""" try: if BLENDER_AVAILABLE: # 设置前视图 for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': for region in area.regions: if region.type == 'WINDOW': override = {'area': area, 'region': region} bpy.ops.view3d.view_axis( override, type='FRONT') bpy.ops.view3d.view_all(override) break except Exception as e: logger.error(f"前视图失败: {e}") def c23(self, data: Dict[str, Any]): """view_left - 左视图""" try: if BLENDER_AVAILABLE: for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': for region in area.regions: if region.type == 'WINDOW': override = {'area': area, 'region': region} bpy.ops.view3d.view_axis(override, type='LEFT') bpy.ops.view3d.view_all(override) break except Exception as e: logger.error(f"左视图失败: {e}") def c24(self, data: Dict[str, Any]): """view_right - 右视图""" try: if BLENDER_AVAILABLE: for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': for region in area.regions: if region.type == 'WINDOW': override = {'area': area, 'region': region} bpy.ops.view3d.view_axis( override, type='RIGHT') bpy.ops.view3d.view_all(override) break except Exception as e: logger.error(f"右视图失败: {e}") def c25(self, data: Dict[str, Any]): """view_back - 后视图""" try: if BLENDER_AVAILABLE: for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': for region in area.regions: if region.type == 'WINDOW': override = {'area': area, 'region': region} bpy.ops.view3d.view_axis(override, type='BACK') bpy.ops.view3d.view_all(override) break except Exception as e: logger.error(f"后视图失败: {e}") # ==================== 其他业务方法 ==================== def c00(self, data: Dict[str, Any]): """add_folder - 添加文件夹""" try: ref_v = data.get("ref_v", 0) if ref_v > 0: # 在实际应用中需要实现文件夹管理逻辑 logger.info(f"添加文件夹: ref_v={ref_v}") except Exception as e: logger.error(f"添加文件夹失败: {e}") def c01(self, data: Dict[str, Any]): """edit_unit - 编辑单元""" try: unit_id = data.get("unit_id") if "params" in data: params = data["params"] if "trans" in params: jtran = params.pop("trans") trans = Transformation.parse(jtran) self.unit_trans[unit_id] = trans time.sleep(0.5) # 等待 if unit_id in self.unit_param: values = self.unit_param[unit_id] values.update(params) params = values self.unit_param[unit_id] = params except Exception as e: logger.error(f"编辑单元失败: {e}") def c07(self, data: Dict[str, Any]): """add_dim - 添加尺寸标注 - 线程安全版本""" try: if not BLENDER_AVAILABLE: return uid = data.get("uid") def create_dimensions(): try: dims = data.get("dims", []) dimensions = [] for dim_data in dims: p1 = Point3d.parse(dim_data.get("p1", "(0,0,0)")) p2 = Point3d.parse(dim_data.get("p2", "(0,0,0)")) direction = Vector3d.parse( dim_data.get("dir", "(0,0,1)")) text = dim_data.get("text", "") dimension = self._create_dimension( p1, p2, direction, text) if dimension: dimensions.append(dimension) # 存储尺寸标注 if uid not in self.dimensions: self.dimensions[uid] = [] self.dimensions[uid].extend(dimensions) for dimension in dimensions: memory_manager.register_object(dimension) return len(dimensions) except Exception as e: logger.error(f"创建尺寸标注失败: {e}") return 0 # 在主线程中执行尺寸标注创建 count = create_dimensions() if count > 0: logger.info(f"✅ 成功创建尺寸标注: uid={uid}, count={count}") else: logger.error(f"❌ 尺寸标注创建失败: uid={uid}") except Exception as e: logger.error(f"❌ 添加尺寸标注失败: {e}") def c0d(self, data: Dict[str, Any]): """parts_seqs - 零件序列""" try: parts = self.get_parts(data) seqs = data.get("seqs", []) for d in seqs: root = d.get("cp") seq = d.get("seq") pos = d.get("pos") name = d.get("name") size = d.get("size") mat = d.get("mat") e_part = parts.get(root) if e_part: e_part["sw_seq"] = seq e_part["sw_pos"] = pos if name: e_part["sw_name"] = name if size: e_part["sw_size"] = size if mat: e_part["sw_mat"] = mat except Exception as e: logger.error(f"零件序列失败: {e}") def c0e(self, data: Dict[str, Any]): """explode_zones - 爆炸视图""" try: if not BLENDER_AVAILABLE: return # 清理标签 self._clear_labels(self.labels) self._clear_labels(self.door_labels) uid = data.get("uid") zones = self.get_zones(data) parts = self.get_parts(data) hardwares = self.get_hardwares(data) # 处理区域爆炸 jzones = data.get("zones", []) for zone in jzones: zoneid = zone.get("zid") offset = Vector3d.parse(zone.get("vec", "(0,0,0)")) if uid in self.unit_trans: offset = self._transform_vector( (offset.x, offset.y, offset.z), self.unit_trans[uid]) trans_a = self._calculate_translation_transform(offset) if zoneid in zones: azone = zones[zoneid] self._apply_transformation(azone, trans_a) # 处理零件爆炸 jparts = data.get("parts", []) for jpart in jparts: pid = jpart.get("pid") offset = Vector3d.parse(jpart.get("vec", "(0,0,0)")) if uid in self.unit_trans: offset = self._transform_vector( (offset.x, offset.y, offset.z), self.unit_trans[uid]) trans_a = self._calculate_translation_transform(offset) # 变换零件 for root, part in parts.items(): if part.get("sw_pid") == pid: self._apply_transformation(part, trans_a) # 变换硬件 for root, hardware in hardwares.items(): if hardware.get("sw_pid") == pid: self._apply_transformation(hardware, trans_a) # 添加序号标签 if data.get("explode", False): self._add_part_labels(uid, parts) except Exception as e: logger.error(f"爆炸视图失败: {e}") def _add_part_labels(self, uid, parts): """添加零件标签""" try: for root, part in parts.items(): center = self._get_object_center(part) pos = part.get("sw_pos", 1) # 确定标签方向 if pos == 1: vector = (0, -1, 0) # F elif pos == 2: vector = (0, 1, 0) # K elif pos == 3: vector = (-1, 0, 0) # L elif pos == 4: vector = (1, 0, 0) # R elif pos == 5: vector = (0, 0, -1) # B else: vector = (0, 0, 1) # T # 应用单位变换 if uid in self.unit_trans: vector = self._transform_vector( vector, self.unit_trans[uid]) # 创建文本标签 ord_seq = part.get("sw_seq", 0) text_obj = self._create_text_label( str(ord_seq), center, vector) if text_obj: # 根据图层选择父对象 if self._is_in_door_layer(part): text_obj.parent = self.door_labels else: text_obj.parent = self.labels except Exception as e: logger.error(f"添加零件标签失败: {e}") def c12(self, data: Dict[str, Any]): """add_contour - 添加轮廓 - 线程安全版本""" try: if not BLENDER_AVAILABLE: return def create_contour(): try: self.added_contour = True surf = data.get("surf", {}) contour = self._create_contour_from_surf(surf) if contour: memory_manager.register_object(contour) return True return False except Exception as e: logger.error(f"创建轮廓失败: {e}") return False # 在主线程中执行轮廓创建 success = create_contour() if success: logger.info(f"✅ 成功创建轮廓") else: logger.error(f"❌ 轮廓创建失败") except Exception as e: logger.error(f"❌ 添加轮廓失败: {e}") def add_surf(self, data: Dict[str, Any]): """add_surf - 添加表面""" try: surf = data.get("surf", {}) self.create_face(bpy.context.scene, surf) except Exception as e: logger.error(f"添加表面失败: {e}") def c13(self, data: Dict[str, Any]): """save_pixmap - 保存像素图 - 线程安全版本""" try: if not BLENDER_AVAILABLE: return def save_pixmap(): try: uid = data.get("uid") file_path = data.get("file") # 设置渲染参数 bpy.context.scene.render.filepath = file_path bpy.context.scene.render.image_settings.file_format = 'PNG' # 渲染当前视图 bpy.ops.render.render(write_still=True) return True except Exception as e: logger.error(f"保存像素图失败: {e}") return False # 在主线程中执行渲染操作 success = save_pixmap() if success: logger.info(f"✅ 成功保存像素图") else: logger.error(f"❌ 像素图保存失败") except Exception as e: logger.error(f"❌ 保存像素图失败: {e}") def c14(self, data: Dict[str, Any]): """pre_save_pixmap - 预保存像素图 - 线程安全版本""" try: if not BLENDER_AVAILABLE: return def pre_save_pixmap(): try: self.sel_clear() # 设置视图 if hasattr(bpy.context, 'space_data') and bpy.context.space_data: bpy.context.space_data.show_gizmo = False bpy.context.space_data.show_overlays = False return True except Exception as e: logger.error(f"预保存像素图失败: {e}") return False # 在主线程中执行预处理操作 success = pre_save_pixmap() if success: logger.info(f"✅ 预保存像素图完成") else: logger.error(f"❌ 预保存像素图失败") except Exception as e: logger.error(f"❌ 预保存像素图失败: {e}") def show_message(self, data: Dict[str, Any]): """显示消息""" try: message = data.get("message", "") logger.info(f"显示消息: {message}") # 在Blender中显示消息 if BLENDER_AVAILABLE: # 可以使用报告系统 pass except Exception as e: logger.error(f"显示消息失败: {e}") # ==================== 辅助方法 ==================== def _set_cmd(self, cmd, params): """设置命令(发送到客户端)""" try: # 在实际应用中需要实现客户端通信逻辑 logger.info(f"发送命令: {cmd}, 参数: {params}") except Exception as e: logger.error(f"设置命令失败: {e}") def _clear_labels(self, label_obj): """清理标签""" try: if label_obj and BLENDER_AVAILABLE: for child in label_obj.children: bpy.data.objects.remove(child, do_unlink=True) except Exception as e: logger.error(f"清理标签失败: {e}") # ==================== 属性访问器 ==================== @classmethod def selected_uid(cls): return cls._selected_uid @classmethod def selected_zone(cls): return cls._selected_zone @classmethod def selected_part(cls): return cls._selected_part @classmethod def selected_obj(cls): return cls._selected_obj @classmethod def server_path(cls): return cls._server_path @classmethod def default_zone(cls): return cls._default_zone # ==================== 清理和销毁 ==================== def shutdown(self): """关闭系统""" try: logger.info("开始关闭SUWood系统") # 执行最终清理 self.force_cleanup() # 清理所有缓存 self.mesh_cache.clear() self.material_cache.clear() self.object_references.clear() # 清理数据结构 self.parts.clear() self.zones.clear() self.hardwares.clear() self.machinings.clear() self.dimensions.clear() self.textures.clear() self.unit_param.clear() self.unit_trans.clear() logger.info("✅ SUWood系统关闭完成") except Exception as e: logger.error(f"关闭系统失败: {e}") def __del__(self): """析构函数""" try: self.shutdown() except: pass # ==================== 内存管理方法(保持原有的优化) ==================== def force_cleanup(self): """强制清理""" try: logger.info("开始强制清理") # 清理孤立数据 cleanup_count = memory_manager.cleanup_orphaned_data() # 清理缓存 self.mesh_cache.clear() # 清理过期的对象引用 current_time = time.time() expired_refs = [] for obj_name, ref_info in self.object_references.items(): if current_time - ref_info.get('creation_time', 0) > 3600: # 1小时 expired_refs.append(obj_name) for obj_name in expired_refs: del self.object_references[obj_name] # 强制垃圾回收 gc.collect() # 更新依赖图 if BLENDER_AVAILABLE: self._update_dependency_graph(full_update=True) logger.info( f"强制清理完成: 清理了 {cleanup_count} 个数据块,{len(expired_refs)} 个过期引用") except Exception as e: logger.error(f"强制清理失败: {e}") def _update_dependency_graph(self, full_update: bool = False): """更新Blender依赖图""" try: if not BLENDER_AVAILABLE: return if full_update: logger.info("执行全局依赖图更新") bpy.context.view_layer.update() bpy.context.evaluated_depsgraph_get().update() # 刷新视图 try: for area in bpy.context.screen.areas: if area.type in ['VIEW_3D', 'OUTLINER']: area.tag_redraw() except: pass logger.info("全局依赖图更新完成") else: # 快速更新 bpy.context.view_layer.update() except Exception as e: logger.error(f"依赖图更新失败: {e}") def get_creation_stats(self) -> Dict[str, Any]: """获取创建统计信息""" try: stats = { "object_references": len(self.object_references), "mesh_cache_size": len(self.mesh_cache), "material_cache_size": len(self.material_cache), "memory_manager_stats": memory_manager.creation_stats.copy(), "blender_available": BLENDER_AVAILABLE } if BLENDER_AVAILABLE: stats["total_objects"] = len(bpy.data.objects) stats["total_meshes"] = len(bpy.data.meshes) stats["total_materials"] = len(bpy.data.materials) return stats except Exception as e: logger.error(f"获取统计信息失败: {e}") return {"error": str(e)} def diagnose_system_state(self): """诊断系统状态""" try: logger.info("=== 系统状态诊断 ===") # 内存使用情况 stats = self.get_creation_stats() for key, value in stats.items(): logger.info(f"{key}: {value}") # 检查潜在问题 issues = [] if BLENDER_AVAILABLE: # 检查孤立数据 orphaned_meshes = [m for m in bpy.data.meshes if m.users == 0] if orphaned_meshes: issues.append(f"发现 {len(orphaned_meshes)} 个孤立网格") # 检查空网格 empty_meshes = [m for m in bpy.data.meshes if not m.vertices] if empty_meshes: issues.append(f"发现 {len(empty_meshes)} 个空网格") # 检查总顶点数 total_vertices = sum(len(m.vertices) for m in bpy.data.meshes) if total_vertices > 1000000: # 100万顶点 issues.append(f"顶点数量过多: {total_vertices}") if issues: logger.warning("发现问题:") for issue in issues: logger.warning(f" - {issue}") else: logger.info("✅ 系统状态正常") return issues except Exception as e: logger.error(f"系统诊断失败: {e}") return [f"诊断失败: {e}"] def get_memory_report(self) -> Dict[str, Any]: """获取内存报告""" try: report = { "timestamp": time.time(), "creation_stats": self.get_creation_stats(), "system_diagnosis": self.diagnose_system_state(), "memory_manager": { "object_registry_size": len(memory_manager.object_registry), "mesh_registry_size": len(memory_manager.mesh_registry), "last_cleanup": memory_manager.last_cleanup_time, "cleanup_interval": memory_manager.cleanup_interval } } if BLENDER_AVAILABLE: report["blender_data"] = { "objects": len(bpy.data.objects), "meshes": len(bpy.data.meshes), "materials": len(bpy.data.materials), "textures": len(bpy.data.textures), "images": len(bpy.data.images) } return report except Exception as e: logger.error(f"生成内存报告失败: {e}") return {"error": str(e)} # ==================== 几何变换辅助方法 ==================== def _transform_point(self, point, trans): """变换点""" try: if isinstance(point, (list, tuple)) and len(point) >= 3: # 简化的变换实现 return ( point[0] + trans.origin.x, point[1] + trans.origin.y, point[2] + trans.origin.z ) return point except Exception as e: logger.error(f"变换点失败: {e}") return point def _transform_vector(self, vector, trans): """变换向量""" try: if isinstance(vector, (list, tuple)) and len(vector) >= 3: # 简化的变换实现 return ( vector[0] * trans.x_axis.x, vector[1] * trans.y_axis.y, vector[2] * trans.z_axis.z ) return vector except Exception as e: logger.error(f"变换向量失败: {e}") return vector def _calculate_swing_door_transform(self, door_ps, door_pe, door_off): """计算平开门变换""" try: # 计算旋转轴和角度 axis = (door_pe[0] - door_ps[0], door_pe[1] - door_ps[1], door_pe[2] - door_ps[2]) angle = math.pi / 2 # 90度 # 在Blender中创建变换矩阵 if BLENDER_AVAILABLE: import mathutils rot_matrix = mathutils.Matrix.Rotation(angle, 4, axis) trans_matrix = mathutils.Matrix.Translation(door_off) return trans_matrix @ rot_matrix return None except Exception as e: logger.error(f"计算平开门变换失败: {e}") return None def _calculate_slide_door_transform(self, door_off): """计算推拉门变换""" try: if BLENDER_AVAILABLE: import mathutils return mathutils.Matrix.Translation(door_off) return None except Exception as e: logger.error(f"计算推拉门变换失败: {e}") return None def _calculate_translation_transform(self, vector): """计算平移变换""" try: if BLENDER_AVAILABLE: import mathutils if isinstance(vector, (list, tuple)): return mathutils.Matrix.Translation(vector) else: return mathutils.Matrix.Translation((vector.x, vector.y, vector.z)) return None except Exception as e: logger.error(f"计算平移变换失败: {e}") return None def _invert_transform(self, transform): """反转变换""" try: if transform and hasattr(transform, 'inverted'): return transform.inverted() return transform except Exception as e: logger.error(f"反转变换失败: {e}") return transform def _normalize_vector(self, x, y, z): """归一化向量""" try: length = math.sqrt(x*x + y*y + z*z) if length > 0: return (x/length, y/length, z/length) return (0, 0, 1) except Exception as e: logger.error(f"归一化向量失败: {e}") return (0, 0, 1) def _get_object_center(self, obj): """获取对象中心""" try: if BLENDER_AVAILABLE and obj and hasattr(obj, 'location'): return obj.location return (0, 0, 0) except Exception as e: logger.error(f"获取对象中心失败: {e}") return (0, 0, 0) def _is_in_door_layer(self, part): """检查是否在门图层""" try: if not part or not self.door_layer: return False return part in self.door_layer.objects except Exception as e: logger.error(f"检查门图层失败: {e}") return False def _create_text_label(self, text, location, direction): """创建文本标签""" try: if not BLENDER_AVAILABLE: return None # 创建文本对象 font_curve = bpy.data.curves.new(type="FONT", name="TextLabel") font_curve.body = text font_obj = bpy.data.objects.new("TextLabel", font_curve) # 设置位置和方向 font_obj.location = location if isinstance(direction, (list, tuple)) and len(direction) >= 3: # 简化的方向设置 font_obj.location = ( location[0] + direction[0] * 0.1, location[1] + direction[1] * 0.1, location[2] + direction[2] * 0.1 ) bpy.context.scene.collection.objects.link(font_obj) memory_manager.register_object(font_obj) return font_obj except Exception as e: logger.error(f"创建文本标签失败: {e}") return None def _create_contour_from_surf(self, surf): """从表面创建轮廓""" try: if not BLENDER_AVAILABLE: return xaxis = Vector3d.parse(surf.get("vx", "(1,0,0)")) zaxis = Vector3d.parse(surf.get("vz", "(0,0,1)")) segs = surf.get("segs", []) edges = [] for seg in segs: if "c" in seg: # 弧形段 c = Point3d.parse(seg["c"]) r = seg.get("r", 1.0) * 0.001 a1 = seg.get("a1", 0.0) a2 = seg.get("a2", math.pi * 2) n = seg.get("n", 12) # 创建弧形边 arc_edges = self._create_arc_edges( c, xaxis, zaxis, r, a1, a2, n) edges.extend(arc_edges) else: # 直线段 s = Point3d.parse(seg.get("s", "(0,0,0)")) e = Point3d.parse(seg.get("e", "(0,0,0)")) edge = self._create_line_edge_simple(bpy.context.scene, (s.x, s.y, s.z), (e.x, e.y, e.z)) if edge: edges.append(edge) # 尝试创建面 try: if edges: self._create_face_from_edges(bpy.context.scene, edges) except Exception as e: logger.warning(f"创建轮廓面失败: {e}") except Exception as e: logger.error(f"创建轮廓失败: {e}") def _create_arc_edges(self, center, xaxis, zaxis, radius, start_angle, end_angle, segments): """创建弧形边""" try: if not BLENDER_AVAILABLE: return [] edges = [] angle_step = (end_angle - start_angle) / segments for i in range(segments): angle1 = start_angle + i * angle_step angle2 = start_angle + (i + 1) * angle_step # 计算点 x1 = center.x + radius * math.cos(angle1) y1 = center.y + radius * math.sin(angle1) z1 = center.z x2 = center.x + radius * math.cos(angle2) y2 = center.y + radius * math.sin(angle2) z2 = center.z edge = self._create_line_edge_simple(bpy.context.scene, (x1, y1, z1), (x2, y2, z2)) if edge: edges.append(edge) return edges except Exception as e: logger.error(f"创建弧形边失败: {e}") return [] def _create_dimension(self, p1, p2, direction, text): """创建尺寸标注""" try: if not BLENDER_AVAILABLE: return None # 在Blender中创建尺寸标注的简化实现 # 创建文本对象显示尺寸 midpoint = ( (p1[0] + p2[0]) / 2 + direction[0] * 0.1, (p1[1] + p2[1]) / 2 + direction[1] * 0.1, (p1[2] + p2[2]) / 2 + direction[2] * 0.1 ) dimension_obj = self._create_text_label(text, midpoint, direction) # 创建尺寸线 if dimension_obj: # 添加线条表示尺寸 line_mesh = bpy.data.meshes.new("DimensionLine") vertices = [p1, p2] edges = [(0, 1)] line_mesh.from_pydata(vertices, edges, []) line_mesh.update() line_obj = bpy.data.objects.new("DimensionLine", line_mesh) line_obj.parent = dimension_obj bpy.context.scene.collection.objects.link(line_obj) memory_manager.register_mesh(line_mesh) memory_manager.register_object(line_obj) return dimension_obj except Exception as e: logger.error(f"创建尺寸标注失败: {e}") return None # ==================== 几何体创建的辅助方法(补充) ==================== def _create_triangle_face(self, container, tri, offset_vec, base_point): """创建三角形面""" try: if not BLENDER_AVAILABLE: return None # 计算三角形的三个顶点 p1 = (tri.x, tri.y, tri.z) p2 = (tri.x + offset_vec.x, tri.y + offset_vec.y, tri.z + offset_vec.z) p3 = (base_point.x + (base_point.x - tri.x), base_point.y + (base_point.y - tri.y), base_point.z + (base_point.z - tri.z)) # 创建网格 mesh = bpy.data.meshes.new("Triangle_Face") vertices = [p1, p2, p3] faces = [(0, 1, 2)] mesh.from_pydata(vertices, [], faces) mesh.update() # 创建对象 obj = bpy.data.objects.new("Triangle_Face_Obj", mesh) obj.parent = container bpy.context.scene.collection.objects.link(obj) memory_manager.register_mesh(mesh) memory_manager.register_object(obj) return obj except Exception as e: logger.error(f"创建三角形面失败: {e}") return None def _create_circle_face(self, container, center, normal, radius): """创建圆形面""" try: if not BLENDER_AVAILABLE: return None # 创建圆形网格 mesh = bpy.data.meshes.new("Circle_Face") # 生成圆形顶点 segments = 32 vertices = [(center.x, center.y, center.z)] # 中心点 for i in range(segments): angle = (i / segments) * 2 * math.pi x = center.x + radius * math.cos(angle) y = center.y + radius * math.sin(angle) z = center.z vertices.append((x, y, z)) # 创建面 faces = [] for i in range(segments): next_i = (i + 1) % segments faces.append((0, i + 1, next_i + 1)) mesh.from_pydata(vertices, [], faces) mesh.update() # 创建对象 obj = bpy.data.objects.new("Circle_Face_Obj", mesh) obj.parent = container bpy.context.scene.collection.objects.link(obj) memory_manager.register_mesh(mesh) memory_manager.register_object(obj) return obj except Exception as e: logger.error(f"创建圆形面失败: {e}") return None def _apply_material_to_face(self, face, material): """为面应用材质""" try: if not face or not material or not BLENDER_AVAILABLE: return if hasattr(face, 'data') and face.data: if not face.data.materials: face.data.materials.append(material) else: face.data.materials[0] = material except Exception as e: logger.error(f"为面应用材质失败: {e}") def _follow_me_face(self, face, path): """面跟随路径""" try: if not face or not path or not BLENDER_AVAILABLE: return # 在Blender中实现跟随路径 # 这里使用简化的实现 if hasattr(face, 'modifiers'): # 添加阵列修改器或其他相关修改器 pass except Exception as e: logger.error(f"面跟随路径失败: {e}") def _cleanup_path(self, path): """清理路径""" try: if path and BLENDER_AVAILABLE and path.name in bpy.data.objects: bpy.data.objects.remove(path, do_unlink=True) except Exception as e: logger.error(f"清理路径失败: {e}") def _cleanup_trimmer(self, trimmer): """清理修剪器""" try: if trimmer and BLENDER_AVAILABLE and trimmer.name in bpy.data.objects: bpy.data.objects.remove(trimmer, do_unlink=True) except Exception as e: logger.error(f"清理修剪器失败: {e}") def _trim_object(self, trimmer, target): """修剪对象""" try: if not trimmer or not target or not BLENDER_AVAILABLE: return target # 在Blender中实现布尔运算 # 这里使用简化的实现 return target except Exception as e: logger.error(f"修剪对象失败: {e}") return target def _mark_differ_faces(self, obj): """标记差异面""" try: if not obj or not BLENDER_AVAILABLE: return texture = self.get_texture("mat_default") if not texture: return # 标记所有使用默认材质的面为差异面 for child in obj.children: if hasattr(child, 'data') and child.data: if (child.data.materials and child.data.materials[0] == texture): child["sw_differ"] = True except Exception as e: logger.error(f"标记差异面失败: {e}") # ==================== 几何验证辅助方法 ==================== def _should_reverse_face(self, face, zaxis, reverse_face): """检查是否应该反转面""" try: if not face or not zaxis: return False # 简化的实现 return reverse_face except Exception as e: logger.error(f"检查面反转失败: {e}") return False def _face_normal_matches(self, face, zaxis): """检查面法向量是否匹配""" try: if not face or not zaxis: return False # 简化的实现 return True except Exception as e: logger.error(f"检查面法向量失败: {e}") return False def _reverse_face(self, face): """反转面""" try: if not face or not BLENDER_AVAILABLE: return if hasattr(face, 'data') and face.data: # 在Blender中反转面的法向量 bpy.context.view_layer.objects.active = face bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.flip_normals() bpy.ops.object.mode_set(mode='OBJECT') except Exception as e: logger.error(f"反转面失败: {e}") def _get_face_normal(self, face): """获取面法向量""" try: if not face or not BLENDER_AVAILABLE: return (0, 0, 1) if hasattr(face, 'data') and face.data and face.data.polygons: # 获取第一个多边形的法向量 return face.data.polygons[0].normal return (0, 0, 1) except Exception as e: logger.error(f"获取面法向量失败: {e}") return (0, 0, 1) def _apply_follow_me(self, face, path): """应用跟随路径""" try: if not face or not path or not BLENDER_AVAILABLE: return # 在Blender中实现跟随路径的简化版本 # 这里需要根据实际需求实现具体的几何操作 pass except Exception as e: logger.error(f"应用跟随路径失败: {e}") def _hide_edges(self, container): """隐藏边""" try: if not container or not BLENDER_AVAILABLE: return for child in container.children: if hasattr(child, 'data') and child.data and hasattr(child.data, 'edges'): for edge in child.data.edges: edge.use_edge_sharp = True except Exception as e: logger.error(f"隐藏边失败: {e}") def _create_face_fast(self, container, surface, material): """创建面 - 快速版本""" try: # 获取分段数据 segs = surface.get("segs", []) if not segs: return None # 快速解析顶点 vertices = [] for seg in segs: if len(seg) >= 2: coord_str = seg[0].strip('()') try: x, y, z = map(float, coord_str.split(',')) vertices.append((x * 0.001, y * 0.001, z * 0.001)) except: continue if len(vertices) < 3: return None # 创建简单网格 mesh = bpy.data.meshes.new(f"FastFace_{int(time.time())}") # 创建面(只支持三角形和四边形) if len(vertices) == 4: faces = [(0, 1, 2, 3)] elif len(vertices) == 3: faces = [(0, 1, 2)] else: # 复杂多边形简化为第一个三角形 faces = [(0, 1, 2)] vertices = vertices[:3] mesh.from_pydata(vertices, [], faces) mesh.update() # 创建对象 face_obj = bpy.data.objects.new(f"Face_{container.name}", mesh) face_obj.parent = container bpy.context.scene.collection.objects.link(face_obj) # 应用材质 if material: face_obj.data.materials.append(material) # 确保可见 face_obj.hide_viewport = False # 注册到内存管理器 memory_manager.register_mesh(mesh) memory_manager.register_object(face_obj) return face_obj except Exception as e: logger.error(f"快速创建面失败: {e}") return None def _create_board_six_faces_fast(self, leaf, data, color, scale, angle, color2, scale2, angle2): """快速创建板件六个面""" try: # 获取正反面数据 obv = data.get("obv") # 正面 rev = data.get("rev") # 反面 if not obv or not rev: logger.warning("缺少正反面数据") return # 处理材质 antiz = data.get("antiz", False) # 根据antiz决定材质分配 if antiz: # 交换正反面材质 obv_color = color2 if color2 else color rev_color = color else: # 正常材质分配 obv_color = color rev_color = color2 if color2 else color # 获取材质 material_obv = self.get_texture(obv_color) if obv_color else None material_rev = self.get_texture(rev_color) if rev_color else None edge_material = material_obv # 边面使用正面材质 # 1. 创建正面 (obverse) obv_face = self._create_face_fast(leaf, obv, material_obv) if obv_face: obv_face["sw_face_type"] = "obverse" obv_face["sw_face_id"] = "front" obv_face["sw_ckey"] = obv_color logger.debug("正面创建成功") # 2. 创建反面 (reverse) rev_face = self._create_face_fast(leaf, rev, material_rev) if rev_face: rev_face["sw_face_type"] = "reverse" rev_face["sw_face_id"] = "back" rev_face["sw_ckey"] = rev_color logger.debug("反面创建成功") # 3. 创建四个边面 self._create_board_edge_faces_fast(leaf, obv, rev, edge_material) logger.debug("板件六面创建完成") except Exception as e: logger.error(f"创建板件六面失败: {e}") def _create_board_edge_faces_fast(self, leaf, obv, rev, edge_material): """快速创建板件的四个边面""" try: # 解析正面和反面的顶点 obv_vertices = self._parse_surface_vertices(obv) rev_vertices = self._parse_surface_vertices(rev) if len(obv_vertices) != len(rev_vertices) or len(obv_vertices) < 3: logger.warning("正反面顶点数量不匹配或不足") return # 创建四个边面 vertex_count = len(obv_vertices) edge_count = 0 for i in range(vertex_count): next_i = (i + 1) % vertex_count # 边面的四个顶点:正面两个点 + 反面对应两个点 edge_vertices = [ obv_vertices[i], # 正面当前点 obv_vertices[next_i], # 正面下一点 rev_vertices[next_i], # 反面下一点 rev_vertices[i] # 反面当前点 ] # 创建边面 edge_face = self._create_face_from_vertices_fast( leaf, edge_vertices, edge_material, f"edge_{i}") if edge_face: edge_face["sw_face_type"] = "edge" edge_face["sw_face_id"] = f"edge_{i}" edge_face["sw_edge_index"] = i edge_count += 1 logger.debug(f"创建了 {edge_count}/{vertex_count} 个边面") except Exception as e: logger.error(f"创建边面失败: {e}") def _parse_surface_vertices(self, surface): """解析表面顶点坐标""" try: vertices = [] segs = surface.get("segs", []) for seg in segs: if len(seg) >= 2: coord_str = seg[0].strip('()') try: x, y, z = map(float, coord_str.split(',')) # 转换为米(Blender使用米作为单位) vertices.append((x * 0.001, y * 0.001, z * 0.001)) except ValueError: continue return vertices except Exception as e: logger.error(f"解析表面顶点失败: {e}") return [] def _create_face_from_vertices_fast(self, container, vertices, material, face_name): """从顶点快速创建面""" try: if len(vertices) < 3: return None # 创建网格 mesh = bpy.data.meshes.new(f"Face_{face_name}_{int(time.time())}") # 创建面 if len(vertices) == 4: faces = [(0, 1, 2, 3)] elif len(vertices) == 3: faces = [(0, 1, 2)] else: # 复杂多边形创建扇形三角形 faces = [] for i in range(1, len(vertices) - 1): faces.append((0, i, i + 1)) mesh.from_pydata(vertices, [], faces) mesh.update() # 创建对象 face_obj = bpy.data.objects.new(f"Face_{face_name}", mesh) face_obj.parent = container bpy.context.scene.collection.objects.link(face_obj) # 应用材质 if material: face_obj.data.materials.append(material) # 确保可见 face_obj.hide_viewport = False # 注册到内存管理器 memory_manager.register_mesh(mesh) memory_manager.register_object(face_obj) return face_obj except Exception as e: logger.error(f"从顶点创建面失败: {e}") return None def _add_part_board_fast(self, part, data): """创建板材部件 - 保持六面逻辑的快速版本""" try: # 创建叶子组 leaf = bpy.data.objects.new( f"Board_{part.name}_{int(time.time())}", None) leaf.parent = part bpy.context.scene.collection.objects.link(leaf) # 获取材质信息 color = data.get("ckey", "mat_default") scale = data.get("scale") angle = data.get("angle") color2 = data.get("ckey2") scale2 = data.get("scale2") angle2 = data.get("angle2") # 设置叶子属性 leaf["sw_ckey"] = color if scale: leaf["sw_scale"] = scale if angle: leaf["sw_angle"] = angle logger.debug(f"板材材质: {color}") # 创建板件的六个面(正面、反面、四个边面) self._create_board_six_faces_fast( leaf, data, color, scale, angle, color2, scale2, angle2) logger.debug(f"板材部件创建完成: {leaf.name}") return leaf except Exception as e: logger.error(f"创建板材部件失败: {e}") return None def _create_transparent_material(self): """创建透明材质用于容器对象 - 修复依赖图问题""" try: material_name = "SUW_Container_Transparent" # 检查是否已存在 if material_name in bpy.data.materials: return bpy.data.materials[material_name] # 创建透明材质 material = bpy.data.materials.new(name=material_name) material.use_nodes = True # 【修复】确保节点树存在 if not material.node_tree: logger.error("材质节点树创建失败") return None # 清理默认节点 material.node_tree.nodes.clear() # 【修复】创建节点时确保正确的依赖关系 try: # 创建 Principled BSDF 节点 bsdf = material.node_tree.nodes.new( type='ShaderNodeBsdfPrincipled') bsdf.location = (0, 0) # 创建输出节点 output = material.node_tree.nodes.new( type='ShaderNodeOutputMaterial') output.location = (300, 0) # 【修复】确保节点有效后再连接 if bsdf and output and bsdf.outputs and output.inputs: # 连接节点 material.node_tree.links.new( bsdf.outputs['BSDF'], output.inputs['Surface']) # 设置完全透明 if 'Base Color' in bsdf.inputs: bsdf.inputs['Base Color'].default_value = ( 0.5, 0.5, 0.5, 1.0) # 灰色 if 'Alpha' in bsdf.inputs: bsdf.inputs['Alpha'].default_value = 0.0 # 完全透明 # 设置混合模式 material.blend_method = 'BLEND' material.use_backface_culling = False # 【修复】强制更新材质节点 material.node_tree.update_tag() logger.info(f"✅ 创建容器透明材质: {material_name}") return material else: logger.error("节点创建失败,无法建立连接") bpy.data.materials.remove(material) return None except Exception as e: logger.error(f"材质节点设置失败: {e}") bpy.data.materials.remove(material) return None except Exception as e: logger.error(f"创建透明材质失败: {e}") return None def c03(self, data: Dict[str, Any]): """add_zone - 添加区域 - 异步安全版本,批量依赖图更新""" # 此函数已由 _schedule_command 在主线程中安全调用, # 无需再使用任何旧的 execute_in_main_thread 封装。 try: uid = data.get("uid") zid = data.get("zid") # 约定:zip_id 为 0 或 1 代表根级别,不寻找父级 zip_id = data.get("zip", 0) elements = data.get("children", []) # 创建区域组 - 保持为Empty对象 group_name = f"Zone_{zid}" # 【修复】安全删除已存在的Zone - 避免递归导致假死 if BLENDER_AVAILABLE and group_name in bpy.data.objects: old_group = bpy.data.objects[group_name] # 【彻底重构】极简化的对象删除策略 - 避免复杂的清理逻辑 def delete_hierarchy_ultra_safe(root_obj): """超级安全的对象删除 - 最小化依赖图干扰""" try: # 【策略1】只删除对象,让Blender自动处理网格清理 if root_obj and hasattr(root_obj, 'name') and root_obj.name in bpy.data.objects: print(f"🗑️ 简单删除对象: {root_obj.name}") # 【修复】使用非阻塞的选择清除 try: if hasattr(root_obj, 'select_set'): root_obj.select_set(False) except: pass # 直接删除对象,让Blender处理所有清理 bpy.data.objects.remove(root_obj, do_unlink=True) # 从内存管理器中移除(但不强制清理网格) if hasattr(root_obj, 'name'): memory_manager.tracked_objects.discard( root_obj.name) print( f"✅ 对象删除完成: {getattr(root_obj, 'name', 'Unknown')}") else: print(f"⚠️ 对象已不存在,跳过删除") except Exception as e: print(f"⚠️ 对象删除失败: {e}") # 不进行任何额外的清理尝试,避免连锁问题 delete_hierarchy_ultra_safe(old_group) print(f"✅ 安全删除了已存在的Zone: {group_name}") if BLENDER_AVAILABLE: group = bpy.data.objects.new(group_name, None) else: # 存根模式下创建模拟对象 class MockObject: def __init__(self, name): self.name = name self.parent = None self.children = [] self._props = {} def __setitem__(self, key, value): self._props[key] = value def __getitem__(self, key): return self._props.get(key) group = MockObject(group_name) # 核心逻辑修改:只有当 zip_id > 1 时,才代表一个真实的父对象 if zip_id > 1: parent_zone_name = f"Zone_{zip_id}" if BLENDER_AVAILABLE: parent_zone = bpy.data.objects.get(parent_zone_name) if parent_zone: group.parent = parent_zone else: # 这个警告是正常的,如果父级命令还没到 print( f"⚠️ 未找到父Zone '{parent_zone_name}',对象 '{group_name}' 将被创建在根级别。") else: print( f"📝 存根模式:设置父Zone '{parent_zone_name}' -> '{group_name}'") # 设置自定义属性 group["sw_uid"] = uid group["sw_zid"] = zid group["sw_zip"] = zip_id group["sw_typ"] = "zid" if BLENDER_AVAILABLE: bpy.context.scene.collection.objects.link(group) else: print(f"📝 存根模式:将对象 '{group_name}' 添加到场景") # 存储引用 - 先维持现状,后续可优化为存储名称 if uid not in self.zones: self.zones[uid] = {} self.zones[uid][zid] = group # 【修复】批量处理子元素,避免频繁的依赖图更新 if elements: print(f"📦 开始创建 {len(elements)} 个子面...") created_faces = [] # 批量创建所有子面,不立即更新依赖图 for i, element in enumerate(elements): surf = element.get("surf") if surf: try: face = self.create_face_safe( group, surf, transparent=True) if face: created_faces.append(face) print( f"✅ 子面 {i+1}/{len(elements)} 创建成功: {face.name}") else: print(f"⚠️ 子面 {i+1}/{len(elements)} 创建失败") except Exception as e: print(f"💥 子面 {i+1}/{len(elements)} 创建异常: {e}") continue # 【临时禁用】依赖图批量更新 - 让Blender自动处理 if created_faces: print(f"🔄 跳过依赖图更新,让Blender自动处理") # dependency_manager.request_update(force=True) # 暂时禁用 print(f"✅ 依赖图更新策略:自动模式") print(f"🎉 Zone_{zid} 创建完成,包含 {len(created_faces)} 个子面") return group except Exception as e: print(f"💥 c03命令执行时发生严重错误: {e}") import traceback traceback.print_exc() return None def create_face_safe(self, container, surface, color=None, scale=None, angle=None, series=None, reverse_face=False, back_material=True, saved_color=None, typ=None, transparent=False): """创建面 - 支持透明材质选项,修复依赖图和内存问题""" try: if not BLENDER_AVAILABLE: logger.error("Blender不可用") return None # 获取分段数据 segs = surface.get("segs", []) if not segs: logger.error("没有分段数据") return None # 创建顶点 vertices = [] for i, seg in enumerate(segs): if len(seg) >= 2: coord_str = seg[0].strip('()') try: x, y, z = map(float, coord_str.split(',')) # 转换为米(Blender使用米作为单位) vertex = (x * 0.001, y * 0.001, z * 0.001) vertices.append(vertex) except ValueError as e: logger.error(f"解析顶点失败: {coord_str}, 错误: {e}") continue if len(vertices) < 3: logger.error(f"顶点数量不足,无法创建面: {len(vertices)}") return None # 【修复】创建唯一的网格名称,避免ID冲突 timestamp = int(time.time() * 1000000) # 微秒级时间戳 face_id = surface.get('f', 0) mesh_name = f"SUW_Face_Mesh_{face_id}_{timestamp}" obj_name = f"Face_{face_id}" # 确保名称唯一性 counter = 1 original_obj_name = obj_name while obj_name in bpy.data.objects: obj_name = f"{original_obj_name}_{counter}" counter += 1 # 【修复】使用上下文管理器确保安全的对象创建 mesh = None face_obj = None try: # 创建网格 mesh = bpy.data.meshes.new(mesh_name) # 创建面 edges = [] faces = [] if len(vertices) == 4: # 四边形 faces = [(0, 1, 2, 3)] elif len(vertices) == 3: # 三角形 faces = [(0, 1, 2)] else: # 复杂多边形,创建扇形三角形 for i in range(1, len(vertices) - 1): faces.append((0, i, i + 1)) # 【修复】安全的网格创建流程 mesh.from_pydata(vertices, edges, faces) mesh.validate() # 验证网格数据 mesh.update() # 更新网格 # 【修复】创建对象前确保网格完整性 if not mesh.vertices or not mesh.polygons: logger.error( f"网格创建失败,顶点数: {len(mesh.vertices)}, 面数: {len(mesh.polygons)}") # 让Blender自动清理无效网格,不手动删除 return None # 创建对象 face_obj = bpy.data.objects.new(obj_name, mesh) # 【修复】设置父对象关系 - 在添加到场景之前 if container and hasattr(container, 'name') and container.name in bpy.data.objects: face_obj.parent = container # 【修复】添加到场景 - 必须在设置属性之前 bpy.context.scene.collection.objects.link(face_obj) # 设置面属性 if surface.get("p"): face_obj["sw_p"] = surface["p"] if surface.get("f"): face_obj["sw_f"] = surface["f"] # 确保对象可见 face_obj.hide_viewport = False face_obj.hide_render = False face_obj.hide_set(False) # 【修复】材质应用 - 简化流程避免依赖图冲突 try: if transparent: # 应用透明材质 transparent_material = self._create_transparent_material() if transparent_material: # 确保材质槽存在 if not face_obj.data.materials: face_obj.data.materials.append(None) face_obj.data.materials[0] = transparent_material except Exception as e: logger.warning(f"材质应用失败: {e}") # 【暂时禁用】内存管理器注册 - 让Blender自动管理 # if mesh: # memory_manager.register_mesh(mesh) # if face_obj: # memory_manager.register_object(face_obj) print(f"📝 对象创建完成,使用Blender自动内存管理") # 添加到series(如果提供) if series is not None: series.append(face_obj) return face_obj except Exception as e: logger.error(f"对象创建过程中发生错误: {e}") # 【修复】安全清理失败的对象 if face_obj: try: if hasattr(face_obj, 'name') and face_obj.name in bpy.data.objects: bpy.data.objects.remove( face_obj, do_unlink=True) if hasattr(face_obj, 'name'): memory_manager.tracked_objects.discard( face_obj.name) except (AttributeError, ReferenceError, RuntimeError): # 对象可能已经被删除 pass except Exception as e: logger.debug(f"清理失败对象时的预期错误: {e}") if mesh: try: # 只清理跟踪记录,不删除网格数据,让Blender自动处理 if hasattr(mesh, 'name'): memory_manager.tracked_meshes.discard( mesh.name) except (AttributeError, ReferenceError, RuntimeError): # 网格可能已经被删除 pass except Exception as e: logger.debug(f"清理失败网格时的预期错误: {e}") return None except Exception as e: logger.error(f"创建面失败: {e}") import traceback traceback.print_exc() return None def _execute_c16(self, data): """sel_zone - 选择区域""" try: return self.sel_zone_local(data) except Exception as e: print(f"💥 c16命令执行时发生严重错误: {e}") import traceback traceback.print_exc() return None def _execute_c17(self, data): """sel_elem - 选择元素(可能涉及父子关系设置)""" try: if self.part_mode: return self.sel_part_parent(data) else: return self.sel_zone_local(data) except Exception as e: print(f"💥 c17命令执行时发生严重错误: {e}") import traceback traceback.print_exc() return None def sel_zone_local(self, data): """选择区域 - 本地执行""" try: uid = data.get("uid") zid = data.get("zid") print(f"🎯 选择区域: uid={uid}, zid={zid}") # 清除之前的选择 self.sel_clear() # 根据 uid 获取对应的 zones if uid not in self.zones: print(f"⚠️ 未找到 uid '{uid}' 对应的区域") return None zones = self.zones[uid] # 查找指定的区域 if zid not in zones: print(f"⚠️ 未找到 zid '{zid}' 对应的区域") return None zone = zones[zid] # 这里可以添加选择逻辑,比如高亮显示等 print(f"✅ 成功选择区域: Zone_{zid}") return zone except Exception as e: print(f"💥 sel_zone_local 执行失败: {e}") return None def sel_part_parent(self, data): """选择部件父级 - 可能涉及父子关系设置""" try: uid = data.get("uid") zid = data.get("zid") pid = data.get("pid") print(f"🎯 选择部件父级: uid={uid}, zid={zid}, pid={pid}") # 清除之前的选择 self.sel_clear() # 这里可以添加部件父子关系的设置逻辑 # 根据你的具体需求来实现 print(f"✅ 成功选择部件父级") return True except Exception as e: print(f"💥 sel_part_parent 执行失败: {e}") return None def sel_clear(self): """清除所有选择""" try: print("🔄 清除所有选择状态") # 这里可以添加清除选择状态的逻辑 except Exception as e: print(f"💥 sel_clear 执行失败: {e}") # ==================== 模块级别的便利函数 ==================== def create_suw_instance(): """创建SUW实例的便利函数""" return SUWImpl.get_instance() def cleanup_blender_memory(): """清理Blender内存的便利函数""" try: if BLENDER_AVAILABLE: cleanup_count = memory_manager.cleanup_orphaned_data() gc.collect() logger.info(f"清理了 {cleanup_count} 个孤立数据") return cleanup_count return 0 except Exception as e: logger.error(f"清理内存失败: {e}") return 0 def get_memory_usage_summary(): """获取内存使用摘要""" try: if BLENDER_AVAILABLE: return { "objects": len(bpy.data.objects), "meshes": len(bpy.data.meshes), "materials": len(bpy.data.materials), "memory_manager_stats": memory_manager.creation_stats.copy() } return {"blender_available": False} except Exception as e: logger.error(f"获取内存使用摘要失败: {e}") return {"error": str(e)} # ==================== 主程序入口 ==================== if __name__ == "__main__": # 测试代码 try: logger.info("开始测试SUWImpl内存管理") # 创建实例 suw = create_suw_instance() suw.startup() # 获取内存报告 report = suw.get_memory_report() logger.info(f"内存报告: {report}") # 执行诊断 issues = suw.diagnose_system_state() if not issues: logger.info("✅ 系统状态正常") # 清理测试 cleanup_count = cleanup_blender_memory() logger.info(f"清理测试完成: {cleanup_count}") except Exception as e: logger.error(f"测试失败: {e}") finally: logger.info("测试完成")