#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SUW Core - Deletion Manager Module 拆分自: suw_impl.py (Line 4274-4800, 6970-7100) 用途: Blender删除管理、对象清理、数据结构维护 版本: 1.0.0 作者: SUWood Team """ from .memory_manager import memory_manager, execute_in_main_thread import time import logging import threading from typing import Dict, Any, List, Optional # 设置日志 logger = logging.getLogger(__name__) # 检查Blender可用性 try: import bpy BLENDER_AVAILABLE = True except ImportError: BLENDER_AVAILABLE = False # 导入依赖模块 # ==================== 删除管理器类 ==================== class DeletionManager: """删除管理器 - 负责所有删除相关操作""" def __init__(self, suw_impl=None): """ 初始化删除管理器 Args: suw_impl: SUWImpl实例的引用(可选) """ self.suw_impl = suw_impl # 删除统计 self.deletion_stats = { "units_deleted": 0, "zones_deleted": 0, "parts_deleted": 0, "hardwares_deleted": 0, "objects_deleted": 0, "deletion_errors": 0 } logger.info("✅ 删除管理器初始化完成") # ==================== 原始命令方法 ==================== def c09(self, data: Dict[str, Any]): """del_entity - 删除实体 - 完全对应c03/c04创建逻辑的析构函数""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过删除操作") return None def delete_entities(): try: # 确保在主线程中执行 if threading.current_thread() != threading.main_thread(): logger.warning("删除操作转移到主线程执行") return 0 # 清除所有选择 if self.suw_impl and hasattr(self.suw_impl, 'sel_clear'): self.suw_impl.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() # 强制更新视图 if BLENDER_AVAILABLE: bpy.context.view_layer.update() logger.info(f"✅ 删除实体完成: uid={uid}, typ={typ}, oid={oid}") return True except Exception as e: logger.error(f"删除实体失败: {e}") self.deletion_stats["deletion_errors"] += 1 import traceback logger.error(traceback.format_exc()) return False # 在主线程中执行删除操作 if self.suw_impl and hasattr(self.suw_impl, 'execute_in_main_thread'): result = self.suw_impl.execute_in_main_thread(delete_entities) # 确保返回有效值 return result if result is not None else True else: # 直接执行 result = delete_entities() return result if result is not None else True except Exception as e: logger.error(f"❌ 删除实体失败: {e}") self.deletion_stats["deletion_errors"] += 1 return None def c03(self, data: Dict[str, Any]): """删除层次结构 - 超级安全版本""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过层次删除") return None logger.info("🗑️ 执行c03命令: 删除层次结构") # 使用保守的删除策略 def delete_hierarchy_ultra_safe(root_obj): """超级保守的层次删除""" try: if not root_obj or not self._is_object_valid(root_obj): return 0 deleted_count = 0 children_to_delete = [] # 收集所有子对象 def collect_children(obj, collection): if obj and self._is_object_valid(obj): collection.append(obj) for child in obj.children: collect_children(child, collection) collect_children(root_obj, children_to_delete) # 从叶子节点开始删除 for obj in reversed(children_to_delete): if self._delete_object_safe(obj): deleted_count += 1 return deleted_count except Exception as e: logger.error(f"层次删除失败: {e}") return 0 # 获取目标对象并执行删除 uid = data.get("uid", "") target_name = data.get("target", f"Unit_{uid}") target_obj = bpy.data.objects.get(target_name) if target_obj: deleted_count = delete_hierarchy_ultra_safe(target_obj) logger.info(f"✅ 层次删除完成: 删除了 {deleted_count} 个对象") return deleted_count else: logger.warning(f"目标对象不存在: {target_name}") return 0 except Exception as e: logger.error(f"c03命令执行失败: {e}") self.deletion_stats["deletion_errors"] += 1 return None # ==================== 核心删除方法 ==================== def _del_unit_complete(self, uid: str): """完整删除单元 - 对应c03/c04的完整创建逻辑""" try: logger.info(f"🗑️ 开始完整删除单元: {uid}") if not self.suw_impl: logger.error("缺少SUWImpl引用,无法删除单元") return # 1. 删除所有区域 (对应c03创建的zones) if hasattr(self.suw_impl, 'zones') and uid in self.suw_impl.zones: zones_to_delete = list(self.suw_impl.zones[uid].keys()) for zid in zones_to_delete: self._del_zone_complete(uid, zid) # 清空zones字典 del self.suw_impl.zones[uid] logger.info(f"✅ 清理了单元 {uid} 的所有区域数据") # 2. 删除所有部件 (对应c04创建的parts) if hasattr(self.suw_impl, 'parts') and uid in self.suw_impl.parts: parts_to_delete = list(self.suw_impl.parts[uid].keys()) for cp in parts_to_delete: self._del_part_complete(uid, cp) # 清空parts字典 del self.suw_impl.parts[uid] logger.info(f"✅ 清理了单元 {uid} 的所有部件数据") # 3. 删除所有硬件 (对应c08创建的hardwares) if hasattr(self.suw_impl, 'hardwares') and uid in self.suw_impl.hardwares: hardwares_to_delete = list(self.suw_impl.hardwares[uid].keys()) for hw_id in hardwares_to_delete: self._del_hardware_complete(uid, hw_id) # 清空hardwares字典 del self.suw_impl.hardwares[uid] logger.info(f"✅ 清理了单元 {uid} 的所有硬件数据") # 4. 删除所有加工 (对应c05创建的machinings) if hasattr(self.suw_impl, 'machinings') and uid in self.suw_impl.machinings: del self.suw_impl.machinings[uid] logger.info(f"✅ 清理了单元 {uid} 的所有加工数据") # 5. 删除所有尺寸标注 (对应c07创建的dimensions) if hasattr(self.suw_impl, 'dimensions') and uid in self.suw_impl.dimensions: del self.suw_impl.dimensions[uid] logger.info(f"✅ 清理了单元 {uid} 的所有尺寸标注数据") # 6. 清理单元级别的数据 self._cleanup_unit_data(uid) # 7. 清理c15缓存 if hasattr(self.suw_impl, '_clear_c15_cache'): self.suw_impl._clear_c15_cache(uid) # 更新统计 self.deletion_stats["units_deleted"] += 1 logger.info(f"🎉 单元 {uid} 完整删除完成") except Exception as e: logger.error(f"完整删除单元失败 {uid}: {e}") self.deletion_stats["deletion_errors"] += 1 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 (self.suw_impl and hasattr(self.suw_impl, 'zones') and uid in self.suw_impl.zones and zid in self.suw_impl.zones[uid]): del self.suw_impl.zones[uid][zid] logger.info(f"✅ 从zones数据结构中移除: uid={uid}, zid={zid}") # 更新统计 self.deletion_stats["zones_deleted"] += 1 logger.info(f"✅ 区域删除完成: uid={uid}, zid={zid}") except Exception as e: logger.error(f"删除区域失败 uid={uid}, zid={zid}: {e}") self.deletion_stats["deletion_errors"] += 1 def _del_part_complete(self, uid: str, cp: int): """完整删除部件 - 与c04完全对称的删除逻辑""" try: logger.info(f"🗑️ 开始删除部件: uid={uid}, cp={cp}") if not self.suw_impl: logger.error("缺少SUWImpl引用,无法删除部件") return # 【数据结构优先策略】先从数据结构获取信息 parts = self.suw_impl.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'}") # 获取创建记录(如果存在) 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}", # 表面板材 ] # 搜索带时间戳的板材 if BLENDER_AVAILABLE: 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. 搜索所有可能的相关对象(基于属性) if BLENDER_AVAILABLE: 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 # 7. 最后清理数据结构(对应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对象用于调试 if BLENDER_AVAILABLE: 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 self.deletion_stats["parts_deleted"] += 1 self.deletion_stats["objects_deleted"] += total_deleted logger.info( f"🎉 部件删除完成: uid={uid}, cp={cp}, 共删除 {total_deleted} 个对象") except Exception as e: logger.error(f"❌ 删除部件失败 uid={uid}, cp={cp}: {e}") self.deletion_stats["deletion_errors"] += 1 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 not self.suw_impl: logger.error("缺少SUWImpl引用,无法删除硬件") return # 从数据结构中查找并删除硬件对象 if (hasattr(self.suw_impl, 'hardwares') and uid in self.suw_impl.hardwares and hw_id in self.suw_impl.hardwares[uid]): hw_obj = self.suw_impl.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.suw_impl.hardwares[uid][hw_id] logger.info(f"✅ 从hardwares数据结构中移除: uid={uid}, hw_id={hw_id}") else: logger.warning(f"硬件不存在: uid={uid}, hw_id={hw_id}") # 更新统计 self.deletion_stats["hardwares_deleted"] += 1 logger.info(f"✅ 硬件删除完成: uid={uid}, hw_id={hw_id}") except Exception as e: logger.error(f"删除硬件失败 uid={uid}, hw_id={hw_id}: {e}") self.deletion_stats["deletion_errors"] += 1 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}") # 获取相应的实体集合 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}") self.deletion_stats["deletion_errors"] += 1 def _del_wall_entity_safe(self, data: Dict[str, Any], uid: str, oid: int): """安全删除墙体实体""" try: logger.info(f"删除墙体实体: uid={uid}, oid={oid}") if not BLENDER_AVAILABLE: return # 查找并删除墙体对象 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}") self.deletion_stats["objects_deleted"] += deleted_count logger.info( f"墙体删除完成: {deleted_count}/{len(objects_to_delete)} 个对象") except Exception as e: logger.error(f"删除墙体实体失败: {e}") self.deletion_stats["deletion_errors"] += 1 # ==================== 辅助方法 ==================== def _is_object_valid(self, obj) -> bool: """检查对象是否仍然有效""" try: if not BLENDER_AVAILABLE or not obj: return False # 尝试访问对象的基本属性 _ = 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) # 从所有集合中移除对象 for collection in obj.users_collection: collection.objects.unlink(obj) # 删除对象 bpy.data.objects.remove(obj, do_unlink=True) # 注册到内存管理器 memory_manager.cleanup_orphaned_data() logger.debug(f"✅ 对象删除成功: {obj_name} ({obj_type})") return True except Exception as e: logger.error( f"❌ 删除对象失败 {obj_name if 'obj_name' in locals() else 'unknown'}: {e}") return False def _cleanup_unit_data(self, uid: str): """清理单元数据""" try: logger.info(f"🧹 清理单元数据: {uid}") if not self.suw_impl: return # 清理单元变换数据 if hasattr(self.suw_impl, 'unit_trans') and uid in self.suw_impl.unit_trans: del self.suw_impl.unit_trans[uid] logger.info(f"✅ 清理单元变换数据: {uid}") # 清理单元参数数据 if hasattr(self.suw_impl, 'unit_param') and uid in self.suw_impl.unit_param: del self.suw_impl.unit_param[uid] logger.info(f"✅ 清理单元参数数据: {uid}") # 强制垃圾回收 import gc gc.collect() except Exception as e: logger.error(f"清理单元数据失败: {e}") def _clear_labels_safe(self): """安全清理标签""" try: if not BLENDER_AVAILABLE: return # 查找并删除标签对象 labels_to_delete = [] for obj in bpy.data.objects: if obj.get("sw_typ") == "label" or "Label" in obj.name: labels_to_delete.append(obj) for label in labels_to_delete: self._delete_object_safe(label) if labels_to_delete: logger.info(f"✅ 清理了 {len(labels_to_delete)} 个标签对象") except Exception as e: logger.error(f"清理标签失败: {e}") # ==================== 统计和管理方法 ==================== def get_deletion_stats(self) -> Dict[str, Any]: """获取删除统计信息""" try: return { "deletion_stats": self.deletion_stats.copy(), "total_deletions": ( self.deletion_stats["units_deleted"] + self.deletion_stats["zones_deleted"] + self.deletion_stats["parts_deleted"] + self.deletion_stats["hardwares_deleted"] ), "success_rate": ( 1.0 - (self.deletion_stats["deletion_errors"] / max(1, sum(self.deletion_stats.values()))) ) * 100 } except Exception as e: logger.error(f"获取删除统计失败: {e}") return {"error": str(e)} def reset_deletion_stats(self): """重置删除统计""" self.deletion_stats = { "units_deleted": 0, "zones_deleted": 0, "parts_deleted": 0, "hardwares_deleted": 0, "objects_deleted": 0, "deletion_errors": 0 } logger.info("删除统计已重置") def cleanup(self): """清理删除管理器""" try: # 重置统计 self.reset_deletion_stats() logger.info("✅ 删除管理器清理完成") except Exception as e: logger.error(f"清理删除管理器失败: {e}") # ==================== 全局删除管理器实例 ==================== # 全局实例 deletion_manager = None def init_deletion_manager(suw_impl): """初始化全局删除管理器实例""" global deletion_manager deletion_manager = DeletionManager(suw_impl) return deletion_manager def get_deletion_manager(): """获取全局删除管理器实例""" return deletion_manager