pyjallib 0.1.8__py3-none-any.whl → 0.1.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 트위스트 뼈대 체인(Twist Bone Chain) 모듈 - 트위스트 뼈대 그룹 관리 기능 제공
6
+
7
+ 이 모듈은 TwistBone 클래스가 생성한 트위스트 뼈대들을 하나의 체인으로 관리하고
8
+ 간편하게 접근할 수 있는 인터페이스를 제공합니다. 일반적으로 캐릭터 리깅에서
9
+ 팔, 다리 등의 관절 부위에 자연스러운 회전 움직임을 구현하기 위해 사용됩니다.
10
+
11
+ TwistBoneChain 클래스는 다음과 같은 주요 기능을 제공합니다:
12
+ - 트위스트 뼈대 체인의 개별 뼈대 접근
13
+ - 체인의 첫 번째/마지막 뼈대 쉽게 가져오기
14
+ - 체인의 모든 뼈대를 한 번에 삭제하기
15
+ - 체인의 타입 정보 관리 (상체/하체)
16
+
17
+ Examples:
18
+ # 트위스트 체인 생성 및 관리 예시
19
+ from pyjallib.max import TwistBone, TwistBoneChain
20
+ from pymxs import runtime as rt
21
+
22
+ # 트위스트 뼈대를 생성할 뼈대 객체와 그 자식 객체 가져오기
23
+ parent_bone = rt.selection[0] # 예: 상완 뼈대
24
+ child_bone = parent_bone.children[0] # 예: 전완 뼈대
25
+
26
+ # TwistBone 클래스 인스턴스 생성
27
+ twist_bone = TwistBone()
28
+
29
+ # 상완(Upper) 타입의 트위스트 뼈대 체인 생성 (4개의 뼈대)
30
+ twist_result = twist_bone.create_upper_limb_bones(parent_bone, child_bone, 4)
31
+
32
+ # 생성된 결과로 TwistBoneChain 인스턴스 생성
33
+ chain = TwistBoneChain.from_twist_bone_result(twist_result)
34
+
35
+ # 체인 관리 기능 사용
36
+ first_bone = chain.get_first_bone() # 첫 번째 트위스트 뼈대
37
+ last_bone = chain.get_last_bone() # 마지막 트위스트 뼈대
38
+ middle_bone = chain.get_bone_at_index(2) # 특정 인덱스의 뼈대
39
+
40
+ # 체인 정보 확인
41
+ bone_count = chain.get_count() # 체인의 뼈대 개수
42
+ chain_type = chain.get_type() # 체인의 타입 (Upper 또는 Lower)
43
+
44
+ # 필요 없어지면 체인의 모든 뼈대 삭제
45
+ # chain.delete_all()
46
+ """
47
+
48
+ from pymxs import runtime as rt
49
+ from pyjallib.max.header import get_pyjallibmaxheader
50
+ jal = get_pyjallibmaxheader()
51
+
52
+ class TwistBoneChain:
53
+ def __init__(self, inResult):
54
+ """
55
+ 클래스 초기화.
56
+
57
+ Args:
58
+ bones: 트위스트 뼈대 체인을 구성하는 뼈대 배열 (기본값: None)
59
+ """
60
+ self.bones = inResult["Bones"]
61
+ self.type = inResult["Type"]
62
+ self.limb = inResult["Limb"]
63
+ self.child = inResult["Child"]
64
+ self.twistNum = inResult["TwistNum"]
65
+
66
+ def get_bone_at_index(self, index):
67
+ """
68
+ 지정된 인덱스의 트위스트 뼈대 가져오기
69
+
70
+ Args:
71
+ index: 가져올 뼈대의 인덱스
72
+
73
+ Returns:
74
+ 해당 인덱스의 뼈대 객체 또는 None (인덱스가 범위를 벗어난 경우)
75
+ """
76
+ if 0 <= index < len(self.bones):
77
+ return self.bones[index]
78
+ return None
79
+
80
+ def get_first_bone(self):
81
+ """
82
+ 체인의 첫 번째 트위스트 뼈대 가져오기
83
+
84
+ Returns:
85
+ 첫 번째 뼈대 객체 또는 None (체인이 비어있는 경우)
86
+ """
87
+ return self.bones[0] if self.bones else None
88
+
89
+ def get_last_bone(self):
90
+ """
91
+ 체인의 마지막 트위스트 뼈대 가져오기
92
+
93
+ Returns:
94
+ 마지막 뼈대 객체 또는 None (체인이 비어있는 경우)
95
+ """
96
+ return self.bones[-1] if self.bones else None
97
+
98
+ def get_count(self):
99
+ """
100
+ 체인의 트위스트 뼈대 개수 가져오기
101
+
102
+ Returns:
103
+ 뼈대 개수
104
+ """
105
+ return self.twistNum
106
+
107
+ def is_empty(self):
108
+ """
109
+ 체인이 비어있는지 확인
110
+
111
+ Returns:
112
+ 체인이 비어있으면 True, 아니면 False
113
+ """
114
+ return len(self.bones) == 0
115
+
116
+ def clear(self):
117
+ """체인의 모든 뼈대 제거"""
118
+ self.bones = []
119
+
120
+ def delete_all(self):
121
+ """
122
+ 체인의 모든 뼈대를 3ds Max 씬에서 삭제
123
+
124
+ Returns:
125
+ 삭제 성공 여부 (boolean)
126
+ """
127
+ if not self.bones:
128
+ return False
129
+
130
+ try:
131
+ for bone in self.bones:
132
+ rt.delete(bone)
133
+ self.clear()
134
+ return True
135
+ except:
136
+ return False
137
+
138
+ def get_type(self):
139
+ """
140
+ 트위스트 뼈대 체인의 타입을 반환합니다.
141
+
142
+ Returns:
143
+ 트위스트 뼈대 체인의 타입 ('upperArm', 'foreArm', 'thigh', 'calf', 'bend' 중 하나) 또는 None
144
+ """
145
+ return self.type
146
+
147
+ @classmethod
148
+ def from_twist_bone_result(cls, inResult):
149
+ """
150
+ TwistBone 클래스의 결과로부터 TwistBoneChain 인스턴스 생성
151
+
152
+ Args:
153
+ twist_bone_result: TwistBone 클래스의 메서드가 반환한 뼈대 배열
154
+ source_bone: 원본 뼈대 객체 (기본값: None)
155
+ type_name: 트위스트 뼈대 타입 (기본값: None)
156
+
157
+ Returns:
158
+ TwistBoneChain 인스턴스
159
+ """
160
+ chain = cls(inResult)
161
+
162
+ return chain
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 관절 부피 유지 본(Volume preserve Bone) 모듈 - 3ds Max용 관절의 부피를 유지하기 위해 추가되는 중간본들을 위한 모듈
6
+
7
+ 이 모듈은 3ds Max에서 캐릭터 애니메이션 과정에서 관절 변형 시 발생하는 부피 감소 문제를 해결하기 위한
8
+ 부피 유지 본 시스템을 제공합니다. 관절이 회전할 때 볼륨감을 자동으로 유지하는 보조 본을 생성하여
9
+ 더 자연스러운 캐릭터 애니메이션을 구현할 수 있습니다.
10
+ """
11
+
12
+ from pymxs import runtime as rt
13
+
14
+ # Import necessary service classes for default initialization
15
+ from .name import Name
16
+ from .anim import Anim
17
+ from .helper import Helper
18
+ from .bone import Bone
19
+ from .constraint import Constraint
20
+
21
+
22
+ class VolumeBone: # Updated class name to match the new file name
23
+ """
24
+ 관절 부피 유지 본(Volume preserve Bone) 클래스
25
+
26
+ 3ds Max에서 관절의 부피를 유지하기 위해 추가되는 중간본들을 위한 클래스입니다.
27
+ 이 클래스는 관절이 회전할 때 자동으로 부피감을 유지하도록 하는 보조 본 시스템을 생성하고
28
+ 관리합니다. 부모 관절과 자식 관절 사이에 부피 유지 본을 배치하여 관절 변형 시 부피 감소를
29
+ 방지하고 더 자연스러운 움직임을 구현합니다.
30
+ """
31
+ def __init__(self, nameService=None, animService=None, constraintService=None, boneService=None, helperService=None):
32
+ """
33
+ 클래스 초기화.
34
+
35
+ 필요한 서비스 객체들을 초기화하거나 외부에서 제공받습니다.
36
+ 각 서비스 객체들은 본 생성, 이름 관리, 애니메이션 제어, 제약 조건 적용 등의
37
+ 기능을 담당합니다.
38
+
39
+ Args:
40
+ nameService: 이름 처리 서비스 (제공되지 않으면 새로 생성)
41
+ animService: 애니메이션 서비스 (제공되지 않으면 새로 생성)
42
+ constraintService: 제약 서비스 (제공되지 않으면 새로 생성)
43
+ boneService: 뼈대 서비스 (제공되지 않으면 새로 생성)
44
+ helperService: 헬퍼 서비스 (제공되지 않으면 새로 생성)
45
+ """
46
+ # 서비스 인스턴스 설정 또는 생성
47
+ self.name = nameService if nameService else Name()
48
+ self.anim = animService if animService else Anim()
49
+
50
+ # 종속성이 있는 서비스들은 이미 생성된 서비스들을 전달
51
+ self.const = constraintService if constraintService else Constraint(nameService=self.name)
52
+ self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim)
53
+ self.helper = helperService if helperService else Helper(nameService=self.name)
54
+
55
+ self.rootBone = None
56
+ self.rotHelper = None
57
+ self.limb = None
58
+ self.limbParent = None
59
+ self.bones = []
60
+ self.rotAxises = []
61
+ self.transAxises = []
62
+ self.transScales = []
63
+ self.volumeSize = 5.0
64
+ self.rotScale = 0.5
65
+
66
+ self.posScriptExpression = (
67
+ "localLimbTm = limb.transform * inverse limbParent.transform\n"
68
+ "localDeltaTm = localLimbTm * inverse localRotRefTm\n"
69
+ "\n"
70
+ "q = localDeltaTm.rotation\n"
71
+ "\n"
72
+ "eulerRot = (quatToEuler q order:6)\n"
73
+ "swizzledRot = (eulerAngles eulerRot.z eulerRot.y eulerRot.x)\n"
74
+ "saturatedTwist = abs ((swizzledRot.x*axis.x + swizzledRot.y*axis.y + swizzledRot.z*axis.z)/180.0)\n"
75
+ "\n"
76
+ "trAxis * saturatedTwist * volumeSize * transScale\n"
77
+ )
78
+
79
+ def reset(self):
80
+ """
81
+ 클래스의 주요 컴포넌트들을 초기화합니다.
82
+ 서비스가 아닌 클래스 자체의 작업 데이터를 초기화하는 함수입니다.
83
+
84
+ Returns:
85
+ self: 메소드 체이닝을 위한 자기 자신 반환
86
+ """
87
+ self.rootBone = None
88
+ self.rotHelper = None
89
+ self.limb = None
90
+ self.limbParent = None
91
+ self.bones = []
92
+ self.rotAxises = []
93
+ self.transAxises = []
94
+ self.transScales = []
95
+ self.volumeSize = 5.0
96
+ self.rotScale = 0.5
97
+
98
+ return self
99
+
100
+ def create_root_bone(self, inObj, inParent, inRotScale=0.5):
101
+ if rt.isValidNode(inObj) == False or rt.isValidNode(inParent) == False:
102
+ return False
103
+
104
+ if rt.isValidNode(self.rootBone) and rt.isValidNode(self.rotHelper):
105
+ return self.rootBone
106
+
107
+ rootBoneName = inObj.name
108
+ filteringChar = self.name._get_filtering_char(rootBoneName)
109
+ rootBoneName = self.name.add_suffix_to_real_name(rootBoneName, filteringChar+"Vol"+filteringChar+"Root")
110
+
111
+ rootBone = self.bone.create_nub_bone(rootBoneName, 2)
112
+ rootBone.name = self.name.remove_name_part("Nub", rootBone.name)
113
+ if rootBone.name[0].islower():
114
+ rootBone.name = rootBone.name.lower()
115
+ rootBoneName = rootBoneName.lower()
116
+
117
+ rt.setProperty(rootBone, "transform", inObj.transform)
118
+ rootBone.parent = inObj
119
+
120
+ rotHelper = self.helper.create_point(rootBoneName)
121
+ rotHelper.name = self.name.replace_name_part("Type", rotHelper.name, self.name.get_name_part_value_by_description("Type", "Dummy"))
122
+ rt.setProperty(rotHelper, "transform", inObj.transform)
123
+ rotHelper.parent = inParent
124
+
125
+ oriConst = self.const.assign_rot_const_multi(rootBone, [inObj, rotHelper])
126
+ oriConst.setWeight(1, inRotScale * 100.0)
127
+ oriConst.setWeight(2, (1.0 - inRotScale) * 100.0)
128
+
129
+ self.rootBone = rootBone
130
+ self.rotHelper = rotHelper
131
+ self.limb = inObj
132
+ self.limbParent = inParent
133
+
134
+ return self.rootBone
135
+
136
+ def create_bone(self, inObj, inParent, inRotScale=0.5, inVolumeSize=5.0, inRotAxis="Z", inTransAxis="PosY", inTransScale=1.0, useRootBone=True, inRootBone=None):
137
+ if rt.isValidNode(inObj) == False or rt.isValidNode(inParent) == False:
138
+ return False
139
+
140
+ if useRootBone:
141
+ if rt.isValidNode(self.rootBone) == False and rt.isValidNode(self.rotHelper) == False:
142
+ return False
143
+ self.rootBone = inRootBone if inRootBone else self.create_root_bone(inObj, inParent, inRotScale)
144
+ else:
145
+ self.create_root_bone(inObj, inParent, inRotScale)
146
+
147
+ self.limb = inObj
148
+ self.limbParent = inParent
149
+
150
+ volBoneName = inObj.name
151
+ filteringChar = self.name._get_filtering_char(volBoneName)
152
+ volBoneName = self.name.add_suffix_to_real_name(volBoneName, filteringChar + "Vol" + filteringChar + inRotAxis + filteringChar+ inTransAxis)
153
+
154
+ volBone = self.bone.create_nub_bone(volBoneName, 2)
155
+ volBone.name = self.name.remove_name_part("Nub", volBone.name)
156
+ if volBone.name[0].islower():
157
+ volBone.name = volBone.name.lower()
158
+ volBoneName = volBoneName.lower()
159
+ rt.setProperty(volBone, "transform", self.rootBone.transform)
160
+
161
+ volBoneTrDir = rt.Point3(0.0, 0.0, 0.0)
162
+ if inTransAxis == "PosX":
163
+ volBoneTrDir = rt.Point3(1.0, 0.0, 0.0)
164
+ elif inTransAxis == "NegX":
165
+ volBoneTrDir = rt.Point3(-1.0, 0.0, 0.0)
166
+ elif inTransAxis == "PosY":
167
+ volBoneTrDir = rt.Point3(0.0, 1.0, 0.0)
168
+ elif inTransAxis == "NegY":
169
+ volBoneTrDir = rt.Point3(0.0, -1.0, 0.0)
170
+ elif inTransAxis == "PosZ":
171
+ volBoneTrDir = rt.Point3(0.0, 0.0, 1.0)
172
+ elif inTransAxis == "NegZ":
173
+ volBoneTrDir = rt.Point3(0.0, 0.0, -1.0)
174
+
175
+ self.anim.move_local(volBone, volBoneTrDir[0]*inVolumeSize, volBoneTrDir[1]*inVolumeSize, volBoneTrDir[2]*inVolumeSize)
176
+ volBone.parent = self.rootBone
177
+
178
+ rotAxis = rt.Point3(0.0, 0.0, 0.0)
179
+ if inRotAxis == "X":
180
+ rotAxis = rt.Point3(1.0, 0.0, 0.0)
181
+ elif inRotAxis == "Y":
182
+ rotAxis = rt.Point3(0.0, 1.0, 0.0)
183
+ elif inRotAxis == "Z":
184
+ rotAxis = rt.Point3(0.0, 0.0, 1.0)
185
+
186
+ # localRotRefTm = self.limb.transform * rt.inverse(self.limbParent.transform)
187
+ localRotRefTm = self.limb.transform * rt.inverse(self.rotHelper.transform)
188
+ volBonePosConst = self.const.assign_pos_script_controller(volBone)
189
+ volBonePosConst.addNode("limb", self.limb)
190
+ # volBonePosConst.addNode("limbParent", self.limbParent)
191
+ volBonePosConst.addNode("limbParent", self.rotHelper)
192
+ volBonePosConst.addConstant("axis", rotAxis)
193
+ volBonePosConst.addConstant("transScale", rt.Float(inTransScale))
194
+ volBonePosConst.addConstant("volumeSize", rt.Float(inVolumeSize))
195
+ volBonePosConst.addConstant("localRotRefTm", localRotRefTm)
196
+ volBonePosConst.addConstant("trAxis", volBoneTrDir)
197
+ volBonePosConst.setExpression(self.posScriptExpression)
198
+ volBonePosConst.update()
199
+
200
+ return True
201
+
202
+ def create_bones(self, inObj, inParent, inRotScale=0.5, inVolumeSize=5.0, inRotAxises=["Z"], inTransAxises=["PosY"], inTransScales=[1.0]):
203
+ """
204
+ 여러 개의 부피 유지 본을 생성합니다.
205
+
206
+ Args:
207
+ inObj: 본을 생성할 객체
208
+ inParent: 부모 객체
209
+ inRotScale: 회전 비율
210
+ inVolumeSize: 부피 크기
211
+ inRotAxises: 회전 축 리스트
212
+ inTransAxises: 변환 축 리스트
213
+ inTransScales: 변환 비율 리스트
214
+
215
+ Returns:
216
+ dict: VolumeBoneChain 생성을 위한 결과 딕셔너리
217
+ """
218
+ if rt.isValidNode(inObj) == False or rt.isValidNode(inParent) == False:
219
+ return None
220
+
221
+ if len(inRotAxises) != len(inTransAxises) or len(inRotAxises) != len(inTransScales):
222
+ return None
223
+
224
+ rootBone = self.create_root_bone(inObj, inParent, inRotScale=inRotScale)
225
+
226
+ # 볼륨 본들 생성
227
+ bones = []
228
+ for i in range(len(inRotAxises)):
229
+ self.create_bone(inObj, inParent, inRotScale, inVolumeSize, inRotAxises[i], inTransAxises[i], inTransScales[i], useRootBone=True, inRootBone=rootBone)
230
+
231
+ # 생성된 본의 이름 패턴으로 찾기
232
+ volBoneName = inObj.name
233
+ filteringChar = self.name._get_filtering_char(volBoneName)
234
+ volBoneName = self.name.add_suffix_to_real_name(volBoneName,
235
+ filteringChar + "Vol" + filteringChar + inRotAxises[i] +
236
+ filteringChar + inTransAxises[i])
237
+
238
+ if volBoneName[0].islower():
239
+ volBoneName = volBoneName.lower()
240
+
241
+ volBone = rt.getNodeByName(self.name.remove_name_part("Nub", volBoneName))
242
+ if rt.isValidNode(volBone):
243
+ bones.append(volBone)
244
+
245
+ # 클래스 변수에 결과 저장
246
+ self.rootBone = rootBone
247
+ self.limb = inObj
248
+ self.limbParent = inParent
249
+ self.bones = bones
250
+ self.rotAxises = inRotAxises.copy()
251
+ self.transAxises = inTransAxises.copy()
252
+ self.transScales = inTransScales.copy()
253
+ self.volumeSize = inVolumeSize
254
+ self.rotScale = inRotScale
255
+
256
+ # VolumeBoneChain이 필요로 하는 형태의 결과 딕셔너리 생성
257
+ result = {
258
+ "RootBone": rootBone,
259
+ "RotHelper": self.rotHelper,
260
+ "RotScale": inRotScale,
261
+ "Limb": inObj,
262
+ "LimbParent": inParent,
263
+ "Bones": bones,
264
+ "RotAxises": inRotAxises,
265
+ "TransAxises": inTransAxises,
266
+ "TransScales": inTransScales,
267
+ "VolumeSize": inVolumeSize
268
+ }
269
+
270
+ # 메소드 호출 후 데이터 초기화
271
+ self.reset()
272
+
273
+ return result