1070 lines
44 KiB
Python
1070 lines
44 KiB
Python
|
#!/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
|