blenderpython/suw_core/deletion_manager.py

1225 lines
48 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 - 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()