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/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)