pyjallib 0.1.0__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 +17 -0
- pyjallib/max/__init__.py +46 -0
- pyjallib/max/align.py +112 -0
- pyjallib/max/anim.py +594 -0
- pyjallib/max/bip.py +508 -0
- pyjallib/max/bone.py +910 -0
- pyjallib/max/constraint.py +973 -0
- pyjallib/max/header.py +57 -0
- pyjallib/max/helper.py +433 -0
- pyjallib/max/layer.py +262 -0
- pyjallib/max/link.py +78 -0
- pyjallib/max/macro/jal_macro_align.py +155 -0
- pyjallib/max/macro/jal_macro_bone.py +358 -0
- pyjallib/max/macro/jal_macro_constraint.py +140 -0
- pyjallib/max/macro/jal_macro_helper.py +321 -0
- pyjallib/max/macro/jal_macro_link.py +55 -0
- pyjallib/max/macro/jal_macro_select.py +91 -0
- pyjallib/max/mirror.py +388 -0
- pyjallib/max/name.py +521 -0
- pyjallib/max/select.py +278 -0
- pyjallib/max/skin.py +996 -0
- pyjallib/max/twistBone.py +418 -0
- pyjallib/namePart.py +633 -0
- pyjallib/nameToPath.py +113 -0
- pyjallib/naming.py +1066 -0
- pyjallib/namingConfig.py +844 -0
- pyjallib/perforce.py +735 -0
- pyjallib/reloadModules.py +33 -0
- pyjallib-0.1.0.dist-info/METADATA +28 -0
- pyjallib-0.1.0.dist-info/RECORD +32 -0
- pyjallib-0.1.0.dist-info/WHEEL +5 -0
- pyjallib-0.1.0.dist-info/top_level.txt +1 -0
pyjallib/max/bone.py
ADDED
@@ -0,0 +1,910 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
뼈대(Bone) 모듈 - 3ds Max용 뼈대 생성 관련 기능 제공
|
6
|
+
원본 MAXScript의 bone.ms를 Python으로 변환하였으며, pymxs 모듈 기반으로 구현됨
|
7
|
+
"""
|
8
|
+
|
9
|
+
from dataclasses import dataclass
|
10
|
+
|
11
|
+
from pymxs import runtime as rt
|
12
|
+
from .name import Name
|
13
|
+
from .anim import Anim
|
14
|
+
from .helper import Helper
|
15
|
+
from .constraint import Constraint
|
16
|
+
|
17
|
+
|
18
|
+
class Bone:
|
19
|
+
"""
|
20
|
+
뼈대(Bone) 관련 기능을 제공하는 클래스.
|
21
|
+
MAXScript의 _Bone 구조체 개념을 Python으로 재구현한 클래스이며,
|
22
|
+
3ds Max의 기능들을 pymxs API를 통해 제어합니다.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self, nameService=None, animService=None, helperService=None, constraintService=None):
|
26
|
+
"""
|
27
|
+
클래스 초기화.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
nameService: 이름 처리 서비스 (제공되지 않으면 새로 생성)
|
31
|
+
animService: 애니메이션 서비스 (제공되지 않으면 새로 생성)
|
32
|
+
helperService: 헬퍼 객체 서비스 (제공되지 않으면 새로 생성)
|
33
|
+
constraintService: 제약 서비스 (제공되지 않으면 새로 생성)
|
34
|
+
"""
|
35
|
+
self.name = nameService if nameService else Name()
|
36
|
+
self.anim = animService if animService else Anim()
|
37
|
+
self.helper = helperService if helperService else Helper(nameService=self.name)
|
38
|
+
self.const = constraintService if constraintService else Constraint(nameService=self.name, helperService=self.helper)
|
39
|
+
|
40
|
+
def remove_ik(self, inBone):
|
41
|
+
"""
|
42
|
+
뼈대에서 IK 체인을 제거.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
inBone: IK 체인을 제거할 뼈대 객체
|
46
|
+
"""
|
47
|
+
# pos 또는 rotation 속성이 없는 경우에만 IK 체인 제거
|
48
|
+
if (not rt.isProperty(inBone, "pos")) or (not rt.isProperty(inBone, "rotation")):
|
49
|
+
rt.HDIKSys.RemoveChain(inBone)
|
50
|
+
|
51
|
+
def get_bone_assemblyHead(self, inBone):
|
52
|
+
"""
|
53
|
+
뼈대 어셈블리의 헤드를 가져옴.
|
54
|
+
|
55
|
+
Args:
|
56
|
+
inBone: 대상 뼈대 객체
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
어셈블리 헤드 또는 None
|
60
|
+
"""
|
61
|
+
tempBone = inBone
|
62
|
+
while tempBone is not None:
|
63
|
+
if tempBone.assemblyHead:
|
64
|
+
return tempBone
|
65
|
+
if not tempBone.assemblyMember:
|
66
|
+
break
|
67
|
+
tempBone = tempBone.parent
|
68
|
+
|
69
|
+
return None
|
70
|
+
|
71
|
+
def put_child_into_bone_assembly(self, inBone):
|
72
|
+
"""
|
73
|
+
자식 뼈대를 어셈블리에 추가.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
inBone: 어셈블리에 추가할 자식 뼈대
|
77
|
+
"""
|
78
|
+
if inBone.parent is not None and inBone.parent.assemblyMember:
|
79
|
+
inBone.assemblyMember = True
|
80
|
+
inBone.assemblyMemberOpen = True
|
81
|
+
|
82
|
+
def sort_bones_as_hierarchy(self, inBoneArray):
|
83
|
+
"""
|
84
|
+
뼈대 배열을 계층 구조에 따라 정렬.
|
85
|
+
|
86
|
+
Args:
|
87
|
+
inBoneArray: 정렬할 뼈대 객체 배열
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
계층 구조에 따라 정렬된 뼈대 배열
|
91
|
+
"""
|
92
|
+
# BoneLevel 구조체 정의 (Python 클래스로 구현)
|
93
|
+
@dataclass
|
94
|
+
class BoneLevel:
|
95
|
+
index: int
|
96
|
+
level: int
|
97
|
+
|
98
|
+
# 뼈대 구조체 배열 초기화
|
99
|
+
bones = []
|
100
|
+
|
101
|
+
# 뼈대 구조체 배열 채우기. 계층 수준을 0으로 초기화
|
102
|
+
for i in range(len(inBoneArray)):
|
103
|
+
bones.append(BoneLevel(i, 0))
|
104
|
+
|
105
|
+
# 뼈대 배열의 각 뼈대에 대한 계층 수준 계산
|
106
|
+
# 계층 수준은 현재 뼈대와 루트 노드 사이의 조상 수
|
107
|
+
for i in range(len(bones)):
|
108
|
+
node = inBoneArray[bones[i].index]
|
109
|
+
n = 0
|
110
|
+
while node is not None:
|
111
|
+
n += 1
|
112
|
+
node = node.parent
|
113
|
+
bones[i].level = n
|
114
|
+
|
115
|
+
# 계층 수준에 따라 뼈대 배열 정렬
|
116
|
+
bones.sort(key=lambda x: x.level)
|
117
|
+
|
118
|
+
# 정렬된 뼈대를 저장할 새 배열 준비
|
119
|
+
returnBonesArray = []
|
120
|
+
for i in range(len(inBoneArray)):
|
121
|
+
returnBonesArray.append(inBoneArray[bones[i].index])
|
122
|
+
|
123
|
+
return returnBonesArray
|
124
|
+
|
125
|
+
def correct_negative_stretch(self, bone, ask=True):
|
126
|
+
"""
|
127
|
+
뼈대의 음수 스케일 보정.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
bone: 보정할 뼈대 객체
|
131
|
+
ask: 사용자에게 확인 요청 여부 (기본값: True)
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
None
|
135
|
+
"""
|
136
|
+
axisIndex = 0
|
137
|
+
|
138
|
+
# 뼈대 축에 따라 인덱스 설정
|
139
|
+
if bone.boneAxis == rt.Name("X"):
|
140
|
+
axisIndex = 0
|
141
|
+
elif bone.boneAxis == rt.Name("Y"):
|
142
|
+
axisIndex = 1
|
143
|
+
elif bone.boneAxis == rt.Name("Z"):
|
144
|
+
axisIndex = 2
|
145
|
+
|
146
|
+
ooscale = bone.objectOffsetScale
|
147
|
+
|
148
|
+
# 음수 스케일 보정
|
149
|
+
if (ooscale[axisIndex] < 0) and ((not ask) or rt.queryBox("Correct negative scale?", title=bone.Name)):
|
150
|
+
ooscale[axisIndex] = -ooscale[axisIndex]
|
151
|
+
axisIndex = axisIndex + 2
|
152
|
+
if axisIndex > 2:
|
153
|
+
axisIndex = axisIndex - 3
|
154
|
+
ooscale[axisIndex] = -ooscale[axisIndex]
|
155
|
+
bone.objectOffsetScale = ooscale
|
156
|
+
|
157
|
+
def reset_scale_of_selected_bones(self, ask=True):
|
158
|
+
"""
|
159
|
+
선택된 뼈대들의 스케일 초기화.
|
160
|
+
|
161
|
+
Args:
|
162
|
+
ask: 음수 스케일 보정 확인 요청 여부 (기본값: True)
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
None
|
166
|
+
"""
|
167
|
+
# 선택된 객체 중 BoneGeometry 타입만 수집
|
168
|
+
bones = [item for item in rt.selection if rt.classOf(item) == rt.BoneGeometry]
|
169
|
+
|
170
|
+
# 계층 구조에 따라 뼈대 정렬
|
171
|
+
bones = self.sort_bones_as_hierarchy(rt.selection)
|
172
|
+
|
173
|
+
# 뼈대 배열의 모든 뼈대에 대해 스케일 초기화
|
174
|
+
for i in range(len(bones)):
|
175
|
+
rt.ResetScale(bones[i])
|
176
|
+
if ask:
|
177
|
+
self.correct_negative_stretch(bones[i], False)
|
178
|
+
|
179
|
+
def is_nub_bone(self, inputBone):
|
180
|
+
"""
|
181
|
+
뼈대가 Nub 뼈대인지 확인 (부모 및 자식이 없는 단일 뼈대).
|
182
|
+
|
183
|
+
Args:
|
184
|
+
inputBone: 확인할 뼈대 객체
|
185
|
+
|
186
|
+
Returns:
|
187
|
+
True: Nub 뼈대인 경우
|
188
|
+
False: 그 외의 경우
|
189
|
+
"""
|
190
|
+
if rt.classOf(inputBone) == rt.BoneGeometry:
|
191
|
+
if inputBone.parent is None and inputBone.children.count == 0:
|
192
|
+
return True
|
193
|
+
else:
|
194
|
+
return False
|
195
|
+
return False
|
196
|
+
|
197
|
+
def is_end_bone(self, inputBone):
|
198
|
+
"""
|
199
|
+
뼈대가 End 뼈대인지 확인 (부모는 있지만 자식이 없는 뼈대).
|
200
|
+
|
201
|
+
Args:
|
202
|
+
inputBone: 확인할 뼈대 객체
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
True: End 뼈대인 경우
|
206
|
+
False: 그 외의 경우
|
207
|
+
"""
|
208
|
+
if rt.classOf(inputBone) == rt.BoneGeometry:
|
209
|
+
if inputBone.parent is not None and inputBone.children.count == 0:
|
210
|
+
return True
|
211
|
+
else:
|
212
|
+
return False
|
213
|
+
return False
|
214
|
+
|
215
|
+
def create_nub_bone(self, inName, inSize):
|
216
|
+
"""
|
217
|
+
Nub 뼈대 생성.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
inName: 뼈대 이름
|
221
|
+
inSize: 뼈대 크기
|
222
|
+
|
223
|
+
Returns:
|
224
|
+
생성된 Nub 뼈대
|
225
|
+
"""
|
226
|
+
nubBone = None
|
227
|
+
|
228
|
+
# 화면 갱신 중지 상태에서 뼈대 생성
|
229
|
+
rt.disableSceneRedraw()
|
230
|
+
|
231
|
+
# 뼈대 생성 및 속성 설정
|
232
|
+
nubBone = rt.BoneSys.createBone(rt.Point3(0, 0, 0), rt.Point3(1, 0, 0), rt.Point3(0, 0, 1))
|
233
|
+
|
234
|
+
nubBone.width = inSize
|
235
|
+
nubBone.height = inSize
|
236
|
+
nubBone.taper = 90
|
237
|
+
nubBone.length = inSize
|
238
|
+
nubBone.frontfin = False
|
239
|
+
nubBone.backfin = False
|
240
|
+
nubBone.sidefins = False
|
241
|
+
nubBone.name = self.name.remove_name_part("Index", inName)
|
242
|
+
nubBone.name = self.name.replace_name_part("Nub", nubBone.name, "Nub")
|
243
|
+
|
244
|
+
# 화면 갱신 재개
|
245
|
+
rt.enableSceneRedraw()
|
246
|
+
rt.redrawViews()
|
247
|
+
|
248
|
+
return nubBone
|
249
|
+
|
250
|
+
def create_nub_bone_on_obj(self, inObj, inSize=1):
|
251
|
+
"""
|
252
|
+
객체 위치에 Nub 뼈대 생성.
|
253
|
+
|
254
|
+
Args:
|
255
|
+
inObj: 위치를 참조할 객체
|
256
|
+
inSize: 뼈대 크기 (기본값: 1)
|
257
|
+
|
258
|
+
Returns:
|
259
|
+
생성된 Nub 뼈대
|
260
|
+
"""
|
261
|
+
boneName = self.name.get_string(inObj.name)
|
262
|
+
newBone = self.create_nub_bone(boneName, inSize)
|
263
|
+
newBone.transform = inObj.transform
|
264
|
+
|
265
|
+
return newBone
|
266
|
+
|
267
|
+
def create_end_bone(self, inBone):
|
268
|
+
"""
|
269
|
+
뼈대의 끝에 End 뼈대 생성.
|
270
|
+
|
271
|
+
Args:
|
272
|
+
inBone: 부모가 될 뼈대 객체
|
273
|
+
|
274
|
+
Returns:
|
275
|
+
생성된 End 뼈대
|
276
|
+
"""
|
277
|
+
parentBone = inBone
|
278
|
+
parentTrans = parentBone.transform
|
279
|
+
parentPos = parentTrans.translation
|
280
|
+
boneName = self.name.get_string(parentBone.name)
|
281
|
+
newBone = self.create_nub_bone(boneName, parentBone.width)
|
282
|
+
|
283
|
+
newBone.transform = parentTrans
|
284
|
+
|
285
|
+
# 로컬 좌표계에서 이동
|
286
|
+
self.anim.move_local(newBone, parentBone.length, 0, 0)
|
287
|
+
|
288
|
+
newBone.parent = parentBone
|
289
|
+
self.put_child_into_bone_assembly(newBone)
|
290
|
+
|
291
|
+
# 뼈대 속성 설정
|
292
|
+
newBone.width = parentBone.width
|
293
|
+
newBone.height = parentBone.height
|
294
|
+
newBone.frontfin = False
|
295
|
+
newBone.backfin = False
|
296
|
+
newBone.sidefins = False
|
297
|
+
newBone.taper = 90
|
298
|
+
newBone.length = (parentBone.width + parentBone.height) / 2
|
299
|
+
newBone.wirecolor = parentBone.wirecolor
|
300
|
+
|
301
|
+
return newBone
|
302
|
+
|
303
|
+
def create_bone(self, inPointArray, inName, end=True, delPoint=False, parent=False, size=2, normals=None):
|
304
|
+
"""
|
305
|
+
포인트 배열을 따라 뼈대 체인 생성.
|
306
|
+
|
307
|
+
Args:
|
308
|
+
inPointArray: 뼈대 위치를 정의하는 포인트 배열
|
309
|
+
inName: 뼈대 기본 이름
|
310
|
+
end: End 뼈대 생성 여부 (기본값: True)
|
311
|
+
delPoint: 포인트 삭제 여부 (기본값: False)
|
312
|
+
parent: 부모 Nub 포인트 생성 여부 (기본값: False)
|
313
|
+
size: 뼈대 크기 (기본값: 2)
|
314
|
+
normals: 법선 벡터 배열 (기본값: None)
|
315
|
+
|
316
|
+
Returns:
|
317
|
+
생성된 뼈대 배열 또는 False (실패 시)
|
318
|
+
"""
|
319
|
+
if normals is None:
|
320
|
+
normals = []
|
321
|
+
|
322
|
+
tempBone = None
|
323
|
+
newBone = None
|
324
|
+
|
325
|
+
returnBoneArray = []
|
326
|
+
|
327
|
+
if len(inPointArray) != 1:
|
328
|
+
for i in range(len(inPointArray) - 1):
|
329
|
+
boneNum = i
|
330
|
+
|
331
|
+
if len(normals) == len(inPointArray):
|
332
|
+
xDir = rt.normalize(inPointArray[i+1].transform.position - inPointArray[i].transform.position)
|
333
|
+
zDir = rt.normalize(rt.cross(xDir, normals[i]))
|
334
|
+
newBone = rt.BoneSys.createBone(inPointArray[i].transform.position, inPointArray[i+1].transform.position, zDir)
|
335
|
+
else:
|
336
|
+
newBone = rt.BoneSys.createBone(inPointArray[i].transform.position, inPointArray[i+1].transform.position, rt.Point3(0, -1, 0))
|
337
|
+
|
338
|
+
newBone.boneFreezeLength = True
|
339
|
+
newBone.name = self.name.replace_name_part("Index", inName, str(boneNum))
|
340
|
+
newBone.height = size
|
341
|
+
newBone.width = size
|
342
|
+
newBone.frontfin = False
|
343
|
+
newBone.backfin = False
|
344
|
+
newBone.sidefins = False
|
345
|
+
|
346
|
+
returnBoneArray.append(newBone)
|
347
|
+
|
348
|
+
if tempBone is not None:
|
349
|
+
tempTm = rt.copy(newBone.transform * rt.Inverse(tempBone.transform))
|
350
|
+
localRot = rt.quatToEuler(tempTm.rotation).x
|
351
|
+
|
352
|
+
self.anim.rotate_local(newBone, -localRot, 0, 0)
|
353
|
+
|
354
|
+
newBone.parent = tempBone
|
355
|
+
tempBone = newBone
|
356
|
+
|
357
|
+
if delPoint:
|
358
|
+
for i in range(len(inPointArray)):
|
359
|
+
if (rt.classOf(inPointArray[i]) == rt.Dummy) or (rt.classOf(inPointArray[i]) == rt.ExposeTm) or (rt.classOf(inPointArray[i]) == rt.Point):
|
360
|
+
rt.delete(inPointArray[i])
|
361
|
+
|
362
|
+
if parent:
|
363
|
+
parentNubPointName = self.name.replace_type(inName, self.name.get_parent_str())
|
364
|
+
parentNubPoint = self.helper.create_point(parentNubPointName, size=size, boxToggle=True, crossToggle=True)
|
365
|
+
parentNubPoint.transform = returnBoneArray[0].transform
|
366
|
+
returnBoneArray[0].parent = parentNubPoint
|
367
|
+
|
368
|
+
rt.select(newBone)
|
369
|
+
|
370
|
+
if end:
|
371
|
+
endBone = self.create_end_bone(newBone)
|
372
|
+
returnBoneArray.append(endBone)
|
373
|
+
|
374
|
+
rt.clearSelection()
|
375
|
+
|
376
|
+
return returnBoneArray
|
377
|
+
else:
|
378
|
+
return returnBoneArray
|
379
|
+
else:
|
380
|
+
return False
|
381
|
+
|
382
|
+
def create_simple_bone(self, inLength, inName, end=True, size=1):
|
383
|
+
"""
|
384
|
+
간단한 뼈대 생성 (시작점과 끝점 지정).
|
385
|
+
|
386
|
+
Args:
|
387
|
+
inLength: 뼈대 길이
|
388
|
+
inName: 뼈대 이름
|
389
|
+
end: End 뼈대 생성 여부 (기본값: True)
|
390
|
+
size: 뼈대 크기 (기본값: 1)
|
391
|
+
|
392
|
+
Returns:
|
393
|
+
생성된 뼈대 배열
|
394
|
+
"""
|
395
|
+
startPoint = self.helper.create_point("tempStart")
|
396
|
+
endPoint = self.helper.create_point("tempEnd", pos=(inLength, 0, 0))
|
397
|
+
returnBoneArray = self.create_bone([startPoint, endPoint], inName, end=end, delPoint=True, size=size)
|
398
|
+
|
399
|
+
return returnBoneArray
|
400
|
+
|
401
|
+
def create_stretch_bone(self, inPointArray, inName, size=2):
|
402
|
+
"""
|
403
|
+
스트레치 뼈대 생성 (포인트를 따라 움직이는 뼈대).
|
404
|
+
|
405
|
+
Args:
|
406
|
+
inPointArray: 뼈대 위치를 정의하는 포인트 배열
|
407
|
+
inName: 뼈대 기본 이름
|
408
|
+
size: 뼈대 크기 (기본값: 2)
|
409
|
+
|
410
|
+
Returns:
|
411
|
+
생성된 스트레치 뼈대 배열
|
412
|
+
"""
|
413
|
+
tempBone = []
|
414
|
+
tempBone = self.create_bone(inPointArray, inName, size=size)
|
415
|
+
|
416
|
+
for i in range(len(tempBone) - 1):
|
417
|
+
self.const.assign_pos_const(tempBone[i], inPointArray[i])
|
418
|
+
self.const.assign_lookat(tempBone[i], inPointArray[i+1])
|
419
|
+
self.const.assign_pos_const(tempBone[-1], inPointArray[-1])
|
420
|
+
|
421
|
+
return tempBone
|
422
|
+
|
423
|
+
def create_simple_stretch_bone(self, inStart, inEnd, inName, squash=False, size=1):
|
424
|
+
"""
|
425
|
+
간단한 스트레치 뼈대 생성 (시작점과 끝점 지정).
|
426
|
+
|
427
|
+
Args:
|
428
|
+
inStart: 시작 포인트
|
429
|
+
inEnd: 끝 포인트
|
430
|
+
inName: 뼈대 이름
|
431
|
+
squash: 스쿼시 효과 적용 여부 (기본값: False)
|
432
|
+
size: 뼈대 크기 (기본값: 1)
|
433
|
+
|
434
|
+
Returns:
|
435
|
+
생성된 스트레치 뼈대 배열
|
436
|
+
"""
|
437
|
+
returnArray = []
|
438
|
+
returnArray = self.create_stretch_bone([inStart, inEnd], inName, size=size)
|
439
|
+
if squash:
|
440
|
+
returnArray[0].boneScaleType = rt.Name("squash")
|
441
|
+
|
442
|
+
return returnArray
|
443
|
+
|
444
|
+
def get_bone_shape(self, inBone):
|
445
|
+
"""
|
446
|
+
뼈대의 형태 속성 가져오기.
|
447
|
+
|
448
|
+
Args:
|
449
|
+
inBone: 속성을 가져올 뼈대 객체
|
450
|
+
|
451
|
+
Returns:
|
452
|
+
뼈대 형태 속성 배열
|
453
|
+
"""
|
454
|
+
returnArray = []
|
455
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
456
|
+
returnArray = [None] * 16 # 빈 배열 초기화
|
457
|
+
returnArray[0] = inBone.width
|
458
|
+
returnArray[1] = inBone.height
|
459
|
+
returnArray[2] = inBone.taper
|
460
|
+
returnArray[3] = inBone.length
|
461
|
+
returnArray[4] = inBone.sidefins
|
462
|
+
returnArray[5] = inBone.sidefinssize
|
463
|
+
returnArray[6] = inBone.sidefinsstarttaper
|
464
|
+
returnArray[7] = inBone.sidefinsendtaper
|
465
|
+
returnArray[8] = inBone.frontfin
|
466
|
+
returnArray[9] = inBone.frontfinsize
|
467
|
+
returnArray[10] = inBone.frontfinstarttaper
|
468
|
+
returnArray[11] = inBone.frontfinendtaper
|
469
|
+
returnArray[12] = inBone.backfin
|
470
|
+
returnArray[13] = inBone.backfinsize
|
471
|
+
returnArray[14] = inBone.backfinstarttaper
|
472
|
+
returnArray[15] = inBone.backfinendtaper
|
473
|
+
|
474
|
+
return returnArray
|
475
|
+
|
476
|
+
def pasete_bone_shape(self, targetBone, shapeArray):
|
477
|
+
"""
|
478
|
+
뼈대에 형태 속성 적용.
|
479
|
+
|
480
|
+
Args:
|
481
|
+
targetBone: 속성을 적용할 뼈대 객체
|
482
|
+
shapeArray: 적용할 뼈대 형태 속성 배열
|
483
|
+
|
484
|
+
Returns:
|
485
|
+
True: 성공
|
486
|
+
False: 실패
|
487
|
+
"""
|
488
|
+
if rt.classOf(targetBone) == rt.BoneGeometry:
|
489
|
+
targetBone.width = shapeArray[0]
|
490
|
+
targetBone.height = shapeArray[1]
|
491
|
+
targetBone.taper = shapeArray[2]
|
492
|
+
#targetBone.length = shapeArray[3] # 길이는 변경하지 않음
|
493
|
+
targetBone.sidefins = shapeArray[4]
|
494
|
+
targetBone.sidefinssize = shapeArray[5]
|
495
|
+
targetBone.sidefinsstarttaper = shapeArray[6]
|
496
|
+
targetBone.sidefinsendtaper = shapeArray[7]
|
497
|
+
targetBone.frontfin = shapeArray[8]
|
498
|
+
targetBone.frontfinsize = shapeArray[9]
|
499
|
+
targetBone.frontfinstarttaper = shapeArray[10]
|
500
|
+
targetBone.frontfinendtaper = shapeArray[11]
|
501
|
+
targetBone.backfin = shapeArray[12]
|
502
|
+
targetBone.backfinsize = shapeArray[13]
|
503
|
+
targetBone.backfinstarttaper = shapeArray[14]
|
504
|
+
targetBone.backfinendtaper = shapeArray[15]
|
505
|
+
|
506
|
+
if self.is_end_bone(targetBone):
|
507
|
+
targetBone.taper = 90
|
508
|
+
targetBone.length = (targetBone.width + targetBone.height) / 2
|
509
|
+
targetBone.frontfin = False
|
510
|
+
targetBone.backfin = False
|
511
|
+
targetBone.sidefins = False
|
512
|
+
|
513
|
+
return True
|
514
|
+
return False
|
515
|
+
|
516
|
+
def set_fin_on(self, inBone, side=True, front=True, back=False, inSize=2.0, inTaper=0.0):
|
517
|
+
"""
|
518
|
+
뼈대의 핀(fin) 설정 활성화.
|
519
|
+
|
520
|
+
Args:
|
521
|
+
inBone: 핀을 설정할 뼈대 객체
|
522
|
+
side: 측면 핀 활성화 여부 (기본값: True)
|
523
|
+
front: 전면 핀 활성화 여부 (기본값: True)
|
524
|
+
back: 후면 핀 활성화 여부 (기본값: False)
|
525
|
+
inSize: 핀 크기 (기본값: 2.0)
|
526
|
+
inTaper: 핀 테이퍼 (기본값: 0.0)
|
527
|
+
"""
|
528
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
529
|
+
if not self.is_end_bone(inBone):
|
530
|
+
inBone.frontfin = front
|
531
|
+
inBone.frontfinsize = inSize
|
532
|
+
inBone.frontfinstarttaper = inTaper
|
533
|
+
inBone.frontfinendtaper = inTaper
|
534
|
+
|
535
|
+
inBone.sidefins = side
|
536
|
+
inBone.sidefinssize = inSize
|
537
|
+
inBone.sidefinsstarttaper = inTaper
|
538
|
+
inBone.sidefinsendtaper = inTaper
|
539
|
+
|
540
|
+
inBone.backfin = back
|
541
|
+
inBone.backfinsize = inSize
|
542
|
+
inBone.backfinstarttaper = inTaper
|
543
|
+
inBone.backfinendtaper = inTaper
|
544
|
+
|
545
|
+
def set_fin_off(self, inBone):
|
546
|
+
"""
|
547
|
+
뼈대의 모든 핀(fin) 비활성화.
|
548
|
+
|
549
|
+
Args:
|
550
|
+
inBone: 핀을 비활성화할 뼈대 객체
|
551
|
+
"""
|
552
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
553
|
+
inBone.frontfin = False
|
554
|
+
inBone.sidefins = False
|
555
|
+
inBone.backfin = False
|
556
|
+
|
557
|
+
def set_bone_size(self, inBone, inSize):
|
558
|
+
"""
|
559
|
+
뼈대 크기 설정.
|
560
|
+
|
561
|
+
Args:
|
562
|
+
inBone: 크기를 설정할 뼈대 객체
|
563
|
+
inSize: 설정할 크기
|
564
|
+
"""
|
565
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
566
|
+
inBone.width = inSize
|
567
|
+
inBone.height = inSize
|
568
|
+
|
569
|
+
if self.is_end_bone(inBone) or self.is_nub_bone(inBone):
|
570
|
+
inBone.taper = 90
|
571
|
+
inBone.length = inSize
|
572
|
+
|
573
|
+
def set_bone_taper(self, inBone, inTaper):
|
574
|
+
"""
|
575
|
+
뼈대 테이퍼 설정.
|
576
|
+
|
577
|
+
Args:
|
578
|
+
inBone: 테이퍼를 설정할 뼈대 객체
|
579
|
+
inTaper: 설정할 테이퍼 값
|
580
|
+
"""
|
581
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
582
|
+
if not self.is_end_bone(inBone):
|
583
|
+
inBone.taper = inTaper
|
584
|
+
|
585
|
+
def delete_bones_safely(self, inBoneArray):
|
586
|
+
"""
|
587
|
+
뼈대 배열을 안전하게 삭제.
|
588
|
+
|
589
|
+
Args:
|
590
|
+
inBoneArray: 삭제할 뼈대 배열
|
591
|
+
"""
|
592
|
+
if len(inBoneArray) > 0:
|
593
|
+
for targetBone in inBoneArray:
|
594
|
+
self.const.collapse(targetBone)
|
595
|
+
targetBone.parent = None
|
596
|
+
rt.delete(targetBone)
|
597
|
+
|
598
|
+
inBoneArray.clear()
|
599
|
+
|
600
|
+
def select_first_children(self, inObj):
|
601
|
+
"""
|
602
|
+
객체의 첫 번째 자식들을 재귀적으로 선택.
|
603
|
+
|
604
|
+
Args:
|
605
|
+
inObj: 시작 객체
|
606
|
+
|
607
|
+
Returns:
|
608
|
+
True: 자식이 있는 경우
|
609
|
+
False: 자식이 없는 경우
|
610
|
+
"""
|
611
|
+
rt.selectmore(inObj)
|
612
|
+
|
613
|
+
for i in range(inObj.children.count):
|
614
|
+
if self.select_first_children(inObj.children[i]):
|
615
|
+
if inObj.children.count == 0 or inObj.children[0] is None:
|
616
|
+
return True
|
617
|
+
else:
|
618
|
+
return False
|
619
|
+
|
620
|
+
def get_every_children(self, inObj):
|
621
|
+
"""
|
622
|
+
객체의 모든 자식들을 가져옴.
|
623
|
+
|
624
|
+
Args:
|
625
|
+
inObj: 시작 객체
|
626
|
+
|
627
|
+
Returns:
|
628
|
+
자식 객체 배열
|
629
|
+
"""
|
630
|
+
children = []
|
631
|
+
|
632
|
+
if inObj.children.count != 0 and inObj.children[0] is not None:
|
633
|
+
for i in range(inObj.children.count):
|
634
|
+
children.append(inObj.children[i])
|
635
|
+
children.extend(self.get_every_children(inObj.children[i]))
|
636
|
+
|
637
|
+
return children
|
638
|
+
|
639
|
+
def select_every_children(self, inObj, includeSelf=False):
|
640
|
+
"""
|
641
|
+
객체의 모든 자식들을 선택.
|
642
|
+
|
643
|
+
Args:
|
644
|
+
inObj: 시작 객체
|
645
|
+
includeSelf: 자신도 포함할지 여부 (기본값: False)
|
646
|
+
|
647
|
+
Returns:
|
648
|
+
선택된 자식 객체 배열
|
649
|
+
"""
|
650
|
+
children = self.get_every_children(inObj)
|
651
|
+
|
652
|
+
# 자신도 포함하는 경우
|
653
|
+
if includeSelf:
|
654
|
+
children.insert(0, inObj)
|
655
|
+
|
656
|
+
rt.select(children)
|
657
|
+
|
658
|
+
def get_bone_end_position(self, inBone):
|
659
|
+
"""
|
660
|
+
뼈대 끝 위치 가져오기.
|
661
|
+
|
662
|
+
Args:
|
663
|
+
inBone: 대상 뼈대 객체
|
664
|
+
|
665
|
+
Returns:
|
666
|
+
뼈대 끝 위치 좌표
|
667
|
+
"""
|
668
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
669
|
+
return rt.Point3(inBone.length, 0, 0) * inBone.objectTransform
|
670
|
+
else:
|
671
|
+
return inBone.transform.translation
|
672
|
+
|
673
|
+
def link_skin_bone(self, inSkinBone, inOriBone):
|
674
|
+
"""
|
675
|
+
스킨 뼈대를 원본 뼈대에 연결.
|
676
|
+
|
677
|
+
Args:
|
678
|
+
inSkinBone: 연결할 스킨 뼈대
|
679
|
+
inOriBone: 원본 뼈대
|
680
|
+
"""
|
681
|
+
self.anim.save_xform(inSkinBone)
|
682
|
+
self.anim.set_xform(inSkinBone)
|
683
|
+
|
684
|
+
self.anim.save_xform(inOriBone)
|
685
|
+
self.anim.set_xform(inOriBone)
|
686
|
+
|
687
|
+
rt.setPropertyController(inSkinBone.controller, "Scale", rt.scaleXYZ())
|
688
|
+
|
689
|
+
linkConst = rt.link_constraint()
|
690
|
+
inSkinBone.controller = linkConst
|
691
|
+
|
692
|
+
self.anim.set_xform([inSkinBone], space="world")
|
693
|
+
linkConst.addTarget(inOriBone, 0)
|
694
|
+
|
695
|
+
def link_skin_bones(self, inSkinBoneArray, inOriBoneArray):
|
696
|
+
"""
|
697
|
+
스킨 뼈대 배열을 원본 뼈대 배열에 연결.
|
698
|
+
|
699
|
+
Args:
|
700
|
+
inSkinBoneArray: 연결할 스킨 뼈대 배열
|
701
|
+
inOriBoneArray: 원본 뼈대 배열
|
702
|
+
|
703
|
+
Returns:
|
704
|
+
True: 성공
|
705
|
+
False: 실패
|
706
|
+
"""
|
707
|
+
if len(inSkinBoneArray) != len(inOriBoneArray):
|
708
|
+
return False
|
709
|
+
|
710
|
+
for i in range(len(inSkinBoneArray)):
|
711
|
+
self.link_skin_bone(inSkinBoneArray[i], inOriBoneArray[i])
|
712
|
+
|
713
|
+
return True
|
714
|
+
|
715
|
+
def create_skin_bone(self, inBoneArray, skipNub=True, mesh=True, link=True, skinBoneBaseName="b"):
|
716
|
+
"""
|
717
|
+
스킨 뼈대 생성.
|
718
|
+
|
719
|
+
Args:
|
720
|
+
inBoneArray: 원본 뼈대 배열
|
721
|
+
skipNub: Nub 뼈대 건너뛰기 (기본값: True)
|
722
|
+
mesh: 메시 스냅샷 사용 (기본값: True)
|
723
|
+
link: 원본 뼈대에 연결 (기본값: True)
|
724
|
+
skinBoneBaseName: 스킨 뼈대 기본 이름 (기본값: "b")
|
725
|
+
|
726
|
+
Returns:
|
727
|
+
생성된 스킨 뼈대 배열
|
728
|
+
"""
|
729
|
+
bones = []
|
730
|
+
skinBoneFilteringChar = "_"
|
731
|
+
skinBonePushAmount = -0.02
|
732
|
+
returnBones = []
|
733
|
+
|
734
|
+
for i in range(len(inBoneArray)):
|
735
|
+
skinBoneName = self.name.replace_base(inBoneArray[i].name, skinBoneBaseName)
|
736
|
+
skinBoneName = self.name.replace_filtering_char(skinBoneName, skinBoneFilteringChar)
|
737
|
+
|
738
|
+
skinBone = self.create_nub_bone("b_TempSkin", 2)
|
739
|
+
skinBone.name = skinBoneName
|
740
|
+
skinBone.wireColor = rt.Color(255, 88, 199)
|
741
|
+
skinBone.transform = inBoneArray[i].transform
|
742
|
+
|
743
|
+
if mesh:
|
744
|
+
snapShotObj = rt.snapshot(inBoneArray[i])
|
745
|
+
rt.addModifier(snapShotObj, rt.Push())
|
746
|
+
snapShotObj.modifiers[rt.Name("Push")].Push_Value = skinBonePushAmount
|
747
|
+
rt.collapseStack(snapShotObj)
|
748
|
+
|
749
|
+
rt.addModifier(skinBone, rt.Edit_Poly())
|
750
|
+
rt.execute("max modify mode")
|
751
|
+
rt.modPanel.setCurrentObject(skinBone.modifiers[rt.Name("Edit_Poly")])
|
752
|
+
skinBone.modifiers[rt.Name("Edit_Poly")].Attach(snapShotObj, editPolyNode=skinBone)
|
753
|
+
|
754
|
+
skinBone.boneEnable = True
|
755
|
+
skinBone.renderable = False
|
756
|
+
skinBone.boneScaleType = rt.Name("none")
|
757
|
+
|
758
|
+
bones.append(skinBone)
|
759
|
+
|
760
|
+
for i in range(len(inBoneArray)):
|
761
|
+
oriParentObj = inBoneArray[i].parent
|
762
|
+
if oriParentObj is not None:
|
763
|
+
skinBoneParentObjName = self.name.replace_base(oriParentObj.name, skinBoneBaseName)
|
764
|
+
skinBoneParentObjName = self.name.replace_filtering_char(skinBoneParentObjName, skinBoneFilteringChar)
|
765
|
+
bones[i].parent = rt.getNodeByName(skinBoneParentObjName)
|
766
|
+
else:
|
767
|
+
bones[i].parent = None
|
768
|
+
|
769
|
+
if link:
|
770
|
+
self.link_skin_bones(bones, inBoneArray)
|
771
|
+
|
772
|
+
if skipNub:
|
773
|
+
for item in bones:
|
774
|
+
if not rt.matchPattern(item.name, pattern=("*" + self.name.get_nub_str())):
|
775
|
+
returnBones.append(item)
|
776
|
+
else:
|
777
|
+
rt.delete(item)
|
778
|
+
else:
|
779
|
+
returnBones = bones.copy()
|
780
|
+
|
781
|
+
bones.clear()
|
782
|
+
|
783
|
+
return returnBones
|
784
|
+
|
785
|
+
def create_skin_bone_from_bip(self, inBoneArray, skipNub=True, mesh=False, link=True, skinBoneBaseName="b"):
|
786
|
+
"""
|
787
|
+
바이페드 객체에서 스킨 뼈대 생성.
|
788
|
+
|
789
|
+
Args:
|
790
|
+
inBoneArray: 바이페드 객체 배열
|
791
|
+
skipNub: Nub 뼈대 건너뛰기 (기본값: True)
|
792
|
+
mesh: 메시 스냅샷 사용 (기본값: False)
|
793
|
+
link: 원본 뼈대에 연결 (기본값: True)
|
794
|
+
skinBoneBaseName: 스킨 뼈대 기본 이름 (기본값: "b")
|
795
|
+
|
796
|
+
Returns:
|
797
|
+
생성된 스킨 뼈대 배열
|
798
|
+
"""
|
799
|
+
# 바이페드 객체만 필터링, Twist 뼈대 제외, 루트 노드 제외
|
800
|
+
targetBones = [item for item in inBoneArray
|
801
|
+
if (rt.classOf(item) == rt.Biped_Object)
|
802
|
+
and (not rt.matchPattern(item.name, pattern="*Twist*"))
|
803
|
+
and (item != item.controller.rootNode)]
|
804
|
+
|
805
|
+
returnSkinBones = self.create_skin_bone(targetBones, skipNub=skipNub, mesh=mesh, link=link, skinBoneBaseName=skinBoneBaseName)
|
806
|
+
|
807
|
+
return returnSkinBones
|
808
|
+
|
809
|
+
def create_skin_bone_from_bip_for_unreal(self, inBoneArray, skipNub=True, mesh=False, link=True, skinBoneBaseName="b"):
|
810
|
+
"""
|
811
|
+
언리얼 엔진용 바이페드 객체에서 스킨 뼈대 생성.
|
812
|
+
|
813
|
+
Args:
|
814
|
+
inBoneArray: 바이페드 객체 배열
|
815
|
+
skipNub: Nub 뼈대 건너뛰기 (기본값: True)
|
816
|
+
mesh: 메시 스냅샷 사용 (기본값: False)
|
817
|
+
link: 원본 뼈대에 연결 (기본값: True)
|
818
|
+
skinBoneBaseName: 스킨 뼈대 기본 이름 (기본값: "b")
|
819
|
+
|
820
|
+
Returns:
|
821
|
+
생성된 스킨 뼈대 배열 또는 False (실패 시)
|
822
|
+
"""
|
823
|
+
genBones = self.create_skin_bone_from_bip(inBoneArray, skipNub=skipNub, mesh=mesh, link=link, skinBoneBaseName=skinBoneBaseName)
|
824
|
+
if len(genBones) == 0:
|
825
|
+
return False
|
826
|
+
|
827
|
+
# 언리얼 엔진용으로 특정 뼈대 회전
|
828
|
+
for item in genBones:
|
829
|
+
if rt.matchPattern(item.name, pattern="*Pelvis*"):
|
830
|
+
self.anim.rotate_local(item, 180, 0, 0)
|
831
|
+
if rt.matchPattern(item.name, pattern="*Spine*"):
|
832
|
+
self.anim.rotate_local(item, 180, 0, 0)
|
833
|
+
if rt.matchPattern(item.name, pattern="*Neck*"):
|
834
|
+
self.anim.rotate_local(item, 180, 0, 0)
|
835
|
+
if rt.matchPattern(item.name, pattern="*Head*"):
|
836
|
+
self.anim.rotate_local(item, 180, 0, 0)
|
837
|
+
|
838
|
+
return genBones
|
839
|
+
|
840
|
+
def set_bone_on(self, inBone):
|
841
|
+
"""
|
842
|
+
뼈대 활성화.
|
843
|
+
|
844
|
+
Args:
|
845
|
+
inBone: 활성화할 뼈대 객체
|
846
|
+
"""
|
847
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
848
|
+
inBone.boneEnable = True
|
849
|
+
|
850
|
+
def set_bone_off(self, inBone):
|
851
|
+
"""
|
852
|
+
뼈대 비활성화.
|
853
|
+
|
854
|
+
Args:
|
855
|
+
inBone: 비활성화할 뼈대 객체
|
856
|
+
"""
|
857
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
858
|
+
inBone.boneEnable = False
|
859
|
+
|
860
|
+
def set_bone_on_selection(self):
|
861
|
+
"""
|
862
|
+
선택된 모든 뼈대 활성화.
|
863
|
+
"""
|
864
|
+
selArray = list(rt.getCurrentSelection())
|
865
|
+
for item in selArray:
|
866
|
+
self.set_bone_on(item)
|
867
|
+
|
868
|
+
def set_bone_off_selection(self):
|
869
|
+
"""
|
870
|
+
선택된 모든 뼈대 비활성화.
|
871
|
+
"""
|
872
|
+
selArray = list(rt.getCurrentSelection())
|
873
|
+
for item in selArray:
|
874
|
+
self.set_bone_off(item)
|
875
|
+
|
876
|
+
def set_freeze_length_on(self, inBone):
|
877
|
+
"""
|
878
|
+
뼈대 길이 고정 활성화.
|
879
|
+
|
880
|
+
Args:
|
881
|
+
inBone: 길이를 고정할 뼈대 객체
|
882
|
+
"""
|
883
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
884
|
+
inBone.boneFreezeLength = True
|
885
|
+
|
886
|
+
def set_freeze_length_off(self, inBone):
|
887
|
+
"""
|
888
|
+
뼈대 길이 고정 비활성화.
|
889
|
+
|
890
|
+
Args:
|
891
|
+
inBone: 길이 고정을 해제할 뼈대 객체
|
892
|
+
"""
|
893
|
+
if rt.classOf(inBone) == rt.BoneGeometry:
|
894
|
+
inBone.boneFreezeLength = False
|
895
|
+
|
896
|
+
def set_freeze_length_on_selection(self):
|
897
|
+
"""
|
898
|
+
선택된 모든 뼈대의 길이 고정 활성화.
|
899
|
+
"""
|
900
|
+
selArray = list(rt.getCurrentSelection())
|
901
|
+
for item in selArray:
|
902
|
+
self.set_freeze_length_on(item)
|
903
|
+
|
904
|
+
def set_freeze_length_off_selection(self):
|
905
|
+
"""
|
906
|
+
선택된 모든 뼈대의 길이 고정 비활성화.
|
907
|
+
"""
|
908
|
+
selArray = list(rt.getCurrentSelection())
|
909
|
+
for item in selArray:
|
910
|
+
self.set_freeze_length_off(item)
|