Maya에서 Three.js 3D asset viewer를 위한 Arnold 쉐이더 속성 Excel로 저장하는 Python 스크립트 – v1.0




Maya에서 Three.js 3D asset viewer를 위한 Arnold 쉐이더 속성 Excel로 저장하는 Python 스크립트 – v1.0

Maya에서 Three.js 3D asset viewer를 위한 Arnold 쉐이더 속성을 추출하여 엑셀 파일로 저장하는 Python 스크립트 – v1.0을 소개합니다.

 

1. 필요한 모듈 가져오기

import maya.cmds as cmds
import os
from openpyxl import Workbook
from maya.OpenMaya import MGlobal

해당 코드에서는 Maya에서 사용할 모듈과 외부 라이브러리를 가져옵니다.

 

2. 해당 속성이 연결되어 있는지 확인하는 함수

# 해당 속성이 연결되어 있는지 확인하는 함수
def is_connected(attr):
    """
    해당 속성이 연결되어 있는지 여부를 확인합니다.
    
    :param attr: 연결을 확인할 속성의 이름
    :return: 연결되어 있으면 True, 아니면 False
    """
    return cmds.connectionInfo(attr, isDestination=True) and cmds.listConnections(attr)

해당 속성이 다른 노드에 연결되어 있는지 확인하는 함수를 정의합니다.

 

3. 해당 속성에 연결된 노드를 가져오는 함수

# 해당 속성에 연결된 노드를 가져오는 함수
def get_connected_node(attr):
    """
    해당 속성에 연결된 노드를 가져옵니다.
    
    :param attr: 연결된 노드를 가져올 속성의 이름
    :return: 연결된 노드의 이름, 없으면 None
    """
    connections = cmds.listConnections(attr, source=True, destination=False)
    if connections:
        return connections[0]
    else:
        return None

해당 속성에 연결된 노드를 가져오는 함수를 정의합니다.

 

4. RGB 값을 16진수로 변환하는 함수

# RGB 값을 16진수로 변환하는 함수
def rgb_to_hex(rgb):
    """
    RGB 값을 16진수로 변환합니다.
    
    :param rgb: RGB 값의 튜플
    :return: 16진수 색상 코드
    """
    return '0x{:02x}{:02x}{:02x}'.format(int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255))

RGB 값을 16진수로 변환하는 함수를 정의합니다.

 

5. 반사율을 계산하는 함수

# 반사율을 계산하는 함수
def calculate_reflectivity(metalness_weight, specular_weight, specular_color):
    """
    반사율을 계산합니다.
    
    :param metalness_weight: 금속성 가중치
    :param specular_weight: 반사 가중치
    :param specular_color: 반사 색상
    :return: 계산된 반사율 값
    """
    # 반사 색상이 튜플의 리스트인 경우 첫 번째 값만 사용합니다.
    if isinstance(specular_color, list) and len(specular_color) == 1 and isinstance(specular_color[0], tuple):
        specular_color = specular_color[0]
    specular_brightness = sum(specular_color) / 3.0
    if metalness_weight == 1:
        return 0.8 + (specular_weight * 0.2)
    else:
        return specular_weight * specular_brightness

반사율을 계산하는 함수를 정의합니다.

 

6. 쉐이더의 속성을 가져오는 함수

# 쉐이더의 속성을 가져오는 함수
def get_shader_attributes(shader):
    """
    쉐이더의 속성을 가져옵니다.
    
    :param shader: 쉐이더의 이름
    :return: 쉐이더의 속성 딕셔너리
    특정 속성(예: transmission, clearcoat, sheen, emissive)이 0이면 관련 속성들을 결과에서 제외합니다.
    """
    attributes = {}
    # 쉐이더 속성 및 해당하는 메이어 노드 속성 매핑
    shader_props = {
        # "쉐이더 속성명": "출력할 속성명(현재는 Three.js 기준)"
        "baseColor": "baseColor",
        "metalness": "metalness",
        "specular": "specularIntensity",
        "specularRoughness": "roughness",
        "specularColor": "specularColor",
        "specularIOR": "ior",
        "transmission": "transmission",
        "transmissionColor": "transmissionColor",
        "coat": "clearcoat",
        "coatColor": "clearcoatColor",
        "coatRoughness": "clearcoatRoughness",
        "sheen": "sheen",
        "sheenColor": "sheenColor",
        "sheenRoughness": "sheenRoughness",
        "emission": "emissive"
    }
    
    for prop, attr_name in shader_props.items():
        prop_attr = f"{shader}.{prop}"
        # 텍스처가 연결되거나 노드인 경우 값을 가져오지 않고 건너뜁니다.
        if not is_connected(prop_attr):
            if prop in ["baseColor", "specularColor", "transmissionColor", "coatColor", "sheenColor"]:
                color_value = cmds.getAttr(prop_attr)[0]
                attributes[attr_name] = rgb_to_hex(color_value)
            else:
                value = cmds.getAttr(prop_attr)
                attributes[attr_name] = value
        else:
            print(f"{prop_attr} is connected to a texture or node, skipping.")

    # 반사율을 별도로 계산하고 'reflectivity'에 할당합니다.
    if "specularColor" in attributes:
        metalness_weight = attributes.get("metalness", 0)
        specular_weight = attributes.get("specularIntensity", 0)
        specular_color = cmds.getAttr(f"{shader}.specularColor")[0] if "specularColor" in shader_props and not is_connected(f"{shader}.specularColor") else (1, 1, 1)
        reflectivity_value = calculate_reflectivity(metalness_weight, specular_weight, specular_color)
        attributes["reflectivity"] = reflectivity_value

    return attributes

쉐이더의 속성을 가져오는 함수를 정의합니다.

 

7. 쉐이더 속성을 엑셀 파일로 내보내는 함수

# 쉐이더 속성을 엑셀 파일로 내보내는 함수
def get_shader_properties_xlsx():
    """
    쉐이더 속성을 엑셀 파일로 내보냅니다.
    """
    # 건너뛸 쉐이더 목록
    skip_shaders = ['lambert1', 'particleCloud1', 'shaderGlow1', 'standardSurface1']
    current_file = cmds.file(query=True, sceneName=True)
    file_path = os.path.dirname(current_file)
    output_file_path = os.path.join(file_path, "props.xlsx")

    # 파일이 이미 존재하면 삭제합니다.
    if os.path.exists(output_file_path):
        os.remove(output_file_path)

    # 엑셀 워크북 생성
    wb = Workbook()
    first_sheet = wb.active
    first_sheet.title = "Arnold Shaders"
    
    # 모든 쉐이더에 대해 반복하면서 속성을 가져옵니다.
    shaders = sorted(cmds.ls(materials=True))
    for shader in shaders:
        if shader not in skip_shaders and cmds.nodeType(shader) == "aiStandardSurface":
            attr_data = get_shader_attributes(shader)
            if attr_data:
                ws = wb.create_sheet(title=shader)
                for attribute, value in attr_data.items():
                    ws.append([attribute, value])

    # 'Arnold Shaders' 시트 삭제
    if 'Arnold Shaders' in wb.sheetnames:
        wb.remove(wb['Arnold Shaders'])

    # 엑셀 파일 저장
    wb.save(output_file_path)
    MGlobal.displayInfo("props.xlsx 파일 생성이 완료되었습니다!!!")

# 함수 호출
get_shader_properties_xlsx()

# 완료 다이얼로그 표시
cmds.confirmDialog(title='완료', message='프로세스가 성공적으로 완료되었습니다!', button='확인')

쉐이더 속성을 엑셀 파일로 내보내는 함수를 정의하고 호출합니다.

 







 

8. 최종 코드

import maya.cmds as cmds
import os
from openpyxl import Workbook
from openpyxl.styles import Alignment
from maya.OpenMaya import MGlobal

def is_connected(attr):
    """속성이 다른 노드에 연결되어 있는지 확인합니다."""
    return cmds.connectionInfo(attr, isDestination=True) and cmds.listConnections(attr)

def get_connected_node(attr):
    """속성에 연결된 노드를 가져옵니다."""
    connections = cmds.listConnections(attr, source=True, destination=False)
    if connections:
        return connections[0]
    else:
        return None

def rgb_to_hex(rgb):
    """RGB 값을 16진수 색상 코드로 변환합니다."""
    return '0x{:02x}{:02x}{:02x}'.format(int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255))

def calculate_reflectivity(metalness_weight, specular_weight, specular_color):
    """반사율을 계산합니다."""
    if isinstance(specular_color, list) and len(specular_color) == 1 and isinstance(specular_color[0], tuple):
        specular_color = specular_color[0]
    specular_brightness = sum(specular_color) / 3.0
    if metalness_weight == 1:
        return 0.8 + (specular_weight * 0.2)
    else:
        return specular_weight * specular_brightness

def get_shader_attributes(shader):
    """
    쉐이더의 속성을 가져옵니다. 특정 속성이 0이면 관련 속성들을 제외합니다.
    
    :param shader: 쉐이더의 이름
    :return: 쉐이더의 속성 딕셔너리
    """
    attributes = {}
    # 속성 검사 및 가져오기
    shader_props = {
        "baseColor": "baseColor",
        "metalness": "metalness",
        "specular": "specularIntensity",
        "specularRoughness": "roughness",
        "specularColor": "specularColor",
        "specularIOR": "ior",
        "transmission": "transmission",
        "transmissionColor": "transmissionColor",
        "coat": "clearcoat",
        "coatColor": "clearcoatColor",
        "coatRoughness": "clearcoatRoughness",
        "sheen": "sheen",
        "sheenColor": "sheenColor",
        "sheenRoughness": "sheenRoughness",
        "emission": "emissive"
    }
    
    # 속성 값을 먼저 검사합니다.
    excluded_props = []
    if cmds.getAttr(f"{shader}.transmission") == 0:
        excluded_props.extend(["transmission", "transmissionColor"])
    if cmds.getAttr(f"{shader}.coat") == 0:
        excluded_props.extend(["coat", "coatColor", "coatRoughness"])
    if cmds.getAttr(f"{shader}.sheen") == 0:
        excluded_props.extend(["sheen", "sheenColor", "sheenRoughness"])
    if cmds.getAttr(f"{shader}.emission") == 0:
        excluded_props.append("emission")

    for prop, attr_name in shader_props.items():
        if prop in excluded_props:
            continue  # 제외할 속성이면 건너뜁니다.
        
        prop_attr = f"{shader}.{prop}"
        if not is_connected(prop_attr):
            if prop in ["baseColor", "specularColor", "transmissionColor", "coatColor", "sheenColor"]:
                color_value = cmds.getAttr(prop_attr)[0]
                attributes[attr_name] = rgb_to_hex(color_value)
            else:
                value = cmds.getAttr(prop_attr)
                attributes[attr_name] = value
        else:
            print(f"{prop_attr} is connected to a texture or node, skipping.")

    if "specularColor" in attributes and "metalness" in attributes and "specularIntensity" in attributes:
        # 반사율 계산은 모든 필요한 속성이 있는 경우에만 수행합니다.
        metalness_weight = attributes["metalness"]
        specular_weight = attributes["specularIntensity"]
        specular_color = cmds.getAttr(f"{shader}.specularColor")[0] if not is_connected(f"{shader}.specularColor") else (1, 1, 1)
        reflectivity_value = calculate_reflectivity(metalness_weight, specular_weight, specular_color)
        attributes["reflectivity"] = reflectivity_value

    return attributes

def get_shader_properties_xlsx():
    """쉐이더 속성을 엑셀 파일로 내보냅니다. 특정 속성이 0이면 관련 속성들을 제외하고, 열 너비와 정렬을 설정합니다."""
    skip_shaders = ['lambert1', 'particleCloud1', 'shaderGlow1', 'standardSurface1']
    current_file = cmds.file(query=True, sceneName=True)
    file_path = os.path.dirname(current_file)
    output_file_path = os.path.join(file_path, "props.xlsx")

    if os.path.exists(output_file_path):
        os.remove(output_file_path)

    wb = Workbook()
    first_sheet = wb.active
    first_sheet.title = "Arnold Shaders"

    shaders = sorted(cmds.ls(materials=True))
    for shader in shaders:
        if shader not in skip_shaders and cmds.nodeType(shader) == "aiStandardSurface":
            attr_data = get_shader_attributes(shader)
            if attr_data:
                ws = wb.create_sheet(title=shader)
                for attribute, value in attr_data.items():
                    ws.append([attribute, value])

                # A열 너비 설정, A열 왼쪽 정렬
                ws.column_dimensions['A'].width = 20
                for cell in ws['A']:
                    cell.alignment = Alignment(horizontal='left')

                # B열 너비 설정, B열 오른쪽 정렬
                ws.column_dimensions['B'].width = 10
                for cell in ws['B']:
                    cell.alignment = Alignment(horizontal='right')

    # 'Arnold Shaders' 시트가 비어있으면 삭제합니다.
    if 'Arnold Shaders' in wb.sheetnames and not list(first_sheet.rows):
        wb.remove(first_sheet)

    # 엑셀 파일 저장
    wb.save(output_file_path)
    MGlobal.displayInfo("props.xlsx 파일 생성이 완료되었습니다!!!")

get_shader_properties_xlsx()
cmds.confirmDialog(title='완료', message='프로세스가 성공적으로 완료되었습니다!', button='확인')