#!/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 # 【新增】材质类型常量 - 按照Ruby代码定义 MAT_TYPE_NORMAL = 0 MAT_TYPE_OBVERSE = 1 MAT_TYPE_NATURE = 2 # 导入内存管理器 # ==================== 材质管理器类 ==================== 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 = 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)), # 【新增】加工相关材质 ("mat_machining", (0.0, 0.5, 1.0, 1.0)), # 蓝色 - 有效加工 ("mat_cancelled", (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: int): """设置材质类型""" self.mat_type = mat_type logger.info(f"设置材质类型: {mat_type}") def get_mat_type(self) -> int: """获取当前材质类型""" 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}") # ==================== 【新增】c11和c30命令方法 ==================== def c11(self, data: Dict[str, Any]): """part_obverse - 设置零件正面显示 - 按照Ruby逻辑实现""" try: if not BLENDER_AVAILABLE: logger.warning("Blender不可用,跳过零件正面显示设置") return 0 uid = data.get("uid") v = data.get("v", False) # 【按照Ruby逻辑】设置材质类型 if v: self.mat_type = MAT_TYPE_OBVERSE # MAT_TYPE_OBVERSE = 1 logger.info("设置材质类型为正面显示") else: self.mat_type = MAT_TYPE_NORMAL # MAT_TYPE_NORMAL = 0 logger.info("设置材质类型为正常显示") # 获取零件数据 from .data_manager import get_data_manager data_manager = get_data_manager() parts_data = data_manager.get_parts({"uid": uid}) processed_count = 0 for root, part in parts_data.items(): if part and hasattr(part, 'data'): try: self._textured_part(part, False) processed_count += 1 except Exception as e: logger.warning(f"处理零件失败: {root}, {e}") logger.info(f"✅ 设置零件正面显示: {processed_count}") return processed_count except Exception as e: logger.error(f"❌ 设置零件正面显示失败: {e}") return 0 def c30(self, data: Dict[str, Any]): """part_nature - 设置零件自然显示 - 按照Ruby逻辑实现""" try: if not BLENDER_AVAILABLE: logger.warning("Blender不可用,跳过零件自然显示设置") return 0 uid = data.get("uid") v = data.get("v", False) # 【按照Ruby逻辑】设置材质类型 if v: self.mat_type = MAT_TYPE_NATURE # MAT_TYPE_NATURE = 2 logger.info("设置材质类型为自然显示") else: self.mat_type = MAT_TYPE_NORMAL # MAT_TYPE_NORMAL = 0 logger.info("设置材质类型为正常显示") # 获取零件数据 from .data_manager import get_data_manager data_manager = get_data_manager() parts_data = data_manager.get_parts({"uid": uid}) processed_count = 0 for root, part in parts_data.items(): if part and hasattr(part, 'data'): try: self._textured_part(part, False) processed_count += 1 except Exception as e: logger.warning(f"处理零件失败: {root}, {e}") logger.info(f"✅ 设置零件自然显示: {processed_count}") return processed_count except Exception as e: logger.error(f"❌ 设置零件自然显示失败: {e}") return 0 def _textured_part(self, part, selected: bool): """为零件应用纹理 - 按照Ruby逻辑实现""" try: if not part or not hasattr(part, 'data'): return # 【按照Ruby逻辑】处理零件的每个子对象 for child in part.children: if not child: continue # 跳过非模型部件 child_type = self._get_part_attribute(child, "typ", "") if child_type != "cp": continue # 跳过加工和拉手 if child_type in ["work", "pull"]: continue # 【按照Ruby逻辑】处理可见性 if self.mat_type == MAT_TYPE_NATURE: # 自然模式下,模型部件隐藏,虚拟部件显示 if hasattr(child, 'type') and child.type == 'MESH': child.hide_viewport = True child.hide_render = True elif self._get_part_attribute(child, "virtual", False): child.hide_viewport = False child.hide_render = False else: # 其他模式下,模型部件显示,虚拟部件隐藏 if hasattr(child, 'type') and child.type == 'MESH': child.hide_viewport = False child.hide_render = False elif self._get_part_attribute(child, "virtual", False): child.hide_viewport = True child.hide_render = True # 【按照Ruby逻辑】为面应用材质 self._apply_part_materials(child, selected) except Exception as e: logger.error(f"为零件应用纹理失败: {e}") def _apply_part_materials(self, obj, selected: bool): """为对象应用材质 - 按照Ruby逻辑实现""" try: if not obj: return # 确定材质类型 material_key = None if selected: material_key = "mat_select" elif self.mat_type == MAT_TYPE_NATURE: # 自然模式下根据材质编号选择材质 mn = self._get_part_attribute(obj, "mn", 0) if mn == 1: material_key = "mat_obverse" # 门板 elif mn == 2: material_key = "mat_reverse" # 柜体 elif mn == 3: material_key = "mat_thin" # 背板 else: # 正常模式或正面模式,使用原始材质 material_key = self._get_part_attribute( obj, "ckey", "mat_default") # 获取材质 material = self.get_texture(material_key) if not material: material = self.get_texture("mat_default") # 应用材质到对象 if hasattr(obj, 'data') and obj.data: if not obj.data.materials: obj.data.materials.append(material) else: obj.data.materials[0] = material except Exception as e: logger.error(f"为对象应用材质失败: {e}") def _get_part_attribute(self, obj, attr_name: str, default_value=None): """获取零件属性 - 支持多种对象类型""" try: if hasattr(obj, 'get'): # 如果是字典或类似对象 return obj.get(attr_name, default_value) elif hasattr(obj, 'sw'): # 如果有sw属性 return obj.sw.get(attr_name, default_value) elif isinstance(obj, dict): # 如果是字典 return obj.get(attr_name, default_value) else: # 尝试从Blender对象的自定义属性获取 try: if hasattr(obj, attr_name): return getattr(obj, attr_name) elif hasattr(obj, 'sw'): return obj.sw.get(attr_name, default_value) elif hasattr(obj, 'get'): return obj.get(attr_name, default_value) except: pass return default_value except Exception as e: logger.debug(f"获取零件属性失败: {e}") return default_value 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)} # 【新增】加工材质应用方法 def apply_machining_material(self, obj): """应用加工材质 - 蓝色,表示有效加工""" try: if not BLENDER_AVAILABLE or not obj: return material = self.get_texture("mat_machining") if not material: # 如果材质不存在,创建一个 material = bpy.data.materials.new(name="mat_machining") material.use_nodes = True if material.node_tree: principled = material.node_tree.nodes.get( "Principled BSDF") if principled: principled.inputs['Base Color'].default_value = ( 0.0, 0.5, 1.0, 1.0) # 蓝色 self.textures["mat_machining"] = material # 应用材质到对象 if hasattr(obj, 'data') and obj.data: if not obj.data.materials: obj.data.materials.append(material) else: obj.data.materials[0] = material except Exception as e: logger.error(f"应用加工材质失败: {e}") def apply_cancelled_machining_material(self, obj): """应用取消加工材质 - 灰色,表示取消的加工""" try: if not BLENDER_AVAILABLE or not obj: return material = self.get_texture("mat_cancelled") if not material: # 如果材质不存在,创建一个 material = bpy.data.materials.new(name="mat_cancelled") material.use_nodes = True if material.node_tree: principled = material.node_tree.nodes.get( "Principled BSDF") if principled: principled.inputs['Base Color'].default_value = ( 0.5, 0.5, 0.5, 1.0) # 灰色 self.textures["mat_cancelled"] = material # 应用材质到对象 if hasattr(obj, 'data') and obj.data: if not obj.data.materials: obj.data.materials.append(material) else: obj.data.materials[0] = material except Exception as e: logger.error(f"应用取消加工材质失败: {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()