blenderpython/suw_core/door_drawer_manager.py

1070 lines
44 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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