pyjallib 0.1.16__py3-none-any.whl → 0.1.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyjallib/__init__.py +4 -2
- pyjallib/logger.py +201 -0
- pyjallib/max/bip.py +0 -21
- pyjallib/naming.py +4 -1
- pyjallib/perforce.py +183 -264
- pyjallib/progressEvent.py +75 -0
- pyjallib/ue5/ConfigFiles/UE5NamingConfig.json +487 -0
- pyjallib/ue5/__init__.py +170 -0
- pyjallib/ue5/disableInterchangeFrameWork.py +82 -0
- pyjallib/ue5/inUnreal/__init__.py +95 -0
- pyjallib/ue5/inUnreal/animationImporter.py +114 -0
- pyjallib/ue5/inUnreal/baseImporter.py +184 -0
- pyjallib/ue5/inUnreal/importerSettings.py +179 -0
- pyjallib/ue5/inUnreal/skeletalMeshImporter.py +112 -0
- pyjallib/ue5/inUnreal/skeletonImporter.py +105 -0
- pyjallib/ue5/logger.py +200 -0
- pyjallib/ue5/templateProcessor.py +254 -0
- pyjallib/ue5/templates/__init__.py +106 -0
- pyjallib/ue5/templates/animImportTemplate.py +22 -0
- pyjallib/ue5/templates/skeletalMeshImportTemplate.py +22 -0
- pyjallib/ue5/templates/skeletonImportTemplate.py +21 -0
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.17.dist-info}/METADATA +1 -1
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.17.dist-info}/RECORD +24 -7
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.17.dist-info}/WHEEL +0 -0
pyjallib/ue5/__init__.py
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
Unreal Engine 5 패키지
|
6
|
+
Unreal Engine 5 작업을 위한 모듈 모음
|
7
|
+
|
8
|
+
새로운 구조:
|
9
|
+
- Unreal Engine이 실행되지 않아도 사용 가능한 기능들 (templates, templateProcessor, logger 등)
|
10
|
+
- Unreal Engine이 필요한 기능들은 inUnreal 서브모듈에 위치
|
11
|
+
"""
|
12
|
+
|
13
|
+
# Unreal Engine 없이도 사용 가능한 기본 모듈들
|
14
|
+
__all__ = []
|
15
|
+
|
16
|
+
# Logger (조건부 unreal 지원)
|
17
|
+
try:
|
18
|
+
from .logger import (
|
19
|
+
ue5_logger,
|
20
|
+
set_log_level,
|
21
|
+
set_ue5_log_level,
|
22
|
+
get_log_file_path,
|
23
|
+
set_log_file_path
|
24
|
+
)
|
25
|
+
__all__.extend([
|
26
|
+
'ue5_logger',
|
27
|
+
'set_log_level',
|
28
|
+
'set_ue5_log_level',
|
29
|
+
'get_log_file_path',
|
30
|
+
'set_log_file_path'
|
31
|
+
])
|
32
|
+
except ImportError as e:
|
33
|
+
ue5_logger = None
|
34
|
+
set_log_level = None
|
35
|
+
set_ue5_log_level = None
|
36
|
+
get_log_file_path = None
|
37
|
+
set_log_file_path = None
|
38
|
+
print(f"[PyJalLib] UE5 Logger를 로드할 수 없습니다: {e}")
|
39
|
+
|
40
|
+
# TemplateProcessor (Unreal 없이 사용 가능)
|
41
|
+
try:
|
42
|
+
from .templateProcessor import TemplateProcessor
|
43
|
+
__all__.append('TemplateProcessor')
|
44
|
+
except ImportError as e:
|
45
|
+
TemplateProcessor = None
|
46
|
+
if ue5_logger:
|
47
|
+
ue5_logger.error(f"TemplateProcessor를 로드할 수 없습니다: {e}")
|
48
|
+
else:
|
49
|
+
print(f"[PyJalLib] TemplateProcessor를 로드할 수 없습니다: {e}")
|
50
|
+
|
51
|
+
# Templates 모듈 (Unreal 없이 사용 가능)
|
52
|
+
try:
|
53
|
+
from .templates import (
|
54
|
+
get_template_path,
|
55
|
+
get_all_template_paths,
|
56
|
+
get_available_templates,
|
57
|
+
validate_template_name,
|
58
|
+
ANIM_IMPORT_TEMPLATE,
|
59
|
+
SKELETON_IMPORT_TEMPLATE,
|
60
|
+
SKELETAL_MESH_IMPORT_TEMPLATE
|
61
|
+
)
|
62
|
+
__all__.extend([
|
63
|
+
'get_template_path',
|
64
|
+
'get_all_template_paths',
|
65
|
+
'get_available_templates',
|
66
|
+
'validate_template_name',
|
67
|
+
'ANIM_IMPORT_TEMPLATE',
|
68
|
+
'SKELETON_IMPORT_TEMPLATE',
|
69
|
+
'SKELETAL_MESH_IMPORT_TEMPLATE'
|
70
|
+
])
|
71
|
+
except ImportError as e:
|
72
|
+
get_template_path = None
|
73
|
+
get_all_template_paths = None
|
74
|
+
get_available_templates = None
|
75
|
+
validate_template_name = None
|
76
|
+
ANIM_IMPORT_TEMPLATE = None
|
77
|
+
SKELETON_IMPORT_TEMPLATE = None
|
78
|
+
SKELETAL_MESH_IMPORT_TEMPLATE = None
|
79
|
+
if ue5_logger:
|
80
|
+
ue5_logger.error(f"Templates 모듈을 로드할 수 없습니다: {e}")
|
81
|
+
else:
|
82
|
+
print(f"[PyJalLib] Templates 모듈을 로드할 수 없습니다: {e}")
|
83
|
+
|
84
|
+
# disableInterchangeFrameWork (Unreal 없이 사용 가능)
|
85
|
+
try:
|
86
|
+
from .disableInterchangeFrameWork import add_disabled_plugins_to_uproject
|
87
|
+
__all__.append('add_disabled_plugins_to_uproject')
|
88
|
+
except ImportError as e:
|
89
|
+
add_disabled_plugins_to_uproject = None
|
90
|
+
if ue5_logger:
|
91
|
+
ue5_logger.error(f"disableInterchangeFrameWork를 로드할 수 없습니다: {e}")
|
92
|
+
else:
|
93
|
+
print(f"[PyJalLib] disableInterchangeFrameWork를 로드할 수 없습니다: {e}")
|
94
|
+
|
95
|
+
# UE5 의존성 상태를 확인할 수 있는 함수
|
96
|
+
def is_ue5_available() -> bool:
|
97
|
+
"""
|
98
|
+
Unreal Engine 5가 사용 가능한지 확인합니다.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
bool: UE5가 사용 가능하면 True, 그렇지 않으면 False
|
102
|
+
"""
|
103
|
+
try:
|
104
|
+
import unreal
|
105
|
+
return True
|
106
|
+
except ImportError:
|
107
|
+
return False
|
108
|
+
|
109
|
+
def get_available_modules() -> dict:
|
110
|
+
"""
|
111
|
+
현재 사용 가능한 모듈 목록을 반환합니다.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
dict: 모듈 카테고리별 사용 가능한 모듈 정보
|
115
|
+
"""
|
116
|
+
available = {
|
117
|
+
'core': [],
|
118
|
+
'templates': [],
|
119
|
+
'utils': [],
|
120
|
+
'unreal_dependent': []
|
121
|
+
}
|
122
|
+
|
123
|
+
# Core 모듈들
|
124
|
+
if ue5_logger is not None:
|
125
|
+
available['core'].append('logger')
|
126
|
+
if TemplateProcessor is not None:
|
127
|
+
available['core'].append('TemplateProcessor')
|
128
|
+
|
129
|
+
# Templates 관련
|
130
|
+
if get_template_path is not None:
|
131
|
+
available['templates'].append('template_management')
|
132
|
+
|
133
|
+
# Utilities
|
134
|
+
if add_disabled_plugins_to_uproject is not None:
|
135
|
+
available['utils'].append('disableInterchangeFrameWork')
|
136
|
+
|
137
|
+
# Unreal 의존성 모듈들 (inUnreal 서브모듈에서 가져오기)
|
138
|
+
try:
|
139
|
+
from . import inUnreal
|
140
|
+
if hasattr(inUnreal, 'get_available_modules'):
|
141
|
+
available['unreal_dependent'] = inUnreal.get_available_modules()
|
142
|
+
except ImportError:
|
143
|
+
available['unreal_dependent'] = []
|
144
|
+
|
145
|
+
return available
|
146
|
+
|
147
|
+
def get_module_status() -> dict:
|
148
|
+
"""
|
149
|
+
모듈 상태 정보를 반환합니다.
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
dict: 모듈 상태 정보
|
153
|
+
"""
|
154
|
+
status = {
|
155
|
+
'ue5_available': is_ue5_available(),
|
156
|
+
'available_modules': get_available_modules(),
|
157
|
+
'structure': {
|
158
|
+
'core_modules': 'Unreal Engine 없이 사용 가능',
|
159
|
+
'inUnreal_modules': 'Unreal Engine 필요',
|
160
|
+
'templates': '템플릿 파일 관리'
|
161
|
+
}
|
162
|
+
}
|
163
|
+
return status
|
164
|
+
|
165
|
+
# 헬퍼 함수들도 __all__에 추가
|
166
|
+
__all__.extend([
|
167
|
+
'is_ue5_available',
|
168
|
+
'get_available_modules',
|
169
|
+
'get_module_status'
|
170
|
+
])
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import json
|
2
|
+
from pathlib import Path
|
3
|
+
from .logger import ue5_logger
|
4
|
+
|
5
|
+
def add_disabled_plugins_to_uproject(uproject_path):
|
6
|
+
"""
|
7
|
+
특정 플러그인들을 Enabled=false 상태로 추가하여 temp_ 접두사가 붙은 새 파일로 저장
|
8
|
+
|
9
|
+
Args:
|
10
|
+
uproject_path (str): 원본 .uproject 파일 경로
|
11
|
+
"""
|
12
|
+
|
13
|
+
# 비활성화 상태로 추가할 플러그인들
|
14
|
+
plugins_to_add = [
|
15
|
+
{
|
16
|
+
"Name": "MeshPainting",
|
17
|
+
"Enabled": False
|
18
|
+
},
|
19
|
+
{
|
20
|
+
"Name": "InterchangeEditor",
|
21
|
+
"Enabled": False,
|
22
|
+
"SupportedTargetPlatforms": [
|
23
|
+
"Win64",
|
24
|
+
"Linux",
|
25
|
+
"Mac"
|
26
|
+
]
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"Name": "GLTFExporter",
|
30
|
+
"Enabled": False
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"Name": "InterchangeTests",
|
34
|
+
"Enabled": False
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"Name": "Interchange",
|
38
|
+
"Enabled": False,
|
39
|
+
"SupportedTargetPlatforms": [
|
40
|
+
"Win64",
|
41
|
+
"Linux",
|
42
|
+
"Mac"
|
43
|
+
]
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"Name": "InterchangeAssets",
|
47
|
+
"Enabled": False
|
48
|
+
}
|
49
|
+
]
|
50
|
+
|
51
|
+
# 파일 읽기
|
52
|
+
with open(uproject_path, 'r', encoding='utf-8') as f:
|
53
|
+
project_data = json.load(f)
|
54
|
+
|
55
|
+
# 기존 플러그인 이름들 확인
|
56
|
+
existing_plugin_names = set()
|
57
|
+
if 'Plugins' in project_data:
|
58
|
+
existing_plugin_names = {plugin['Name'] for plugin in project_data['Plugins']}
|
59
|
+
|
60
|
+
# 새 플러그인 추가 (중복 체크)
|
61
|
+
added_count = 0
|
62
|
+
for plugin in plugins_to_add:
|
63
|
+
if plugin['Name'] not in existing_plugin_names:
|
64
|
+
project_data['Plugins'].append(plugin)
|
65
|
+
added_count += 1
|
66
|
+
ue5_logger.info(f"추가됨 (비활성화): {plugin['Name']}")
|
67
|
+
else:
|
68
|
+
ue5_logger.info(f"이미 존재: {plugin['Name']}")
|
69
|
+
|
70
|
+
# 출력 파일 경로 생성 (temp_ 접두사 추가)
|
71
|
+
input_path = Path(uproject_path)
|
72
|
+
output_filename = f"temp_{input_path.name}"
|
73
|
+
output_path = input_path.parent / output_filename
|
74
|
+
|
75
|
+
# 파일 저장
|
76
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
77
|
+
json.dump(project_data, f, indent='\t', ensure_ascii=False)
|
78
|
+
|
79
|
+
ue5_logger.info(f"총 {added_count}개 플러그인 추가 (비활성화 상태)")
|
80
|
+
ue5_logger.info(f"출력 파일: {output_path}")
|
81
|
+
|
82
|
+
return output_path
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
UE5 inUnreal 패키지
|
6
|
+
Unreal Engine 5가 실행 중일 때만 사용 가능한 모듈들
|
7
|
+
|
8
|
+
주의: 이 패키지의 모든 모듈은 Unreal Engine이 실행 중일 때만 임포트 가능합니다.
|
9
|
+
"""
|
10
|
+
|
11
|
+
# UE5 가용성 확인
|
12
|
+
def is_ue5_available() -> bool:
|
13
|
+
"""
|
14
|
+
Unreal Engine 5가 사용 가능한지 확인합니다.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
bool: UE5가 사용 가능하면 True, 그렇지 않으면 False
|
18
|
+
"""
|
19
|
+
try:
|
20
|
+
import unreal
|
21
|
+
return True
|
22
|
+
except ImportError:
|
23
|
+
return False
|
24
|
+
|
25
|
+
# 기본적으로 사용 가능한 모듈들
|
26
|
+
__all__ = ['is_ue5_available']
|
27
|
+
|
28
|
+
# UE5가 사용 가능한 경우에만 모듈들을 임포트
|
29
|
+
if is_ue5_available():
|
30
|
+
try:
|
31
|
+
from .importerSettings import ImporterSettings
|
32
|
+
__all__.append('ImporterSettings')
|
33
|
+
except ImportError as e:
|
34
|
+
ImporterSettings = None
|
35
|
+
print(f"[PyJalLib] ImporterSettings 임포트 실패: {e}")
|
36
|
+
|
37
|
+
try:
|
38
|
+
from .baseImporter import BaseImporter
|
39
|
+
__all__.append('BaseImporter')
|
40
|
+
except ImportError as e:
|
41
|
+
BaseImporter = None
|
42
|
+
print(f"[PyJalLib] BaseImporter 임포트 실패: {e}")
|
43
|
+
|
44
|
+
try:
|
45
|
+
from .skeletonImporter import SkeletonImporter
|
46
|
+
__all__.append('SkeletonImporter')
|
47
|
+
except ImportError as e:
|
48
|
+
SkeletonImporter = None
|
49
|
+
print(f"[PyJalLib] SkeletonImporter 임포트 실패: {e}")
|
50
|
+
|
51
|
+
try:
|
52
|
+
from .skeletalMeshImporter import SkeletalMeshImporter
|
53
|
+
__all__.append('SkeletalMeshImporter')
|
54
|
+
except ImportError as e:
|
55
|
+
SkeletalMeshImporter = None
|
56
|
+
print(f"[PyJalLib] SkeletalMeshImporter 임포트 실패: {e}")
|
57
|
+
|
58
|
+
try:
|
59
|
+
from .animationImporter import AnimationImporter
|
60
|
+
__all__.append('AnimationImporter')
|
61
|
+
except ImportError as e:
|
62
|
+
AnimationImporter = None
|
63
|
+
print(f"[PyJalLib] AnimationImporter 임포트 실패: {e}")
|
64
|
+
|
65
|
+
else:
|
66
|
+
# UE5가 사용 불가능한 경우 모든 모듈을 None으로 설정
|
67
|
+
ImporterSettings = None
|
68
|
+
BaseImporter = None
|
69
|
+
SkeletonImporter = None
|
70
|
+
SkeletalMeshImporter = None
|
71
|
+
AnimationImporter = None
|
72
|
+
print("[PyJalLib] Unreal Engine이 실행되지 않았습니다. inUnreal 모듈들을 사용할 수 없습니다.")
|
73
|
+
|
74
|
+
def get_available_modules() -> list:
|
75
|
+
"""
|
76
|
+
현재 사용 가능한 모듈 목록을 반환합니다.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
list: 사용 가능한 모듈 이름 목록
|
80
|
+
"""
|
81
|
+
available = []
|
82
|
+
if 'ImporterSettings' in __all__ and ImporterSettings is not None:
|
83
|
+
available.append('ImporterSettings')
|
84
|
+
if 'BaseImporter' in __all__ and BaseImporter is not None:
|
85
|
+
available.append('BaseImporter')
|
86
|
+
if 'SkeletonImporter' in __all__ and SkeletonImporter is not None:
|
87
|
+
available.append('SkeletonImporter')
|
88
|
+
if 'SkeletalMeshImporter' in __all__ and SkeletalMeshImporter is not None:
|
89
|
+
available.append('SkeletalMeshImporter')
|
90
|
+
if 'AnimationImporter' in __all__ and AnimationImporter is not None:
|
91
|
+
available.append('AnimationImporter')
|
92
|
+
return available
|
93
|
+
|
94
|
+
# 헬퍼 함수도 __all__에 추가
|
95
|
+
__all__.append('get_available_modules')
|
@@ -0,0 +1,114 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
UE5 애니메이션 임포터 모듈
|
6
|
+
|
7
|
+
이 모듈은 FBX 파일에서 애니메이션 에셋을 UE5로 임포트하는 기능을 제공합니다.
|
8
|
+
PyJalLib의 naming 모듈을 사용하여 에셋 이름을 자동 생성합니다.
|
9
|
+
"""
|
10
|
+
|
11
|
+
import unreal
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import Optional, Dict, Any
|
14
|
+
|
15
|
+
# UE5 모듈 import
|
16
|
+
from .baseImporter import BaseImporter
|
17
|
+
from ..logger import ue5_logger
|
18
|
+
from .importerSettings import ImporterSettings
|
19
|
+
|
20
|
+
class AnimationImporter(BaseImporter):
|
21
|
+
def __init__(self, inContentRootPrefix: str, inFbxRootPrefix: str):
|
22
|
+
super().__init__(inContentRootPrefix, inFbxRootPrefix, "Animation")
|
23
|
+
ue5_logger.info("AnimationImporter 초기화 완료")
|
24
|
+
|
25
|
+
@property
|
26
|
+
def asset_type(self) -> str:
|
27
|
+
return "Animation"
|
28
|
+
|
29
|
+
def _create_import_task(self, inFbxFile: str, inDestinationPath: str, inFbxSkeletonPath: str):
|
30
|
+
"""애니메이션 임포트를 위한 태스크 생성 - 스켈레톤 필수 지정"""
|
31
|
+
ue5_logger.debug(f"애니메이션 임포트 태스크 생성 시작: {inFbxFile}")
|
32
|
+
|
33
|
+
importOptions = self.importerSettings.load_options()
|
34
|
+
ue5_logger.debug("애니메이션 임포트 옵션 로드 완료")
|
35
|
+
|
36
|
+
# 스켈레톤 필수 설정
|
37
|
+
if inFbxSkeletonPath is None:
|
38
|
+
error_msg = "애니메이션 임포트에는 스켈레톤이 필수입니다"
|
39
|
+
ue5_logger.error(error_msg)
|
40
|
+
raise ValueError(error_msg)
|
41
|
+
|
42
|
+
skeletonPath = self.convert_fbx_path_to_skeleton_path(inFbxSkeletonPath)
|
43
|
+
skeletonAssetData = unreal.EditorAssetLibrary.find_asset_data(skeletonPath)
|
44
|
+
if not skeletonAssetData.is_valid():
|
45
|
+
error_msg = f"스켈레톤 에셋을 찾을 수 없음: {skeletonPath}"
|
46
|
+
ue5_logger.error(error_msg)
|
47
|
+
raise ValueError(error_msg)
|
48
|
+
|
49
|
+
animSkeleton = skeletonAssetData.get_asset()
|
50
|
+
importOptions.set_editor_property('skeleton', animSkeleton)
|
51
|
+
ue5_logger.debug(f"스켈레톤 설정됨: {animSkeleton.get_name()}")
|
52
|
+
|
53
|
+
# 에셋 이름 결정: FBX 파일 이름에서 확장자 제거
|
54
|
+
assetName = Path(inFbxFile).stem
|
55
|
+
|
56
|
+
task = unreal.AssetImportTask()
|
57
|
+
task.automated = True
|
58
|
+
task.destination_path = inDestinationPath
|
59
|
+
task.filename = inFbxFile
|
60
|
+
task.destination_name = assetName
|
61
|
+
task.replace_existing = True
|
62
|
+
task.save = True
|
63
|
+
task.options = importOptions
|
64
|
+
|
65
|
+
ue5_logger.debug(f"애니메이션 임포트 태스크 생성 완료: Destination={inDestinationPath}, AssetName={assetName}")
|
66
|
+
return task
|
67
|
+
|
68
|
+
def import_animation(self, inFbxFile: str, inFbxSkeletonPath: str, inAssetName: str = None, inDescription: str = None):
|
69
|
+
ue5_logger.info(f"애니메이션 임포트 시작: {inFbxFile}")
|
70
|
+
|
71
|
+
destinationPath, assetName = self._prepare_import_paths(inFbxFile, inAssetName)
|
72
|
+
assetFullPath = f"{destinationPath}/{assetName}"
|
73
|
+
|
74
|
+
# 기존 에셋이 있는 경우 소스 컨트롤에서 체크아웃
|
75
|
+
if unreal.Paths.file_exists(assetFullPath):
|
76
|
+
unreal.SourceControl.check_out_or_add_file(assetFullPath, silent=True)
|
77
|
+
|
78
|
+
task = self._create_import_task(inFbxFile, destinationPath, inFbxSkeletonPath)
|
79
|
+
|
80
|
+
ue5_logger.info(f"애니메이션 임포트 실행: {inFbxFile} -> {destinationPath}/{assetName}")
|
81
|
+
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
82
|
+
|
83
|
+
result = task.get_objects()
|
84
|
+
if len(result) == 0:
|
85
|
+
error_msg = f"애니메이션 임포트 실패: {inFbxFile}"
|
86
|
+
ue5_logger.error(error_msg)
|
87
|
+
raise ValueError(error_msg)
|
88
|
+
|
89
|
+
# 임포트된 애니메이션 에셋의 시스템 경로 가져오기
|
90
|
+
importedAnimation = None
|
91
|
+
for asset in result:
|
92
|
+
if isinstance(asset, unreal.AnimSequence):
|
93
|
+
importedAnimation = asset
|
94
|
+
break
|
95
|
+
|
96
|
+
if importedAnimation is None:
|
97
|
+
error_msg = f"애니메이션 에셋을 찾을 수 없음: {inFbxFile}"
|
98
|
+
ue5_logger.error(error_msg)
|
99
|
+
raise ValueError(error_msg)
|
100
|
+
|
101
|
+
importedObjectPaths = self.get_dirty_deps(assetFullPath)
|
102
|
+
|
103
|
+
animationSystemFullPath = unreal.SystemLibrary.get_system_path(importedAnimation)
|
104
|
+
importedObjectPaths.append(animationSystemFullPath)
|
105
|
+
|
106
|
+
checkInDescription = f"Animation Imported by {inFbxFile} to {assetFullPath}"
|
107
|
+
if inDescription is not None:
|
108
|
+
checkInDescription = inDescription
|
109
|
+
|
110
|
+
unreal.SourceControl.check_in_files(importedObjectPaths, checkInDescription, silent=True)
|
111
|
+
|
112
|
+
ue5_logger.info(f"애니메이션 임포트 성공: {inFbxFile} -> {len(result)}개 객체 생성")
|
113
|
+
return self._create_result_dict(inFbxFile, destinationPath, assetName, True)
|
114
|
+
|
@@ -0,0 +1,184 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
UE5 베이스 임포터 모듈
|
6
|
+
UE5 에셋 임포트의 기본 기능을 제공하는 추상 클래스입니다.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import json
|
10
|
+
import os
|
11
|
+
from pathlib import Path
|
12
|
+
from abc import ABC, abstractmethod
|
13
|
+
from typing import Dict, Any, Optional
|
14
|
+
|
15
|
+
from pyjallib.naming import Naming
|
16
|
+
|
17
|
+
import unreal
|
18
|
+
|
19
|
+
# UE5 모듈 import
|
20
|
+
from .importerSettings import ImporterSettings
|
21
|
+
from ..logger import ue5_logger
|
22
|
+
|
23
|
+
class BaseImporter(ABC):
|
24
|
+
"""모든 UE5 임포터의 베이스 클래스"""
|
25
|
+
|
26
|
+
def __init__(self, inContentRootPrefix: str, inFbxRootPrefix: str, inPresetName: str):
|
27
|
+
self.contentRootPrefix = inContentRootPrefix
|
28
|
+
self.fbxRootPrefix = inFbxRootPrefix
|
29
|
+
self.importerSettings = ImporterSettings(
|
30
|
+
inContentRootPrefix=inContentRootPrefix,
|
31
|
+
inFbxRootPrefix=inFbxRootPrefix,
|
32
|
+
inPresetName=inPresetName
|
33
|
+
)
|
34
|
+
config_path = str(Path(__file__).parent.parent / "ConfigFiles" / "UE5NamingConfig.json")
|
35
|
+
self.naming = Naming(configPath=config_path)
|
36
|
+
ue5_logger.debug(f"BaseImporter 초기화: ContentRoot={inContentRootPrefix}, FbxRoot={inFbxRootPrefix}, Preset={inPresetName}")
|
37
|
+
|
38
|
+
@property
|
39
|
+
@abstractmethod
|
40
|
+
def asset_type(self) -> str:
|
41
|
+
"""에셋 타입을 반환하는 추상 프로퍼티"""
|
42
|
+
pass
|
43
|
+
|
44
|
+
def convert_fbx_path_to_absolute_content_path(self, inFbxPath: str) -> str:
|
45
|
+
"""
|
46
|
+
FBX 파일 경로를 UE5 Content 경로로 변환합니다.
|
47
|
+
fbxRootPrefix가 inFbxPath의 prefix일 경우, contentRootPrefix로 치환합니다.
|
48
|
+
Args:
|
49
|
+
inFbxPath (str): 변환할 FBX 파일 경로
|
50
|
+
Returns:
|
51
|
+
str: 변환된 Content 경로
|
52
|
+
"""
|
53
|
+
ue5_logger.debug(f"FBX 경로 변환 시작: {inFbxPath}")
|
54
|
+
|
55
|
+
fbxRoot = Path(self.fbxRootPrefix).resolve()
|
56
|
+
contentRoot = Path(self.contentRootPrefix).resolve()
|
57
|
+
fbxPath = Path(inFbxPath).resolve()
|
58
|
+
|
59
|
+
if str(fbxPath).startswith(str(fbxRoot)):
|
60
|
+
relative_path = fbxPath.relative_to(fbxRoot)
|
61
|
+
result_path = str(contentRoot / relative_path)
|
62
|
+
ue5_logger.debug(f"경로 변환 완료: {inFbxPath} -> {result_path}")
|
63
|
+
return result_path
|
64
|
+
else:
|
65
|
+
ue5_logger.error(f"입력 경로가 fbxRootPrefix로 시작하지 않습니다: {inFbxPath}")
|
66
|
+
return ""
|
67
|
+
|
68
|
+
def convert_fbx_path_to_content_path(self, inFbxPath: str) -> str:
|
69
|
+
ue5_logger.debug(f"Content 경로 변환 시작: {inFbxPath}")
|
70
|
+
|
71
|
+
absoluteContentPath = self.convert_fbx_path_to_absolute_content_path(inFbxPath)
|
72
|
+
if absoluteContentPath == "":
|
73
|
+
return ""
|
74
|
+
|
75
|
+
# UE5 프로젝트의 Content 디렉토리 경로 가져오기
|
76
|
+
contentPath = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_content_dir())
|
77
|
+
|
78
|
+
absoluteContentPathObj = Path(absoluteContentPath)
|
79
|
+
contentPathObj = Path(contentPath)
|
80
|
+
|
81
|
+
# absoluteContentPath가 contentPath로 시작하는지 확인
|
82
|
+
if str(absoluteContentPathObj).startswith(str(contentPathObj)):
|
83
|
+
# contentPath 부분을 /Game/으로 직접 치환
|
84
|
+
relativePath = absoluteContentPathObj.relative_to(contentPathObj)
|
85
|
+
# pathlib을 사용하여 경로 정규화
|
86
|
+
normalizedPath = Path(relativePath).as_posix()
|
87
|
+
result_path = f"/Game/{normalizedPath}"
|
88
|
+
|
89
|
+
# UE5 내장 함수를 사용하여 경로 정규화
|
90
|
+
normalizedResultPath = unreal.Paths.normalize_directory_name(result_path)
|
91
|
+
|
92
|
+
ue5_logger.debug(f"Content 경로 변환 완료: {inFbxPath} -> {normalizedResultPath}")
|
93
|
+
return normalizedResultPath
|
94
|
+
else:
|
95
|
+
ue5_logger.error(f"절대 경로가 콘텐츠 디렉토리로 시작하지 않습니다: {absoluteContentPath}")
|
96
|
+
return ""
|
97
|
+
|
98
|
+
def convert_fbx_path_to_skeleton_path(self, inFbxPath: str) -> str:
|
99
|
+
"""
|
100
|
+
FBX 파일 경로를 스켈레톤 경로로 변환합니다.
|
101
|
+
fbxRootPrefix가 inFbxPath의 prefix일 경우, contentRootPrefix로 치환합니다.
|
102
|
+
"""
|
103
|
+
skeletonPath = self.convert_fbx_path_to_content_path(inFbxPath)
|
104
|
+
if skeletonPath == "":
|
105
|
+
return ""
|
106
|
+
|
107
|
+
destinationPath = unreal.Paths.get_path(skeletonPath)
|
108
|
+
assetName = unreal.Paths.get_base_filename(skeletonPath)
|
109
|
+
assetName = self.naming.replace_name_part("AssetType", assetName, self.naming.get_name_part("AssetType").get_value_by_description("Skeleton"))
|
110
|
+
skeletonFullPath = f"{destinationPath}/{assetName}"
|
111
|
+
return skeletonFullPath
|
112
|
+
|
113
|
+
def _create_result_dict(self, inSourceFile: str, inPath: str, inName: str, inSuccess: bool = True):
|
114
|
+
"""결과 딕셔너리를 생성하는 공통 메서드"""
|
115
|
+
result = {
|
116
|
+
"SourceFile": inSourceFile,
|
117
|
+
"Path": inPath,
|
118
|
+
"Name": inName,
|
119
|
+
"Type": self.asset_type,
|
120
|
+
"Success": inSuccess
|
121
|
+
}
|
122
|
+
ue5_logger.debug(f"결과 딕셔너리 생성: {result}")
|
123
|
+
return result
|
124
|
+
|
125
|
+
def _prepare_import_paths(self, inFbxFile: str, inAssetName: str = None):
|
126
|
+
"""임포트 경로를 준비하는 공통 메서드"""
|
127
|
+
ue5_logger.info(f"임포트 경로 준비 시작: {inFbxFile}")
|
128
|
+
|
129
|
+
assetPath = self.convert_fbx_path_to_content_path(inFbxFile)
|
130
|
+
if assetPath == "":
|
131
|
+
error_msg = f"FBX 파일 경로가 올바르지 않습니다: {inFbxFile}"
|
132
|
+
ue5_logger.error(error_msg)
|
133
|
+
raise ValueError(error_msg)
|
134
|
+
|
135
|
+
# 경로에서 파일명 분리
|
136
|
+
destinationPath = unreal.Paths.get_path(assetPath)
|
137
|
+
assetName = unreal.Paths.get_base_filename(assetPath)
|
138
|
+
|
139
|
+
# 에셋 이름 결정: 입력된 이름이 있으면 사용, 없으면 FBX 파일 이름에서 확장자 제거
|
140
|
+
if inAssetName is not None:
|
141
|
+
assetName = inAssetName
|
142
|
+
|
143
|
+
ue5_logger.info(f"임포트 경로 정보: Destination={destinationPath}, AssetName={assetName}")
|
144
|
+
|
145
|
+
if not unreal.Paths.directory_exists(destinationPath):
|
146
|
+
ue5_logger.info(f"디렉토리 생성: {destinationPath}")
|
147
|
+
unreal.EditorAssetLibrary.make_directory(destinationPath)
|
148
|
+
|
149
|
+
if unreal.Paths.file_exists(assetPath):
|
150
|
+
ue5_logger.info(f"기존 파일 체크아웃: {assetPath}")
|
151
|
+
unreal.SourceControl.check_out_or_add_file(assetPath)
|
152
|
+
|
153
|
+
return destinationPath, assetName
|
154
|
+
|
155
|
+
@abstractmethod
|
156
|
+
def _create_import_task(self, inFbxFile: str, inDestinationPath: str):
|
157
|
+
"""임포트 태스크를 생성하는 추상 메서드 - 각 임포터에서 구현"""
|
158
|
+
pass
|
159
|
+
|
160
|
+
def get_dirty_deps(self, inAssetPath: str):
|
161
|
+
returnList = []
|
162
|
+
|
163
|
+
assetRegistry = unreal.AssetRegistryHelpers.get_asset_registry()
|
164
|
+
assetData = unreal.EditorAssetLibrary.find_asset_data(inAssetPath)
|
165
|
+
|
166
|
+
depPackages = assetRegistry.get_dependencies(
|
167
|
+
assetData.package_name,
|
168
|
+
unreal.AssetRegistryDependencyOptions(
|
169
|
+
include_soft_package_references=False, # Soft reference 제외
|
170
|
+
include_hard_package_references=True, # Hard reference만
|
171
|
+
include_searchable_names=False,
|
172
|
+
include_soft_management_references=False,
|
173
|
+
include_hard_management_references=False
|
174
|
+
)
|
175
|
+
)
|
176
|
+
|
177
|
+
for dep in depPackages:
|
178
|
+
depPathStart = str(dep).split('/')[1]
|
179
|
+
assetPathStart = str(assetData.package_name).split('/')[1]
|
180
|
+
if depPathStart == assetPathStart:
|
181
|
+
if unreal.EditorAssetLibrary.save_asset(dep, only_if_is_dirty=True):
|
182
|
+
returnList.append(dep)
|
183
|
+
|
184
|
+
return returnList
|