5364 lines
188 KiB
Python
5364 lines
188 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
SUW Implementation - Python翻译版本
|
||
原文件: SUWImpl.rb (2019行)
|
||
用途: 核心实现类,SUWood的主要功能
|
||
|
||
翻译进度: 完整实现 - 对应Ruby版本所有功能
|
||
内存管理优化: 应用 Blender Python API 最佳实践
|
||
"""
|
||
|
||
import re
|
||
import math
|
||
import logging
|
||
import time
|
||
import gc
|
||
import weakref
|
||
import threading
|
||
import queue
|
||
from typing import Optional, Any, Dict, List, Tuple, Union, Callable
|
||
from contextlib import contextmanager
|
||
|
||
# 设置日志
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 尝试相对导入,失败则使用绝对导入
|
||
try:
|
||
from .suw_constants import SUWood
|
||
except ImportError:
|
||
try:
|
||
from suw_constants import SUWood
|
||
except ImportError:
|
||
# 如果都找不到,创建一个基本的存根
|
||
class SUWood:
|
||
@staticmethod
|
||
def suwood_path(version):
|
||
return "."
|
||
|
||
try:
|
||
import bpy
|
||
import mathutils
|
||
import bmesh
|
||
BLENDER_AVAILABLE = True
|
||
except ImportError:
|
||
BLENDER_AVAILABLE = False
|
||
print("⚠️ Blender API 不可用,使用基础几何类")
|
||
# 创建存根mathutils模块
|
||
|
||
class MockMathutils:
|
||
class Vector:
|
||
def __init__(self, vec):
|
||
self.x, self.y, self.z = vec[:3] if len(
|
||
vec) >= 3 else (vec + [0, 0])[:3]
|
||
|
||
def normalized(self):
|
||
return self
|
||
|
||
def dot(self, other):
|
||
return 0
|
||
|
||
class Matrix:
|
||
@staticmethod
|
||
def Scale(scale, size, axis):
|
||
return MockMathutils.Matrix()
|
||
|
||
@staticmethod
|
||
def Translation(vec):
|
||
return MockMathutils.Matrix()
|
||
|
||
@staticmethod
|
||
def Rotation(angle, size):
|
||
return MockMathutils.Matrix()
|
||
|
||
def __matmul__(self, other):
|
||
return MockMathutils.Matrix()
|
||
|
||
mathutils = MockMathutils()
|
||
|
||
# ==================== 内存管理核心类 ====================
|
||
|
||
|
||
class BlenderMemoryManager:
|
||
"""Blender内存管理器 - 修复弱引用问题"""
|
||
|
||
def __init__(self):
|
||
# 改用普通集合和字典来跟踪对象,而不是弱引用
|
||
self.tracked_objects = set() # 存储对象名称而不是对象本身
|
||
self.tracked_meshes = set() # 存储网格名称
|
||
self.tracked_images = set() # 存储图像名称
|
||
self.tracked_materials = set() # 存储材质名称
|
||
self.tracked_collections = set() # 存储集合名称
|
||
self.cleanup_interval = 100
|
||
self.operation_count = 0
|
||
self.last_cleanup = time.time()
|
||
self.max_memory_mb = 2048
|
||
self._cleanup_lock = threading.Lock()
|
||
|
||
def register_object(self, obj):
|
||
"""注册对象到内存管理器 - 修复版本"""
|
||
if obj is None or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
try:
|
||
with self._cleanup_lock:
|
||
# 根据对象类型分别处理
|
||
if hasattr(obj, 'name'):
|
||
obj_name = obj.name
|
||
|
||
# 根据对象类型存储到不同的集合
|
||
if hasattr(obj, 'type'): # Blender Object
|
||
self.tracked_objects.add(obj_name)
|
||
elif str(type(obj)).find('Material') != -1: # Material
|
||
self.tracked_materials.add(obj_name)
|
||
elif str(type(obj)).find('Mesh') != -1: # Mesh
|
||
self.tracked_meshes.add(obj_name)
|
||
elif str(type(obj)).find('Image') != -1: # Image
|
||
self.tracked_images.add(obj_name)
|
||
elif str(type(obj)).find('Collection') != -1: # Collection
|
||
self.tracked_collections.add(obj_name)
|
||
else:
|
||
self.tracked_objects.add(obj_name)
|
||
|
||
self.operation_count += 1
|
||
|
||
# 定期清理
|
||
if self.should_cleanup():
|
||
self.cleanup_orphaned_data()
|
||
|
||
except Exception as e:
|
||
# 静默处理,不输出错误日志
|
||
pass
|
||
|
||
def register_mesh(self, mesh):
|
||
"""注册网格到内存管理器 - 修复版本"""
|
||
if mesh is None or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
try:
|
||
with self._cleanup_lock:
|
||
if hasattr(mesh, 'name'):
|
||
self.tracked_meshes.add(mesh.name)
|
||
self.operation_count += 1
|
||
except Exception as e:
|
||
# 静默处理
|
||
pass
|
||
|
||
def register_image(self, image):
|
||
"""注册图像到内存管理器 - 修复版本"""
|
||
if image is None or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
try:
|
||
with self._cleanup_lock:
|
||
if hasattr(image, 'name'):
|
||
self.tracked_images.add(image.name)
|
||
self.operation_count += 1
|
||
except Exception as e:
|
||
# 静默处理
|
||
pass
|
||
|
||
def should_cleanup(self):
|
||
"""检查是否需要清理"""
|
||
return (self.operation_count >= self.cleanup_interval or
|
||
time.time() - self.last_cleanup > 300) # 5分钟强制清理
|
||
|
||
def cleanup_orphaned_data(self):
|
||
"""清理孤立数据 - 修复版本"""
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 确保在主线程中执行
|
||
if threading.current_thread() != threading.main_thread():
|
||
return
|
||
|
||
try:
|
||
with self._cleanup_lock:
|
||
current_time = time.time()
|
||
if current_time - self.last_cleanup < 30: # 30秒内不重复清理
|
||
return
|
||
|
||
logger.debug("🧹 开始清理孤立数据...")
|
||
|
||
cleanup_count = {
|
||
'meshes': 0,
|
||
'materials': 0,
|
||
'images': 0,
|
||
'collections': 0,
|
||
'objects': 0
|
||
}
|
||
|
||
# 清理孤立的网格
|
||
orphaned_meshes = []
|
||
for mesh in bpy.data.meshes:
|
||
if mesh.users == 0:
|
||
orphaned_meshes.append(mesh)
|
||
|
||
for mesh in orphaned_meshes:
|
||
try:
|
||
bpy.data.meshes.remove(mesh)
|
||
cleanup_count['meshes'] += 1
|
||
except:
|
||
pass
|
||
|
||
# 清理孤立的材质
|
||
orphaned_materials = []
|
||
for material in bpy.data.materials:
|
||
if material.users == 0:
|
||
orphaned_materials.append(material)
|
||
|
||
for material in orphaned_materials:
|
||
try:
|
||
bpy.data.materials.remove(material)
|
||
cleanup_count['materials'] += 1
|
||
except:
|
||
pass
|
||
|
||
# 清理孤立的图像
|
||
orphaned_images = []
|
||
for image in bpy.data.images:
|
||
if image.users == 0 and not image.is_dirty:
|
||
orphaned_images.append(image)
|
||
|
||
for image in orphaned_images:
|
||
try:
|
||
bpy.data.images.remove(image)
|
||
cleanup_count['images'] += 1
|
||
except:
|
||
pass
|
||
|
||
# 清理孤立的集合
|
||
orphaned_collections = []
|
||
for collection in bpy.data.collections:
|
||
if collection.users == 0:
|
||
orphaned_collections.append(collection)
|
||
|
||
for collection in orphaned_collections:
|
||
try:
|
||
bpy.data.collections.remove(collection)
|
||
cleanup_count['collections'] += 1
|
||
except:
|
||
pass
|
||
|
||
# 清理跟踪集合中的无效引用
|
||
self._cleanup_tracked_references()
|
||
|
||
# 强制垃圾回收
|
||
gc.collect()
|
||
|
||
self.last_cleanup = current_time
|
||
self.operation_count = 0
|
||
|
||
# 只在有实际清理时才输出日志
|
||
total_cleaned = sum(cleanup_count.values())
|
||
if total_cleaned > 0:
|
||
logger.info(f"✅ 清理完成: {cleanup_count}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理孤立数据失败: {e}")
|
||
|
||
def _cleanup_tracked_references(self):
|
||
"""清理跟踪集合中的无效引用"""
|
||
try:
|
||
# 清理无效的对象引用
|
||
valid_objects = set()
|
||
for obj_name in self.tracked_objects:
|
||
if obj_name in bpy.data.objects:
|
||
valid_objects.add(obj_name)
|
||
self.tracked_objects = valid_objects
|
||
|
||
# 清理无效的网格引用
|
||
valid_meshes = set()
|
||
for mesh_name in self.tracked_meshes:
|
||
if mesh_name in bpy.data.meshes:
|
||
valid_meshes.add(mesh_name)
|
||
self.tracked_meshes = valid_meshes
|
||
|
||
# 清理无效的材质引用
|
||
valid_materials = set()
|
||
for mat_name in self.tracked_materials:
|
||
if mat_name in bpy.data.materials:
|
||
valid_materials.add(mat_name)
|
||
self.tracked_materials = valid_materials
|
||
|
||
# 清理无效的图像引用
|
||
valid_images = set()
|
||
for img_name in self.tracked_images:
|
||
if img_name in bpy.data.images:
|
||
valid_images.add(img_name)
|
||
self.tracked_images = valid_images
|
||
|
||
# 清理无效的集合引用
|
||
valid_collections = set()
|
||
for col_name in self.tracked_collections:
|
||
if col_name in bpy.data.collections:
|
||
valid_collections.add(col_name)
|
||
self.tracked_collections = valid_collections
|
||
|
||
except Exception as e:
|
||
logger.warning(f"清理跟踪引用失败: {e}")
|
||
|
||
def get_memory_stats(self) -> Dict[str, int]:
|
||
"""获取内存统计信息"""
|
||
try:
|
||
with self._cleanup_lock:
|
||
return {
|
||
'tracked_objects': len(self.tracked_objects),
|
||
'tracked_meshes': len(self.tracked_meshes),
|
||
'tracked_materials': len(self.tracked_materials),
|
||
'tracked_images': len(self.tracked_images),
|
||
'tracked_collections': len(self.tracked_collections),
|
||
'operation_count': self.operation_count,
|
||
'blender_objects': len(bpy.data.objects) if BLENDER_AVAILABLE else 0,
|
||
'blender_meshes': len(bpy.data.meshes) if BLENDER_AVAILABLE else 0,
|
||
'blender_materials': len(bpy.data.materials) if BLENDER_AVAILABLE else 0,
|
||
'blender_images': len(bpy.data.images) if BLENDER_AVAILABLE else 0,
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"获取内存统计失败: {e}")
|
||
return {}
|
||
|
||
def force_cleanup(self):
|
||
"""强制清理"""
|
||
try:
|
||
with self._cleanup_lock:
|
||
self.last_cleanup = 0 # 重置时间以强制清理
|
||
self.cleanup_orphaned_data()
|
||
except Exception as e:
|
||
logger.error(f"强制清理失败: {e}")
|
||
|
||
|
||
# 全局内存管理器实例
|
||
memory_manager = BlenderMemoryManager()
|
||
|
||
# 全局主线程任务队列
|
||
_main_thread_queue = queue.Queue()
|
||
_main_thread_id = None
|
||
|
||
|
||
def init_main_thread():
|
||
"""初始化主线程ID"""
|
||
global _main_thread_id
|
||
_main_thread_id = threading.current_thread().ident
|
||
|
||
|
||
def execute_in_main_thread(func: Callable, *args, **kwargs) -> Any:
|
||
"""在主线程中执行函数 - 增加超时时间"""
|
||
global _main_thread_queue, _main_thread_id
|
||
|
||
# 如果已经在主线程中,直接执行
|
||
if threading.current_thread().ident == _main_thread_id:
|
||
return func(*args, **kwargs)
|
||
|
||
# 否则,将任务放入队列
|
||
result_queue = queue.Queue()
|
||
exception_queue = queue.Queue()
|
||
|
||
def wrapper():
|
||
try:
|
||
result = func(*args, **kwargs)
|
||
result_queue.put(result)
|
||
except Exception as e:
|
||
exception_queue.put(e)
|
||
|
||
_main_thread_queue.put(wrapper)
|
||
|
||
# 等待结果,增加超时时间到60秒
|
||
timeout = 60
|
||
start_time = time.time()
|
||
|
||
while time.time() - start_time < timeout:
|
||
try:
|
||
if not exception_queue.empty():
|
||
raise exception_queue.get_nowait()
|
||
if not result_queue.empty():
|
||
return result_queue.get_nowait()
|
||
time.sleep(0.01)
|
||
except queue.Empty:
|
||
continue
|
||
|
||
raise TimeoutError("主线程执行超时")
|
||
|
||
|
||
def process_main_thread_tasks():
|
||
"""处理主线程任务队列 - 应该在主线程中定期调用"""
|
||
global _main_thread_queue
|
||
|
||
try:
|
||
while not _main_thread_queue.empty():
|
||
task = _main_thread_queue.get_nowait()
|
||
task()
|
||
except queue.Empty:
|
||
pass
|
||
|
||
|
||
@contextmanager
|
||
def safe_blender_operation(operation_name: str):
|
||
"""线程安全的Blender操作上下文管理器 - 修复版本"""
|
||
if not BLENDER_AVAILABLE:
|
||
logger.warning(f"Blender不可用,跳过操作: {operation_name}")
|
||
yield
|
||
return
|
||
|
||
start_time = time.time()
|
||
logger.debug(f"🔄 开始操作: {operation_name}")
|
||
|
||
# 保存当前状态
|
||
original_mode = None
|
||
original_selection = []
|
||
original_active = None
|
||
|
||
def _execute_operation():
|
||
nonlocal original_mode, original_selection, original_active
|
||
|
||
try:
|
||
# 确保在对象模式下
|
||
if hasattr(bpy.context, 'mode') and bpy.context.mode != 'OBJECT':
|
||
original_mode = bpy.context.mode
|
||
bpy.ops.object.mode_set(mode='OBJECT')
|
||
|
||
# 保存当前选择和活动对象
|
||
if hasattr(bpy.context, 'selected_objects'):
|
||
original_selection = list(bpy.context.selected_objects)
|
||
if hasattr(bpy.context, 'active_object'):
|
||
original_active = bpy.context.active_object
|
||
|
||
# 清除选择以避免冲突
|
||
bpy.ops.object.select_all(action='DESELECT')
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"准备操作失败: {e}")
|
||
return False
|
||
|
||
def _cleanup_operation():
|
||
try:
|
||
# 尝试恢复原始状态
|
||
bpy.ops.object.select_all(action='DESELECT')
|
||
for obj in original_selection:
|
||
if obj and obj.name in bpy.data.objects:
|
||
obj.select_set(True)
|
||
|
||
# 恢复活动对象
|
||
if original_active and original_active.name in bpy.data.objects:
|
||
bpy.context.view_layer.objects.active = original_active
|
||
|
||
# 恢复模式
|
||
if original_mode and original_mode != 'OBJECT':
|
||
bpy.ops.object.mode_set(mode=original_mode)
|
||
|
||
except Exception as restore_error:
|
||
logger.warning(f"恢复状态失败: {restore_error}")
|
||
|
||
try:
|
||
# 如果不在主线程,使用主线程执行准备操作
|
||
if threading.current_thread().ident != _main_thread_id:
|
||
success = execute_in_main_thread(_execute_operation)
|
||
if not success:
|
||
raise RuntimeError("准备操作失败")
|
||
else:
|
||
success = _execute_operation()
|
||
if not success:
|
||
raise RuntimeError("准备操作失败")
|
||
|
||
# 执行用户操作
|
||
yield
|
||
|
||
elapsed_time = time.time() - start_time
|
||
if elapsed_time > 5.0:
|
||
logger.warning(f"操作耗时过长: {operation_name} ({elapsed_time:.2f}s)")
|
||
else:
|
||
logger.debug(f"✅ 操作完成: {operation_name} ({elapsed_time:.2f}s)")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 操作失败: {operation_name} - {e}")
|
||
raise
|
||
|
||
finally:
|
||
# 清理操作也需要在主线程中执行
|
||
if threading.current_thread().ident != _main_thread_id:
|
||
try:
|
||
execute_in_main_thread(_cleanup_operation)
|
||
except:
|
||
pass
|
||
else:
|
||
_cleanup_operation()
|
||
|
||
# ==================== 几何类扩展 ====================
|
||
|
||
|
||
class Point3d:
|
||
"""3D点类 - 对应Ruby的Geom::Point3d"""
|
||
|
||
def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0):
|
||
self.x = x
|
||
self.y = y
|
||
self.z = z
|
||
|
||
@classmethod
|
||
def parse(cls, value: str):
|
||
"""从字符串解析3D点"""
|
||
if not value or value.strip() == "":
|
||
return None
|
||
|
||
# 解析格式: "(x,y,z)" 或 "x,y,z"
|
||
clean_value = re.sub(r'[()]*', '', value)
|
||
xyz = [float(axis.strip()) for axis in clean_value.split(',')]
|
||
|
||
# 转换mm为内部单位(假设输入是mm)
|
||
return cls(xyz[0] * 0.001, xyz[1] * 0.001, xyz[2] * 0.001)
|
||
|
||
def to_s(self, unit: str = "mm", digits: int = -1) -> str:
|
||
"""转换为字符串"""
|
||
if unit == "cm":
|
||
x_val = self.x * 100 # 内部单位转换为cm
|
||
y_val = self.y * 100
|
||
z_val = self.z * 100
|
||
return f"({x_val:.3f}, {y_val:.3f}, {z_val:.3f})"
|
||
else: # mm
|
||
x_val = self.x * 1000 # 内部单位转换为mm
|
||
y_val = self.y * 1000
|
||
z_val = self.z * 1000
|
||
|
||
if digits == -1:
|
||
return f"({x_val}, {y_val}, {z_val})"
|
||
else:
|
||
return f"({x_val:.{digits}f}, {y_val:.{digits}f}, {z_val:.{digits}f})"
|
||
|
||
def __str__(self):
|
||
return self.to_s()
|
||
|
||
def __repr__(self):
|
||
return f"Point3d({self.x}, {self.y}, {self.z})"
|
||
|
||
|
||
class Vector3d:
|
||
"""3D向量类 - 对应Ruby的Geom::Vector3d"""
|
||
|
||
def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0):
|
||
self.x = x
|
||
self.y = y
|
||
self.z = z
|
||
|
||
@classmethod
|
||
def parse(cls, value: str):
|
||
"""从字符串解析3D向量"""
|
||
if not value or value.strip() == "":
|
||
return None
|
||
|
||
clean_value = re.sub(r'[()]*', '', value)
|
||
xyz = [float(axis.strip()) for axis in clean_value.split(',')]
|
||
|
||
return cls(xyz[0] * 0.001, xyz[1] * 0.001, xyz[2] * 0.001)
|
||
|
||
def to_s(self, unit: str = "mm") -> str:
|
||
"""转换为字符串"""
|
||
if unit == "cm":
|
||
x_val = self.x * 100 # 内部单位转换为cm
|
||
y_val = self.y * 100
|
||
z_val = self.z * 100
|
||
return f"({x_val:.3f}, {y_val:.3f}, {z_val:.3f})"
|
||
elif unit == "in":
|
||
return f"({self.x}, {self.y}, {self.z})"
|
||
else: # mm
|
||
x_val = self.x * 1000 # 内部单位转换为mm
|
||
y_val = self.y * 1000
|
||
z_val = self.z * 1000
|
||
return f"({x_val}, {y_val}, {z_val})"
|
||
|
||
def normalize(self):
|
||
"""归一化向量"""
|
||
length = math.sqrt(self.x**2 + self.y**2 + self.z**2)
|
||
if length > 0:
|
||
return Vector3d(self.x/length, self.y/length, self.z/length)
|
||
return Vector3d(0, 0, 0)
|
||
|
||
def __str__(self):
|
||
return self.to_s()
|
||
|
||
|
||
class Transformation:
|
||
"""变换矩阵类 - 对应Ruby的Geom::Transformation"""
|
||
|
||
def __init__(self, origin: Point3d = None, x_axis: Vector3d = None,
|
||
y_axis: Vector3d = None, z_axis: Vector3d = None):
|
||
self.origin = origin or Point3d(0, 0, 0)
|
||
self.x_axis = x_axis or Vector3d(1, 0, 0)
|
||
self.y_axis = y_axis or Vector3d(0, 1, 0)
|
||
self.z_axis = z_axis or Vector3d(0, 0, 1)
|
||
|
||
@classmethod
|
||
def parse(cls, data: Dict[str, str]):
|
||
"""从字典解析变换"""
|
||
origin = Point3d.parse(data.get("o"))
|
||
x_axis = Vector3d.parse(data.get("x"))
|
||
y_axis = Vector3d.parse(data.get("y"))
|
||
z_axis = Vector3d.parse(data.get("z"))
|
||
|
||
return cls(origin, x_axis, y_axis, z_axis)
|
||
|
||
def store(self, data: Dict[str, str]):
|
||
"""存储变换到字典"""
|
||
data["o"] = self.origin.to_s("mm")
|
||
data["x"] = self.x_axis.to_s("in")
|
||
data["y"] = self.y_axis.to_s("in")
|
||
data["z"] = self.z_axis.to_s("in")
|
||
|
||
# ==================== SUWood 材质类型常量 ====================
|
||
|
||
|
||
MAT_TYPE_NORMAL = 0
|
||
MAT_TYPE_OBVERSE = 1
|
||
MAT_TYPE_NATURE = 2
|
||
|
||
# ==================== SUWImpl 核心实现类 ====================
|
||
|
||
|
||
class SUWImpl:
|
||
"""SUWood核心实现类 - 完整翻译版本,应用内存管理最佳实践"""
|
||
|
||
_instance = None
|
||
_selected_uid = None
|
||
_selected_obj = None
|
||
_selected_zone = None
|
||
_selected_part = None
|
||
_scaled_zone = None
|
||
_server_path = None
|
||
_default_zone = None
|
||
_creation_lock = False
|
||
_mesh_creation_count = 0
|
||
_batch_operation_active = False
|
||
|
||
def __init__(self):
|
||
"""初始化SUWImpl实例"""
|
||
# 基础属性
|
||
self.zones = {}
|
||
self.parts = {}
|
||
self.hardwares = {}
|
||
self.machinings = {}
|
||
self.dimensions = {}
|
||
self.textures = {}
|
||
self.unit_param = {}
|
||
self.unit_trans = {}
|
||
|
||
# 内存管理相关
|
||
self.object_references = {} # 存储对象名称而非引用
|
||
self.mesh_cache = {}
|
||
self.material_cache = {}
|
||
|
||
# 批量操作优化
|
||
self.deferred_updates = []
|
||
self.batch_size = 50
|
||
|
||
# 状态管理
|
||
self.added_contour = False
|
||
self.part_mode = False
|
||
self.hide_none = False
|
||
self.mat_type = MAT_TYPE_NORMAL
|
||
self.selected_faces = []
|
||
self.selected_parts = []
|
||
self.selected_hws = []
|
||
self.menu_handle = 0
|
||
self.back_material = False
|
||
|
||
# 图层管理
|
||
self.door_layer = None
|
||
self.drawer_layer = None
|
||
self.labels = None
|
||
self.door_labels = None
|
||
|
||
logger.info("SUWImpl 初始化完成,启用内存管理优化")
|
||
|
||
@classmethod
|
||
def get_instance(cls):
|
||
"""获取单例实例"""
|
||
if cls._instance is None:
|
||
cls._instance = cls()
|
||
return cls._instance
|
||
|
||
def startup(self):
|
||
"""启动SUW实现"""
|
||
try:
|
||
# 初始化主线程
|
||
init_main_thread()
|
||
|
||
logger.info("🚀 启动SUW实现...")
|
||
|
||
if BLENDER_AVAILABLE:
|
||
self._create_layers()
|
||
self._init_materials()
|
||
self._init_default_zone()
|
||
|
||
# 启动主线程任务处理器
|
||
self._start_main_thread_processor()
|
||
|
||
logger.info("✅ SUW实现启动完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"启动SUW实现失败: {e}")
|
||
|
||
def _start_main_thread_processor(self):
|
||
"""启动主线程任务处理器"""
|
||
try:
|
||
# 使用Blender的定时器来处理主线程任务
|
||
if hasattr(bpy.app, 'timers'):
|
||
def process_tasks():
|
||
process_main_thread_tasks()
|
||
return 0.01 # 每10毫秒处理一次
|
||
|
||
bpy.app.timers.register(process_tasks, persistent=True)
|
||
logger.debug("主线程任务处理器已启动")
|
||
except Exception as e:
|
||
logger.warning(f"启动主线程任务处理器失败: {e}")
|
||
|
||
def _create_layers(self):
|
||
"""创建集合系统(在Blender 2.8+中创建集合) - 修复版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
logger.debug("创建集合系统...")
|
||
|
||
# 创建门板集合(默认可见)
|
||
door_collection_name = "DOOR_LAYER"
|
||
if door_collection_name not in bpy.data.collections:
|
||
door_collection = bpy.data.collections.new(
|
||
door_collection_name)
|
||
bpy.context.scene.collection.children.link(door_collection)
|
||
# 修改:默认显示门板集合
|
||
door_collection.hide_viewport = False
|
||
door_collection.hide_render = False
|
||
logger.debug("门板集合已创建(可见)")
|
||
|
||
# 创建抽屉集合(默认可见)
|
||
drawer_collection_name = "DRAWER_LAYER"
|
||
if drawer_collection_name not in bpy.data.collections:
|
||
drawer_collection = bpy.data.collections.new(
|
||
drawer_collection_name)
|
||
bpy.context.scene.collection.children.link(drawer_collection)
|
||
# 修改:默认显示抽屉集合
|
||
drawer_collection.hide_viewport = False
|
||
drawer_collection.hide_render = False
|
||
logger.debug("抽屉集合已创建(可见)")
|
||
|
||
logger.debug("集合系统创建完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建集合系统失败: {e}")
|
||
|
||
def _init_materials(self):
|
||
"""初始化材质 - 减少注册调用"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
logger.debug("初始化材质...")
|
||
|
||
# 创建基础材质
|
||
materials_to_create = [
|
||
("mat_default", (0.8, 0.8, 0.8, 1.0)),
|
||
("mat_select", (1.0, 0.5, 0.0, 1.0)),
|
||
("mat_normal", (0.7, 0.7, 0.7, 1.0)),
|
||
("mat_obverse", (0.9, 0.9, 0.9, 1.0)),
|
||
("mat_reverse", (0.6, 0.6, 0.6, 1.0)),
|
||
("mat_thin", (0.5, 0.5, 0.5, 1.0)),
|
||
]
|
||
|
||
for mat_name, color in materials_to_create:
|
||
if mat_name not in bpy.data.materials:
|
||
material = bpy.data.materials.new(name=mat_name)
|
||
material.use_nodes = True
|
||
|
||
# 设置基础颜色
|
||
if material.node_tree:
|
||
principled = material.node_tree.nodes.get(
|
||
"Principled BSDF")
|
||
if principled:
|
||
principled.inputs['Base Color'].default_value = color
|
||
|
||
# 只注册一次
|
||
memory_manager.register_object(material)
|
||
self.textures[mat_name] = material
|
||
else:
|
||
# 如果材质已存在,直接使用
|
||
self.textures[mat_name] = bpy.data.materials[mat_name]
|
||
|
||
except Exception as e:
|
||
logger.error(f"初始化材质失败: {e}")
|
||
|
||
def _init_default_zone(self):
|
||
"""初始化默认区域"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
logger.debug("初始化默认区域...")
|
||
|
||
# 创建默认区域模板
|
||
self._default_zone = bpy.data.objects.new("DefaultZone", None)
|
||
# 不添加到场景中,只作为模板使用
|
||
|
||
# 设置默认属性
|
||
self._default_zone["sw_typ"] = "zid"
|
||
self._default_zone.hide_viewport = True
|
||
|
||
memory_manager.register_object(self._default_zone)
|
||
|
||
logger.debug("默认区域初始化完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"初始化默认区域失败: {e}")
|
||
|
||
# ==================== 材质管理方法 ====================
|
||
|
||
def add_mat_rgb(self, mat_id: str, alpha: float, r: int, g: int, b: int):
|
||
"""添加RGB材质"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 检查材质是否已存在
|
||
if mat_id in self.material_cache:
|
||
material_name = self.material_cache[mat_id]
|
||
if material_name in bpy.data.materials:
|
||
return bpy.data.materials[material_name]
|
||
|
||
# 创建新材质
|
||
material = bpy.data.materials.new(mat_id)
|
||
material.use_nodes = True
|
||
|
||
# 设置颜色
|
||
if material.node_tree:
|
||
principled = material.node_tree.nodes.get("Principled BSDF")
|
||
if principled:
|
||
color = (r/255.0, g/255.0, b/255.0, alpha)
|
||
principled.inputs[0].default_value = color
|
||
|
||
# 设置透明度
|
||
if alpha < 1.0:
|
||
material.blend_method = 'BLEND'
|
||
# Alpha input
|
||
principled.inputs[21].default_value = alpha
|
||
|
||
# 缓存材质
|
||
self.material_cache[mat_id] = material.name
|
||
self.textures[mat_id] = material
|
||
memory_manager.register_object(material)
|
||
|
||
logger.info(f"创建RGB材质: {mat_id}")
|
||
return material
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建RGB材质失败: {e}")
|
||
return None
|
||
|
||
def get_texture(self, key: str):
|
||
"""获取纹理材质 - 增强版本"""
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
try:
|
||
# 检查键是否有效
|
||
if not key:
|
||
return self.textures.get("mat_default")
|
||
|
||
# 从缓存中获取
|
||
if key in self.textures:
|
||
material = self.textures[key]
|
||
# 验证材质是否仍然有效
|
||
if material and material.name in bpy.data.materials:
|
||
return material
|
||
else:
|
||
# 清理无效的缓存
|
||
del self.textures[key]
|
||
|
||
# 在现有材质中查找
|
||
for material in bpy.data.materials:
|
||
if key in material.name:
|
||
self.textures[key] = material
|
||
return material
|
||
|
||
# 返回默认材质
|
||
default_material = self.textures.get("mat_default")
|
||
if default_material and default_material.name in bpy.data.materials:
|
||
return default_material
|
||
|
||
logger.warning(f"未找到纹理: {key}")
|
||
return None
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取纹理失败: {e}")
|
||
return None
|
||
|
||
# ==================== 数据获取方法 ====================
|
||
|
||
def get_zones(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""获取区域信息"""
|
||
uid = data.get("uid")
|
||
if uid not in self.zones:
|
||
self.zones[uid] = {}
|
||
return self.zones[uid]
|
||
|
||
def get_parts(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""获取零件信息"""
|
||
uid = data.get("uid")
|
||
if uid not in self.parts:
|
||
self.parts[uid] = {}
|
||
return self.parts[uid]
|
||
|
||
def get_hardwares(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||
"""获取硬件信息"""
|
||
uid = data.get("uid")
|
||
if uid not in self.hardwares:
|
||
self.hardwares[uid] = {}
|
||
return self.hardwares[uid]
|
||
|
||
# ==================== 配置管理方法 ====================
|
||
|
||
def set_config(self, data: Dict[str, Any]):
|
||
"""设置配置"""
|
||
try:
|
||
if "server_path" in data:
|
||
self.__class__._server_path = data["server_path"]
|
||
|
||
if "order_id" in data:
|
||
# 在Blender中存储为场景属性
|
||
if BLENDER_AVAILABLE:
|
||
bpy.context.scene["sw_order_id"] = data["order_id"]
|
||
|
||
if "order_code" in data:
|
||
if BLENDER_AVAILABLE:
|
||
bpy.context.scene["sw_order_code"] = data["order_code"]
|
||
|
||
if "back_material" in data:
|
||
self.back_material = data["back_material"]
|
||
|
||
if "part_mode" in data:
|
||
self.part_mode = data["part_mode"]
|
||
|
||
if "hide_none" in data:
|
||
self.hide_none = data["hide_none"]
|
||
|
||
if "unit_drawing" in data:
|
||
print(
|
||
f"{data.get('drawing_name', '')}:\t{data['unit_drawing']}")
|
||
|
||
if "zone_corner" in data:
|
||
zones = self.get_zones(data)
|
||
zone = zones.get(data["zid"])
|
||
if zone:
|
||
zone["sw_cor"] = data["zone_corner"]
|
||
|
||
# 应用内存管理相关配置
|
||
if "memory_cleanup_interval" in data:
|
||
memory_manager.cleanup_interval = data["memory_cleanup_interval"]
|
||
|
||
if "batch_size" in data:
|
||
self.batch_size = data["batch_size"]
|
||
|
||
logger.info(f"设置配置: {len(data)} 个配置项")
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置配置失败: {e}")
|
||
|
||
# ==================== 材质类型管理方法 ====================
|
||
|
||
def c11(self, data: Dict[str, Any]):
|
||
"""part_obverse - 设置零件正面显示"""
|
||
try:
|
||
self.mat_type = MAT_TYPE_OBVERSE if data.get(
|
||
"v", False) else MAT_TYPE_NORMAL
|
||
parts = self.get_parts(data)
|
||
|
||
for root, part in parts.items():
|
||
if part and not self._is_selected_part(part):
|
||
self.textured_part(part, False)
|
||
|
||
logger.info(f"设置零件正面显示: {self.mat_type}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置零件正面显示失败: {e}")
|
||
|
||
def c30(self, data: Dict[str, Any]):
|
||
"""part_nature - 设置零件自然显示"""
|
||
try:
|
||
self.mat_type = MAT_TYPE_NATURE if data.get(
|
||
"v", False) else MAT_TYPE_NORMAL
|
||
parts = self.get_parts(data)
|
||
|
||
for root, part in parts.items():
|
||
if part and not self._is_selected_part(part):
|
||
self.textured_part(part, False)
|
||
|
||
logger.info(f"设置零件自然显示: {self.mat_type}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置零件自然显示失败: {e}")
|
||
|
||
def _is_selected_part(self, part):
|
||
"""检查零件是否被选中"""
|
||
return part in self.selected_parts
|
||
|
||
# ==================== 纹理管理方法 ====================
|
||
|
||
def c02(self, data: Dict[str, Any]):
|
||
"""add_texture - 添加纹理 - 简化版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.warning("Blender 不可用,跳过纹理创建")
|
||
return
|
||
|
||
ckey = data.get("ckey")
|
||
if not ckey:
|
||
logger.warning("纹理键为空,跳过创建")
|
||
return
|
||
|
||
# 检查纹理是否已存在且有效
|
||
if ckey in self.textures:
|
||
existing_material = self.textures[ckey]
|
||
if existing_material and existing_material.name in bpy.data.materials:
|
||
return existing_material
|
||
else:
|
||
# 清理无效的缓存
|
||
del self.textures[ckey]
|
||
|
||
def create_material():
|
||
try:
|
||
# 创建新材质
|
||
material = bpy.data.materials.new(name=ckey)
|
||
material.use_nodes = True
|
||
|
||
# 获取材质节点
|
||
nodes = material.node_tree.nodes
|
||
links = material.node_tree.links
|
||
|
||
# 清理所有默认节点,重新创建
|
||
nodes.clear()
|
||
|
||
# 创建基础节点
|
||
principled = nodes.new(type='ShaderNodeBsdfPrincipled')
|
||
principled.location = (0, 0)
|
||
|
||
output = nodes.new(type='ShaderNodeOutputMaterial')
|
||
output.location = (300, 0)
|
||
|
||
# 连接基础节点
|
||
links.new(
|
||
principled.outputs['BSDF'], output.inputs['Surface'])
|
||
|
||
# 设置纹理图像
|
||
src_path = data.get("src")
|
||
|
||
if src_path:
|
||
try:
|
||
# 安全地加载图像
|
||
import os
|
||
|
||
# 检查路径是否存在
|
||
if os.path.exists(src_path):
|
||
# 检查是否已经加载过这个图像
|
||
image_name = os.path.basename(src_path)
|
||
image = bpy.data.images.get(image_name)
|
||
|
||
if not image:
|
||
image = bpy.data.images.load(src_path)
|
||
memory_manager.register_image(image)
|
||
|
||
# 创建纹理坐标节点
|
||
tex_coord = nodes.new(
|
||
type='ShaderNodeTexCoord')
|
||
tex_coord.location = (-600, 0)
|
||
|
||
# 创建图像纹理节点
|
||
tex_image = nodes.new(
|
||
type='ShaderNodeTexImage')
|
||
tex_image.image = image
|
||
tex_image.location = (-300, 0)
|
||
|
||
# 连接节点
|
||
links.new(
|
||
tex_coord.outputs['UV'], tex_image.inputs['Vector'])
|
||
links.new(
|
||
tex_image.outputs['Color'], principled.inputs['Base Color'])
|
||
|
||
# 如果有透明度,也连接Alpha
|
||
alpha_value = data.get("alpha", 1.0)
|
||
if alpha_value < 1.0:
|
||
links.new(
|
||
tex_image.outputs['Alpha'], principled.inputs['Alpha'])
|
||
material.blend_method = 'BLEND'
|
||
material.show_transparent_back = False
|
||
|
||
else:
|
||
# 创建一个纯色材质作为替代
|
||
principled.inputs['Base Color'].default_value = (
|
||
0.5, 0.5, 0.5, 1.0)
|
||
|
||
except Exception as img_error:
|
||
logger.error(f"加载纹理图像失败: {img_error}")
|
||
# 创建纯色材质作为替代
|
||
principled.inputs['Base Color'].default_value = (
|
||
1.0, 0.0, 0.0, 1.0) # 红色表示错误
|
||
else:
|
||
# 没有图片路径,创建纯色材质
|
||
# 尝试从RGB数据创建颜色
|
||
r = data.get("r", 128) / 255.0
|
||
g = data.get("g", 128) / 255.0
|
||
b = data.get("b", 128) / 255.0
|
||
principled.inputs['Base Color'].default_value = (
|
||
r, g, b, 1.0)
|
||
|
||
# 设置透明度
|
||
alpha_value = data.get("alpha", 1.0)
|
||
principled.inputs['Alpha'].default_value = alpha_value
|
||
if alpha_value < 1.0:
|
||
material.blend_method = 'BLEND'
|
||
material.use_backface_culling = False
|
||
|
||
# 设置其他属性
|
||
if "reflection" in data:
|
||
metallic_value = data["reflection"]
|
||
principled.inputs['Metallic'].default_value = metallic_value
|
||
|
||
if "reflection_glossiness" in data:
|
||
roughness_value = 1.0 - data["reflection_glossiness"]
|
||
principled.inputs['Roughness'].default_value = roughness_value
|
||
|
||
return material
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建材质失败: {e}")
|
||
return None
|
||
|
||
# 在主线程中执行材质创建
|
||
material = execute_in_main_thread(create_material)
|
||
|
||
if material:
|
||
# 存储材质
|
||
self.textures[ckey] = material
|
||
memory_manager.register_object(material)
|
||
else:
|
||
logger.error(f"材质创建失败: {ckey}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"添加纹理失败 {ckey}: {e}")
|
||
|
||
# 清理可能创建的无效材质
|
||
try:
|
||
if ckey in self.textures:
|
||
del self.textures[ckey]
|
||
if ckey in bpy.data.materials:
|
||
bpy.data.materials.remove(bpy.data.materials[ckey])
|
||
except:
|
||
pass
|
||
|
||
def c04(self, data: Dict[str, Any]):
|
||
"""c04 - 添加部件 - 简化版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.warning("Blender 不可用,跳过零件创建")
|
||
return
|
||
|
||
uid = data.get("uid")
|
||
root = data.get("cp")
|
||
|
||
if not uid or not root:
|
||
logger.error("缺少必要参数: uid或cp")
|
||
return
|
||
|
||
def create_part():
|
||
try:
|
||
# 获取部件集合
|
||
parts = self.get_parts(data)
|
||
|
||
# 创建部件容器
|
||
part = bpy.data.objects.new(f"Part_{root}", None)
|
||
bpy.context.scene.collection.objects.link(part)
|
||
|
||
# 应用透明材质给Part容器
|
||
transparent_material = self._create_transparent_material()
|
||
if transparent_material:
|
||
# Part是Empty对象,不能直接应用材质,所以保持原样
|
||
pass
|
||
|
||
# 设置部件属性
|
||
part["sw_uid"] = uid
|
||
part["sw_cp"] = root
|
||
part["sw_typ"] = "part"
|
||
|
||
# 存储部件
|
||
parts[root] = part
|
||
memory_manager.register_object(part)
|
||
|
||
# 处理finals数据
|
||
finals = data.get("finals", [])
|
||
for final_data in finals:
|
||
try:
|
||
board = self._create_board_with_material_and_uv(
|
||
part, final_data)
|
||
if not board:
|
||
logger.warning("板材创建失败")
|
||
except Exception as e:
|
||
logger.error(f"创建板材失败: {e}")
|
||
|
||
return part
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建部件失败: {e}")
|
||
return None
|
||
|
||
# 执行创建
|
||
part = execute_in_main_thread(create_part)
|
||
if not part:
|
||
logger.error(f"零件创建失败: uid={uid}, cp={root}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"添加零件失败: {e}")
|
||
|
||
def _create_board_with_material_and_uv(self, part, data):
|
||
"""创建板材并关联材质和启用UV - 简化版本"""
|
||
try:
|
||
# 获取正反面数据
|
||
obv = data.get("obv")
|
||
rev = data.get("rev")
|
||
|
||
if not obv or not rev:
|
||
logger.warning("缺少正反面数据,创建默认板材")
|
||
return self._create_default_board_with_material(part, data)
|
||
|
||
# 解析顶点计算尺寸
|
||
obv_vertices = self._parse_surface_vertices(obv)
|
||
rev_vertices = self._parse_surface_vertices(rev)
|
||
|
||
if len(obv_vertices) >= 4 and len(rev_vertices) >= 4:
|
||
# 计算板材尺寸
|
||
all_vertices = obv_vertices + rev_vertices
|
||
|
||
min_x = min(v[0] for v in all_vertices)
|
||
max_x = max(v[0] for v in all_vertices)
|
||
min_y = min(v[1] for v in all_vertices)
|
||
max_y = max(v[1] for v in all_vertices)
|
||
min_z = min(v[2] for v in all_vertices)
|
||
max_z = max(v[2] for v in all_vertices)
|
||
|
||
# 计算中心点和尺寸
|
||
center_x = (min_x + max_x) / 2
|
||
center_y = (min_y + max_y) / 2
|
||
center_z = (min_z + max_z) / 2
|
||
|
||
size_x = max(max_x - min_x, 0.001)
|
||
size_y = max(max_y - min_y, 0.001)
|
||
size_z = max(max_z - min_z, 0.001)
|
||
|
||
# 创建立方体
|
||
bpy.ops.mesh.primitive_cube_add(
|
||
size=1,
|
||
location=(center_x, center_y, center_z)
|
||
)
|
||
board = bpy.context.active_object
|
||
|
||
# 缩放到正确尺寸
|
||
board.scale = (size_x, size_y, size_z)
|
||
|
||
# 设置属性
|
||
board.parent = part
|
||
board.name = f"Board_{part.name}"
|
||
board["sw_face_type"] = "board"
|
||
|
||
# 关联材质
|
||
color = data.get("ckey", "mat_default")
|
||
if color:
|
||
material = self.get_texture(color)
|
||
if material and board.data:
|
||
# 清空现有材质
|
||
board.data.materials.clear()
|
||
# 添加新材质
|
||
board.data.materials.append(material)
|
||
else:
|
||
logger.warning(f"材质 {color} 未找到或板件数据无效")
|
||
|
||
# 启用UV
|
||
self._enable_uv_for_board(board)
|
||
|
||
return board
|
||
else:
|
||
logger.warning("顶点数据不足,创建默认板材")
|
||
return self._create_default_board_with_material(part, data)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建板材失败: {e}")
|
||
return self._create_default_board_with_material(part, data)
|
||
|
||
def _enable_uv_for_board(self, board):
|
||
"""为板件启用UV - 简化版本"""
|
||
try:
|
||
if not board or not board.data:
|
||
logger.warning("无效的板件对象,无法启用UV")
|
||
return
|
||
|
||
# 确保网格数据存在
|
||
mesh = board.data
|
||
if not mesh:
|
||
logger.warning("板件没有网格数据")
|
||
return
|
||
|
||
# 创建UV贴图层(如果不存在)
|
||
if not mesh.uv_layers:
|
||
uv_layer = mesh.uv_layers.new(name="UVMap")
|
||
else:
|
||
uv_layer = mesh.uv_layers[0]
|
||
|
||
# 确保UV层是活动的
|
||
mesh.uv_layers.active = uv_layer
|
||
|
||
# 更新网格数据
|
||
mesh.calc_loop_triangles()
|
||
|
||
# 为立方体创建基本UV坐标
|
||
if len(mesh.polygons) == 6: # 标准立方体
|
||
# 为每个面分配UV坐标
|
||
for poly_idx, poly in enumerate(mesh.polygons):
|
||
# 标准UV坐标 (0,0) (1,0) (1,1) (0,1)
|
||
uv_coords = [(0.0, 0.0), (1.0, 0.0),
|
||
(1.0, 1.0), (0.0, 1.0)]
|
||
|
||
for loop_idx, loop_index in enumerate(poly.loop_indices):
|
||
if loop_idx < len(uv_coords):
|
||
uv_layer.data[loop_index].uv = uv_coords[loop_idx]
|
||
else:
|
||
# 为非标准网格设置简单UV
|
||
for loop in mesh.loops:
|
||
uv_layer.data[loop.index].uv = (0.5, 0.5)
|
||
|
||
# 更新网格
|
||
mesh.update()
|
||
|
||
except Exception as e:
|
||
logger.error(f"启用UV失败: {e}")
|
||
|
||
def _create_default_board_with_material(self, part, data):
|
||
"""创建默认板材 - 带材质和UV"""
|
||
try:
|
||
# 创建默认立方体
|
||
bpy.ops.mesh.primitive_cube_add(
|
||
size=1,
|
||
location=(0, 0, 0)
|
||
)
|
||
board = bpy.context.active_object
|
||
|
||
# 设置属性
|
||
board.parent = part
|
||
board.name = f"Board_{part.name}_default"
|
||
board["sw_face_type"] = "board"
|
||
|
||
# 关联材质
|
||
color = data.get("ckey", "mat_default")
|
||
if color:
|
||
material = self.get_texture(color)
|
||
if material and board.data:
|
||
board.data.materials.clear()
|
||
board.data.materials.append(material)
|
||
logger.info(f"✅ 默认材质 {color} 已关联到板件 {board.name}")
|
||
|
||
# 启用UV
|
||
self._enable_uv_for_board(board)
|
||
|
||
logger.info(f"✅ 创建默认板材: {board.name}")
|
||
return board
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建默认板材失败: {e}")
|
||
return None
|
||
|
||
def _add_part_stretch_safe(self, part, data, timeout=5):
|
||
"""创建拉伸部件 - 安全版本"""
|
||
try:
|
||
logger.debug("创建拉伸部件(简化版本)")
|
||
|
||
# 创建简单的拉伸对象
|
||
stretch_obj = bpy.data.objects.new(f"Stretch_{part.name}", None)
|
||
stretch_obj.parent = part
|
||
bpy.context.scene.collection.objects.link(stretch_obj)
|
||
|
||
return stretch_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建拉伸部件失败: {e}")
|
||
return None
|
||
|
||
def _add_part_arc_safe(self, part, data, antiz, profiles, timeout=5):
|
||
"""创建弧形部件 - 安全版本"""
|
||
try:
|
||
logger.debug("创建弧形部件(简化版本)")
|
||
|
||
# 创建简单的弧形对象
|
||
arc_obj = bpy.data.objects.new(f"Arc_{part.name}", None)
|
||
arc_obj.parent = part
|
||
bpy.context.scene.collection.objects.link(arc_obj)
|
||
|
||
return arc_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建弧形部件失败: {e}")
|
||
return None
|
||
|
||
def _update_viewport(self):
|
||
"""更新视图端口"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
# 更新依赖图
|
||
bpy.context.view_layer.update()
|
||
|
||
# 刷新视图
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
region.tag_redraw()
|
||
|
||
logger.debug("视图端口已更新")
|
||
except Exception as e:
|
||
logger.warning(f"更新视图端口失败: {e}")
|
||
|
||
def _process_final_geometry(self, part, data, virtual=False):
|
||
"""处理最终几何体 - 增强版本"""
|
||
try:
|
||
final = data.get("final")
|
||
if not final:
|
||
logger.warning("没有找到最终几何体数据")
|
||
return
|
||
|
||
logger.debug(f"处理最终几何体: typ={final.get('typ')}")
|
||
|
||
# 获取几何体类型
|
||
typ = final.get("typ", 1)
|
||
antiz = final.get("antiz", False)
|
||
profiles = final.get("profiles", {})
|
||
|
||
# 创建几何体
|
||
if typ == 1:
|
||
# 板材部件
|
||
logger.debug("创建板材部件")
|
||
leaf = self._add_part_board(part, final, antiz, profiles)
|
||
elif typ == 2:
|
||
# 拉伸部件
|
||
logger.debug("创建拉伸部件")
|
||
leaf = self._add_part_stretch(part, final)
|
||
elif typ == 3:
|
||
# 弧形部件
|
||
logger.debug("创建弧形部件")
|
||
leaf = self._add_part_arc(part, final, antiz, profiles)
|
||
else:
|
||
logger.warning(f"未知的几何体类型: {typ}")
|
||
return
|
||
|
||
if leaf:
|
||
# 设置属性
|
||
leaf["sw_typ"] = "cp"
|
||
leaf["sw_mn"] = final.get("mn", 0)
|
||
|
||
# 设置可见性
|
||
if not virtual:
|
||
leaf.hide_viewport = False
|
||
|
||
memory_manager.register_object(leaf)
|
||
logger.debug(f"几何体创建成功: {leaf.name}")
|
||
else:
|
||
logger.warning("几何体创建失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理最终几何体失败: {e}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
|
||
def _add_part_board(self, part, data, antiz, profiles):
|
||
"""创建板材部件 - 增强版本"""
|
||
try:
|
||
logger.debug("开始创建板材部件")
|
||
|
||
# 创建叶子组
|
||
leaf = bpy.data.objects.new(f"Board_{part.name}", None)
|
||
leaf.parent = part
|
||
bpy.context.scene.collection.objects.link(leaf)
|
||
|
||
# 获取材质信息
|
||
color = data.get("ckey", "mat_default")
|
||
scale = data.get("scale")
|
||
angle = data.get("angle")
|
||
color2 = data.get("ckey2")
|
||
scale2 = data.get("scale2")
|
||
angle2 = data.get("angle2")
|
||
|
||
# 设置叶子属性
|
||
leaf["sw_ckey"] = color
|
||
if scale:
|
||
leaf["sw_scale"] = scale
|
||
if angle:
|
||
leaf["sw_angle"] = angle
|
||
|
||
logger.debug(f"板材材质: {color}")
|
||
|
||
# 处理截面
|
||
if "sects" in data:
|
||
logger.debug("处理截面数据")
|
||
sects = data["sects"]
|
||
for sect in sects:
|
||
segs = sect.get("segs", [])
|
||
surf = sect.get("sect", {})
|
||
paths = self._create_paths(part, segs)
|
||
self._follow_me(leaf, surf, paths, color, scale, angle)
|
||
|
||
# 创建第二个叶子用于表面
|
||
leaf2 = bpy.data.objects.new(
|
||
f"Board_Surface_{part.name}", None)
|
||
leaf2.parent = leaf
|
||
bpy.context.scene.collection.objects.link(leaf2)
|
||
|
||
self._add_part_surf(leaf2, data, antiz, color,
|
||
scale, angle, color2, scale2, angle2, profiles)
|
||
else:
|
||
# 直接创建表面
|
||
logger.debug("创建板材表面")
|
||
self._add_part_surf(
|
||
leaf, data, antiz, color, scale, angle, color2, scale2, angle2, profiles)
|
||
|
||
logger.debug(f"板材部件创建完成: {leaf.name}")
|
||
return leaf
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建板材部件失败: {e}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
return None
|
||
|
||
def _add_part_surf(self, leaf, data, antiz, color, scale, angle, color2, scale2, angle2, profiles):
|
||
"""创建部件表面 - 增强版本"""
|
||
try:
|
||
logger.debug("开始创建部件表面")
|
||
|
||
# 获取正反面数据
|
||
obv = data.get("obv") # 正面
|
||
rev = data.get("rev") # 反面
|
||
|
||
if not obv or not rev:
|
||
logger.warning("缺少正反面数据")
|
||
return
|
||
|
||
logger.debug(f"正面数据: {obv}")
|
||
logger.debug(f"反面数据: {rev}")
|
||
|
||
# 设置材质类型
|
||
obv_type = "o"
|
||
obv_save = color
|
||
obv_scale = scale
|
||
obv_angle = angle
|
||
rev_type = "r"
|
||
rev_save = color2 if color2 else color
|
||
rev_scale = scale2 if color2 else scale
|
||
rev_angle = angle2 if color2 else angle
|
||
|
||
# 如果antiz为真,交换正反面
|
||
if antiz:
|
||
obv_type, rev_type = rev_type, obv_type
|
||
obv_save, rev_save = rev_save, obv_save
|
||
obv_scale, rev_scale = rev_scale, obv_scale
|
||
obv_angle, rev_angle = rev_angle, obv_angle
|
||
|
||
# 确定显示材质
|
||
obv_show = "mat_obverse" if self.mat_type == MAT_TYPE_OBVERSE else obv_save
|
||
rev_show = "mat_reverse" if self.mat_type == MAT_TYPE_OBVERSE else rev_save
|
||
|
||
# 创建面
|
||
series1 = []
|
||
series2 = []
|
||
|
||
logger.debug("创建正面")
|
||
obv_face = self.create_face(
|
||
leaf, obv, obv_show, obv_scale, obv_angle, series1, False, self.back_material, obv_save, obv_type)
|
||
|
||
logger.debug("创建反面")
|
||
rev_face = self.create_face(
|
||
leaf, rev, rev_show, rev_scale, rev_angle, series2, True, self.back_material, rev_save, rev_type)
|
||
|
||
# 创建边缘
|
||
if series1 and series2:
|
||
logger.debug("创建边缘")
|
||
self._add_part_edges(
|
||
leaf, series1, series2, obv, rev, profiles)
|
||
|
||
logger.debug("部件表面创建完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建部件表面失败: {e}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
|
||
def _clear_part_children(self, part):
|
||
"""清除零件的子对象"""
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
try:
|
||
children_to_remove = []
|
||
for child in part.children:
|
||
if child.get("sw_typ") == "cp":
|
||
children_to_remove.append(child)
|
||
|
||
for child in children_to_remove:
|
||
bpy.data.objects.remove(child, do_unlink=True)
|
||
|
||
except Exception as e:
|
||
logger.error(f"清除零件子对象失败: {e}")
|
||
|
||
def _set_drawer_properties(self, part, data):
|
||
"""设置抽屉属性"""
|
||
drawer_type = data.get("drw", 0)
|
||
part["sw_drawer"] = drawer_type
|
||
|
||
if drawer_type in [73, 74]: # DR_LP/DR_RP
|
||
part["sw_dr_depth"] = data.get("drd", 0)
|
||
|
||
if drawer_type == 70: # DR_DP
|
||
drv = data.get("drv")
|
||
if drv:
|
||
drawer_dir = Vector3d.parse(drv)
|
||
part["sw_drawer_dir"] = (
|
||
drawer_dir.x, drawer_dir.y, drawer_dir.z)
|
||
|
||
def _set_door_properties(self, part, data):
|
||
"""设置门属性"""
|
||
door_type = data.get("dor", 0)
|
||
part["sw_door"] = door_type
|
||
|
||
if door_type in [10, 15]:
|
||
part["sw_door_width"] = data.get("dow", 0)
|
||
part["sw_door_pos"] = data.get("dop", "F")
|
||
|
||
def _load_prefab_part(self, part, data):
|
||
"""加载预制件"""
|
||
if "sid" not in data:
|
||
return None
|
||
|
||
try:
|
||
mirr = data.get("mr", "")
|
||
if mirr:
|
||
mirr = "_" + mirr
|
||
|
||
# 构建文件路径
|
||
file_path = f"{SUWood.suwood_path('V_StructPart')}/{data['sid']}{mirr}.skp"
|
||
print(f"尝试加载预制件: {file_path}")
|
||
|
||
# 在Blender中,我们需要使用不同的方法加载外部文件
|
||
# 这里创建一个占位符
|
||
inst = bpy.data.objects.new(f"Prefab_{data['sid']}", None)
|
||
inst.parent = part
|
||
bpy.context.scene.collection.objects.link(inst)
|
||
inst["sw_typ"] = "cp"
|
||
|
||
# 设置缩放
|
||
if "l" in data and "w" in data:
|
||
inst.scale = (data["l"] * 0.001, data["w"] * 0.001, 1.0)
|
||
|
||
# 应用变换
|
||
if "trans" in data:
|
||
trans = Transformation.parse(data["trans"])
|
||
self._apply_transformation(inst, trans)
|
||
|
||
return inst
|
||
|
||
except Exception as e:
|
||
logger.error(f"加载预制件失败: {e}")
|
||
return None
|
||
|
||
def _create_virtual_geometry(self, part, data):
|
||
"""创建虚拟几何体"""
|
||
try:
|
||
leaf = bpy.data.objects.new("Virtual_Geometry", None)
|
||
leaf.parent = part
|
||
bpy.context.scene.collection.objects.link(leaf)
|
||
|
||
if data.get("typ") == 3:
|
||
# 弧形部件
|
||
self._create_arc_geometry(leaf, data)
|
||
else:
|
||
# 板材部件
|
||
self._create_board_geometry(leaf, data)
|
||
|
||
leaf["sw_typ"] = "cp"
|
||
leaf["sw_virtual"] = True
|
||
leaf.hide_viewport = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建虚拟几何体失败: {e}")
|
||
|
||
def _create_arc_geometry(self, leaf, data):
|
||
"""创建弧形几何体"""
|
||
try:
|
||
co = data.get("co")
|
||
cr = data.get("cr")
|
||
if co and cr:
|
||
center_o = Point3d.parse(co)
|
||
center_r = Point3d.parse(cr)
|
||
|
||
# 创建弧形路径
|
||
path = self._create_arc_path(leaf, center_o, center_r)
|
||
|
||
# 创建截面
|
||
obv = data.get("obv", {})
|
||
self._follow_me(leaf, obv, path, None)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建弧形几何体失败: {e}")
|
||
|
||
def _create_board_geometry(self, leaf, data):
|
||
"""创建板材几何体"""
|
||
try:
|
||
obv = data.get("obv", {})
|
||
rev = data.get("rev", {})
|
||
|
||
series1 = []
|
||
series2 = []
|
||
|
||
self.create_face(leaf, obv, series=series1)
|
||
self.create_face(leaf, rev, series=series2)
|
||
|
||
self._add_part_edges(leaf, series1, series2, obv, rev)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建板材几何体失败: {e}")
|
||
|
||
def _add_part_edges(self, leaf, series1, series2, obv, rev, profiles=None):
|
||
"""创建部件的边缘面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE or not series1 or not series2:
|
||
return
|
||
|
||
unplanar = False
|
||
|
||
for index in range(len(series1)):
|
||
pts1 = series1[index]
|
||
pts2 = series2[index]
|
||
|
||
for i in range(1, len(pts1)):
|
||
pts = [pts1[i-1], pts1[i], pts2[i], pts2[i-1]]
|
||
|
||
try:
|
||
# 创建边缘面
|
||
face = self._create_edge_face(leaf, pts)
|
||
if face and profiles:
|
||
self._add_part_profile(face, index, profiles)
|
||
|
||
except Exception as e:
|
||
unplanar = True
|
||
logger.warning(f"Points are not planar {index}: {i}")
|
||
logger.warning(f"Points: {pts}")
|
||
|
||
if unplanar:
|
||
# 输出调试信息
|
||
segs_o = obv.get("segs", [])
|
||
pts_o = [seg[0] for seg in segs_o]
|
||
segs_r = rev.get("segs", [])
|
||
pts_r = [seg[0] for seg in segs_r]
|
||
|
||
logger.warning("=" * 30)
|
||
logger.warning(f"obv: {pts_o}")
|
||
logger.warning(f"rev: {pts_r}")
|
||
logger.warning(f"series1: {series1}")
|
||
logger.warning(f"series2: {series2}")
|
||
logger.warning("=" * 30)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建部件边缘失败: {e}")
|
||
|
||
def _create_edge_face(self, container, points):
|
||
"""创建边缘面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 创建网格
|
||
mesh = bpy.data.meshes.new("Edge_Face")
|
||
vertices = [(p.x, p.y, p.z) if hasattr(
|
||
p, 'x') else p for p in points]
|
||
faces = [list(range(len(vertices)))]
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj = bpy.data.objects.new("Edge_Face_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
# 隐藏某些边
|
||
for i, edge in enumerate(mesh.edges):
|
||
if i in [1, 3]:
|
||
edge.use_edge_sharp = True
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建边缘面失败: {e}")
|
||
return None
|
||
|
||
def _add_part_profile(self, face, index, profiles):
|
||
"""为面添加型材属性和纹理"""
|
||
try:
|
||
profile = profiles.get(index)
|
||
if not profile:
|
||
return
|
||
|
||
color = profile.get("ckey")
|
||
scale = profile.get("scale")
|
||
angle = profile.get("angle")
|
||
typ = profile.get("typ", "0")
|
||
|
||
# 确定当前颜色
|
||
if self.mat_type == MAT_TYPE_OBVERSE:
|
||
if typ == "1":
|
||
current = "mat_obverse" # thick profile
|
||
elif typ == "2":
|
||
current = "mat_thin" # thin profile
|
||
else:
|
||
current = "mat_reverse" # none profile
|
||
else:
|
||
current = color
|
||
|
||
face["sw_typ"] = f"e{typ}"
|
||
self._textured_surf(face, self.back_material,
|
||
current, color, scale, angle)
|
||
|
||
except Exception as e:
|
||
logger.error(f"添加型材属性失败: {e}")
|
||
|
||
def _add_part_stretch(self, part, data):
|
||
"""创建拉伸部件"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
compensates = data.get("compensates", [])
|
||
trim_surfs = data.get("trim_surfs", [])
|
||
baselines = self._create_paths(part, data.get("baselines", []))
|
||
|
||
# 尝试加载预制件
|
||
inst = None
|
||
if ("sid" in data and not compensates and not trim_surfs and len(baselines) == 1):
|
||
file_path = f"{SUWood.suwood_path('V_StretchPart')}/{data['sid']}.skp"
|
||
# 在实际应用中需要实现文件加载逻辑
|
||
# 这里创建占位符
|
||
inst = self._load_stretch_prefab(
|
||
part, file_path, data, baselines[0])
|
||
|
||
if inst:
|
||
# 创建虚拟几何体
|
||
leaf = bpy.data.objects.new("Virtual_Stretch", None)
|
||
leaf.parent = part
|
||
bpy.context.scene.collection.objects.link(leaf)
|
||
|
||
surf = data.get("sect", {})
|
||
surf["segs"] = data.get("bounds", [])
|
||
self._follow_me(leaf, surf, baselines, None)
|
||
|
||
leaf["sw_virtual"] = True
|
||
leaf.hide_viewport = True
|
||
|
||
else:
|
||
# 创建实际几何体
|
||
thick = data.get("thick", 18) * 0.001 # mm to meters
|
||
leaf = bpy.data.objects.new("Stretch_Part", None)
|
||
leaf.parent = part
|
||
bpy.context.scene.collection.objects.link(leaf)
|
||
|
||
zaxis = Vector3d.parse(data.get("zaxis", "(0,0,1)"))
|
||
color = data.get("ckey")
|
||
sect = data.get("sect", {})
|
||
|
||
self._follow_me(leaf, sect, baselines, color)
|
||
|
||
# 处理补偿面
|
||
for compensate in compensates:
|
||
self._apply_compensate(leaf, compensate, zaxis, thick)
|
||
|
||
# 处理修剪面
|
||
for trim_surf in trim_surfs:
|
||
self._apply_trim_surf(leaf, trim_surf, zaxis, thick)
|
||
|
||
leaf["sw_ckey"] = color
|
||
|
||
return leaf
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建拉伸部件失败: {e}")
|
||
return None
|
||
|
||
def _add_part_arc(self, part, data, antiz, profiles):
|
||
"""创建弧形部件"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
leaf = bpy.data.objects.new("Arc_Part", None)
|
||
leaf.parent = part
|
||
bpy.context.scene.collection.objects.link(leaf)
|
||
|
||
obv = data.get("obv", {})
|
||
color = data.get("ckey")
|
||
scale = data.get("scale")
|
||
angle = data.get("angle")
|
||
color2 = data.get("ckey2")
|
||
scale2 = data.get("scale2")
|
||
angle2 = data.get("angle2")
|
||
|
||
# 设置属性
|
||
leaf["sw_ckey"] = color
|
||
if scale:
|
||
leaf["sw_scale"] = scale
|
||
if angle:
|
||
leaf["sw_angle"] = angle
|
||
|
||
# 创建弧形路径
|
||
center_o = Point3d.parse(data.get("co", "(0,0,0)"))
|
||
center_r = Point3d.parse(data.get("cr", "(0,0,0)"))
|
||
path = self._create_arc_path(leaf, center_o, center_r)
|
||
|
||
# 创建弧形几何体
|
||
series = []
|
||
normal = self._follow_me(
|
||
leaf, obv, path, color, scale, angle, False, series, True)
|
||
|
||
# 处理面和边
|
||
if len(series) == 4:
|
||
self._process_arc_faces(
|
||
leaf, series, normal, center_o, center_r, color2, scale2, angle2, profiles)
|
||
|
||
return leaf
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建弧形部件失败: {e}")
|
||
return None
|
||
|
||
def _process_arc_faces(self, leaf, series, normal, center_o, center_r, color2, scale2, angle2, profiles):
|
||
"""处理弧形面和边"""
|
||
try:
|
||
count = 0
|
||
edge1 = False
|
||
edge3 = False
|
||
face2 = color2 is None
|
||
|
||
for child in leaf.children:
|
||
if not hasattr(child, 'data') or not child.data:
|
||
continue
|
||
|
||
# 检查是否是平行于法向量的面
|
||
if self._is_parallel_to_normal(child, normal):
|
||
if self._is_on_plane(center_o, child):
|
||
self._add_part_profile(child, 2, profiles)
|
||
count += 1
|
||
else:
|
||
self._add_part_profile(child, 0, profiles)
|
||
count += 1
|
||
else:
|
||
# 处理边
|
||
if not edge1 and self._contains_series_points(child, series[1]):
|
||
self._add_part_profile(child, 1, profiles)
|
||
count += 1
|
||
edge1 = True
|
||
elif not edge3 and self._contains_series_points(child, series[3]):
|
||
self._add_part_profile(child, 3, profiles)
|
||
count += 1
|
||
edge3 = True
|
||
elif not face2 and self._contains_series_points(child, series[2]):
|
||
self._textured_surf(
|
||
child, self.back_material, color2, color2, scale2, angle2)
|
||
count += 1
|
||
face2 = True
|
||
|
||
# 检查是否完成
|
||
expected_count = 5 if color2 else 4
|
||
if count >= expected_count:
|
||
break
|
||
|
||
except Exception as e:
|
||
logger.error(f"处理弧形面失败: {e}")
|
||
|
||
# ==================== 硬件管理方法 ====================
|
||
|
||
def c08(self, data: Dict[str, Any]):
|
||
"""add_hardware - 添加硬件 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
uid = data.get("uid")
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
def create_hardware():
|
||
try:
|
||
items = data.get("items", [])
|
||
created_count = 0
|
||
|
||
for item in items:
|
||
root = item.get("root")
|
||
file_path = item.get("file")
|
||
ps = Point3d.parse(item.get("ps", "(0,0,0)"))
|
||
pe = Point3d.parse(item.get("pe", "(0,0,0)"))
|
||
|
||
if file_path:
|
||
hardware = self._load_hardware_file(
|
||
file_path, item, ps, pe)
|
||
else:
|
||
hardware = self._create_simple_hardware(
|
||
ps, pe, item)
|
||
|
||
if hardware:
|
||
hardware["sw_uid"] = uid
|
||
hardware["sw_root"] = root
|
||
hardware["sw_typ"] = "hw"
|
||
|
||
# 应用单元变换
|
||
if uid in self.unit_trans:
|
||
self._apply_transformation(
|
||
hardware, self.unit_trans[uid])
|
||
|
||
hardwares[root] = hardware
|
||
memory_manager.register_object(hardware)
|
||
created_count += 1
|
||
|
||
return created_count
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建硬件失败: {e}")
|
||
return 0
|
||
|
||
# 在主线程中执行硬件创建
|
||
count = execute_in_main_thread(create_hardware)
|
||
|
||
if count > 0:
|
||
logger.info(f"✅ 成功创建硬件: uid={uid}, count={count}")
|
||
else:
|
||
logger.error(f"❌ 硬件创建失败: uid={uid}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 添加硬件失败: {e}")
|
||
|
||
def _load_hardware_file(self, file_path, item, ps, pe):
|
||
"""加载硬件文件"""
|
||
try:
|
||
# 在实际应用中需要实现文件加载逻辑
|
||
# 这里创建占位符
|
||
elem = bpy.data.objects.new(
|
||
f"Hardware_{item.get('uid', 'unknown')}", None)
|
||
bpy.context.scene.collection.objects.link(elem)
|
||
|
||
# 设置缩放
|
||
if ps and pe:
|
||
distance = math.sqrt((pe.x - ps.x)**2 +
|
||
(pe.y - ps.y)**2 + (pe.z - ps.z)**2)
|
||
elem.scale = (distance, 1.0, 1.0)
|
||
|
||
# 应用变换
|
||
if "trans" in item:
|
||
trans = Transformation.parse(item["trans"])
|
||
self._apply_transformation(elem, trans)
|
||
|
||
return elem
|
||
|
||
except Exception as e:
|
||
logger.error(f"加载硬件文件失败: {e}")
|
||
return None
|
||
|
||
def _create_simple_hardware(self, ps, pe, item):
|
||
"""创建简单硬件几何体"""
|
||
try:
|
||
elem = bpy.data.objects.new("Simple_Hardware", None)
|
||
bpy.context.scene.collection.objects.link(elem)
|
||
|
||
# 创建路径
|
||
path = self._create_line_path(ps, pe)
|
||
|
||
# 创建截面
|
||
sect = item.get("sect", {})
|
||
color = item.get("ckey")
|
||
|
||
self._follow_me(elem, sect, path, color)
|
||
elem["sw_ckey"] = color
|
||
|
||
return elem
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建简单硬件失败: {e}")
|
||
return None
|
||
|
||
# ==================== 加工管理方法 ====================
|
||
|
||
def c05(self, data: Dict[str, Any]):
|
||
"""add_machining - 添加加工 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
uid = data.get("uid")
|
||
|
||
def create_machining():
|
||
try:
|
||
works = data.get("works", [])
|
||
machinings = []
|
||
|
||
for work in works:
|
||
component = work.get("component")
|
||
machining = self._create_machining(component, work)
|
||
if machining:
|
||
machinings.append(machining)
|
||
|
||
# 存储加工
|
||
if uid not in self.machinings:
|
||
self.machinings[uid] = []
|
||
self.machinings[uid].extend(machinings)
|
||
|
||
for machining in machinings:
|
||
memory_manager.register_object(machining)
|
||
|
||
return len(machinings)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建加工失败: {e}")
|
||
return 0
|
||
|
||
# 在主线程中执行加工创建
|
||
count = execute_in_main_thread(create_machining)
|
||
|
||
if count > 0:
|
||
logger.info(f"✅ 成功创建加工: uid={uid}, count={count}")
|
||
else:
|
||
logger.error(f"❌ 加工创建失败: uid={uid}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 添加加工失败: {e}")
|
||
|
||
def _create_machining(self, component, work):
|
||
"""创建加工对象"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
special = work.get("special", 0)
|
||
|
||
machining = bpy.data.objects.new("Machining", None)
|
||
machining.parent = component
|
||
bpy.context.scene.collection.objects.link(machining)
|
||
|
||
machining["sw_typ"] = "work"
|
||
machining["sw_special"] = special
|
||
|
||
# 解析点
|
||
p1 = Point3d.parse(work.get("p1", "(0,0,0)"))
|
||
p2 = Point3d.parse(work.get("p2", "(0,0,0)"))
|
||
|
||
# 创建路径和面
|
||
if "tri" in work:
|
||
# 三角形加工
|
||
tri = Point3d.parse(work.get("tri", "(0,0,0)"))
|
||
p3 = Point3d.parse(work.get("p3", "(0,0,0)"))
|
||
path = self._create_line_path(p1, p3)
|
||
face = self._create_triangle_face(machining, tri, p2, p1)
|
||
elif "surf" in work:
|
||
# 表面加工
|
||
path = self._create_line_path(p1, p2)
|
||
surf = work.get("surf", {})
|
||
face = self.create_face(machining, surf)
|
||
else:
|
||
# 圆形加工
|
||
path = self._create_line_path(p1, p2)
|
||
za = self._normalize_vector(
|
||
p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
|
||
dia = work.get("dia", 6) * 0.001 # mm to meters
|
||
face = self._create_circle_face(machining, p1, za, dia / 2.0)
|
||
|
||
# 设置材质
|
||
if special == 0 and work.get("cancel", 0) == 0:
|
||
texture = self.get_texture("mat_machine")
|
||
if face and texture:
|
||
self._apply_material_to_face(face, texture)
|
||
|
||
# 执行跟随
|
||
if face and path:
|
||
self._follow_me_face(face, path)
|
||
self._cleanup_path(path)
|
||
|
||
return machining
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建加工对象失败: {e}")
|
||
return None
|
||
|
||
def _work_trimmed(self, part, work):
|
||
"""工作修剪"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 获取所有子叶
|
||
leaves = []
|
||
for child in part.children:
|
||
if child.get("sw_typ") == "cp":
|
||
leaves.append(child)
|
||
|
||
for leaf in leaves:
|
||
if not leaf or leaf.name not in bpy.data.objects:
|
||
continue
|
||
|
||
# 保存属性
|
||
attributes = {}
|
||
for key in leaf.keys():
|
||
if key.startswith("sw_"):
|
||
attributes[key] = leaf[key]
|
||
|
||
# 创建修剪器
|
||
trimmer = self._create_trimmer(part, work)
|
||
if not trimmer:
|
||
continue
|
||
|
||
# 执行修剪
|
||
trimmed = self._trim_object(trimmer, leaf)
|
||
|
||
# 恢复属性
|
||
if trimmed:
|
||
for key, value in attributes.items():
|
||
trimmed[key] = value
|
||
|
||
# 清理
|
||
self._cleanup_trimmer(trimmer)
|
||
|
||
# 处理差异面
|
||
if work.get("differ", False):
|
||
self._mark_differ_faces(leaf)
|
||
|
||
except Exception as e:
|
||
logger.error(f"工作修剪失败: {e}")
|
||
|
||
def _create_trimmer(self, part, work):
|
||
"""创建修剪器"""
|
||
try:
|
||
trimmer = bpy.data.objects.new("Trimmer", None)
|
||
trimmer.parent = part
|
||
bpy.context.scene.collection.objects.link(trimmer)
|
||
|
||
p1 = Point3d.parse(work.get("p1", "(0,0,0)"))
|
||
p2 = Point3d.parse(work.get("p2", "(0,0,0)"))
|
||
|
||
if "tri" in work:
|
||
# 三角形修剪器
|
||
tri = Point3d.parse(work.get("tri", "(0,0,0)"))
|
||
p3 = Point3d.parse(work.get("p3", "(0,0,0)"))
|
||
path = self._create_line_path(p1, p3)
|
||
face = self._create_triangle_face(trimmer, tri, p2, p1)
|
||
elif "surf" in work:
|
||
# 表面修剪器
|
||
path = self._create_line_path(p1, p2)
|
||
surf = work.get("surf", {})
|
||
face = self.create_face(trimmer, surf)
|
||
else:
|
||
# 圆形修剪器
|
||
path = self._create_line_path(p1, p2)
|
||
za = self._normalize_vector(
|
||
p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
|
||
dia = work.get("dia", 6) * 0.001
|
||
face = self._create_circle_face(trimmer, p1, za, dia / 2.0)
|
||
|
||
# 设置差异材质
|
||
if work.get("differ", False):
|
||
texture = self.get_texture("mat_default")
|
||
if face and texture:
|
||
self._apply_material_to_face(face, texture)
|
||
|
||
# 执行跟随
|
||
if face and path:
|
||
self._follow_me_face(face, path)
|
||
|
||
return trimmer
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建修剪器失败: {e}")
|
||
return None
|
||
|
||
# ==================== 几何体创建辅助方法 ====================
|
||
|
||
def create_face(self, container, surface, color=None, scale=None, angle=None,
|
||
series=None, reverse_face=False, back_material=True,
|
||
saved_color=None, typ=None):
|
||
"""创建面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
segs = surface.get("segs", [])
|
||
if not segs:
|
||
return None
|
||
|
||
# 创建边
|
||
edges = self._create_edges(container, segs, series)
|
||
if not edges:
|
||
return None
|
||
|
||
# 创建面
|
||
face = self._create_face_from_edges(container, edges)
|
||
if not face:
|
||
return None
|
||
|
||
# 处理法向量
|
||
if "vz" in surface:
|
||
zaxis = Vector3d.parse(surface["vz"])
|
||
if series and "vx" in surface:
|
||
xaxis = Vector3d.parse(surface["vx"])
|
||
if self._should_reverse_face(face, zaxis, reverse_face):
|
||
self._reverse_face(face)
|
||
elif reverse_face and self._face_normal_matches(face, zaxis):
|
||
self._reverse_face(face)
|
||
|
||
# 设置属性
|
||
if typ:
|
||
face["sw_typ"] = typ
|
||
|
||
# 应用材质
|
||
if color:
|
||
self._textured_surf(face, back_material,
|
||
color, saved_color, scale, angle)
|
||
else:
|
||
self._textured_surf(face, back_material, "mat_normal")
|
||
|
||
return face
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建面失败: {e}")
|
||
return None
|
||
|
||
def _create_edges(self, container, segments, series=None):
|
||
"""创建边"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return []
|
||
|
||
edges = []
|
||
seg_pts = {}
|
||
|
||
# 解析所有点
|
||
for index, segment in enumerate(segments):
|
||
pts = []
|
||
for point_str in segment:
|
||
point = Point3d.parse(point_str)
|
||
if point:
|
||
pts.append((point.x, point.y, point.z))
|
||
seg_pts[index] = pts
|
||
|
||
# 创建边
|
||
for this_i in range(len(segments)):
|
||
pts_i = seg_pts[this_i]
|
||
pts_p = seg_pts[(this_i - 1) % len(segments)]
|
||
pts_n = seg_pts[(this_i + 1) % len(segments)]
|
||
|
||
if len(pts_i) > 2:
|
||
# 多点段
|
||
if len(pts_p) > 2:
|
||
prev_p = pts_p[-1]
|
||
this_p = pts_i[0]
|
||
if prev_p != this_p:
|
||
edges.append(self._create_line_edge_simple(
|
||
container, prev_p, this_p))
|
||
|
||
# 添加段内边
|
||
for j in range(len(pts_i) - 1):
|
||
edges.append(self._create_line_edge_simple(
|
||
container, pts_i[j], pts_i[j+1]))
|
||
|
||
if series:
|
||
series.append(pts_i)
|
||
else:
|
||
# 两点段
|
||
point_s = pts_p[-1] if len(pts_p) > 2 else pts_i[0]
|
||
point_e = pts_n[0] if len(pts_n) > 2 else pts_i[-1]
|
||
edges.append(self._create_line_edge_simple(
|
||
container, point_s, point_e))
|
||
|
||
if series:
|
||
series.append([point_s, point_e])
|
||
|
||
return [e for e in edges if e]
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建边失败: {e}")
|
||
return []
|
||
|
||
def _create_line_edge_simple(self, container, start, end):
|
||
"""创建简单线边"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 创建网格
|
||
mesh = bpy.data.meshes.new("Simple_Edge")
|
||
vertices = [start, end]
|
||
edges = [(0, 1)]
|
||
|
||
mesh.from_pydata(vertices, edges, [])
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj = bpy.data.objects.new("Simple_Edge_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建简单线边失败: {e}")
|
||
return None
|
||
|
||
def _create_face_from_edges(self, container, edges):
|
||
"""从边创建面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE or not edges:
|
||
return None
|
||
|
||
# 收集所有顶点
|
||
vertices = []
|
||
vertex_map = {}
|
||
|
||
for edge in edges:
|
||
if hasattr(edge, 'data') and edge.data:
|
||
for vert in edge.data.vertices:
|
||
co = tuple(vert.co)
|
||
if co not in vertex_map:
|
||
vertex_map[co] = len(vertices)
|
||
vertices.append(co)
|
||
|
||
if len(vertices) < 3:
|
||
return None
|
||
|
||
# 创建面网格
|
||
mesh = bpy.data.meshes.new("Face_Mesh")
|
||
faces = [list(range(len(vertices)))]
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj = bpy.data.objects.new("Face_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"从边创建面失败: {e}")
|
||
return None
|
||
|
||
def _create_paths(self, container, segments):
|
||
"""创建路径"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return []
|
||
|
||
paths = []
|
||
|
||
for seg in segments:
|
||
if "c" not in seg:
|
||
# 直线段
|
||
s = Point3d.parse(seg.get("s", "(0,0,0)"))
|
||
e = Point3d.parse(seg.get("e", "(0,0,0)"))
|
||
path = self._create_line_path(s, e)
|
||
if path:
|
||
paths.append(path)
|
||
else:
|
||
# 弧线段
|
||
c = Point3d.parse(seg.get("c", "(0,0,0)"))
|
||
x = Vector3d.parse(seg.get("x", "(1,0,0)"))
|
||
z = Vector3d.parse(seg.get("z", "(0,0,1)"))
|
||
r = seg.get("r", 1.0) * 0.001 # mm to meters
|
||
a1 = seg.get("a1", 0.0)
|
||
a2 = seg.get("a2", math.pi * 2)
|
||
n = seg.get("n", 12)
|
||
|
||
arc_paths = self._create_arc_paths(c, x, z, r, a1, a2, n)
|
||
paths.extend(arc_paths)
|
||
|
||
return paths
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建路径失败: {e}")
|
||
return []
|
||
|
||
def _create_line_path(self, start, end):
|
||
"""创建直线路径"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
mesh = bpy.data.meshes.new("Line_Path")
|
||
vertices = [(start.x, start.y, start.z), (end.x, end.y, end.z)]
|
||
edges = [(0, 1)]
|
||
|
||
mesh.from_pydata(vertices, edges, [])
|
||
mesh.update()
|
||
|
||
obj = bpy.data.objects.new("Line_Path_Obj", mesh)
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建直线路径失败: {e}")
|
||
return None
|
||
|
||
def _create_arc_path(self, container, center_o, center_r):
|
||
"""创建弧形路径"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
mesh = bpy.data.meshes.new("Arc_Path")
|
||
vertices = [(center_o.x, center_o.y, center_o.z),
|
||
(center_r.x, center_r.y, center_r.z)]
|
||
edges = [(0, 1)]
|
||
|
||
mesh.from_pydata(vertices, edges, [])
|
||
mesh.update()
|
||
|
||
obj = bpy.data.objects.new("Arc_Path_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建弧形路径失败: {e}")
|
||
return None
|
||
|
||
def _follow_me(self, container, surface, path, color, scale=None, angle=None,
|
||
reverse_face=True, series=None, saved_color=None):
|
||
"""跟随路径创建几何体"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 创建截面
|
||
face = self.create_face(container, surface, color, scale, angle,
|
||
series, reverse_face, self.back_material, saved_color)
|
||
|
||
if not face:
|
||
return None
|
||
|
||
# 获取法向量
|
||
normal = self._get_face_normal(face)
|
||
|
||
# 执行跟随操作(在Blender中需要使用修改器或其他方法)
|
||
self._apply_follow_me(face, path)
|
||
|
||
# 隐藏边
|
||
self._hide_edges(container)
|
||
|
||
# 清理路径
|
||
if isinstance(path, list):
|
||
for p in path:
|
||
self._cleanup_path(p)
|
||
else:
|
||
self._cleanup_path(path)
|
||
|
||
return normal
|
||
|
||
except Exception as e:
|
||
logger.error(f"跟随路径失败: {e}")
|
||
return None
|
||
|
||
# ==================== 材质和纹理处理方法 ====================
|
||
|
||
def _textured_surf(self, face, back_material, color, saved_color=None, scale_a=None, angle_a=None):
|
||
"""为表面应用纹理"""
|
||
try:
|
||
if not BLENDER_AVAILABLE or not face:
|
||
return
|
||
|
||
# 保存属性
|
||
if saved_color:
|
||
face["sw_ckey"] = saved_color
|
||
if scale_a:
|
||
face["sw_scale"] = scale_a
|
||
if angle_a:
|
||
face["sw_angle"] = angle_a
|
||
|
||
# 获取材质
|
||
texture = self.get_texture(color)
|
||
if not texture:
|
||
return
|
||
|
||
# 应用材质
|
||
if hasattr(face, 'data') and face.data:
|
||
if not face.data.materials:
|
||
face.data.materials.append(texture)
|
||
else:
|
||
face.data.materials[0] = texture
|
||
|
||
# 设置背面材质
|
||
if back_material or texture.get("alpha", 1.0) < 1.0:
|
||
# 在Blender中设置背面材质
|
||
pass
|
||
|
||
# 应用纹理变换
|
||
if face.get("sw_ckey") == color:
|
||
scale = face.get("sw_scale")
|
||
angle = face.get("sw_angle")
|
||
if (scale or angle) and not face.get("sw_rt"):
|
||
self._rotate_texture(face, scale, angle)
|
||
face["sw_rt"] = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"应用表面纹理失败: {e}")
|
||
|
||
def _rotate_texture(self, face, scale, angle):
|
||
"""旋转纹理"""
|
||
try:
|
||
if not BLENDER_AVAILABLE or not face:
|
||
return
|
||
|
||
# 在Blender中实现纹理旋转
|
||
# 这需要操作UV坐标
|
||
if hasattr(face, 'data') and face.data:
|
||
# 获取UV层
|
||
if face.data.uv_layers:
|
||
uv_layer = face.data.uv_layers[0]
|
||
# 应用缩放和旋转变换
|
||
self._apply_uv_transform(uv_layer, scale, angle)
|
||
|
||
except Exception as e:
|
||
logger.error(f"旋转纹理失败: {e}")
|
||
|
||
def _apply_uv_transform(self, uv_layer, scale, angle):
|
||
"""应用UV变换"""
|
||
try:
|
||
if not scale and not angle:
|
||
return
|
||
|
||
scale_factor = scale if scale else 1.0
|
||
angle_rad = math.radians(angle) if angle else 0.0
|
||
|
||
# 变换UV坐标
|
||
for loop in uv_layer.data:
|
||
u, v = loop.uv
|
||
|
||
# 应用缩放
|
||
u *= scale_factor
|
||
v *= scale_factor
|
||
|
||
# 应用旋转
|
||
if angle_rad != 0:
|
||
cos_a = math.cos(angle_rad)
|
||
sin_a = math.sin(angle_rad)
|
||
new_u = u * cos_a - v * sin_a
|
||
new_v = u * sin_a + v * cos_a
|
||
u, v = new_u, new_v
|
||
|
||
loop.uv = (u, v)
|
||
|
||
except Exception as e:
|
||
logger.error(f"应用UV变换失败: {e}")
|
||
|
||
# ==================== 选择管理方法 ====================
|
||
|
||
def sel_clear(self):
|
||
"""清除选择"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
bpy.ops.object.select_all(action='DESELECT')
|
||
|
||
self.__class__._selected_uid = None
|
||
self.__class__._selected_obj = None
|
||
self.__class__._selected_zone = None
|
||
self.__class__._selected_part = None
|
||
|
||
# 清除选择的面、零件和硬件
|
||
for face in self.selected_faces:
|
||
if face:
|
||
self._textured_face(face, False)
|
||
self.selected_faces.clear()
|
||
|
||
for part in self.selected_parts:
|
||
if part:
|
||
self.textured_part(part, False)
|
||
self.selected_parts.clear()
|
||
|
||
for hw in self.selected_hws:
|
||
if hw:
|
||
self._textured_hw(hw, False)
|
||
self.selected_hws.clear()
|
||
|
||
logger.info("选择已清除")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清除选择失败: {e}")
|
||
|
||
def sel_local(self, obj):
|
||
"""选择本地对象"""
|
||
try:
|
||
if not BLENDER_AVAILABLE or not obj:
|
||
return
|
||
|
||
uid = obj.get("sw_uid")
|
||
zid = obj.get("sw_zid")
|
||
typ = obj.get("sw_typ")
|
||
pid = obj.get("sw_pid", -1)
|
||
cp = obj.get("sw_cp", -1)
|
||
|
||
params = {
|
||
"uid": uid,
|
||
"zid": zid
|
||
}
|
||
|
||
# 检查是否已选择
|
||
if typ == "zid":
|
||
if (self.__class__._selected_uid == uid and
|
||
self.__class__._selected_obj == zid):
|
||
return
|
||
elif typ == "cp":
|
||
if (self.__class__._selected_uid == uid and
|
||
(self.__class__._selected_obj == pid or
|
||
self.__class__._selected_obj == cp)):
|
||
return
|
||
else:
|
||
self.sel_clear()
|
||
return
|
||
|
||
# 执行选择
|
||
if typ == "cp" and self.part_mode:
|
||
params["pid"] = pid
|
||
params["cp"] = cp
|
||
self._sel_part_local(params)
|
||
else:
|
||
params["pid"] = -1
|
||
params["cp"] = -1
|
||
self._sel_zone_local(params)
|
||
|
||
# 发送选择事件
|
||
self._set_cmd("r01", params) # select_client
|
||
|
||
except Exception as e:
|
||
logger.error(f"选择本地对象失败: {e}")
|
||
|
||
def _sel_zone_local(self, data):
|
||
"""选择区域(本地)"""
|
||
try:
|
||
self.sel_clear()
|
||
|
||
uid = data.get("uid")
|
||
zid = data.get("zid")
|
||
zones = self.get_zones({"uid": uid})
|
||
parts = self.get_parts({"uid": uid})
|
||
hardwares = self.get_hardwares({"uid": uid})
|
||
|
||
# 获取子区域
|
||
children = self._get_child_zones(zones, zid, True)
|
||
|
||
for child in children:
|
||
child_id = child.get("zid")
|
||
child_zone = zones.get(child_id)
|
||
leaf = child.get("leaf")
|
||
|
||
if not child_zone:
|
||
continue
|
||
|
||
# 处理零件
|
||
for v_root, part in parts.items():
|
||
if part and part.get("sw_zid") == child_id:
|
||
self.textured_part(part, True)
|
||
|
||
# 处理硬件
|
||
for v_root, hw in hardwares.items():
|
||
if hw and hw.get("sw_zid") == child_id:
|
||
self._textured_hw(hw, True)
|
||
|
||
# 处理区域显示
|
||
if not leaf or self.hide_none:
|
||
child_zone.hide_viewport = True
|
||
else:
|
||
child_zone.hide_viewport = False
|
||
|
||
# 处理区域面
|
||
for face_child in child_zone.children:
|
||
if hasattr(face_child, 'data'):
|
||
self._textured_face(face_child, True)
|
||
|
||
# 设置选择状态
|
||
if child_id == zid:
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = zid
|
||
self.__class__._selected_zone = child_zone
|
||
|
||
except Exception as e:
|
||
logger.error(f"选择区域失败: {e}")
|
||
|
||
def _sel_part_local(self, data):
|
||
"""选择零件(本地)"""
|
||
try:
|
||
self.sel_clear()
|
||
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
uid = data.get("uid")
|
||
cp = data.get("cp")
|
||
|
||
if cp in parts:
|
||
part = parts[cp]
|
||
if part:
|
||
self.textured_part(part, True)
|
||
self.__class__._selected_part = part
|
||
elif cp in hardwares:
|
||
hw = hardwares[cp]
|
||
if hw:
|
||
self._textured_hw(hw, True)
|
||
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = cp
|
||
|
||
except Exception as e:
|
||
logger.error(f"选择零件失败: {e}")
|
||
|
||
def _get_child_zones(self, zones, zip_id, myself=False):
|
||
"""获取子区域"""
|
||
try:
|
||
children = []
|
||
|
||
for zid, entity in zones.items():
|
||
if entity and entity.get("sw_zip") == zip_id:
|
||
grandchildren = self._get_child_zones(zones, zid, False)
|
||
child = {
|
||
"zid": zid,
|
||
"leaf": len(grandchildren) == 0
|
||
}
|
||
children.append(child)
|
||
children.extend(grandchildren)
|
||
|
||
if myself:
|
||
child = {
|
||
"zid": zip_id,
|
||
"leaf": len(children) == 0
|
||
}
|
||
children.append(child)
|
||
|
||
return children
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取子区域失败: {e}")
|
||
return []
|
||
|
||
def _textured_face(self, face, selected):
|
||
"""为面应用纹理"""
|
||
try:
|
||
if selected:
|
||
self.selected_faces.append(face)
|
||
|
||
color = "mat_select" if selected else "mat_normal"
|
||
texture = self.get_texture(color)
|
||
|
||
if texture and hasattr(face, 'data') and face.data:
|
||
if not face.data.materials:
|
||
face.data.materials.append(texture)
|
||
else:
|
||
face.data.materials[0] = texture
|
||
|
||
# 设置背面材质
|
||
if self.back_material or texture.get("alpha", 1.0) < 1.0:
|
||
# 在Blender中设置背面材质
|
||
pass
|
||
|
||
except Exception as e:
|
||
logger.error(f"为面应用纹理失败: {e}")
|
||
|
||
def textured_part(self, part, selected):
|
||
"""为零件应用纹理"""
|
||
try:
|
||
if not part:
|
||
return
|
||
|
||
# 检查是否有预制件
|
||
for child in part.children:
|
||
if (hasattr(child, 'type') and child.type == 'MESH' and
|
||
child.get("sw_typ") == "cp"):
|
||
break
|
||
|
||
if selected:
|
||
self.selected_parts.append(part)
|
||
|
||
# 处理子对象
|
||
for leaf in part.children:
|
||
if not leaf or leaf.get("sw_typ") == "work" or leaf.get("sw_typ") == "pull":
|
||
continue
|
||
|
||
# 处理预制件
|
||
if hasattr(leaf, 'type') and leaf.type == 'MESH':
|
||
if leaf.get("sw_typ") != "cp": # 非零件对象(硬件等)
|
||
continue
|
||
|
||
# 设置可见性
|
||
leaf.hide_viewport = not (
|
||
selected or self.mat_type == MAT_TYPE_NATURE)
|
||
continue
|
||
elif leaf.get("sw_virtual", False): # 虚拟部件
|
||
leaf.hide_viewport = not (
|
||
selected or self.mat_type == MAT_TYPE_NATURE)
|
||
|
||
# 确定材质类型
|
||
nature = None
|
||
if selected:
|
||
nature = "mat_select"
|
||
elif self.mat_type == MAT_TYPE_NATURE:
|
||
mn = leaf.get("sw_mn")
|
||
if mn == 1:
|
||
nature = "mat_obverse" # 门板
|
||
elif mn == 2:
|
||
nature = "mat_reverse" # 柜体
|
||
elif mn == 3:
|
||
nature = "mat_thin" # 背板
|
||
|
||
# 处理面
|
||
for entity in leaf.children:
|
||
if hasattr(entity, 'data') and entity.data:
|
||
color = nature if nature else self._face_color(
|
||
entity, leaf)
|
||
self._textured_surf(entity, self.back_material, color)
|
||
elif hasattr(entity, 'children'): # 组对象
|
||
for entity2 in entity.children:
|
||
if hasattr(entity2, 'data') and entity2.data:
|
||
color = nature if nature else self._face_color(
|
||
entity2, leaf)
|
||
self._textured_surf(
|
||
entity2, self.back_material, color)
|
||
|
||
except Exception as e:
|
||
logger.error(f"为零件应用纹理失败: {e}")
|
||
|
||
def _face_color(self, face, leaf):
|
||
"""获取面的颜色"""
|
||
try:
|
||
# 检查差异面
|
||
if face.get("sw_differ", False):
|
||
return "mat_default"
|
||
|
||
# 检查正反面类型
|
||
if self.mat_type == MAT_TYPE_OBVERSE:
|
||
typ = face.get("sw_typ")
|
||
if typ in ["o", "e1"]:
|
||
return "mat_obverse"
|
||
elif typ == "e2":
|
||
return "mat_thin"
|
||
elif typ in ["r", "e0"]:
|
||
return "mat_reverse"
|
||
|
||
# 获取保存的颜色
|
||
color = face.get("sw_ckey")
|
||
if not color:
|
||
color = leaf.get("sw_ckey")
|
||
|
||
return color
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取面颜色失败: {e}")
|
||
return "mat_default"
|
||
|
||
def _textured_hw(self, hw, selected):
|
||
"""为硬件应用纹理"""
|
||
try:
|
||
if not hw:
|
||
return
|
||
|
||
# 跳过预制件
|
||
if hasattr(hw, 'type') and hw.type == 'MESH':
|
||
return
|
||
|
||
if selected:
|
||
self.selected_hws.append(hw)
|
||
|
||
color = "mat_select" if selected else hw.get(
|
||
"sw_ckey", "mat_default")
|
||
texture = self.get_texture(color)
|
||
|
||
# 处理硬件的所有面
|
||
for entity in hw.children:
|
||
if hasattr(entity, 'data') and entity.data:
|
||
if texture:
|
||
if not entity.data.materials:
|
||
entity.data.materials.append(texture)
|
||
else:
|
||
entity.data.materials[0] = texture
|
||
|
||
except Exception as e:
|
||
logger.error(f"为硬件应用纹理失败: {e}")
|
||
|
||
# ==================== 删除管理方法 ====================
|
||
|
||
def c09(self, data: Dict[str, Any]):
|
||
"""del_entity - 删除实体 - 修复对象引用问题"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.warning("Blender 不可用,跳过删除操作")
|
||
return
|
||
|
||
def delete_entities():
|
||
try:
|
||
# 确保在主线程中执行
|
||
if threading.current_thread() != threading.main_thread():
|
||
logger.warning("删除操作转移到主线程执行")
|
||
return
|
||
|
||
# 清除所有选择
|
||
self.sel_clear()
|
||
|
||
uid = data.get("uid")
|
||
typ = data.get("typ") # uid/zid/cp/work/hw/pull/wall
|
||
oid = data.get("oid", 0)
|
||
|
||
logger.info(f"🗑️ 删除实体: uid={uid}, typ={typ}, oid={oid}")
|
||
|
||
if typ == "uid":
|
||
# 删除整个单元的所有内容
|
||
self._del_unit_entities_safe(uid)
|
||
elif typ == "wall":
|
||
# 删除墙体实体
|
||
self._del_wall_entity_safe(data, uid, oid)
|
||
else:
|
||
# 删除标准实体
|
||
self._del_standard_entities_safe(data, uid, typ, oid)
|
||
|
||
# 清理标签
|
||
self._clear_labels_safe()
|
||
|
||
# 强制更新视图
|
||
bpy.context.view_layer.update()
|
||
|
||
logger.info(f"✅ 删除实体完成: uid={uid}, typ={typ}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除实体失败: {e}")
|
||
import traceback
|
||
logger.error(traceback.format_exc())
|
||
|
||
# 在主线程中执行删除操作
|
||
execute_in_main_thread(delete_entities)
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除实体失败: {e}")
|
||
|
||
def _del_unit_entities_safe(self, uid: str):
|
||
"""安全删除单元所有实体"""
|
||
try:
|
||
if not uid:
|
||
return
|
||
|
||
logger.info(f"删除单元所有实体: {uid}")
|
||
|
||
# 收集要删除的对象
|
||
objects_to_delete = []
|
||
|
||
for obj in list(bpy.data.objects):
|
||
try:
|
||
# 检查对象是否仍然有效
|
||
if not self._is_object_valid(obj):
|
||
continue
|
||
|
||
# 检查是否属于该单元
|
||
obj_uid = obj.get("sw_uid")
|
||
if obj_uid == uid:
|
||
objects_to_delete.append(obj)
|
||
logger.debug(f"标记删除对象: {obj.name}")
|
||
|
||
except Exception as e:
|
||
logger.warning(f"检查对象失败: {e}")
|
||
continue
|
||
|
||
# 安全删除对象
|
||
deleted_count = 0
|
||
for obj in objects_to_delete:
|
||
try:
|
||
if self._delete_object_safe(obj):
|
||
deleted_count += 1
|
||
except Exception as e:
|
||
logger.error(
|
||
f"删除对象失败 {obj.name if hasattr(obj, 'name') else 'unknown'}: {e}")
|
||
|
||
logger.info(
|
||
f"单元删除完成: {deleted_count}/{len(objects_to_delete)} 个对象")
|
||
|
||
# 清理相关数据结构
|
||
self._cleanup_unit_data(uid)
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除单元实体失败: {e}")
|
||
|
||
def _del_standard_entities_safe(self, data: Dict[str, Any], uid: str, typ: str, oid: int):
|
||
"""安全删除标准实体"""
|
||
try:
|
||
logger.info(f"删除标准实体: uid={uid}, typ={typ}, oid={oid}")
|
||
|
||
# 获取相应的实体集合
|
||
entities = None
|
||
if typ == "zid":
|
||
entities = self.get_zones(data)
|
||
elif typ == "cp":
|
||
entities = self.get_parts(data)
|
||
elif typ == "hw":
|
||
entities = self.get_hardwares(data)
|
||
|
||
if entities and oid in entities:
|
||
obj = entities[oid]
|
||
if self._delete_object_safe(obj):
|
||
del entities[oid]
|
||
logger.info(f"✅ 删除实体成功: {typ}={oid}")
|
||
else:
|
||
logger.warning(f"实体删除失败: {typ}={oid}")
|
||
else:
|
||
logger.warning(f"未找到实体: {typ}={oid}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除标准实体失败: {e}")
|
||
|
||
def _is_object_valid(self, obj) -> bool:
|
||
"""检查对象是否仍然有效"""
|
||
try:
|
||
# 尝试访问对象的基本属性
|
||
_ = obj.name
|
||
_ = obj.type
|
||
|
||
# 检查对象是否仍在数据中
|
||
return obj.name in bpy.data.objects
|
||
|
||
except (ReferenceError, AttributeError):
|
||
# 对象已被删除或无效
|
||
return False
|
||
except Exception:
|
||
# 其他错误,假设对象无效
|
||
return False
|
||
|
||
def _delete_object_safe(self, obj) -> bool:
|
||
"""安全删除对象"""
|
||
try:
|
||
if not self._is_object_valid(obj):
|
||
logger.debug(f"对象已无效,跳过删除")
|
||
return True
|
||
|
||
obj_name = obj.name
|
||
obj_type = obj.type
|
||
|
||
# 递归删除子对象
|
||
children = list(obj.children)
|
||
for child in children:
|
||
if self._is_object_valid(child):
|
||
self._delete_object_safe(child)
|
||
|
||
# 从所有集合中移除对象
|
||
try:
|
||
for collection in list(obj.users_collection):
|
||
if collection and collection.name in bpy.data.collections:
|
||
collection.objects.unlink(obj)
|
||
except Exception as e:
|
||
logger.warning(f"从集合移除对象失败: {e}")
|
||
|
||
# 删除对象的网格数据(如果有)
|
||
mesh_data = None
|
||
if obj_type == 'MESH' and obj.data:
|
||
mesh_data = obj.data
|
||
|
||
# 删除对象
|
||
try:
|
||
bpy.data.objects.remove(obj, do_unlink=True)
|
||
logger.debug(f"删除对象成功: {obj_name}")
|
||
except Exception as e:
|
||
logger.error(f"删除对象失败: {obj_name}, 错误: {e}")
|
||
return False
|
||
|
||
# 删除网格数据
|
||
if mesh_data and mesh_data.name in bpy.data.meshes:
|
||
try:
|
||
bpy.data.meshes.remove(mesh_data, do_unlink=True)
|
||
logger.debug(f"删除网格数据成功: {mesh_data.name}")
|
||
except Exception as e:
|
||
logger.warning(f"删除网格数据失败: {e}")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"安全删除对象失败: {e}")
|
||
return False
|
||
|
||
def _cleanup_unit_data(self, uid: str):
|
||
"""清理单元相关数据"""
|
||
try:
|
||
# 清理单元变换数据
|
||
if hasattr(self, 'unit_trans') and uid in self.unit_trans:
|
||
del self.unit_trans[uid]
|
||
|
||
# 清理选择状态
|
||
if self.__class__._selected_uid == uid:
|
||
self.__class__._selected_uid = None
|
||
self.__class__._selected_obj = None
|
||
self.__class__._selected_zone = None
|
||
self.__class__._selected_part = None
|
||
|
||
logger.debug(f"单元数据清理完成: {uid}")
|
||
|
||
except Exception as e:
|
||
logger.warning(f"清理单元数据失败: {e}")
|
||
|
||
def _clear_labels_safe(self):
|
||
"""安全清理标签"""
|
||
try:
|
||
# 查找并删除标签对象
|
||
labels_to_delete = []
|
||
for obj in list(bpy.data.objects):
|
||
try:
|
||
if not self._is_object_valid(obj):
|
||
continue
|
||
|
||
if (obj.name.startswith("Label_") or
|
||
obj.name.startswith("Dimension_") or
|
||
obj.get("sw_typ") in ["label", "dimension"]):
|
||
labels_to_delete.append(obj)
|
||
|
||
except Exception:
|
||
continue
|
||
|
||
# 删除标签
|
||
for label_obj in labels_to_delete:
|
||
self._delete_object_safe(label_obj)
|
||
|
||
if labels_to_delete:
|
||
logger.debug(f"清理标签完成: {len(labels_to_delete)} 个")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理标签失败: {e}")
|
||
|
||
def c0a(self, data: Dict[str, Any]):
|
||
"""del_machining - 删除加工 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def delete_machining():
|
||
try:
|
||
uid = data.get("uid")
|
||
typ = data.get("typ")
|
||
oid = data.get("oid")
|
||
special = data.get("special", 1)
|
||
|
||
if uid not in self.machinings:
|
||
return True
|
||
|
||
machinings = self.machinings[uid]
|
||
valid_machinings = []
|
||
|
||
for machining in machinings:
|
||
should_delete = False
|
||
|
||
if machining and hasattr(machining, 'name') and machining.name in bpy.data.objects:
|
||
if typ == "uid":
|
||
should_delete = True
|
||
else:
|
||
attr_value = machining.get(f"sw_{typ}")
|
||
should_delete = (attr_value == oid)
|
||
|
||
if should_delete and special == 0:
|
||
machining_special = machining.get(
|
||
"sw_special", 0)
|
||
should_delete = (machining_special == 0)
|
||
|
||
if should_delete:
|
||
try:
|
||
bpy.data.objects.remove(
|
||
machining, do_unlink=True)
|
||
logger.debug(f"已删除加工: {machining.name}")
|
||
except Exception as e:
|
||
logger.warning(f"删除加工失败: {e}")
|
||
else:
|
||
valid_machinings.append(machining)
|
||
|
||
self.machinings[uid] = valid_machinings
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除加工失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行删除操作
|
||
success = execute_in_main_thread(delete_machining)
|
||
|
||
if success:
|
||
logger.info(f"✅ 加工删除完成")
|
||
else:
|
||
logger.error(f"❌ 加工删除失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除加工失败: {e}")
|
||
|
||
def c0c(self, data: Dict[str, Any]):
|
||
"""del_dim - 删除尺寸标注 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def delete_dimensions():
|
||
try:
|
||
uid = data.get("uid")
|
||
|
||
if uid in self.dimensions:
|
||
dimensions = self.dimensions[uid]
|
||
for dim in dimensions:
|
||
try:
|
||
if dim and hasattr(dim, 'name') and dim.name in bpy.data.objects:
|
||
bpy.data.objects.remove(
|
||
dim, do_unlink=True)
|
||
logger.debug(f"已删除尺寸标注: {dim.name}")
|
||
except Exception as e:
|
||
logger.warning(f"删除尺寸标注失败: {e}")
|
||
|
||
del self.dimensions[uid]
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除尺寸标注失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行删除操作
|
||
success = execute_in_main_thread(delete_dimensions)
|
||
|
||
if success:
|
||
logger.info(f"✅ 尺寸标注删除完成")
|
||
else:
|
||
logger.error(f"❌ 尺寸标注删除失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除尺寸标注失败: {e}")
|
||
|
||
# ==================== 视图管理方法 ====================
|
||
|
||
def c15(self, data: Dict[str, Any]):
|
||
"""sel_unit - 选择单元"""
|
||
try:
|
||
self.sel_clear()
|
||
zones = self.get_zones(data)
|
||
|
||
for zid, zone in zones.items():
|
||
if zone and zone.name in bpy.data.objects:
|
||
leaf = self._is_leaf_zone(zid, zones)
|
||
zone.hide_viewport = leaf and self.hide_none
|
||
|
||
except Exception as e:
|
||
logger.error(f"选择单元失败: {e}")
|
||
|
||
def _is_leaf_zone(self, zip_id, zones):
|
||
"""检查是否是叶子区域"""
|
||
try:
|
||
for zid, zone in zones.items():
|
||
if zone and zone.get("sw_zip") == zip_id:
|
||
return False
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查叶子区域失败: {e}")
|
||
return True
|
||
|
||
def c16(self, data: Dict[str, Any]):
|
||
"""sel_zone - 选择区域"""
|
||
try:
|
||
self._sel_zone_local(data)
|
||
except Exception as e:
|
||
logger.error(f"选择区域失败: {e}")
|
||
|
||
def c17(self, data: Dict[str, Any]):
|
||
"""sel_elem - 选择元素"""
|
||
try:
|
||
if self.part_mode:
|
||
self._sel_part_parent(data)
|
||
else:
|
||
self._sel_zone_local(data)
|
||
except Exception as e:
|
||
logger.error(f"选择元素失败: {e}")
|
||
|
||
def _sel_part_parent(self, data):
|
||
"""选择零件父级"""
|
||
try:
|
||
self.sel_clear()
|
||
zones = self.get_zones(data)
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
uid = data.get("uid")
|
||
zid = data.get("zid")
|
||
pid = data.get("pid")
|
||
|
||
parted = False
|
||
|
||
# 选择零件
|
||
for v_root, part in parts.items():
|
||
if part and part.get("sw_pid") == pid:
|
||
self.textured_part(part, True)
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = pid
|
||
parted = True
|
||
|
||
# 选择硬件
|
||
for v_root, hw in hardwares.items():
|
||
if hw and hw.get("sw_pid") == pid:
|
||
self._textured_hw(hw, True)
|
||
|
||
# 处理区域
|
||
children = self._get_child_zones(zones, zid, True)
|
||
for child in children:
|
||
child_id = child.get("zid")
|
||
child_zone = zones.get(child_id)
|
||
leaf = child.get("leaf")
|
||
|
||
if not child_zone:
|
||
continue
|
||
|
||
if leaf and child_id == zid:
|
||
if not self.hide_none:
|
||
child_zone.hide_viewport = False
|
||
for face in child_zone.children:
|
||
if hasattr(face, 'data'):
|
||
selected = face.get("sw_child") == pid
|
||
self._textured_face(face, selected)
|
||
if selected:
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = pid
|
||
elif not leaf and child_id == zid:
|
||
if not parted:
|
||
child_zone.hide_viewport = False
|
||
for face in child_zone.children:
|
||
if (hasattr(face, 'data') and
|
||
face.get("sw_child") == pid):
|
||
self._textured_face(face, True)
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = pid
|
||
elif leaf and not self.hide_none:
|
||
child_zone.hide_viewport = False
|
||
for face in child_zone.children:
|
||
if hasattr(face, 'data'):
|
||
self._textured_face(face, False)
|
||
else:
|
||
child_zone.hide_viewport = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"选择零件父级失败: {e}")
|
||
|
||
# ==================== 门和抽屉功能方法 ====================
|
||
|
||
def c10(self, data: Dict[str, Any]):
|
||
"""set_doorinfo - 设置门信息"""
|
||
try:
|
||
parts = self.get_parts(data)
|
||
doors = data.get("drs", [])
|
||
|
||
for door in doors:
|
||
root = door.get("cp", 0)
|
||
door_dir = door.get("dov", "")
|
||
ps = Point3d.parse(door.get("ps", "(0,0,0)"))
|
||
pe = Point3d.parse(door.get("pe", "(0,0,0)"))
|
||
offset = Vector3d.parse(door.get("off", "(0,0,0)"))
|
||
|
||
if root > 0 and root in parts:
|
||
part = parts[root]
|
||
part["sw_door_dir"] = door_dir
|
||
part["sw_door_ps"] = (ps.x, ps.y, ps.z)
|
||
part["sw_door_pe"] = (pe.x, pe.y, pe.z)
|
||
part["sw_door_offset"] = (offset.x, offset.y, offset.z)
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置门信息失败: {e}")
|
||
|
||
def c1a(self, data: Dict[str, Any]):
|
||
"""open_doors - 开门"""
|
||
try:
|
||
uid = data.get("uid")
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
mydoor = data.get("cp", 0)
|
||
value = data.get("v", False)
|
||
|
||
for root, part in parts.items():
|
||
if mydoor != 0 and mydoor != root:
|
||
continue
|
||
|
||
door_type = part.get("sw_door", 0)
|
||
if door_type <= 0:
|
||
continue
|
||
|
||
is_open = part.get("sw_door_open", False)
|
||
if is_open == value:
|
||
continue
|
||
|
||
if door_type not in [10, 15]:
|
||
continue
|
||
|
||
# 获取门的参数
|
||
door_ps = part.get("sw_door_ps")
|
||
door_pe = part.get("sw_door_pe")
|
||
door_off = part.get("sw_door_offset")
|
||
|
||
if not all([door_ps, door_pe, door_off]):
|
||
continue
|
||
|
||
# 应用单位变换
|
||
if uid in self.unit_trans:
|
||
trans = self.unit_trans[uid]
|
||
door_ps = self._transform_point(door_ps, trans)
|
||
door_pe = self._transform_point(door_pe, trans)
|
||
door_off = self._transform_vector(door_off, trans)
|
||
|
||
# 计算变换
|
||
if door_type == 10: # 平开门
|
||
trans_a = self._calculate_swing_door_transform(
|
||
door_ps, door_pe, door_off)
|
||
else: # 推拉门
|
||
trans_a = self._calculate_slide_door_transform(door_off)
|
||
|
||
if is_open:
|
||
trans_a = self._invert_transform(trans_a)
|
||
|
||
# 应用变换
|
||
self._apply_transformation(part, trans_a)
|
||
part["sw_door_open"] = not is_open
|
||
|
||
# 变换相关硬件
|
||
for key, hardware in hardwares.items():
|
||
if hardware.get("sw_part") == root:
|
||
self._apply_transformation(hardware, trans_a)
|
||
|
||
except Exception as e:
|
||
logger.error(f"开门失败: {e}")
|
||
|
||
def c1b(self, data: Dict[str, Any]):
|
||
"""slide_drawers - 滑动抽屉"""
|
||
try:
|
||
uid = data.get("uid")
|
||
zones = self.get_zones(data)
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
# 收集抽屉信息
|
||
drawers = {}
|
||
depths = {}
|
||
|
||
for root, part in parts.items():
|
||
drawer_type = part.get("sw_drawer", 0)
|
||
if drawer_type > 0:
|
||
if drawer_type == 70: # DR_DP
|
||
pid = part.get("sw_pid")
|
||
drawer_dir = part.get("sw_drawer_dir")
|
||
if pid and drawer_dir:
|
||
drawers[pid] = Vector3d(
|
||
drawer_dir[0], drawer_dir[1], drawer_dir[2])
|
||
|
||
if drawer_type in [73, 74]: # DR_LP/DR_RP
|
||
pid = part.get("sw_pid")
|
||
dr_depth = part.get("sw_dr_depth", 300)
|
||
if pid:
|
||
depths[pid] = dr_depth
|
||
|
||
# 计算偏移量
|
||
offsets = {}
|
||
for drawer, direction in drawers.items():
|
||
zone = zones.get(drawer)
|
||
if not zone:
|
||
continue
|
||
|
||
dr_depth = depths.get(drawer, 300) * 0.001 # mm to meters
|
||
vector = Vector3d(direction.x, direction.y, direction.z)
|
||
vector_length = math.sqrt(
|
||
vector.x**2 + vector.y**2 + vector.z**2)
|
||
if vector_length > 0:
|
||
scale = (dr_depth * 0.9) / vector_length
|
||
vector = Vector3d(vector.x * scale,
|
||
vector.y * scale, vector.z * scale)
|
||
|
||
# 应用单位变换
|
||
if uid in self.unit_trans:
|
||
vector = self._transform_vector(
|
||
(vector.x, vector.y, vector.z), self.unit_trans[uid])
|
||
|
||
offsets[drawer] = vector
|
||
|
||
# 应用抽屉变换
|
||
value = data.get("v", False)
|
||
for drawer, vector in offsets.items():
|
||
zone = zones.get(drawer)
|
||
if not zone:
|
||
continue
|
||
|
||
is_open = zone.get("sw_drawer_open", False)
|
||
if is_open == value:
|
||
continue
|
||
|
||
# 计算变换
|
||
trans_a = self._calculate_translation_transform(vector)
|
||
if is_open:
|
||
trans_a = self._invert_transform(trans_a)
|
||
|
||
# 应用到区域
|
||
zone["sw_drawer_open"] = not is_open
|
||
|
||
# 变换零件
|
||
for root, part in parts.items():
|
||
if part.get("sw_pid") == drawer:
|
||
self._apply_transformation(part, trans_a)
|
||
|
||
# 变换硬件
|
||
for root, hardware in hardwares.items():
|
||
if hardware.get("sw_pid") == drawer:
|
||
self._apply_transformation(hardware, trans_a)
|
||
|
||
except Exception as e:
|
||
logger.error(f"滑动抽屉失败: {e}")
|
||
|
||
# ==================== 视图控制方法 ====================
|
||
|
||
def c18(self, data: Dict[str, Any]):
|
||
"""hide_door - 隐藏门"""
|
||
try:
|
||
visible = not data.get("v", False)
|
||
|
||
if self.door_layer:
|
||
# 在Blender中控制集合可见性
|
||
self.door_layer.hide_viewport = not visible
|
||
|
||
if self.door_labels:
|
||
self.door_labels.hide_viewport = not visible
|
||
|
||
except Exception as e:
|
||
logger.error(f"隐藏门失败: {e}")
|
||
|
||
def c28(self, data: Dict[str, Any]):
|
||
"""hide_drawer - 隐藏抽屉"""
|
||
try:
|
||
visible = not data.get("v", False)
|
||
|
||
if self.drawer_layer:
|
||
self.drawer_layer.hide_viewport = not visible
|
||
|
||
if self.door_labels:
|
||
self.door_labels.hide_viewport = not visible
|
||
|
||
except Exception as e:
|
||
logger.error(f"隐藏抽屉失败: {e}")
|
||
|
||
def c0f(self, data: Dict[str, Any]):
|
||
"""view_front - 前视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
# 设置前视图
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(
|
||
override, type='FRONT')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"前视图失败: {e}")
|
||
|
||
def c23(self, data: Dict[str, Any]):
|
||
"""view_left - 左视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(override, type='LEFT')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"左视图失败: {e}")
|
||
|
||
def c24(self, data: Dict[str, Any]):
|
||
"""view_right - 右视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(
|
||
override, type='RIGHT')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"右视图失败: {e}")
|
||
|
||
def c25(self, data: Dict[str, Any]):
|
||
"""view_back - 后视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(override, type='BACK')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"后视图失败: {e}")
|
||
|
||
# ==================== 其他业务方法 ====================
|
||
|
||
def c00(self, data: Dict[str, Any]):
|
||
"""add_folder - 添加文件夹"""
|
||
try:
|
||
ref_v = data.get("ref_v", 0)
|
||
if ref_v > 0:
|
||
# 在实际应用中需要实现文件夹管理逻辑
|
||
logger.info(f"添加文件夹: ref_v={ref_v}")
|
||
except Exception as e:
|
||
logger.error(f"添加文件夹失败: {e}")
|
||
|
||
def c01(self, data: Dict[str, Any]):
|
||
"""edit_unit - 编辑单元"""
|
||
try:
|
||
unit_id = data.get("unit_id")
|
||
|
||
if "params" in data:
|
||
params = data["params"]
|
||
|
||
if "trans" in params:
|
||
jtran = params.pop("trans")
|
||
trans = Transformation.parse(jtran)
|
||
self.unit_trans[unit_id] = trans
|
||
time.sleep(0.5) # 等待
|
||
|
||
if unit_id in self.unit_param:
|
||
values = self.unit_param[unit_id]
|
||
values.update(params)
|
||
params = values
|
||
|
||
self.unit_param[unit_id] = params
|
||
|
||
except Exception as e:
|
||
logger.error(f"编辑单元失败: {e}")
|
||
|
||
def c07(self, data: Dict[str, Any]):
|
||
"""add_dim - 添加尺寸标注 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
uid = data.get("uid")
|
||
|
||
def create_dimensions():
|
||
try:
|
||
dims = data.get("dims", [])
|
||
dimensions = []
|
||
|
||
for dim_data in dims:
|
||
p1 = Point3d.parse(dim_data.get("p1", "(0,0,0)"))
|
||
p2 = Point3d.parse(dim_data.get("p2", "(0,0,0)"))
|
||
direction = Vector3d.parse(
|
||
dim_data.get("dir", "(0,0,1)"))
|
||
text = dim_data.get("text", "")
|
||
|
||
dimension = self._create_dimension(
|
||
p1, p2, direction, text)
|
||
if dimension:
|
||
dimensions.append(dimension)
|
||
|
||
# 存储尺寸标注
|
||
if uid not in self.dimensions:
|
||
self.dimensions[uid] = []
|
||
self.dimensions[uid].extend(dimensions)
|
||
|
||
for dimension in dimensions:
|
||
memory_manager.register_object(dimension)
|
||
|
||
return len(dimensions)
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建尺寸标注失败: {e}")
|
||
return 0
|
||
|
||
# 在主线程中执行尺寸标注创建
|
||
count = execute_in_main_thread(create_dimensions)
|
||
|
||
if count > 0:
|
||
logger.info(f"✅ 成功创建尺寸标注: uid={uid}, count={count}")
|
||
else:
|
||
logger.error(f"❌ 尺寸标注创建失败: uid={uid}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 添加尺寸标注失败: {e}")
|
||
|
||
def c0d(self, data: Dict[str, Any]):
|
||
"""parts_seqs - 零件序列"""
|
||
try:
|
||
parts = self.get_parts(data)
|
||
seqs = data.get("seqs", [])
|
||
|
||
for d in seqs:
|
||
root = d.get("cp")
|
||
seq = d.get("seq")
|
||
pos = d.get("pos")
|
||
name = d.get("name")
|
||
size = d.get("size")
|
||
mat = d.get("mat")
|
||
|
||
e_part = parts.get(root)
|
||
if e_part:
|
||
e_part["sw_seq"] = seq
|
||
e_part["sw_pos"] = pos
|
||
if name:
|
||
e_part["sw_name"] = name
|
||
if size:
|
||
e_part["sw_size"] = size
|
||
if mat:
|
||
e_part["sw_mat"] = mat
|
||
|
||
except Exception as e:
|
||
logger.error(f"零件序列失败: {e}")
|
||
|
||
def c0e(self, data: Dict[str, Any]):
|
||
"""explode_zones - 爆炸视图"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 清理标签
|
||
self._clear_labels(self.labels)
|
||
self._clear_labels(self.door_labels)
|
||
|
||
uid = data.get("uid")
|
||
zones = self.get_zones(data)
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
# 处理区域爆炸
|
||
jzones = data.get("zones", [])
|
||
for zone in jzones:
|
||
zoneid = zone.get("zid")
|
||
offset = Vector3d.parse(zone.get("vec", "(0,0,0)"))
|
||
|
||
if uid in self.unit_trans:
|
||
offset = self._transform_vector(
|
||
(offset.x, offset.y, offset.z), self.unit_trans[uid])
|
||
|
||
trans_a = self._calculate_translation_transform(offset)
|
||
|
||
if zoneid in zones:
|
||
azone = zones[zoneid]
|
||
self._apply_transformation(azone, trans_a)
|
||
|
||
# 处理零件爆炸
|
||
jparts = data.get("parts", [])
|
||
for jpart in jparts:
|
||
pid = jpart.get("pid")
|
||
offset = Vector3d.parse(jpart.get("vec", "(0,0,0)"))
|
||
|
||
if uid in self.unit_trans:
|
||
offset = self._transform_vector(
|
||
(offset.x, offset.y, offset.z), self.unit_trans[uid])
|
||
|
||
trans_a = self._calculate_translation_transform(offset)
|
||
|
||
# 变换零件
|
||
for root, part in parts.items():
|
||
if part.get("sw_pid") == pid:
|
||
self._apply_transformation(part, trans_a)
|
||
|
||
# 变换硬件
|
||
for root, hardware in hardwares.items():
|
||
if hardware.get("sw_pid") == pid:
|
||
self._apply_transformation(hardware, trans_a)
|
||
|
||
# 添加序号标签
|
||
if data.get("explode", False):
|
||
self._add_part_labels(uid, parts)
|
||
|
||
except Exception as e:
|
||
logger.error(f"爆炸视图失败: {e}")
|
||
|
||
def _add_part_labels(self, uid, parts):
|
||
"""添加零件标签"""
|
||
try:
|
||
for root, part in parts.items():
|
||
center = self._get_object_center(part)
|
||
pos = part.get("sw_pos", 1)
|
||
|
||
# 确定标签方向
|
||
if pos == 1:
|
||
vector = (0, -1, 0) # F
|
||
elif pos == 2:
|
||
vector = (0, 1, 0) # K
|
||
elif pos == 3:
|
||
vector = (-1, 0, 0) # L
|
||
elif pos == 4:
|
||
vector = (1, 0, 0) # R
|
||
elif pos == 5:
|
||
vector = (0, 0, -1) # B
|
||
else:
|
||
vector = (0, 0, 1) # T
|
||
|
||
# 应用单位变换
|
||
if uid in self.unit_trans:
|
||
vector = self._transform_vector(
|
||
vector, self.unit_trans[uid])
|
||
|
||
# 创建文本标签
|
||
ord_seq = part.get("sw_seq", 0)
|
||
text_obj = self._create_text_label(
|
||
str(ord_seq), center, vector)
|
||
|
||
if text_obj:
|
||
# 根据图层选择父对象
|
||
if self._is_in_door_layer(part):
|
||
text_obj.parent = self.door_labels
|
||
else:
|
||
text_obj.parent = self.labels
|
||
|
||
except Exception as e:
|
||
logger.error(f"添加零件标签失败: {e}")
|
||
|
||
def c12(self, data: Dict[str, Any]):
|
||
"""add_contour - 添加轮廓 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def create_contour():
|
||
try:
|
||
self.added_contour = True
|
||
surf = data.get("surf", {})
|
||
|
||
contour = self._create_contour_from_surf(surf)
|
||
if contour:
|
||
memory_manager.register_object(contour)
|
||
return True
|
||
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建轮廓失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行轮廓创建
|
||
success = execute_in_main_thread(create_contour)
|
||
|
||
if success:
|
||
logger.info(f"✅ 成功创建轮廓")
|
||
else:
|
||
logger.error(f"❌ 轮廓创建失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 添加轮廓失败: {e}")
|
||
|
||
def add_surf(self, data: Dict[str, Any]):
|
||
"""add_surf - 添加表面"""
|
||
try:
|
||
surf = data.get("surf", {})
|
||
self.create_face(bpy.context.scene, surf)
|
||
except Exception as e:
|
||
logger.error(f"添加表面失败: {e}")
|
||
|
||
def c13(self, data: Dict[str, Any]):
|
||
"""save_pixmap - 保存像素图 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def save_pixmap():
|
||
try:
|
||
uid = data.get("uid")
|
||
file_path = data.get("file")
|
||
|
||
# 设置渲染参数
|
||
bpy.context.scene.render.filepath = file_path
|
||
bpy.context.scene.render.image_settings.file_format = 'PNG'
|
||
|
||
# 渲染当前视图
|
||
bpy.ops.render.render(write_still=True)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"保存像素图失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行渲染操作
|
||
success = execute_in_main_thread(save_pixmap)
|
||
|
||
if success:
|
||
logger.info(f"✅ 成功保存像素图")
|
||
else:
|
||
logger.error(f"❌ 像素图保存失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 保存像素图失败: {e}")
|
||
|
||
def c14(self, data: Dict[str, Any]):
|
||
"""pre_save_pixmap - 预保存像素图 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def pre_save_pixmap():
|
||
try:
|
||
self.sel_clear()
|
||
|
||
# 设置视图
|
||
if hasattr(bpy.context, 'space_data') and bpy.context.space_data:
|
||
bpy.context.space_data.show_gizmo = False
|
||
bpy.context.space_data.show_overlays = False
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"预保存像素图失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行预处理操作
|
||
success = execute_in_main_thread(pre_save_pixmap)
|
||
|
||
if success:
|
||
logger.info(f"✅ 预保存像素图完成")
|
||
else:
|
||
logger.error(f"❌ 预保存像素图失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 预保存像素图失败: {e}")
|
||
|
||
def show_message(self, data: Dict[str, Any]):
|
||
"""显示消息"""
|
||
try:
|
||
message = data.get("message", "")
|
||
logger.info(f"显示消息: {message}")
|
||
|
||
# 在Blender中显示消息
|
||
if BLENDER_AVAILABLE:
|
||
# 可以使用报告系统
|
||
pass
|
||
|
||
except Exception as e:
|
||
logger.error(f"显示消息失败: {e}")
|
||
|
||
# ==================== 辅助方法 ====================
|
||
|
||
def _set_cmd(self, cmd, params):
|
||
"""设置命令(发送到客户端)"""
|
||
try:
|
||
# 在实际应用中需要实现客户端通信逻辑
|
||
logger.info(f"发送命令: {cmd}, 参数: {params}")
|
||
except Exception as e:
|
||
logger.error(f"设置命令失败: {e}")
|
||
|
||
def _clear_labels(self, label_obj):
|
||
"""清理标签"""
|
||
try:
|
||
if label_obj and BLENDER_AVAILABLE:
|
||
for child in label_obj.children:
|
||
bpy.data.objects.remove(child, do_unlink=True)
|
||
except Exception as e:
|
||
logger.error(f"清理标签失败: {e}")
|
||
|
||
# ==================== 属性访问器 ====================
|
||
|
||
@classmethod
|
||
def selected_uid(cls):
|
||
return cls._selected_uid
|
||
|
||
@classmethod
|
||
def selected_zone(cls):
|
||
return cls._selected_zone
|
||
|
||
@classmethod
|
||
def selected_part(cls):
|
||
return cls._selected_part
|
||
|
||
@classmethod
|
||
def selected_obj(cls):
|
||
return cls._selected_obj
|
||
|
||
@classmethod
|
||
def server_path(cls):
|
||
return cls._server_path
|
||
|
||
@classmethod
|
||
def default_zone(cls):
|
||
return cls._default_zone
|
||
|
||
# ==================== 清理和销毁 ====================
|
||
|
||
def shutdown(self):
|
||
"""关闭系统"""
|
||
try:
|
||
logger.info("开始关闭SUWood系统")
|
||
|
||
# 执行最终清理
|
||
self.force_cleanup()
|
||
|
||
# 清理所有缓存
|
||
self.mesh_cache.clear()
|
||
self.material_cache.clear()
|
||
self.object_references.clear()
|
||
|
||
# 清理数据结构
|
||
self.parts.clear()
|
||
self.zones.clear()
|
||
self.hardwares.clear()
|
||
self.machinings.clear()
|
||
self.dimensions.clear()
|
||
self.textures.clear()
|
||
self.unit_param.clear()
|
||
self.unit_trans.clear()
|
||
|
||
logger.info("✅ SUWood系统关闭完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"关闭系统失败: {e}")
|
||
|
||
def __del__(self):
|
||
"""析构函数"""
|
||
try:
|
||
self.shutdown()
|
||
except:
|
||
pass
|
||
|
||
# ==================== 内存管理方法(保持原有的优化) ====================
|
||
|
||
def force_cleanup(self):
|
||
"""强制清理"""
|
||
try:
|
||
logger.info("开始强制清理")
|
||
|
||
# 清理孤立数据
|
||
cleanup_count = memory_manager.cleanup_orphaned_data()
|
||
|
||
# 清理缓存
|
||
self.mesh_cache.clear()
|
||
|
||
# 清理过期的对象引用
|
||
current_time = time.time()
|
||
expired_refs = []
|
||
for obj_name, ref_info in self.object_references.items():
|
||
if current_time - ref_info.get('creation_time', 0) > 3600: # 1小时
|
||
expired_refs.append(obj_name)
|
||
|
||
for obj_name in expired_refs:
|
||
del self.object_references[obj_name]
|
||
|
||
# 强制垃圾回收
|
||
gc.collect()
|
||
|
||
# 更新依赖图
|
||
if BLENDER_AVAILABLE:
|
||
self._update_dependency_graph(full_update=True)
|
||
|
||
logger.info(
|
||
f"强制清理完成: 清理了 {cleanup_count} 个数据块,{len(expired_refs)} 个过期引用")
|
||
|
||
except Exception as e:
|
||
logger.error(f"强制清理失败: {e}")
|
||
|
||
def _update_dependency_graph(self, full_update: bool = False):
|
||
"""更新Blender依赖图"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
if full_update:
|
||
logger.info("执行全局依赖图更新")
|
||
bpy.context.view_layer.update()
|
||
bpy.context.evaluated_depsgraph_get().update()
|
||
|
||
# 刷新视图
|
||
try:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type in ['VIEW_3D', 'OUTLINER']:
|
||
area.tag_redraw()
|
||
except:
|
||
pass
|
||
|
||
logger.info("全局依赖图更新完成")
|
||
else:
|
||
# 快速更新
|
||
bpy.context.view_layer.update()
|
||
|
||
except Exception as e:
|
||
logger.error(f"依赖图更新失败: {e}")
|
||
|
||
def get_creation_stats(self) -> Dict[str, Any]:
|
||
"""获取创建统计信息"""
|
||
try:
|
||
stats = {
|
||
"object_references": len(self.object_references),
|
||
"mesh_cache_size": len(self.mesh_cache),
|
||
"material_cache_size": len(self.material_cache),
|
||
"memory_manager_stats": memory_manager.creation_stats.copy(),
|
||
"blender_available": BLENDER_AVAILABLE
|
||
}
|
||
|
||
if BLENDER_AVAILABLE:
|
||
stats["total_objects"] = len(bpy.data.objects)
|
||
stats["total_meshes"] = len(bpy.data.meshes)
|
||
stats["total_materials"] = len(bpy.data.materials)
|
||
|
||
return stats
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取统计信息失败: {e}")
|
||
return {"error": str(e)}
|
||
|
||
def diagnose_system_state(self):
|
||
"""诊断系统状态"""
|
||
try:
|
||
logger.info("=== 系统状态诊断 ===")
|
||
|
||
# 内存使用情况
|
||
stats = self.get_creation_stats()
|
||
for key, value in stats.items():
|
||
logger.info(f"{key}: {value}")
|
||
|
||
# 检查潜在问题
|
||
issues = []
|
||
|
||
if BLENDER_AVAILABLE:
|
||
# 检查孤立数据
|
||
orphaned_meshes = [m for m in bpy.data.meshes if m.users == 0]
|
||
if orphaned_meshes:
|
||
issues.append(f"发现 {len(orphaned_meshes)} 个孤立网格")
|
||
|
||
# 检查空网格
|
||
empty_meshes = [m for m in bpy.data.meshes if not m.vertices]
|
||
if empty_meshes:
|
||
issues.append(f"发现 {len(empty_meshes)} 个空网格")
|
||
|
||
# 检查总顶点数
|
||
total_vertices = sum(len(m.vertices) for m in bpy.data.meshes)
|
||
if total_vertices > 1000000: # 100万顶点
|
||
issues.append(f"顶点数量过多: {total_vertices}")
|
||
|
||
if issues:
|
||
logger.warning("发现问题:")
|
||
for issue in issues:
|
||
logger.warning(f" - {issue}")
|
||
else:
|
||
logger.info("✅ 系统状态正常")
|
||
|
||
return issues
|
||
|
||
except Exception as e:
|
||
logger.error(f"系统诊断失败: {e}")
|
||
return [f"诊断失败: {e}"]
|
||
|
||
def get_memory_report(self) -> Dict[str, Any]:
|
||
"""获取内存报告"""
|
||
try:
|
||
report = {
|
||
"timestamp": time.time(),
|
||
"creation_stats": self.get_creation_stats(),
|
||
"system_diagnosis": self.diagnose_system_state(),
|
||
"memory_manager": {
|
||
"object_registry_size": len(memory_manager.object_registry),
|
||
"mesh_registry_size": len(memory_manager.mesh_registry),
|
||
"last_cleanup": memory_manager.last_cleanup_time,
|
||
"cleanup_interval": memory_manager.cleanup_interval
|
||
}
|
||
}
|
||
|
||
if BLENDER_AVAILABLE:
|
||
report["blender_data"] = {
|
||
"objects": len(bpy.data.objects),
|
||
"meshes": len(bpy.data.meshes),
|
||
"materials": len(bpy.data.materials),
|
||
"textures": len(bpy.data.textures),
|
||
"images": len(bpy.data.images)
|
||
}
|
||
|
||
return report
|
||
|
||
except Exception as e:
|
||
logger.error(f"生成内存报告失败: {e}")
|
||
return {"error": str(e)}
|
||
|
||
# ==================== 几何变换辅助方法 ====================
|
||
|
||
def _transform_point(self, point, trans):
|
||
"""变换点"""
|
||
try:
|
||
if isinstance(point, (list, tuple)) and len(point) >= 3:
|
||
# 简化的变换实现
|
||
return (
|
||
point[0] + trans.origin.x,
|
||
point[1] + trans.origin.y,
|
||
point[2] + trans.origin.z
|
||
)
|
||
return point
|
||
except Exception as e:
|
||
logger.error(f"变换点失败: {e}")
|
||
return point
|
||
|
||
def _transform_vector(self, vector, trans):
|
||
"""变换向量"""
|
||
try:
|
||
if isinstance(vector, (list, tuple)) and len(vector) >= 3:
|
||
# 简化的变换实现
|
||
return (
|
||
vector[0] * trans.x_axis.x,
|
||
vector[1] * trans.y_axis.y,
|
||
vector[2] * trans.z_axis.z
|
||
)
|
||
return vector
|
||
except Exception as e:
|
||
logger.error(f"变换向量失败: {e}")
|
||
return vector
|
||
|
||
def _calculate_swing_door_transform(self, door_ps, door_pe, door_off):
|
||
"""计算平开门变换"""
|
||
try:
|
||
# 计算旋转轴和角度
|
||
axis = (door_pe[0] - door_ps[0], door_pe[1] -
|
||
door_ps[1], door_pe[2] - door_ps[2])
|
||
angle = math.pi / 2 # 90度
|
||
|
||
# 在Blender中创建变换矩阵
|
||
if BLENDER_AVAILABLE:
|
||
import mathutils
|
||
rot_matrix = mathutils.Matrix.Rotation(angle, 4, axis)
|
||
trans_matrix = mathutils.Matrix.Translation(door_off)
|
||
return trans_matrix @ rot_matrix
|
||
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"计算平开门变换失败: {e}")
|
||
return None
|
||
|
||
def _calculate_slide_door_transform(self, door_off):
|
||
"""计算推拉门变换"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
import mathutils
|
||
return mathutils.Matrix.Translation(door_off)
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"计算推拉门变换失败: {e}")
|
||
return None
|
||
|
||
def _calculate_translation_transform(self, vector):
|
||
"""计算平移变换"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
import mathutils
|
||
if isinstance(vector, (list, tuple)):
|
||
return mathutils.Matrix.Translation(vector)
|
||
else:
|
||
return mathutils.Matrix.Translation((vector.x, vector.y, vector.z))
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"计算平移变换失败: {e}")
|
||
return None
|
||
|
||
def _invert_transform(self, transform):
|
||
"""反转变换"""
|
||
try:
|
||
if transform and hasattr(transform, 'inverted'):
|
||
return transform.inverted()
|
||
return transform
|
||
except Exception as e:
|
||
logger.error(f"反转变换失败: {e}")
|
||
return transform
|
||
|
||
def _normalize_vector(self, x, y, z):
|
||
"""归一化向量"""
|
||
try:
|
||
length = math.sqrt(x*x + y*y + z*z)
|
||
if length > 0:
|
||
return (x/length, y/length, z/length)
|
||
return (0, 0, 1)
|
||
except Exception as e:
|
||
logger.error(f"归一化向量失败: {e}")
|
||
return (0, 0, 1)
|
||
|
||
def _get_object_center(self, obj):
|
||
"""获取对象中心"""
|
||
try:
|
||
if BLENDER_AVAILABLE and obj and hasattr(obj, 'location'):
|
||
return obj.location
|
||
return (0, 0, 0)
|
||
except Exception as e:
|
||
logger.error(f"获取对象中心失败: {e}")
|
||
return (0, 0, 0)
|
||
|
||
def _is_in_door_layer(self, part):
|
||
"""检查是否在门图层"""
|
||
try:
|
||
if not part or not self.door_layer:
|
||
return False
|
||
return part in self.door_layer.objects
|
||
except Exception as e:
|
||
logger.error(f"检查门图层失败: {e}")
|
||
return False
|
||
|
||
def _create_text_label(self, text, location, direction):
|
||
"""创建文本标签"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 创建文本对象
|
||
font_curve = bpy.data.curves.new(type="FONT", name="TextLabel")
|
||
font_curve.body = text
|
||
font_obj = bpy.data.objects.new("TextLabel", font_curve)
|
||
|
||
# 设置位置和方向
|
||
font_obj.location = location
|
||
if isinstance(direction, (list, tuple)) and len(direction) >= 3:
|
||
# 简化的方向设置
|
||
font_obj.location = (
|
||
location[0] + direction[0] * 0.1,
|
||
location[1] + direction[1] * 0.1,
|
||
location[2] + direction[2] * 0.1
|
||
)
|
||
|
||
bpy.context.scene.collection.objects.link(font_obj)
|
||
memory_manager.register_object(font_obj)
|
||
|
||
return font_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建文本标签失败: {e}")
|
||
return None
|
||
|
||
def _create_contour_from_surf(self, surf):
|
||
"""从表面创建轮廓"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
xaxis = Vector3d.parse(surf.get("vx", "(1,0,0)"))
|
||
zaxis = Vector3d.parse(surf.get("vz", "(0,0,1)"))
|
||
segs = surf.get("segs", [])
|
||
|
||
edges = []
|
||
for seg in segs:
|
||
if "c" in seg:
|
||
# 弧形段
|
||
c = Point3d.parse(seg["c"])
|
||
r = seg.get("r", 1.0) * 0.001
|
||
a1 = seg.get("a1", 0.0)
|
||
a2 = seg.get("a2", math.pi * 2)
|
||
n = seg.get("n", 12)
|
||
|
||
# 创建弧形边
|
||
arc_edges = self._create_arc_edges(
|
||
c, xaxis, zaxis, r, a1, a2, n)
|
||
edges.extend(arc_edges)
|
||
else:
|
||
# 直线段
|
||
s = Point3d.parse(seg.get("s", "(0,0,0)"))
|
||
e = Point3d.parse(seg.get("e", "(0,0,0)"))
|
||
edge = self._create_line_edge_simple(bpy.context.scene,
|
||
(s.x, s.y, s.z),
|
||
(e.x, e.y, e.z))
|
||
if edge:
|
||
edges.append(edge)
|
||
|
||
# 尝试创建面
|
||
try:
|
||
if edges:
|
||
self._create_face_from_edges(bpy.context.scene, edges)
|
||
except Exception as e:
|
||
logger.warning(f"创建轮廓面失败: {e}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建轮廓失败: {e}")
|
||
|
||
def _create_arc_edges(self, center, xaxis, zaxis, radius, start_angle, end_angle, segments):
|
||
"""创建弧形边"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return []
|
||
|
||
edges = []
|
||
angle_step = (end_angle - start_angle) / segments
|
||
|
||
for i in range(segments):
|
||
angle1 = start_angle + i * angle_step
|
||
angle2 = start_angle + (i + 1) * angle_step
|
||
|
||
# 计算点
|
||
x1 = center.x + radius * math.cos(angle1)
|
||
y1 = center.y + radius * math.sin(angle1)
|
||
z1 = center.z
|
||
|
||
x2 = center.x + radius * math.cos(angle2)
|
||
y2 = center.y + radius * math.sin(angle2)
|
||
z2 = center.z
|
||
|
||
edge = self._create_line_edge_simple(bpy.context.scene,
|
||
(x1, y1, z1),
|
||
(x2, y2, z2))
|
||
if edge:
|
||
edges.append(edge)
|
||
|
||
return edges
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建弧形边失败: {e}")
|
||
return []
|
||
|
||
def _create_dimension(self, p1, p2, direction, text):
|
||
"""创建尺寸标注"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 在Blender中创建尺寸标注的简化实现
|
||
# 创建文本对象显示尺寸
|
||
midpoint = (
|
||
(p1[0] + p2[0]) / 2 + direction[0] * 0.1,
|
||
(p1[1] + p2[1]) / 2 + direction[1] * 0.1,
|
||
(p1[2] + p2[2]) / 2 + direction[2] * 0.1
|
||
)
|
||
|
||
dimension_obj = self._create_text_label(text, midpoint, direction)
|
||
|
||
# 创建尺寸线
|
||
if dimension_obj:
|
||
# 添加线条表示尺寸
|
||
line_mesh = bpy.data.meshes.new("DimensionLine")
|
||
vertices = [p1, p2]
|
||
edges = [(0, 1)]
|
||
|
||
line_mesh.from_pydata(vertices, edges, [])
|
||
line_mesh.update()
|
||
|
||
line_obj = bpy.data.objects.new("DimensionLine", line_mesh)
|
||
line_obj.parent = dimension_obj
|
||
bpy.context.scene.collection.objects.link(line_obj)
|
||
|
||
memory_manager.register_mesh(line_mesh)
|
||
memory_manager.register_object(line_obj)
|
||
|
||
return dimension_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建尺寸标注失败: {e}")
|
||
return None
|
||
|
||
# ==================== 几何体创建的辅助方法(补充) ====================
|
||
|
||
def _create_triangle_face(self, container, tri, offset_vec, base_point):
|
||
"""创建三角形面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 计算三角形的三个顶点
|
||
p1 = (tri.x, tri.y, tri.z)
|
||
p2 = (tri.x + offset_vec.x, tri.y +
|
||
offset_vec.y, tri.z + offset_vec.z)
|
||
p3 = (base_point.x + (base_point.x - tri.x),
|
||
base_point.y + (base_point.y - tri.y),
|
||
base_point.z + (base_point.z - tri.z))
|
||
|
||
# 创建网格
|
||
mesh = bpy.data.meshes.new("Triangle_Face")
|
||
vertices = [p1, p2, p3]
|
||
faces = [(0, 1, 2)]
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj = bpy.data.objects.new("Triangle_Face_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建三角形面失败: {e}")
|
||
return None
|
||
|
||
def _create_circle_face(self, container, center, normal, radius):
|
||
"""创建圆形面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 创建圆形网格
|
||
mesh = bpy.data.meshes.new("Circle_Face")
|
||
|
||
# 生成圆形顶点
|
||
segments = 32
|
||
vertices = [(center.x, center.y, center.z)] # 中心点
|
||
|
||
for i in range(segments):
|
||
angle = (i / segments) * 2 * math.pi
|
||
x = center.x + radius * math.cos(angle)
|
||
y = center.y + radius * math.sin(angle)
|
||
z = center.z
|
||
vertices.append((x, y, z))
|
||
|
||
# 创建面
|
||
faces = []
|
||
for i in range(segments):
|
||
next_i = (i + 1) % segments
|
||
faces.append((0, i + 1, next_i + 1))
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj = bpy.data.objects.new("Circle_Face_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建圆形面失败: {e}")
|
||
return None
|
||
|
||
def _apply_material_to_face(self, face, material):
|
||
"""为面应用材质"""
|
||
try:
|
||
if not face or not material or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
if hasattr(face, 'data') and face.data:
|
||
if not face.data.materials:
|
||
face.data.materials.append(material)
|
||
else:
|
||
face.data.materials[0] = material
|
||
|
||
except Exception as e:
|
||
logger.error(f"为面应用材质失败: {e}")
|
||
|
||
def _follow_me_face(self, face, path):
|
||
"""面跟随路径"""
|
||
try:
|
||
if not face or not path or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 在Blender中实现跟随路径
|
||
# 这里使用简化的实现
|
||
if hasattr(face, 'modifiers'):
|
||
# 添加阵列修改器或其他相关修改器
|
||
pass
|
||
|
||
except Exception as e:
|
||
logger.error(f"面跟随路径失败: {e}")
|
||
|
||
def _cleanup_path(self, path):
|
||
"""清理路径"""
|
||
try:
|
||
if path and BLENDER_AVAILABLE and path.name in bpy.data.objects:
|
||
bpy.data.objects.remove(path, do_unlink=True)
|
||
except Exception as e:
|
||
logger.error(f"清理路径失败: {e}")
|
||
|
||
def _cleanup_trimmer(self, trimmer):
|
||
"""清理修剪器"""
|
||
try:
|
||
if trimmer and BLENDER_AVAILABLE and trimmer.name in bpy.data.objects:
|
||
bpy.data.objects.remove(trimmer, do_unlink=True)
|
||
except Exception as e:
|
||
logger.error(f"清理修剪器失败: {e}")
|
||
|
||
def _trim_object(self, trimmer, target):
|
||
"""修剪对象"""
|
||
try:
|
||
if not trimmer or not target or not BLENDER_AVAILABLE:
|
||
return target
|
||
|
||
# 在Blender中实现布尔运算
|
||
# 这里使用简化的实现
|
||
return target
|
||
|
||
except Exception as e:
|
||
logger.error(f"修剪对象失败: {e}")
|
||
return target
|
||
|
||
def _mark_differ_faces(self, obj):
|
||
"""标记差异面"""
|
||
try:
|
||
if not obj or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
texture = self.get_texture("mat_default")
|
||
if not texture:
|
||
return
|
||
|
||
# 标记所有使用默认材质的面为差异面
|
||
for child in obj.children:
|
||
if hasattr(child, 'data') and child.data:
|
||
if (child.data.materials and
|
||
child.data.materials[0] == texture):
|
||
child["sw_differ"] = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"标记差异面失败: {e}")
|
||
|
||
# ==================== 几何验证辅助方法 ====================
|
||
|
||
def _should_reverse_face(self, face, zaxis, reverse_face):
|
||
"""检查是否应该反转面"""
|
||
try:
|
||
if not face or not zaxis:
|
||
return False
|
||
|
||
# 简化的实现
|
||
return reverse_face
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查面反转失败: {e}")
|
||
return False
|
||
|
||
def _face_normal_matches(self, face, zaxis):
|
||
"""检查面法向量是否匹配"""
|
||
try:
|
||
if not face or not zaxis:
|
||
return False
|
||
|
||
# 简化的实现
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查面法向量失败: {e}")
|
||
return False
|
||
|
||
def _reverse_face(self, face):
|
||
"""反转面"""
|
||
try:
|
||
if not face or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
if hasattr(face, 'data') and face.data:
|
||
# 在Blender中反转面的法向量
|
||
bpy.context.view_layer.objects.active = face
|
||
bpy.ops.object.mode_set(mode='EDIT')
|
||
bpy.ops.mesh.select_all(action='SELECT')
|
||
bpy.ops.mesh.flip_normals()
|
||
bpy.ops.object.mode_set(mode='OBJECT')
|
||
|
||
except Exception as e:
|
||
logger.error(f"反转面失败: {e}")
|
||
|
||
def _get_face_normal(self, face):
|
||
"""获取面法向量"""
|
||
try:
|
||
if not face or not BLENDER_AVAILABLE:
|
||
return (0, 0, 1)
|
||
|
||
if hasattr(face, 'data') and face.data and face.data.polygons:
|
||
# 获取第一个多边形的法向量
|
||
return face.data.polygons[0].normal
|
||
|
||
return (0, 0, 1)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取面法向量失败: {e}")
|
||
return (0, 0, 1)
|
||
|
||
def _apply_follow_me(self, face, path):
|
||
"""应用跟随路径"""
|
||
try:
|
||
if not face or not path or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 在Blender中实现跟随路径的简化版本
|
||
# 这里需要根据实际需求实现具体的几何操作
|
||
pass
|
||
|
||
except Exception as e:
|
||
logger.error(f"应用跟随路径失败: {e}")
|
||
|
||
def _hide_edges(self, container):
|
||
"""隐藏边"""
|
||
try:
|
||
if not container or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
for child in container.children:
|
||
if hasattr(child, 'data') and child.data and hasattr(child.data, 'edges'):
|
||
for edge in child.data.edges:
|
||
edge.use_edge_sharp = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"隐藏边失败: {e}")
|
||
|
||
def _create_face_fast(self, container, surface, material):
|
||
"""创建面 - 快速版本"""
|
||
try:
|
||
# 获取分段数据
|
||
segs = surface.get("segs", [])
|
||
if not segs:
|
||
return None
|
||
|
||
# 快速解析顶点
|
||
vertices = []
|
||
for seg in segs:
|
||
if len(seg) >= 2:
|
||
coord_str = seg[0].strip('()')
|
||
try:
|
||
x, y, z = map(float, coord_str.split(','))
|
||
vertices.append((x * 0.001, y * 0.001, z * 0.001))
|
||
except:
|
||
continue
|
||
|
||
if len(vertices) < 3:
|
||
return None
|
||
|
||
# 创建简单网格
|
||
mesh = bpy.data.meshes.new(f"FastFace_{int(time.time())}")
|
||
|
||
# 创建面(只支持三角形和四边形)
|
||
if len(vertices) == 4:
|
||
faces = [(0, 1, 2, 3)]
|
||
elif len(vertices) == 3:
|
||
faces = [(0, 1, 2)]
|
||
else:
|
||
# 复杂多边形简化为第一个三角形
|
||
faces = [(0, 1, 2)]
|
||
vertices = vertices[:3]
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
face_obj = bpy.data.objects.new(f"Face_{container.name}", mesh)
|
||
face_obj.parent = container
|
||
bpy.context.scene.collection.objects.link(face_obj)
|
||
|
||
# 应用材质
|
||
if material:
|
||
face_obj.data.materials.append(material)
|
||
|
||
# 确保可见
|
||
face_obj.hide_viewport = False
|
||
|
||
# 注册到内存管理器
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(face_obj)
|
||
|
||
return face_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"快速创建面失败: {e}")
|
||
return None
|
||
|
||
def _create_board_six_faces_fast(self, leaf, data, color, scale, angle, color2, scale2, angle2):
|
||
"""快速创建板件六个面"""
|
||
try:
|
||
# 获取正反面数据
|
||
obv = data.get("obv") # 正面
|
||
rev = data.get("rev") # 反面
|
||
|
||
if not obv or not rev:
|
||
logger.warning("缺少正反面数据")
|
||
return
|
||
|
||
# 处理材质
|
||
antiz = data.get("antiz", False)
|
||
|
||
# 根据antiz决定材质分配
|
||
if antiz:
|
||
# 交换正反面材质
|
||
obv_color = color2 if color2 else color
|
||
rev_color = color
|
||
else:
|
||
# 正常材质分配
|
||
obv_color = color
|
||
rev_color = color2 if color2 else color
|
||
|
||
# 获取材质
|
||
material_obv = self.get_texture(obv_color) if obv_color else None
|
||
material_rev = self.get_texture(rev_color) if rev_color else None
|
||
edge_material = material_obv # 边面使用正面材质
|
||
|
||
# 1. 创建正面 (obverse)
|
||
obv_face = self._create_face_fast(leaf, obv, material_obv)
|
||
if obv_face:
|
||
obv_face["sw_face_type"] = "obverse"
|
||
obv_face["sw_face_id"] = "front"
|
||
obv_face["sw_ckey"] = obv_color
|
||
logger.debug("正面创建成功")
|
||
|
||
# 2. 创建反面 (reverse)
|
||
rev_face = self._create_face_fast(leaf, rev, material_rev)
|
||
if rev_face:
|
||
rev_face["sw_face_type"] = "reverse"
|
||
rev_face["sw_face_id"] = "back"
|
||
rev_face["sw_ckey"] = rev_color
|
||
logger.debug("反面创建成功")
|
||
|
||
# 3. 创建四个边面
|
||
self._create_board_edge_faces_fast(leaf, obv, rev, edge_material)
|
||
|
||
logger.debug("板件六面创建完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建板件六面失败: {e}")
|
||
|
||
def _create_board_edge_faces_fast(self, leaf, obv, rev, edge_material):
|
||
"""快速创建板件的四个边面"""
|
||
try:
|
||
# 解析正面和反面的顶点
|
||
obv_vertices = self._parse_surface_vertices(obv)
|
||
rev_vertices = self._parse_surface_vertices(rev)
|
||
|
||
if len(obv_vertices) != len(rev_vertices) or len(obv_vertices) < 3:
|
||
logger.warning("正反面顶点数量不匹配或不足")
|
||
return
|
||
|
||
# 创建四个边面
|
||
vertex_count = len(obv_vertices)
|
||
edge_count = 0
|
||
|
||
for i in range(vertex_count):
|
||
next_i = (i + 1) % vertex_count
|
||
|
||
# 边面的四个顶点:正面两个点 + 反面对应两个点
|
||
edge_vertices = [
|
||
obv_vertices[i], # 正面当前点
|
||
obv_vertices[next_i], # 正面下一点
|
||
rev_vertices[next_i], # 反面下一点
|
||
rev_vertices[i] # 反面当前点
|
||
]
|
||
|
||
# 创建边面
|
||
edge_face = self._create_face_from_vertices_fast(
|
||
leaf, edge_vertices, edge_material, f"edge_{i}")
|
||
|
||
if edge_face:
|
||
edge_face["sw_face_type"] = "edge"
|
||
edge_face["sw_face_id"] = f"edge_{i}"
|
||
edge_face["sw_edge_index"] = i
|
||
edge_count += 1
|
||
|
||
logger.debug(f"创建了 {edge_count}/{vertex_count} 个边面")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建边面失败: {e}")
|
||
|
||
def _parse_surface_vertices(self, surface):
|
||
"""解析表面顶点坐标"""
|
||
try:
|
||
vertices = []
|
||
segs = surface.get("segs", [])
|
||
|
||
for seg in segs:
|
||
if len(seg) >= 2:
|
||
coord_str = seg[0].strip('()')
|
||
try:
|
||
x, y, z = map(float, coord_str.split(','))
|
||
# 转换为米(Blender使用米作为单位)
|
||
vertices.append((x * 0.001, y * 0.001, z * 0.001))
|
||
except ValueError:
|
||
continue
|
||
|
||
return vertices
|
||
|
||
except Exception as e:
|
||
logger.error(f"解析表面顶点失败: {e}")
|
||
return []
|
||
|
||
def _create_face_from_vertices_fast(self, container, vertices, material, face_name):
|
||
"""从顶点快速创建面"""
|
||
try:
|
||
if len(vertices) < 3:
|
||
return None
|
||
|
||
# 创建网格
|
||
mesh = bpy.data.meshes.new(f"Face_{face_name}_{int(time.time())}")
|
||
|
||
# 创建面
|
||
if len(vertices) == 4:
|
||
faces = [(0, 1, 2, 3)]
|
||
elif len(vertices) == 3:
|
||
faces = [(0, 1, 2)]
|
||
else:
|
||
# 复杂多边形创建扇形三角形
|
||
faces = []
|
||
for i in range(1, len(vertices) - 1):
|
||
faces.append((0, i, i + 1))
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
face_obj = bpy.data.objects.new(f"Face_{face_name}", mesh)
|
||
face_obj.parent = container
|
||
bpy.context.scene.collection.objects.link(face_obj)
|
||
|
||
# 应用材质
|
||
if material:
|
||
face_obj.data.materials.append(material)
|
||
|
||
# 确保可见
|
||
face_obj.hide_viewport = False
|
||
|
||
# 注册到内存管理器
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(face_obj)
|
||
|
||
return face_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"从顶点创建面失败: {e}")
|
||
return None
|
||
|
||
def _add_part_board_fast(self, part, data):
|
||
"""创建板材部件 - 保持六面逻辑的快速版本"""
|
||
try:
|
||
# 创建叶子组
|
||
leaf = bpy.data.objects.new(
|
||
f"Board_{part.name}_{int(time.time())}", None)
|
||
leaf.parent = part
|
||
bpy.context.scene.collection.objects.link(leaf)
|
||
|
||
# 获取材质信息
|
||
color = data.get("ckey", "mat_default")
|
||
scale = data.get("scale")
|
||
angle = data.get("angle")
|
||
color2 = data.get("ckey2")
|
||
scale2 = data.get("scale2")
|
||
angle2 = data.get("angle2")
|
||
|
||
# 设置叶子属性
|
||
leaf["sw_ckey"] = color
|
||
if scale:
|
||
leaf["sw_scale"] = scale
|
||
if angle:
|
||
leaf["sw_angle"] = angle
|
||
|
||
logger.debug(f"板材材质: {color}")
|
||
|
||
# 创建板件的六个面(正面、反面、四个边面)
|
||
self._create_board_six_faces_fast(
|
||
leaf, data, color, scale, angle, color2, scale2, angle2)
|
||
|
||
logger.debug(f"板材部件创建完成: {leaf.name}")
|
||
return leaf
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建板材部件失败: {e}")
|
||
return None
|
||
|
||
def _create_transparent_material(self):
|
||
"""创建透明材质用于容器对象"""
|
||
try:
|
||
material_name = "SUW_Container_Transparent"
|
||
|
||
# 检查是否已存在
|
||
if material_name in bpy.data.materials:
|
||
return bpy.data.materials[material_name]
|
||
|
||
# 创建透明材质
|
||
material = bpy.data.materials.new(name=material_name)
|
||
material.use_nodes = True
|
||
|
||
# 清理默认节点
|
||
material.node_tree.nodes.clear()
|
||
|
||
# 创建节点
|
||
bsdf = material.node_tree.nodes.new(
|
||
type='ShaderNodeBsdfPrincipled')
|
||
bsdf.location = (0, 0)
|
||
|
||
output = material.node_tree.nodes.new(
|
||
type='ShaderNodeOutputMaterial')
|
||
output.location = (300, 0)
|
||
|
||
# 连接节点
|
||
material.node_tree.links.new(
|
||
bsdf.outputs['BSDF'], output.inputs['Surface'])
|
||
|
||
# 设置完全透明
|
||
bsdf.inputs['Base Color'].default_value = (
|
||
0.5, 0.5, 0.5, 1.0) # 灰色
|
||
bsdf.inputs['Alpha'].default_value = 0.0 # 完全透明
|
||
|
||
# 设置混合模式
|
||
material.blend_method = 'BLEND'
|
||
material.use_backface_culling = False
|
||
|
||
logger.info(f"✅ 创建容器透明材质: {material_name}")
|
||
return material
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建透明材质失败: {e}")
|
||
return None
|
||
|
||
def c03(self, data: Dict[str, Any]):
|
||
"""add_zone - 添加区域 - 修复父对象设置问题"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.error("Blender不可用")
|
||
return
|
||
|
||
uid = data.get("uid")
|
||
zid = data.get("zid")
|
||
zip_id = data.get("zip", -1)
|
||
elements = data.get("children", [])
|
||
|
||
logger.info(f"🏗️ 开始创建区域: uid={uid}, zid={zid}, zip={zip_id}")
|
||
|
||
def create_zone():
|
||
try:
|
||
# 创建区域组 - 保持为Empty对象
|
||
group_name = f"Zone_{zid}"
|
||
group = bpy.data.objects.new(group_name, None)
|
||
|
||
# 设置父对象 - 根据zip_id查找父Zone
|
||
if zip_id > 0:
|
||
# 查找父Zone
|
||
parent_zone_name = f"Zone_{zip_id}"
|
||
parent_zone = bpy.data.objects.get(parent_zone_name)
|
||
if parent_zone:
|
||
group.parent = parent_zone
|
||
else:
|
||
logger.warning(f"未找到父Zone: {parent_zone_name}")
|
||
# 如果zip_id <= 0,则不设置父对象(顶级Zone)
|
||
|
||
# 设置属性
|
||
group["sw_uid"] = uid
|
||
group["sw_zid"] = zid
|
||
group["sw_zip"] = zip_id
|
||
group["sw_typ"] = "zid"
|
||
|
||
bpy.context.scene.collection.objects.link(group)
|
||
|
||
# 将Zone存储到zones字典中
|
||
zones_dict = self.get_zones(data)
|
||
zones_dict[zid] = group
|
||
|
||
# 处理子元素 - 给face应用透明材质
|
||
for element in elements:
|
||
surf = element.get("surf")
|
||
if surf:
|
||
self.create_face_safe(
|
||
group, surf, transparent=True)
|
||
|
||
logger.info(f"✅ 区域创建完成: {group_name}")
|
||
return group
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建区域失败: {e}")
|
||
return None
|
||
|
||
# 在主线程执行
|
||
return execute_in_main_thread(create_zone)
|
||
|
||
except Exception as e:
|
||
logger.error(f"c03命令失败: {e}")
|
||
return None
|
||
|
||
def create_face_safe(self, container, surface, color=None, scale=None, angle=None,
|
||
series=None, reverse_face=False, back_material=True,
|
||
saved_color=None, typ=None, transparent=False):
|
||
"""创建面 - 支持透明材质选项"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.error("Blender不可用")
|
||
return None
|
||
|
||
# 获取分段数据
|
||
segs = surface.get("segs", [])
|
||
if not segs:
|
||
logger.error("没有分段数据")
|
||
return None
|
||
|
||
# 创建顶点
|
||
vertices = []
|
||
for i, seg in enumerate(segs):
|
||
if len(seg) >= 2:
|
||
coord_str = seg[0].strip('()')
|
||
try:
|
||
x, y, z = map(float, coord_str.split(','))
|
||
# 转换为米(Blender使用米作为单位)
|
||
vertex = (x * 0.001, y * 0.001, z * 0.001)
|
||
vertices.append(vertex)
|
||
except ValueError as e:
|
||
logger.error(f"解析顶点失败: {coord_str}, 错误: {e}")
|
||
continue
|
||
|
||
if len(vertices) < 3:
|
||
logger.error(f"顶点数量不足,无法创建面: {len(vertices)}")
|
||
return None
|
||
|
||
# 创建网格
|
||
mesh_name = f"Face_{surface.get('f', 0)}_{int(time.time())}"
|
||
mesh = bpy.data.meshes.new(mesh_name)
|
||
|
||
# 创建面
|
||
edges = []
|
||
faces = []
|
||
|
||
if len(vertices) == 4:
|
||
# 四边形
|
||
faces = [(0, 1, 2, 3)]
|
||
elif len(vertices) == 3:
|
||
# 三角形
|
||
faces = [(0, 1, 2)]
|
||
else:
|
||
# 复杂多边形,创建扇形三角形
|
||
for i in range(1, len(vertices) - 1):
|
||
faces.append((0, i, i + 1))
|
||
|
||
# 从顶点、边、面创建网格
|
||
mesh.from_pydata(vertices, edges, faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj_name = f"Face_{surface.get('f', 0)}"
|
||
face_obj = bpy.data.objects.new(obj_name, mesh)
|
||
|
||
# 设置父对象
|
||
face_obj.parent = container
|
||
|
||
# 添加到场景
|
||
bpy.context.scene.collection.objects.link(face_obj)
|
||
|
||
# 设置面属性
|
||
if surface.get("p"):
|
||
face_obj["sw_p"] = surface["p"]
|
||
if surface.get("f"):
|
||
face_obj["sw_f"] = surface["f"]
|
||
|
||
# 确保对象可见
|
||
face_obj.hide_viewport = False
|
||
face_obj.hide_render = False
|
||
face_obj.hide_set(False)
|
||
|
||
# 应用材质
|
||
if transparent:
|
||
# 应用透明材质
|
||
transparent_material = self._create_transparent_material()
|
||
if transparent_material:
|
||
face_obj.data.materials.append(transparent_material)
|
||
logger.info(f"✅ Face {obj_name} 应用透明材质")
|
||
elif color:
|
||
# 应用指定材质
|
||
material = self.get_texture(color)
|
||
if material:
|
||
face_obj.data.materials.append(material)
|
||
else:
|
||
# 创建默认材质
|
||
default_mat = bpy.data.materials.new(
|
||
name="DefaultMaterial")
|
||
default_mat.use_nodes = True
|
||
default_mat.node_tree.nodes["Principled BSDF"].inputs[0].default_value = (
|
||
0.8, 0.8, 0.8, 1.0)
|
||
face_obj.data.materials.append(default_mat)
|
||
|
||
# 注册到内存管理器
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(face_obj)
|
||
|
||
# 添加到series(如果提供)
|
||
if series is not None:
|
||
series.append(face_obj)
|
||
|
||
return face_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建面失败: {e}")
|
||
return None
|
||
|
||
|
||
# ==================== 模块级别的便利函数 ====================
|
||
|
||
def create_suw_instance():
|
||
"""创建SUW实例的便利函数"""
|
||
return SUWImpl.get_instance()
|
||
|
||
|
||
def cleanup_blender_memory():
|
||
"""清理Blender内存的便利函数"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
cleanup_count = memory_manager.cleanup_orphaned_data()
|
||
gc.collect()
|
||
logger.info(f"清理了 {cleanup_count} 个孤立数据")
|
||
return cleanup_count
|
||
return 0
|
||
except Exception as e:
|
||
logger.error(f"清理内存失败: {e}")
|
||
return 0
|
||
|
||
|
||
def get_memory_usage_summary():
|
||
"""获取内存使用摘要"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
return {
|
||
"objects": len(bpy.data.objects),
|
||
"meshes": len(bpy.data.meshes),
|
||
"materials": len(bpy.data.materials),
|
||
"memory_manager_stats": memory_manager.creation_stats.copy()
|
||
}
|
||
return {"blender_available": False}
|
||
except Exception as e:
|
||
logger.error(f"获取内存使用摘要失败: {e}")
|
||
return {"error": str(e)}
|
||
|
||
|
||
# ==================== 主程序入口 ====================
|
||
|
||
if __name__ == "__main__":
|
||
# 测试代码
|
||
try:
|
||
logger.info("开始测试SUWImpl内存管理")
|
||
|
||
# 创建实例
|
||
suw = create_suw_instance()
|
||
suw.startup()
|
||
|
||
# 获取内存报告
|
||
report = suw.get_memory_report()
|
||
logger.info(f"内存报告: {report}")
|
||
|
||
# 执行诊断
|
||
issues = suw.diagnose_system_state()
|
||
if not issues:
|
||
logger.info("✅ 系统状态正常")
|
||
|
||
# 清理测试
|
||
cleanup_count = cleanup_blender_memory()
|
||
logger.info(f"清理测试完成: {cleanup_count}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"测试失败: {e}")
|
||
|
||
finally:
|
||
logger.info("测试完成")
|