#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SUW Core - Door Drawer Manager Module 拆分自: suw_impl.py (Line 1809-1834, 6163-6240) 用途: Blender门抽屉管理、变换计算、属性设置 版本: 1.0.0 作者: SUWood Team """ from .geometry_utils import Vector3d, Point3d from .memory_manager import memory_manager from .data_manager import data_manager, get_data_manager import math import logging from typing import Dict, Any, Optional, List, Tuple # 设置日志 logger = logging.getLogger(__name__) # 检查Blender可用性 try: import bpy import mathutils BLENDER_AVAILABLE = True except ImportError: BLENDER_AVAILABLE = False # 导入依赖模块 # ==================== 门抽屉管理器类 ==================== class DoorDrawerManager: """门抽屉管理器 - 负责所有门抽屉相关操作""" def __init__(self): """ 初始化门抽屉管理器 - 完全独立,不依赖suw_impl """ # 使用全局数据管理器 self.data_manager = get_data_manager() if BLENDER_AVAILABLE: try: # 确保 DOOR_LAYER 集合存在 if hasattr(bpy.data, 'collections'): self.door_layer = bpy.data.collections.get("DOOR_LAYER") if not self.door_layer: self.door_layer = bpy.data.collections.new("DOOR_LAYER") if hasattr(bpy.context, 'scene') and bpy.context.scene: bpy.context.scene.collection.children.link(self.door_layer) # 确保 DRAWER_LAYER 集合存在 self.drawer_layer = bpy.data.collections.get("DRAWER_LAYER") if not self.drawer_layer: self.drawer_layer = bpy.data.collections.new("DRAWER_LAYER") if hasattr(bpy.context, 'scene') and bpy.context.scene: bpy.context.scene.collection.children.link(self.drawer_layer) else: logger.warning("⚠️ bpy.data.collections 不可用,跳过集合创建") self.door_layer = None self.drawer_layer = None except Exception as e: logger.warning(f"⚠️ 创建门抽屉集合失败: {e}") self.door_layer = None self.drawer_layer = None else: self.door_layer = None self.drawer_layer = None logger.info("DoorDrawerManager 初始化完成") # ==================== 门抽屉属性设置 ==================== def set_drawer_properties(self, part, data): """设置抽屉属性""" try: 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) except Exception as e: logger.error(f"设置抽屉属性失败: {e}") def set_door_properties(self, part, data): """设置门属性""" try: 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") except Exception as e: logger.error(f"设置门属性失败: {e}") # ==================== 门变换计算 ==================== def calculate_swing_door_transform(self, door_ps, door_pe, door_off): """计算平开门变换 - 修复版本,正确处理单位转换""" try: if not BLENDER_AVAILABLE: return None # 【修复】确保坐标使用正确的单位(米) # 如果输入是毫米,需要转换为米 def convert_to_meters(coord): if isinstance(coord, (list, tuple)): # 检查是否需要转换(如果数值大于100,可能是毫米) return tuple(x * 0.001 if abs(x) > 100 else x for x in coord) return coord # 转换坐标到米 door_ps = convert_to_meters(door_ps) door_pe = convert_to_meters(door_pe) door_off = convert_to_meters(door_off) logger.debug(f"🔧 转换后的坐标(米):") logger.debug(f" door_ps: {door_ps}") logger.debug(f" door_pe: {door_pe}") logger.debug(f" door_off: {door_off}") # 计算旋转轴(从起点到终点的向量) axis = ( door_pe[0] - door_ps[0], door_pe[1] - door_ps[1], door_pe[2] - door_ps[2] ) # 归一化旋转轴 axis_length = math.sqrt(axis[0]**2 + axis[1]**2 + axis[2]**2) if axis_length > 0: axis = (axis[0]/axis_length, axis[1] / axis_length, axis[2]/axis_length) else: logger.error("旋转轴长度为零") return None # 创建旋转矩阵(以door_ps为中心,绕axis轴旋转90度) angle = 1.5708 # 90度 = π/2 # 【修复】简化变换计算,避免复杂的矩阵组合 # 直接创建以door_ps为中心的旋转矩阵 rot_matrix = mathutils.Matrix.Rotation(angle, 4, axis) # 创建平移矩阵 trans_matrix = mathutils.Matrix.Translation(door_off) # 组合变换:先旋转,再平移 final_transform = trans_matrix @ rot_matrix logger.debug(f"🔧 平开门变换计算:") logger.debug(f" 旋转中心: {door_ps}") logger.debug(f" 旋转轴: {axis}") logger.debug(f" 旋转角度: {angle} 弧度") logger.debug(f" 偏移: {door_off}") return final_transform except Exception as e: logger.error(f"计算平开门变换失败: {e}") return None def calculate_slide_door_transform(self, door_off): """计算推拉门变换""" try: if BLENDER_AVAILABLE: 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: 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 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 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 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 apply_transformation(self, obj, transform): """应用变换到对象""" try: if not BLENDER_AVAILABLE or not obj: return False # 检查对象是否有效 if not self._is_object_valid(obj): return False # 应用变换到对象的矩阵 if hasattr(obj, 'matrix_world'): # 将变换矩阵应用到当前矩阵 obj.matrix_world = transform @ obj.matrix_world logger.debug(f"✅ 变换应用到 {obj.name}: {transform}") return True else: logger.warning(f"对象 {obj} 没有 matrix_world 属性") return False except Exception as e: logger.error(f"应用变换失败: {e}") return False def transform_vector(self, vector, transform): """变换向量""" try: if not BLENDER_AVAILABLE or not transform: return vector if isinstance(vector, (list, tuple)) and len(vector) >= 3: vec = mathutils.Vector(vector) transformed = transform @ vec return (transformed.x, transformed.y, transformed.z) return vector except Exception as e: logger.error(f"变换向量失败: {e}") return vector def transform_point(self, point, transform): """变换点""" try: if not BLENDER_AVAILABLE or not transform: return point if hasattr(point, 'x'): vec = mathutils.Vector((point.x, point.y, point.z)) elif isinstance(point, (list, tuple)) and len(point) >= 3: vec = mathutils.Vector(point) else: return point transformed = transform @ vec return Point3d(transformed.x, transformed.y, transformed.z) except Exception as e: logger.error(f"变换点失败: {e}") return point # ==================== 管理器统计 ==================== def get_door_drawer_stats(self) -> Dict[str, Any]: """获取门抽屉管理器统计信息""" try: stats = { "manager_type": "DoorDrawerManager", "door_layer_objects": 0, "has_door_layer": self.door_layer is not None, "blender_available": BLENDER_AVAILABLE } if self.door_layer and BLENDER_AVAILABLE: stats["door_layer_objects"] = len(self.door_layer.objects) return stats except Exception as e: logger.error(f"获取门抽屉统计失败: {e}") return {"error": str(e)} def c10(self, data: Dict[str, Any]): """set_doorinfo - 设置门的方向、起点、终点、偏移等属性""" try: parts = self.data_manager.get_parts(data) doors = data.get("drs", []) for door in doors: root = door.get("cp", 0) door_dir = door.get("dov", "") ps = door.get("ps") pe = door.get("pe") offset = door.get("off") # 解析点和向量字符串 if isinstance(ps, str): ps = self._parse_point3d(ps) if isinstance(pe, str): pe = self._parse_point3d(pe) if isinstance(offset, str): offset = self._parse_vector3d(offset) if root > 0 and root in parts: part = parts[root] part["sw_door_dir"] = door_dir part["sw_door_ps"] = ps part["sw_door_pe"] = pe part["sw_door_offset"] = offset logger.info("✅ 门信息已设置") return True except Exception as e: logger.error(f"设置门信息失败: {e}") return False def c18(self, data: Dict[str, Any]): """hide_door - 隐藏门板""" try: visible = not data.get("v", False) logger.info(f" 设置门板可见性: {visible}") if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过门板隐藏操作") return True # 查找所有标记为门板图层的部件和板材 hidden_count = 0 for obj in bpy.data.objects: try: # 检查是否是门板部件 (layer=1) if (obj.get("sw_layer") == 1 and obj.get("sw_typ") == "part"): obj.hide_viewport = not visible hidden_count += 1 logger.debug(f"🚪 设置门板部件可见性: {obj.name} -> {visible}") # 检查是否是门板板材 (父对象是门板部件) elif (obj.get("sw_typ") == "board" and obj.parent and obj.parent.get("sw_layer") == 1): obj.hide_viewport = not visible hidden_count += 1 logger.debug(f"🚪 设置门板板材可见性: {obj.name} -> {visible}") except Exception as e: logger.debug(f"处理对象 {obj.name} 时出错: {e}") continue logger.info(f"✅ 门板隐藏操作完成: 处理了 {hidden_count} 个对象,可见性={visible}") # 如果有门标签,也设置其可见性 if (hasattr(self.data_manager, 'door_labels') and self.data_manager.door_labels): if BLENDER_AVAILABLE: self.data_manager.door_labels.hide_viewport = not visible logger.info(f"✅ 门标签可见性已设置: {visible}") return True except Exception as e: logger.error(f"隐藏门板失败: {e}") return False def c28(self, data: Dict[str, Any]): """hide_drawer - 隐藏抽屉""" try: visible = not data.get("v", False) logger.info(f" 设置抽屉可见性: {visible}") if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过抽屉隐藏操作") return True # 查找所有标记为抽屉图层的部件和板材 hidden_count = 0 for obj in bpy.data.objects: try: # 检查是否是抽屉部件 (layer=2) if (obj.get("sw_layer") == 2 and obj.get("sw_typ") == "part"): obj.hide_viewport = not visible hidden_count += 1 logger.debug(f"📦 设置抽屉部件可见性: {obj.name} -> {visible}") # 检查是否是抽屉板材 (父对象是抽屉部件) elif (obj.get("sw_typ") == "board" and obj.parent and obj.parent.get("sw_layer") == 2): obj.hide_viewport = not visible hidden_count += 1 logger.debug(f"📦 设置抽屉板材可见性: {obj.name} -> {visible}") except Exception as e: logger.debug(f"处理对象 {obj.name} 时出错: {e}") continue logger.info(f"✅ 抽屉隐藏操作完成: 处理了 {hidden_count} 个对象,可见性={visible}") # 如果有门标签,也设置其可见性(参考Ruby版本) if (hasattr(self.data_manager, 'door_labels') and self.data_manager.door_labels): if BLENDER_AVAILABLE: self.data_manager.door_labels.hide_viewport = not visible logger.info(f"✅ 门标签可见性已设置: {visible}") return True except Exception as e: logger.error(f"隐藏抽屉失败: {e}") return False def c1a(self, data: Dict[str, Any]): """open_doors - 打开门板""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过门板打开操作") return True uid = data.get("uid") mydoor = data.get("cp", 0) value = data.get("v", False) logger.info(f" 执行门板打开操作: uid={uid}, cp={mydoor}, v={value}") # 【修复】在开始处理之前清理无效的对象引用 self._cleanup_invalid_references(data) # 获取部件和硬件数据 parts = self.data_manager.get_parts(data) hardwares = self.data_manager.get_hardwares(data) processed_count = 0 for root, part in parts.items(): # 【修复】检查对象是否仍然有效,避免访问已删除的对象 if not self._is_object_valid(part): logger.warning(f"⚠️ 部件对象已无效,跳过处理: {root}") continue # 检查是否匹配指定的门板 if mydoor != 0 and mydoor != root: continue # 检查门板类型 try: door_type = part.get("sw_door", 0) except Exception as e: logger.warning(f"⚠️ 无法获取门板类型,跳过处理: {root}, 错误: {e}") continue if door_type <= 0: continue # 检查当前开门状态 try: is_open = part.get("sw_door_open", False) except Exception as e: logger.warning(f"⚠️ 无法获取开门状态,跳过处理: {root}, 错误: {e}") continue if is_open == value: continue # 只处理平开门(10)和推拉门(15) if door_type not in [10, 15]: continue logger.info( f"🔧 处理门板 {root}: door_type={door_type}, is_open={is_open}, target={value}") # 获取门板变换信息 try: door_ps = part.get("sw_door_ps") door_pe = part.get("sw_door_pe") door_off = part.get("sw_door_offset") except Exception as e: logger.warning(f"⚠️ 无法获取门板变换信息,跳过处理: {root}, 错误: {e}") continue logger.debug(f" 门板变换信息(原始):") logger.debug(f" door_ps: {door_ps}") logger.debug(f" door_pe: {door_pe}") logger.debug(f" door_off: {door_off}") # 【修复】检查门板当前位置 try: if hasattr(part, 'location'): logger.debug(f" 门板当前位置: {part.location}") except Exception as e: logger.warning(f"⚠️ 无法获取门板位置,跳过处理: {root}, 错误: {e}") continue if door_type == 10: # 平开门 if not all([door_ps, door_pe, door_off]): logger.warning(f"门板 {root} 缺少变换信息,跳过") continue # 【修复】按照Ruby版本的正确逻辑 try: # 【修复】坐标已经在c10中被转换过了,这里直接使用 # 检查坐标是否已经是元组格式(已转换) if isinstance(door_ps, tuple): # 已经是转换后的坐标,直接使用 door_ps_coords = door_ps logger.debug( f" 使用已转换的door_ps坐标: {door_ps_coords}") else: # 需要转换 door_ps_coords = self._parse_point3d(door_ps) logger.debug( f" 转换door_ps坐标: {door_ps} -> {door_ps_coords}") if isinstance(door_pe, tuple): # 已经是转换后的坐标,直接使用 door_pe_coords = door_pe logger.debug( f" 使用已转换的door_pe坐标: {door_pe_coords}") else: # 需要转换 door_pe_coords = self._parse_point3d(door_pe) logger.debug( f" 转换door_pe坐标: {door_pe} -> {door_pe_coords}") if isinstance(door_off, tuple): # 已经是转换后的坐标,直接使用 door_off_coords = door_off logger.debug( f" 使用已转换的door_off坐标: {door_off_coords}") else: # 需要转换 door_off_coords = self._parse_vector3d(door_off) logger.debug( f" 转换door_off坐标: {door_off} -> {door_off_coords}") # 【新增】检查坐标值是否过小,如果是,说明被转换了两次 def check_and_fix_coordinates(coords, name): """检查并修复过小的坐标值""" if any(abs(coord) < 0.001 for coord in coords): # 坐标值过小,可能是被转换了两次,需要放大1000倍 fixed_coords = ( coords[0] * 1000, coords[1] * 1000, coords[2] * 1000) logger.info( f"🔄 修复过小的{name}坐标: {coords} -> {fixed_coords}") return fixed_coords return coords # 检查并修复所有坐标 door_ps_coords = check_and_fix_coordinates( door_ps_coords, "door_ps") door_pe_coords = check_and_fix_coordinates( door_pe_coords, "door_pe") door_off_coords = check_and_fix_coordinates( door_off_coords, "door_off") logger.debug(f" 门板变换信息(修复后,米):") logger.debug(f" door_ps: {door_ps_coords}") logger.debug(f" door_pe: {door_pe_coords}") logger.debug(f" door_off: {door_off_coords}") # 应用单元变换 if hasattr(self.data_manager, 'unit_trans') and uid in self.data_manager.unit_trans: unit_trans = self.data_manager.unit_trans[uid] door_ps_coords = self.transform_point( door_ps_coords, unit_trans) door_pe_coords = self.transform_point( door_pe_coords, unit_trans) door_off_coords = self.transform_vector( door_off_coords, unit_trans) logger.debug(f" 应用单元变换后:") logger.debug(f" door_ps: {door_ps_coords}") logger.debug(f" door_pe: {door_pe_coords}") logger.debug(f" door_off: {door_off_coords}") # 【修复】按照Ruby版本:以door_ps为旋转中心,以(door_pe-door_ps)为旋转轴 # 计算旋转轴(从起点到终点的向量) rotation_axis = ( door_pe_coords[0] - door_ps_coords[0], door_pe_coords[1] - door_ps_coords[1], door_pe_coords[2] - door_ps_coords[2] ) # 归一化旋转轴 axis_length = math.sqrt( rotation_axis[0]**2 + rotation_axis[1]**2 + rotation_axis[2]**2) if axis_length > 0: rotation_axis = ( rotation_axis[0] / axis_length, rotation_axis[1] / axis_length, rotation_axis[2] / axis_length ) else: logger.error(f"旋转轴长度为零: {rotation_axis}") continue # 【修复】简化变换逻辑,直接按照Ruby版本的方式 angle = 1.5708 # 90度 = π/2 # 创建以door_ps为中心的旋转变换 rotation_transform = self._create_rotation_around_point( door_ps_coords, rotation_axis, angle) # 创建平移变换 translation_transform = mathutils.Matrix.Translation( door_off_coords) # 组合变换:先旋转,再平移 final_transform = translation_transform @ rotation_transform # 如果门板已经打开,需要反转变换来关闭 if is_open: final_transform = final_transform.inverted() logger.info(f" 门板已打开,反转变换来关闭") # 【调试】添加详细的变换信息 logger.info(f"🔧 门板 {root} 变换详情:") logger.info( f" 原始坐标: door_ps={door_ps_coords}, door_pe={door_pe_coords}, door_off={door_off_coords}") logger.info(f" 旋转中心: {door_ps_coords}") logger.info(f" 旋转轴: {rotation_axis}") logger.info(f" 旋转角度: {angle} 弧度") logger.info(f" 平移向量: {door_off_coords}") logger.info( f" 门板状态: {'已打开,需要关闭' if is_open else '已关闭,需要打开'}") # 检查门板当前位置 try: if hasattr(part, 'location'): logger.info(f" 门板当前位置: {part.location}") except Exception as e: logger.warning( f"⚠️ 无法获取门板位置,跳过处理: {root}, 错误: {e}") continue # 应用变换到门板 if self.apply_transformation(part, final_transform): # 检查变换后的位置 try: if hasattr(part, 'location'): logger.info(f" 变换后位置: {part.location}") except Exception as e: logger.warning(f"⚠️ 无法获取变换后位置: {e}") # 应用变换到关联的硬件 hw_count = 0 for hw_key, hardware in hardwares.items(): if hardware.get("sw_part") == root: if self.apply_transformation(hardware, final_transform): hw_count += 1 # 更新开门状态 part["sw_door_open"] = not is_open processed_count += 1 logger.info( f"✅ 平开门 {root} 变换完成,同时变换了 {hw_count} 个硬件") else: logger.error(f"❌ 门板 {root} 变换应用失败") except Exception as e: logger.error(f"❌ 门板 {root} 变换计算失败: {e}") continue else: # 推拉门 (door_type == 15) if not door_off: logger.warning(f"推拉门 {root} 缺少偏移信息,跳过") continue # 【修复】简化推拉门变换 try: # 【修复】单位转换:毫米转米 door_off = self._parse_vector3d(door_off) # 应用单元变换 if hasattr(self.data_manager, 'unit_trans') and uid in self.data_manager.unit_trans: unit_trans = self.data_manager.unit_trans[uid] door_off = self.transform_vector( door_off, unit_trans) # 创建平移变换 if is_open: # 如果门是开的,需要关闭(反向平移) door_off = (-door_off[0], - door_off[1], -door_off[2]) trans_matrix = mathutils.Matrix.Translation(door_off) # 应用变换到门板 if self.apply_transformation(part, trans_matrix): # 应用变换到关联的硬件 hw_count = 0 for hw_key, hardware in hardwares.items(): if hardware.get("sw_part") == root: if self.apply_transformation(hardware, trans_matrix): hw_count += 1 # 更新开门状态 part["sw_door_open"] = not is_open processed_count += 1 logger.info( f"✅ 推拉门 {root} 变换完成,同时变换了 {hw_count} 个硬件") else: logger.error(f"❌ 推拉门 {root} 变换应用失败") except Exception as e: logger.error(f"❌ 推拉门 {root} 变换计算失败: {e}") continue logger.info(f"🎉 门板打开操作完成: 处理了 {processed_count} 个门板") return True except Exception as e: logger.error(f"打开门板失败: {e}") import traceback logger.error(traceback.format_exc()) return False def c1b(self, data: Dict[str, Any]): """slide_drawers - 打开抽屉""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过抽屉打开操作") return True uid = data.get("uid") value = data.get("v", False) logger.info(f" 执行抽屉打开操作: uid={uid}, v={value}") # 获取部件和硬件数据 parts = self.data_manager.get_parts(data) hardwares = self.data_manager.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] = drawer_dir 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, dir_vector in drawers.items(): # 解析抽屉方向向量 if isinstance(dir_vector, str): dir_vector = self._parse_vector3d(dir_vector) elif hasattr(dir_vector, '__iter__'): dir_vector = tuple(dir_vector) # 获取抽屉深度 dr_depth = depths.get(drawer, 300) # 【修复】单位转换:毫米转米 dr_depth_m = dr_depth * 0.001 # mm -> m # 计算偏移向量(深度 * 0.9) offset_length = dr_depth_m * 0.9 logger.debug( f"🔧 抽屉 {drawer} 深度转换: {dr_depth} mm -> {dr_depth_m} m, 偏移长度: {offset_length} m") # 归一化方向向量并设置长度 if dir_vector: # 计算向量长度 length = math.sqrt( dir_vector[0]**2 + dir_vector[1]**2 + dir_vector[2]**2) if length > 0: # 归一化并设置新长度 normalized_dir = ( dir_vector[0] / length, dir_vector[1] / length, dir_vector[2] / length ) offset_vector = ( normalized_dir[0] * offset_length, normalized_dir[1] * offset_length, normalized_dir[2] * offset_length ) # 应用单元变换 if hasattr(self.data_manager, 'unit_trans') and uid in self.data_manager.unit_trans: unit_trans = self.data_manager.unit_trans[uid] offset_vector = self.transform_vector( offset_vector, unit_trans) offsets[drawer] = offset_vector # 处理每个抽屉 processed_count = 0 for drawer, offset_vector in offsets.items(): # 检查抽屉当前状态 is_open = False # 默认关闭状态 # 查找抽屉相关的部件,检查是否有打开状态标记 for root, part in parts.items(): if part.get("sw_pid") == drawer: is_open = part.get("sw_drawer_open", False) break # 如果状态已经是目标状态,跳过 if is_open == value: continue # 创建变换矩阵 if is_open: # 如果抽屉已经打开,需要关闭(反向变换) offset_vector = (-offset_vector[0], - offset_vector[1], -offset_vector[2]) trans_matrix = mathutils.Matrix.Translation(offset_vector) # 应用变换到相关部件 part_count = 0 for root, part in parts.items(): if part.get("sw_pid") == drawer: if self.apply_transformation(part, trans_matrix): part["sw_drawer_open"] = not is_open part_count += 1 # 应用变换到相关硬件 hw_count = 0 for hw_key, hardware in hardwares.items(): if hardware.get("sw_pid") == drawer: if self.apply_transformation(hardware, trans_matrix): hw_count += 1 processed_count += 1 logger.info( f"✅ 抽屉 {drawer} 变换完成,处理了 {part_count} 个部件和 {hw_count} 个硬件") logger.info(f"🎉 抽屉打开操作完成: 处理了 {processed_count} 个抽屉") return True except Exception as e: logger.error(f"打开抽屉失败: {e}") import traceback logger.error(traceback.format_exc()) return False def _parse_point3d(self, point_data): """解析3D点数据为元组 - 修复版本,统一处理毫米到米的转换""" try: coords = [] # 处理不同的数据类型 if isinstance(point_data, str): # 处理字符串格式 point_str = point_data.strip("()") coords = [float(x.strip()) for x in point_str.split(",")] elif hasattr(point_data, '__iter__') and not isinstance(point_data, str): # 处理IDPropertyArray、list、tuple等可迭代对象 try: # 尝试转换为list并获取前3个元素 data_list = list(point_data) if len(data_list) >= 3: coords = [float(data_list[0]), float( data_list[1]), float(data_list[2])] else: logger.warning(f"坐标数据长度不足: {point_data}") return (0, 0, 0) except Exception as e: logger.warning(f"转换坐标数据失败: {e}, 数据: {point_data}") return (0, 0, 0) elif hasattr(point_data, '__len__') and hasattr(point_data, '__getitem__'): # 处理类似数组的对象(如IDPropertyArray) try: if len(point_data) >= 3: coords = [float(point_data[0]), float( point_data[1]), float(point_data[2])] else: logger.warning(f"坐标数据长度不足: {point_data}") return (0, 0, 0) except Exception as e: logger.warning(f"数组式访问失败: {e}, 数据: {point_data}") return (0, 0, 0) else: logger.warning(f"不支持的坐标数据类型: {type(point_data)}") return (0, 0, 0) # 【修复】统一单位转换:假设所有输入都是毫米,转换为米 # 参考Ruby版本:Point3d.new(xyz[0].mm, xyz[1].mm, xyz[2].mm) x = coords[0] * 0.001 # mm -> m y = coords[1] * 0.001 # mm -> m z = coords[2] * 0.001 # mm -> m logger.debug(f"🔧 坐标转换: {coords} mm -> ({x}, {y}, {z}) m") return (x, y, z) except Exception as e: logger.error(f"解析3D点失败: {e}") return (0, 0, 0) def _parse_vector3d(self, vector_str): """解析3D向量字符串为元组 - 修复单位转换(mm转m)""" return self._parse_point3d(vector_str) def _cleanup_invalid_references(self, data: Dict[str, Any]): """清理无效的对象引用""" try: uid = data.get("uid") if not uid: return logger.info(f"🔄 开始清理无效的对象引用: uid={uid}") # 清理parts中的无效引用 parts = self.data_manager.get_parts(data) invalid_parts = [] for root, part in parts.items(): if not self._is_object_valid(part): invalid_parts.append(root) logger.debug(f"发现无效的部件引用: {root}") for root in invalid_parts: del parts[root] logger.info(f"✅ 清理无效的部件引用: {root}") # 清理hardwares中的无效引用 hardwares = self.data_manager.get_hardwares(data) invalid_hardwares = [] for hw_id, hw in hardwares.items(): if not self._is_object_valid(hw): invalid_hardwares.append(hw_id) logger.debug(f"发现无效的硬件引用: {hw_id}") for hw_id in invalid_hardwares: del hardwares[hw_id] logger.info(f"✅ 清理无效的硬件引用: {hw_id}") # 清理zones中的无效引用 zones = self.data_manager.get_zones(data) invalid_zones = [] for zid, zone in zones.items(): if not self._is_object_valid(zone): invalid_zones.append(zid) logger.debug(f"发现无效的区域引用: {zid}") for zid in invalid_zones: del zones[zid] logger.info(f"✅ 清理无效的区域引用: {zid}") logger.info(f"✅ 无效引用清理完成: uid={uid}") except Exception as e: logger.error(f"清理无效引用失败: {e}") def _is_object_valid(self, obj): """检查对象是否有效(存在且不是空对象)- 改进版本,检测已删除的对象""" try: if obj is None: return False if not BLENDER_AVAILABLE: return True # 检查对象是否有基本属性 if not hasattr(obj, 'name'): return False # 检查对象是否在Blender数据中 if obj.name not in bpy.data.objects: return False # 【新增】检查对象是否已被标记为删除 try: # 尝试访问一个简单的属性来检查对象是否仍然有效 _ = obj.name _ = obj.type return True except Exception as e: # 如果出现"StructRNA has been removed"错误,说明对象已被删除 if "StructRNA" in str(e) and "removed" in str(e): logger.debug( f"对象已被删除: {obj.name if hasattr(obj, 'name') else 'unknown'}") return False else: # 其他错误,也认为对象无效 logger.debug(f"对象访问失败: {e}") return False except Exception as e: logger.debug(f"检查对象有效性时发生错误: {e}") return False def _create_rotation_around_point(self, center_point, axis, angle): """创建绕指定点的旋转变换""" try: # 移动到中心点 move_to_center = mathutils.Matrix.Translation(center_point) # 从中心点移回原点 move_from_center = mathutils.Matrix.Translation( (-center_point[0], -center_point[1], -center_point[2])) # 在原点旋转 rotation = mathutils.Matrix.Rotation(angle, 4, axis) # 组合变换:移动到中心 -> 旋转 -> 移回原位置 return move_to_center @ rotation @ move_from_center except Exception as e: logger.error(f"创建绕点旋转变换失败: {e}") return mathutils.Matrix.Identity(4) # ==================== 模块实例 ==================== # 全局实例,将由SUWImpl初始化时设置 door_drawer_manager = None def init_door_drawer_manager(): """初始化门抽屉管理器 - 不再需要suw_impl参数""" global door_drawer_manager door_drawer_manager = DoorDrawerManager() return door_drawer_manager def get_door_drawer_manager(): """获取门抽屉管理器实例""" global door_drawer_manager if door_drawer_manager is None: door_drawer_manager = init_door_drawer_manager() return door_drawer_manager