pyjallib 0.1.17__py3-none-any.whl → 0.1.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.17'
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
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, inEnableConsole: bool = True, inEnableUE5: bool = False):
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
- inEnableUE5 (bool): UE5 출력 활성화 여부 (기본값: False)
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(logging.DEBUG)
57
+ self._logger.setLevel(self._logLevel)
51
58
  self._logger.handlers.clear() # 기존 핸들러 제거
52
59
  self._setup_handlers()
53
60
 
54
- def debug(self, inMessage: str) -> None:
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"{current_date}_{self._logFileName}.log"
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',
@@ -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
@@ -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 제약 컨트롤러를 찾아 반환.