#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SUW Core - Data Manager Module 数据管理中心 - 统一管理所有共用数据 用途: 替代Ruby中的@zones、@parts、@hardwares等全局变量 版本: 1.0.0 作者: SUWood Team """ import logging import threading from typing import Dict, Any, Optional, List # 设置日志 logger = logging.getLogger(__name__) # 检查Blender可用性 try: import bpy BLENDER_AVAILABLE = True except ImportError: BLENDER_AVAILABLE = False # ==================== 数据管理器类 ==================== class DataManager: """数据管理器 - 统一管理所有SUWood数据""" def __init__(self): """初始化数据管理器""" # 核心数据存储 - 对应Ruby的实例变量 self.zones = {} # @zones - {uid: {zone_id: zone_data}} self.parts = {} # @parts - {uid: {part_id: part_data}} self.hardwares = {} # @hardwares - {uid: {hw_id: hw_data}} self.labels = {} # @labels - {uid: {label_id: label_data}} self.door_labels = {} # @door_labels - {uid: {door_label_id: door_label_data}} self.machinings = {} # @machinings - {uid: [machining_list]} self.dimensions = {} # @dimensions - {uid: [dimension_list]} # 单元参数 - 对应Ruby的@unit_param和@unit_trans self.unit_params = {} # @unit_param - {uid: params} self.unit_trans = {} # @unit_trans - {uid: transformation} # 材质和纹理 - 对应Ruby的@textures self.textures = {} # @textures - {ckey: material} # 系统状态 self.part_mode = False # @part_mode self.hide_none = False # @hide_none self.mat_type = 0 # @mat_type (MAT_TYPE_NORMAL) self.back_material = False # @back_material self.added_contour = False # @added_contour # 选择状态 - 对应Ruby的类变量 self.selected_uid = None # @@selected_uid self.selected_obj = None # @@selected_obj self.selected_zone = None # @@selected_zone self.selected_part = None # @@selected_part self.scaled_zone = None # @@scaled_zone # 选择集合 self.selected_faces = [] # @selected_faces self.selected_parts = [] # @selected_parts self.selected_hws = [] # @selected_hws # Blender对象引用 self.labels_group = None # @labels - Blender组对象 self.door_labels_group = None # @door_labels - Blender组对象 self.door_layer = None # @door_layer self.drawer_layer = None # @drawer_layer self.default_zone = None # @@default_zone # 线程安全 self.lock = threading.Lock() logger.info("✅ 数据管理器初始化完成") # ==================== Zones 数据管理 ==================== def get_zones(self, data: Dict[str, Any]) -> Dict[str, Any]: """获取区域数据 - 对应Ruby的get_zones方法""" uid = data.get("uid") if not uid: return {} with self.lock: if uid not in self.zones: self.zones[uid] = {} return self.zones[uid] def add_zone(self, uid: str, zid: Any, zone_obj: Any): """添加区域""" with self.lock: if uid not in self.zones: self.zones[uid] = {} self.zones[uid][zid] = zone_obj logger.debug(f"添加区域: uid={uid}, zid={zid}") def remove_zone(self, uid: str, zid: Any) -> bool: """删除区域""" with self.lock: if uid in self.zones and zid in self.zones[uid]: del self.zones[uid][zid] logger.debug(f"删除区域: uid={uid}, zid={zid}") return True return False def clear_zones(self, uid: str): """清空指定uid的所有区域""" with self.lock: if uid in self.zones: del self.zones[uid] logger.debug(f"清空区域: uid={uid}") # ==================== Parts 数据管理 ==================== def get_parts(self, data: Dict[str, Any]) -> Dict[str, Any]: """获取部件数据 - 对应Ruby的get_parts方法""" uid = data.get("uid") if not uid: return {} with self.lock: if uid not in self.parts: self.parts[uid] = {} return self.parts[uid] def add_part(self, uid: str, part_id: Any, part_obj: Any): """添加部件""" with self.lock: if uid not in self.parts: self.parts[uid] = {} self.parts[uid][part_id] = part_obj logger.debug(f"添加部件: uid={uid}, part_id={part_id}") def remove_part(self, uid: str, part_id: Any) -> bool: """删除部件""" with self.lock: if uid in self.parts and part_id in self.parts[uid]: del self.parts[uid][part_id] logger.debug(f"删除部件: uid={uid}, part_id={part_id}") return True return False def clear_parts(self, uid: str): """清空指定uid的所有部件""" with self.lock: if uid in self.parts: del self.parts[uid] logger.debug(f"清空部件: uid={uid}") # ==================== Hardwares 数据管理 ==================== def get_hardwares(self, data: Dict[str, Any]) -> Dict[str, Any]: """获取硬件数据 - 对应Ruby的get_hardwares方法""" uid = data.get("uid") if not uid: return {} with self.lock: if uid not in self.hardwares: self.hardwares[uid] = {} return self.hardwares[uid] def add_hardware(self, uid: str, hw_id: Any, hw_obj: Any): """添加硬件""" with self.lock: if uid not in self.hardwares: self.hardwares[uid] = {} self.hardwares[uid][hw_id] = hw_obj logger.debug(f"添加硬件: uid={uid}, hw_id={hw_id}") def remove_hardware(self, uid: str, hw_id: Any) -> bool: """删除硬件""" with self.lock: if uid in self.hardwares and hw_id in self.hardwares[uid]: del self.hardwares[uid][hw_id] logger.debug(f"删除硬件: uid={uid}, hw_id={hw_id}") return True return False def clear_hardwares(self, uid: str): """清空指定uid的所有硬件""" with self.lock: if uid in self.hardwares: del self.hardwares[uid] logger.debug(f"清空硬件: uid={uid}") # ==================== Labels 数据管理 ==================== def get_labels(self, uid: str) -> Dict[str, Any]: """获取标签数据""" with self.lock: if uid not in self.labels: self.labels[uid] = {} return self.labels[uid] def get_door_labels(self, uid: str) -> Dict[str, Any]: """获取门标签数据""" with self.lock: if uid not in self.door_labels: self.door_labels[uid] = {} return self.door_labels[uid] # ==================== Machinings 数据管理 ==================== def get_machinings(self, uid: str) -> List[Any]: """获取加工数据""" with self.lock: if uid not in self.machinings: self.machinings[uid] = [] return self.machinings[uid] def add_machining(self, uid: str, machining_obj: Any): """添加加工""" with self.lock: if uid not in self.machinings: self.machinings[uid] = [] self.machinings[uid].append(machining_obj) logger.debug(f"添加加工: uid={uid}") def clear_machinings(self, uid: str): """清空指定uid的所有加工""" with self.lock: if uid in self.machinings: self.machinings[uid].clear() logger.debug(f"清空加工: uid={uid}") def cleanup_machinings(self, uid: str): """清理指定uid的已删除加工对象""" with self.lock: if uid in self.machinings: # 移除已删除的对象 self.machinings[uid] = [ machining for machining in self.machinings[uid] if machining and self._is_entity_valid(machining) ] logger.debug( f"清理加工: uid={uid}, 剩余 {len(self.machinings[uid])} 个对象") # ==================== Dimensions 数据管理 ==================== def get_dimensions(self, uid: str) -> List[Any]: """获取尺寸标注数据""" with self.lock: if uid not in self.dimensions: self.dimensions[uid] = [] return self.dimensions[uid] def add_dimension(self, uid: str, dimension_obj: Any): """添加尺寸标注""" with self.lock: if uid not in self.dimensions: self.dimensions[uid] = [] self.dimensions[uid].append(dimension_obj) logger.debug(f"添加尺寸标注: uid={uid}") def clear_dimensions(self, uid: str): """清空指定uid的所有尺寸标注""" with self.lock: if uid in self.dimensions: del self.dimensions[uid] logger.debug(f"清空尺寸标注: uid={uid}") # ==================== 材质管理 ==================== def get_texture(self, key: str) -> Any: """获取材质 - 对应Ruby的get_texture方法""" if key and key in self.textures: return self.textures[key] return self.textures.get("mat_default") def add_texture(self, key: str, material: Any): """添加材质""" with self.lock: self.textures[key] = material logger.debug(f"添加材质: key={key}") # ==================== 选择管理 ==================== def sel_clear(self): """清除所有选择 - 对应Ruby的sel_clear方法""" with self.lock: self.selected_uid = None self.selected_obj = None self.selected_zone = None self.selected_part = None self.selected_faces.clear() self.selected_parts.clear() self.selected_hws.clear() logger.debug("清除所有选择") def set_selected(self, uid: str, obj: Any, zone: Any = None, part: Any = None): """设置选择状态""" with self.lock: self.selected_uid = uid self.selected_obj = obj if zone: self.selected_zone = zone if part: self.selected_part = part logger.debug(f"设置选择: uid={uid}, obj={obj}") # ==================== 删除实体的核心实现 ==================== def del_entities_by_type(self, entities: Dict[str, Any], typ: str, oid: int) -> int: """按类型删除实体 - 修复版本,确保删除所有相关对象""" if not entities: return 0 deleted_count = 0 entities_to_delete = [] # 【修复1】收集数据结构中需要删除的实体 for key, entity in entities.items(): if entity and self._is_entity_valid(entity): # 对应Ruby逻辑: typ == "uid" || entity.get_attribute("sw", typ) == oid if typ == "uid" or self._get_entity_attribute(entity, typ) == oid: entities_to_delete.append(key) # 【修复2】删除数据结构中的实体 for key in entities_to_delete: entity = entities[key] if self._delete_entity_safe(entity): del entities[key] deleted_count += 1 # 【修复3】遍历Blender中的所有对象,查找并删除相关对象 if BLENDER_AVAILABLE: blender_deleted_count = self._delete_blender_objects_by_type( typ, oid) deleted_count += blender_deleted_count logger.debug( f"从Blender中删除对象: typ={typ}, oid={oid}, 删除数量={blender_deleted_count}") logger.debug(f"按类型删除实体: typ={typ}, oid={oid}, 总删除数量={deleted_count}") return deleted_count def _matches_delete_condition(self, entity, typ: str, oid: int, uid: str = None) -> bool: """检查实体是否匹配删除条件 - 添加详细调试""" try: if not entity or not hasattr(entity, 'get'): return False # 【调试】打印实体的所有属性 entity_uid = entity.get("sw_uid") entity_typ_value = entity.get(f"sw_{typ}") logger.debug( f"🔍 检查删除条件: {entity.name if hasattr(entity, 'name') else 'unknown'}") logger.debug( f" 实体属性: sw_uid={entity_uid}, sw_{typ}={entity_typ_value}") logger.debug(f" 删除条件: uid={uid}, typ={typ}, oid={oid}") # 【修复】正确的删除条件逻辑 if typ == "uid": # 删除整个单元:检查sw_uid uid_matches = entity_uid == oid logger.debug(f" uid删除匹配: {uid_matches}") return uid_matches else: # 删除特定类型:需要同时匹配uid和对应的类型属性 uid_matches = uid is None or entity_uid == uid typ_matches = entity_typ_value == oid logger.debug( f" 类型删除匹配: uid匹配={uid_matches}, {typ}匹配={typ_matches}") # 必须同时匹配uid和类型值 return uid_matches and typ_matches except Exception as e: logger.error(f"检查删除条件时发生错误: {e}") return False def _delete_blender_objects_by_type(self, typ: str, oid: int, uid: str = None) -> int: """从Blender中删除指定类型的对象 - 添加详细调试""" deleted_count = 0 try: logger.info(f" 开始搜索Blender对象: typ={typ}, oid={oid}, uid={uid}") # 遍历所有Blender对象 objects_to_delete = [] checked_objects = [] for obj in bpy.data.objects: checked_objects.append(obj.name) if self._should_delete_blender_object(obj, typ, oid, uid): objects_to_delete.append(obj) logger.info(f"🎯 标记删除: {obj.name}") logger.info( f"📊 检查了 {len(checked_objects)} 个对象,标记删除 {len(objects_to_delete)} 个") # 删除收集到的对象 for obj in objects_to_delete: try: logger.info( f"️ 删除Blender对象: {obj.name}, typ={typ}, oid={oid}, uid={uid}") bpy.data.objects.remove(obj, do_unlink=True) deleted_count += 1 except Exception as e: logger.error(f"删除Blender对象失败: {obj.name}, 错误: {e}") # 清理孤立的网格数据 self._cleanup_orphaned_meshes() except Exception as e: logger.error(f"删除Blender对象时发生错误: {e}") return deleted_count def _should_delete_blender_object(self, obj, typ: str, oid: int, uid: str = None) -> bool: """判断是否应该删除Blender对象 - 添加详细调试""" try: if not obj or not hasattr(obj, 'get'): return False # 【调试】打印对象的所有sw_属性 sw_attrs = {} for key, value in obj.items(): if key.startswith('sw_'): sw_attrs[key] = value logger.debug(f"🔍 检查对象 {obj.name} 的sw_属性: {sw_attrs}") # 使用相同的删除条件逻辑 should_delete = self._matches_delete_condition(obj, typ, oid, uid) if should_delete: logger.info(f"✅ 对象 {obj.name} 匹配删除条件") else: logger.debug(f"❌ 对象 {obj.name} 不匹配删除条件") return should_delete except Exception as e: logger.error(f"检查Blender对象删除条件时发生错误: {e}") return False def _cleanup_orphaned_meshes(self): """清理孤立的网格数据""" try: # 清理没有对象的网格 for mesh in bpy.data.meshes: if mesh.users == 0: bpy.data.meshes.remove(mesh) logger.debug(f"清理孤立网格: {mesh.name}") # 清理没有对象的材质 for material in bpy.data.materials: if material.users == 0: bpy.data.materials.remove(material) logger.debug(f"清理孤立材质: {material.name}") except Exception as e: logger.error(f"清理孤立数据时发生错误: {e}") def _get_entity_attribute(self, entity, attr_name: str): """获取实体属性 - 改进版本""" try: if BLENDER_AVAILABLE and hasattr(entity, 'get'): # 【修复7】检查多种可能的属性名 possible_attrs = [ f"sw_{attr_name}", attr_name, f"sw{attr_name}" ] for attr in possible_attrs: value = entity.get(attr) if value is not None: return value return None except: return None def _delete_entity_safe(self, entity) -> bool: """安全删除实体 - 改进版本""" try: if not entity or not BLENDER_AVAILABLE: return False # 【修复8】更全面的对象检查 if hasattr(entity, 'name'): # 检查是否在Blender对象中 if entity.name in bpy.data.objects: bpy.data.objects.remove(entity, do_unlink=True) return True # 检查是否在网格中 elif entity.name in bpy.data.meshes: bpy.data.meshes.remove(entity, do_unlink=True) return True # 检查是否在材质中 elif entity.name in bpy.data.materials: bpy.data.materials.remove(entity, do_unlink=True) return True return False except Exception as e: logger.error(f"删除实体失败: {e}") return False def _is_entity_valid(self, entity) -> bool: """检查实体是否有效""" try: if entity is None: return False # 检查是否是 Blender 对象 if hasattr(entity, 'name'): # 检查对象是否已被删除 if hasattr(entity, 'is_valid'): return entity.is_valid elif hasattr(entity, 'users'): return entity.users > 0 else: return True # 检查是否是字典或其他类型 return entity is not None except Exception: return False # ==================== 统计和管理方法 ==================== def get_data_stats(self) -> Dict[str, Any]: """获取数据统计信息""" with self.lock: return { "total_units": len(set(list(self.zones.keys()) + list(self.parts.keys()) + list(self.hardwares.keys()))), "zones": { "units": len(self.zones), "total_zones": sum(len(zones) for zones in self.zones.values()) }, "parts": { "units": len(self.parts), "total_parts": sum(len(parts) for parts in self.parts.values()) }, "hardwares": { "units": len(self.hardwares), "total_hardwares": sum(len(hws) for hws in self.hardwares.values()) }, "machinings": { "units": len(self.machinings), "total_machinings": sum(len(machs) for machs in self.machinings.values()) }, "dimensions": { "units": len(self.dimensions), "total_dimensions": sum(len(dims) for dims in self.dimensions.values()) }, "textures": len(self.textures), "selected_objects": { "uid": self.selected_uid, "obj": self.selected_obj, "faces": len(self.selected_faces), "parts": len(self.selected_parts), "hardwares": len(self.selected_hws) }, "system_state": { "part_mode": self.part_mode, "hide_none": self.hide_none, "mat_type": self.mat_type, "back_material": self.back_material, "added_contour": self.added_contour } } def cleanup_all(self): """清理所有数据""" with self.lock: self.zones.clear() self.parts.clear() self.hardwares.clear() self.labels.clear() self.door_labels.clear() self.machinings.clear() self.dimensions.clear() self.textures.clear() self.unit_params.clear() self.unit_trans.clear() self.sel_clear() logger.info("✅ 数据管理器清理完成") # ==================== 全局数据管理器实例 ==================== # 全局数据管理器实例 data_manager: Optional[DataManager] = None def init_data_manager() -> DataManager: """初始化全局数据管理器实例""" global data_manager if data_manager is None: data_manager = DataManager() return data_manager def get_data_manager() -> DataManager: """获取全局数据管理器实例""" global data_manager if data_manager is None: data_manager = init_data_manager() return data_manager # 自动初始化 data_manager = init_data_manager() # ==================== 兼容性函数 ==================== def get_zones(data: Dict[str, Any]) -> Dict[str, Any]: """兼容性函数 - 获取zones""" return data_manager.get_zones(data) def get_parts(data: Dict[str, Any]) -> Dict[str, Any]: """兼容性函数 - 获取parts""" return data_manager.get_parts(data) def get_hardwares(data: Dict[str, Any]) -> Dict[str, Any]: """兼容性函数 - 获取hardwares""" return data_manager.get_hardwares(data) def get_texture(key: str) -> Any: """兼容性函数 - 获取材质""" return data_manager.get_texture(key) def sel_clear(): """兼容性函数 - 清除选择""" return data_manager.sel_clear() # ==================== 兼容性函数 ==================== def get_zones(data: Dict[str, Any]) -> Dict[str, Any]: """兼容性函数 - 获取zones""" return data_manager.get_zones(data) def get_parts(data: Dict[str, Any]) -> Dict[str, Any]: """兼容性函数 - 获取parts""" return data_manager.get_parts(data) def get_hardwares(data: Dict[str, Any]) -> Dict[str, Any]: """兼容性函数 - 获取hardwares""" return data_manager.get_hardwares(data) def get_texture(key: str) -> Any: """兼容性函数 - 获取材质""" return data_manager.get_texture(key) def sel_clear(): """兼容性函数 - 清除选择""" return data_manager.sel_clear()