pyjallib 0.1.17__tar.gz → 0.1.20__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.
Files changed (115) hide show
  1. {pyjallib-0.1.17 → pyjallib-0.1.20}/PKG-INFO +1 -1
  2. {pyjallib-0.1.17 → pyjallib-0.1.20}/pyproject.toml +21 -21
  3. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/__init__.py +5 -4
  4. pyjallib-0.1.20/src/pyjallib/exceptions.py +75 -0
  5. pyjallib-0.1.20/src/pyjallib/logger.py +288 -0
  6. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/__init__.py +8 -0
  7. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/autoClavicle.py +17 -5
  8. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/bone.py +21 -1
  9. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/boneChain.py +2 -0
  10. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/constraint.py +27 -2
  11. pyjallib-0.1.20/src/pyjallib/max/elbow.py +105 -0
  12. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/groinBone.py +2 -0
  13. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/header.py +121 -113
  14. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/hip.py +2 -0
  15. pyjallib-0.1.20/src/pyjallib/max/inguinal.py +117 -0
  16. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/kneeBone.py +2 -0
  17. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/macro/jal_macro_bone.py +221 -8
  18. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/macro/jal_macro_constraint.py +30 -0
  19. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/mirror.py +20 -13
  20. pyjallib-0.1.20/src/pyjallib/max/shoulder.py +173 -0
  21. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/toolManager.py +22 -6
  22. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/twistBone.py +22 -19
  23. pyjallib-0.1.20/src/pyjallib/max/ui/__init__.py +10 -0
  24. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/volumeBone.py +12 -1
  25. pyjallib-0.1.20/src/pyjallib/max/wrist.py +113 -0
  26. pyjallib-0.1.20/src/pyjallib/nameToPath.py +130 -0
  27. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/perforce.py +101 -171
  28. pyjallib-0.1.20/src/pyjallib/ue5/inUnreal/animationImporter.py +206 -0
  29. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/inUnreal/baseImporter.py +10 -7
  30. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/inUnreal/skeletalMeshImporter.py +1 -1
  31. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/inUnreal/skeletonImporter.py +1 -1
  32. pyjallib-0.1.20/src/pyjallib/ue5/logger.py +241 -0
  33. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/templateProcessor.py +35 -2
  34. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/templates/__init__.py +6 -3
  35. pyjallib-0.1.20/src/pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
  36. pyjallib-0.1.20/tests/example_perforce_usage.py +110 -0
  37. pyjallib-0.1.20/tests/perforce_exception_test.py +167 -0
  38. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/perforce_logging_example.py +32 -62
  39. {pyjallib-0.1.17 → pyjallib-0.1.20}/uv.lock +1 -1
  40. pyjallib-0.1.17/src/pyjallib/logger.py +0 -201
  41. pyjallib-0.1.17/src/pyjallib/nameToPath.py +0 -113
  42. pyjallib-0.1.17/src/pyjallib/ue5/inUnreal/animationImporter.py +0 -114
  43. pyjallib-0.1.17/src/pyjallib/ue5/logger.py +0 -200
  44. {pyjallib-0.1.17 → pyjallib-0.1.20}/.gitignore +0 -0
  45. {pyjallib-0.1.17 → pyjallib-0.1.20}/.python-version +0 -0
  46. {pyjallib-0.1.17 → pyjallib-0.1.20}/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
  47. {pyjallib-0.1.17 → pyjallib-0.1.20}/PRD/PyJalLib_Mocap_Module_PRD.md +0 -0
  48. {pyjallib-0.1.17 → pyjallib-0.1.20}/PRD//353/241/234/352/271/205 /353/252/250/353/223/210 /354/240/234/354/236/221.md" +0 -0
  49. {pyjallib-0.1.17 → pyjallib-0.1.20}/README.md +0 -0
  50. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/index.html +0 -0
  51. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/max.html +0 -0
  52. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/namePart.html +0 -0
  53. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/nameToPath.html +0 -0
  54. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/naming.html +0 -0
  55. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/namingConfig.html +0 -0
  56. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/p4module.html +0 -0
  57. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/perforce.html +0 -0
  58. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib/reloadModules.html +0 -0
  59. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/pyjallib.html +0 -0
  60. {pyjallib-0.1.17 → pyjallib-0.1.20}/docs/search.js +0 -0
  61. {pyjallib-0.1.17 → pyjallib-0.1.20}/generate_docs.ps1 +0 -0
  62. {pyjallib-0.1.17 → pyjallib-0.1.20}/logs/20250702_max_fbx_export.log +0 -0
  63. {pyjallib-0.1.17 → pyjallib-0.1.20}/logs/20250702_project_specific.log +0 -0
  64. {pyjallib-0.1.17 → pyjallib-0.1.20}/logs/20250702_pyjallib.log +0 -0
  65. {pyjallib-0.1.17 → pyjallib-0.1.20}/logs/pyjallib.log +0 -0
  66. {pyjallib-0.1.17 → pyjallib-0.1.20}/ref/BIPPY.ms +0 -0
  67. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ConfigFiles/namingConfig.json +0 -0
  68. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/ConfigFiles/3DSMaxNamingConfig.json +0 -0
  69. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/ConfigFiles/Default_3DSMaxNamingConfig.json +0 -0
  70. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/align.py +0 -0
  71. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/anim.py +0 -0
  72. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/bip.py +0 -0
  73. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/fbxHandler.py +0 -0
  74. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/helper.py +0 -0
  75. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/layer.py +0 -0
  76. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/link.py +0 -0
  77. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/macro/jal_macro_align.py +0 -0
  78. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/macro/jal_macro_helper.py +0 -0
  79. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/macro/jal_macro_link.py +0 -0
  80. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/macro/jal_macro_select.py +0 -0
  81. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/mocap.py +0 -0
  82. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/morph.py +0 -0
  83. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/name.py +0 -0
  84. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/progress.py +0 -0
  85. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/rootMotion.py +0 -0
  86. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/select.py +0 -0
  87. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/skeleton.py +0 -0
  88. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/skin.py +0 -0
  89. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/max/ui/Container.py +0 -0
  90. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/namePart.py +0 -0
  91. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/naming.py +0 -0
  92. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/namingConfig.py +0 -0
  93. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/progressEvent.py +0 -0
  94. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/py.typed +0 -0
  95. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/reloadModules.py +0 -0
  96. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/ConfigFiles/UE5NamingConfig.json +0 -0
  97. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/__init__.py +0 -0
  98. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/disableInterchangeFrameWork.py +0 -0
  99. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/inUnreal/__init__.py +0 -0
  100. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/inUnreal/importerSettings.py +0 -0
  101. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/templates/animImportTemplate.py +0 -0
  102. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/templates/skeletalMeshImportTemplate.py +0 -0
  103. {pyjallib-0.1.17 → pyjallib-0.1.20}/src/pyjallib/ue5/templates/skeletonImportTemplate.py +0 -0
  104. {pyjallib-0.1.17 → pyjallib-0.1.20}/test_debug.log +0 -0
  105. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/animImportTest.py +0 -0
  106. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/animNodeTest.py +0 -0
  107. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/autoclavicleTest.py +0 -0
  108. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/fbxExportTest.py +0 -0
  109. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/globalVarTest.py +0 -0
  110. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/moduleImportTest.py +0 -0
  111. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/p4Test.py +0 -0
  112. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/skelImportTest.py +0 -0
  113. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/template_processor_test.py +0 -0
  114. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/ueImportTest.py +0 -0
  115. {pyjallib-0.1.17 → pyjallib-0.1.20}/tests/volumePreserveBoneTest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyjallib
3
- Version: 0.1.17
3
+ Version: 0.1.20
4
4
  Summary: A utility library for 3D game character development pipelines.
5
5
  Author-email: Dongseok Kim <jalnagakds@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -1,21 +1,21 @@
1
- [project]
2
- name = "pyjallib"
3
- version = "0.1.17"
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.20"
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.17'
9
+ __version__ = '0.1.20'
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)
@@ -92,6 +92,8 @@ class BoneChain:
92
92
  self.bones = []
93
93
  self.helpers = []
94
94
 
95
+ rt.redrawViews()
96
+
95
97
  return True
96
98
  except:
97
99
  return False
@@ -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
- rt.setPropertyController(inObj.controller, "Rotation", rt.Euler_XYZ())
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 제약 컨트롤러를 찾아 반환.