blenderpython/suw_core/machining_manager.py

1170 lines
44 KiB
Python
Raw Permalink Normal View History

2025-08-01 17:13:30 +08:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SUW Core - Machining Manager Module
拆分自: suw_impl.py (Line 2292-3500, 4790-4990)
用途: Blender加工管理几何体创建布尔运算
版本: 1.0.0
作者: SUWood Team
"""
from .material_manager import material_manager
from .memory_manager import memory_manager
from .data_manager import data_manager, get_data_manager
import time
import logging
import threading
from typing import Dict, Any, List, Optional
import math
# 设置日志
logger = logging.getLogger(__name__)
# 检查Blender可用性
try:
import bpy
import bmesh
BLENDER_AVAILABLE = True
except ImportError:
BLENDER_AVAILABLE = False
# 导入依赖模块
# ==================== 加工管理器类 ====================
class MachiningManager:
"""加工管理器 - 负责所有加工相关操作"""
def __init__(self):
"""
初始化加工管理器 - 完全独立不依赖suw_impl
"""
# 使用全局数据管理器
self.data_manager = get_data_manager()
# 【新增】添加材质管理器引用
from .material_manager import get_material_manager
self.material_manager = get_material_manager()
# 加工数据存储
self.machinings = {}
# 加工统计
self.machining_stats = {
"machinings_created": 0,
"trim_operations": 0,
"creation_errors": 0
}
logger.info("✅ 加工管理器初始化完成")
# ==================== 原始命令方法 ====================
def c05(self, data: Dict[str, Any]):
"""c05 - 添加加工 - 参考SUW IMPL实现按板件分组批量创建"""
try:
logger.info("🔧 执行c05命令: 创建加工")
if not BLENDER_AVAILABLE:
logger.warning("Blender 不可用,跳过加工创建")
return 0
uid = data.get("uid")
items = data.get("items", [])
logger.info(f"开始创建加工: uid={uid}, 项目数={len(items)}")
# 【参考SUW IMPL】获取部件和硬件集合
parts = self._get_parts(data)
hardwares = self._get_hardwares(data)
# 【参考SUW IMPL】分类处理可视化加工 vs 布尔运算
visual_works = []
boolean_works = []
for i, work in enumerate(items):
# 【修复】不再跳过cancel=1的项目所有项目都要创建
cp = work.get("cp")
if not cp:
continue
# 【参考SUW IMPL】获取组件
component = None
if cp in parts:
component = parts[cp]
elif cp in hardwares:
component = hardwares[cp]
if not component or not self._is_object_valid(component):
logger.info(f"🚨 组件查找失败: cp={cp}, component={component}")
continue
work['component'] = component
work['index'] = i
if work.get("trim3d", 0) == 1:
boolean_works.append(work)
else:
visual_works.append(work)
created_count = 0
# 【参考SUW IMPL】1. 批量处理可视化加工
if visual_works:
created_count += self._create_visual_machining_batch_suw_style(
visual_works, uid)
# 【参考SUW IMPL】2. 批量处理布尔运算
if boolean_works:
created_count += self._create_boolean_machining_batch_suw_style(
boolean_works)
logger.info(f"✅ c05创建加工完成: {created_count} 个对象")
return created_count
except Exception as e:
logger.error(f"c05创建加工异常: {e}")
return 0
def _create_visual_machining_batch_suw_style(self, visual_works, uid):
"""批量创建可视化加工对象 - 参考SUW IMPL实现支持材质区分"""
try:
import bmesh
created_count = 0
# 【参考SUW IMPL】按组件分组同一组件的加工可以批量创建
component_groups = {}
for work in visual_works:
component = work['component']
if component not in component_groups:
component_groups[component] = []
component_groups[component].append(work)
for component, works in component_groups.items():
logger.info(f"🔨 为组件 {component.name} 批量创建 {len(works)} 个加工对象")
# 【参考SUW IMPL】创建主加工组
main_machining = bpy.data.objects.new(
f"Machining_{component.name}", None)
bpy.context.scene.collection.objects.link(main_machining)
main_machining.parent = component
main_machining["sw_typ"] = "work"
# 【参考SUW IMPL】创建记录标准化为c0a对称删除做准备
import time
creation_record = {
"type": "visual_batch",
"main_machining": main_machining.name,
"geometry_objects": [],
"material_applied": None,
"created_timestamp": time.time()
}
# 添加到数据管理器
self.data_manager.add_machining(uid, main_machining)
# 【修复】按cancel状态分组分别创建不同材质的几何体
active_works = [] # cancel=0蓝色材质
cancelled_works = [] # cancel=1灰色材质
for work in works:
if work.get("cancel", 0) == 1:
cancelled_works.append(work)
else:
active_works.append(work)
# 创建有效加工组(蓝色)
if active_works:
created_count += self._create_work_group_with_material(
main_machining, active_works, "active")
# 创建取消加工组(灰色)
if cancelled_works:
created_count += self._create_work_group_with_material(
main_machining, cancelled_works, "cancelled")
return created_count
except Exception as e:
logger.error(f"批量创建可视化加工失败: {e}")
return 0
def _create_work_group_with_material(self, main_machining, works, work_type):
"""为指定材质类型创建工作组"""
try:
if not works:
return 0
import bmesh
created_count = 0
# 创建bmesh
bm = bmesh.new()
for work in works:
try:
# 解析坐标
p1 = self._parse_point3d(work.get("p1", "(0,0,0)"))
p2 = self._parse_point3d(work.get("p2", "(0,0,0)"))
# 根据类型创建几何体
if "tri" in work:
self._add_triangle_to_bmesh_suw_style(bm, work, p1, p2)
elif "surf" in work:
self._add_surface_to_bmesh_suw_style(bm, work, p1, p2)
else:
self._add_circle_to_bmesh_suw_style(bm, work, p1, p2)
created_count += 1
except Exception as e:
logger.error(f"创建单个加工几何体失败: {e}")
# 创建网格对象
if bm.verts:
mesh = bpy.data.meshes.new(
f"MachiningMesh_{main_machining.name}_{work_type}")
bm.to_mesh(mesh)
mesh.update()
# 创建对象
mesh_obj = bpy.data.objects.new(
f"MachiningGeometry_{main_machining.name}_{work_type}", mesh)
bpy.context.scene.collection.objects.link(mesh_obj)
mesh_obj.parent = main_machining
# 【修复】应用对应材质
try:
if hasattr(self, 'material_manager'):
if work_type == "active":
# 蓝色材质 - 有效加工
self.material_manager.apply_machining_material(mesh_obj)
else:
# 灰色材质 - 取消的加工
self.material_manager.apply_cancelled_machining_material(mesh_obj)
except Exception as e:
logger.warning(f"应用材质失败: {e}")
bm.free()
return created_count
except Exception as e:
logger.error(f"创建工作组失败: {e}")
return 0
def _add_circle_to_bmesh_suw_style(self, bm, work, p1, p2):
"""向bmesh添加圆形几何体 - 参考SUW IMPL实现修复孔位朝向"""
try:
import bmesh
dia = work.get("dia", 5.0)
radius = dia * 0.001 / 2.0
# 【参考SUW IMPL】计算方向和位置
if BLENDER_AVAILABLE:
import mathutils
# 转换为mathutils.Vector
p1_vec = mathutils.Vector(p1)
p2_vec = mathutils.Vector(p2)
# 计算方向和长度
direction = p2_vec - p1_vec
length = direction.length
midpoint = (p1_vec + p2_vec) / 2
if length < 0.0001:
logger.warning("圆柱体长度过短,跳过创建")
return
logger.debug(f"🔧 创建圆柱体: 半径={radius:.3f}, 长度={length:.3f}")
# 【参考SUW IMPL】计算旋转矩阵 - 将Z轴对齐到加工方向
# 使用rotation_difference计算精确旋转避免万向节锁
z_axis = mathutils.Vector((0, 0, 1))
rotation_quat = z_axis.rotation_difference(
direction.normalized())
rotation_matrix = rotation_quat.to_matrix().to_4x4()
# 组合变换矩阵: 先旋转,再平移
translation_matrix = mathutils.Matrix.Translation(midpoint)
final_transform_matrix = translation_matrix @ rotation_matrix
# 在临时bmesh中创建标准圆柱体
temp_bm = bmesh.new()
bmesh.ops.create_cone(
temp_bm,
cap_ends=True, # 生成端盖
cap_tris=False, # 端盖用 n 边而非三角
segments=12,
radius1=radius,
radius2=radius, # 与 radius1 相同 → 圆柱
depth=length
)
# 应用变换矩阵
bmesh.ops.transform(
temp_bm, matrix=final_transform_matrix, verts=temp_bm.verts)
# 将变换后的几何体合并到主bmesh
vert_map = {}
for v in temp_bm.verts:
new_v = bm.verts.new(v.co)
vert_map[v] = new_v
for f in temp_bm.faces:
bm.faces.new(tuple(vert_map[v] for v in f.verts))
temp_bm.free()
logger.debug(
f"✅ 圆柱体变换完成: 世界坐标中点({midpoint.x:.3f}, {midpoint.y:.3f}, {midpoint.z:.3f})")
else:
# 非Blender环境的简化版本
direction = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2])
length = (direction[0]**2 + direction[1]
** 2 + direction[2]**2)**0.5
center = ((p1[0] + p2[0])/2, (p1[1] + p2[1]) /
2, (p1[2] + p2[2])/2)
# 创建圆柱体(简化版本,不做旋转)
bmesh.ops.create_cone(
bm,
cap_ends=True,
cap_tris=False,
segments=12,
radius1=radius,
radius2=radius,
depth=max(length, 0.01)
)
# 移动到正确位置
bmesh.ops.translate(
bm,
vec=center,
verts=bm.verts[-24:] # 圆柱体的顶点
)
except Exception as e:
logger.error(f"添加圆形到bmesh失败: {e}")
import traceback
logger.error(traceback.format_exc())
def _add_triangle_to_bmesh_suw_style(self, bm, work, p1, p2):
"""向bmesh添加三角形几何体 - 参考SUW IMPL实现"""
try:
# 获取第三个点
tri = self._parse_point3d(work.get("tri", "(0,0,0)"))
p3 = self._parse_point3d(work.get("p3", "(0,0,0)"))
# 计算三角形顶点
pts = [
tri,
(tri[0] + p2[0] - p1[0], tri[1] +
p2[1] - p1[1], tri[2] + p2[2] - p1[2]),
(p1[0] + p1[0] - tri[0], p1[1] +
p1[1] - tri[1], p1[2] + p1[2] - tri[2])
]
# 创建三角形顶点
v1 = bm.verts.new(pts[0])
v2 = bm.verts.new(pts[1])
v3 = bm.verts.new(pts[2])
# 创建面
bm.faces.new([v1, v2, v3])
except Exception as e:
logger.error(f"添加三角形到bmesh失败: {e}")
def _add_surface_to_bmesh_suw_style(self, bm, work, p1, p2):
"""向bmesh添加表面几何体 - 参考SUW IMPL实现"""
try:
# 解析表面数据
surf = work.get("surf", {})
segs = surf.get("segs", [])
if not segs:
return
# 简化的表面创建
# 这里需要根据实际的表面数据格式进行解析
# 暂时创建一个简单的平面
v1 = bm.verts.new(p1)
v2 = bm.verts.new(p2)
v3 = bm.verts.new([(p1[0] + p2[0])/2, p1[1], (p1[2] + p2[2])/2])
v4 = bm.verts.new([(p1[0] + p2[0])/2, p2[1], (p1[2] + p2[2])/2])
# 创建面
bm.faces.new([v1, v2, v3, v4])
except Exception as e:
logger.error(f"添加表面到bmesh失败: {e}")
def _create_boolean_machining_batch_suw_style(self, boolean_works):
"""批量创建布尔运算加工对象 - 参考SUW IMPL实现"""
try:
created_count = 0
# 按组件分组
component_groups = {}
for work in boolean_works:
component = work['component']
if component not in component_groups:
component_groups[component] = []
component_groups[component].append(work)
for component, works in component_groups.items():
logger.info(
f"🔨 为组件 {component.name} 批量创建 {len(works)} 个布尔运算加工")
# 按类型分组
circle_works = []
triangle_works = []
surface_works = []
for work in works:
p1 = self._parse_point3d(work.get("p1", "(0,0,0)"))
p2 = self._parse_point3d(work.get("p2", "(0,0,0)"))
if "tri" in work:
triangle_works.append((work, p1, p2))
elif "surf" in work:
surface_works.append((work, p1, p2))
else:
circle_works.append((work, p1, p2))
# 批量创建统一修剪器
if circle_works:
unified_circle = self._create_unified_circle_trimmer_suw_style(
circle_works, component.name)
if unified_circle:
created_count += len(circle_works)
if triangle_works:
unified_triangle = self._create_unified_triangle_trimmer_suw_style(
triangle_works, component.name)
if unified_triangle:
created_count += len(triangle_works)
if surface_works:
unified_surface = self._create_unified_surface_trimmer_suw_style(
surface_works, component.name)
if unified_surface:
created_count += len(surface_works)
return created_count
except Exception as e:
logger.error(f"批量创建布尔运算加工失败: {e}")
return 0
def _create_unified_circle_trimmer_suw_style(self, circle_data_list, component_name):
"""创建统一圆形剪切器 - 参考SUW IMPL实现"""
try:
if not circle_data_list:
return None
# 创建合并的圆形剪切器
bm = bmesh.new()
for circle_data in circle_data_list:
work, p1, p2 = circle_data
# 使用相同的圆形创建逻辑
self._add_circle_to_bmesh_suw_style(bm, work, p1, p2)
# 创建网格
mesh = bpy.data.meshes.new(
f"UnifiedCircleTrimmer_{component_name}")
bm.to_mesh(mesh)
mesh.update()
obj = bpy.data.objects.new(
f"UnifiedCircleTrimmer_{component_name}", mesh)
bpy.context.scene.collection.objects.link(obj)
bm.free()
return obj
except Exception as e:
logger.error(f"创建统一圆形剪切器失败: {e}")
return None
def _create_unified_triangle_trimmer_suw_style(self, triangle_data_list, component_name):
"""创建统一三角形剪切器 - 参考SUW IMPL实现"""
try:
if not triangle_data_list:
return None
# 简化实现
return None
except Exception as e:
logger.error(f"创建统一三角形剪切器失败: {e}")
return None
def _create_unified_surface_trimmer_suw_style(self, surface_data_list, component_name):
"""创建统一表面剪切器 - 参考SUW IMPL实现"""
try:
if not surface_data_list:
return None
# 简化实现
return None
except Exception as e:
logger.error(f"创建统一表面剪切器失败: {e}")
return None
def _create_cylinder_ultra_safe(self, machining, p1, p2, diameter, index):
"""使用超安全的方法创建圆柱体 - 避免所有依赖图问题"""
try:
# 计算长度和中心点
length = math.sqrt((p2[0] - p1[0])**2 +
(p2[1] - p1[1])**2 + (p2[2] - p1[2])**2)
if length < 0.001:
length = 0.001
center = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) /
2, (p1[2] + p2[2]) / 2]
radius = (diameter * 0.001) / 2.0 # mm -> m
# 【修复】使用时间戳确保唯一命名
import time
timestamp = int(time.time() * 1000) % 100000
unique_id = f"{machining.name}_{index}_{timestamp}"
# 【修复】使用更简单的命名避免组件ID冲突
mesh_name = f"Mesh_{unique_id}"
obj_name = f"Cylinder_{unique_id}"
# 【修复】检查名称是否已存在
if mesh_name in bpy.data.meshes:
bpy.data.meshes.remove(bpy.data.meshes[mesh_name])
if obj_name in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects[obj_name])
# 创建网格数据
mesh = bpy.data.meshes.new(mesh_name)
# 使用简单的顶点和面创建圆柱体
vertices = []
faces = []
# 创建6个分段的圆柱体减少复杂度
segments = 6
for i in range(segments):
angle = 2 * math.pi * i / segments
cos_val = math.cos(angle)
sin_val = math.sin(angle)
# 第一个端面
x1 = center[0] + radius * cos_val
y1 = center[1] + radius * sin_val
z1 = center[2] - length / 2
vertices.append((x1, y1, z1))
# 第二个端面
x2 = center[0] + radius * cos_val
y2 = center[1] + radius * sin_val
z2 = center[2] + length / 2
vertices.append((x2, y2, z2))
# 创建侧面
for i in range(segments):
v1 = i * 2
v2 = (i + 1) % segments * 2
v3 = (i + 1) % segments * 2 + 1
v4 = i * 2 + 1
faces.append((v1, v2, v3, v4))
# 创建端面
end1_verts = list(range(0, segments * 2, 2))
if len(end1_verts) >= 3:
faces.append(end1_verts)
end2_verts = list(range(1, segments * 2, 2))
if len(end2_verts) >= 3:
faces.append(end2_verts)
# 【修复】安全的网格创建
try:
mesh.from_pydata(vertices, [], faces)
mesh.update()
except Exception as e:
logger.error(f"创建网格数据失败: {e}")
bpy.data.meshes.remove(mesh)
return None
# 【修复】安全的对象创建
try:
cylinder_obj = bpy.data.objects.new(obj_name, mesh)
except Exception as e:
logger.error(f"创建对象失败: {e}")
bpy.data.meshes.remove(mesh)
return None
# 【修复】安全的场景链接
try:
bpy.context.scene.collection.objects.link(cylinder_obj)
except Exception as e:
logger.error(f"链接到场景失败: {e}")
bpy.data.objects.remove(cylinder_obj)
bpy.data.meshes.remove(mesh)
return None
# 【修复】安全的父对象设置
try:
if machining and machining.name in bpy.data.objects:
cylinder_obj.parent = machining
except Exception as e:
logger.warning(f"设置父对象失败: {e}")
# 【修复】安全的属性设置
try:
cylinder_obj["sw_typ"] = "work"
cylinder_obj["sw_special"] = 0
except Exception as e:
logger.warning(f"设置属性失败: {e}")
# 【修复】强制更新对象
try:
cylinder_obj.update_tag()
bpy.context.view_layer.update()
except:
pass
return cylinder_obj
except Exception as e:
logger.error(f"创建超安全圆柱体失败: {e}")
return None
def _create_simple_surface(self, machining, item, index):
"""创建简单的表面几何体 - 修复版本"""
try:
surf = item.get("surf", {})
segs = surf.get("segs", [])
if not segs:
return None
# 【修复】使用时间戳确保唯一命名
import time
timestamp = int(time.time() * 1000) % 100000
unique_id = f"{machining.name}_{index}_{timestamp}"
mesh_name = f"SurfaceMesh_{unique_id}"
obj_name = f"Surface_{unique_id}"
# 【修复】检查名称是否已存在
if mesh_name in bpy.data.meshes:
bpy.data.meshes.remove(bpy.data.meshes[mesh_name])
if obj_name in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects[obj_name])
# 创建网格数据
mesh = bpy.data.meshes.new(mesh_name)
# 【修复】使用更安全的方法创建平面
try:
# 使用bmesh创建平面
bm = bmesh.new()
# 使用正确的操作符名称
bmesh.ops.create_grid(
bm,
x_segments=1,
y_segments=1,
size=0.1
)
# 转换为网格
bm.to_mesh(mesh)
mesh.update()
bm.free()
except Exception as e:
logger.error(f"创建表面网格失败: {e}")
bpy.data.meshes.remove(mesh)
return None
# 【修复】安全的对象创建
try:
surface_obj = bpy.data.objects.new(obj_name, mesh)
bpy.context.scene.collection.objects.link(surface_obj)
except Exception as e:
logger.error(f"创建表面对象失败: {e}")
bpy.data.meshes.remove(mesh)
return None
# 【修复】安全的父对象设置
try:
if machining and machining.name in bpy.data.objects:
surface_obj.parent = machining
except Exception as e:
logger.warning(f"设置父对象失败: {e}")
# 【修复】安全的属性设置
try:
surface_obj["sw_typ"] = "work"
surface_obj["sw_special"] = 0
except Exception as e:
logger.warning(f"设置属性失败: {e}")
return surface_obj
except Exception as e:
logger.error(f"创建简单表面失败: {e}")
return None
def c0a(self, data: Dict[str, Any]):
"""del_machining - 删除加工 - 最终版本,处理已删除对象的引用问题"""
try:
logger.info("🗑️ 执行c0a命令: 删除加工")
uid = data.get("uid")
typ = data.get("typ") # type is unit or source
oid = data.get("oid")
special = data.get("special", 1)
logger.info(
f"🗑️ 删除加工参数: uid={uid}, typ={typ}, oid={oid}, special={special}")
# 获取加工数据
machinings = self.data_manager.get_machinings(uid)
if not machinings:
logger.info(f"未找到单元 {uid} 的加工数据")
return 0
logger.info(f"🔍 找到 {len(machinings)} 个加工对象")
deleted_count = 0
# 【修复】使用更安全的删除策略:先收集要删除的对象名称,再批量删除
objects_to_delete = []
# 第一步:收集要删除的对象名称
for entity in machinings:
try:
# 检查对象是否有效
if not self._is_object_valid(entity):
continue
# 条件1: typ匹配
typ_match = False
if typ == "uid":
typ_match = True
else:
# 尝试获取属性如果不存在则返回None
try:
entity_attr = entity.get("sw_" + typ, None)
typ_match = (entity_attr == oid)
except Exception as e:
logger.debug(f"获取对象属性失败: {e}")
continue
# 条件2: special匹配
special_match = False
if special == 1:
special_match = True
else: # special == 0
# 获取sw_special属性如果不存在则默认为0
try:
entity_special = entity.get("sw_special", 0)
special_match = (entity_special == 0)
except Exception as e:
logger.debug(f"获取special属性失败: {e}")
continue
# 如果两个条件都满足,添加到删除列表
if typ_match and special_match:
logger.info(f"🗑️ 标记删除加工对象: {entity.name}")
objects_to_delete.append(entity.name)
except Exception as e:
logger.debug(f"处理加工对象时出错: {e}")
continue
# 第二步:批量删除收集到的对象
logger.info(f"🔍 开始删除 {len(objects_to_delete)} 个对象")
for obj_name in objects_to_delete:
try:
# 使用名称查找对象
if obj_name in bpy.data.objects:
obj = bpy.data.objects[obj_name]
if self._is_object_valid(obj):
if self._delete_object_safe(obj):
deleted_count += 1
else:
logger.debug(f"删除对象失败: {obj_name}")
else:
logger.debug(f"对象已无效: {obj_name}")
# 如果对象无效,检查是否已经被删除
if obj_name not in bpy.data.objects:
deleted_count += 1
else:
logger.debug(f"对象不在Blender数据中: {obj_name}")
# 如果对象不在数据中,认为已经被删除
deleted_count += 1
except Exception as e:
logger.debug(f"删除对象时出错: {e}")
# 检查对象是否已经被删除
try:
if obj_name not in bpy.data.objects:
deleted_count += 1
except:
pass
continue
# 【修复】按照Ruby版本最后清理已删除的对象
self.data_manager.cleanup_machinings(uid)
logger.info(f"✅ 删除加工完成: {deleted_count} 个对象")
return deleted_count
except Exception as e:
logger.error(f"c0a命令执行失败: {e}")
return 0
# ==================== 核心实现方法 ====================
def _create_machining_object(self, uid):
"""为本次加工批次创建一个空的父对象并注册到data_manager - 修复版本"""
try:
import bpy
# 检查Blender可用性
if not BLENDER_AVAILABLE:
logger.error("Blender不可用无法创建加工对象")
return None
# 检查uid是否有效
if not uid:
logger.error("无效的uid")
return None
name = f"Machining_{uid}"
# 检查是否已存在同名对象
if name in bpy.data.objects:
logger.info(f"加工对象已存在: {name}")
return bpy.data.objects[name]
# 创建空对象
try:
obj = bpy.data.objects.new(name, None)
except Exception as e:
logger.error(f"创建对象失败: {e}")
return None
# 检查对象是否创建成功
if not obj:
logger.error("对象创建失败")
return None
# 链接到场景
try:
if hasattr(bpy.context, 'scene') and bpy.context.scene:
bpy.context.scene.collection.objects.link(obj)
else:
logger.error("无法获取场景")
bpy.data.objects.remove(obj)
return None
except Exception as e:
logger.error(f"链接对象到场景失败: {e}")
bpy.data.objects.remove(obj)
return None
# 设置属性
try:
obj["sw_typ"] = "work"
except Exception as e:
logger.warning(f"设置对象属性失败: {e}")
# 添加到数据管理器
try:
self.data_manager.add_machining(uid, obj)
except Exception as e:
logger.warning(f"添加到数据管理器失败: {e}")
logger.info(f"✅ 创建加工对象成功: {name}")
return obj
except Exception as e:
logger.error(f"创建加工对象异常: {e}")
return None
def _parse_point3d(self, point_str):
"""解析3D点字符串 - 修复单位转换"""
try:
# 移除括号和空格
point_str = point_str.strip("()").replace(" ", "")
coords = point_str.split(",")
if len(coords) >= 3:
# 【修复】单位转换:毫米转米
x = float(coords[0]) * 0.001 # mm -> m
y = float(coords[1]) * 0.001 # mm -> m
z = float(coords[2]) * 0.001 # mm -> m
return [x, y, z]
else:
return [0.0, 0.0, 0.0]
except Exception as e:
logger.error(f"解析3D点失败: {e}")
return [0.0, 0.0, 0.0]
def _parse_surface_vertices(self, surface):
"""解析表面顶点"""
try:
# 简化的表面解析
vertices = []
# 这里应该根据实际的表面数据格式解析
return vertices
except Exception as e:
logger.error(f"解析表面顶点失败: {e}")
return []
def _set_machining_color(self, machining, item):
"""设置加工颜色"""
try:
# 获取加工材质
material = material_manager.get_texture("mat_machining")
if material and hasattr(machining, 'data') and machining.data:
if not machining.data.materials:
machining.data.materials.append(material)
else:
machining.data.materials[0] = material
except Exception as e:
logger.error(f"设置加工颜色失败: {e}")
def _is_object_valid(self, obj) -> bool:
"""检查对象是否有效 - 增强版本"""
try:
if not obj:
return False
if not BLENDER_AVAILABLE:
return True
# 【修复】更强的有效性检查
try:
# 检查对象是否有name属性
if not hasattr(obj, 'name'):
return False
# 检查name是否为空
if not obj.name:
return False
# 检查对象是否在Blender数据中
if obj.name not in bpy.data.objects:
return False
# 尝试访问对象属性来验证其有效性
test_name = obj.name
return True
except Exception as e:
logger.debug(f"对象有效性检查失败: {e}")
return False
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
# 【修复】更强的对象有效性检查
try:
# 检查对象是否仍然存在于Blender数据中
if obj.name not in bpy.data.objects:
logger.debug(f"对象 {obj.name} 已不在Blender数据中")
return True # 如果对象已经不在数据中,认为删除成功
# 检查对象是否仍然有效(没有被删除)
if not hasattr(obj, 'name') or not obj.name:
logger.debug("对象已无效没有name属性")
return True # 如果对象已经无效,认为删除成功
except Exception as e:
logger.debug(f"对象有效性检查失败: {e}")
return True # 如果检查失败,认为对象已经被删除
# 【修复】使用更安全的删除策略
try:
# 先收集所有子对象(使用名称而不是对象引用)
children_names = []
try:
if hasattr(obj, 'children'):
for child in obj.children:
try:
if child and hasattr(child, 'name') and child.name in bpy.data.objects:
children_names.append(child.name)
except Exception as e:
logger.debug(f"检查子对象时出错: {e}")
continue
except Exception as e:
logger.debug(f"获取子对象列表失败: {e}")
# 先删除子对象(使用名称查找)
for child_name in children_names:
try:
if child_name in bpy.data.objects:
child_obj = bpy.data.objects[child_name]
# 再次检查对象是否有效
if hasattr(child_obj, 'name') and child_obj.name == child_name:
bpy.data.objects.remove(
child_obj, do_unlink=True)
logger.debug(f"删除子对象: {child_name}")
except Exception as e:
logger.debug(f"删除子对象 {child_name} 失败: {e}")
# 再删除父对象
try:
# 最终检查对象是否仍然有效
if obj.name in bpy.data.objects:
# 再次验证对象引用是否有效
try:
if hasattr(obj, 'name') and obj.name in bpy.data.objects:
bpy.data.objects.remove(obj, do_unlink=True)
logger.debug(f"删除父对象: {obj.name}")
return True
else:
logger.debug(f"父对象 {obj.name} 在删除前已无效")
return True # 对象已经无效,认为删除成功
except Exception as e:
logger.debug(f"删除父对象时出错: {e}")
# 检查对象是否已经被删除
if obj.name not in bpy.data.objects:
logger.debug(f"父对象 {obj.name} 已被删除")
return True
return False
else:
logger.debug(f"父对象 {obj.name} 已不在Blender数据中")
return True # 对象已经不在数据中,认为删除成功
except Exception as e:
logger.debug(f"删除父对象过程中出错: {e}")
# 检查对象是否已经被删除
try:
if obj.name not in bpy.data.objects:
logger.debug(f"父对象 {obj.name} 已被删除")
return True
except:
pass
return False
except Exception as e:
logger.debug(f"删除对象过程中出错: {e}")
# 检查对象是否已经被删除
try:
if obj.name not in bpy.data.objects:
logger.debug(f"对象 {obj.name} 已被删除")
return True
except:
pass
return False
except Exception as e:
logger.debug(f"删除对象失败: {e}")
# 最后检查对象是否已经被删除
try:
if obj.name not in bpy.data.objects:
logger.debug(f"对象 {obj.name} 已被删除")
return True
except:
pass
return False
def _get_parts(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""获取部件数据 - 使用data_manager"""
return self.data_manager.get_parts(data)
def _get_hardwares(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""获取硬件数据 - 使用data_manager"""
return self.data_manager.get_hardwares(data)
# ==================== 统计和管理方法 ====================
def get_machining_stats(self) -> Dict[str, Any]:
"""获取加工统计信息"""
try:
total_machinings = sum(len(self.data_manager.get_machinings(uid))
for uid in self.data_manager.machinings.keys())
stats = {
"total_units": len(self.data_manager.machinings),
"total_machinings": total_machinings,
"creation_stats": self.machining_stats.copy(),
"memory_usage": {
"machinings_dict_size": len(self.data_manager.machinings),
}
}
if BLENDER_AVAILABLE:
stats["blender_objects"] = len([obj for obj in bpy.data.objects
if obj.get("sw_typ") == "work"])
return stats
except Exception as e:
logger.error(f"获取加工统计失败: {e}")
return {"error": str(e)}
def get_creation_stats(self) -> Dict[str, Any]:
"""获取创建统计信息"""
return self.machining_stats.copy()
def reset_creation_stats(self):
"""重置创建统计"""
self.machining_stats = {
"machinings_created": 0,
"trim_operations": 0,
"creation_errors": 0
}
logger.info("加工统计已重置")
def cleanup(self):
"""清理加工管理器"""
try:
# 清理所有加工数据
total_deleted = 0
for uid in list(self.data_manager.machinings.keys()):
machinings = self.data_manager.get_machinings(uid)
for machining in machinings:
if self._delete_object_safe(machining):
total_deleted += 1
self.data_manager.clear_machinings(uid)
self.reset_creation_stats()
logger.info(f"✅ 加工管理器清理完成,删除了 {total_deleted} 个对象")
except Exception as e:
logger.error(f"清理加工管理器失败: {e}")
# ==================== 全局加工管理器实例 ====================
# 全局实例
machining_manager = None
def init_machining_manager():
"""初始化全局加工管理器实例 - 不再需要suw_impl参数"""
global machining_manager
machining_manager = MachiningManager()
return machining_manager
def get_machining_manager():
"""获取全局加工管理器实例"""
global machining_manager
if machining_manager is None:
machining_manager = init_machining_manager()
return machining_manager