161 lines
3.9 KiB
Python
161 lines
3.9 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""
|
||
|
|
CustomProperties 문자열 파싱 유틸리티
|
||
|
|
|
||
|
|
AnimMontage의 AnimNotifies에 있는 CustomProperties는 모두 문자열로 저장되어 있어서
|
||
|
|
타입 변환 및 파싱이 필요합니다.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import re
|
||
|
|
from typing import Any, Optional
|
||
|
|
|
||
|
|
|
||
|
|
def parse_float(value: str) -> Optional[float]:
|
||
|
|
"""문자열을 float로 변환 (실패 시 None)"""
|
||
|
|
try:
|
||
|
|
return float(value.strip())
|
||
|
|
except (ValueError, AttributeError):
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def parse_tag_name(value: str) -> Optional[str]:
|
||
|
|
"""
|
||
|
|
(TagName="Event.Attack.Normal") 형태에서 태그 이름 추출
|
||
|
|
|
||
|
|
Example:
|
||
|
|
'(TagName="Event.Attack.Normal")' -> 'Event.Attack.Normal'
|
||
|
|
"""
|
||
|
|
if not value:
|
||
|
|
return None
|
||
|
|
|
||
|
|
match = re.search(r'TagName="([^"]+)"', value)
|
||
|
|
if match:
|
||
|
|
return match.group(1)
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def parse_vector(value: str) -> Optional[dict]:
|
||
|
|
"""
|
||
|
|
(X=0.0,Y=1.0,Z=0.0) 형태를 dict로 변환
|
||
|
|
|
||
|
|
Example:
|
||
|
|
'(X=0.0,Y=1.0,Z=0.0)' -> {'X': 0.0, 'Y': 1.0, 'Z': 0.0}
|
||
|
|
"""
|
||
|
|
if not value:
|
||
|
|
return None
|
||
|
|
|
||
|
|
try:
|
||
|
|
x_match = re.search(r'X=([-\d.]+)', value)
|
||
|
|
y_match = re.search(r'Y=([-\d.]+)', value)
|
||
|
|
z_match = re.search(r'Z=([-\d.]+)', value)
|
||
|
|
|
||
|
|
if x_match and y_match and z_match:
|
||
|
|
return {
|
||
|
|
'X': float(x_match.group(1)),
|
||
|
|
'Y': float(y_match.group(1)),
|
||
|
|
'Z': float(z_match.group(1))
|
||
|
|
}
|
||
|
|
except (ValueError, AttributeError):
|
||
|
|
pass
|
||
|
|
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def parse_array(value: str) -> Optional[list]:
|
||
|
|
"""
|
||
|
|
(0.1,0.2,0.3) 형태를 리스트로 변환
|
||
|
|
|
||
|
|
Example:
|
||
|
|
'(0.1,0.2,0.3)' -> [0.1, 0.2, 0.3]
|
||
|
|
"""
|
||
|
|
if not value:
|
||
|
|
return None
|
||
|
|
|
||
|
|
try:
|
||
|
|
# 괄호 제거
|
||
|
|
cleaned = value.strip().strip('()')
|
||
|
|
if not cleaned:
|
||
|
|
return []
|
||
|
|
|
||
|
|
# 쉼표로 분리
|
||
|
|
parts = cleaned.split(',')
|
||
|
|
return [float(p.strip()) for p in parts if p.strip()]
|
||
|
|
except (ValueError, AttributeError):
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def parse_custom_property(value: str, field_name: str) -> Any:
|
||
|
|
"""
|
||
|
|
CustomProperties 필드의 값을 적절한 타입으로 변환
|
||
|
|
|
||
|
|
Args:
|
||
|
|
value: 원본 문자열 값
|
||
|
|
field_name: 필드 이름 (타입 추론에 사용)
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
파싱된 값 (float, str, dict, list 등)
|
||
|
|
"""
|
||
|
|
if not isinstance(value, str):
|
||
|
|
return value
|
||
|
|
|
||
|
|
value = value.strip()
|
||
|
|
|
||
|
|
# 빈 문자열
|
||
|
|
if not value or value == '':
|
||
|
|
return None
|
||
|
|
|
||
|
|
# TagName 파싱
|
||
|
|
if 'TagName=' in value:
|
||
|
|
tag = parse_tag_name(value)
|
||
|
|
if tag and tag != 'None':
|
||
|
|
return tag
|
||
|
|
return None
|
||
|
|
|
||
|
|
# Vector 파싱
|
||
|
|
if value.startswith('(') and ('X=' in value or 'Y=' in value or 'Z=' in value):
|
||
|
|
vec = parse_vector(value)
|
||
|
|
if vec:
|
||
|
|
return vec
|
||
|
|
|
||
|
|
# Array 파싱 (TimeArray, LocationArray 등)
|
||
|
|
if 'Array' in field_name and value.startswith('('):
|
||
|
|
arr = parse_array(value)
|
||
|
|
if arr is not None:
|
||
|
|
return arr
|
||
|
|
|
||
|
|
# Boolean 파싱
|
||
|
|
if value.lower() in ['true', 'false']:
|
||
|
|
return value.lower() == 'true'
|
||
|
|
|
||
|
|
# Float 파싱 시도
|
||
|
|
float_val = parse_float(value)
|
||
|
|
if float_val is not None:
|
||
|
|
return float_val
|
||
|
|
|
||
|
|
# 기본: 문자열 그대로 반환
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
def extract_notify_properties(notify: dict, target_fields: list) -> dict:
|
||
|
|
"""
|
||
|
|
AnimNotify에서 CustomProperties의 특정 필드만 추출
|
||
|
|
|
||
|
|
Args:
|
||
|
|
notify: AnimNotify 딕셔너리
|
||
|
|
target_fields: 추출할 필드 이름 리스트
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
추출된 프로퍼티 딕셔너리
|
||
|
|
"""
|
||
|
|
custom_props = notify.get('CustomProperties', {})
|
||
|
|
result = {}
|
||
|
|
|
||
|
|
for field in target_fields:
|
||
|
|
if field in custom_props:
|
||
|
|
value = custom_props[field]
|
||
|
|
parsed = parse_custom_property(value, field)
|
||
|
|
if parsed is not None:
|
||
|
|
result[field] = parsed
|
||
|
|
|
||
|
|
return result
|