Files
2025-11-29 18:18:51 +08:00

362 lines
9.3 KiB
Python

"""
Material Operations
머티리얼 및 셰이더 관련 작업을 처리하는 명령 핸들러
"""
import bpy
from typing import Dict, List, Tuple, Optional, Any
from ..utils.logger import get_logger
logger = get_logger(__name__)
# ============================================================================
# Material Creation (머티리얼 생성)
# ============================================================================
def create_material(
name: str,
use_nodes: bool = True
) -> Dict[str, Any]:
"""
머티리얼 생성
Args:
name: 머티리얼 이름
use_nodes: 노드 시스템 사용 여부 (기본값: True)
Returns:
생성된 머티리얼 정보
"""
logger.info(f"Creating material: {name}")
# 기존 머티리얼 확인
if name in bpy.data.materials:
logger.warn(f"Material '{name}' already exists, returning existing")
mat = bpy.data.materials[name]
else:
mat = bpy.data.materials.new(name=name)
mat.use_nodes = use_nodes
return {
'name': mat.name,
'use_nodes': mat.use_nodes
}
def list_materials() -> List[Dict[str, Any]]:
"""
모든 머티리얼 목록 조회
Returns:
머티리얼 목록
"""
logger.info("Listing all materials")
materials = []
for mat in bpy.data.materials:
materials.append({
'name': mat.name,
'use_nodes': mat.use_nodes,
'users': mat.users # 사용 중인 오브젝트 수
})
return materials
def delete_material(name: str) -> Dict[str, str]:
"""
머티리얼 삭제
Args:
name: 머티리얼 이름
Returns:
삭제 결과
"""
logger.info(f"Deleting material: {name}")
mat = bpy.data.materials.get(name)
if not mat:
raise ValueError(f"Material '{name}' not found")
bpy.data.materials.remove(mat)
return {'status': 'success', 'message': f"Material '{name}' deleted"}
# ============================================================================
# Material Assignment (머티리얼 할당)
# ============================================================================
def assign_material(
object_name: str,
material_name: str,
slot_index: int = 0
) -> Dict[str, Any]:
"""
오브젝트에 머티리얼 할당
Args:
object_name: 오브젝트 이름
material_name: 머티리얼 이름
slot_index: 머티리얼 슬롯 인덱스 (기본값: 0)
Returns:
할당 결과
"""
logger.info(f"Assigning material '{material_name}' to object '{object_name}'")
obj = bpy.data.objects.get(object_name)
if not obj:
raise ValueError(f"Object '{object_name}' not found")
mat = bpy.data.materials.get(material_name)
if not mat:
raise ValueError(f"Material '{material_name}' not found")
# 머티리얼 슬롯이 없으면 생성
if len(obj.data.materials) == 0:
obj.data.materials.append(mat)
else:
# 기존 슬롯에 할당
if slot_index < len(obj.data.materials):
obj.data.materials[slot_index] = mat
else:
obj.data.materials.append(mat)
return {
'object': object_name,
'material': material_name,
'slot_index': slot_index
}
def list_object_materials(object_name: str) -> List[Dict[str, Any]]:
"""
오브젝트의 머티리얼 슬롯 목록 조회
Args:
object_name: 오브젝트 이름
Returns:
머티리얼 슬롯 목록
"""
logger.info(f"Listing materials for object: {object_name}")
obj = bpy.data.objects.get(object_name)
if not obj:
raise ValueError(f"Object '{object_name}' not found")
materials = []
for i, mat_slot in enumerate(obj.material_slots):
materials.append({
'slot_index': i,
'material': mat_slot.material.name if mat_slot.material else None
})
return materials
# ============================================================================
# Material Properties (머티리얼 속성)
# ============================================================================
def set_material_base_color(
material_name: str,
color: Tuple[float, float, float, float]
) -> Dict[str, Any]:
"""
머티리얼 기본 색상 설정 (Principled BSDF)
Args:
material_name: 머티리얼 이름
color: RGBA 색상 (0.0 ~ 1.0)
Returns:
설정 결과
"""
logger.info(f"Setting base color for material: {material_name}")
mat = bpy.data.materials.get(material_name)
if not mat:
raise ValueError(f"Material '{material_name}' not found")
if not mat.use_nodes:
raise ValueError(f"Material '{material_name}' does not use nodes")
# Principled BSDF 노드 찾기
principled = None
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
principled = node
break
if not principled:
raise ValueError(f"Principled BSDF node not found in material '{material_name}'")
# Base Color 설정
principled.inputs['Base Color'].default_value = color
return {
'material': material_name,
'base_color': list(color)
}
def set_material_metallic(
material_name: str,
metallic: float
) -> Dict[str, Any]:
"""
머티리얼 Metallic 값 설정
Args:
material_name: 머티리얼 이름
metallic: Metallic 값 (0.0 ~ 1.0)
Returns:
설정 결과
"""
logger.info(f"Setting metallic for material: {material_name}")
mat = bpy.data.materials.get(material_name)
if not mat or not mat.use_nodes:
raise ValueError(f"Material '{material_name}' not found or does not use nodes")
# Principled BSDF 노드 찾기
principled = None
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
principled = node
break
if not principled:
raise ValueError(f"Principled BSDF node not found")
principled.inputs['Metallic'].default_value = metallic
return {
'material': material_name,
'metallic': metallic
}
def set_material_roughness(
material_name: str,
roughness: float
) -> Dict[str, Any]:
"""
머티리얼 Roughness 값 설정
Args:
material_name: 머티리얼 이름
roughness: Roughness 값 (0.0 ~ 1.0)
Returns:
설정 결과
"""
logger.info(f"Setting roughness for material: {material_name}")
mat = bpy.data.materials.get(material_name)
if not mat or not mat.use_nodes:
raise ValueError(f"Material '{material_name}' not found or does not use nodes")
# Principled BSDF 노드 찾기
principled = None
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
principled = node
break
if not principled:
raise ValueError(f"Principled BSDF node not found")
principled.inputs['Roughness'].default_value = roughness
return {
'material': material_name,
'roughness': roughness
}
def set_material_emission(
material_name: str,
color: Tuple[float, float, float, float],
strength: float = 1.0
) -> Dict[str, Any]:
"""
머티리얼 Emission 설정
Args:
material_name: 머티리얼 이름
color: Emission 색상 RGBA (0.0 ~ 1.0)
strength: Emission 강도 (기본값: 1.0)
Returns:
설정 결과
"""
logger.info(f"Setting emission for material: {material_name}")
mat = bpy.data.materials.get(material_name)
if not mat or not mat.use_nodes:
raise ValueError(f"Material '{material_name}' not found or does not use nodes")
# Principled BSDF 노드 찾기
principled = None
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
principled = node
break
if not principled:
raise ValueError(f"Principled BSDF node not found")
principled.inputs['Emission'].default_value = color
principled.inputs['Emission Strength'].default_value = strength
return {
'material': material_name,
'emission_color': list(color),
'emission_strength': strength
}
def get_material_properties(material_name: str) -> Dict[str, Any]:
"""
머티리얼 속성 조회
Args:
material_name: 머티리얼 이름
Returns:
머티리얼 속성
"""
logger.info(f"Getting properties for material: {material_name}")
mat = bpy.data.materials.get(material_name)
if not mat:
raise ValueError(f"Material '{material_name}' not found")
props = {
'name': mat.name,
'use_nodes': mat.use_nodes
}
if mat.use_nodes:
# Principled BSDF 속성 가져오기
principled = None
for node in mat.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
principled = node
break
if principled:
props['base_color'] = list(principled.inputs['Base Color'].default_value)
props['metallic'] = principled.inputs['Metallic'].default_value
props['roughness'] = principled.inputs['Roughness'].default_value
props['emission'] = list(principled.inputs['Emission'].default_value)
props['emission_strength'] = principled.inputs['Emission Strength'].default_value
return props