pyjallib 0.1.17__py3-none-any.whl → 0.1.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyjallib/__init__.py +5 -4
- pyjallib/exceptions.py +75 -0
- pyjallib/logger.py +152 -65
- pyjallib/max/__init__.py +8 -0
- pyjallib/max/autoClavicle.py +17 -5
- pyjallib/max/bone.py +21 -1
- pyjallib/max/boneChain.py +2 -0
- pyjallib/max/constraint.py +27 -2
- pyjallib/max/elbow.py +105 -0
- pyjallib/max/groinBone.py +2 -0
- pyjallib/max/header.py +121 -113
- pyjallib/max/hip.py +2 -0
- pyjallib/max/inguinal.py +117 -0
- pyjallib/max/kneeBone.py +2 -0
- pyjallib/max/macro/jal_macro_bone.py +221 -8
- pyjallib/max/macro/jal_macro_constraint.py +30 -0
- pyjallib/max/mirror.py +20 -13
- pyjallib/max/shoulder.py +173 -0
- pyjallib/max/toolManager.py +22 -6
- pyjallib/max/twistBone.py +22 -19
- pyjallib/max/ui/__init__.py +10 -0
- pyjallib/max/volumeBone.py +12 -1
- pyjallib/max/wrist.py +113 -0
- pyjallib/nameToPath.py +78 -61
- pyjallib/perforce.py +101 -171
- pyjallib/ue5/inUnreal/animationImporter.py +111 -19
- pyjallib/ue5/inUnreal/baseImporter.py +10 -7
- pyjallib/ue5/inUnreal/skeletalMeshImporter.py +1 -1
- pyjallib/ue5/inUnreal/skeletonImporter.py +1 -1
- pyjallib/ue5/logger.py +152 -111
- pyjallib/ue5/templateProcessor.py +35 -2
- pyjallib/ue5/templates/__init__.py +6 -3
- pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
- {pyjallib-0.1.17.dist-info → pyjallib-0.1.20.dist-info}/METADATA +1 -1
- {pyjallib-0.1.17.dist-info → pyjallib-0.1.20.dist-info}/RECORD +36 -29
- {pyjallib-0.1.17.dist-info → pyjallib-0.1.20.dist-info}/WHEEL +0 -0
pyjallib/__init__.py
CHANGED
@@ -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.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
|
pyjallib/exceptions.py
ADDED
@@ -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
|
pyjallib/logger.py
CHANGED
@@ -7,15 +7,20 @@ PyJalLib 중앙 집중식 로깅 모듈
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
import logging
|
10
|
+
import inspect
|
11
|
+
import sys
|
10
12
|
from pathlib import Path
|
11
13
|
from typing import Optional
|
12
14
|
from datetime import datetime
|
13
15
|
|
16
|
+
from pyjallib.exceptions import PyJalLibError
|
17
|
+
|
14
18
|
|
15
19
|
class Logger:
|
16
20
|
"""PyJalLib 간단한 로깅 클래스"""
|
17
21
|
|
18
|
-
def __init__(self, inLogPath: Optional[str] = None, inLogFileName: Optional[str] = None,
|
22
|
+
def __init__(self, inLogPath: Optional[str] = None, inLogFileName: Optional[str] = None,
|
23
|
+
inEnableConsole: bool = True, inLogLevel: str = "debug"):
|
19
24
|
"""로거 인스턴스 초기화
|
20
25
|
|
21
26
|
Args:
|
@@ -25,7 +30,7 @@ class Logger:
|
|
25
30
|
None인 경우 기본값 "pyjallib" 사용
|
26
31
|
실제 파일명은 "YYYYMMDD_파일명.log" 형식으로 생성
|
27
32
|
inEnableConsole (bool): 콘솔 출력 활성화 여부 (기본값: True)
|
28
|
-
|
33
|
+
inLogLevel (str): 로깅 레벨 (debug, info, warning, error, critical). 기본값: "debug"
|
29
34
|
"""
|
30
35
|
# 기본 로그 경로 설정
|
31
36
|
if inLogPath is None:
|
@@ -42,33 +47,122 @@ class Logger:
|
|
42
47
|
|
43
48
|
# 출력 옵션 설정
|
44
49
|
self._enableConsole = inEnableConsole
|
45
|
-
self._enableUE5 = inEnableUE5
|
46
50
|
self._sessionName = None # 초기에는 세션 없음
|
47
51
|
|
52
|
+
# 로깅 레벨 설정
|
53
|
+
self._logLevel = self._parse_log_level(inLogLevel)
|
54
|
+
|
48
55
|
# 로거 생성 및 설정
|
49
56
|
self._logger = logging.getLogger(f"pyjallib_{id(self)}")
|
50
|
-
self._logger.setLevel(
|
57
|
+
self._logger.setLevel(self._logLevel)
|
51
58
|
self._logger.handlers.clear() # 기존 핸들러 제거
|
52
59
|
self._setup_handlers()
|
53
60
|
|
54
|
-
def
|
55
|
-
"""
|
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")
|
56
126
|
self._logger.debug(inMessage)
|
57
127
|
|
58
|
-
def info(self, inMessage: str) -> None:
|
59
|
-
"""정보 레벨 로그 메시지
|
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")
|
60
136
|
self._logger.info(inMessage)
|
61
137
|
|
62
|
-
def warning(self, inMessage: str) -> None:
|
63
|
-
"""경고 레벨 로그 메시지
|
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")
|
64
146
|
self._logger.warning(inMessage)
|
65
147
|
|
66
|
-
def error(self, inMessage: str) -> None:
|
67
|
-
"""에러 레벨 로그 메시지
|
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")
|
68
156
|
self._logger.error(inMessage)
|
69
157
|
|
70
|
-
def critical(self, inMessage: str) -> None:
|
71
|
-
"""치명적 에러 레벨 로그 메시지
|
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")
|
72
166
|
self._logger.critical(inMessage)
|
73
167
|
|
74
168
|
def set_session(self, inSessionName: str) -> None:
|
@@ -101,7 +195,46 @@ class Logger:
|
|
101
195
|
self._logger.removeHandler(handler)
|
102
196
|
except Exception:
|
103
197
|
pass
|
104
|
-
|
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
|
+
|
105
238
|
def _log_separator(self, inMessage: str) -> None:
|
106
239
|
"""구분선 메시지를 모든 핸들러에 직접 출력"""
|
107
240
|
# 구분선은 INFO 레벨로 출력하되, 특별한 포맷 사용
|
@@ -125,9 +258,6 @@ class Logger:
|
|
125
258
|
handler.stream.write(inMessage + "\n")
|
126
259
|
if hasattr(handler, 'flush'):
|
127
260
|
handler.flush()
|
128
|
-
elif isinstance(handler, _UE5LogHandler):
|
129
|
-
# UE5 핸들러의 경우 직접 emit 호출
|
130
|
-
handler.emit(separator_record)
|
131
261
|
except Exception:
|
132
262
|
# 핸들러 오류 시 무시
|
133
263
|
pass
|
@@ -136,66 +266,23 @@ class Logger:
|
|
136
266
|
"""로거에 핸들러 설정"""
|
137
267
|
# 파일 핸들러 (항상 활성화) - 날짜 기반 파일명
|
138
268
|
current_date = datetime.now().strftime("%Y%m%d")
|
139
|
-
log_filename = f"{
|
269
|
+
log_filename = f"{self._logFileName}_{current_date}.log"
|
140
270
|
log_file = self._logPath / log_filename
|
141
271
|
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
272
|
+
file_handler.setLevel(self._logLevel) # 파일 핸들러도 동일한 레벨 적용
|
142
273
|
file_handler.setFormatter(self._get_formatter())
|
143
274
|
self._logger.addHandler(file_handler)
|
144
275
|
|
145
276
|
# 콘솔 핸들러 (선택사항)
|
146
277
|
if self._enableConsole:
|
147
278
|
console_handler = logging.StreamHandler()
|
279
|
+
console_handler.setLevel(self._logLevel) # 콘솔 핸들러도 동일한 레벨 적용
|
148
280
|
console_handler.setFormatter(self._get_formatter())
|
149
281
|
self._logger.addHandler(console_handler)
|
150
282
|
|
151
|
-
# UE5 핸들러 (선택사항)
|
152
|
-
if self._enableUE5:
|
153
|
-
ue5_handler = self._create_ue5_handler()
|
154
|
-
if ue5_handler:
|
155
|
-
self._logger.addHandler(ue5_handler)
|
156
|
-
|
157
283
|
def _get_formatter(self) -> logging.Formatter:
|
158
284
|
"""표준 포맷터 반환"""
|
159
285
|
return logging.Formatter(
|
160
286
|
'%(asctime)s - %(levelname)s - %(message)s',
|
161
287
|
datefmt='%Y-%m-%d %H:%M:%S'
|
162
|
-
)
|
163
|
-
|
164
|
-
def _create_ue5_handler(self) -> Optional[logging.Handler]:
|
165
|
-
"""UE5 핸들러 생성 (기존 UE5LogHandler 코드 활용)"""
|
166
|
-
try:
|
167
|
-
return _UE5LogHandler()
|
168
|
-
except Exception:
|
169
|
-
# UE5 핸들러 생성 실패 시 None 반환
|
170
|
-
return None
|
171
|
-
|
172
|
-
|
173
|
-
class _UE5LogHandler(logging.Handler):
|
174
|
-
"""UE5 전용 로그 핸들러 - UE5의 로그 시스템과 호환되도록 설계"""
|
175
|
-
|
176
|
-
def emit(self, record):
|
177
|
-
"""로그 레코드를 UE5 로그 시스템으로 전송"""
|
178
|
-
try:
|
179
|
-
# UE5의 unreal.log 함수 사용
|
180
|
-
import unreal
|
181
|
-
|
182
|
-
# 메시지 포맷팅
|
183
|
-
message = self.format(record) if self.formatter else record.getMessage()
|
184
|
-
|
185
|
-
# 로그 레벨에 따라 적절한 UE5 로그 함수 호출
|
186
|
-
if record.levelno >= logging.ERROR:
|
187
|
-
unreal.log_error(f"[PyJalLib] {message}")
|
188
|
-
elif record.levelno >= logging.WARNING:
|
189
|
-
unreal.log_warning(f"[PyJalLib] {message}")
|
190
|
-
elif record.levelno >= logging.INFO:
|
191
|
-
unreal.log(f"[PyJalLib] {message}")
|
192
|
-
else: # DEBUG
|
193
|
-
unreal.log(f"[PyJalLib-DEBUG] {message}")
|
194
|
-
|
195
|
-
except ImportError:
|
196
|
-
# unreal 모듈이 없는 경우 표준 출력 사용
|
197
|
-
message = self.format(record) if self.formatter else record.getMessage()
|
198
|
-
print(f"[PyJalLib] {message}")
|
199
|
-
except Exception:
|
200
|
-
# 모든 예외를 무시하여 로깅 실패가 애플리케이션을 중단하지 않도록 함
|
201
|
-
pass
|
288
|
+
)
|
pyjallib/max/__init__.py
CHANGED
@@ -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',
|
pyjallib/max/autoClavicle.py
CHANGED
@@ -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):
|
pyjallib/max/bone.py
CHANGED
@@ -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)
|
pyjallib/max/boneChain.py
CHANGED
pyjallib/max/constraint.py
CHANGED
@@ -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 제약 컨트롤러를 찾아 반환.
|