Initial commit
This commit is contained in:
361
skills/addon/commands/material.py
Normal file
361
skills/addon/commands/material.py
Normal file
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user