pyjallib 0.1.17__tar.gz → 0.1.19__tar.gz
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-0.1.17 → pyjallib-0.1.19}/PKG-INFO +1 -1
- {pyjallib-0.1.17 → pyjallib-0.1.19}/pyproject.toml +21 -21
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/__init__.py +5 -4
- pyjallib-0.1.19/src/pyjallib/exceptions.py +75 -0
- pyjallib-0.1.19/src/pyjallib/logger.py +288 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/__init__.py +8 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/autoClavicle.py +17 -5
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/bone.py +21 -1
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/boneChain.py +2 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/constraint.py +27 -2
- pyjallib-0.1.19/src/pyjallib/max/elbow.py +105 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/groinBone.py +2 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/header.py +121 -113
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/hip.py +2 -0
- pyjallib-0.1.19/src/pyjallib/max/inguinal.py +117 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/kneeBone.py +2 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/macro/jal_macro_bone.py +221 -8
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/macro/jal_macro_constraint.py +30 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/mirror.py +20 -13
- pyjallib-0.1.19/src/pyjallib/max/shoulder.py +173 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/twistBone.py +22 -19
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/volumeBone.py +12 -1
- pyjallib-0.1.19/src/pyjallib/max/wrist.py +113 -0
- pyjallib-0.1.19/src/pyjallib/nameToPath.py +130 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/perforce.py +101 -171
- pyjallib-0.1.19/src/pyjallib/ue5/inUnreal/animationImporter.py +206 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/inUnreal/baseImporter.py +10 -7
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/inUnreal/skeletalMeshImporter.py +1 -1
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/inUnreal/skeletonImporter.py +1 -1
- pyjallib-0.1.19/src/pyjallib/ue5/logger.py +241 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/templateProcessor.py +35 -2
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/templates/__init__.py +6 -3
- pyjallib-0.1.19/src/pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
- pyjallib-0.1.19/tests/example_perforce_usage.py +110 -0
- pyjallib-0.1.19/tests/perforce_exception_test.py +167 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/perforce_logging_example.py +32 -62
- {pyjallib-0.1.17 → pyjallib-0.1.19}/uv.lock +1 -1
- pyjallib-0.1.17/src/pyjallib/logger.py +0 -201
- pyjallib-0.1.17/src/pyjallib/nameToPath.py +0 -113
- pyjallib-0.1.17/src/pyjallib/ue5/inUnreal/animationImporter.py +0 -114
- pyjallib-0.1.17/src/pyjallib/ue5/logger.py +0 -200
- {pyjallib-0.1.17 → pyjallib-0.1.19}/.gitignore +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/.python-version +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/PRD/BIPPY_/353/260/224/354/235/264/355/214/250/353/223/234_/354/203/235/354/204/261_/353/266/204/354/204/235.md" +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/PRD/PyJalLib_Mocap_Module_PRD.md +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/PRD//353/241/234/352/271/205 /353/252/250/353/223/210 /354/240/234/354/236/221.md" +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/README.md +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/index.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/max.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/namePart.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/nameToPath.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/naming.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/namingConfig.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/p4module.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/perforce.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib/reloadModules.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/pyjallib.html +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/docs/search.js +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/generate_docs.ps1 +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/logs/20250702_max_fbx_export.log +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/logs/20250702_project_specific.log +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/logs/20250702_pyjallib.log +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/logs/pyjallib.log +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/ref/BIPPY.ms +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ConfigFiles/namingConfig.json +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/ConfigFiles/3DSMaxNamingConfig.json +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/ConfigFiles/Default_3DSMaxNamingConfig.json +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/align.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/anim.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/bip.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/fbxHandler.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/helper.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/layer.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/link.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/macro/jal_macro_align.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/macro/jal_macro_helper.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/macro/jal_macro_link.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/macro/jal_macro_select.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/mocap.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/morph.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/name.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/progress.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/rootMotion.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/select.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/skeleton.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/skin.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/toolManager.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/max/ui/Container.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/namePart.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/naming.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/namingConfig.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/progressEvent.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/py.typed +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/reloadModules.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/ConfigFiles/UE5NamingConfig.json +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/__init__.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/disableInterchangeFrameWork.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/inUnreal/__init__.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/inUnreal/importerSettings.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/templates/animImportTemplate.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/templates/skeletalMeshImportTemplate.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/src/pyjallib/ue5/templates/skeletonImportTemplate.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/test_debug.log +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/animImportTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/animNodeTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/autoclavicleTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/fbxExportTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/globalVarTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/moduleImportTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/p4Test.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/skelImportTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/template_processor_test.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/ueImportTest.py +0 -0
- {pyjallib-0.1.17 → pyjallib-0.1.19}/tests/volumePreserveBoneTest.py +0 -0
@@ -1,21 +1,21 @@
|
|
1
|
-
[project]
|
2
|
-
name = "pyjallib"
|
3
|
-
version = "0.1.
|
4
|
-
description = "A utility library for 3D game character development pipelines."
|
5
|
-
readme = "README.md"
|
6
|
-
authors = [
|
7
|
-
{ name = "Dongseok Kim", email = "jalnagakds@gmail.com" }
|
8
|
-
]
|
9
|
-
requires-python = ">=3.10"
|
10
|
-
dependencies = [
|
11
|
-
"p4python>=2024.2.2682690",
|
12
|
-
]
|
13
|
-
|
14
|
-
[build-system]
|
15
|
-
requires = ["hatchling"]
|
16
|
-
build-backend = "hatchling.build"
|
17
|
-
|
18
|
-
[tool.pdoc]
|
19
|
-
modules = ["pyjallib"]
|
20
|
-
output_directory = "docs"
|
21
|
-
docformat = "markdown"
|
1
|
+
[project]
|
2
|
+
name = "pyjallib"
|
3
|
+
version = "0.1.19"
|
4
|
+
description = "A utility library for 3D game character development pipelines."
|
5
|
+
readme = "README.md"
|
6
|
+
authors = [
|
7
|
+
{ name = "Dongseok Kim", email = "jalnagakds@gmail.com" }
|
8
|
+
]
|
9
|
+
requires-python = ">=3.10"
|
10
|
+
dependencies = [
|
11
|
+
"p4python>=2024.2.2682690",
|
12
|
+
]
|
13
|
+
|
14
|
+
[build-system]
|
15
|
+
requires = ["hatchling"]
|
16
|
+
build-backend = "hatchling.build"
|
17
|
+
|
18
|
+
[tool.pdoc]
|
19
|
+
modules = ["pyjallib"]
|
20
|
+
output_directory = "docs"
|
21
|
+
docformat = "markdown"
|
@@ -6,14 +6,15 @@ pyjallib Package
|
|
6
6
|
Python library for game character development pipeline.
|
7
7
|
"""
|
8
8
|
|
9
|
-
__version__ = '0.1.
|
9
|
+
__version__ = '0.1.19'
|
10
10
|
|
11
11
|
# reload_modules 함수를 패키지 레벨에서 사용 가능하게 함
|
12
|
+
from pyjallib.logger import Logger
|
13
|
+
from pyjallib.progressEvent import ProgressEvent
|
14
|
+
from pyjallib.exceptions import PyJalLibError, PerforceError, ValidationError, FileOperationError, NamingConfigError, MaxError, UE5Error
|
12
15
|
from pyjallib.namePart import NamePart, NamePartType
|
13
16
|
from pyjallib.naming import Naming
|
14
17
|
from pyjallib.namingConfig import NamingConfig
|
15
18
|
from pyjallib.nameToPath import NameToPath
|
16
19
|
from pyjallib.perforce import Perforce
|
17
|
-
from pyjallib.reloadModules import reload_modules
|
18
|
-
from pyjallib.logger import Logger
|
19
|
-
from pyjallib.progressEvent import ProgressEvent
|
20
|
+
from pyjallib.reloadModules import reload_modules
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
PyJalLib 커스텀 예외 클래스들
|
6
|
+
라이브러리에서 발생할 수 있는 다양한 예외 상황을 명확히 구분하기 위한 예외 클래스 정의
|
7
|
+
"""
|
8
|
+
|
9
|
+
import inspect
|
10
|
+
from typing import Optional
|
11
|
+
|
12
|
+
|
13
|
+
class PyJalLibError(Exception):
|
14
|
+
"""PyJalLib 기본 예외 클래스"""
|
15
|
+
|
16
|
+
def __init__(self, inMessage: str, inFunctionName: Optional[str] = None):
|
17
|
+
"""
|
18
|
+
Args:
|
19
|
+
inMessage (str): 에러 메시지
|
20
|
+
inFunctionName (str, optional): 에러가 발생한 함수명. None인 경우 자동으로 감지 시도
|
21
|
+
"""
|
22
|
+
self.function_name = inFunctionName or self._get_caller_function_name()
|
23
|
+
super().__init__(inMessage)
|
24
|
+
|
25
|
+
def _get_caller_function_name(self) -> Optional[str]:
|
26
|
+
"""호출자 함수명을 자동으로 감지"""
|
27
|
+
try:
|
28
|
+
# 현재 스택에서 호출자 함수명 찾기
|
29
|
+
frame = inspect.currentframe()
|
30
|
+
if frame and frame.f_back and frame.f_back.f_back:
|
31
|
+
return frame.f_back.f_back.f_code.co_name
|
32
|
+
except Exception:
|
33
|
+
pass
|
34
|
+
return None
|
35
|
+
|
36
|
+
def get_function_name(self) -> Optional[str]:
|
37
|
+
"""에러가 발생한 함수명 반환"""
|
38
|
+
return self.function_name
|
39
|
+
|
40
|
+
def __str__(self) -> str:
|
41
|
+
"""에러 메시지 반환 (함수명 포함)"""
|
42
|
+
message = super().__str__()
|
43
|
+
if self.function_name:
|
44
|
+
return f"[{self.function_name}] {message}"
|
45
|
+
return message
|
46
|
+
|
47
|
+
|
48
|
+
class PerforceError(PyJalLibError):
|
49
|
+
"""Perforce 관련 예외"""
|
50
|
+
pass
|
51
|
+
|
52
|
+
|
53
|
+
class ValidationError(PyJalLibError):
|
54
|
+
"""입력값 검증 실패 예외"""
|
55
|
+
pass
|
56
|
+
|
57
|
+
|
58
|
+
class FileOperationError(PyJalLibError):
|
59
|
+
"""파일 작업 실패 예외"""
|
60
|
+
pass
|
61
|
+
|
62
|
+
|
63
|
+
class NamingConfigError(PyJalLibError):
|
64
|
+
"""NamingConfig 관련 예외"""
|
65
|
+
pass
|
66
|
+
|
67
|
+
|
68
|
+
class MaxError(PyJalLibError):
|
69
|
+
"""3ds Max 관련 예외"""
|
70
|
+
pass
|
71
|
+
|
72
|
+
|
73
|
+
class UE5Error(PyJalLibError):
|
74
|
+
"""UE5 관련 예외"""
|
75
|
+
pass
|
@@ -0,0 +1,288 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
PyJalLib 중앙 집중식 로깅 모듈
|
6
|
+
모든 PyJalLib 모듈에서 사용할 수 있는 통합 로깅 시스템을 제공합니다.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import logging
|
10
|
+
import inspect
|
11
|
+
import sys
|
12
|
+
from pathlib import Path
|
13
|
+
from typing import Optional
|
14
|
+
from datetime import datetime
|
15
|
+
|
16
|
+
from pyjallib.exceptions import PyJalLibError
|
17
|
+
|
18
|
+
|
19
|
+
class Logger:
|
20
|
+
"""PyJalLib 간단한 로깅 클래스"""
|
21
|
+
|
22
|
+
def __init__(self, inLogPath: Optional[str] = None, inLogFileName: Optional[str] = None,
|
23
|
+
inEnableConsole: bool = True, inLogLevel: str = "debug"):
|
24
|
+
"""로거 인스턴스 초기화
|
25
|
+
|
26
|
+
Args:
|
27
|
+
inLogPath (str, optional): 로그 파일 저장 경로.
|
28
|
+
None인 경우 기본 경로 사용 (Documents/PyJalLib/logs)
|
29
|
+
inLogFileName (str, optional): 로그 파일명 (확장자 제외).
|
30
|
+
None인 경우 기본값 "pyjallib" 사용
|
31
|
+
실제 파일명은 "YYYYMMDD_파일명.log" 형식으로 생성
|
32
|
+
inEnableConsole (bool): 콘솔 출력 활성화 여부 (기본값: True)
|
33
|
+
inLogLevel (str): 로깅 레벨 (debug, info, warning, error, critical). 기본값: "debug"
|
34
|
+
"""
|
35
|
+
# 기본 로그 경로 설정
|
36
|
+
if inLogPath is None:
|
37
|
+
documents_path = Path.home() / "Documents"
|
38
|
+
self._logPath = documents_path / "PyJalLib" / "logs"
|
39
|
+
else:
|
40
|
+
self._logPath = Path(inLogPath)
|
41
|
+
|
42
|
+
# 로그 디렉토리 생성
|
43
|
+
self._logPath.mkdir(parents=True, exist_ok=True)
|
44
|
+
|
45
|
+
# 로그 파일명 설정 (확장자 제외)
|
46
|
+
self._logFileName = inLogFileName if inLogFileName is not None else "pyjallib"
|
47
|
+
|
48
|
+
# 출력 옵션 설정
|
49
|
+
self._enableConsole = inEnableConsole
|
50
|
+
self._sessionName = None # 초기에는 세션 없음
|
51
|
+
|
52
|
+
# 로깅 레벨 설정
|
53
|
+
self._logLevel = self._parse_log_level(inLogLevel)
|
54
|
+
|
55
|
+
# 로거 생성 및 설정
|
56
|
+
self._logger = logging.getLogger(f"pyjallib_{id(self)}")
|
57
|
+
self._logger.setLevel(self._logLevel)
|
58
|
+
self._logger.handlers.clear() # 기존 핸들러 제거
|
59
|
+
self._setup_handlers()
|
60
|
+
|
61
|
+
def _parse_log_level(self, inLogLevel: str) -> int:
|
62
|
+
"""문자열 로깅 레벨을 logging 상수로 변환
|
63
|
+
|
64
|
+
Args:
|
65
|
+
inLogLevel (str): 로깅 레벨 문자열
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
int: logging 모듈의 레벨 상수
|
69
|
+
"""
|
70
|
+
level_map = {
|
71
|
+
"debug": logging.DEBUG,
|
72
|
+
"info": logging.INFO,
|
73
|
+
"warning": logging.WARNING,
|
74
|
+
"error": logging.ERROR,
|
75
|
+
"critical": logging.CRITICAL
|
76
|
+
}
|
77
|
+
|
78
|
+
level_str = inLogLevel.lower().strip()
|
79
|
+
if level_str in level_map:
|
80
|
+
return level_map[level_str]
|
81
|
+
else:
|
82
|
+
# 잘못된 레벨이 입력된 경우 기본값으로 DEBUG 사용
|
83
|
+
return logging.DEBUG
|
84
|
+
|
85
|
+
def _generate_auto_message(self, inLevel: str) -> str:
|
86
|
+
"""자동 메시지 생성
|
87
|
+
|
88
|
+
Args:
|
89
|
+
inLevel (str): 로깅 레벨 문자열
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
str: 자동 생성된 메시지
|
93
|
+
"""
|
94
|
+
# 호출자 함수명 감지 (2단계 위: _generate_auto_message <- debug/info/etc <- 실제 호출자)
|
95
|
+
try:
|
96
|
+
caller_frame = inspect.currentframe().f_back.f_back
|
97
|
+
function_name = caller_frame.f_code.co_name
|
98
|
+
filename = caller_frame.f_code.co_filename
|
99
|
+
line_number = caller_frame.f_lineno
|
100
|
+
|
101
|
+
# 파일명에서 경로 제거하고 확장자만 유지
|
102
|
+
file_basename = Path(filename).name
|
103
|
+
except (AttributeError, TypeError):
|
104
|
+
function_name = "unknown"
|
105
|
+
file_basename = "unknown"
|
106
|
+
line_number = 0
|
107
|
+
|
108
|
+
# 현재 예외 정보 확인
|
109
|
+
exc_type, exc_value, exc_tb = sys.exc_info()
|
110
|
+
|
111
|
+
if exc_value:
|
112
|
+
# 예외가 활성화된 상태라면 예외 정보 포함
|
113
|
+
return f"[{file_basename}:{line_number}:{function_name}] {exc_type.__name__}: {exc_value}"
|
114
|
+
else:
|
115
|
+
# 예외가 없다면 기본 메시지
|
116
|
+
return f"[{file_basename}:{line_number}:{function_name}] {inLevel.upper()} 로그"
|
117
|
+
|
118
|
+
def debug(self, inMessage: Optional[str] = None) -> None:
|
119
|
+
"""디버그 레벨 로그 메시지
|
120
|
+
|
121
|
+
Args:
|
122
|
+
inMessage (str, optional): 로그 메시지. None인 경우 자동 생성
|
123
|
+
"""
|
124
|
+
if inMessage is None:
|
125
|
+
inMessage = self._generate_auto_message("debug")
|
126
|
+
self._logger.debug(inMessage)
|
127
|
+
|
128
|
+
def info(self, inMessage: Optional[str] = None) -> None:
|
129
|
+
"""정보 레벨 로그 메시지
|
130
|
+
|
131
|
+
Args:
|
132
|
+
inMessage (str, optional): 로그 메시지. None인 경우 자동 생성
|
133
|
+
"""
|
134
|
+
if inMessage is None:
|
135
|
+
inMessage = self._generate_auto_message("info")
|
136
|
+
self._logger.info(inMessage)
|
137
|
+
|
138
|
+
def warning(self, inMessage: Optional[str] = None) -> None:
|
139
|
+
"""경고 레벨 로그 메시지
|
140
|
+
|
141
|
+
Args:
|
142
|
+
inMessage (str, optional): 로그 메시지. None인 경우 자동 생성
|
143
|
+
"""
|
144
|
+
if inMessage is None:
|
145
|
+
inMessage = self._generate_auto_message("warning")
|
146
|
+
self._logger.warning(inMessage)
|
147
|
+
|
148
|
+
def error(self, inMessage: Optional[str] = None) -> None:
|
149
|
+
"""에러 레벨 로그 메시지
|
150
|
+
|
151
|
+
Args:
|
152
|
+
inMessage (str, optional): 로그 메시지. None인 경우 자동 생성
|
153
|
+
"""
|
154
|
+
if inMessage is None:
|
155
|
+
inMessage = self._generate_auto_message("error")
|
156
|
+
self._logger.error(inMessage)
|
157
|
+
|
158
|
+
def critical(self, inMessage: Optional[str] = None) -> None:
|
159
|
+
"""치명적 에러 레벨 로그 메시지
|
160
|
+
|
161
|
+
Args:
|
162
|
+
inMessage (str, optional): 로그 메시지. None인 경우 자동 생성
|
163
|
+
"""
|
164
|
+
if inMessage is None:
|
165
|
+
inMessage = self._generate_auto_message("critical")
|
166
|
+
self._logger.critical(inMessage)
|
167
|
+
|
168
|
+
def set_session(self, inSessionName: str) -> None:
|
169
|
+
"""새로운 로깅 세션 설정 및 시작
|
170
|
+
|
171
|
+
Args:
|
172
|
+
inSessionName (str): 세션 구분용 이름
|
173
|
+
"""
|
174
|
+
# 기존 세션이 있다면 종료
|
175
|
+
if self._sessionName is not None:
|
176
|
+
self.end_session()
|
177
|
+
|
178
|
+
# 새 세션 시작
|
179
|
+
self._sessionName = inSessionName
|
180
|
+
separator_msg = f"===== {self._sessionName} 로깅 시작 ====="
|
181
|
+
self._log_separator(separator_msg)
|
182
|
+
|
183
|
+
def end_session(self) -> None:
|
184
|
+
"""현재 로깅 세션 종료 구분선 출력"""
|
185
|
+
if self._sessionName is not None:
|
186
|
+
separator_msg = f"===== {self._sessionName} 로깅 끝 ====="
|
187
|
+
self._log_separator(separator_msg)
|
188
|
+
self._sessionName = None
|
189
|
+
|
190
|
+
def close(self) -> None:
|
191
|
+
"""로거 핸들러들을 명시적으로 닫기"""
|
192
|
+
for handler in self._logger.handlers[:]:
|
193
|
+
try:
|
194
|
+
handler.close()
|
195
|
+
self._logger.removeHandler(handler)
|
196
|
+
except Exception:
|
197
|
+
pass
|
198
|
+
|
199
|
+
def log_exception(self, inException: Exception, inCustomMessage: Optional[str] = None) -> None:
|
200
|
+
"""예외 정보를 로그에 기록
|
201
|
+
|
202
|
+
Args:
|
203
|
+
inException (Exception): 기록할 예외 객체
|
204
|
+
inCustomMessage (str, optional): 사용자 정의 메시지. None인 경우 예외 메시지만 기록
|
205
|
+
"""
|
206
|
+
if inCustomMessage:
|
207
|
+
message = f"{inCustomMessage}: {inException}"
|
208
|
+
else:
|
209
|
+
message = str(inException)
|
210
|
+
|
211
|
+
self._logger.error(message)
|
212
|
+
|
213
|
+
def log_pyjallib_error(self, inError: PyJalLibError, inCustomMessage: Optional[str] = None) -> None:
|
214
|
+
"""PyJalLib 예외를 로그에 기록 (함수명 포함)
|
215
|
+
|
216
|
+
Args:
|
217
|
+
inError (PyJalLibError): PyJalLib 예외 객체
|
218
|
+
inCustomMessage (str, optional): 사용자 정의 메시지
|
219
|
+
"""
|
220
|
+
# PyJalLibError는 이미 함수명이 포함된 메시지를 반환
|
221
|
+
if inCustomMessage:
|
222
|
+
message = f"{inCustomMessage}: {inError}"
|
223
|
+
else:
|
224
|
+
message = str(inError)
|
225
|
+
|
226
|
+
self._logger.error(message)
|
227
|
+
|
228
|
+
def log_function_error(self, inFunctionName: str, inMessage: str) -> None:
|
229
|
+
"""함수명을 포함한 에러 메시지를 로그에 기록
|
230
|
+
|
231
|
+
Args:
|
232
|
+
inFunctionName (str): 함수명
|
233
|
+
inMessage (str): 에러 메시지
|
234
|
+
"""
|
235
|
+
message = f"[{inFunctionName}] {inMessage}"
|
236
|
+
self._logger.error(message)
|
237
|
+
|
238
|
+
def _log_separator(self, inMessage: str) -> None:
|
239
|
+
"""구분선 메시지를 모든 핸들러에 직접 출력"""
|
240
|
+
# 구분선은 INFO 레벨로 출력하되, 특별한 포맷 사용
|
241
|
+
separator_record = logging.LogRecord(
|
242
|
+
name=self._logger.name,
|
243
|
+
level=logging.INFO,
|
244
|
+
pathname="",
|
245
|
+
lineno=0,
|
246
|
+
msg=inMessage,
|
247
|
+
args=(),
|
248
|
+
exc_info=None
|
249
|
+
)
|
250
|
+
|
251
|
+
# 각 핸들러에 직접 전송 (포맷터 우회)
|
252
|
+
for handler in self._logger.handlers:
|
253
|
+
try:
|
254
|
+
# 핸들러 레벨 확인
|
255
|
+
if handler.level <= logging.INFO:
|
256
|
+
# 구분선만 특별한 포맷으로 출력
|
257
|
+
if hasattr(handler, 'stream'):
|
258
|
+
handler.stream.write(inMessage + "\n")
|
259
|
+
if hasattr(handler, 'flush'):
|
260
|
+
handler.flush()
|
261
|
+
except Exception:
|
262
|
+
# 핸들러 오류 시 무시
|
263
|
+
pass
|
264
|
+
|
265
|
+
def _setup_handlers(self) -> None:
|
266
|
+
"""로거에 핸들러 설정"""
|
267
|
+
# 파일 핸들러 (항상 활성화) - 날짜 기반 파일명
|
268
|
+
current_date = datetime.now().strftime("%Y%m%d")
|
269
|
+
log_filename = f"{self._logFileName}_{current_date}.log"
|
270
|
+
log_file = self._logPath / log_filename
|
271
|
+
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
272
|
+
file_handler.setLevel(self._logLevel) # 파일 핸들러도 동일한 레벨 적용
|
273
|
+
file_handler.setFormatter(self._get_formatter())
|
274
|
+
self._logger.addHandler(file_handler)
|
275
|
+
|
276
|
+
# 콘솔 핸들러 (선택사항)
|
277
|
+
if self._enableConsole:
|
278
|
+
console_handler = logging.StreamHandler()
|
279
|
+
console_handler.setLevel(self._logLevel) # 콘솔 핸들러도 동일한 레벨 적용
|
280
|
+
console_handler.setFormatter(self._get_formatter())
|
281
|
+
self._logger.addHandler(console_handler)
|
282
|
+
|
283
|
+
def _get_formatter(self) -> logging.Formatter:
|
284
|
+
"""표준 포맷터 반환"""
|
285
|
+
return logging.Formatter(
|
286
|
+
'%(asctime)s - %(levelname)s - %(message)s',
|
287
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
288
|
+
)
|
@@ -32,7 +32,11 @@ from pyjallib.max.boneChain import BoneChain
|
|
32
32
|
from pyjallib.max.twistBone import TwistBone
|
33
33
|
from pyjallib.max.groinBone import GroinBone
|
34
34
|
from pyjallib.max.autoClavicle import AutoClavicle
|
35
|
+
from pyjallib.max.shoulder import Shoulder
|
35
36
|
from pyjallib.max.volumeBone import VolumeBone
|
37
|
+
from pyjallib.max.elbow import Elbow
|
38
|
+
from pyjallib.max.wrist import Wrist
|
39
|
+
from pyjallib.max.inguinal import Inguinal
|
36
40
|
from pyjallib.max.kneeBone import KneeBone
|
37
41
|
from pyjallib.max.hip import Hip
|
38
42
|
|
@@ -65,7 +69,11 @@ __all__ = [
|
|
65
69
|
'TwistBone',
|
66
70
|
'GroinBone',
|
67
71
|
'AutoClavicle',
|
72
|
+
'Shoulder',
|
68
73
|
'VolumeBone',
|
74
|
+
'Elbow',
|
75
|
+
'Wrist',
|
76
|
+
'Inguinal',
|
69
77
|
'KneeBone',
|
70
78
|
'Hip',
|
71
79
|
'RootMotion',
|
@@ -48,6 +48,10 @@ class AutoClavicle:
|
|
48
48
|
self.bip = bipService if bipService else Bip(nameService=self.name, animService=self.anim)
|
49
49
|
|
50
50
|
self.boneSize = 2.0
|
51
|
+
self.rotTargetUpperArmPosConstExpression = ""
|
52
|
+
self.rotTargetUpperArmPosConstExpression += "local clavicleDistance = distance clavicle upperArm\n"
|
53
|
+
self.rotTargetUpperArmPosConstExpression += "local xPos = (clavicleDistance/2.0) * distanceDir * liftScale\n"
|
54
|
+
self.rotTargetUpperArmPosConstExpression += "[xPos, 0.0, 0.0]\n"
|
51
55
|
|
52
56
|
# 초기화된 결과를 저장할 변수들
|
53
57
|
self.genBones = []
|
@@ -106,37 +110,43 @@ class AutoClavicle:
|
|
106
110
|
autoClavicleBone = self.bone.create_nub_bone(autoClavicleName, 2)
|
107
111
|
autoClavicleBone.name = self.name.remove_name_part("Nub", autoClavicleBone.name)
|
108
112
|
autoClavicleBone.transform = inClavicle.transform
|
109
|
-
self.anim.move_local(autoClavicleBone, clavicleLength/2.0, 0.0, 0.0)
|
110
113
|
autoClavicleBone.parent = inClavicle
|
114
|
+
autoClvaiclePosConst = self.const.assign_pos_const_multi(autoClavicleBone, [inClavicle, inUpperArm])
|
111
115
|
genBones.append(autoClavicleBone)
|
112
116
|
|
113
117
|
# 타겟 헬퍼 포인트 생성 (쇄골과 상완용)
|
114
118
|
rotTargetClavicle = self.helper.create_point(self.name.replace_name_part("Type", autoClavicleName, self.name.get_name_part_value_by_description("Type", "Target")))
|
115
119
|
rotTargetClavicle.name = self.name.replace_name_part("Index", rotTargetClavicle.name, "0")
|
116
120
|
rotTargetClavicle.transform = inClavicle.transform
|
117
|
-
self.anim.move_local(rotTargetClavicle, clavicleLength, 0.0, 0.0)
|
118
|
-
|
119
121
|
rotTargetClavicle.parent = inClavicle
|
122
|
+
rotTargetClaviclePosConst = self.const.assign_pos_const(rotTargetClavicle, inUpperArm)
|
120
123
|
genHelpers.append(rotTargetClavicle)
|
121
124
|
|
122
125
|
rotTargetUpperArm = self.helper.create_point(self.name.replace_name_part("Type", autoClavicleName, self.name.get_name_part_value_by_description("Type", "Target")))
|
123
126
|
rotTargetUpperArm.name = self.name.add_suffix_to_real_name(rotTargetUpperArm.name, self.name._get_filtering_char(inClavicle.name) + "arm")
|
124
127
|
rotTargetUpperArm.transform = inUpperArm.transform
|
125
|
-
self.anim.move_local(rotTargetUpperArm, (clavicleLength/2.0)*liftScale, 0.0, 0.0)
|
126
|
-
|
127
128
|
rotTargetUpperArm.parent = inUpperArm
|
129
|
+
rotTargetUpperArmPosConst = self.const.assign_pos_script_controller(rotTargetUpperArm)
|
130
|
+
rotTargetUpperArmPosConst.addConstant("distanceDir", distanceDir)
|
131
|
+
rotTargetUpperArmPosConst.addConstant("liftScale", liftScale)
|
132
|
+
rotTargetUpperArmPosConst.addNode("clavicle", inClavicle)
|
133
|
+
rotTargetUpperArmPosConst.addNode("upperArm", inUpperArm)
|
134
|
+
rotTargetUpperArmPosConst.setExpression(self.rotTargetUpperArmPosConstExpression)
|
128
135
|
genHelpers.append(rotTargetUpperArm)
|
129
136
|
|
130
137
|
# 회전 헬퍼 포인트 생성
|
131
138
|
autoClavicleRotHelper = self.helper.create_point(self.name.replace_name_part("Type", autoClavicleName, self.name.get_name_part_value_by_description("Type", "Rotation")))
|
132
139
|
autoClavicleRotHelper.transform = autoClavicleBone.transform
|
133
140
|
autoClavicleRotHelper.parent = inClavicle
|
141
|
+
autoClavicleRotHelperPosConst = self.const.assign_pos_const_multi(autoClavicleRotHelper, [inClavicle, inUpperArm])
|
134
142
|
|
135
143
|
lookAtConst = self.const.assign_lookat_multi(autoClavicleRotHelper, [rotTargetClavicle, rotTargetUpperArm])
|
136
144
|
|
137
145
|
lookAtConst.upnode_world = False
|
138
146
|
lookAtConst.pickUpNode = inClavicle
|
139
147
|
lookAtConst.lookat_vector_length = 0.0
|
148
|
+
if distanceDir < 0:
|
149
|
+
lookAtConst.target_axisFlip = True
|
140
150
|
|
141
151
|
genHelpers.append(autoClavicleRotHelper)
|
142
152
|
|
@@ -175,6 +185,8 @@ class AutoClavicle:
|
|
175
185
|
# 메소드 호출 후 데이터 초기화
|
176
186
|
self.reset()
|
177
187
|
|
188
|
+
rt.redrawViews()
|
189
|
+
|
178
190
|
return BoneChain.from_result(result)
|
179
191
|
|
180
192
|
def create_bones_from_chain(self, inBoneChain: BoneChain):
|
@@ -976,8 +976,16 @@ class Bone:
|
|
976
976
|
spineDistance = rt.distance(spine3, neck)/3.0
|
977
977
|
rt.setProperty(spine4, "transform", spine3.transform)
|
978
978
|
rt.setProperty(spine5, "transform", spine3.transform)
|
979
|
+
|
979
980
|
self.anim.move_local(spine4, spineDistance, 0, 0)
|
981
|
+
spine4PosConst = self.const.assign_pos_const_multi(spine4, [spine3, neck])
|
982
|
+
spine4PosConst.setWeight(1, 100.0*(2.0/3.0))
|
983
|
+
spine4PosConst.setWeight(2, 100.0 - (100.0*(2.0/3.0)))
|
984
|
+
|
980
985
|
self.anim.move_local(spine5, spineDistance * 2, 0, 0)
|
986
|
+
spine5PosConst = self.const.assign_pos_const_multi(spine5, [spine3, neck])
|
987
|
+
spine5PosConst.setWeight(1, 100.0*(1.0/3.0))
|
988
|
+
spine5PosConst.setWeight(2, 100.0 - (100.0*(1.0/3.0)))
|
981
989
|
|
982
990
|
returnBones.append(spine4)
|
983
991
|
returnBones.append(spine5)
|
@@ -995,6 +1003,7 @@ class Bone:
|
|
995
1003
|
neckDistance = rt.distance(neck, head)/2.0
|
996
1004
|
rt.setProperty(nekc2, "transform", neck.transform)
|
997
1005
|
self.anim.move_local(nekc2, neckDistance, 0, 0)
|
1006
|
+
neck2PosConst = self.const.assign_pos_const_multi(nekc2, [neck, head])
|
998
1007
|
|
999
1008
|
returnBones.append(nekc2)
|
1000
1009
|
|
@@ -1338,4 +1347,15 @@ class Bone:
|
|
1338
1347
|
"""
|
1339
1348
|
selArray = list(rt.getCurrentSelection())
|
1340
1349
|
for item in selArray:
|
1341
|
-
self.set_freeze_length_off(item)
|
1350
|
+
self.set_freeze_length_off(item)
|
1351
|
+
|
1352
|
+
def turn_bone(self, inBone, inAngle):
|
1353
|
+
"""
|
1354
|
+
입력된 각도만큼 입력된 본의 로컬 X 축을 회전 합니다.
|
1355
|
+
|
1356
|
+
Args:
|
1357
|
+
inBone: 회전할 뼈대 객체
|
1358
|
+
inAngle: 회전할 각도
|
1359
|
+
"""
|
1360
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
1361
|
+
self.anim.rotate_local(inBone, inAngle, 0, 0, dontAffectChildren=True)
|
@@ -32,7 +32,7 @@ class Constraint:
|
|
32
32
|
self.name = nameService if nameService else Name()
|
33
33
|
self.helper = helperService if helperService else Helper(nameService=self.name) # Pass the potentially newly created nameService
|
34
34
|
|
35
|
-
def collapse(self, inObj):
|
35
|
+
def collapse(self, inObj, inUseTCBRot=False):
|
36
36
|
"""
|
37
37
|
비 Biped 객체의 트랜스폼 컨트롤러를 기본 컨트롤러로 초기화하고 현재 변환 상태 유지.
|
38
38
|
|
@@ -48,7 +48,10 @@ class Constraint:
|
|
48
48
|
|
49
49
|
# 기본 컨트롤러로 위치, 회전, 스케일 초기화
|
50
50
|
rt.setPropertyController(inObj.controller, "Position", rt.Position_XYZ())
|
51
|
-
|
51
|
+
if inUseTCBRot:
|
52
|
+
rt.setPropertyController(inObj.controller, "Rotation", rt.TCB_Rotation())
|
53
|
+
else:
|
54
|
+
rt.setPropertyController(inObj.controller, "Rotation", rt.Euler_XYZ())
|
52
55
|
rt.setPropertyController(inObj.controller, "Scale", rt.Bezier_Scale())
|
53
56
|
|
54
57
|
# 백업한 변환 상태 복원
|
@@ -473,6 +476,28 @@ class Constraint:
|
|
473
476
|
|
474
477
|
return eulerXYZ
|
475
478
|
|
479
|
+
def assign_tcb_rot(self, inObj):
|
480
|
+
"""
|
481
|
+
객체에 TCB 회전 컨트롤러를 할당.
|
482
|
+
|
483
|
+
Args:
|
484
|
+
inObj: 컨트롤러를 할당할 객체
|
485
|
+
"""
|
486
|
+
# 회전 컨트롤러가 리스트 형태가 아니면 변환
|
487
|
+
rot_controller = rt.getPropertyController(inObj.controller, "Rotation")
|
488
|
+
if rt.classOf(rot_controller) != rt.Rotation_list:
|
489
|
+
rt.setPropertyController(inObj.controller, "Rotation", rt.Rotation_list())
|
490
|
+
|
491
|
+
# 회전 리스트 컨트롤러 가져오기
|
492
|
+
rotList = self.assign_rot_list(inObj)
|
493
|
+
|
494
|
+
# TCB 회전 컨트롤러 할당
|
495
|
+
tcbRot = rt.TCB_Rotation()
|
496
|
+
rt.setPropertyController(rotList, "Available", tcbRot)
|
497
|
+
rotList.setActive(rotList.count)
|
498
|
+
|
499
|
+
return tcbRot
|
500
|
+
|
476
501
|
def get_lookat(self, inObj):
|
477
502
|
"""
|
478
503
|
객체의 LookAt 제약 컨트롤러를 찾아 반환.
|