blenderpython/suw_core/material_manager.py

842 lines
31 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
# 【新增】材质类型常量 - 按照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()