1225 lines
48 KiB
Python
1225 lines
48 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
SUW Core - Deletion Manager Module
|
||
拆分自: suw_impl.py (Line 4274-4800, 6970-7100)
|
||
用途: Blender删除管理、对象清理、数据结构维护
|
||
版本: 1.0.0
|
||
作者: SUWood Team
|
||
"""
|
||
|
||
from .memory_manager import memory_manager, execute_in_main_thread
|
||
from .data_manager import data_manager, get_data_manager
|
||
import time
|
||
import logging
|
||
import threading
|
||
import sys
|
||
from typing import Dict, Any, List, Optional
|
||
|
||
# 配置日志系统
|
||
|
||
|
||
def setup_logging():
|
||
"""配置日志系统,确保在Blender控制台中能看到输出"""
|
||
try:
|
||
# 获取根日志记录器
|
||
root_logger = logging.getLogger()
|
||
|
||
# 如果已经有处理器,不重复配置
|
||
if root_logger.handlers:
|
||
return
|
||
|
||
# 设置日志级别
|
||
root_logger.setLevel(logging.INFO)
|
||
|
||
# 创建控制台处理器
|
||
console_handler = logging.StreamHandler(sys.stdout)
|
||
console_handler.setLevel(logging.INFO)
|
||
|
||
# 创建格式化器
|
||
formatter = logging.Formatter(
|
||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||
datefmt='%H:%M:%S'
|
||
)
|
||
console_handler.setFormatter(formatter)
|
||
|
||
# 添加处理器到根日志记录器
|
||
root_logger.addHandler(console_handler)
|
||
|
||
# 特别配置SUW相关的日志记录器
|
||
suw_logger = logging.getLogger('suw_core')
|
||
suw_logger.setLevel(logging.INFO)
|
||
|
||
print("✅ 日志系统配置完成")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 日志系统配置失败: {e}")
|
||
|
||
|
||
# 在模块加载时自动配置日志
|
||
setup_logging()
|
||
|
||
# 设置日志
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 检查Blender可用性
|
||
try:
|
||
import bpy
|
||
BLENDER_AVAILABLE = True
|
||
except ImportError:
|
||
BLENDER_AVAILABLE = False
|
||
|
||
# ==================== 删除管理器类 ====================
|
||
|
||
|
||
class DeletionManager:
|
||
"""删除管理器 - 负责所有删除相关操作"""
|
||
|
||
def __init__(self):
|
||
"""
|
||
初始化删除管理器 - 完全独立,不依赖suw_impl
|
||
"""
|
||
# 使用全局数据管理器
|
||
self.data_manager = get_data_manager()
|
||
|
||
# 【修复】删除统计 - 添加缺失的键
|
||
self.deletion_stats = {
|
||
"units_deleted": 0,
|
||
"zones_deleted": 0,
|
||
"parts_deleted": 0,
|
||
"hardwares_deleted": 0,
|
||
"objects_deleted": 0,
|
||
"deletion_errors": 0,
|
||
"total_deletions": 0, # 添加这个键
|
||
"successful_deletions": 0, # 添加这个键
|
||
"failed_deletions": 0 # 添加这个键
|
||
}
|
||
|
||
logger.info("✅ 删除管理器初始化完成")
|
||
|
||
# ==================== 原始命令方法 ====================
|
||
|
||
def c09(self, data: Dict[str, Any]):
|
||
"""c09 - 删除实体 - 与Ruby版本保持一致的逻辑"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.warning("Blender 不可用,跳过删除操作")
|
||
return
|
||
|
||
logger.info("️ 执行c09命令: 删除实体")
|
||
|
||
uid = data.get("uid")
|
||
typ = data.get("typ")
|
||
oid = data.get("oid")
|
||
|
||
if not uid or not typ or oid is None:
|
||
logger.error("缺少必要参数: uid, typ, oid")
|
||
return
|
||
|
||
logger.info(f"🗑️ 删除参数: uid={uid}, typ={typ}, oid={oid}")
|
||
|
||
# 【修复】与Ruby版本保持一致:先清除所有选择
|
||
self._clear_selection()
|
||
|
||
def delete_entities():
|
||
"""安全地删除实体,修复了重复删除导致崩溃的问题"""
|
||
try:
|
||
self.data_manager.data = data
|
||
deleted_count = 0
|
||
|
||
if typ == "wall":
|
||
logger.info(f"🗑️ 删除墙体: uid={uid}, wall={oid}")
|
||
result = self._del_wall_entity_safe(data, uid, oid)
|
||
deleted_count = result if result is not None else 0
|
||
|
||
elif typ == "zid":
|
||
logger.info(f"🗑️ 删除区域及其内容: uid={uid}, zid={oid}")
|
||
result = self._del_zone_complete(uid, oid)
|
||
deleted_count += result if result is not None else 0
|
||
|
||
# 清理数据结构 - 不再重新删除Blender对象
|
||
logger.info(f"🧹 清理区域关联的Parts数据...")
|
||
parts = self.data_manager.get_parts(data)
|
||
parts_to_remove = [
|
||
cp for cp, part in parts.items()
|
||
if self._is_object_valid(part) and part.get("sw_zid") == oid
|
||
]
|
||
for cp in parts_to_remove:
|
||
self.data_manager.remove_part(uid, cp)
|
||
|
||
logger.info(f"🧹 清理区域关联的Hardwares数据...")
|
||
hardwares = self.data_manager.get_hardwares(data)
|
||
hardwares_to_remove = [
|
||
hw_id for hw_id, hw in hardwares.items()
|
||
if self._is_object_valid(hw) and hw.get("sw_zid") == oid
|
||
]
|
||
for hw_id in hardwares_to_remove:
|
||
self.data_manager.remove_hardware(uid, hw_id)
|
||
|
||
elif typ == "cp":
|
||
logger.info(f"🗑️ 删除部件: uid={uid}, cp={oid}")
|
||
result = self._del_part_complete(uid, oid)
|
||
deleted_count += result if result is not None else 0
|
||
|
||
else: # typ == "uid" 或其他过滤条件
|
||
logger.info(f"🗑️ 按类型 '{typ}' 和 oid '{oid}' 删除实体")
|
||
zones = self.data_manager.get_zones(data)
|
||
deleted_count += self._del_entities_by_type(
|
||
zones, typ, oid, uid)
|
||
|
||
parts = self.data_manager.get_parts(data)
|
||
deleted_count += self._del_entities_by_type(
|
||
parts, typ, oid, uid)
|
||
|
||
hardwares = self.data_manager.get_hardwares(data)
|
||
deleted_count += self._del_entities_by_type(
|
||
hardwares, typ, oid, uid)
|
||
|
||
# 容器级别删除后的通用清理
|
||
if typ in ["uid", "zid"]:
|
||
self._clear_labels_safe(uid)
|
||
self._clear_door_labels_safe(uid)
|
||
logger.info(f"✅ 清理labels完成")
|
||
self._cleanup_machinings(uid)
|
||
logger.info(f"✅ 清理machinings完成")
|
||
|
||
# 顶级(unit)删除后的特殊清理
|
||
if typ == "uid":
|
||
# 重置材质类型为正常
|
||
if hasattr(self.data_manager, 'mat_type'):
|
||
self.data_manager.mat_type = 0 # MAT_TYPE_NORMAL
|
||
# 删除尺寸标注
|
||
self._del_dimensions(data)
|
||
logger.info(f"✅ 重置材质类型并删除尺寸标注")
|
||
|
||
# 清理临时存储的data
|
||
self.data_manager.data = None
|
||
|
||
logger.info(f"✅ c09删除完成: 共处理约 {deleted_count} 个对象")
|
||
return deleted_count
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ c09删除失败: {e}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
return 0
|
||
|
||
# 执行删除
|
||
deleted_count = delete_entities()
|
||
|
||
# 【修复】确保deleted_count是数字
|
||
if deleted_count is None:
|
||
deleted_count = 0
|
||
|
||
# 当未找到匹配对象时,跳过清理数据和删除对象操作
|
||
if deleted_count == 0:
|
||
logger.info(
|
||
f"ℹ️ 未找到匹配的对象,跳过清理操作: uid={uid}, typ={typ}, oid={oid}")
|
||
# 即使没有删除任何对象,也认为是成功的(对象本来就不存在)
|
||
self.deletion_stats["total_deletions"] += 1
|
||
self.deletion_stats["successful_deletions"] += 1
|
||
logger.info(f"✅ 命令 c09 执行成功,未找到匹配对象")
|
||
return True # 修复:返回True
|
||
|
||
# 更新统计
|
||
self.deletion_stats["total_deletions"] += 1
|
||
if deleted_count > 0:
|
||
self.deletion_stats["successful_deletions"] += 1
|
||
else:
|
||
self.deletion_stats["failed_deletions"] += 1
|
||
|
||
logger.info(f"✅ 命令 c09 执行成功,删除 {deleted_count} 个对象")
|
||
return True # 修复:返回True
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ c09命令异常: {e}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
|
||
def c03(self, data: Dict[str, Any]):
|
||
"""add_zone - 添加区域 - 修复版本:直接创建六个面组成Zone"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.warning("Blender 不可用,跳过区域创建")
|
||
return None
|
||
|
||
logger.info("️ 执行c03命令: 添加区域")
|
||
|
||
uid = data.get("uid")
|
||
zid = data.get("zid")
|
||
elements = data.get("children", [])
|
||
|
||
logger.info(f" Zone_{uid} 数据: uid={uid}, 元素数量={len(elements)}")
|
||
|
||
# 【修复】不再创建线框立方体,直接创建六个面组成Zone
|
||
# 创建一个空的组对象作为Zone容器
|
||
group = bpy.data.objects.new(f"Zone_{uid}", None)
|
||
group.empty_display_type = 'PLAIN_AXES' # 改为PLAIN_AXES,不显示线框
|
||
bpy.context.scene.collection.objects.link(group)
|
||
|
||
# 【修复】确保属性只设置一次,避免重复
|
||
group["sw_uid"] = uid
|
||
group["sw_zid"] = zid # 只设置一次
|
||
group["sw_zip"] = data.get("zip", -1)
|
||
group["sw_typ"] = "zone" # 改为"zone"而不是"zid"
|
||
|
||
# 【调试】打印设置的属性
|
||
logger.info(f"🔧 Zone_{uid} 属性设置:")
|
||
logger.info(f" sw_uid: {group.get('sw_uid')}")
|
||
logger.info(f" sw_zid: {group.get('sw_zid')}")
|
||
logger.info(f" sw_zip: {group.get('sw_zip')}")
|
||
logger.info(f" sw_typ: {group.get('sw_typ')}")
|
||
|
||
if "cor" in data:
|
||
group["sw_cor"] = data["cor"]
|
||
|
||
# 为每个元素创建面
|
||
created_faces = []
|
||
failed_faces = []
|
||
|
||
for i, element in enumerate(elements):
|
||
surf = element.get("surf", {})
|
||
child = element.get("child")
|
||
p_value = surf.get("p", 0)
|
||
f_value = surf.get("f", 0)
|
||
|
||
logger.info(
|
||
f"🔧 处理元素 {i+1}/{len(elements)}: child={child}, p={p_value}, f={f_value}")
|
||
logger.info(f" 顶点数据: {surf.get('segs', [])}")
|
||
|
||
# 创建面
|
||
face = self._create_face_safe(group, surf)
|
||
if face:
|
||
# 设置面的属性
|
||
face["sw_child"] = child
|
||
face["sw_uid"] = uid
|
||
face["sw_zid"] = zid
|
||
face["sw_typ"] = "face"
|
||
face["sw_p"] = p_value
|
||
face["sw_f"] = f_value
|
||
|
||
# 如果是门板层(p=1),设置到门板层
|
||
if p_value == 1:
|
||
face["sw_door_layer"] = True
|
||
|
||
created_faces.append(face)
|
||
logger.info(
|
||
f"✅ 创建面成功: {face.name}, child={child}, p={p_value}")
|
||
else:
|
||
failed_faces.append((child, p_value, f_value))
|
||
logger.error(
|
||
f"❌ 创建面失败: child={child}, p={p_value}, f={f_value}")
|
||
|
||
# 记录创建的面数量
|
||
group["sw_created_faces"] = len(created_faces)
|
||
group["sw_failed_faces"] = len(failed_faces)
|
||
|
||
logger.info(f"📊 Zone_{uid} 创建统计:")
|
||
logger.info(f" 成功创建: {len(created_faces)} 个面")
|
||
logger.info(f" 创建失败: {len(failed_faces)} 个面")
|
||
if failed_faces:
|
||
logger.info(f" 失败的面: {failed_faces}")
|
||
|
||
# 应用单元变换(如果存在)
|
||
if hasattr(self.data_manager, 'unit_trans') and uid in self.data_manager.unit_trans:
|
||
unit_trans = self.data_manager.unit_trans[uid]
|
||
group.matrix_world @= unit_trans
|
||
|
||
# 【修复】使用data_manager.add_zone()方法存储数据,而不是直接操作字典
|
||
self.data_manager.add_zone(uid, zid, group)
|
||
logger.info(
|
||
f"✅ 使用data_manager.add_zone()存储区域数据: uid={uid}, zid={zid}")
|
||
|
||
logger.info(
|
||
f"✅ 区域创建完成: uid={uid}, zid={zid}, 面数={len(created_faces)}")
|
||
return group
|
||
|
||
except Exception as e:
|
||
logger.error(f"c03命令执行失败: {e}")
|
||
self.deletion_stats["deletion_errors"] += 1
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
return None
|
||
|
||
def _parse_transformation(self, trans_data: Dict[str, str]):
|
||
"""解析变换矩阵"""
|
||
try:
|
||
import bpy
|
||
from mathutils import Matrix, Vector
|
||
|
||
# 解析原点
|
||
o_str = trans_data.get("o", "(0,0,0)")
|
||
o = self._parse_point3d(o_str)
|
||
|
||
# 解析轴向量
|
||
x_str = trans_data.get("x", "(1,0,0)")
|
||
y_str = trans_data.get("y", "(0,1,0)")
|
||
z_str = trans_data.get("z", "(0,0,1)")
|
||
|
||
x = self._parse_vector3d(x_str)
|
||
y = self._parse_vector3d(y_str)
|
||
z = self._parse_vector3d(z_str)
|
||
|
||
# 创建变换矩阵
|
||
trans_matrix = Matrix((
|
||
(x.x, y.x, z.x, o.x),
|
||
(x.y, y.y, z.y, o.y),
|
||
(x.z, y.z, z.z, o.z),
|
||
(0, 0, 0, 1)
|
||
))
|
||
|
||
return trans_matrix
|
||
|
||
except Exception as e:
|
||
logger.error(f"解析变换矩阵失败: {e}")
|
||
return Matrix.Identity(4)
|
||
|
||
def _parse_point3d(self, point_str: str):
|
||
"""解析3D点 - 修复尺寸比例问题"""
|
||
try:
|
||
from mathutils import Vector
|
||
|
||
# 移除括号并分割
|
||
point_str = point_str.strip("()")
|
||
coords = point_str.split(",")
|
||
x = float(coords[0].strip()) * 0.001 # 转换为米(Blender使用米作为单位)
|
||
y = float(coords[1].strip()) * 0.001 # 转换为米
|
||
z = float(coords[2].strip()) * 0.001 # 转换为米
|
||
return Vector((x, y, z))
|
||
except Exception as e:
|
||
logger.error(f"解析3D点失败: {e}")
|
||
from mathutils import Vector
|
||
return Vector((0, 0, 0))
|
||
|
||
def _parse_vector3d(self, vector_str: str):
|
||
"""解析3D向量"""
|
||
return self._parse_point3d(vector_str)
|
||
|
||
def _create_face_safe(self, container, surface):
|
||
"""安全创建面 - 使用Blender的正确方式"""
|
||
try:
|
||
import bpy
|
||
from mathutils import Vector
|
||
|
||
segs = surface.get("segs", [])
|
||
if not segs:
|
||
logger.warning("面数据中没有segs信息")
|
||
return None
|
||
|
||
logger.info(f"🔧 开始创建面,segs数量: {len(segs)}")
|
||
|
||
# 解析所有顶点
|
||
vertices = []
|
||
for i, seg in enumerate(segs):
|
||
if len(seg) >= 2:
|
||
start = self._parse_point3d(seg[0])
|
||
vertices.append(start)
|
||
logger.debug(f" 顶点 {i}: {seg[0]} -> {start}")
|
||
|
||
logger.info(f"📊 解析得到 {len(vertices)} 个顶点")
|
||
|
||
# 去重并保持顺序
|
||
unique_vertices = []
|
||
for v in vertices:
|
||
if v not in unique_vertices:
|
||
unique_vertices.append(v)
|
||
|
||
logger.info(f"📊 去重后 {len(unique_vertices)} 个唯一顶点")
|
||
|
||
if len(unique_vertices) < 3:
|
||
logger.warning(f"顶点数量不足,无法创建面: {len(unique_vertices)}")
|
||
return None
|
||
|
||
# 创建网格
|
||
mesh = bpy.data.meshes.new(f"Face_{len(bpy.data.meshes)}")
|
||
obj = bpy.data.objects.new(f"Face_{len(bpy.data.objects)}", mesh)
|
||
|
||
# 链接到场景
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
# 设置父对象
|
||
obj.parent = container
|
||
|
||
# 创建面数据
|
||
# 将顶点转换为列表格式
|
||
verts = [(v.x, v.y, v.z) for v in unique_vertices]
|
||
|
||
# 创建面的索引(假设是四边形,如果不是则调整)
|
||
if len(unique_vertices) == 4:
|
||
faces = [(0, 1, 2, 3)]
|
||
logger.info("📐 创建四边形面")
|
||
elif len(unique_vertices) == 3:
|
||
faces = [(0, 1, 2)]
|
||
logger.info("📐 创建三角形面")
|
||
else:
|
||
# 对于更多顶点,创建三角形面
|
||
faces = []
|
||
for i in range(1, len(unique_vertices) - 1):
|
||
faces.append((0, i, i + 1))
|
||
logger.info(f"📐 创建 {len(faces)} 个三角形面")
|
||
|
||
# 创建网格数据
|
||
mesh.from_pydata(verts, [], faces)
|
||
mesh.update()
|
||
|
||
# 设置面的属性
|
||
obj["sw_p"] = surface.get("p", 0)
|
||
obj["sw_f"] = surface.get("f", 0)
|
||
|
||
# 【新增】为Zone的面添加透明材质 - 参考suw_impl.py的实现
|
||
try:
|
||
if obj.data:
|
||
# 创建透明材质名称
|
||
material_name = "Zone_Transparent"
|
||
|
||
# 检查是否已存在
|
||
if material_name in bpy.data.materials:
|
||
transparent_material = bpy.data.materials[material_name]
|
||
else:
|
||
# 创建新的透明材质
|
||
transparent_material = bpy.data.materials.new(
|
||
name=material_name)
|
||
transparent_material.use_nodes = True
|
||
|
||
# 设置透明属性
|
||
if transparent_material.node_tree:
|
||
principled = transparent_material.node_tree.nodes.get(
|
||
"Principled BSDF")
|
||
if principled:
|
||
# 设置基础颜色为半透明白色
|
||
principled.inputs['Base Color'].default_value = (
|
||
1.0, 1.0, 1.0, 0.5)
|
||
# 设置Alpha为完全透明
|
||
principled.inputs['Alpha'].default_value = 0.0
|
||
|
||
# 设置混合模式
|
||
transparent_material.blend_method = 'BLEND'
|
||
|
||
# 清空现有材质
|
||
obj.data.materials.clear()
|
||
# 添加透明材质
|
||
obj.data.materials.append(transparent_material)
|
||
logger.info(f"✅ 为Zone面 {obj.name} 添加透明材质")
|
||
else:
|
||
logger.warning(f"无法为Zone面 {obj.name} 添加透明材质:缺少网格数据")
|
||
except Exception as material_error:
|
||
logger.error(f"为Zone面添加透明材质失败: {material_error}")
|
||
|
||
logger.info(f"✅ 面创建成功: {obj.name}, 顶点数: {len(unique_vertices)}")
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建面失败: {e}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
return None
|
||
|
||
# ==================== 核心删除方法 ====================
|
||
|
||
def _del_unit_complete(self, uid: str):
|
||
"""完整删除单元 - 对应c03/c04的完整创建逻辑"""
|
||
try:
|
||
logger.info(f"🗑️ 开始完整删除单元: {uid}")
|
||
|
||
# 1. 删除所有区域 (对应c03创建的zones)
|
||
if hasattr(self.data_manager, 'zones') and uid in self.data_manager.zones:
|
||
zones_to_delete = list(self.data_manager.zones[uid].keys())
|
||
for zid in zones_to_delete:
|
||
self._del_zone_complete(uid, zid)
|
||
# 清空zones字典
|
||
del self.data_manager.zones[uid]
|
||
logger.info(f"✅ 清理了单元 {uid} 的所有区域数据")
|
||
|
||
# 2. 删除所有部件 (对应c04创建的parts)
|
||
if hasattr(self.data_manager, 'parts') and uid in self.data_manager.parts:
|
||
parts_to_delete = list(self.data_manager.parts[uid].keys())
|
||
for cp in parts_to_delete:
|
||
self._del_part_complete(uid, cp)
|
||
# 清空parts字典
|
||
del self.data_manager.parts[uid]
|
||
logger.info(f"✅ 清理了单元 {uid} 的所有部件数据")
|
||
|
||
# 3. 删除所有硬件 (对应c08创建的hardwares)
|
||
if hasattr(self.data_manager, 'hardwares') and uid in self.data_manager.hardwares:
|
||
hardwares_to_delete = list(
|
||
self.data_manager.hardwares[uid].keys())
|
||
for hw_id in hardwares_to_delete:
|
||
self._del_hardware_complete(uid, hw_id)
|
||
# 清空hardwares字典
|
||
del self.data_manager.hardwares[uid]
|
||
logger.info(f"✅ 清理了单元 {uid} 的所有硬件数据")
|
||
|
||
# 4. 删除所有加工 (对应c05创建的machinings)
|
||
if hasattr(self.data_manager, 'machinings') and uid in self.data_manager.machinings:
|
||
del self.data_manager.machinings[uid]
|
||
logger.info(f"✅ 清理了单元 {uid} 的所有加工数据")
|
||
|
||
# 5. 删除所有尺寸标注 (对应c07创建的dimensions)
|
||
if hasattr(self.data_manager, 'dimensions') and uid in self.data_manager.dimensions:
|
||
del self.data_manager.dimensions[uid]
|
||
logger.info(f"✅ 清理了单元 {uid} 的所有尺寸标注数据")
|
||
|
||
# 6. 清理单元级别的数据
|
||
self._cleanup_unit_data(uid)
|
||
|
||
# 7. 清理c15缓存
|
||
if hasattr(self.data_manager, '_clear_c15_cache'):
|
||
self.data_manager._clear_c15_cache(uid)
|
||
|
||
# 更新统计
|
||
self.deletion_stats["units_deleted"] += 1
|
||
|
||
logger.info(f"✅ 单元 {uid} 完整删除完成")
|
||
return 1 # 返回1表示成功
|
||
|
||
except Exception as e:
|
||
logger.error(f"完整删除单元失败 {uid}: {e}")
|
||
self.deletion_stats["deletion_errors"] += 1
|
||
return None # 返回None表示失败
|
||
|
||
def _delete_hierarchy(self, root_obj) -> int:
|
||
"""
|
||
[V4 Helper] Deletes a root object and its entire hierarchy of children using BFS and a reversed list.
|
||
Returns the number of objects deleted.
|
||
"""
|
||
if not self._is_object_valid(root_obj):
|
||
return 0
|
||
|
||
# 1. Collect all objects in the hierarchy using Breadth-First Search
|
||
all_objects_in_hierarchy = []
|
||
queue = [root_obj]
|
||
visited_in_hierarchy = {root_obj}
|
||
|
||
while queue:
|
||
current_obj = queue.pop(0)
|
||
all_objects_in_hierarchy.append(current_obj)
|
||
for child in current_obj.children:
|
||
if child not in visited_in_hierarchy:
|
||
visited_in_hierarchy.add(child)
|
||
queue.append(child)
|
||
|
||
# 2. Delete objects in reverse order (children first)
|
||
deleted_count = 0
|
||
for obj_to_delete in reversed(all_objects_in_hierarchy):
|
||
if self._delete_object_safe(obj_to_delete):
|
||
deleted_count += 1
|
||
|
||
return deleted_count
|
||
|
||
def _del_zone_complete(self, uid: str, zid: int):
|
||
"""
|
||
[V4] 完整删除区域及其所有后代对象(Parts, Boards, Faces等)。
|
||
"""
|
||
try:
|
||
logger.info(f"🗑️ [V3] 开始删除区域及其所有后代: uid={uid}, zid={zid}")
|
||
|
||
if hasattr(bpy.app, 'is_job_running'):
|
||
try:
|
||
if bpy.app.is_job_running('RENDER') or bpy.app.is_job_running('OBJECT_BAKE'):
|
||
logger.error("删除操作必须在主线程中执行")
|
||
return 0
|
||
except Exception:
|
||
pass
|
||
|
||
total_deleted_count = 0
|
||
zone_objects = [
|
||
obj for obj in bpy.data.objects
|
||
if (obj.get("sw_uid") == uid and obj.get("sw_zid") == zid and obj.get("sw_typ") == "zone")
|
||
or (obj.name == f"Zone_{uid}" and obj.get("sw_zid") == zid)
|
||
]
|
||
|
||
if not zone_objects:
|
||
logger.warning(f"⚠️ 未找到匹配的Zone对象: uid={uid}, zid={zid}")
|
||
if (self.data_manager and hasattr(self.data_manager, 'zones') and
|
||
uid in self.data_manager.zones and zid in self.data_manager.zones[uid]):
|
||
del self.data_manager.zones[uid][zid]
|
||
logger.info(f"✅ 从数据结构中移除了不存在的Zone记录")
|
||
return 0
|
||
|
||
for zone_obj in zone_objects:
|
||
deleted_count = self._delete_hierarchy(zone_obj)
|
||
|
||
logger.info(
|
||
f"✅ 区域删除完成: 共删除了 {deleted_count} 个对象 (uid={uid}, zid={zid})")
|
||
self.deletion_stats["objects_deleted"] += deleted_count
|
||
return deleted_count
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除区域时发生严重错误: {e}")
|
||
self.deletion_stats["deletion_errors"] += 1
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
return 0
|
||
|
||
def _del_part_complete(self, uid: str, cp: int):
|
||
"""完整删除部件 - [V2] 使用层级删除"""
|
||
try:
|
||
part_obj = self.data_manager.get_parts({"uid": uid}).get(cp)
|
||
|
||
if not self._is_object_valid(part_obj):
|
||
logger.warning(f"部件无效或已被删除,跳过: cp={cp}")
|
||
# 即使对象无效,也应该从数据管理器中移除记录
|
||
self.data_manager.remove_part(uid, cp)
|
||
return 0
|
||
|
||
deleted_count = self._delete_hierarchy(part_obj)
|
||
self.data_manager.remove_part(uid, cp)
|
||
logger.info(f"✅ 成功删除部件及其子对象: cp={cp}, 删除数量={deleted_count}")
|
||
return deleted_count
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除部件时发生严重错误: {e}")
|
||
self.deletion_stats["deletion_errors"] += 1
|
||
return 0
|
||
|
||
def _del_hardware_complete(self, uid: str, hw_id: int):
|
||
"""完整删除硬件 - [V2] 使用层级删除"""
|
||
try:
|
||
hw_obj = self.data_manager.get_hardwares({"uid": uid}).get(hw_id)
|
||
|
||
if not self._is_object_valid(hw_obj):
|
||
logger.warning(f"硬件无效或已被删除,跳过: hw_id={hw_id}")
|
||
self.data_manager.remove_hardware(uid, hw_id)
|
||
return 0
|
||
|
||
deleted_count = self._delete_hierarchy(hw_obj)
|
||
self.data_manager.remove_hardware(uid, hw_id)
|
||
logger.info(f"✅ 成功删除硬件及其子对象: hw_id={hw_id}, 删除数量={deleted_count}")
|
||
return deleted_count
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除硬件时发生严重错误: {e}")
|
||
self.deletion_stats["deletion_errors"] += 1
|
||
return 0
|
||
|
||
# ==================== 辅助方法 ====================
|
||
|
||
def _is_object_valid(self, obj) -> bool:
|
||
"""检查对象是否有效 - 改进版本"""
|
||
try:
|
||
if not obj:
|
||
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
|
||
|
||
# 检查对象是否已被标记为删除
|
||
if hasattr(obj, 'is_updated_data') and obj.is_updated_data:
|
||
return False
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.debug(f"检查对象有效性时发生错误: {e}")
|
||
return False
|
||
|
||
def _delete_object_safe(self, obj) -> bool:
|
||
"""安全删除对象 - 修复版本,添加主线程检查"""
|
||
try:
|
||
if not obj or not BLENDER_AVAILABLE:
|
||
return False
|
||
|
||
# 【修复1】检查是否在主线程中 - 修复is_job_running调用
|
||
if hasattr(bpy.app, 'is_job_running'):
|
||
try:
|
||
# 检查是否有任何后台任务在运行
|
||
if bpy.app.is_job_running('RENDER') or bpy.app.is_job_running('OBJECT_BAKE'):
|
||
logger.warning("对象删除操作必须在主线程中执行")
|
||
return False
|
||
except Exception:
|
||
# 如果检查失败,继续执行
|
||
pass
|
||
|
||
# 【修复2】检查对象是否仍然有效
|
||
if not self._is_object_valid(obj):
|
||
logger.debug(
|
||
f"对象已无效,跳过删除: {obj.name if hasattr(obj, 'name') else 'unknown'}")
|
||
return True # 对象已经不存在,视为删除成功
|
||
|
||
# 【修复3】检查对象是否在Blender数据中
|
||
if hasattr(obj, 'name'):
|
||
if obj.name not in bpy.data.objects:
|
||
logger.debug(f"对象不在bpy.data.objects中,跳过删除: {obj.name}")
|
||
return True # 对象已经不在数据中,视为删除成功
|
||
|
||
# 【修复4】安全删除对象 - 添加更多错误处理
|
||
try:
|
||
# 在删除前记录对象名称
|
||
obj_name = obj.name if hasattr(obj, 'name') else 'unknown'
|
||
|
||
# 检查对象是否仍然有效
|
||
if not self._is_object_valid(obj):
|
||
logger.debug(f"对象在删除前已无效: {obj_name}")
|
||
return True
|
||
|
||
# 执行删除
|
||
bpy.data.objects.remove(obj, do_unlink=True)
|
||
logger.debug(f"✅ 成功删除对象: {obj_name}")
|
||
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 True # 对象已被移除,视为删除成功
|
||
else:
|
||
logger.error(
|
||
f"删除对象失败: {obj.name if hasattr(obj, 'name') else 'unknown'}, 错误: {e}")
|
||
return False
|
||
|
||
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 True # 对象已被移除,视为删除成功
|
||
else:
|
||
logger.error(f"安全删除对象时发生错误: {e}")
|
||
return False
|
||
|
||
def _cleanup_orphaned_meshes(self):
|
||
"""清理孤立的网格数据 - 改进版本"""
|
||
try:
|
||
# 【修复】更安全的清理逻辑
|
||
meshes_to_remove = []
|
||
for mesh in bpy.data.meshes:
|
||
try:
|
||
if mesh.users == 0:
|
||
meshes_to_remove.append(mesh)
|
||
except Exception as e:
|
||
# 检查是否是"StructRNA has been removed"错误
|
||
if "StructRNA" in str(e) and "removed" in str(e):
|
||
logger.debug(f"网格已被移除,跳过清理")
|
||
continue
|
||
else:
|
||
logger.debug(f"检查网格时发生错误: {e}")
|
||
continue
|
||
|
||
for mesh in meshes_to_remove:
|
||
try:
|
||
# 再次检查网格是否仍然有效
|
||
if not self._is_object_valid(mesh):
|
||
logger.debug(f"网格已无效,跳过删除")
|
||
continue
|
||
|
||
bpy.data.meshes.remove(mesh)
|
||
logger.debug(f"清理孤立网格: {mesh.name}")
|
||
except Exception as e:
|
||
# 检查是否是"StructRNA has been removed"错误
|
||
if "StructRNA" in str(e) and "removed" in str(e):
|
||
logger.debug(f"网格已被移除,跳过删除")
|
||
continue
|
||
else:
|
||
logger.debug(f"删除孤立网格失败: {e}")
|
||
|
||
# 【修复】更安全的材质清理逻辑
|
||
materials_to_remove = []
|
||
for material in bpy.data.materials:
|
||
try:
|
||
# 【修复】检查材质是否仍然有效
|
||
if not material or not hasattr(material, 'users'):
|
||
continue
|
||
|
||
if material.users == 0:
|
||
materials_to_remove.append(material)
|
||
except Exception as e:
|
||
# 检查是否是"StructRNA has been removed"错误
|
||
if "StructRNA" in str(e) and "removed" in str(e):
|
||
logger.debug(f"材质已被移除,跳过清理")
|
||
continue
|
||
else:
|
||
logger.debug(f"检查材质时发生错误: {e}")
|
||
continue
|
||
|
||
for material in materials_to_remove:
|
||
try:
|
||
# 【修复】再次检查材质是否仍然有效
|
||
if material and hasattr(material, 'name') and material.name in bpy.data.materials:
|
||
bpy.data.materials.remove(material)
|
||
logger.debug(f"清理孤立材质: {material.name}")
|
||
except Exception as e:
|
||
# 检查是否是"StructRNA has been removed"错误
|
||
if "StructRNA" in str(e) and "removed" in str(e):
|
||
logger.debug(f"材质已被移除,跳过删除")
|
||
continue
|
||
else:
|
||
logger.debug(f"删除孤立材质失败: {e}")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"清理孤立数据时发生错误: {e}")
|
||
|
||
def _cleanup_unit_data(self, uid: str):
|
||
"""清理单元数据"""
|
||
try:
|
||
logger.info(f"🧹 清理单元数据: {uid}")
|
||
|
||
# 清理单元变换数据
|
||
if hasattr(self.data_manager, 'unit_trans') and uid in self.data_manager.unit_trans:
|
||
del self.data_manager.unit_trans[uid]
|
||
logger.info(f"✅ 清理单元变换数据: {uid}")
|
||
|
||
# 清理单元参数数据
|
||
if hasattr(self.data_manager, 'unit_param') and uid in self.data_manager.unit_param:
|
||
del self.data_manager.unit_param[uid]
|
||
logger.info(f"✅ 清理单元参数数据: {uid}")
|
||
|
||
# 强制垃圾回收
|
||
import gc
|
||
gc.collect()
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理单元数据失败: {e}")
|
||
|
||
def _clear_labels_safe(self, uid: str = None):
|
||
"""安全清理标签 - 修复参数问题"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 查找并删除标签对象
|
||
labels_to_delete = []
|
||
for obj in bpy.data.objects:
|
||
if obj.get("sw_typ") == "label" or "Label" in obj.name:
|
||
# 如果指定了uid,只删除该uid的标签
|
||
if uid is None or obj.get("sw_uid") == uid:
|
||
labels_to_delete.append(obj)
|
||
|
||
for label in labels_to_delete:
|
||
self._delete_object_safe(label)
|
||
|
||
if labels_to_delete:
|
||
logger.info(f"✅ 清理了 {len(labels_to_delete)} 个标签对象")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理标签失败: {e}")
|
||
|
||
# ==================== 统计和管理方法 ====================
|
||
|
||
def get_deletion_stats(self) -> Dict[str, Any]:
|
||
"""获取删除统计信息"""
|
||
try:
|
||
return {
|
||
"deletion_stats": self.deletion_stats.copy(),
|
||
"total_deletions": (
|
||
self.deletion_stats["units_deleted"] +
|
||
self.deletion_stats["zones_deleted"] +
|
||
self.deletion_stats["parts_deleted"] +
|
||
self.deletion_stats["hardwares_deleted"]
|
||
),
|
||
"success_rate": (
|
||
1.0 - (self.deletion_stats["deletion_errors"] /
|
||
max(1, sum(self.deletion_stats.values())))
|
||
) * 100
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"获取删除统计失败: {e}")
|
||
return {"error": str(e)}
|
||
|
||
def reset_deletion_stats(self):
|
||
"""重置删除统计"""
|
||
self.deletion_stats = {
|
||
"units_deleted": 0,
|
||
"zones_deleted": 0,
|
||
"parts_deleted": 0,
|
||
"hardwares_deleted": 0,
|
||
"objects_deleted": 0,
|
||
"deletion_errors": 0
|
||
}
|
||
logger.info("删除统计已重置")
|
||
|
||
def cleanup(self):
|
||
"""清理删除管理器"""
|
||
try:
|
||
# 重置统计
|
||
self.reset_deletion_stats()
|
||
|
||
logger.info("✅ 删除管理器清理完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理删除管理器失败: {e}")
|
||
|
||
def _clear_selection(self):
|
||
"""清除所有选择 - 对应Ruby的sel_clear"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 清除所有对象的选择状态
|
||
for obj in bpy.data.objects:
|
||
try:
|
||
if hasattr(obj, 'select_set'):
|
||
obj.select_set(False)
|
||
except Exception:
|
||
pass
|
||
|
||
# 清除活动对象
|
||
if hasattr(bpy.context, 'view_layer'):
|
||
bpy.context.view_layer.objects.active = None
|
||
|
||
logger.debug("✅ 清除所有选择完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清除选择失败: {e}")
|
||
|
||
def _del_entities_by_type(self, entities: Dict[str, Any], typ: str, oid: int, uid: str = None) -> int:
|
||
"""按类型删除实体 - V4: 使用层级删除"""
|
||
try:
|
||
if not entities:
|
||
return 0
|
||
|
||
total_deleted_count = 0
|
||
|
||
# 创建一个要检查的键的副本,因为字典会在循环中被修改
|
||
keys_to_check = list(entities.keys())
|
||
|
||
for key in keys_to_check:
|
||
# 检查键是否仍然存在,因为它可能作为另一个实体的子级被删除了
|
||
if key not in entities:
|
||
continue
|
||
|
||
entity = entities[key]
|
||
if self._is_object_valid(entity):
|
||
if typ == "uid" or self._get_entity_attribute(entity, typ) == oid:
|
||
# 执行层级删除
|
||
deleted_in_hierarchy = self._delete_hierarchy(entity)
|
||
total_deleted_count += deleted_in_hierarchy
|
||
|
||
# 删除后,从原始字典中移除键
|
||
if key in entities:
|
||
del entities[key]
|
||
|
||
logger.info(
|
||
f"✅ 按类型删除实体: typ={typ}, oid={oid}, 删除数量={total_deleted_count}")
|
||
return total_deleted_count
|
||
except Exception as e:
|
||
logger.error(f"❌ 按类型删除实体失败: typ={typ}, oid={oid}, 错误: {e}")
|
||
return 0
|
||
|
||
def _get_entity_attribute(self, entity, attr_name: str):
|
||
"""获取实体属性 - 对应Ruby的get_attribute逻辑"""
|
||
try:
|
||
if BLENDER_AVAILABLE and hasattr(entity, 'get'):
|
||
# 检查多种可能的属性名
|
||
possible_attrs = [
|
||
f"sw_{attr_name}",
|
||
attr_name,
|
||
f"sw{attr_name}"
|
||
]
|
||
|
||
for attr in possible_attrs:
|
||
value = entity.get(attr)
|
||
if value is not None:
|
||
return value
|
||
|
||
return None
|
||
except:
|
||
return None
|
||
|
||
def _cleanup_machinings(self, uid: str):
|
||
"""清理加工数据 - 对应Ruby的machinings清理逻辑"""
|
||
try:
|
||
if not self.data_manager or not hasattr(self.data_manager, 'machinings'):
|
||
return
|
||
|
||
if uid in self.data_manager.machinings:
|
||
machinings = self.data_manager.machinings[uid]
|
||
# 清理已删除的加工对象 (对应Ruby的delete_if{|entity| entity.deleted?})
|
||
valid_machinings = []
|
||
for machining in machinings:
|
||
if machining and self._is_object_valid(machining):
|
||
valid_machinings.append(machining)
|
||
|
||
self.data_manager.machinings[uid] = valid_machinings
|
||
logger.info(
|
||
f"✅ 清理加工数据: uid={uid}, 保留{len(valid_machinings)}个有效加工")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理加工数据失败: {e}")
|
||
|
||
def _del_dimensions(self, data: Dict[str, Any]):
|
||
"""删除尺寸标注 - 对应Ruby的c0c方法"""
|
||
try:
|
||
uid = data.get("uid")
|
||
if not uid or not self.data_manager or not hasattr(self.data_manager, 'dimensions'):
|
||
return
|
||
|
||
if uid in self.data_manager.dimensions:
|
||
dimensions = self.data_manager.dimensions[uid]
|
||
deleted_count = 0
|
||
|
||
# 删除所有尺寸标注
|
||
for dim in dimensions:
|
||
if dim and self._is_object_valid(dim):
|
||
if self._delete_object_safe(dim):
|
||
deleted_count += 1
|
||
|
||
# 从数据结构中移除
|
||
del self.data_manager.dimensions[uid]
|
||
logger.info(f"✅ 删除尺寸标注: uid={uid}, 删除了{deleted_count}个标注")
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除尺寸标注失败: {e}")
|
||
|
||
def _clear_door_labels_safe(self, uid: str = None):
|
||
"""安全清理门标签 - 修复参数问题"""
|
||
try:
|
||
if not BLENDER_AVAILABLE or not self.data_manager:
|
||
return
|
||
|
||
if hasattr(self.data_manager, 'door_labels') and self.data_manager.door_labels:
|
||
# 查找并删除门标签对象
|
||
door_labels_to_delete = []
|
||
for obj in bpy.data.objects:
|
||
if (obj.get("sw_typ") == "door_label" or
|
||
"DoorLabel" in obj.name or
|
||
obj.get("sw_label_type") == "door"):
|
||
# 如果指定了uid,只删除该uid的门标签
|
||
if uid is None or obj.get("sw_uid") == uid:
|
||
door_labels_to_delete.append(obj)
|
||
|
||
for label in door_labels_to_delete:
|
||
self._delete_object_safe(label)
|
||
|
||
if door_labels_to_delete:
|
||
logger.info(f"✅ 清理了 {len(door_labels_to_delete)} 个门标签对象")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理门标签失败: {e}")
|
||
|
||
def _matches_delete_condition(self, entity, typ: str, oid: int, uid: str = None) -> bool:
|
||
"""检查实体是否匹配删除条件 - 添加详细调试"""
|
||
try:
|
||
if not entity or not hasattr(entity, 'get'):
|
||
return False
|
||
|
||
# 【调试】打印实体的所有属性
|
||
entity_uid = entity.get("sw_uid")
|
||
entity_typ_value = entity.get(f"sw_{typ}")
|
||
|
||
logger.debug(
|
||
f"🔍 检查删除条件: {entity.name if hasattr(entity, 'name') else 'unknown'}")
|
||
logger.debug(
|
||
f" 实体属性: sw_uid={entity_uid}, sw_{typ}={entity_typ_value}")
|
||
logger.debug(f" 删除条件: uid={uid}, typ={typ}, oid={oid}")
|
||
|
||
# 【修复】正确的删除条件逻辑
|
||
if typ == "uid":
|
||
# 删除整个单元:检查sw_uid
|
||
uid_matches = entity_uid == oid
|
||
logger.debug(f" uid删除匹配: {uid_matches}")
|
||
return uid_matches
|
||
else:
|
||
# 删除特定类型:需要同时匹配uid和对应的类型属性
|
||
uid_matches = uid is None or entity_uid == uid
|
||
typ_matches = entity_typ_value == oid
|
||
|
||
logger.debug(
|
||
f" 类型删除匹配: uid匹配={uid_matches}, {typ}匹配={typ_matches}")
|
||
|
||
# 必须同时匹配uid和类型值
|
||
return uid_matches and typ_matches
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查删除条件时发生错误: {e}")
|
||
return False
|
||
|
||
def _delete_blender_objects_by_type(self, typ: str, oid: int, uid: str = None) -> int:
|
||
"""从Blender中删除指定类型的对象 - 添加详细调试"""
|
||
deleted_count = 0
|
||
|
||
try:
|
||
logger.info(f" 开始搜索Blender对象: typ={typ}, oid={oid}, uid={uid}")
|
||
|
||
# 遍历所有Blender对象
|
||
objects_to_delete = []
|
||
checked_objects = []
|
||
|
||
for obj in bpy.data.objects:
|
||
checked_objects.append(obj.name)
|
||
if self._should_delete_blender_object(obj, typ, oid, uid):
|
||
objects_to_delete.append(obj)
|
||
logger.info(f"🎯 标记删除: {obj.name}")
|
||
|
||
logger.info(
|
||
f"📊 检查了 {len(checked_objects)} 个对象,标记删除 {len(objects_to_delete)} 个")
|
||
|
||
# 删除收集到的对象
|
||
for obj in objects_to_delete:
|
||
try:
|
||
logger.info(
|
||
f"️ 删除Blender对象: {obj.name}, typ={typ}, oid={oid}, uid={uid}")
|
||
bpy.data.objects.remove(obj, do_unlink=True)
|
||
deleted_count += 1
|
||
except Exception as e:
|
||
logger.error(f"删除Blender对象失败: {obj.name}, 错误: {e}")
|
||
|
||
# 清理孤立的网格数据 注释
|
||
# self._cleanup_orphaned_meshes()
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除Blender对象时发生错误: {e}")
|
||
|
||
return deleted_count
|
||
|
||
def _should_delete_blender_object(self, obj, typ: str, oid: int, uid: str = None) -> bool:
|
||
"""判断是否应该删除Blender对象 - 添加详细调试"""
|
||
try:
|
||
if not obj or not hasattr(obj, 'get'):
|
||
return False
|
||
|
||
# 【调试】打印对象的所有sw_属性
|
||
sw_attrs = {}
|
||
for key, value in obj.items():
|
||
if key.startswith('sw_'):
|
||
sw_attrs[key] = value
|
||
|
||
logger.debug(f"🔍 检查对象 {obj.name} 的sw_属性: {sw_attrs}")
|
||
|
||
# 使用相同的删除条件逻辑
|
||
should_delete = self._matches_delete_condition(obj, typ, oid, uid)
|
||
|
||
if should_delete:
|
||
logger.info(f"✅ 对象 {obj.name} 匹配删除条件")
|
||
else:
|
||
logger.debug(f"❌ 对象 {obj.name} 不匹配删除条件")
|
||
|
||
return should_delete
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查Blender对象删除条件时发生错误: {e}")
|
||
return False
|
||
|
||
# ==================== 全局删除管理器实例 ====================
|
||
|
||
|
||
# 全局删除管理器实例
|
||
deletion_manager: Optional[DeletionManager] = None
|
||
|
||
|
||
def init_deletion_manager() -> DeletionManager:
|
||
"""初始化全局删除管理器实例"""
|
||
global deletion_manager
|
||
if deletion_manager is None:
|
||
deletion_manager = DeletionManager()
|
||
return deletion_manager
|
||
|
||
|
||
def get_deletion_manager() -> DeletionManager:
|
||
"""获取全局删除管理器实例"""
|
||
global deletion_manager
|
||
if deletion_manager is None:
|
||
deletion_manager = init_deletion_manager()
|
||
return deletion_manager
|
||
|
||
|
||
# 自动初始化
|
||
deletion_manager = init_deletion_manager()
|