#!/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("测试完成")