# 清理选择状态 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("测试完成")