blenderpython/suw_core/machining_manager.py

1170 lines
44 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SUW Core - 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