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