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.
@@ -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
+
pyjallib/ue5/logger.py ADDED
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ UE5 모듈 로깅 설정 모듈
6
+ UE5 모듈의 중앙 집중식 로깅 시스템을 관리합니다.
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ from pathlib import Path
12
+ from datetime import datetime
13
+
14
+ class UE5LogHandler(logging.Handler):
15
+ """UE5 전용 로그 핸들러 - UE5의 로그 시스템과 호환되도록 설계"""
16
+
17
+ def emit(self, record):
18
+ """로그 레코드를 UE5 로그 시스템으로 전송"""
19
+ try:
20
+ # UE5의 unreal.log 함수 사용
21
+ import unreal
22
+
23
+ # 로그 레벨에 따라 적절한 UE5 로그 함수 호출
24
+ if record.levelno >= logging.ERROR:
25
+ unreal.log_error(f"[PyJalLib] {record.getMessage()}")
26
+ elif record.levelno >= logging.WARNING:
27
+ unreal.log_warning(f"[PyJalLib] {record.getMessage()}")
28
+ elif record.levelno >= logging.INFO:
29
+ unreal.log(f"[PyJalLib] {record.getMessage()}")
30
+ else: # DEBUG
31
+ unreal.log(f"[PyJalLib-DEBUG] {record.getMessage()}")
32
+
33
+ except ImportError:
34
+ # unreal 모듈이 없는 경우 표준 출력 사용
35
+ print(f"[PyJalLib] {record.getMessage()}")
36
+ except Exception:
37
+ # 모든 예외를 무시하여 로깅 실패가 애플리케이션을 중단하지 않도록 함
38
+ pass
39
+
40
+ def _setup_ue5_logging():
41
+ """UE5 모듈의 중앙 집중식 로깅 설정"""
42
+
43
+ # Documents 폴더 경로 가져오기
44
+ documents_path = Path.home() / "Documents"
45
+ log_folder = documents_path / "PyJalLib" / "logs"
46
+
47
+ # 로그 폴더 생성
48
+ log_folder.mkdir(parents=True, exist_ok=True)
49
+
50
+ # 로그 파일명 생성 (날짜 포함)
51
+ current_date = datetime.now().strftime("%Y%m%d")
52
+ log_filename = f"ue5_module_{current_date}.log"
53
+ log_file_path = log_folder / log_filename
54
+
55
+ # UE5 모듈 전용 로거 생성
56
+ ue5_logger = logging.getLogger('pyjallib.ue5')
57
+ ue5_logger.setLevel(logging.DEBUG)
58
+
59
+ # 기존 핸들러 제거 (중복 방지)
60
+ for handler in ue5_logger.handlers[:]:
61
+ ue5_logger.removeHandler(handler)
62
+
63
+ # 파일 핸들러 설정 (UTF-8 인코딩)
64
+ file_handler = logging.FileHandler(log_file_path, encoding='utf-8')
65
+ file_handler.setLevel(logging.DEBUG)
66
+
67
+ # UE5 전용 핸들러 설정
68
+ ue5_handler = UE5LogHandler()
69
+ ue5_handler.setLevel(logging.INFO) # UE5에서는 INFO 이상만 표시
70
+
71
+ # 포맷터 설정
72
+ formatter = logging.Formatter(
73
+ '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
74
+ datefmt='%Y-%m-%d %H:%M:%S'
75
+ )
76
+
77
+ file_handler.setFormatter(formatter)
78
+ ue5_handler.setFormatter(formatter)
79
+
80
+ # 핸들러 추가
81
+ ue5_logger.addHandler(file_handler)
82
+ ue5_logger.addHandler(ue5_handler)
83
+
84
+ # 로거가 상위 로거로 전파되지 않도록 설정
85
+ ue5_logger.propagate = False
86
+
87
+ return ue5_logger
88
+
89
+ # 로깅 설정 실행
90
+ ue5_logger = _setup_ue5_logging()
91
+
92
+ def set_log_level(inLevel: str):
93
+ """
94
+ UE5 모듈의 로깅 레벨을 설정합니다.
95
+
96
+ Args:
97
+ inLevel (str): 로깅 레벨 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
98
+ """
99
+ level_map = {
100
+ 'DEBUG': logging.DEBUG,
101
+ 'INFO': logging.INFO,
102
+ 'WARNING': logging.WARNING,
103
+ 'ERROR': logging.ERROR,
104
+ 'CRITICAL': logging.CRITICAL
105
+ }
106
+
107
+ if inLevel.upper() not in level_map:
108
+ ue5_logger.warning(f"잘못된 로깅 레벨: {inLevel}. 기본값 INFO로 설정합니다.")
109
+ inLevel = 'INFO'
110
+
111
+ ue5_logger.setLevel(level_map[inLevel.upper()])
112
+ ue5_logger.info(f"로깅 레벨이 {inLevel.upper()}로 설정되었습니다.")
113
+
114
+ def set_ue5_log_level(inLevel: str):
115
+ """
116
+ UE5 출력의 로깅 레벨을 설정합니다.
117
+
118
+ Args:
119
+ inLevel (str): 로깅 레벨 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
120
+ """
121
+ level_map = {
122
+ 'DEBUG': logging.DEBUG,
123
+ 'INFO': logging.INFO,
124
+ 'WARNING': logging.WARNING,
125
+ 'ERROR': logging.ERROR,
126
+ 'CRITICAL': logging.CRITICAL
127
+ }
128
+
129
+ if inLevel.upper() not in level_map:
130
+ ue5_logger.warning(f"잘못된 로깅 레벨: {inLevel}. 기본값 INFO로 설정합니다.")
131
+ inLevel = 'INFO'
132
+
133
+ # UE5 핸들러 찾기
134
+ for handler in ue5_logger.handlers:
135
+ if isinstance(handler, UE5LogHandler):
136
+ handler.setLevel(level_map[inLevel.upper()])
137
+ ue5_logger.info(f"UE5 로깅 레벨이 {inLevel.upper()}로 설정되었습니다.")
138
+ return
139
+
140
+ ue5_logger.warning("UE5 핸들러를 찾을 수 없습니다.")
141
+
142
+ def get_log_file_path():
143
+ """
144
+ 현재 로그 파일의 경로를 반환합니다.
145
+
146
+ Returns:
147
+ str: 로그 파일의 절대 경로
148
+ """
149
+ documents_path = Path.home() / "Documents"
150
+ log_folder = documents_path / "PyJalLib" / "logs"
151
+ current_date = datetime.now().strftime("%Y%m%d")
152
+ log_filename = f"ue5_module_{current_date}.log"
153
+ return str(log_folder / log_filename)
154
+
155
+ def set_log_file_path(inLogFolder: str = None, inLogFilename: str = None):
156
+ """
157
+ 로그 파일의 경로를 동적으로 변경합니다.
158
+
159
+ Args:
160
+ inLogFolder (str, optional): 로그 폴더 경로. None인 경우 기본 Documents/PyJalLib/logs 사용
161
+ inLogFilename (str, optional): 로그 파일명. None인 경우 기본 날짜 기반 파일명 사용
162
+ """
163
+ # 기본값 설정
164
+ if inLogFolder is None:
165
+ documents_path = Path.home() / "Documents"
166
+ inLogFolder = str(documents_path / "PyJalLib" / "logs")
167
+
168
+ if inLogFilename is None:
169
+ current_date = datetime.now().strftime("%Y%m%d")
170
+ inLogFilename = f"ue5_module_{current_date}.log"
171
+
172
+ # 경로 생성
173
+ log_folder = Path(inLogFolder)
174
+ log_folder.mkdir(parents=True, exist_ok=True)
175
+ log_file_path = log_folder / inLogFilename
176
+
177
+ # 기존 파일 핸들러 제거
178
+ for handler in ue5_logger.handlers[:]:
179
+ if isinstance(handler, logging.FileHandler):
180
+ ue5_logger.removeHandler(handler)
181
+ handler.close()
182
+
183
+ # 새로운 파일 핸들러 생성
184
+ new_file_handler = logging.FileHandler(log_file_path, encoding='utf-8')
185
+ new_file_handler.setLevel(logging.DEBUG)
186
+
187
+ # 포맷터 설정 (기존과 동일)
188
+ formatter = logging.Formatter(
189
+ '%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
190
+ datefmt='%Y-%m-%d %H:%M:%S'
191
+ )
192
+ new_file_handler.setFormatter(formatter)
193
+
194
+ # 새 핸들러 추가
195
+ ue5_logger.addHandler(new_file_handler)
196
+
197
+ ue5_logger.info(f"로그 파일 경로가 변경되었습니다: {log_file_path}")
198
+
199
+ # 로깅 설정 완료 메시지
200
+ ue5_logger.info("UE5 모듈 로깅 시스템 초기화 완료")