pyjallib 0.1.16__py3-none-any.whl → 0.1.19__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 -1
- pyjallib/exceptions.py +75 -0
- pyjallib/logger.py +288 -0
- pyjallib/max/__init__.py +8 -0
- pyjallib/max/autoClavicle.py +17 -5
- pyjallib/max/bip.py +0 -21
- pyjallib/max/bone.py +21 -1
- pyjallib/max/boneChain.py +2 -0
- pyjallib/max/constraint.py +27 -2
- pyjallib/max/elbow.py +105 -0
- pyjallib/max/groinBone.py +2 -0
- pyjallib/max/header.py +121 -113
- pyjallib/max/hip.py +2 -0
- pyjallib/max/inguinal.py +117 -0
- pyjallib/max/kneeBone.py +2 -0
- pyjallib/max/macro/jal_macro_bone.py +221 -8
- pyjallib/max/macro/jal_macro_constraint.py +30 -0
- pyjallib/max/mirror.py +20 -13
- pyjallib/max/shoulder.py +173 -0
- pyjallib/max/twistBone.py +22 -19
- pyjallib/max/volumeBone.py +12 -1
- pyjallib/max/wrist.py +113 -0
- pyjallib/nameToPath.py +78 -61
- pyjallib/naming.py +4 -1
- pyjallib/perforce.py +196 -347
- 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 +206 -0
- pyjallib/ue5/inUnreal/baseImporter.py +187 -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 +241 -0
- pyjallib/ue5/templateProcessor.py +287 -0
- pyjallib/ue5/templates/__init__.py +109 -0
- pyjallib/ue5/templates/animImportTemplate.py +22 -0
- pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
- pyjallib/ue5/templates/skeletalMeshImportTemplate.py +22 -0
- pyjallib/ue5/templates/skeletonImportTemplate.py +21 -0
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/METADATA +1 -1
- pyjallib-0.1.19.dist-info/RECORD +72 -0
- pyjallib-0.1.16.dist-info/RECORD +0 -49
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/WHEEL +0 -0
@@ -0,0 +1,187 @@
|
|
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
|
+
ue5_logger.error(f"assetData: {assetData.asset_name}")
|
167
|
+
|
168
|
+
depPackages = assetRegistry.get_dependencies(
|
169
|
+
assetData.package_name,
|
170
|
+
unreal.AssetRegistryDependencyOptions(
|
171
|
+
include_soft_package_references=False, # Soft reference 제외
|
172
|
+
include_hard_package_references=True, # Hard reference만
|
173
|
+
include_searchable_names=False,
|
174
|
+
include_soft_management_references=False,
|
175
|
+
include_hard_management_references=False
|
176
|
+
)
|
177
|
+
)
|
178
|
+
|
179
|
+
if depPackages is not None:
|
180
|
+
for dep in depPackages:
|
181
|
+
depPathStart = str(dep).split('/')[1]
|
182
|
+
assetPathStart = str(assetData.package_name).split('/')[1]
|
183
|
+
if depPathStart == assetPathStart:
|
184
|
+
if unreal.EditorAssetLibrary.save_asset(dep, only_if_is_dirty=True):
|
185
|
+
returnList.append(dep)
|
186
|
+
|
187
|
+
return returnList
|
@@ -0,0 +1,179 @@
|
|
1
|
+
"""
|
2
|
+
UE5 에셋 임포트 설정 관리 모듈
|
3
|
+
|
4
|
+
이 모듈은 UE5 에셋 임포트에 필요한 설정을 관리합니다.
|
5
|
+
JSON 설정 파일을 로드하고, 프리셋 기반으로 설정을 반환하는 기능을 제공합니다.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import os
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Dict, Any, Optional
|
12
|
+
|
13
|
+
import unreal
|
14
|
+
from ..logger import ue5_logger
|
15
|
+
|
16
|
+
|
17
|
+
class ImporterSettings:
|
18
|
+
"""UE5 에셋 임포트 설정 관리 클래스"""
|
19
|
+
|
20
|
+
def __init__(self, inContentRootPrefix: str, inFbxRootPrefix: str, inPresetName: str):
|
21
|
+
"""
|
22
|
+
ImporterSettings 초기화
|
23
|
+
|
24
|
+
Args:
|
25
|
+
inContentRootPrefix: UE5 Content 경로의 루트 접두사
|
26
|
+
inFbxRootPrefix: FBX 파일 경로의 루트 접두사
|
27
|
+
inPresetName: 사용할 프리셋 이름 (Skeleton, SkeletalMesh, Animation 중 하나)
|
28
|
+
"""
|
29
|
+
self.contentRootPrefix = inContentRootPrefix
|
30
|
+
self.fbxRootPrefix = inFbxRootPrefix
|
31
|
+
self.presetName = inPresetName
|
32
|
+
|
33
|
+
self.configPath = Path(__file__).parent / 'ConfigFiles' / 'UE5ImportConfig.json'
|
34
|
+
ue5_logger.debug(f"ImporterSettings 초기화: ContentRoot={inContentRootPrefix}, FbxRoot={inFbxRootPrefix}, Preset={inPresetName}")
|
35
|
+
|
36
|
+
def load_preset(self, inPresetName: Optional[str] = None):
|
37
|
+
if inPresetName is None:
|
38
|
+
inPresetName = self.presetName
|
39
|
+
|
40
|
+
if inPresetName is None:
|
41
|
+
raise ValueError("Preset name is required")
|
42
|
+
|
43
|
+
preset_path = Path(__file__).parent / 'ConfigFiles' / f'{inPresetName}.json'
|
44
|
+
|
45
|
+
def set_options_for_skeleton_import(self):
|
46
|
+
"""
|
47
|
+
스켈레톤 임포트를 위한 옵션을 설정합니다.
|
48
|
+
|
49
|
+
스켈레탈 메쉬는 임포트하고, 애니메이션은 임포트하지 않으며,
|
50
|
+
매테리얼이나 텍스처도 임포트하지 않고, 스켈레톤은 새로 생성합니다.
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
unreal.FbxImportUI: 설정된 임포트 옵션
|
54
|
+
"""
|
55
|
+
# FBX 임포트 옵션 설정
|
56
|
+
fbxImportOptions = unreal.FbxImportUI()
|
57
|
+
fbxImportOptions.reset_to_default()
|
58
|
+
fbxImportOptions.set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_SKELETAL_MESH) # 스켈레탈 메쉬 타입
|
59
|
+
|
60
|
+
# 메시 임포트 옵션 설정
|
61
|
+
fbxImportOptions.set_editor_property('import_mesh', True) # 스켈레탈 메쉬 임포트
|
62
|
+
fbxImportOptions.set_editor_property('import_textures', False) # 텍스처 임포트 안함
|
63
|
+
fbxImportOptions.set_editor_property('import_materials', False) # 매테리얼 임포트 안함
|
64
|
+
fbxImportOptions.set_editor_property('import_animations', False) # 애니메이션 임포트 안함
|
65
|
+
fbxImportOptions.set_editor_property('import_as_skeletal', True) # 스켈레탈 메쉬로 임포트
|
66
|
+
fbxImportOptions.set_editor_property('mesh_type_to_import', unreal.FBXImportType.FBXIT_SKELETAL_MESH) # 스켈레탈 메쉬로 임포트
|
67
|
+
fbxImportOptions.set_editor_property('create_physics_asset', False) # 피직 애셋 생성 안함
|
68
|
+
|
69
|
+
# 스켈레탈 메쉬 임포트 세부 옵션
|
70
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('import_morph_targets', False) # 모프 타겟 임포트 안함
|
71
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('import_mesh_lo_ds', False) # LOD 임포트 안함
|
72
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('convert_scene_unit', False) # 씬 단위 변환 안함
|
73
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('force_front_x_axis', False) # X축 강제 변환 안함
|
74
|
+
|
75
|
+
# LOD 임포트 (필요하다면)
|
76
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('import_mesh_lo_ds', False) # LOD를 임포트하지 않음
|
77
|
+
|
78
|
+
# 스켈레톤 생성 옵션
|
79
|
+
fbxImportOptions.set_editor_property('skeleton', None) # 새 스켈레톤 생성
|
80
|
+
|
81
|
+
return fbxImportOptions
|
82
|
+
|
83
|
+
def set_options_for_skeletal_mesh_import(self):
|
84
|
+
"""
|
85
|
+
스켈레탈 메쉬 임포트를 위한 옵션을 설정합니다.
|
86
|
+
|
87
|
+
애니메이션은 임포트하지 않고, 메쉬만 임포트하며, 텍스처와 매테리얼은 임포트하지 않고,
|
88
|
+
피직 애셋은 만들지 않고, 스켈레톤은 생성하지 않으며, 모프 타겟은 임포트하고,
|
89
|
+
노멀과 탄젠트를 계산하여 Geometry and Skin weights로 임포트합니다.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
unreal.FbxImportUI: 설정된 임포트 옵션
|
93
|
+
"""
|
94
|
+
# FBX 임포트 옵션 설정
|
95
|
+
fbxImportOptions = unreal.FbxImportUI()
|
96
|
+
fbxImportOptions.reset_to_default()
|
97
|
+
fbxImportOptions.set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_SKELETAL_MESH) # 스켈레탈 메쉬 타입
|
98
|
+
|
99
|
+
# 메시 임포트 옵션 설정
|
100
|
+
fbxImportOptions.set_editor_property('import_mesh', True) # 메쉬 임포트
|
101
|
+
fbxImportOptions.set_editor_property('import_textures', False) # 텍스처 임포트 안함
|
102
|
+
fbxImportOptions.set_editor_property('import_materials', False) # 매테리얼 임포트 안함
|
103
|
+
fbxImportOptions.set_editor_property('import_animations', False) # 애니메이션 임포트 안함
|
104
|
+
fbxImportOptions.set_editor_property('import_as_skeletal', True) # 스켈레탈 메쉬로 임포트
|
105
|
+
fbxImportOptions.set_editor_property('create_physics_asset', False) # 피직 애셋 생성 안함
|
106
|
+
|
107
|
+
# 스켈레탈 메쉬 임포트 세부 옵션
|
108
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('import_morph_targets', True) # 모프 타겟 임포트
|
109
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('normal_import_method', unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS) # 노멀 임포트
|
110
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('normal_generation_method', unreal.FBXNormalGenerationMethod.MIKK_T_SPACE) # 탄젠트 계산
|
111
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('preserve_smoothing_groups', True) # 스무딩 그룹 보존
|
112
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('reorder_material_to_fbx_order', True) # Material 순서 재정렬
|
113
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('import_mesh_lo_ds', False) # LOD 임포트 안함
|
114
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('convert_scene_unit', False) # 씬 단위 변환 안함
|
115
|
+
fbxImportOptions.skeletal_mesh_import_data.set_editor_property('force_front_x_axis', False) # X축 강제 변환 안함
|
116
|
+
|
117
|
+
return fbxImportOptions
|
118
|
+
|
119
|
+
def set_options_for_animation_import(self):
|
120
|
+
"""
|
121
|
+
애니메이션 임포트를 위한 옵션을 설정합니다.
|
122
|
+
|
123
|
+
애니메이션은 임포트하고, 메쉬는 임포트하지 않으며, 텍스처와 매테리얼은 임포트하지 않고,
|
124
|
+
피직 애셋은 만들지 않고, 스켈레톤은 생성하지 않으며, Animation Length는 Source와 같게 설정합니다.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
unreal.FbxImportUI: 설정된 임포트 옵션
|
128
|
+
"""
|
129
|
+
# FBX 임포트 옵션 설정
|
130
|
+
fbxImportOptions = unreal.FbxImportUI()
|
131
|
+
fbxImportOptions.reset_to_default()
|
132
|
+
fbxImportOptions.set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_ANIMATION) # 애니메이션 타입
|
133
|
+
|
134
|
+
# 메시 임포트 옵션 설정
|
135
|
+
fbxImportOptions.set_editor_property('import_animations', True) # 애니메이션 임포트
|
136
|
+
fbxImportOptions.set_editor_property('import_mesh', False) # 메쉬 임포트 안함
|
137
|
+
fbxImportOptions.set_editor_property('import_textures', False) # 텍스처 임포트 안함
|
138
|
+
fbxImportOptions.set_editor_property('import_materials', False) # 매테리얼 임포트 안함
|
139
|
+
|
140
|
+
fbxImportOptions.anim_sequence_import_data.set_editor_property('animation_length', unreal.FBXAnimationLengthImportType.FBXALIT_EXPORTED_TIME)
|
141
|
+
fbxImportOptions.anim_sequence_import_data.set_editor_property('do_not_import_curve_with_zero', True)
|
142
|
+
fbxImportOptions.anim_sequence_import_data.set_editor_property('import_bone_tracks', True)
|
143
|
+
fbxImportOptions.anim_sequence_import_data.set_editor_property('import_custom_attribute', True)
|
144
|
+
fbxImportOptions.anim_sequence_import_data.set_editor_property('import_meshes_in_bone_hierarchy', True)
|
145
|
+
|
146
|
+
return fbxImportOptions
|
147
|
+
|
148
|
+
def load_options(self, inPresetName: Optional[str] = None) -> unreal.FbxImportUI:
|
149
|
+
"""
|
150
|
+
PresetName에 따라 적절한 임포트 옵션을 로드합니다.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
inPresetName (Optional[str]): 프리셋 이름. None인 경우 self.presetName 사용
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
unreal.FbxImportUI: 설정된 임포트 옵션
|
157
|
+
|
158
|
+
Raises:
|
159
|
+
ValueError: 지원하지 않는 프리셋 이름인 경우
|
160
|
+
"""
|
161
|
+
if inPresetName is None:
|
162
|
+
inPresetName = self.presetName
|
163
|
+
|
164
|
+
if inPresetName is None:
|
165
|
+
raise ValueError("Preset name is required")
|
166
|
+
|
167
|
+
# PresetName에 따라 적절한 메소드 호출
|
168
|
+
if inPresetName.lower() == "skeleton":
|
169
|
+
return self.set_options_for_skeleton_import()
|
170
|
+
elif inPresetName.lower() == "skeletalmesh":
|
171
|
+
return self.set_options_for_skeletal_mesh_import()
|
172
|
+
elif inPresetName.lower() == "animation":
|
173
|
+
return self.set_options_for_animation_import()
|
174
|
+
else:
|
175
|
+
ue5_logger.error(f"Unsupported preset name: {inPresetName}. Supported presets: Skeleton, SkeletalMesh, Animation")
|
176
|
+
raise None
|
177
|
+
|
178
|
+
|
179
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
UE5 스켈레탈 메쉬 임포터 모듈
|
6
|
+
UE5에서 스켈레탈 메쉬를 임포트하는 기능을 제공합니다.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import unreal
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Optional, Dict, Any
|
12
|
+
|
13
|
+
from ..logger import ue5_logger
|
14
|
+
from .importerSettings import ImporterSettings
|
15
|
+
|
16
|
+
# UE5 모듈 import
|
17
|
+
from .baseImporter import BaseImporter
|
18
|
+
|
19
|
+
class SkeletalMeshImporter(BaseImporter):
|
20
|
+
def __init__(self, inContentRootPrefix: str, inFbxRootPrefix: str):
|
21
|
+
super().__init__(inContentRootPrefix, inFbxRootPrefix, "SkeletalMesh")
|
22
|
+
ue5_logger.info("SkeletalMeshImporter 초기화 완료")
|
23
|
+
|
24
|
+
@property
|
25
|
+
def asset_type(self) -> str:
|
26
|
+
return "SkeletalMesh"
|
27
|
+
|
28
|
+
def create_import_task(self, inFbxFile: str, inDestinationPath: str, inFbxSkeletonPath: str):
|
29
|
+
"""스켈레탈 메시 임포트를 위한 태스크 생성 - 스켈레톤 필수 지정"""
|
30
|
+
ue5_logger.debug(f"스켈레탈 메시 임포트 태스크 생성 시작: {inFbxFile}")
|
31
|
+
|
32
|
+
importOptions = self.importerSettings.load_options()
|
33
|
+
ue5_logger.debug("스켈레탈 메시 임포트 옵션 로드 완료")
|
34
|
+
|
35
|
+
# 스켈레톤 필수 설정
|
36
|
+
if inFbxSkeletonPath is None:
|
37
|
+
error_msg = "스켈레탈 메시 임포트에는 스켈레톤이 필수입니다"
|
38
|
+
ue5_logger.error(error_msg)
|
39
|
+
raise ValueError(error_msg)
|
40
|
+
|
41
|
+
skeletonPath = self.convert_fbx_path_to_skeleton_path(inFbxSkeletonPath)
|
42
|
+
skeletonAssetData = unreal.EditorAssetLibrary.find_asset_data(skeletonPath)
|
43
|
+
if not skeletonAssetData.is_valid():
|
44
|
+
error_msg = f"스켈레톤 에셋을 찾을 수 없음: {skeletonPath}"
|
45
|
+
ue5_logger.error(error_msg)
|
46
|
+
raise ValueError(error_msg)
|
47
|
+
|
48
|
+
skeletalSkeleton = skeletonAssetData.get_asset()
|
49
|
+
importOptions.set_editor_property('skeleton', skeletalSkeleton)
|
50
|
+
ue5_logger.debug(f"스켈레톤 설정됨: {skeletalSkeleton.get_name()}")
|
51
|
+
|
52
|
+
# 에셋 이름 결정: FBX 파일 이름에서 확장자 제거
|
53
|
+
assetName = Path(inFbxFile).stem
|
54
|
+
|
55
|
+
task = unreal.AssetImportTask()
|
56
|
+
task.automated = True
|
57
|
+
task.destination_path = inDestinationPath
|
58
|
+
task.filename = inFbxFile
|
59
|
+
task.destination_name = assetName
|
60
|
+
task.replace_existing = True
|
61
|
+
task.save = True
|
62
|
+
task.options = importOptions
|
63
|
+
|
64
|
+
ue5_logger.debug(f"스켈레탈 메시 임포트 태스크 생성 완료: Destination={inDestinationPath}, AssetName={assetName}")
|
65
|
+
return task
|
66
|
+
|
67
|
+
def import_skeletal_mesh(self, inFbxFile: str, inFbxSkeletonPath: str, inAssetName: str = None, inDescription: str = None):
|
68
|
+
ue5_logger.info(f"스켈레탈 메시 임포트 시작: {inFbxFile}")
|
69
|
+
|
70
|
+
destinationPath, assetName = self._prepare_import_paths(inFbxFile, inAssetName)
|
71
|
+
assetFullPath = f"{destinationPath}/{assetName}"
|
72
|
+
|
73
|
+
# 기존 에셋이 있는 경우 소스 컨트롤에서 체크아웃
|
74
|
+
if unreal.Paths.file_exists(assetFullPath):
|
75
|
+
unreal.SourceControl.check_out_or_add_file(assetFullPath, silent=True)
|
76
|
+
|
77
|
+
task = self._create_import_task(inFbxFile, destinationPath, inFbxSkeletonPath)
|
78
|
+
|
79
|
+
ue5_logger.info(f"스켈레탈 메시 임포트 실행: {inFbxFile} -> {destinationPath}/{assetName}")
|
80
|
+
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
81
|
+
|
82
|
+
result = task.get_objects()
|
83
|
+
if len(result) == 0:
|
84
|
+
error_msg = f"스켈레탈 메시 임포트 실패: {inFbxFile}"
|
85
|
+
ue5_logger.error(error_msg)
|
86
|
+
raise ValueError(error_msg)
|
87
|
+
|
88
|
+
# 임포트된 스켈레탈 메시 에셋의 시스템 경로 가져오기
|
89
|
+
importedSkeletalMesh = None
|
90
|
+
for asset in result:
|
91
|
+
if isinstance(asset, unreal.SkeletalMesh):
|
92
|
+
importedSkeletalMesh = asset
|
93
|
+
break
|
94
|
+
|
95
|
+
if importedSkeletalMesh is None:
|
96
|
+
error_msg = f"스켈레탈 메시 에셋을 찾을 수 없음: {inFbxFile}"
|
97
|
+
ue5_logger.error(error_msg)
|
98
|
+
raise ValueError(error_msg)
|
99
|
+
|
100
|
+
importedObjectPaths = self.get_dirty_deps(assetFullPath)
|
101
|
+
|
102
|
+
skeletalMeshSystemFullPath = unreal.SystemLibrary.get_system_path(importedSkeletalMesh)
|
103
|
+
importedObjectPaths.append(skeletalMeshSystemFullPath)
|
104
|
+
|
105
|
+
checkInDescription = f"SkeletalMesh Imported by {inFbxFile} to {assetFullPath}"
|
106
|
+
if inDescription is not None:
|
107
|
+
checkInDescription = inDescription
|
108
|
+
|
109
|
+
unreal.SourceControl.check_in_files(importedObjectPaths, checkInDescription, silent=True)
|
110
|
+
|
111
|
+
ue5_logger.info(f"스켈레탈 메시 임포트 성공: {inFbxFile} -> {len(result)}개 객체 생성")
|
112
|
+
return self._create_result_dict(inFbxFile, destinationPath, assetName, True)
|
@@ -0,0 +1,105 @@
|
|
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 SkeletonImporter(BaseImporter):
|
21
|
+
def __init__(self, inContentRootPrefix: str, inFbxRootPrefix: str):
|
22
|
+
super().__init__(inContentRootPrefix, inFbxRootPrefix, "Skeleton")
|
23
|
+
ue5_logger.info("SkeletonImporter 초기화 완료")
|
24
|
+
|
25
|
+
@property
|
26
|
+
def asset_type(self) -> str:
|
27
|
+
return "Skeleton"
|
28
|
+
|
29
|
+
def create_import_task(self, inFbxFile: str, inDestinationPath: str):
|
30
|
+
"""스켈레톤 임포트를 위한 태스크 생성 - 새 스켈레톤 생성"""
|
31
|
+
ue5_logger.debug(f"스켈레톤 임포트 태스크 생성 시작: {inFbxFile}")
|
32
|
+
|
33
|
+
importOptions = self.importerSettings.load_options()
|
34
|
+
ue5_logger.debug("스켈레톤 임포트 옵션 로드 완료")
|
35
|
+
|
36
|
+
# 에셋 이름 결정: FBX 파일 이름에서 확장자 제거
|
37
|
+
assetName = Path(inFbxFile).stem
|
38
|
+
|
39
|
+
task = unreal.AssetImportTask()
|
40
|
+
task.automated = True
|
41
|
+
task.destination_path = inDestinationPath
|
42
|
+
task.filename = inFbxFile
|
43
|
+
task.destination_name = assetName
|
44
|
+
task.replace_existing = True
|
45
|
+
task.save = True
|
46
|
+
task.options = importOptions
|
47
|
+
|
48
|
+
ue5_logger.debug(f"스켈레톤 임포트 태스크 생성 완료: Destination={inDestinationPath}, AssetName={assetName}")
|
49
|
+
return task
|
50
|
+
|
51
|
+
def import_skeleton(self, inFbxFile: str, inAssetName: str = None, inDescription: str = None):
|
52
|
+
ue5_logger.info(f"스켈레톤 임포트 시작: {inFbxFile}")
|
53
|
+
|
54
|
+
destinationPath, assetName = self._prepare_import_paths(inFbxFile, inAssetName)
|
55
|
+
skeletonName = self.naming.replace_name_part("AssetType", assetName, self.naming.get_name_part("AssetType").get_value_by_description("Skeleton"))
|
56
|
+
|
57
|
+
assetFullPath = f"{destinationPath}/{assetName}"
|
58
|
+
skeletonFullPath = f"{destinationPath}/{skeletonName}"
|
59
|
+
|
60
|
+
if unreal.Paths.file_exists(assetFullPath) or unreal.Paths.file_exists(skeletonFullPath):
|
61
|
+
if unreal.Paths.file_exists(assetFullPath):
|
62
|
+
unreal.SourceControl.check_out_or_add_file(assetFullPath, silent=True)
|
63
|
+
if unreal.Paths.file_exists(skeletonFullPath):
|
64
|
+
unreal.SourceControl.check_out_or_add_file(skeletonFullPath, silent=True)
|
65
|
+
|
66
|
+
task = self._create_import_task(inFbxFile, destinationPath)
|
67
|
+
|
68
|
+
ue5_logger.info(f"스켈레톤 임포트 실행: {inFbxFile} -> {destinationPath}/{assetName}")
|
69
|
+
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
70
|
+
|
71
|
+
result = task.get_objects()
|
72
|
+
if len(result) == 0:
|
73
|
+
error_msg = f"스켈레톤 임포트 실패: {inFbxFile}"
|
74
|
+
ue5_logger.error(error_msg)
|
75
|
+
raise ValueError(error_msg)
|
76
|
+
|
77
|
+
importedSkeletalMesh = None
|
78
|
+
for asset in result:
|
79
|
+
if isinstance(asset, unreal.SkeletalMesh):
|
80
|
+
importedSkeletalMesh = asset
|
81
|
+
importedSkeleton = importedSkeletalMesh.skeleton
|
82
|
+
skeletonRenameData = unreal.AssetRenameData(importedSkeleton, destinationPath, skeletonName)
|
83
|
+
unreal.AssetToolsHelpers.get_asset_tools().rename_assets([skeletonRenameData])
|
84
|
+
|
85
|
+
skeletalMeshSystemFullPath = unreal.SystemLibrary.get_system_path(importedSkeletalMesh)
|
86
|
+
skeletonSystemFullPath = unreal.SystemLibrary.get_system_path(importedSkeletalMesh.skeleton)
|
87
|
+
|
88
|
+
importedObjectPaths = self.get_dirty_deps(skeletonSystemFullPath)
|
89
|
+
importedObjectPaths.append(skeletonSystemFullPath)
|
90
|
+
|
91
|
+
checkInDescription = f"Skeleton Imported by {inFbxFile} to {assetFullPath}"
|
92
|
+
if inDescription is not None:
|
93
|
+
checkInDescription = inDescription
|
94
|
+
|
95
|
+
unreal.SourceControl.check_in_files(importedObjectPaths, checkInDescription, silent=True)
|
96
|
+
|
97
|
+
ue5_logger.info(f"스켈레톤 임포트 성공: {inFbxFile} -> {len(result)}개 객체 생성")
|
98
|
+
return self._create_result_dict(inFbxFile, destinationPath, skeletonName, True)
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
|