blenderpython/suw_core/door_drawer_manager.py

1070 lines
44 KiB
Python
Raw Normal View History

2025-08-01 17:13:30 +08:00
#!/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