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.
Files changed (46) hide show
  1. pyjallib/__init__.py +4 -1
  2. pyjallib/exceptions.py +75 -0
  3. pyjallib/logger.py +288 -0
  4. pyjallib/max/__init__.py +8 -0
  5. pyjallib/max/autoClavicle.py +17 -5
  6. pyjallib/max/bip.py +0 -21
  7. pyjallib/max/bone.py +21 -1
  8. pyjallib/max/boneChain.py +2 -0
  9. pyjallib/max/constraint.py +27 -2
  10. pyjallib/max/elbow.py +105 -0
  11. pyjallib/max/groinBone.py +2 -0
  12. pyjallib/max/header.py +121 -113
  13. pyjallib/max/hip.py +2 -0
  14. pyjallib/max/inguinal.py +117 -0
  15. pyjallib/max/kneeBone.py +2 -0
  16. pyjallib/max/macro/jal_macro_bone.py +221 -8
  17. pyjallib/max/macro/jal_macro_constraint.py +30 -0
  18. pyjallib/max/mirror.py +20 -13
  19. pyjallib/max/shoulder.py +173 -0
  20. pyjallib/max/twistBone.py +22 -19
  21. pyjallib/max/volumeBone.py +12 -1
  22. pyjallib/max/wrist.py +113 -0
  23. pyjallib/nameToPath.py +78 -61
  24. pyjallib/naming.py +4 -1
  25. pyjallib/perforce.py +196 -347
  26. pyjallib/progressEvent.py +75 -0
  27. pyjallib/ue5/ConfigFiles/UE5NamingConfig.json +487 -0
  28. pyjallib/ue5/__init__.py +170 -0
  29. pyjallib/ue5/disableInterchangeFrameWork.py +82 -0
  30. pyjallib/ue5/inUnreal/__init__.py +95 -0
  31. pyjallib/ue5/inUnreal/animationImporter.py +206 -0
  32. pyjallib/ue5/inUnreal/baseImporter.py +187 -0
  33. pyjallib/ue5/inUnreal/importerSettings.py +179 -0
  34. pyjallib/ue5/inUnreal/skeletalMeshImporter.py +112 -0
  35. pyjallib/ue5/inUnreal/skeletonImporter.py +105 -0
  36. pyjallib/ue5/logger.py +241 -0
  37. pyjallib/ue5/templateProcessor.py +287 -0
  38. pyjallib/ue5/templates/__init__.py +109 -0
  39. pyjallib/ue5/templates/animImportTemplate.py +22 -0
  40. pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
  41. pyjallib/ue5/templates/skeletalMeshImportTemplate.py +22 -0
  42. pyjallib/ue5/templates/skeletonImportTemplate.py +21 -0
  43. {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/METADATA +1 -1
  44. pyjallib-0.1.19.dist-info/RECORD +72 -0
  45. pyjallib-0.1.16.dist-info/RECORD +0 -49
  46. {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/WHEEL +0 -0
pyjallib/ue5/logger.py ADDED
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ UE5 모듈 로깅 설정 모듈
6
+ 메인 Logger 클래스를 상속하여 UE5 전용 기능을 추가합니다.
7
+ """
8
+
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Optional
12
+ from datetime import datetime
13
+
14
+ from pyjallib.logger import Logger
15
+
16
+
17
+ class UE5LogHandler(logging.Handler):
18
+ """UE5 전용 로그 핸들러 - UE5의 로그 시스템과 호환되도록 설계"""
19
+
20
+ def emit(self, record):
21
+ """로그 레코드를 UE5 로그 시스템으로 전송"""
22
+ try:
23
+ # UE5의 unreal.log 함수 사용
24
+ import unreal
25
+
26
+ # 메시지 포맷팅
27
+ message = self.format(record) if self.formatter else record.getMessage()
28
+
29
+ # 로그 레벨에 따라 적절한 UE5 로그 함수 호출
30
+ if record.levelno >= logging.ERROR:
31
+ unreal.log_error(f"[PyJalLib] {message}")
32
+ elif record.levelno >= logging.WARNING:
33
+ unreal.log_warning(f"[PyJalLib] {message}")
34
+ elif record.levelno >= logging.INFO:
35
+ unreal.log(f"[PyJalLib] {message}")
36
+ else: # DEBUG
37
+ unreal.log(f"[PyJalLib-DEBUG] {message}")
38
+
39
+ except ImportError:
40
+ # unreal 모듈이 없는 경우 표준 출력 사용
41
+ message = self.format(record) if self.formatter else record.getMessage()
42
+ print(f"[PyJalLib] {message}")
43
+ except Exception:
44
+ # 모든 예외를 무시하여 로깅 실패가 애플리케이션을 중단하지 않도록 함
45
+ pass
46
+
47
+
48
+ class UE5Logger(Logger):
49
+ """UE5 전용 로거 클래스 - 메인 Logger 클래스를 상속하여 UE5 기능을 추가"""
50
+
51
+ def __init__(self, inLogPath: Optional[str] = None, inLogFileName: Optional[str] = None,
52
+ inEnableConsole: bool = True, inEnableUE5: bool = True):
53
+ """UE5 로거 인스턴스 초기화
54
+
55
+ Args:
56
+ inLogPath (str, optional): 로그 파일 저장 경로.
57
+ None인 경우 기본 경로 사용 (Documents/PyJalLib/logs)
58
+ inLogFileName (str, optional): 로그 파일명 (확장자 제외).
59
+ None인 경우 기본값 "ue5_module" 사용
60
+ 실제 파일명은 "YYYYMMDD_파일명.log" 형식으로 생성
61
+ inEnableConsole (bool): 콘솔 출력 활성화 여부 (기본값: True)
62
+ inEnableUE5 (bool): UE5 출력 활성화 여부 (기본값: True)
63
+ """
64
+ # UE5 기본 파일명 설정
65
+ if inLogFileName is None:
66
+ inLogFileName = "ue5_module"
67
+
68
+ # 부모 클래스 초기화
69
+ super().__init__(inLogPath, inLogFileName, inEnableConsole)
70
+
71
+ # UE5 핸들러 설정
72
+ self._enableUE5 = inEnableUE5
73
+ if self._enableUE5:
74
+ self._add_ue5_handler()
75
+
76
+ def set_ue5_log_level(self, inLevel: str):
77
+ """
78
+ UE5 출력의 로깅 레벨을 설정합니다.
79
+
80
+ Args:
81
+ inLevel (str): 로깅 레벨 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
82
+ """
83
+ level_map = {
84
+ 'DEBUG': logging.DEBUG,
85
+ 'INFO': logging.INFO,
86
+ 'WARNING': logging.WARNING,
87
+ 'ERROR': logging.ERROR,
88
+ 'CRITICAL': logging.CRITICAL
89
+ }
90
+
91
+ if inLevel.upper() not in level_map:
92
+ self.warning(f"잘못된 로깅 레벨: {inLevel}. 기본값 INFO로 설정합니다.")
93
+ inLevel = 'INFO'
94
+
95
+ # UE5 핸들러 찾기
96
+ for handler in self._logger.handlers:
97
+ if isinstance(handler, UE5LogHandler):
98
+ handler.setLevel(level_map[inLevel.upper()])
99
+ self.info(f"UE5 로깅 레벨이 {inLevel.upper()}로 설정되었습니다.")
100
+ return
101
+
102
+ self.warning("UE5 핸들러를 찾을 수 없습니다.")
103
+
104
+ def enable_ue5_output(self, inEnable: bool = True):
105
+ """
106
+ UE5 출력을 활성화/비활성화합니다.
107
+
108
+ Args:
109
+ inEnable (bool): UE5 출력 활성화 여부
110
+ """
111
+ if inEnable and not self._enableUE5:
112
+ # UE5 핸들러 추가
113
+ self._add_ue5_handler()
114
+ self._enableUE5 = True
115
+ self.info("UE5 출력이 활성화되었습니다.")
116
+ elif not inEnable and self._enableUE5:
117
+ # UE5 핸들러 제거
118
+ self._remove_ue5_handler()
119
+ self._enableUE5 = False
120
+ self.info("UE5 출력이 비활성화되었습니다.")
121
+
122
+ def _add_ue5_handler(self):
123
+ """UE5 핸들러를 로거에 추가"""
124
+ try:
125
+ ue5_handler = UE5LogHandler()
126
+ ue5_handler.setLevel(logging.INFO) # UE5에서는 INFO 이상만 표시
127
+ ue5_handler.setFormatter(self._get_formatter())
128
+ self._logger.addHandler(ue5_handler)
129
+ except Exception:
130
+ # UE5 핸들러 생성 실패 시 무시
131
+ pass
132
+
133
+ def _remove_ue5_handler(self):
134
+ """UE5 핸들러를 로거에서 제거"""
135
+ for handler in self._logger.handlers[:]:
136
+ if isinstance(handler, UE5LogHandler):
137
+ self._logger.removeHandler(handler)
138
+ try:
139
+ handler.close()
140
+ except Exception:
141
+ pass
142
+
143
+ def _log_separator(self, inMessage: str) -> None:
144
+ """구분선 메시지를 모든 핸들러에 직접 출력 (UE5 핸들러 포함)"""
145
+ # 구분선은 INFO 레벨로 출력하되, 특별한 포맷 사용
146
+ separator_record = logging.LogRecord(
147
+ name=self._logger.name,
148
+ level=logging.INFO,
149
+ pathname="",
150
+ lineno=0,
151
+ msg=inMessage,
152
+ args=(),
153
+ exc_info=None
154
+ )
155
+
156
+ # 각 핸들러에 직접 전송 (포맷터 우회)
157
+ for handler in self._logger.handlers:
158
+ try:
159
+ # 핸들러 레벨 확인
160
+ if handler.level <= logging.INFO:
161
+ if isinstance(handler, UE5LogHandler):
162
+ # UE5 핸들러의 경우 직접 emit 호출
163
+ handler.emit(separator_record)
164
+ elif hasattr(handler, 'stream'):
165
+ # 구분선만 특별한 포맷으로 출력
166
+ handler.stream.write(inMessage + "\n")
167
+ if hasattr(handler, 'flush'):
168
+ handler.flush()
169
+ except Exception:
170
+ # 핸들러 오류 시 무시
171
+ pass
172
+
173
+
174
+ # 편의를 위한 전역 UE5 로거 인스턴스
175
+ ue5_logger = UE5Logger()
176
+
177
+ # 호환성을 위한 기존 함수들
178
+ def set_log_level(inLevel: str):
179
+ """
180
+ UE5 모듈의 로깅 레벨을 설정합니다.
181
+
182
+ Args:
183
+ inLevel (str): 로깅 레벨 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
184
+ """
185
+ level_map = {
186
+ 'DEBUG': logging.DEBUG,
187
+ 'INFO': logging.INFO,
188
+ 'WARNING': logging.WARNING,
189
+ 'ERROR': logging.ERROR,
190
+ 'CRITICAL': logging.CRITICAL
191
+ }
192
+
193
+ if inLevel.upper() not in level_map:
194
+ ue5_logger.warning(f"잘못된 로깅 레벨: {inLevel}. 기본값 INFO로 설정합니다.")
195
+ inLevel = 'INFO'
196
+
197
+ ue5_logger._logger.setLevel(level_map[inLevel.upper()])
198
+ ue5_logger.info(f"로깅 레벨이 {inLevel.upper()}로 설정되었습니다.")
199
+
200
+ def set_ue5_log_level(inLevel: str):
201
+ """
202
+ UE5 출력의 로깅 레벨을 설정합니다.
203
+
204
+ Args:
205
+ inLevel (str): 로깅 레벨 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
206
+ """
207
+ ue5_logger.set_ue5_log_level(inLevel)
208
+
209
+ def get_log_file_path():
210
+ """
211
+ 현재 로그 파일의 경로를 반환합니다.
212
+
213
+ Returns:
214
+ str: 로그 파일의 절대 경로
215
+ """
216
+ documents_path = Path.home() / "Documents"
217
+ log_folder = documents_path / "PyJalLib" / "logs"
218
+ current_date = datetime.now().strftime("%Y%m%d")
219
+ log_filename = f"{current_date}_ue5_module.log"
220
+ return str(log_folder / log_filename)
221
+
222
+ def set_log_file_path(inLogFolder: str = None, inLogFilename: str = None):
223
+ """
224
+ 로그 파일의 경로를 동적으로 변경합니다.
225
+
226
+ Args:
227
+ inLogFolder (str, optional): 로그 폴더 경로. None인 경우 기본 Documents/PyJalLib/logs 사용
228
+ inLogFilename (str, optional): 로그 파일명. None인 경우 기본 날짜 기반 파일명 사용
229
+ """
230
+ # 새로운 UE5Logger 인스턴스 생성
231
+ global ue5_logger
232
+
233
+ # 기존 로거 정리
234
+ ue5_logger.close()
235
+
236
+ # 새로운 로거 생성
237
+ ue5_logger = UE5Logger(inLogPath=inLogFolder, inLogFileName=inLogFilename)
238
+ ue5_logger.info(f"로그 파일 경로가 변경되었습니다.")
239
+
240
+ # 로깅 설정 완료 메시지
241
+ ue5_logger.info("UE5 모듈 로깅 시스템 초기화 완료")
@@ -0,0 +1,287 @@
1
+ """
2
+ 템플릿 처리를 위한 유틸리티 모듈
3
+ UE5 익스포트 시 파이썬 스크립트 템플릿을 실제 코드로 변환하는 기능을 제공합니다.
4
+ """
5
+
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Dict, Any, Optional
9
+ from .logger import ue5_logger
10
+ from .templates import (
11
+ get_template_path,
12
+ get_all_template_paths,
13
+ get_available_templates,
14
+ ANIM_IMPORT_TEMPLATE,
15
+ SKELETON_IMPORT_TEMPLATE,
16
+ SKELETAL_MESH_IMPORT_TEMPLATE,
17
+ BATCH_ANIM_IMPORT_TEMPLATE
18
+ )
19
+
20
+
21
+ class TemplateProcessor:
22
+ """템플릿 처리를 위한 확장된 클래스"""
23
+
24
+ def __init__(self):
25
+ """TemplateProcessor 초기화"""
26
+ ue5_logger.debug("TemplateProcessor 초기화")
27
+ self._default_output_dir = Path.cwd() / "temp_scripts"
28
+
29
+ def process_template(self, inTemplatePath: str, inTemplateOutPath: str, inTemplateData: Dict[str, Any]) -> str:
30
+ """
31
+ 템플릿을 처리하여 실제 코드로 변환 (기존 메서드 유지)
32
+
33
+ Args:
34
+ inTemplatePath (str): 템플릿 파일 경로
35
+ inTemplateOutPath (str): 출력 파일 경로
36
+ inTemplateData (Dict[str, Any]): 템플릿에서 치환할 데이터
37
+
38
+ Returns:
39
+ str: 처리된 템플릿 내용
40
+
41
+ Raises:
42
+ FileNotFoundError: 템플릿 파일이 존재하지 않는 경우
43
+ PermissionError: 파일 읽기/쓰기 권한이 없는 경우
44
+ OSError: 디렉토리 생성 실패 등 파일 시스템 오류
45
+ UnicodeDecodeError: 파일 인코딩 오류
46
+ """
47
+ # 템플릿 파일 존재 확인
48
+ templatePath = Path(inTemplatePath)
49
+ if not templatePath.exists():
50
+ ue5_logger.error(f"템플릿 파일을 찾을 수 없습니다: {inTemplatePath}")
51
+ raise FileNotFoundError(f"템플릿 파일을 찾을 수 없습니다: {inTemplatePath}")
52
+
53
+ # 템플릿 파일 읽기 권한 확인
54
+ if not os.access(templatePath, os.R_OK):
55
+ ue5_logger.error(f"템플릿 파일 읽기 권한이 없습니다: {inTemplatePath}")
56
+ raise PermissionError(f"템플릿 파일 읽기 권한이 없습니다: {inTemplatePath}")
57
+
58
+ # 템플릿 파일 읽기
59
+ with open(templatePath, 'r', encoding='utf-8') as file:
60
+ templateContent = file.read()
61
+
62
+ # 템플릿 데이터로 플레이스홀더 치환
63
+ processedContent = templateContent
64
+ for key, value in inTemplateData.items():
65
+ placeholder = f'{{{key}}}'
66
+ if placeholder in processedContent:
67
+ processedContent = processedContent.replace(placeholder, str(value))
68
+
69
+ # 출력 디렉토리 생성 (존재하지 않는 경우)
70
+ outputPath = Path(inTemplateOutPath)
71
+ outputPath.parent.mkdir(parents=True, exist_ok=True)
72
+
73
+ # 출력 파일 쓰기 권한 확인 (기존 파일이 있는 경우)
74
+ if outputPath.exists() and not os.access(outputPath, os.W_OK):
75
+ ue5_logger.error(f"출력 파일 쓰기 권한이 없습니다: {inTemplateOutPath}")
76
+ raise PermissionError(f"출력 파일 쓰기 권한이 없습니다: {inTemplateOutPath}")
77
+
78
+ # 처리된 내용을 파일로 출력
79
+ with open(outputPath, 'w', encoding='utf-8') as file:
80
+ file.write(processedContent)
81
+
82
+ ue5_logger.info(f"템플릿 처리 완료: {inTemplatePath} -> {inTemplateOutPath}")
83
+ return processedContent
84
+
85
+ # === 새로운 템플릿 경로 관리 메서드 ===
86
+ def get_template_path(self, template_name: str) -> str:
87
+ """
88
+ 템플릿 파일 경로를 간단하게 가져오기
89
+
90
+ Args:
91
+ template_name (str): 'animImport', 'skeletonImport', 'skeletalMeshImport' 중 하나
92
+
93
+ Returns:
94
+ str: 템플릿 파일의 절대 경로
95
+ """
96
+ return get_template_path(template_name)
97
+
98
+ def get_all_template_paths(self) -> Dict[str, str]:
99
+ """모든 템플릿 경로를 딕셔너리로 반환"""
100
+ return get_all_template_paths()
101
+
102
+ def get_available_templates(self) -> list:
103
+ """사용 가능한 템플릿 목록 반환"""
104
+ return get_available_templates()
105
+
106
+ # === 타입별 특화 처리 메서드 ===
107
+ def process_animation_import_template(self,
108
+ inTemplateData: Dict[str, Any],
109
+ inOutputPath: Optional[str] = None) -> str:
110
+ """
111
+ 애니메이션 임포트 전용 템플릿 처리
112
+
113
+ Args:
114
+ inTemplateData (Dict[str, Any]): 템플릿 데이터
115
+ 필수 키:
116
+ - inExtPackagePath: 외부 패키지 경로
117
+ - inAnimFbxPath: 애니메이션 FBX 경로
118
+ - inSkeletonFbxPath: 스켈레톤 FBX 경로
119
+ - inContentRootPrefix: Content 루트 경로
120
+ - inFbxRootPrefix: FBX 루트 경로
121
+ inOutputPath (Optional[str]): 출력 파일 경로. None인 경우 기본 경로 사용
122
+
123
+ Returns:
124
+ str: 처리된 템플릿 내용
125
+ """
126
+ # 필수 키 검증
127
+ required_keys = ['inExtPackagePath', 'inAnimFbxPath', 'inSkeletonFbxPath',
128
+ 'inContentRootPrefix', 'inFbxRootPrefix']
129
+ if not self.validate_template_data(ANIM_IMPORT_TEMPLATE, inTemplateData, required_keys):
130
+ raise ValueError(f"애니메이션 템플릿에 필요한 키가 누락되었습니다: {required_keys}")
131
+
132
+ template_path = get_template_path(ANIM_IMPORT_TEMPLATE)
133
+
134
+ if inOutputPath is None:
135
+ inOutputPath = self.get_default_output_path(ANIM_IMPORT_TEMPLATE, "animImportScript")
136
+
137
+ return self.process_template(template_path, inOutputPath, inTemplateData)
138
+
139
+ def process_skeleton_import_template(self,
140
+ inTemplateData: Dict[str, Any],
141
+ inOutputPath: Optional[str] = None) -> str:
142
+ """
143
+ 스켈레톤 임포트 전용 템플릿 처리
144
+
145
+ Args:
146
+ inTemplateData (Dict[str, Any]): 템플릿 데이터
147
+ 필수 키:
148
+ - inExtPackagePath: 외부 패키지 경로
149
+ - inSkeletonFbxPath: 스켈레톤 FBX 경로
150
+ - inContentRootPrefix: Content 루트 경로
151
+ - inFbxRootPrefix: FBX 루트 경로
152
+ inOutputPath (Optional[str]): 출력 파일 경로. None인 경우 기본 경로 사용
153
+
154
+ Returns:
155
+ str: 처리된 템플릿 내용
156
+ """
157
+ # 필수 키 검증
158
+ required_keys = ['inExtPackagePath', 'inSkeletonFbxPath',
159
+ 'inContentRootPrefix', 'inFbxRootPrefix']
160
+ if not self.validate_template_data(SKELETON_IMPORT_TEMPLATE, inTemplateData, required_keys):
161
+ raise ValueError(f"스켈레톤 템플릿에 필요한 키가 누락되었습니다: {required_keys}")
162
+
163
+ template_path = get_template_path(SKELETON_IMPORT_TEMPLATE)
164
+
165
+ if inOutputPath is None:
166
+ inOutputPath = self.get_default_output_path(SKELETON_IMPORT_TEMPLATE, "skeletonImportScript")
167
+
168
+ return self.process_template(template_path, inOutputPath, inTemplateData)
169
+
170
+ def process_skeletal_mesh_import_template(self,
171
+ inTemplateData: Dict[str, Any],
172
+ inOutputPath: Optional[str] = None) -> str:
173
+ """
174
+ 스켈레탈 메시 임포트 전용 템플릿 처리
175
+
176
+ Args:
177
+ inTemplateData (Dict[str, Any]): 템플릿 데이터
178
+ 필수 키:
179
+ - inExtPackagePath: 외부 패키지 경로
180
+ - inSkeletalMeshFbxPath: 스켈레탈 메시 FBX 경로
181
+ - inSkeletonFbxPath: 스켈레톤 FBX 경로
182
+ - inContentRootPrefix: Content 루트 경로
183
+ - inFbxRootPrefix: FBX 루트 경로
184
+ inOutputPath (Optional[str]): 출력 파일 경로. None인 경우 기본 경로 사용
185
+
186
+ Returns:
187
+ str: 처리된 템플릿 내용
188
+ """
189
+ # 필수 키 검증
190
+ required_keys = ['inExtPackagePath', 'inSkeletalMeshFbxPath', 'inSkeletonFbxPath',
191
+ 'inContentRootPrefix', 'inFbxRootPrefix']
192
+ if not self.validate_template_data(SKELETAL_MESH_IMPORT_TEMPLATE, inTemplateData, required_keys):
193
+ raise ValueError(f"스켈레탈 메시 템플릿에 필요한 키가 누락되었습니다: {required_keys}")
194
+
195
+ template_path = get_template_path(SKELETAL_MESH_IMPORT_TEMPLATE)
196
+
197
+ if inOutputPath is None:
198
+ inOutputPath = self.get_default_output_path(SKELETAL_MESH_IMPORT_TEMPLATE, "skeletalMeshImportScript")
199
+
200
+ return self.process_template(template_path, inOutputPath, inTemplateData)
201
+
202
+ def process_batch_anim_import_template(self,
203
+ inTemplateData: Dict[str, Any],
204
+ inOutputPath: Optional[str] = None) -> str:
205
+ """
206
+ 배치 애니메이션 임포트 전용 템플릿 처리
207
+
208
+ Args:
209
+ inTemplateData (Dict[str, Any]): 템플릿 데이터
210
+ 필수 키:
211
+ - inExtPackagePath: 외부 패키지 경로
212
+ - inAnimFbxPaths: 애니메이션 FBX 경로들 (리스트)
213
+ - inSkeletonFbxPaths: 스켈레톤 FBX 경로들 (리스트)
214
+ - inContentRootPrefix: Content 루트 경로
215
+ - inFbxRootPrefix: FBX 루트 경로
216
+ inOutputPath (Optional[str]): 출력 파일 경로. None인 경우 기본 경로 사용
217
+
218
+ Returns:
219
+ str: 처리된 템플릿 내용
220
+ """
221
+ # 필수 키 검증
222
+ required_keys = ['inExtPackagePath', 'inAnimFbxPaths', 'inSkeletonFbxPaths',
223
+ 'inContentRootPrefix', 'inFbxRootPrefix']
224
+ if not self.validate_template_data(BATCH_ANIM_IMPORT_TEMPLATE, inTemplateData, required_keys):
225
+ raise ValueError(f"배치 애니메이션 템플릿에 필요한 키가 누락되었습니다: {required_keys}")
226
+
227
+ template_path = get_template_path(BATCH_ANIM_IMPORT_TEMPLATE)
228
+
229
+ if inOutputPath is None:
230
+ inOutputPath = self.get_default_output_path(BATCH_ANIM_IMPORT_TEMPLATE, "batchAnimImportScript")
231
+
232
+ return self.process_template(template_path, inOutputPath, inTemplateData)
233
+
234
+ # === 유틸리티 메서드 ===
235
+ def validate_template_data(self, template_type: str, template_data: Dict[str, Any], required_keys: list = None) -> bool:
236
+ """
237
+ 템플릿 데이터 유효성 검사
238
+
239
+ Args:
240
+ template_type (str): 템플릿 타입
241
+ template_data (Dict[str, Any]): 검사할 템플릿 데이터
242
+ required_keys (list, optional): 필수 키 목록. None인 경우 기본 검사만 수행
243
+
244
+ Returns:
245
+ bool: 유효한 데이터면 True, 그렇지 않으면 False
246
+ """
247
+ if not isinstance(template_data, dict):
248
+ ue5_logger.error(f"템플릿 데이터가 딕셔너리가 아닙니다: {type(template_data)}")
249
+ return False
250
+
251
+ if required_keys:
252
+ missing_keys = [key for key in required_keys if key not in template_data]
253
+ if missing_keys:
254
+ ue5_logger.error(f"템플릿 데이터에 필수 키가 누락되었습니다: {missing_keys}")
255
+ return False
256
+
257
+ return True
258
+
259
+ def get_default_output_path(self, template_type: str, base_name: str = None) -> str:
260
+ """
261
+ 기본 출력 경로 생성
262
+
263
+ Args:
264
+ template_type (str): 템플릿 타입
265
+ base_name (str, optional): 기본 파일명. None인 경우 템플릿 타입으로 생성
266
+
267
+ Returns:
268
+ str: 기본 출력 파일 경로
269
+ """
270
+ if base_name is None:
271
+ base_name = f"{template_type}Script"
272
+
273
+ # 기본 출력 디렉토리 생성
274
+ self._default_output_dir.mkdir(parents=True, exist_ok=True)
275
+
276
+ output_path = self._default_output_dir / f"{base_name}.py"
277
+ return str(output_path)
278
+
279
+ def set_default_output_directory(self, directory_path: str):
280
+ """
281
+ 기본 출력 디렉토리 설정
282
+
283
+ Args:
284
+ directory_path (str): 새로운 기본 출력 디렉토리 경로
285
+ """
286
+ self._default_output_dir = Path(directory_path)
287
+ ue5_logger.info(f"기본 출력 디렉토리가 변경되었습니다: {directory_path}")
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ UE5 템플릿 관리 모듈
6
+ 템플릿 파일 경로를 쉽게 가져올 수 있는 기능 제공
7
+ """
8
+
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Dict, Optional
12
+
13
+ # 템플릿 이름 상수
14
+ ANIM_IMPORT_TEMPLATE = "animImport"
15
+ SKELETON_IMPORT_TEMPLATE = "skeletonImport"
16
+ SKELETAL_MESH_IMPORT_TEMPLATE = "skeletalMeshImport"
17
+ BATCH_ANIM_IMPORT_TEMPLATE = "batchAnimImport"
18
+
19
+ # 템플릿 파일 매핑
20
+ _TEMPLATE_FILE_MAP = {
21
+ ANIM_IMPORT_TEMPLATE: "animImportTemplate.py",
22
+ SKELETON_IMPORT_TEMPLATE: "skeletonImportTemplate.py",
23
+ SKELETAL_MESH_IMPORT_TEMPLATE: "skeletalMeshImportTemplate.py",
24
+ BATCH_ANIM_IMPORT_TEMPLATE: "batchAnimImportTemplate.py"
25
+ }
26
+
27
+ def get_template_path(template_name: str) -> str:
28
+ """
29
+ 템플릿 이름으로 템플릿 파일 경로 반환
30
+
31
+ Args:
32
+ template_name (str): 'animImport', 'skeletonImport', 'skeletalMeshImport', 'batchAnimImport' 중 하나
33
+
34
+ Returns:
35
+ str: 템플릿 파일의 절대 경로
36
+
37
+ Raises:
38
+ ValueError: 지원하지 않는 템플릿 이름인 경우
39
+ FileNotFoundError: 템플릿 파일이 존재하지 않는 경우
40
+ """
41
+ if template_name not in _TEMPLATE_FILE_MAP:
42
+ supported_templates = list(_TEMPLATE_FILE_MAP.keys())
43
+ raise ValueError(f"지원하지 않는 템플릿 이름: {template_name}. 지원되는 템플릿: {supported_templates}")
44
+
45
+ template_filename = _TEMPLATE_FILE_MAP[template_name]
46
+ templates_dir = Path(__file__).parent
47
+ template_path = templates_dir / template_filename
48
+
49
+ if not template_path.exists():
50
+ raise FileNotFoundError(f"템플릿 파일을 찾을 수 없습니다: {template_path}")
51
+
52
+ return str(template_path.resolve())
53
+
54
+ def get_all_template_paths() -> Dict[str, str]:
55
+ """
56
+ 모든 템플릿 경로를 딕셔너리로 반환
57
+
58
+ Returns:
59
+ Dict[str, str]: 템플릿 이름을 키로 하고 파일 경로를 값으로 하는 딕셔너리
60
+ """
61
+ template_paths = {}
62
+ for template_name in _TEMPLATE_FILE_MAP.keys():
63
+ try:
64
+ template_paths[template_name] = get_template_path(template_name)
65
+ except (ValueError, FileNotFoundError):
66
+ # 존재하지 않는 템플릿은 건너뛰기
67
+ continue
68
+
69
+ return template_paths
70
+
71
+ def get_available_templates() -> list:
72
+ """
73
+ 사용 가능한 템플릿 목록 반환
74
+
75
+ Returns:
76
+ list: 사용 가능한 템플릿 이름 목록
77
+ """
78
+ available = []
79
+ templates_dir = Path(__file__).parent
80
+
81
+ for template_name, filename in _TEMPLATE_FILE_MAP.items():
82
+ template_path = templates_dir / filename
83
+ if template_path.exists():
84
+ available.append(template_name)
85
+
86
+ return available
87
+
88
+ def validate_template_name(template_name: str) -> bool:
89
+ """
90
+ 템플릿 이름이 유효한지 확인
91
+
92
+ Args:
93
+ template_name (str): 확인할 템플릿 이름
94
+
95
+ Returns:
96
+ bool: 유효한 템플릿 이름이면 True, 그렇지 않으면 False
97
+ """
98
+ return template_name in _TEMPLATE_FILE_MAP
99
+
100
+ __all__ = [
101
+ 'get_template_path',
102
+ 'get_all_template_paths',
103
+ 'get_available_templates',
104
+ 'validate_template_name',
105
+ 'ANIM_IMPORT_TEMPLATE',
106
+ 'SKELETON_IMPORT_TEMPLATE',
107
+ 'SKELETAL_MESH_IMPORT_TEMPLATE',
108
+ 'BATCH_ANIM_IMPORT_TEMPLATE'
109
+ ]
@@ -0,0 +1,22 @@
1
+ import sys
2
+ import os
3
+ from pathlib import Path
4
+
5
+ # 프로젝트 루트 디렉토리 추가 (PyJalLib 디렉토리)
6
+ extPackagePath = r'{inExtPackagePath}'
7
+
8
+ if extPackagePath not in sys.path:
9
+ sys.path.insert(0, extPackagePath)
10
+
11
+ import pyjallib
12
+ from pyjallib.ue5.inUnreal.animationImporter import AnimationImporter
13
+
14
+ fbxPath = r'{inAnimFbxPath}'
15
+ skeletonPath = r'{inSkeletonFbxPath}'
16
+
17
+ contentRootPrefix = r'{inContentRootPrefix}'
18
+ fbxRootPrefix = r'{inFbxRootPrefix}'
19
+
20
+ animImporter = AnimationImporter(inContentRootPrefix=contentRootPrefix, inFbxRootPrefix=fbxRootPrefix)
21
+
22
+ result = animImporter.import_animation(fbxPath, skeletonPath)
@@ -0,0 +1,21 @@
1
+ import sys
2
+ import os
3
+ from pathlib import Path
4
+
5
+ # 프로젝트 루트 디렉토리 추가 (PyJalLib 디렉토리)
6
+ extPackagePath = r'{inExtPackagePath}'
7
+
8
+ if extPackagePath not in sys.path:
9
+ sys.path.insert(0, extPackagePath)
10
+
11
+ from pyjallib.ue5.inUnreal.animationImporter import AnimationImporter
12
+
13
+ fbxPaths = {inAnimFbxPaths}
14
+ skeletonPaths = {inSkeletonFbxPaths}
15
+
16
+ contentRootPrefix = r'{inContentRootPrefix}'
17
+ fbxRootPrefix = r'{inFbxRootPrefix}'
18
+
19
+ animImporter = AnimationImporter(inContentRootPrefix=contentRootPrefix, inFbxRootPrefix=fbxRootPrefix)
20
+
21
+ result = animImporter.import_animations(fbxPaths, skeletonPaths)