suwoodblender/blenderpython/suw_core/material_manager.py

582 lines
21 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()