362 lines
9.3 KiB
Python
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
|