#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SUW Core - Material Manager Module 拆分自: suw_impl.py (Line 880-1200, 6470-6950) 用途: Blender材质管理、纹理处理、材质应用 版本: 1.0.0 作者: SUWood Team """ from .memory_manager import memory_manager import time import logging from typing import Dict, Any, Optional # 设置日志 logger = logging.getLogger(__name__) # 检查Blender可用性 try: import bpy BLENDER_AVAILABLE = True except ImportError: BLENDER_AVAILABLE = False # 导入内存管理器 # ==================== 材质管理器类 ==================== class MaterialManager: """材质管理器 - 负责所有材质相关操作""" def __init__(self): """ 初始化材质管理器 - 完全独立,不依赖suw_impl """ self.textures = {} # 材质缓存 self.material_cache = {} # 【修复】添加缺少的材质缓存字典 self.material_stats = { "materials_created": 0, "textures_loaded": 0, "creation_errors": 0 } # 材质类型配置 self.mat_type = "normal" # 当前材质类型 self.back_material = True # 是否应用背面材质 logger.info("MaterialManager 初始化完成") 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] logger.info("材质初始化完成") 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): """获取纹理材质 - 修复版本,支持Default_前缀查找""" if not BLENDER_AVAILABLE: return None try: # 检查键是否有效 if not key: return self.textures.get("mat_default") # 【修复1】从缓存中获取 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] # 【修复2】在现有材质中查找 - 支持多种匹配方式 for material in bpy.data.materials: material_name = material.name # 精确匹配 if key == material_name: self.textures[key] = material logger.info(f"✅ 找到精确匹配材质: {key}") return material # 包含匹配(处理Default_前缀) if key in material_name: self.textures[key] = material logger.info(f"✅ 找到包含匹配材质: {key} -> {material_name}") return material # Default_前缀匹配 if material_name.startswith(f"Default_{key}"): self.textures[key] = material logger.info(f"✅ 找到Default_前缀材质: {key} -> {material_name}") return material # 【修复3】如果没找到,尝试创建默认材质 logger.warning(f"未找到纹理: {key},尝试创建默认材质") try: # 创建默认材质 default_material = bpy.data.materials.new( name=f"Default_{key}") default_material.use_nodes = True # 设置基础颜色 if default_material.node_tree: principled = default_material.node_tree.nodes.get( "Principled BSDF") if principled: # 使用灰色作为默认颜色 principled.inputs['Base Color'].default_value = ( 0.7, 0.7, 0.7, 1.0) # 缓存材质 self.textures[key] = default_material if memory_manager: memory_manager.register_object(default_material) logger.info(f"✅ 创建默认材质: Default_{key}") return default_material except Exception as create_error: logger.error(f"创建默认材质失败: {create_error}") # 【修复4】返回默认材质 default_material = self.textures.get("mat_default") if default_material and default_material.name in bpy.data.materials: logger.warning(f"使用系统默认材质: {key}") return default_material logger.warning(f"未找到纹理: {key}") return None 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 create_transparent_material(self): """创建透明材质""" try: if not BLENDER_AVAILABLE: return None # 检查是否已存在透明材质 transparent_mat_name = "mat_transparent" if transparent_mat_name in self.textures: return self.textures[transparent_mat_name] # 创建透明材质 material = bpy.data.materials.new(name=transparent_mat_name) material.use_nodes = True material.blend_method = 'BLEND' # 设置透明属性 if material.node_tree: principled = material.node_tree.nodes.get("Principled BSDF") if principled: # 设置基础颜色为半透明白色 principled.inputs['Base Color'].default_value = ( 1.0, 1.0, 1.0, 0.5) # 设置Alpha principled.inputs['Alpha'].default_value = 0.5 # 缓存材质 self.textures[transparent_mat_name] = material memory_manager.register_object(material) logger.info("创建透明材质完成") return material except Exception as e: logger.error(f"创建透明材质失败: {e}") return None # ==================== 【修复】添加缺少的c02方法 ==================== def c02(self, data: Dict[str, Any]): """add_texture - 添加纹理""" try: logger.info( f"🎨 MaterialManager.c02: 处理纹理 {data.get('ckey', 'unknown')}") if not BLENDER_AVAILABLE: logger.warning("Blender不可用,跳过纹理创建") return None ckey = data.get("ckey") if not ckey: logger.warning("纹理键为空,跳过创建") return None # 检查纹理是否已存在 if ckey in self.textures: existing_material = self.textures[ckey] if existing_material and existing_material.name in bpy.data.materials: logger.info(f"✅ 纹理 {ckey} 已存在") return existing_material else: # 清理无效缓存 del self.textures[ckey] # 创建新材质 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) if memory_manager: 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_value = data.get("alpha", 1.0) if alpha_value < 1.0: links.new( tex_image.outputs['Alpha'], tex_image.inputs['Alpha']) material.blend_method = 'BLEND' else: # 文件不存在,使用纯色 principled.inputs['Base Color'].default_value = ( 0.5, 0.5, 0.5, 1.0) logger.warning(f"纹理文件不存在: {src_path}") 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' # 设置其他属性 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 # 缓存材质 self.textures[ckey] = material if memory_manager: memory_manager.register_object(material) # 更新统计 if hasattr(self, 'material_stats'): self.material_stats["materials_created"] += 1 logger.info(f"✅ 创建纹理材质成功: {ckey}") return material except Exception as e: logger.error(f"❌ MaterialManager.c02 执行失败: {e}") self.material_stats["creation_errors"] += 1 return None # ==================== 其他方法继续 ==================== def textured_surf(self, face, back_material, color, saved_color=None, scale_a=None, angle_a=None): """为表面应用纹理 - 保持原始方法名和参数""" try: if not face or not BLENDER_AVAILABLE: return # 获取材质 material = None if color: material = self.get_texture(color) if not material and saved_color: material = self.get_texture(saved_color) if not material: material = self.get_texture("mat_default") # 应用材质 if material: self.apply_material_to_face(face, material) # 应用纹理变换 if scale_a or angle_a: self.apply_texture_transform(face, material, scale_a, angle_a) except Exception as e: logger.error(f"应用表面纹理失败: {e}") def apply_texture_transform(self, face, material, scale=None, angle=None): """应用纹理变换 - 保持原始方法名和参数""" try: if not face or not material or not BLENDER_AVAILABLE: return if not hasattr(face, 'data') or not face.data: return mesh = face.data # 确保有UV层 if not mesh.uv_layers: mesh.uv_layers.new(name="UVMap") uv_layer = mesh.uv_layers.active if uv_layer: 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 uv_layer: return import math # 应用缩放和旋转 if scale or angle: for loop in uv_layer.data: u, v = loop.uv # 应用缩放 if scale: u *= scale v *= scale # 应用旋转 if angle: angle_rad = math.radians(angle) cos_a = math.cos(angle_rad) sin_a = math.sin(angle_rad) # 绕中心点旋转 u_centered = u - 0.5 v_centered = v - 0.5 u_new = u_centered * cos_a - v_centered * sin_a + 0.5 v_new = u_centered * sin_a + v_centered * cos_a + 0.5 u, v = u_new, v_new loop.uv = (u, v) except Exception as e: logger.error(f"应用UV变换失败: {e}") def rotate_texture(self, face, scale, angle): """旋转纹理 - 保持原始方法名和参数""" try: if not face or not BLENDER_AVAILABLE: return if not hasattr(face, 'data') or not face.data: return mesh = face.data if not mesh.uv_layers: return uv_layer = mesh.uv_layers.active if uv_layer: self.apply_uv_transform(uv_layer, scale, angle) except Exception as e: logger.error(f"旋转纹理失败: {e}") def set_mat_type(self, mat_type: str): """设置材质类型""" self.mat_type = mat_type logger.info(f"设置材质类型: {mat_type}") def get_mat_type(self) -> str: """获取当前材质类型""" return self.mat_type def clear_material_cache(self): """清理材质缓存""" try: if hasattr(self, 'material_cache'): self.material_cache.clear() # 保留基础材质,清理其他缓存 base_materials = ["mat_default", "mat_select", "mat_normal", "mat_obverse", "mat_reverse", "mat_thin"] filtered_textures = { k: v for k, v in self.textures.items() if k in base_materials} self.textures = filtered_textures logger.info("材质缓存清理完成") except Exception as e: logger.error(f"清理材质缓存失败: {e}") def get_material_stats(self) -> Dict[str, Any]: """获取材质管理器统计信息""" try: stats = { "manager_type": "MaterialManager", "cached_textures": len(self.textures), "cached_materials": len(getattr(self, 'material_cache', {})), "current_mat_type": self.mat_type, "back_material": self.back_material, "blender_available": BLENDER_AVAILABLE } if BLENDER_AVAILABLE: stats["total_blender_materials"] = len(bpy.data.materials) return stats except Exception as e: logger.error(f"获取材质统计失败: {e}") return {"error": str(e)} # ==================== 模块实例 ==================== # 全局实例,将由SUWImpl初始化时设置 material_manager = None def init_material_manager(): """初始化材质管理器 - 不再需要suw_impl参数""" global material_manager material_manager = MaterialManager() return material_manager def get_material_manager(): """获取全局材质管理器实例""" global material_manager if material_manager is None: material_manager = init_material_manager() return material_manager # 自动初始化 material_manager = init_material_manager()